@geekmidas/cli 10.0.0-alpha.97 → 10.0.0-alpha.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{GoDaddyProvider-AqNu60WQ.mjs → GoDaddyProvider-206NguW1.mjs} +2 -2
- package/dist/{GoDaddyProvider-AqNu60WQ.mjs.map → GoDaddyProvider-206NguW1.mjs.map} +1 -1
- package/dist/{GoDaddyProvider-B4R_QFl8.cjs → GoDaddyProvider-D1aQy2i1.cjs} +2 -2
- package/dist/{GoDaddyProvider-B4R_QFl8.cjs.map → GoDaddyProvider-D1aQy2i1.cjs.map} +1 -1
- package/dist/{LegacyStateStore-B-aRqRdD.cjs → LegacyStateStore-CgJEr1G7.cjs} +3 -3
- package/dist/{LegacyStateStore-B-aRqRdD.cjs.map → LegacyStateStore-CgJEr1G7.cjs.map} +1 -1
- package/dist/{LegacyStateStore-8F05-BOo.mjs → LegacyStateStore-wmFfdzuh.mjs} +3 -3
- package/dist/{LegacyStateStore-8F05-BOo.mjs.map → LegacyStateStore-wmFfdzuh.mjs.map} +1 -1
- package/dist/{LocalStateStore-B_bRI8M7.cjs → LocalStateStore-BD14kra9.cjs} +2 -2
- package/dist/{LocalStateStore-B_bRI8M7.cjs.map → LocalStateStore-BD14kra9.cjs.map} +1 -1
- package/dist/{LocalStateStore-B_mfgcO8.mjs → LocalStateStore-C5mNp91D.mjs} +2 -2
- package/dist/{LocalStateStore-B_mfgcO8.mjs.map → LocalStateStore-C5mNp91D.mjs.map} +1 -1
- package/dist/{S3StateStore-CMN0Uzdp.mjs → S3StateStore-CEGbsDHi.mjs} +10 -9
- package/dist/S3StateStore-CEGbsDHi.mjs.map +1 -0
- package/dist/{S3StateStore-zK9zcd9f.cjs → S3StateStore-Csu_kJCy.cjs} +11 -10
- package/dist/S3StateStore-Csu_kJCy.cjs.map +1 -0
- package/dist/{SSMStateStore-Cv7VcMak.mjs → SSMStateStore-Bd62qwp4.mjs} +2 -2
- package/dist/{SSMStateStore-Cv7VcMak.mjs.map → SSMStateStore-Bd62qwp4.mjs.map} +1 -1
- package/dist/{SSMStateStore-C489DnR6.cjs → SSMStateStore-Rp21jtP5.cjs} +2 -2
- package/dist/{SSMStateStore-C489DnR6.cjs.map → SSMStateStore-Rp21jtP5.cjs.map} +1 -1
- package/dist/{add-BA5d4pA0.mjs → add-DyIEYI97.mjs} +4 -4
- package/dist/{add-BA5d4pA0.mjs.map → add-DyIEYI97.mjs.map} +1 -1
- package/dist/{add-HXzTCy_I.cjs → add-Iuo3ao5E.cjs} +5 -5
- package/dist/{add-HXzTCy_I.cjs.map → add-Iuo3ao5E.cjs.map} +1 -1
- package/dist/{aws-Dz2bYxK1.mjs → aws-8QQ8n-t6.mjs} +2 -2
- package/dist/{aws-Dz2bYxK1.mjs.map → aws-8QQ8n-t6.mjs.map} +1 -1
- package/dist/{aws-BTReGjpY.cjs → aws-DY9NqQIT.cjs} +2 -2
- package/dist/{aws-BTReGjpY.cjs.map → aws-DY9NqQIT.cjs.map} +1 -1
- package/dist/{awsStore-BWa1KNY-.mjs → awsStore-Bx5nCFnS.mjs} +8 -6
- package/dist/awsStore-Bx5nCFnS.mjs.map +1 -0
- package/dist/{awsStore-B5We7bMg.cjs → awsStore-u3ehhMoY.cjs} +8 -6
- package/dist/awsStore-u3ehhMoY.cjs.map +1 -0
- package/dist/{bundler-SX3Uy8qT.cjs → bundler-Bgoq8IyR.cjs} +5 -5
- package/dist/{bundler-SX3Uy8qT.cjs.map → bundler-Bgoq8IyR.cjs.map} +1 -1
- package/dist/{bundler-CBomFsnd.mjs → bundler-Q6Kp8b0r.mjs} +5 -5
- package/dist/{bundler-CBomFsnd.mjs.map → bundler-Q6Kp8b0r.mjs.map} +1 -1
- package/dist/{cli-D7z1lQtO.mjs → cli-B9BudUV0.mjs} +2 -4
- package/dist/cli-B9BudUV0.mjs.map +1 -0
- package/dist/{cli-1l0L9LCA.cjs → cli-W2DMRIbP.cjs} +2 -4
- package/dist/cli-W2DMRIbP.cjs.map +1 -0
- package/dist/compose-CIrFNWHb.cjs +3147 -0
- package/dist/compose-CIrFNWHb.cjs.map +1 -0
- package/dist/compose-CLsRcHkA.mjs +3052 -0
- package/dist/compose-CLsRcHkA.mjs.map +1 -0
- package/dist/{config-xIWIaDhW.d.cts → config-BVdHfj8b.d.cts} +4 -4
- package/dist/{config-D-k0gaAr.d.mts.map → config-BVdHfj8b.d.cts.map} +1 -1
- package/dist/{config-D-k0gaAr.d.mts → config-D2OqCmAM.d.mts} +4 -4
- package/dist/{config-xIWIaDhW.d.cts.map → config-D2OqCmAM.d.mts.map} +1 -1
- package/dist/config.cjs +1 -1
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +1 -1
- package/dist/deploy/public.cjs +9 -8
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +5 -16
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +5 -16
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +8 -7
- package/dist/deploy/public.mjs.map +1 -1
- package/dist/{deploy-Cu_HyWoJ.cjs → deploy-CbJMJsgX.cjs} +26 -62
- package/dist/deploy-CbJMJsgX.cjs.map +1 -0
- package/dist/{deploy-LY-20Yeb.mjs → deploy-n_IB8r6H.mjs} +17 -59
- package/dist/deploy-n_IB8r6H.mjs.map +1 -0
- package/dist/{discover-CbJCQq4J.d.mts → discover-Caf6Hnv1.d.cts} +2 -2
- package/dist/{discover-CbJCQq4J.d.mts.map → discover-Caf6Hnv1.d.cts.map} +1 -1
- package/dist/{discover-RCW5mBXb.d.cts → discover-Cd5M2jPv.d.mts} +2 -2
- package/dist/{discover-RCW5mBXb.d.cts.map → discover-Cd5M2jPv.d.mts.map} +1 -1
- package/dist/{dns-BbTOzmVp.mjs → dns-C_LPS3qV.mjs} +3 -3
- package/dist/{dns-BbTOzmVp.mjs.map → dns-C_LPS3qV.mjs.map} +1 -1
- package/dist/{dns-BFnoawgh.cjs → dns-kp8To8Qj.cjs} +3 -3
- package/dist/{dns-BFnoawgh.cjs.map → dns-kp8To8Qj.cjs.map} +1 -1
- package/dist/{dokploy-LR94sBVI.mjs → dokploy-Dl_kNVeE.mjs} +2 -2
- package/dist/{dokploy-LR94sBVI.mjs.map → dokploy-Dl_kNVeE.mjs.map} +1 -1
- package/dist/{dokploy-CKgSww82.cjs → dokploy-gRIae9Ht.cjs} +2 -2
- package/dist/{dokploy-CKgSww82.cjs.map → dokploy-gRIae9Ht.cjs.map} +1 -1
- package/dist/{encryption-C0plOf4a.mjs → encryption-C3DcVZQs.mjs} +3 -3
- package/dist/{encryption-C0plOf4a.mjs.map → encryption-C3DcVZQs.mjs.map} +1 -1
- package/dist/{encryption-CUyw-8sc.cjs → encryption-CmQvQOSe.cjs} +3 -3
- package/dist/{encryption-CUyw-8sc.cjs.map → encryption-CmQvQOSe.cjs.map} +1 -1
- package/dist/{engine-DqbqQhck.mjs → engine-DBXuhHyf.mjs} +5 -4
- package/dist/{engine-DqbqQhck.mjs.map → engine-DBXuhHyf.mjs.map} +1 -1
- package/dist/{engine-D8TeraDK.cjs → engine-Dk19Mxc4.cjs} +27 -26
- package/dist/{engine-D8TeraDK.cjs.map → engine-Dk19Mxc4.cjs.map} +1 -1
- package/dist/{github-DC4SIqV1.cjs → github-Ds2RYnSU.cjs} +43 -20
- package/dist/github-Ds2RYnSU.cjs.map +1 -0
- package/dist/{github-zgaBOQvh.mjs → github-RawRLERq.mjs} +42 -19
- package/dist/github-RawRLERq.mjs.map +1 -0
- package/dist/identity-CBXr4wNi.mjs +129 -0
- package/dist/identity-CBXr4wNi.mjs.map +1 -0
- package/dist/identity-CbLfgQNT.cjs +182 -0
- package/dist/identity-CbLfgQNT.cjs.map +1 -0
- package/dist/{index-ClqOsbBl.d.mts → index-BU4G20G6.d.cts} +2 -2
- package/dist/{index-ClqOsbBl.d.mts.map → index-BU4G20G6.d.cts.map} +1 -1
- package/dist/{index-DgOBAEl5.d.cts → index-yDbd1WcM.d.mts} +2 -2
- package/dist/{index-DgOBAEl5.d.cts.map → index-yDbd1WcM.d.mts.map} +1 -1
- package/dist/index.cjs +156 -151
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +153 -148
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-BJ8aLOWk.mjs → migrate-CZIGT3kB.mjs} +3 -3
- package/dist/{migrate-BJ8aLOWk.mjs.map → migrate-CZIGT3kB.mjs.map} +1 -1
- package/dist/migrate-CxZYzF_u.cjs +181 -0
- package/dist/migrate-CxZYzF_u.cjs.map +1 -0
- package/dist/migrate-DR1x96I4.mjs +181 -0
- package/dist/migrate-DR1x96I4.mjs.map +1 -0
- package/dist/{migrate-CWq8-s21.cjs → migrate-DjrTwWJA.cjs} +3 -3
- package/dist/{migrate-CWq8-s21.cjs.map → migrate-DjrTwWJA.cjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{projectBucket-BFp-SFee.mjs → projectBucket-BYOxRnHY.mjs} +2 -2
- package/dist/{projectBucket-BFp-SFee.mjs.map → projectBucket-BYOxRnHY.mjs.map} +1 -1
- package/dist/{projectBucket-Cho9DpSh.cjs → projectBucket-CDu5rXjp.cjs} +2 -2
- package/dist/{projectBucket-Cho9DpSh.cjs.map → projectBucket-CDu5rXjp.cjs.map} +1 -1
- package/dist/reconcile/public.d.cts +2 -2
- package/dist/reconcile/public.d.mts +2 -2
- package/dist/{resolve-6EYNqYdr.mjs → resolve-CNYbGH8g.mjs} +3 -3
- package/dist/{resolve-6EYNqYdr.mjs.map → resolve-CNYbGH8g.mjs.map} +1 -1
- package/dist/{resolve-C8VkR4oW.cjs → resolve-DC98Rl1t.cjs} +3 -3
- package/dist/{resolve-C8VkR4oW.cjs.map → resolve-DC98Rl1t.cjs.map} +1 -1
- package/dist/{rollback-BsThTeSN.mjs → rollback-BkIkE8Qe.mjs} +5 -5
- package/dist/{rollback-BsThTeSN.mjs.map → rollback-BkIkE8Qe.mjs.map} +1 -1
- package/dist/{rollback-CvGfm4DX.cjs → rollback-DBfLvC8k.cjs} +5 -5
- package/dist/{rollback-CvGfm4DX.cjs.map → rollback-DBfLvC8k.cjs.map} +1 -1
- package/dist/{runtime-CaW4QqF2.d.cts → runtime-CLz3bf07.d.mts} +2 -2
- package/dist/{runtime-CaW4QqF2.d.cts.map → runtime-CLz3bf07.d.mts.map} +1 -1
- package/dist/{runtime-K5uxp6HE.d.mts → runtime-CUWf0IUk.d.cts} +2 -2
- package/dist/{runtime-K5uxp6HE.d.mts.map → runtime-CUWf0IUk.d.cts.map} +1 -1
- package/dist/s3-BwhQk3QM.mjs +215 -0
- package/dist/s3-BwhQk3QM.mjs.map +1 -0
- package/dist/s3-DLp0V-Ox.cjs +215 -0
- package/dist/s3-DLp0V-Ox.cjs.map +1 -0
- package/dist/{sandbox-D8cfZUAA.d.cts → sandbox-CfpLWTPP.d.mts} +2 -2
- package/dist/{sandbox-B_BkfmTP.d.mts.map → sandbox-CfpLWTPP.d.mts.map} +1 -1
- package/dist/{sandbox-B_BkfmTP.d.mts → sandbox-D1XeNEJ9.d.cts} +2 -2
- package/dist/{sandbox-D8cfZUAA.d.cts.map → sandbox-D1XeNEJ9.d.cts.map} +1 -1
- package/dist/{secretsManager-C6wm-9uZ.mjs → secretsManager-CQORubp9.mjs} +2 -2
- package/dist/{secretsManager-C6wm-9uZ.mjs.map → secretsManager-CQORubp9.mjs.map} +1 -1
- package/dist/{secretsManager-Cwwgs81g.cjs → secretsManager-RAGcEDX1.cjs} +2 -2
- package/dist/{secretsManager-Cwwgs81g.cjs.map → secretsManager-RAGcEDX1.cjs.map} +1 -1
- package/dist/{seeds-BQlj9Fsg.cjs → seeds-BNHSpjNP.cjs} +22 -166
- package/dist/seeds-BNHSpjNP.cjs.map +1 -0
- package/dist/{seeds-BsJRUL1K.mjs → seeds-C4bZ8L-T.mjs} +23 -119
- package/dist/seeds-C4bZ8L-T.mjs.map +1 -0
- package/dist/{sniffer-DVsvP8s-.mjs → sniffer-BIBm9dAb.mjs} +7 -7
- package/dist/{sniffer-DVsvP8s-.mjs.map → sniffer-BIBm9dAb.mjs.map} +1 -1
- package/dist/{sniffer-CeUKUoHn.cjs → sniffer-Cylp9W2D.cjs} +7 -7
- package/dist/{sniffer-CeUKUoHn.cjs.map → sniffer-Cylp9W2D.cjs.map} +1 -1
- package/dist/{stages-DEmn7qI8.mjs → stages-CcS9vnNu.mjs} +5 -18
- package/dist/stages-CcS9vnNu.mjs.map +1 -0
- package/dist/{stages-BpDXFPGg.cjs → stages-DdBV7msu.cjs} +5 -18
- package/dist/stages-DdBV7msu.cjs.map +1 -0
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{types-4vPS_YFX.d.cts → types-BhThob0H.d.mts} +90 -21
- package/dist/types-BhThob0H.d.mts.map +1 -0
- package/dist/{types-CFsEyFb1.d.mts → types-DwoLHelT.d.cts} +91 -20
- package/dist/types-DwoLHelT.d.cts.map +1 -0
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-Bv4xROeu.mjs → workspace-D5CpOp1y.mjs} +77 -5
- package/dist/workspace-D5CpOp1y.mjs.map +1 -0
- package/dist/{workspace-C1M-S4al.cjs → workspace-DRjdu1OY.cjs} +88 -4
- package/dist/workspace-DRjdu1OY.cjs.map +1 -0
- package/package.json +15 -15
- package/dist/S3StateStore-CMN0Uzdp.mjs.map +0 -1
- package/dist/S3StateStore-zK9zcd9f.cjs.map +0 -1
- package/dist/awsStore-B5We7bMg.cjs.map +0 -1
- package/dist/awsStore-BWa1KNY-.mjs.map +0 -1
- package/dist/cli-1l0L9LCA.cjs.map +0 -1
- package/dist/cli-D7z1lQtO.mjs.map +0 -1
- package/dist/compose-C39XupJu.cjs +0 -1558
- package/dist/compose-C39XupJu.cjs.map +0 -1
- package/dist/compose-DgWrNd2k.mjs +0 -1511
- package/dist/compose-DgWrNd2k.mjs.map +0 -1
- package/dist/deploy-Cu_HyWoJ.cjs.map +0 -1
- package/dist/deploy-LY-20Yeb.mjs.map +0 -1
- package/dist/dns-Bmbhmz41.mjs +0 -44
- package/dist/dns-Bmbhmz41.mjs.map +0 -1
- package/dist/dns-Dro_vbk9.cjs +0 -55
- package/dist/dns-Dro_vbk9.cjs.map +0 -1
- package/dist/github-DC4SIqV1.cjs.map +0 -1
- package/dist/github-zgaBOQvh.mjs.map +0 -1
- package/dist/migrate-C230QeXU.mjs +0 -94
- package/dist/migrate-C230QeXU.mjs.map +0 -1
- package/dist/migrate-CsMrI7Dk.cjs +0 -94
- package/dist/migrate-CsMrI7Dk.cjs.map +0 -1
- package/dist/secrets-BTY6JhTp.cjs +0 -47
- package/dist/secrets-BTY6JhTp.cjs.map +0 -1
- package/dist/secrets-HzKNTWg9.mjs +0 -36
- package/dist/secrets-HzKNTWg9.mjs.map +0 -1
- package/dist/seeds-BQlj9Fsg.cjs.map +0 -1
- package/dist/seeds-BsJRUL1K.mjs.map +0 -1
- package/dist/stack-BQOyqaVG.mjs +0 -1201
- package/dist/stack-BQOyqaVG.mjs.map +0 -1
- package/dist/stack-Clzi40EE.cjs +0 -1308
- package/dist/stack-Clzi40EE.cjs.map +0 -1
- package/dist/stages-BpDXFPGg.cjs.map +0 -1
- package/dist/stages-DEmn7qI8.mjs.map +0 -1
- package/dist/types-4vPS_YFX.d.cts.map +0 -1
- package/dist/types-CFsEyFb1.d.mts.map +0 -1
- package/dist/workspace-Bv4xROeu.mjs.map +0 -1
- package/dist/workspace-C1M-S4al.cjs.map +0 -1
package/dist/stack-Clzi40EE.cjs
DELETED
|
@@ -1,1308 +0,0 @@
|
|
|
1
|
-
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
2
|
-
const require_errors = require("./errors-DMC4Umsb.cjs");
|
|
3
|
-
const require_derive = require("./derive-DAbo49sm.cjs");
|
|
4
|
-
const require_workspace = require("./workspace-C1M-S4al.cjs");
|
|
5
|
-
const require_seeds = require("./seeds-BQlj9Fsg.cjs");
|
|
6
|
-
const require_SubscriberGenerator = require("./SubscriberGenerator-Dvz7EzqK.cjs");
|
|
7
|
-
let node_path = require("node:path");
|
|
8
|
-
let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
9
|
-
let node_crypto = require("node:crypto");
|
|
10
|
-
let yaml = require("yaml");
|
|
11
|
-
//#region src/compose/traefik.ts
|
|
12
|
-
/**
|
|
13
|
-
* The server's shared edge, rendered for Traefik: what the edge itself runs
|
|
14
|
-
* with, and what one stack registers with it.
|
|
15
|
-
*
|
|
16
|
-
* One Traefik per server owns 80 and 443, the ACME state and the redirect to
|
|
17
|
-
* HTTPS. It is configured through its file provider alone: each stack writes
|
|
18
|
-
* one file — its routers, services and middlewares, every name prefixed with
|
|
19
|
-
* its compose project — into a directory the edge watches, and removes it
|
|
20
|
-
* when it stops. There is no Docker socket: the edge never learns what runs
|
|
21
|
-
* on the server except from those files.
|
|
22
|
-
*
|
|
23
|
-
* The edge and the stacks meet on one external Docker network. Only a
|
|
24
|
-
* stack's public services join it, each under an alias prefixed with its
|
|
25
|
-
* project (`<project>-api`), because the network is shared: a bare service
|
|
26
|
-
* name there would be answered by every stack's `api`.
|
|
27
|
-
*
|
|
28
|
-
* Pure: the documents are data, and the edge's side effects are `edge.ts`'s.
|
|
29
|
-
*/
|
|
30
|
-
/**
|
|
31
|
-
* The image, pinned: the edge is every stack's way in, so a new version is a
|
|
32
|
-
* change to this line, not whatever `latest` is on the day a server starts.
|
|
33
|
-
*/
|
|
34
|
-
const TRAEFIK_IMAGE = "traefik:v3.7.13";
|
|
35
|
-
/** The edge's compose project, one per server. */
|
|
36
|
-
const EDGE_PROJECT = "gkm-edge";
|
|
37
|
-
/** The external network the edge and every stack's public services share. */
|
|
38
|
-
const EDGE_NETWORK = "gkm-edge";
|
|
39
|
-
/** The edge's compose service. */
|
|
40
|
-
const EDGE_SERVICE = "traefik";
|
|
41
|
-
/** Inside the edge's container: what it watches, and where certificates are. */
|
|
42
|
-
const EDGE_DYNAMIC_DIR = "/etc/gkm-edge/dynamic";
|
|
43
|
-
const EDGE_CERTS_DIR = "/etc/gkm-edge/certs";
|
|
44
|
-
/** The certificate resolver every deployed route without its own certificate uses. */
|
|
45
|
-
const ACME_RESOLVER = "letsencrypt";
|
|
46
|
-
/** The internal entrypoint the edge's health check asks; never published. */
|
|
47
|
-
const PING_PORT = 8082;
|
|
48
|
-
/** A stack's service, as the edge — and the stack itself — reach it. */
|
|
49
|
-
function edgeAlias(project, service) {
|
|
50
|
-
return `${project}-${service}`;
|
|
51
|
-
}
|
|
52
|
-
/** A stack's certificate, as the edge's container reads it. */
|
|
53
|
-
function edgeCertificatePaths(project) {
|
|
54
|
-
return {
|
|
55
|
-
certFile: `${EDGE_CERTS_DIR}/${project}.crt`,
|
|
56
|
-
keyFile: `${EDGE_CERTS_DIR}/${project}.key`
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* The edge's static configuration: the entrypoints, the redirect, ACME, and
|
|
61
|
-
* the directory it watches. No API and no dashboard — nothing about the edge
|
|
62
|
-
* is reachable but the routes stacks register.
|
|
63
|
-
*/
|
|
64
|
-
function traefikStatic(ports) {
|
|
65
|
-
return {
|
|
66
|
-
global: {
|
|
67
|
-
checkNewVersion: false,
|
|
68
|
-
sendAnonymousUsage: false
|
|
69
|
-
},
|
|
70
|
-
entryPoints: {
|
|
71
|
-
web: {
|
|
72
|
-
address: ":80",
|
|
73
|
-
http: { redirections: { entryPoint: {
|
|
74
|
-
to: ports.https === 443 ? "websecure" : `:${ports.https}`,
|
|
75
|
-
scheme: "https",
|
|
76
|
-
permanent: true
|
|
77
|
-
} } }
|
|
78
|
-
},
|
|
79
|
-
websecure: { address: ":443" },
|
|
80
|
-
ping: { address: `:${PING_PORT}` }
|
|
81
|
-
},
|
|
82
|
-
ping: { entryPoint: "ping" },
|
|
83
|
-
providers: { file: {
|
|
84
|
-
directory: EDGE_DYNAMIC_DIR,
|
|
85
|
-
watch: true
|
|
86
|
-
} },
|
|
87
|
-
certificatesResolvers: { [ACME_RESOLVER]: { acme: {
|
|
88
|
-
storage: "/acme/acme.json",
|
|
89
|
-
httpChallenge: { entryPoint: "web" }
|
|
90
|
-
} } },
|
|
91
|
-
log: { level: "INFO" }
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
/** The edge's compose document. */
|
|
95
|
-
function edgeCompose(options) {
|
|
96
|
-
const { dir, ports } = options;
|
|
97
|
-
return {
|
|
98
|
-
name: EDGE_PROJECT,
|
|
99
|
-
services: { [EDGE_SERVICE]: {
|
|
100
|
-
image: TRAEFIK_IMAGE,
|
|
101
|
-
restart: "unless-stopped",
|
|
102
|
-
ports: [`${ports.https}:443`, `${ports.http}:80`],
|
|
103
|
-
volumes: [
|
|
104
|
-
`${dir}/traefik.yml:/etc/traefik/traefik.yml:ro`,
|
|
105
|
-
`${dir}/dynamic:${EDGE_DYNAMIC_DIR}:ro`,
|
|
106
|
-
`${dir}/certs:${EDGE_CERTS_DIR}:ro`,
|
|
107
|
-
"acme:/acme"
|
|
108
|
-
],
|
|
109
|
-
labels: { "dev.geekmidas.edge.config": (0, node_crypto.createHash)("sha256").update(options.staticConfig).digest("hex").slice(0, 16) },
|
|
110
|
-
networks: ["edge"],
|
|
111
|
-
healthcheck: {
|
|
112
|
-
test: [
|
|
113
|
-
"CMD",
|
|
114
|
-
"traefik",
|
|
115
|
-
"healthcheck",
|
|
116
|
-
"--ping"
|
|
117
|
-
],
|
|
118
|
-
interval: "10s",
|
|
119
|
-
timeout: "5s",
|
|
120
|
-
retries: 5
|
|
121
|
-
},
|
|
122
|
-
logging: options.logging
|
|
123
|
-
} },
|
|
124
|
-
volumes: { acme: {} },
|
|
125
|
-
networks: { edge: {
|
|
126
|
-
name: EDGE_NETWORK,
|
|
127
|
-
external: true
|
|
128
|
-
} }
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
/** A stack's dynamic configuration, as an object. */
|
|
132
|
-
function traefikDynamic(routes, options) {
|
|
133
|
-
const { project, tls } = options;
|
|
134
|
-
const name = (route, suffix) => `${project}-${route.name}${suffix ? `-${suffix}` : ""}`;
|
|
135
|
-
const url = ({ service, port }) => `http://${edgeAlias(project, service)}:${port}`;
|
|
136
|
-
const routers = {};
|
|
137
|
-
const services = {};
|
|
138
|
-
const middlewares = {};
|
|
139
|
-
const strip = `${project}-strip-gkm-headers`;
|
|
140
|
-
if (routes.length > 0) middlewares[strip] = { headers: { customRequestHeaders: { [_geekmidas_manifest.CLIENT_IP_HEADER]: "" } } };
|
|
141
|
-
for (const route of routes) {
|
|
142
|
-
const chain = [strip];
|
|
143
|
-
if (route.allow) {
|
|
144
|
-
middlewares[name(route, "allow")] = { ipAllowList: { sourceRange: [...route.allow] } };
|
|
145
|
-
chain.push(name(route, "allow"));
|
|
146
|
-
}
|
|
147
|
-
if (route.prefix) {
|
|
148
|
-
middlewares[name(route, "prefix")] = { addPrefix: { prefix: route.prefix } };
|
|
149
|
-
chain.push(name(route, "prefix"));
|
|
150
|
-
}
|
|
151
|
-
routers[name(route)] = {
|
|
152
|
-
rule: `Host(\`${route.host}\`)`,
|
|
153
|
-
entryPoints: ["websecure"],
|
|
154
|
-
service: name(route),
|
|
155
|
-
middlewares: chain,
|
|
156
|
-
tls: tls.kind === "acme" ? { certResolver: ACME_RESOLVER } : {}
|
|
157
|
-
};
|
|
158
|
-
services[name(route)] = { loadBalancer: {
|
|
159
|
-
servers: route.upstreams.map((upstream) => ({ url: url(upstream) })),
|
|
160
|
-
...route.prefix ? { passHostHeader: false } : {},
|
|
161
|
-
...route.streaming ? { responseForwarding: { flushInterval: "-1ms" } } : {},
|
|
162
|
-
...route.health ? { healthCheck: {
|
|
163
|
-
path: route.health,
|
|
164
|
-
interval: "5s",
|
|
165
|
-
timeout: "3s"
|
|
166
|
-
} } : {}
|
|
167
|
-
} };
|
|
168
|
-
}
|
|
169
|
-
return {
|
|
170
|
-
http: {
|
|
171
|
-
routers,
|
|
172
|
-
services,
|
|
173
|
-
...Object.keys(middlewares).length > 0 ? { middlewares } : {}
|
|
174
|
-
},
|
|
175
|
-
...tls.kind === "files" ? { tls: { certificates: [{
|
|
176
|
-
certFile: tls.certFile,
|
|
177
|
-
keyFile: tls.keyFile
|
|
178
|
-
}] } } : {}
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
/** A stack's dynamic configuration file, as written to the edge's directory. */
|
|
182
|
-
function traefikDynamicFile(routes, options) {
|
|
183
|
-
return `# Generated by gkm compose for ${options.project} — do not edit.
|
|
184
|
-
# The shared edge (${EDGE_PROJECT}) reads every file in this directory; this
|
|
185
|
-
# one is removed by gkm compose --down.
|
|
186
|
-
${yaml$1(traefikDynamic(routes, options))}`;
|
|
187
|
-
}
|
|
188
|
-
/** The edge's static configuration file. */
|
|
189
|
-
function traefikStaticFile(ports) {
|
|
190
|
-
return `# Generated by gkm compose — the server's shared edge. Do not edit.
|
|
191
|
-
${yaml$1(traefikStatic(ports))}`;
|
|
192
|
-
}
|
|
193
|
-
/** The edge's compose file. */
|
|
194
|
-
function edgeComposeFile(options) {
|
|
195
|
-
return `# Generated by gkm compose — the server's shared edge. Do not edit.
|
|
196
|
-
#
|
|
197
|
-
# Started by the first stack that runs with proxy: 'traefik', and left
|
|
198
|
-
# running by gkm compose --down. Stop it, once nothing uses it, with:
|
|
199
|
-
#
|
|
200
|
-
# docker compose -p ${EDGE_PROJECT} -f ${options.dir}/docker-compose.yml down
|
|
201
|
-
${yaml$1(edgeCompose(options))}`;
|
|
202
|
-
}
|
|
203
|
-
function yaml$1(value) {
|
|
204
|
-
return (0, yaml.stringify)(value, {
|
|
205
|
-
lineWidth: 0,
|
|
206
|
-
aliasDuplicateObjects: false
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
//#endregion
|
|
210
|
-
//#region src/compose/images.ts
|
|
211
|
-
/**
|
|
212
|
-
* Which image each app runs — and, for a tag, whether every one exists.
|
|
213
|
-
*
|
|
214
|
-
* A tag is a release's identity: `--tag=v1.4.0` means the images CI pushed as
|
|
215
|
-
* v1.4.0, and nothing else. So a stack is never half rolled out: every app's
|
|
216
|
-
* image is asked of the registry before anything is pulled or the running
|
|
217
|
-
* stack is touched, and one missing image stops the whole run naming every
|
|
218
|
-
* one that is missing. Nor is a missing image quietly built instead — a build
|
|
219
|
-
* of whatever is checked out here is not the release the tag names.
|
|
220
|
-
*/
|
|
221
|
-
/**
|
|
222
|
-
* The tag a site's image carries for a release.
|
|
223
|
-
*
|
|
224
|
-
* A site's public URLs are inlined when it is built, so one bundle serves one
|
|
225
|
-
* stage: `v1.4.0-production` and `v1.4.0-staging` are two images of the same
|
|
226
|
-
* commit. A backend reads its URLs at runtime, so one image serves every stage
|
|
227
|
-
* and its tag is the release's alone.
|
|
228
|
-
*/
|
|
229
|
-
function siteTag(tag, stage) {
|
|
230
|
-
return `${tag}-${stage}`;
|
|
231
|
-
}
|
|
232
|
-
/** Images a tag names that the registry does not have. */
|
|
233
|
-
var ImageTagNotFound = class extends require_errors.GkmError {
|
|
234
|
-
tag;
|
|
235
|
-
refs;
|
|
236
|
-
constructor(tag, refs) {
|
|
237
|
-
super(`The registry has no image for ${refs.length === 1 ? "this app" : `${refs.length} apps`} at '${tag}':\n` + refs.map((ref) => ` - ${ref}`).join("\n") + `\nNothing was pulled or started. Push '${tag}' for every app from CI, pick a tag that exists, or run without --tag to build from this checkout.`);
|
|
238
|
-
this.tag = tag;
|
|
239
|
-
this.refs = refs;
|
|
240
|
-
this.name = "ImageTagNotFound";
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
/** The registry did not answer, or did not let this machine in. */
|
|
244
|
-
var RegistryUnreachable = class extends require_errors.GkmError {
|
|
245
|
-
ref;
|
|
246
|
-
detail;
|
|
247
|
-
constructor(ref, detail) {
|
|
248
|
-
super(`Could not ask the registry about ${ref}: ${detail || "no answer"}\nNothing was pulled or started. Check the registry is reachable and that this machine is logged in to it (\`docker login <registry>\`).`);
|
|
249
|
-
this.ref = ref;
|
|
250
|
-
this.detail = detail;
|
|
251
|
-
this.name = "RegistryUnreachable";
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
/**
|
|
255
|
-
* Assert every image exists, before anything else happens.
|
|
256
|
-
*
|
|
257
|
-
* Asked all at once and reported all at once: a release missing two images is
|
|
258
|
-
* one problem with two names, and finding the second only after fixing the
|
|
259
|
-
* first is a slower way to learn the same thing.
|
|
260
|
-
*
|
|
261
|
-
* @throws {RegistryUnreachable} when the registry cannot say
|
|
262
|
-
* @throws {ImageTagNotFound} listing every missing image
|
|
263
|
-
*/
|
|
264
|
-
async function assertImagesExist(docker, tag, images) {
|
|
265
|
-
const lookups = await Promise.all(images.map((image) => docker.lookup(image.ref)));
|
|
266
|
-
const unreachable = lookups.find((lookup) => lookup.status === "unreachable");
|
|
267
|
-
if (unreachable?.status === "unreachable") throw new RegistryUnreachable(unreachable.ref, unreachable.detail);
|
|
268
|
-
const missing = lookups.filter((lookup) => lookup.status === "missing").map((lookup) => lookup.ref);
|
|
269
|
-
if (missing.length > 0) throw new ImageTagNotFound(tag, missing);
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* A push or a pull with no `deploy.registry` to push to or pull from.
|
|
273
|
-
*
|
|
274
|
-
* Without one an image is named `<namespace>/<project>-<app>:<tag>`, which
|
|
275
|
-
* Docker resolves to Docker Hub — so a release would be pushed to, or pulled
|
|
276
|
-
* from, somebody else's account. It is never a fallback.
|
|
277
|
-
*/
|
|
278
|
-
var RegistryRequired = class extends require_errors.GkmError {
|
|
279
|
-
operation;
|
|
280
|
-
ref;
|
|
281
|
-
constructor(operation, ref) {
|
|
282
|
-
super(`${operation === "push" ? "Pushing" : "Pulling"} images needs a registry, and gkm.config.ts sets no deploy.registry: '${ref}' would be ${operation === "push" ? "pushed to" : "pulled from"} Docker Hub. Set deploy.registry (e.g. registry: 'ghcr.io/acme') in gkm.config.ts.` + (operation === "pull" ? " To run images built here instead, pass --build." : ""));
|
|
283
|
-
this.operation = operation;
|
|
284
|
-
this.ref = ref;
|
|
285
|
-
this.name = "RegistryRequired";
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
/** An image pinned to its digest: `<ref>@sha256:…`. */
|
|
289
|
-
function pinnedRef(ref, digest) {
|
|
290
|
-
return `${ref.split("@")[0]}@${digest}`;
|
|
291
|
-
}
|
|
292
|
-
/** A digests file with no entry for an app the stack runs. */
|
|
293
|
-
var ImageDigestMissing = class extends require_errors.GkmError {
|
|
294
|
-
file;
|
|
295
|
-
apps;
|
|
296
|
-
constructor(file, apps) {
|
|
297
|
-
super(`${file} pins no digest for ${apps.join(", ")}. Use the file the same release's \`gkm compose --build --push --digests-file\` wrote, or run without --digests-file to pull by tag.`);
|
|
298
|
-
this.file = file;
|
|
299
|
-
this.apps = apps;
|
|
300
|
-
this.name = "ImageDigestMissing";
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
/** A digests file entry that is not this app's image. */
|
|
304
|
-
var ImageDigestMismatch = class extends require_errors.GkmError {
|
|
305
|
-
app;
|
|
306
|
-
expected;
|
|
307
|
-
found;
|
|
308
|
-
constructor(app, expected, found) {
|
|
309
|
-
super(`The digests file pins '${app}' to ${found}, but this release runs ${expected}. It was written for another registry, project, stage or tag; use the file this stage's push of this tag wrote.`);
|
|
310
|
-
this.app = app;
|
|
311
|
-
this.expected = expected;
|
|
312
|
-
this.found = found;
|
|
313
|
-
this.name = "ImageDigestMismatch";
|
|
314
|
-
}
|
|
315
|
-
};
|
|
316
|
-
/** A digests file that is not a JSON object of app to pinned ref. */
|
|
317
|
-
var ImageDigestsInvalid = class extends require_errors.GkmError {
|
|
318
|
-
file;
|
|
319
|
-
detail;
|
|
320
|
-
constructor(file, detail) {
|
|
321
|
-
super(`${file} is not a digests file (${detail}). It should be the JSON object \`gkm compose --build --push --digests-file\` writes: { "api": "<registry>/<image>:<tag>@sha256:…" }.`);
|
|
322
|
-
this.file = file;
|
|
323
|
-
this.detail = detail;
|
|
324
|
-
this.name = "ImageDigestsInvalid";
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
/** Parse a digests file's content, checking its shape. */
|
|
328
|
-
function parseDigests(file, content) {
|
|
329
|
-
let parsed;
|
|
330
|
-
try {
|
|
331
|
-
parsed = JSON.parse(content);
|
|
332
|
-
} catch (error) {
|
|
333
|
-
throw new ImageDigestsInvalid(file, error instanceof Error ? error.message : String(error));
|
|
334
|
-
}
|
|
335
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new ImageDigestsInvalid(file, "not a JSON object");
|
|
336
|
-
for (const [app, value] of Object.entries(parsed)) if (typeof value !== "string" || !/@sha256:[0-9a-f]{64}$/.test(value)) throw new ImageDigestsInvalid(file, `'${app}' is not an <image>:<tag>@sha256:… ref`);
|
|
337
|
-
return parsed;
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Each image pinned to the digest the file names for it.
|
|
341
|
-
*
|
|
342
|
-
* @throws {ImageDigestMissing} naming every app the file has no entry for
|
|
343
|
-
* @throws {ImageDigestMismatch} when an entry is another image or tag
|
|
344
|
-
*/
|
|
345
|
-
function pinImages(file, digests, images) {
|
|
346
|
-
const missing = images.filter((image) => !digests[image.app]).map((image) => image.app);
|
|
347
|
-
if (missing.length > 0) throw new ImageDigestMissing(file, missing);
|
|
348
|
-
return images.map((image) => {
|
|
349
|
-
const pinned = digests[image.app];
|
|
350
|
-
if (pinned.split("@")[0] !== image.ref) throw new ImageDigestMismatch(image.app, image.ref, pinned);
|
|
351
|
-
return {
|
|
352
|
-
...image,
|
|
353
|
-
ref: pinned
|
|
354
|
-
};
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
//#endregion
|
|
358
|
-
//#region src/compose/caddyfile.ts
|
|
359
|
-
/**
|
|
360
|
-
* The stack's own edge, rendered for Caddy: one site block per route.
|
|
361
|
-
*
|
|
362
|
-
* Deployed, each host is the stage's real domain and Caddy gets it a
|
|
363
|
-
* certificate from Let's Encrypt on first request — the same automatic HTTPS
|
|
364
|
-
* it does anywhere, with nothing to configure but the name — or serves the
|
|
365
|
-
* stage's own certificate (`deploy.compose.tls`). Locally the hosts are
|
|
366
|
-
* `*.localhost` and the certificates come from Caddy's own CA, as the `gkm
|
|
367
|
-
* dev` edge does, so a local stack is `https://` like a deployed one.
|
|
368
|
-
*
|
|
369
|
-
* Pure: the routes are data (`routes.ts`), and rendering them is one string.
|
|
370
|
-
*/
|
|
371
|
-
/** Where Caddy reads a stage's own certificate, inside its container. */
|
|
372
|
-
const CADDY_TLS_DIR = "/etc/caddy/tls";
|
|
373
|
-
/** An upstream on the stack's own network: its service name. */
|
|
374
|
-
function address({ service, port }) {
|
|
375
|
-
return `${service}:${port}`;
|
|
376
|
-
}
|
|
377
|
-
/** The Caddyfile for a stack's edge. */
|
|
378
|
-
function edgeCaddyfile(routes, options) {
|
|
379
|
-
const { tls } = options;
|
|
380
|
-
const global = tls.kind === "internal" ? `{
|
|
381
|
-
# No ACME and no public DNS: the CA is Caddy's own and the names resolve
|
|
382
|
-
# to this machine.
|
|
383
|
-
local_certs
|
|
384
|
-
auto_https disable_redirects
|
|
385
|
-
}` : options.email ? `{
|
|
386
|
-
email ${options.email}
|
|
387
|
-
}` : "";
|
|
388
|
-
const certificate = tls.kind === "internal" ? `
|
|
389
|
-
tls internal
|
|
390
|
-
` : tls.kind === "files" ? `
|
|
391
|
-
tls ${tls.certFile} ${tls.keyFile}
|
|
392
|
-
` : "";
|
|
393
|
-
const blocks = routes.map(({ host, upstreams, prefix, allow, streaming }) => `https://${host} {${certificate}
|
|
394
|
-
# gkm's own header, which only a service of this stack may send: the auth
|
|
395
|
-
# server rate-limits by the client address it carries.
|
|
396
|
-
request_header -${_geekmidas_manifest.CLIENT_IP_HEADER}
|
|
397
|
-
${allow ? `
|
|
398
|
-
# Only these addresses — the peer Caddy sees, never a header a client
|
|
399
|
-
# could set. Everything else is refused before it reaches the upstream.
|
|
400
|
-
@denied not remote_ip ${allow.join(" ")}
|
|
401
|
-
respond @denied 403
|
|
402
|
-
` : ""}${prefix ? `
|
|
403
|
-
# A bucket, served at a host of its own: the bucket is a prefix on MinIO,
|
|
404
|
-
# which routes and signs on the Host header — so it is sent the upstream's.
|
|
405
|
-
reverse_proxy ${upstreams.map(address).join(" ")} {
|
|
406
|
-
rewrite ${prefix}{uri}
|
|
407
|
-
header_up Host {upstream_hostport}
|
|
408
|
-
}` : `
|
|
409
|
-
# Host and X-Forwarded-* are passed through as Caddy does by default, so
|
|
410
|
-
# an app builds redirects, cookies and links on the address a caller used.
|
|
411
|
-
reverse_proxy ${upstreams.map(address).join(" ")}${streaming ? ` {
|
|
412
|
-
# Never buffer: a streamed response — server-sent events, a Next.js
|
|
413
|
-
# RSC payload — reaches the client as each chunk is written.
|
|
414
|
-
flush_interval -1
|
|
415
|
-
}` : ""}`}
|
|
416
|
-
}`);
|
|
417
|
-
return `${HEADER}${global ? `${global}\n\n` : ""}${blocks.join("\n\n")}\n`;
|
|
418
|
-
}
|
|
419
|
-
const HEADER = `# Generated by gkm compose from the construct manifest — do not edit.
|
|
420
|
-
#
|
|
421
|
-
# One host per API and site, each in front of its own container, one per
|
|
422
|
-
# file server in front of the stack's MinIO, and — when its logs are served
|
|
423
|
-
# publicly — one for OpenObserve, to the addresses it allows.
|
|
424
|
-
`;
|
|
425
|
-
//#endregion
|
|
426
|
-
//#region src/compose/stack.ts
|
|
427
|
-
/**
|
|
428
|
-
* The stack a workspace runs as for one stage — pure.
|
|
429
|
-
*
|
|
430
|
-
* Everything `gkm compose` writes is decided here, as data: the compose file,
|
|
431
|
-
* the Caddyfile, one env file per backend, and the image each app runs. The
|
|
432
|
-
* command around it reads the workspace, asks the registry, writes the files
|
|
433
|
-
* and starts Docker; none of those decide anything, so all of it can be
|
|
434
|
-
* asserted without a daemon.
|
|
435
|
-
*
|
|
436
|
-
* The values come from the same derivations every other target uses. The
|
|
437
|
-
* plan is reconcile's `planFor`, the keys an app reads are `appEnvKeys`, and
|
|
438
|
-
* every URL is `envFor`'s — asked twice, once with each app at its public
|
|
439
|
-
* address and once on the compose network, because a browser and a sibling
|
|
440
|
-
* service reach the same API by different names.
|
|
441
|
-
*/
|
|
442
|
-
var stack_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
443
|
-
EDGE_PORT_ENV: () => EDGE_PORT_ENV,
|
|
444
|
-
EnvValueMultiline: () => EnvValueMultiline,
|
|
445
|
-
LOGS_PORT_ENV: () => LOGS_PORT_ENV,
|
|
446
|
-
LOG_ROTATION: () => LOG_ROTATION,
|
|
447
|
-
LocalCredentialsMissing: () => LocalCredentialsMissing,
|
|
448
|
-
NothingToCompose: () => NothingToCompose,
|
|
449
|
-
StageSecretMissing: () => StageSecretMissing,
|
|
450
|
-
StageSeedMissing: () => StageSeedMissing,
|
|
451
|
-
composeProject: () => composeProject,
|
|
452
|
-
composeStack: () => composeStack,
|
|
453
|
-
derivedCredentials: () => derivedCredentials,
|
|
454
|
-
envFile: () => envFile,
|
|
455
|
-
stackDir: () => stackDir
|
|
456
|
-
});
|
|
457
|
-
/** Where a stack's files are written, relative to the workspace root. */
|
|
458
|
-
function stackDir(stage) {
|
|
459
|
-
return `.gkm/compose/${stage}`;
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* The compose project a stage's stack runs as.
|
|
463
|
-
*
|
|
464
|
-
* The deploy identity's scope, so two workspaces with one name in different
|
|
465
|
-
* namespaces never share containers or volumes — and the stage, so a
|
|
466
|
-
* machine can run two stages side by side.
|
|
467
|
-
*/
|
|
468
|
-
function composeProject(identity) {
|
|
469
|
-
return `${identity.scope}-${identity.stage}`;
|
|
470
|
-
}
|
|
471
|
-
/** The ports the edge is published on, and the variables that move them. */
|
|
472
|
-
const EDGE_PORT_ENV = {
|
|
473
|
-
https: "GKM_COMPOSE_HTTPS_PORT",
|
|
474
|
-
http: "GKM_COMPOSE_HTTP_PORT"
|
|
475
|
-
};
|
|
476
|
-
/**
|
|
477
|
-
* The variable that moves the local stage's OpenObserve off its loopback
|
|
478
|
-
* port, 5080 — which the local stage takes from nowhere else, since it
|
|
479
|
-
* ignores `deploy.telemetry`. Another stack, or another tool, may hold 5080.
|
|
480
|
-
*/
|
|
481
|
-
const LOGS_PORT_ENV = "GKM_COMPOSE_LOGS_PORT";
|
|
482
|
-
/**
|
|
483
|
-
* Containers this target does not run.
|
|
484
|
-
*
|
|
485
|
-
* The AWS emulator never applies: a stack is a server target, whose events
|
|
486
|
-
* are pg-boss. Reconcile's Caddy fronts `gkm dev`'s host processes; the stack
|
|
487
|
-
* brings its own edge.
|
|
488
|
-
*
|
|
489
|
-
* MinIO and Mailpit run on the local stage always, and on a deployed stage
|
|
490
|
-
* only where `--allow-dev-services` allowed them and its secrets configure no
|
|
491
|
-
* real bucket or mail server — see `devServices.ts`.
|
|
492
|
-
*/
|
|
493
|
-
const NOT_RUN = {
|
|
494
|
-
localstack: true,
|
|
495
|
-
caddy: true
|
|
496
|
-
};
|
|
497
|
-
/** The kinds a deployed stage takes from its secrets, or a dev service. */
|
|
498
|
-
const SERVICE_KINDS = {
|
|
499
|
-
email: "email",
|
|
500
|
-
objects: "objects",
|
|
501
|
-
"file-server": "file-server"
|
|
502
|
-
};
|
|
503
|
-
/** The region MinIO is addressed with. It has no regions; the SDK wants one. */
|
|
504
|
-
const MINIO_REGION = "us-east-1";
|
|
505
|
-
/** The S3 client's credentials, read beside a bucket's URL when it has them. */
|
|
506
|
-
const STORAGE_KEYS = [
|
|
507
|
-
"AWS_ACCESS_KEY_ID",
|
|
508
|
-
"AWS_SECRET_ACCESS_KEY",
|
|
509
|
-
"AWS_REGION"
|
|
510
|
-
];
|
|
511
|
-
/**
|
|
512
|
-
* Every service's Docker logs, rotated. The json-file driver keeps a
|
|
513
|
-
* container's output in one file that grows until the disk is full — on a
|
|
514
|
-
* small server, in weeks. Three files of 10 MB each per container is enough
|
|
515
|
-
* to see what happened and never fills anything. A project's own override
|
|
516
|
-
* file that sets `logging` wins over it.
|
|
517
|
-
*/
|
|
518
|
-
const LOG_ROTATION = {
|
|
519
|
-
driver: "json-file",
|
|
520
|
-
options: {
|
|
521
|
-
"max-size": "10m",
|
|
522
|
-
"max-file": "3"
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
/** The local stage's stack, without the logins `gkm dev` generates. */
|
|
526
|
-
var LocalCredentialsMissing = class extends require_errors.GkmError {
|
|
527
|
-
stage;
|
|
528
|
-
constructor(stage) {
|
|
529
|
-
super(`The local stage '${stage}' runs with this machine's generated logins, and none were passed. Load them with loadLocalCredentials(workspace) and pass them as localCredentials.`);
|
|
530
|
-
this.stage = stage;
|
|
531
|
-
this.name = "LocalCredentialsMissing";
|
|
532
|
-
}
|
|
533
|
-
};
|
|
534
|
-
/**
|
|
535
|
-
* A deployed stage's container logins, derived from its seed like its role
|
|
536
|
-
* passwords — so every deploy of the stage computes the same ones, and no one
|
|
537
|
-
* reading the repo can.
|
|
538
|
-
*/
|
|
539
|
-
function derivedCredentials(project, plan, seed) {
|
|
540
|
-
const derive = (purpose) => require_seeds.localRolePassword(project, plan, purpose, seed);
|
|
541
|
-
return {
|
|
542
|
-
postgres: {
|
|
543
|
-
user: require_seeds.postgresSuperuser(project),
|
|
544
|
-
password: derive("master")
|
|
545
|
-
},
|
|
546
|
-
minio: {
|
|
547
|
-
user: `${project}-minio`,
|
|
548
|
-
password: derive("minio")
|
|
549
|
-
},
|
|
550
|
-
rabbitmq: {
|
|
551
|
-
user: require_seeds.LOCAL_RABBITMQ_USER,
|
|
552
|
-
password: derive("rabbitmq")
|
|
553
|
-
},
|
|
554
|
-
redis: { password: derive("redis") },
|
|
555
|
-
cacheToken: derive("cache-token"),
|
|
556
|
-
emulator: {
|
|
557
|
-
accessKeyId: require_seeds.EMULATOR_ACCESS_KEY_ID,
|
|
558
|
-
secretAccessKey: derive("emulator")
|
|
559
|
-
},
|
|
560
|
-
logs: {
|
|
561
|
-
email: `admin@${project}`,
|
|
562
|
-
password: derive("logs")
|
|
563
|
-
}
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
/** A value only the stage's secrets can hold, and they do not. */
|
|
567
|
-
var StageSecretMissing = class extends require_errors.GkmError {
|
|
568
|
-
app;
|
|
569
|
-
key;
|
|
570
|
-
stage;
|
|
571
|
-
constructor(app, key, stage) {
|
|
572
|
-
super(`'${app}' reads ${key}, and the stage '${stage}' has none. Nothing in the stack can derive it; set it in the stage's secrets: gkm secrets:set ${key} '…' --stage ${stage}`);
|
|
573
|
-
this.app = app;
|
|
574
|
-
this.key = key;
|
|
575
|
-
this.stage = stage;
|
|
576
|
-
this.name = "StageSecretMissing";
|
|
577
|
-
}
|
|
578
|
-
};
|
|
579
|
-
/** A deployed stage's secrets with no seed to derive its passwords from. */
|
|
580
|
-
var StageSeedMissing = class extends require_errors.GkmError {
|
|
581
|
-
stage;
|
|
582
|
-
constructor(stage) {
|
|
583
|
-
super(`The stage '${stage}' has no seed in its secrets, and a deployed stage's database passwords are derived from it. Run gkm compose --stage ${stage} without --dry-run once, or gkm deploy, to generate it.`);
|
|
584
|
-
this.stage = stage;
|
|
585
|
-
this.name = "StageSeedMissing";
|
|
586
|
-
}
|
|
587
|
-
};
|
|
588
|
-
/** A workspace with no API and no site to run. */
|
|
589
|
-
var NothingToCompose = class extends require_errors.GkmError {
|
|
590
|
-
root;
|
|
591
|
-
constructor(root) {
|
|
592
|
-
super(`${root} declares no RestApi, no site and no Worker with work to do, so a stack would run nothing. Mobile apps ship through their own toolchain.`);
|
|
593
|
-
this.root = root;
|
|
594
|
-
this.name = "NothingToCompose";
|
|
595
|
-
}
|
|
596
|
-
};
|
|
597
|
-
/** A value that cannot be written to an env file a line at a time. */
|
|
598
|
-
var EnvValueMultiline = class extends require_errors.GkmError {
|
|
599
|
-
app;
|
|
600
|
-
key;
|
|
601
|
-
constructor(app, key) {
|
|
602
|
-
super(`${key} for '${app}' spans more than one line, and an env file holds one value per line. Store it on one line — base64, or JSON — and decode it where it is read.`);
|
|
603
|
-
this.app = app;
|
|
604
|
-
this.key = key;
|
|
605
|
-
this.name = "EnvValueMultiline";
|
|
606
|
-
}
|
|
607
|
-
};
|
|
608
|
-
/** The services a stack's apps wait on and the edge waits on in turn. */
|
|
609
|
-
const HEALTHY = { condition: "service_healthy" };
|
|
610
|
-
/**
|
|
611
|
-
* An app's health check: its own server, asked on 127.0.0.1 — not
|
|
612
|
-
* `localhost`, which resolves to `::1` first in an Alpine container while a
|
|
613
|
-
* server may listen on IPv4 alone.
|
|
614
|
-
*/
|
|
615
|
-
function probe(port, path) {
|
|
616
|
-
return {
|
|
617
|
-
test: [
|
|
618
|
-
"CMD",
|
|
619
|
-
"wget",
|
|
620
|
-
"-q",
|
|
621
|
-
"-O",
|
|
622
|
-
"/dev/null",
|
|
623
|
-
`http://127.0.0.1:${port}${path}`
|
|
624
|
-
],
|
|
625
|
-
interval: "5s",
|
|
626
|
-
timeout: "3s",
|
|
627
|
-
retries: 20,
|
|
628
|
-
start_period: "10s"
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
|
-
/** The stack for one stage. */
|
|
632
|
-
function composeStack(input) {
|
|
633
|
-
const { workspace, manifest, stage, identity } = input;
|
|
634
|
-
const local = stage === workspace.stages.local;
|
|
635
|
-
const project = composeProject(identity);
|
|
636
|
-
const custom = input.secrets?.custom ?? {};
|
|
637
|
-
const https = input.ports?.https ?? 443;
|
|
638
|
-
const http = input.ports?.http ?? 80;
|
|
639
|
-
const proxy = require_workspace.proxyFor(workspace.deploy?.compose, stage, local);
|
|
640
|
-
const certificate = local ? void 0 : require_workspace.certificateFor(workspace.deploy?.compose, stage, workspace.root);
|
|
641
|
-
const plan = require_seeds.planFor(manifest, stage, (0, _geekmidas_manifest.provisionOrder)(manifest), {
|
|
642
|
-
localStage: workspace.stages.local,
|
|
643
|
-
events: require_SubscriberGenerator.DEFAULT_EVENTS.server,
|
|
644
|
-
cache: require_seeds.STACK_CACHE,
|
|
645
|
-
edge: false
|
|
646
|
-
});
|
|
647
|
-
const byId = new Map(plan.resources.map((r) => [r.id, r]));
|
|
648
|
-
if (local && !input.localCredentials) throw new LocalCredentialsMissing(stage);
|
|
649
|
-
const seed = local ? input.localCredentials.seed : input.secrets?.seed;
|
|
650
|
-
if (!seed) throw new StageSeedMissing(stage);
|
|
651
|
-
const containers = local ? input.localCredentials : derivedCredentials(workspace.name, plan, seed);
|
|
652
|
-
const credential = {
|
|
653
|
-
containers: {
|
|
654
|
-
...containers,
|
|
655
|
-
minio: {
|
|
656
|
-
user: custom.AWS_ACCESS_KEY_ID ?? containers.minio.user,
|
|
657
|
-
password: custom.AWS_SECRET_ACCESS_KEY ?? containers.minio.password
|
|
658
|
-
}
|
|
659
|
-
},
|
|
660
|
-
seed
|
|
661
|
-
};
|
|
662
|
-
const apps = stackApps(workspace, manifest, plan, {
|
|
663
|
-
local,
|
|
664
|
-
stage,
|
|
665
|
-
https,
|
|
666
|
-
identity,
|
|
667
|
-
images: input.images
|
|
668
|
-
});
|
|
669
|
-
const workers = stackWorkers(require_seeds.workerUnits(workspace, manifest, input.background ?? {}), {
|
|
670
|
-
stage,
|
|
671
|
-
identity,
|
|
672
|
-
images: input.images
|
|
673
|
-
});
|
|
674
|
-
if (apps.length === 0 && workers.length === 0) throw new NothingToCompose(workspace.root);
|
|
675
|
-
const owners = /* @__PURE__ */ new Map();
|
|
676
|
-
for (const resource of plan.resources) {
|
|
677
|
-
owners.set(resource.envKey, resource);
|
|
678
|
-
if (resource.kind === "email") {
|
|
679
|
-
owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "from"), resource);
|
|
680
|
-
owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "inboxUrl"), resource);
|
|
681
|
-
}
|
|
682
|
-
if (resource.kind === "external-api") owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "credentials"), resource);
|
|
683
|
-
}
|
|
684
|
-
const domain = workspace.domains?.[stage];
|
|
685
|
-
const telemetry = require_workspace.resolveStageTelemetry({
|
|
686
|
-
...workspace.deploy?.telemetry ? { telemetry: workspace.deploy.telemetry } : {},
|
|
687
|
-
stage,
|
|
688
|
-
local,
|
|
689
|
-
target: "compose",
|
|
690
|
-
runtime: "server",
|
|
691
|
-
selfHosted: true,
|
|
692
|
-
used: require_seeds.usesTelemetry(manifest)
|
|
693
|
-
});
|
|
694
|
-
const logsBase = telemetry?.provider === "self-hosted" ? require_seeds.stackLogs({
|
|
695
|
-
config: local && input.ports?.logs ? {
|
|
696
|
-
...telemetry.selfHosted,
|
|
697
|
-
port: input.ports.logs
|
|
698
|
-
} : telemetry.selfHosted,
|
|
699
|
-
stage,
|
|
700
|
-
local,
|
|
701
|
-
project: workspace.name,
|
|
702
|
-
...domain ? { domain } : {},
|
|
703
|
-
custom,
|
|
704
|
-
https,
|
|
705
|
-
...local ? { localLogin: input.localCredentials.logs } : {}
|
|
706
|
-
}) : void 0;
|
|
707
|
-
const services = local ? {
|
|
708
|
-
missing: [],
|
|
709
|
-
minio: plan.resources.filter((r) => r.kind === "objects").map((r) => r.id),
|
|
710
|
-
mailpit: plan.resources.filter((r) => r.kind === "email").map((r) => r.id)
|
|
711
|
-
} : require_seeds.externalServices({
|
|
712
|
-
stage,
|
|
713
|
-
declarations: require_seeds.stageServiceDeclarations({
|
|
714
|
-
manifest,
|
|
715
|
-
...input.runnables ? { runnables: input.runnables } : {}
|
|
716
|
-
}),
|
|
717
|
-
supplied: custom,
|
|
718
|
-
allow: input.allowDevServices === true,
|
|
719
|
-
...domain ? { domain } : {},
|
|
720
|
-
providers: require_seeds.stageProviderNotes(workspace, stage)
|
|
721
|
-
});
|
|
722
|
-
const redis = require_seeds.stackRedis({
|
|
723
|
-
plan,
|
|
724
|
-
stage,
|
|
725
|
-
local,
|
|
726
|
-
custom,
|
|
727
|
-
...local ? { localPassword: input.localCredentials.redis.password } : {}
|
|
728
|
-
});
|
|
729
|
-
const infra = [...plan.containers.filter((c) => !NOT_RUN[c]).filter((c) => c !== require_seeds.LOGS_SERVICE).filter((c) => c !== "minio" || services.minio.length > 0).filter((c) => c !== "mailpit" || services.mailpit.length > 0).filter((c) => c !== "redis" || redis !== void 0), ...logsBase ? [require_seeds.LOGS_SERVICE] : []].sort();
|
|
730
|
-
const storage = infra.includes("minio") ? (() => {
|
|
731
|
-
const buckets = services.minio.map((id) => byId.get(id)?.name).filter((name) => Boolean(name)).sort();
|
|
732
|
-
return {
|
|
733
|
-
...credential.containers.minio,
|
|
734
|
-
buckets,
|
|
735
|
-
policies: require_seeds.bucketPolicies(plan).filter((p) => buckets.includes(p.bucket))
|
|
736
|
-
};
|
|
737
|
-
})() : void 0;
|
|
738
|
-
const fileServers = /* @__PURE__ */ new Map();
|
|
739
|
-
if (storage) for (const resource of plan.resources) {
|
|
740
|
-
if (resource.kind !== "file-server" || !resource.of) continue;
|
|
741
|
-
if (!services.minio.includes(resource.of)) continue;
|
|
742
|
-
if (custom[resource.envKey] !== void 0) continue;
|
|
743
|
-
const bucket = byId.get(resource.of);
|
|
744
|
-
if (!bucket) continue;
|
|
745
|
-
let host;
|
|
746
|
-
if (local) host = require_seeds.hostFor(resource, workspace.name);
|
|
747
|
-
else {
|
|
748
|
-
if (!domain) throw new require_seeds.NoDomainForStage(stage);
|
|
749
|
-
host = `${resource.subdomain ?? require_derive.appKey(resource.id)}.${domain}`;
|
|
750
|
-
}
|
|
751
|
-
fileServers.set(resource.id, {
|
|
752
|
-
url: `https://${host}${local && https !== 443 ? `:${https}` : ""}`,
|
|
753
|
-
route: {
|
|
754
|
-
name: `files-${require_derive.appKey(resource.id)}`,
|
|
755
|
-
host,
|
|
756
|
-
upstreams: [{
|
|
757
|
-
service: "minio",
|
|
758
|
-
port: 9e3
|
|
759
|
-
}],
|
|
760
|
-
streaming: false,
|
|
761
|
-
prefix: `/${bucket.name}`
|
|
762
|
-
}
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
const edgeServices = new Set(proxy === "traefik" ? [
|
|
766
|
-
...apps.map((app) => app.name),
|
|
767
|
-
...fileServers.size > 0 ? ["minio"] : [],
|
|
768
|
-
...logsBase?.host && logsBase.public ? [require_seeds.LOGS_SERVICE] : []
|
|
769
|
-
] : []);
|
|
770
|
-
const hostOf = (service) => edgeServices.has(service) ? edgeAlias(project, service) : service;
|
|
771
|
-
const logs = logsBase && {
|
|
772
|
-
...logsBase,
|
|
773
|
-
appEnv: {
|
|
774
|
-
...logsBase.appEnv,
|
|
775
|
-
OTEL_EXPORTER_OTLP_ENDPOINT: logsBase.appEnv.OTEL_EXPORTER_OTLP_ENDPOINT.replace(`//openobserve:`, `//${hostOf("openobserve")}:`)
|
|
776
|
-
}
|
|
777
|
-
};
|
|
778
|
-
const derivation = {
|
|
779
|
-
project: workspace.name,
|
|
780
|
-
credentials: credential.containers,
|
|
781
|
-
seed: credential.seed,
|
|
782
|
-
...!local && domain ? { mailFrom: `noreply@${domain}` } : {}
|
|
783
|
-
};
|
|
784
|
-
const outside = require_seeds.networkEnv(plan, {
|
|
785
|
-
...derivation,
|
|
786
|
-
addresses: Object.fromEntries(apps.map((app) => [app.id, app.url]))
|
|
787
|
-
}, hostOf);
|
|
788
|
-
const inside = require_seeds.networkEnv(plan, {
|
|
789
|
-
...derivation,
|
|
790
|
-
addresses: Object.fromEntries(apps.map((app) => [app.id, `http://${hostOf(app.name)}:${app.port}`]))
|
|
791
|
-
}, hostOf);
|
|
792
|
-
const surfaceUrls = new Map(plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site").map((r) => [r.envKey, r.id]));
|
|
793
|
-
const appById = new Map(apps.map((app) => [app.id, app]));
|
|
794
|
-
/**
|
|
795
|
-
* What a key resolves to for one app, from the stage's secrets where only
|
|
796
|
-
* they can hold it, and the derivations otherwise.
|
|
797
|
-
*/
|
|
798
|
-
const valueFor = (app, key, owner) => {
|
|
799
|
-
if (require_workspace.isReservedStageKey(key)) return void 0;
|
|
800
|
-
if (owner && SERVICE_KINDS[owner.kind]) {
|
|
801
|
-
if (custom[key] !== void 0) return custom[key];
|
|
802
|
-
if (owner.kind === "file-server") return fileServers.get(owner.id)?.url;
|
|
803
|
-
return outside[key];
|
|
804
|
-
}
|
|
805
|
-
if (owner && require_seeds.suppliedOnly(owner.kind, owner.id, key, local)) {
|
|
806
|
-
const value = custom[key];
|
|
807
|
-
if (value === void 0) throw new StageSecretMissing(app.name, key, stage);
|
|
808
|
-
return value;
|
|
809
|
-
}
|
|
810
|
-
if (custom[key] !== void 0) return custom[key];
|
|
811
|
-
if (owner?.kind === "cache") return redis?.urls[key];
|
|
812
|
-
const surface = surfaceUrls.get(key);
|
|
813
|
-
if (app.kind === "rest-api" && surface && surface !== app.id) return inside[key];
|
|
814
|
-
return outside[key];
|
|
815
|
-
};
|
|
816
|
-
const telemetryValues = logs ? require_workspace.telemetryEnv({
|
|
817
|
-
endpoint: logs.appEnv.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
818
|
-
headers: logs.appEnv.OTEL_EXPORTER_OTLP_HEADERS
|
|
819
|
-
}, telemetry?.sampleRate ?? 1) : telemetry?.provider === "otlp" ? require_workspace.otlpTelemetryEnv(telemetry) : void 0;
|
|
820
|
-
/** Each key one backend process reads, resolved for this stage. */
|
|
821
|
-
const valuesFor = (app, keys) => {
|
|
822
|
-
const values = {};
|
|
823
|
-
for (const key of [...keys].sort()) {
|
|
824
|
-
if (require_seeds.isTelemetryKey(key)) continue;
|
|
825
|
-
const owner = owners.get(key) ?? storageOwner(key, plan);
|
|
826
|
-
if (!local && owner?.kind === "email" && key.endsWith("_INBOX_URL")) continue;
|
|
827
|
-
if (STORAGE_KEYS.includes(key)) {
|
|
828
|
-
const value = custom[key] ?? storageValue(key, storage);
|
|
829
|
-
if (value !== void 0) values[key] = value;
|
|
830
|
-
continue;
|
|
831
|
-
}
|
|
832
|
-
const value = valueFor(app, key, owner);
|
|
833
|
-
if (value !== void 0) values[key] = value;
|
|
834
|
-
}
|
|
835
|
-
return values;
|
|
836
|
-
};
|
|
837
|
-
for (const app of apps) {
|
|
838
|
-
if (app.kind === "site") {
|
|
839
|
-
const site = manifest[app.id];
|
|
840
|
-
if (site?.kind !== "site") continue;
|
|
841
|
-
const args = {};
|
|
842
|
-
for (const [key, source] of Object.entries((0, _geekmidas_manifest.publicEnvFor)(site, manifest))) {
|
|
843
|
-
const value = valueFor(app, source, owners.get(source));
|
|
844
|
-
if (value !== void 0) args[key] = value;
|
|
845
|
-
}
|
|
846
|
-
if (app.build && Object.keys(args).length > 0) app.build.args = args;
|
|
847
|
-
continue;
|
|
848
|
-
}
|
|
849
|
-
if (input.buildOnly) continue;
|
|
850
|
-
const keys = require_seeds.appEnvKeys(manifest, app.name, input.runnables) ?? /* @__PURE__ */ new Set();
|
|
851
|
-
const values = valuesFor(app, keys);
|
|
852
|
-
const trusted = (0, _geekmidas_manifest.provideKey)(app.id, "trustedOrigins");
|
|
853
|
-
if (keys.has(trusted)) {
|
|
854
|
-
const internal = (byId.get(app.id)?.callers ?? []).map((caller) => appById.get(caller)).filter((caller) => caller?.kind === "rest-api").map((caller) => `http://${hostOf(caller.name)}:${caller.port}`);
|
|
855
|
-
const origins = [...(values[trusted] ?? "").split(",").filter(Boolean), ...internal];
|
|
856
|
-
values[trusted] = [...new Set(origins)].join(",");
|
|
857
|
-
}
|
|
858
|
-
app.env = {
|
|
859
|
-
...require_seeds.scopeTelemetryEnv(values, {
|
|
860
|
-
uses: require_seeds.telemetryOf(manifest, app.id) !== void 0,
|
|
861
|
-
serviceName: app.name,
|
|
862
|
-
...telemetryValues ? { telemetry: telemetryValues } : {}
|
|
863
|
-
}),
|
|
864
|
-
NODE_ENV: "production",
|
|
865
|
-
PORT: String(app.port),
|
|
866
|
-
STAGE: stage
|
|
867
|
-
};
|
|
868
|
-
for (const [key, value] of Object.entries(app.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(app.name, key);
|
|
869
|
-
}
|
|
870
|
-
for (const worker of input.buildOnly ? [] : workers) {
|
|
871
|
-
const keys = require_seeds.workerEnvKeys(manifest, worker.id, input.runnables) ?? /* @__PURE__ */ new Set();
|
|
872
|
-
worker.env = {
|
|
873
|
-
...require_seeds.scopeTelemetryEnv(valuesFor({
|
|
874
|
-
name: worker.name,
|
|
875
|
-
id: worker.id,
|
|
876
|
-
kind: "rest-api"
|
|
877
|
-
}, keys), {
|
|
878
|
-
uses: require_seeds.telemetryOf(manifest, worker.id) !== void 0,
|
|
879
|
-
serviceName: worker.name,
|
|
880
|
-
...telemetryValues ? { telemetry: telemetryValues } : {}
|
|
881
|
-
}),
|
|
882
|
-
NODE_ENV: "production",
|
|
883
|
-
PORT: String(worker.port),
|
|
884
|
-
STAGE: stage
|
|
885
|
-
};
|
|
886
|
-
for (const [key, value] of Object.entries(worker.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(worker.name, key);
|
|
887
|
-
}
|
|
888
|
-
const deployedTls = certificate ? {
|
|
889
|
-
kind: "files",
|
|
890
|
-
...proxy === "caddy" ? {
|
|
891
|
-
certFile: `${CADDY_TLS_DIR}/cert.pem`,
|
|
892
|
-
keyFile: `${CADDY_TLS_DIR}/key.pem`
|
|
893
|
-
} : edgeCertificatePaths(project)
|
|
894
|
-
} : { kind: "acme" };
|
|
895
|
-
const tls = local ? { kind: "internal" } : deployedTls;
|
|
896
|
-
const layout = input.layout ?? defaultLayout(workspace.root);
|
|
897
|
-
const compose = stackFile({
|
|
898
|
-
project,
|
|
899
|
-
plan,
|
|
900
|
-
infra,
|
|
901
|
-
apps,
|
|
902
|
-
workers,
|
|
903
|
-
credentials: credential.containers,
|
|
904
|
-
...storage ? { storage } : {},
|
|
905
|
-
...logs ? { logs } : {},
|
|
906
|
-
...redis ? { redis } : {},
|
|
907
|
-
https,
|
|
908
|
-
http,
|
|
909
|
-
composeDir: (0, node_path.join)(workspace.root, stackDir(stage)),
|
|
910
|
-
buildRoot: layout.buildRoot,
|
|
911
|
-
workspaceRoot: workspace.root,
|
|
912
|
-
envFiles: !input.buildOnly,
|
|
913
|
-
proxy,
|
|
914
|
-
edge: new Map([...edgeServices].map((service) => [service, hostOf(service)])),
|
|
915
|
-
tls: certificate !== void 0
|
|
916
|
-
});
|
|
917
|
-
const routes = [
|
|
918
|
-
...apps.map((app) => ({
|
|
919
|
-
name: app.name,
|
|
920
|
-
host: app.host,
|
|
921
|
-
upstreams: [{
|
|
922
|
-
service: app.name,
|
|
923
|
-
port: app.port
|
|
924
|
-
}],
|
|
925
|
-
streaming: true,
|
|
926
|
-
health: app.kind === "site" ? "/" : "/health"
|
|
927
|
-
})),
|
|
928
|
-
...[...fileServers.values()].map(({ route }) => route),
|
|
929
|
-
...logs?.host && logs.public ? [{
|
|
930
|
-
name: require_seeds.LOGS_SERVICE,
|
|
931
|
-
host: logs.host,
|
|
932
|
-
upstreams: [{
|
|
933
|
-
service: require_seeds.LOGS_SERVICE,
|
|
934
|
-
port: require_workspace.LOGS_PORT
|
|
935
|
-
}],
|
|
936
|
-
streaming: true,
|
|
937
|
-
allow: logs.public.allow
|
|
938
|
-
}] : []
|
|
939
|
-
];
|
|
940
|
-
const dockerfiles = {};
|
|
941
|
-
for (const app of apps) {
|
|
942
|
-
if (!app.build) continue;
|
|
943
|
-
dockerfiles[app.build.dockerfile] = require_seeds.appDockerfile(app.name, workspace.apps[app.name], {
|
|
944
|
-
layout,
|
|
945
|
-
workspaceRoot: workspace.root,
|
|
946
|
-
apps: workspace.apps,
|
|
947
|
-
manifest,
|
|
948
|
-
cache: require_seeds.STACK_CACHE,
|
|
949
|
-
workspace,
|
|
950
|
-
stage
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
for (const worker of workers) {
|
|
954
|
-
if (!worker.build) continue;
|
|
955
|
-
dockerfiles[worker.build.dockerfile] = require_seeds.workerDockerfile({
|
|
956
|
-
id: worker.id,
|
|
957
|
-
name: worker.name,
|
|
958
|
-
app: worker.host
|
|
959
|
-
}, workspace.apps[worker.host], {
|
|
960
|
-
layout,
|
|
961
|
-
workspaceRoot: workspace.root,
|
|
962
|
-
cache: require_seeds.STACK_CACHE
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
|
-
return {
|
|
966
|
-
project,
|
|
967
|
-
stage,
|
|
968
|
-
local,
|
|
969
|
-
apps,
|
|
970
|
-
workers,
|
|
971
|
-
infra,
|
|
972
|
-
compose,
|
|
973
|
-
proxy,
|
|
974
|
-
routes,
|
|
975
|
-
...proxy === "caddy" ? { caddyfile: edgeCaddyfile(routes, { tls }) } : { traefik: traefikDynamicFile(routes, {
|
|
976
|
-
project,
|
|
977
|
-
tls: deployedTls
|
|
978
|
-
}) },
|
|
979
|
-
...certificate ? { tls: certificate } : {},
|
|
980
|
-
dockerfiles,
|
|
981
|
-
plan,
|
|
982
|
-
credential,
|
|
983
|
-
...storage ? { storage } : {},
|
|
984
|
-
devServices: local ? [] : require_seeds.devServicesUsed(services),
|
|
985
|
-
...logs ? { logs } : {},
|
|
986
|
-
...telemetry ? { telemetry } : {},
|
|
987
|
-
...redis ? { redis } : {}
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
/** The stack's MinIO's value for one of the S3 client's keys. */
|
|
991
|
-
function storageValue(key, storage) {
|
|
992
|
-
if (!storage) return void 0;
|
|
993
|
-
if (key === "AWS_ACCESS_KEY_ID") return storage.user;
|
|
994
|
-
if (key === "AWS_SECRET_ACCESS_KEY") return storage.password;
|
|
995
|
-
if (key === "AWS_REGION") return MINIO_REGION;
|
|
996
|
-
}
|
|
997
|
-
/** Which bucket an S3 credential key belongs to — the first one declared. */
|
|
998
|
-
function storageOwner(key, plan) {
|
|
999
|
-
if (!STORAGE_KEYS.includes(key)) return void 0;
|
|
1000
|
-
return plan.resources.find((r) => r.kind === "objects");
|
|
1001
|
-
}
|
|
1002
|
-
/** The apps a stack runs: every API and every site, at its stage address. */
|
|
1003
|
-
function stackApps(workspace, manifest, plan, options) {
|
|
1004
|
-
const apps = [];
|
|
1005
|
-
const resources = new Map(plan.resources.map((r) => [r.id, r]));
|
|
1006
|
-
for (const [id, declaration] of Object.entries(manifest)) {
|
|
1007
|
-
if (declaration.kind !== "rest-api" && declaration.kind !== "site") continue;
|
|
1008
|
-
const name = require_derive.appKey(id);
|
|
1009
|
-
const app = workspace.apps[name];
|
|
1010
|
-
const resource = resources.get(id);
|
|
1011
|
-
if (!app || !resource || !app.port) continue;
|
|
1012
|
-
const host = hostOf(name, app, workspace, resource, options);
|
|
1013
|
-
const tag = declaration.kind === "site" ? siteTag(options.images.tag, options.stage) : options.images.tag;
|
|
1014
|
-
const ref = require_seeds.validateImageRef(require_seeds.imageRef(options.identity, name, options.images.registry, tag));
|
|
1015
|
-
apps.push({
|
|
1016
|
-
app: name,
|
|
1017
|
-
name,
|
|
1018
|
-
id,
|
|
1019
|
-
kind: declaration.kind,
|
|
1020
|
-
path: app.path,
|
|
1021
|
-
port: app.port,
|
|
1022
|
-
host,
|
|
1023
|
-
url: `https://${host}${options.local && options.https !== 443 ? `:${options.https}` : ""}`,
|
|
1024
|
-
ref,
|
|
1025
|
-
tag,
|
|
1026
|
-
...options.images.mode === "build" ? { build: { dockerfile: `${stackDir(options.stage)}/Dockerfile.${name}` } } : {}
|
|
1027
|
-
});
|
|
1028
|
-
}
|
|
1029
|
-
return apps.sort((a, b) => a.name.localeCompare(b.name));
|
|
1030
|
-
}
|
|
1031
|
-
/** Each worker's image, and where it is built from. */
|
|
1032
|
-
function stackWorkers(units, options) {
|
|
1033
|
-
return units.map((unit) => ({
|
|
1034
|
-
app: unit.name,
|
|
1035
|
-
name: unit.name,
|
|
1036
|
-
id: unit.id,
|
|
1037
|
-
host: unit.app,
|
|
1038
|
-
port: require_seeds.WORKER_PORT,
|
|
1039
|
-
ref: require_seeds.validateImageRef(require_seeds.imageRef(options.identity, unit.name, options.images.registry, options.images.tag)),
|
|
1040
|
-
tag: options.images.tag,
|
|
1041
|
-
...options.images.mode === "build" ? { build: { dockerfile: `${stackDir(options.stage)}/Dockerfile.${unit.name}` } } : {}
|
|
1042
|
-
}));
|
|
1043
|
-
}
|
|
1044
|
-
/**
|
|
1045
|
-
* The host an app answers on for a stage: the `gkm dev` edge's
|
|
1046
|
-
* `*.localhost` names for the local stage, and the stage's domain — by the
|
|
1047
|
-
* same rule a deploy follows — for any other.
|
|
1048
|
-
*/
|
|
1049
|
-
function hostOf(name, app, workspace, resource, options) {
|
|
1050
|
-
if (options.local) return require_seeds.hostFor(resource, workspace.name);
|
|
1051
|
-
return require_seeds.resolveHost(name, app, options.stage, workspace.domains, require_seeds.isMainFrontendApp(name, app, workspace.apps));
|
|
1052
|
-
}
|
|
1053
|
-
/** The compose document: the infrastructure, the apps, and the edge. */
|
|
1054
|
-
function stackFile(options) {
|
|
1055
|
-
const { project, plan, apps } = options;
|
|
1056
|
-
const infra = options.infra.filter((name) => name !== require_seeds.LOGS_SERVICE);
|
|
1057
|
-
const derived = require_seeds.composeFor({
|
|
1058
|
-
...plan,
|
|
1059
|
-
containers: [...infra]
|
|
1060
|
-
}, {
|
|
1061
|
-
project,
|
|
1062
|
-
ports: Object.fromEntries(require_seeds.portKeys(infra, plan.fakes).map((key) => [key, 0])),
|
|
1063
|
-
credentials: options.credentials
|
|
1064
|
-
});
|
|
1065
|
-
const services = {};
|
|
1066
|
-
const volumes = {
|
|
1067
|
-
...derived.volumes,
|
|
1068
|
-
...options.proxy === "caddy" ? {
|
|
1069
|
-
"caddy-data": {},
|
|
1070
|
-
"caddy-config": {}
|
|
1071
|
-
} : {},
|
|
1072
|
-
...options.logs ? { "openobserve-data": {} } : {}
|
|
1073
|
-
};
|
|
1074
|
-
for (const name of infra) {
|
|
1075
|
-
const { ports: _ports, depends_on, profiles: _profiles, ...service } = derived.services[name];
|
|
1076
|
-
services[name] = {
|
|
1077
|
-
...service,
|
|
1078
|
-
...depends_on?.length ? { depends_on: Object.fromEntries(depends_on.map((dep) => [dep, HEALTHY])) } : {}
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
const postgres = services.postgres;
|
|
1082
|
-
if (postgres) postgres.ports = ["127.0.0.1::5432"];
|
|
1083
|
-
const minio = services.minio;
|
|
1084
|
-
if (minio && options.storage) {
|
|
1085
|
-
minio.environment = {
|
|
1086
|
-
...minio.environment,
|
|
1087
|
-
MINIO_ROOT_USER: options.storage.user,
|
|
1088
|
-
MINIO_ROOT_PASSWORD: options.storage.password
|
|
1089
|
-
};
|
|
1090
|
-
minio.ports = ["127.0.0.1::9000"];
|
|
1091
|
-
}
|
|
1092
|
-
if (services["redis"] && options.redis) services[require_seeds.REDIS_SERVICE] = require_seeds.redisService(options.redis);
|
|
1093
|
-
if (options.logs) services[require_seeds.LOGS_SERVICE] = require_seeds.logsService(options.logs);
|
|
1094
|
-
for (const app of apps) services[app.name] = {
|
|
1095
|
-
image: app.ref,
|
|
1096
|
-
...app.build ? { build: {
|
|
1097
|
-
...require_seeds.composeBuildPaths({
|
|
1098
|
-
composeDir: options.composeDir,
|
|
1099
|
-
buildRoot: options.buildRoot,
|
|
1100
|
-
workspaceRoot: options.workspaceRoot,
|
|
1101
|
-
dockerfile: app.build.dockerfile
|
|
1102
|
-
}),
|
|
1103
|
-
...app.build.args ? { args: app.build.args } : {}
|
|
1104
|
-
} } : {},
|
|
1105
|
-
restart: "unless-stopped",
|
|
1106
|
-
...app.kind === "rest-api" ? {
|
|
1107
|
-
env_file: [{
|
|
1108
|
-
path: `./${app.name}.env`,
|
|
1109
|
-
format: "raw"
|
|
1110
|
-
}],
|
|
1111
|
-
...infra.length ? { depends_on: Object.fromEntries(infra.map((name) => [name, HEALTHY])) } : {}
|
|
1112
|
-
} : {},
|
|
1113
|
-
healthcheck: probe(app.port, app.kind === "site" ? "/" : "/health")
|
|
1114
|
-
};
|
|
1115
|
-
for (const worker of options.workers) services[worker.name] = {
|
|
1116
|
-
image: worker.ref,
|
|
1117
|
-
...worker.build ? { build: require_seeds.composeBuildPaths({
|
|
1118
|
-
composeDir: options.composeDir,
|
|
1119
|
-
buildRoot: options.buildRoot,
|
|
1120
|
-
workspaceRoot: options.workspaceRoot,
|
|
1121
|
-
dockerfile: worker.build.dockerfile
|
|
1122
|
-
}) } : {},
|
|
1123
|
-
restart: "unless-stopped",
|
|
1124
|
-
env_file: [{
|
|
1125
|
-
path: `./${worker.name}.env`,
|
|
1126
|
-
format: "raw"
|
|
1127
|
-
}],
|
|
1128
|
-
...infra.length ? { depends_on: Object.fromEntries(infra.map((name) => [name, HEALTHY])) } : {},
|
|
1129
|
-
healthcheck: probe(worker.port, "/health")
|
|
1130
|
-
};
|
|
1131
|
-
if (options.proxy === "caddy") services.caddy = {
|
|
1132
|
-
image: require_seeds.DEFAULT_IMAGES.caddy,
|
|
1133
|
-
restart: "unless-stopped",
|
|
1134
|
-
ports: [`${options.https}:443`, `${options.http}:80`],
|
|
1135
|
-
volumes: [
|
|
1136
|
-
"./Caddyfile:/etc/caddy/Caddyfile:ro",
|
|
1137
|
-
"caddy-data:/data",
|
|
1138
|
-
"caddy-config:/config",
|
|
1139
|
-
...options.tls ? [`./tls:${CADDY_TLS_DIR}:ro`] : []
|
|
1140
|
-
],
|
|
1141
|
-
depends_on: Object.fromEntries(apps.map((app) => [app.name, HEALTHY])),
|
|
1142
|
-
healthcheck: {
|
|
1143
|
-
test: [
|
|
1144
|
-
"CMD",
|
|
1145
|
-
"caddy",
|
|
1146
|
-
"version"
|
|
1147
|
-
],
|
|
1148
|
-
interval: "10s",
|
|
1149
|
-
timeout: "5s",
|
|
1150
|
-
retries: 5
|
|
1151
|
-
}
|
|
1152
|
-
};
|
|
1153
|
-
for (const [service, alias] of options.edge) {
|
|
1154
|
-
const definition = services[service];
|
|
1155
|
-
if (!definition) continue;
|
|
1156
|
-
definition.networks = {
|
|
1157
|
-
default: { aliases: [alias] },
|
|
1158
|
-
edge: { aliases: [alias] }
|
|
1159
|
-
};
|
|
1160
|
-
}
|
|
1161
|
-
for (const service of Object.values(services)) {
|
|
1162
|
-
if (!options.envFiles) delete service.env_file;
|
|
1163
|
-
service.logging = {
|
|
1164
|
-
driver: LOG_ROTATION.driver,
|
|
1165
|
-
options: { ...LOG_ROTATION.options }
|
|
1166
|
-
};
|
|
1167
|
-
}
|
|
1168
|
-
return {
|
|
1169
|
-
name: project,
|
|
1170
|
-
services,
|
|
1171
|
-
volumes,
|
|
1172
|
-
...options.edge.size > 0 ? { networks: { edge: {
|
|
1173
|
-
name: EDGE_NETWORK,
|
|
1174
|
-
external: true
|
|
1175
|
-
} } } : {}
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
/**
|
|
1179
|
-
* A build without a layout: the workspace is the build root, with pnpm.
|
|
1180
|
-
* What a caller that never looked at the filesystem gets.
|
|
1181
|
-
*/
|
|
1182
|
-
function defaultLayout(root) {
|
|
1183
|
-
return {
|
|
1184
|
-
buildRoot: root,
|
|
1185
|
-
gkmRoot: ".",
|
|
1186
|
-
tools: {
|
|
1187
|
-
packageManager: "pnpm",
|
|
1188
|
-
turboVersion: require_seeds.TURBO_VERSION,
|
|
1189
|
-
monorepo: true
|
|
1190
|
-
},
|
|
1191
|
-
gkmPaths: ["gkm.config.*"]
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
/** Render an app's env file: one `KEY=value` per line, read raw by compose. */
|
|
1195
|
-
function envFile(env) {
|
|
1196
|
-
return `${Object.entries(env).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`).join("\n")}\n`;
|
|
1197
|
-
}
|
|
1198
|
-
//#endregion
|
|
1199
|
-
Object.defineProperty(exports, "EDGE_NETWORK", {
|
|
1200
|
-
enumerable: true,
|
|
1201
|
-
get: function() {
|
|
1202
|
-
return EDGE_NETWORK;
|
|
1203
|
-
}
|
|
1204
|
-
});
|
|
1205
|
-
Object.defineProperty(exports, "EDGE_PORT_ENV", {
|
|
1206
|
-
enumerable: true,
|
|
1207
|
-
get: function() {
|
|
1208
|
-
return EDGE_PORT_ENV;
|
|
1209
|
-
}
|
|
1210
|
-
});
|
|
1211
|
-
Object.defineProperty(exports, "EDGE_PROJECT", {
|
|
1212
|
-
enumerable: true,
|
|
1213
|
-
get: function() {
|
|
1214
|
-
return EDGE_PROJECT;
|
|
1215
|
-
}
|
|
1216
|
-
});
|
|
1217
|
-
Object.defineProperty(exports, "EDGE_SERVICE", {
|
|
1218
|
-
enumerable: true,
|
|
1219
|
-
get: function() {
|
|
1220
|
-
return EDGE_SERVICE;
|
|
1221
|
-
}
|
|
1222
|
-
});
|
|
1223
|
-
Object.defineProperty(exports, "LOGS_PORT_ENV", {
|
|
1224
|
-
enumerable: true,
|
|
1225
|
-
get: function() {
|
|
1226
|
-
return LOGS_PORT_ENV;
|
|
1227
|
-
}
|
|
1228
|
-
});
|
|
1229
|
-
Object.defineProperty(exports, "LOG_ROTATION", {
|
|
1230
|
-
enumerable: true,
|
|
1231
|
-
get: function() {
|
|
1232
|
-
return LOG_ROTATION;
|
|
1233
|
-
}
|
|
1234
|
-
});
|
|
1235
|
-
Object.defineProperty(exports, "RegistryRequired", {
|
|
1236
|
-
enumerable: true,
|
|
1237
|
-
get: function() {
|
|
1238
|
-
return RegistryRequired;
|
|
1239
|
-
}
|
|
1240
|
-
});
|
|
1241
|
-
Object.defineProperty(exports, "assertImagesExist", {
|
|
1242
|
-
enumerable: true,
|
|
1243
|
-
get: function() {
|
|
1244
|
-
return assertImagesExist;
|
|
1245
|
-
}
|
|
1246
|
-
});
|
|
1247
|
-
Object.defineProperty(exports, "composeProject", {
|
|
1248
|
-
enumerable: true,
|
|
1249
|
-
get: function() {
|
|
1250
|
-
return composeProject;
|
|
1251
|
-
}
|
|
1252
|
-
});
|
|
1253
|
-
Object.defineProperty(exports, "composeStack", {
|
|
1254
|
-
enumerable: true,
|
|
1255
|
-
get: function() {
|
|
1256
|
-
return composeStack;
|
|
1257
|
-
}
|
|
1258
|
-
});
|
|
1259
|
-
Object.defineProperty(exports, "edgeComposeFile", {
|
|
1260
|
-
enumerable: true,
|
|
1261
|
-
get: function() {
|
|
1262
|
-
return edgeComposeFile;
|
|
1263
|
-
}
|
|
1264
|
-
});
|
|
1265
|
-
Object.defineProperty(exports, "envFile", {
|
|
1266
|
-
enumerable: true,
|
|
1267
|
-
get: function() {
|
|
1268
|
-
return envFile;
|
|
1269
|
-
}
|
|
1270
|
-
});
|
|
1271
|
-
Object.defineProperty(exports, "parseDigests", {
|
|
1272
|
-
enumerable: true,
|
|
1273
|
-
get: function() {
|
|
1274
|
-
return parseDigests;
|
|
1275
|
-
}
|
|
1276
|
-
});
|
|
1277
|
-
Object.defineProperty(exports, "pinImages", {
|
|
1278
|
-
enumerable: true,
|
|
1279
|
-
get: function() {
|
|
1280
|
-
return pinImages;
|
|
1281
|
-
}
|
|
1282
|
-
});
|
|
1283
|
-
Object.defineProperty(exports, "pinnedRef", {
|
|
1284
|
-
enumerable: true,
|
|
1285
|
-
get: function() {
|
|
1286
|
-
return pinnedRef;
|
|
1287
|
-
}
|
|
1288
|
-
});
|
|
1289
|
-
Object.defineProperty(exports, "stackDir", {
|
|
1290
|
-
enumerable: true,
|
|
1291
|
-
get: function() {
|
|
1292
|
-
return stackDir;
|
|
1293
|
-
}
|
|
1294
|
-
});
|
|
1295
|
-
Object.defineProperty(exports, "stack_exports", {
|
|
1296
|
-
enumerable: true,
|
|
1297
|
-
get: function() {
|
|
1298
|
-
return stack_exports;
|
|
1299
|
-
}
|
|
1300
|
-
});
|
|
1301
|
-
Object.defineProperty(exports, "traefikStaticFile", {
|
|
1302
|
-
enumerable: true,
|
|
1303
|
-
get: function() {
|
|
1304
|
-
return traefikStaticFile;
|
|
1305
|
-
}
|
|
1306
|
-
});
|
|
1307
|
-
|
|
1308
|
-
//# sourceMappingURL=stack-Clzi40EE.cjs.map
|