@geekmidas/cli 10.0.0-alpha.65 → 10.0.0-alpha.66
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/README.md +14 -0
- package/dist/{HostingerProvider-Dlmbfass.cjs → HostingerProvider-BznqRC2S.cjs} +3 -3
- package/dist/HostingerProvider-BznqRC2S.cjs.map +1 -0
- package/dist/{HostingerProvider-x9gQTAGZ.mjs → HostingerProvider-CY_VpVud.mjs} +3 -3
- package/dist/HostingerProvider-CY_VpVud.mjs.map +1 -0
- package/dist/{LegacyStateStore-BGoo__sO.cjs → LegacyStateStore-BFcTT2QL.cjs} +2 -2
- package/dist/{LegacyStateStore-BGoo__sO.cjs.map → LegacyStateStore-BFcTT2QL.cjs.map} +1 -1
- package/dist/{LegacyStateStore-BUu1nQ5u.mjs → LegacyStateStore-CFheFo62.mjs} +2 -2
- package/dist/{LegacyStateStore-BUu1nQ5u.mjs.map → LegacyStateStore-CFheFo62.mjs.map} +1 -1
- package/dist/{LocalStateStore-93urXyQe.cjs → LocalStateStore-BVUexSvu.cjs} +2 -2
- package/dist/{LocalStateStore-93urXyQe.cjs.map → LocalStateStore-BVUexSvu.cjs.map} +1 -1
- package/dist/{LocalStateStore-BRhMQRuy.mjs → LocalStateStore-Bm3x9oOg.mjs} +2 -2
- package/dist/{LocalStateStore-BRhMQRuy.mjs.map → LocalStateStore-Bm3x9oOg.mjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-BWuDRHe9.cjs → OpenApiTsGenerator-B2zrdyP4.cjs} +2 -2
- package/dist/{OpenApiTsGenerator-BWuDRHe9.cjs.map → OpenApiTsGenerator-B2zrdyP4.cjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-C6v-IeHt.mjs → OpenApiTsGenerator-BmUAcR7d.mjs} +2 -2
- package/dist/{OpenApiTsGenerator-C6v-IeHt.mjs.map → OpenApiTsGenerator-BmUAcR7d.mjs.map} +1 -1
- package/dist/{Route53Provider-DVILBsvV.cjs → Route53Provider-Bllus2NR.cjs} +2 -2
- package/dist/Route53Provider-Bllus2NR.cjs.map +1 -0
- package/dist/{Route53Provider-Cvj40A8I.mjs → Route53Provider-QXxjkZO1.mjs} +2 -2
- package/dist/Route53Provider-QXxjkZO1.mjs.map +1 -0
- package/dist/{S3StateStore-pw1GhWwt.mjs → S3StateStore-BFHuctWM.mjs} +2 -2
- package/dist/{S3StateStore-pw1GhWwt.mjs.map → S3StateStore-BFHuctWM.mjs.map} +1 -1
- package/dist/{S3StateStore-F4dxw6kS.cjs → S3StateStore-CoXBhILd.cjs} +2 -2
- package/dist/{S3StateStore-F4dxw6kS.cjs.map → S3StateStore-CoXBhILd.cjs.map} +1 -1
- package/dist/{SSMStateStore-Bt9acuAF.cjs → SSMStateStore-BqCA9TP2.cjs} +2 -2
- package/dist/{SSMStateStore-Bt9acuAF.cjs.map → SSMStateStore-BqCA9TP2.cjs.map} +1 -1
- package/dist/{SSMStateStore-Dmj7zzFO.mjs → SSMStateStore-C1kvHScY.mjs} +2 -2
- package/dist/{SSMStateStore-Dmj7zzFO.mjs.map → SSMStateStore-C1kvHScY.mjs.map} +1 -1
- package/dist/{backup-provisioner-DIVCbfee.mjs → backup-provisioner-C8DnCmxK.mjs} +2 -2
- package/dist/backup-provisioner-C8DnCmxK.mjs.map +1 -0
- package/dist/{backup-provisioner-BmhIO_Yr.cjs → backup-provisioner-oDEsPj02.cjs} +2 -2
- package/dist/backup-provisioner-oDEsPj02.cjs.map +1 -0
- package/dist/{bundler-D_14JLZF.mjs → bundler-Dvaiud0h.mjs} +4 -4
- package/dist/{bundler-D_14JLZF.mjs.map → bundler-Dvaiud0h.mjs.map} +1 -1
- package/dist/{bundler-D6MlIfBT.cjs → bundler-orRqUJNZ.cjs} +4 -4
- package/dist/{bundler-D6MlIfBT.cjs.map → bundler-orRqUJNZ.cjs.map} +1 -1
- package/dist/{cli-P56vvrDQ.mjs → cli-CfHm0AO7.mjs} +5 -4
- package/dist/cli-CfHm0AO7.mjs.map +1 -0
- package/dist/{cli-BfH1deAT.cjs → cli-D1JkkOwg.cjs} +5 -4
- package/dist/cli-D1JkkOwg.cjs.map +1 -0
- package/dist/{compose-DprE3ASm.mjs → compose-CNQL8BnO.mjs} +18 -482
- package/dist/compose-CNQL8BnO.mjs.map +1 -0
- package/dist/{compose-391oEKj1.cjs → compose-jrLy7KGO.cjs} +16 -571
- package/dist/compose-jrLy7KGO.cjs.map +1 -0
- package/dist/{config-V_X_BMpm.d.cts → config-4XFxX1al.d.cts} +3 -3
- package/dist/{config-BonvTW8p.d.mts.map → config-4XFxX1al.d.cts.map} +1 -1
- package/dist/{config-BonvTW8p.d.mts → config-UtKzhpFb.d.mts} +3 -3
- package/dist/{config-V_X_BMpm.d.cts.map → config-UtKzhpFb.d.mts.map} +1 -1
- package/dist/config.cjs +3 -3
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +3 -3
- package/dist/databases-C2p5LGAp.cjs +419 -0
- package/dist/databases-C2p5LGAp.cjs.map +1 -0
- package/dist/databases-hUHTuwhx.mjs +346 -0
- package/dist/databases-hUHTuwhx.mjs.map +1 -0
- package/dist/deploy/public.cjs +26 -17
- package/dist/deploy/public.d.cts +136 -19
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +136 -19
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +8 -8
- package/dist/deploy-B7aJi_Ah.cjs +511 -0
- package/dist/deploy-B7aJi_Ah.cjs.map +1 -0
- package/dist/deploy-DWn7YfIc.mjs +488 -0
- package/dist/deploy-DWn7YfIc.mjs.map +1 -0
- package/dist/{discover-LMrqsWSU.mjs → discover-BFoJWKGD.mjs} +4 -2
- package/dist/discover-BFoJWKGD.mjs.map +1 -0
- package/dist/{discover-DHdECueV.cjs → discover-OEfZ9OkA.cjs} +4 -2
- package/dist/discover-OEfZ9OkA.cjs.map +1 -0
- package/dist/dokploy-B4LcYG1E.cjs +38 -0
- package/dist/dokploy-B4LcYG1E.cjs.map +1 -0
- package/dist/dokploy-DalwLML5.mjs +38 -0
- package/dist/dokploy-DalwLML5.mjs.map +1 -0
- package/dist/{encryption-B1wJwgae.cjs → encryption-3KlGS1sl.cjs} +5 -7
- package/dist/{encryption-B1wJwgae.cjs.map → encryption-3KlGS1sl.cjs.map} +1 -1
- package/dist/{encryption-DF_hgIpl.mjs → encryption-CPfENwWO.mjs} +3 -5
- package/dist/{encryption-DF_hgIpl.mjs.map → encryption-CPfENwWO.mjs.map} +1 -1
- package/dist/{deploy-CZ5nlpsu.mjs → engine-CunvOG3s.mjs} +2100 -1733
- package/dist/engine-CunvOG3s.mjs.map +1 -0
- package/dist/{deploy-Cy8igpQF.cjs → engine-DR6KurWX.cjs} +2136 -1727
- package/dist/engine-DR6KurWX.cjs.map +1 -0
- package/dist/{github-CYcmTdFs.cjs → github-BJ5gh9bw.cjs} +7 -7
- package/dist/{github-CYcmTdFs.cjs.map → github-BJ5gh9bw.cjs.map} +1 -1
- package/dist/{github-B0YYE98x.mjs → github-MXA9v7Ge.mjs} +3 -3
- package/dist/{github-B0YYE98x.mjs.map → github-MXA9v7Ge.mjs.map} +1 -1
- package/dist/{index-DwKD_8lt.d.mts → index-CyApaHVe.d.cts} +2 -2
- package/dist/{index-DPUAGJaK.d.cts.map → index-CyApaHVe.d.cts.map} +1 -1
- package/dist/{index-DPUAGJaK.d.cts → index-DD1FfUHh.d.mts} +2 -2
- package/dist/{index-DwKD_8lt.d.mts.map → index-DD1FfUHh.d.mts.map} +1 -1
- package/dist/index.cjs +276 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +254 -50
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-B2qL5wj4.mjs → migrate-BJBcm4sG.mjs} +6 -6
- package/dist/{migrate-B2qL5wj4.mjs.map → migrate-BJBcm4sG.mjs.map} +1 -1
- package/dist/{migrate-q4pPAXtO.cjs → migrate-CF4J4GQS.cjs} +16 -16
- package/dist/{migrate-q4pPAXtO.cjs.map → migrate-CF4J4GQS.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/provider-B3Sv_a74.cjs +173 -0
- package/dist/provider-B3Sv_a74.cjs.map +1 -0
- package/dist/provider-D7rQzzzf.mjs +138 -0
- package/dist/provider-D7rQzzzf.mjs.map +1 -0
- package/dist/{ready-bQfu9GES.mjs → ready-2s3-CndS.mjs} +3 -3
- package/dist/{ready-bQfu9GES.mjs.map → ready-2s3-CndS.mjs.map} +1 -1
- package/dist/{ready-brEhh68f.cjs → ready-B6uHr4sm.cjs} +3 -3
- package/dist/{ready-brEhh68f.cjs.map → ready-B6uHr4sm.cjs.map} +1 -1
- package/dist/reconcile/public.cjs +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/reconcile/public.mjs +1 -1
- package/dist/{resolve-N_i4XNd-.cjs → resolve-CVmch0wI.cjs} +3 -3
- package/dist/{resolve-N_i4XNd-.cjs.map → resolve-CVmch0wI.cjs.map} +1 -1
- package/dist/{resolve-DLkIXTct.mjs → resolve-Db0tNlrW.mjs} +3 -3
- package/dist/{resolve-DLkIXTct.mjs.map → resolve-Db0tNlrW.mjs.map} +1 -1
- package/dist/rollback-DvAo4OKS.mjs +133 -0
- package/dist/rollback-DvAo4OKS.mjs.map +1 -0
- package/dist/rollback-DxJK84hy.cjs +162 -0
- package/dist/rollback-DxJK84hy.cjs.map +1 -0
- package/dist/{runtime-BnD7Nn96.d.cts → runtime-BRqFOvPM.d.cts} +2 -2
- package/dist/{runtime-BnD7Nn96.d.cts.map → runtime-BRqFOvPM.d.cts.map} +1 -1
- package/dist/{runtime-j-tmFL65.d.mts → runtime-CEk5n4VH.d.mts} +2 -2
- package/dist/{runtime-j-tmFL65.d.mts.map → runtime-CEk5n4VH.d.mts.map} +1 -1
- package/dist/sandbox/discover-worker.cjs +1 -1
- package/dist/sandbox/discover-worker.mjs +1 -1
- package/dist/sandbox/migrate-worker.cjs +61 -0
- package/dist/sandbox/migrate-worker.cjs.map +1 -0
- package/dist/sandbox/migrate-worker.d.cts +1 -0
- package/dist/sandbox/migrate-worker.d.mts +1 -0
- package/dist/sandbox/migrate-worker.mjs +62 -0
- package/dist/sandbox/migrate-worker.mjs.map +1 -0
- package/dist/{schemas-D2LCF689.mjs → schemas-Cfhtpdo0.mjs} +10 -2
- package/dist/{schemas-Ckm_aBnF.cjs.map → schemas-Cfhtpdo0.mjs.map} +1 -1
- package/dist/{schemas-Ckm_aBnF.cjs → schemas-RUlKHu27.cjs} +15 -1
- package/dist/{schemas-D2LCF689.mjs.map → schemas-RUlKHu27.cjs.map} +1 -1
- package/dist/{sniffer-BSwaL-dX.cjs → sniffer-5xmrkh2a.cjs} +112 -606
- package/dist/sniffer-5xmrkh2a.cjs.map +1 -0
- package/dist/{sniffer-BZ-OJO4Y.mjs → sniffer-BAQhLEsI.mjs} +95 -546
- package/dist/sniffer-BAQhLEsI.mjs.map +1 -0
- package/dist/{state-DF26e_lo.cjs → state-Y6ATakMX.cjs} +744 -20
- package/dist/state-Y6ATakMX.cjs.map +1 -0
- package/dist/{state-B8YwmUZU.mjs → state-uuYqnRVO.mjs} +605 -19
- package/dist/state-uuYqnRVO.mjs.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/{terminal-Bs5E1oxm.cjs → terminal-CHy_MdjG.cjs} +11 -15
- package/dist/{terminal-Bs5E1oxm.cjs.map → terminal-CHy_MdjG.cjs.map} +1 -1
- package/dist/{terminal-CfbiYUbZ.mjs → terminal-DUhcZ1ND.mjs} +4 -4
- package/dist/{terminal-CfbiYUbZ.mjs.map → terminal-DUhcZ1ND.mjs.map} +1 -1
- package/dist/{types-ByMWkXKP.d.cts → types-CSw2w69a.d.mts} +68 -10
- package/dist/types-CSw2w69a.d.mts.map +1 -0
- package/dist/{types-BCzMWBb6.d.mts → types-D5WIxRDP.d.cts} +69 -9
- package/dist/types-D5WIxRDP.d.cts.map +1 -0
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- 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-C06_pW4g.mjs → workspace-Be6uBhlF.mjs} +10 -3
- package/dist/workspace-Be6uBhlF.mjs.map +1 -0
- package/dist/{workspace-BewAHZIo.cjs → workspace-ON-bqiJz.cjs} +10 -3
- package/dist/workspace-ON-bqiJz.cjs.map +1 -0
- package/package.json +13 -13
- package/dist/HostingerProvider-Dlmbfass.cjs.map +0 -1
- package/dist/HostingerProvider-x9gQTAGZ.mjs.map +0 -1
- package/dist/Route53Provider-Cvj40A8I.mjs.map +0 -1
- package/dist/Route53Provider-DVILBsvV.cjs.map +0 -1
- package/dist/backup-provisioner-BmhIO_Yr.cjs.map +0 -1
- package/dist/backup-provisioner-DIVCbfee.mjs.map +0 -1
- package/dist/cli-BfH1deAT.cjs.map +0 -1
- package/dist/cli-P56vvrDQ.mjs.map +0 -1
- package/dist/compose-391oEKj1.cjs.map +0 -1
- package/dist/compose-DprE3ASm.mjs.map +0 -1
- package/dist/deploy-9MBG_QLd.mjs +0 -137
- package/dist/deploy-9MBG_QLd.mjs.map +0 -1
- package/dist/deploy-B80idPUZ.cjs +0 -142
- package/dist/deploy-B80idPUZ.cjs.map +0 -1
- package/dist/deploy-CZ5nlpsu.mjs.map +0 -1
- package/dist/deploy-Cy8igpQF.cjs.map +0 -1
- package/dist/discover-DHdECueV.cjs.map +0 -1
- package/dist/discover-LMrqsWSU.mjs.map +0 -1
- package/dist/dokploy-BdtBePjp.mjs +0 -32
- package/dist/dokploy-BdtBePjp.mjs.map +0 -1
- package/dist/dokploy-iFY8MY-L.cjs +0 -32
- package/dist/dokploy-iFY8MY-L.cjs.map +0 -1
- package/dist/sniffer-BSwaL-dX.cjs.map +0 -1
- package/dist/sniffer-BZ-OJO4Y.mjs.map +0 -1
- package/dist/state-B8YwmUZU.mjs.map +0 -1
- package/dist/state-DF26e_lo.cjs.map +0 -1
- package/dist/storage-BzaUxmqe.mjs +0 -124
- package/dist/storage-BzaUxmqe.mjs.map +0 -1
- package/dist/storage-CSGoC3bK.cjs +0 -152
- package/dist/storage-CSGoC3bK.cjs.map +0 -1
- package/dist/store-CJF1Tqkq.cjs +0 -510
- package/dist/store-CJF1Tqkq.cjs.map +0 -1
- package/dist/store-CLiHnqOY.mjs +0 -415
- package/dist/store-CLiHnqOY.mjs.map +0 -1
- package/dist/types-BCzMWBb6.d.mts.map +0 -1
- package/dist/types-ByMWkXKP.d.cts.map +0 -1
- package/dist/workspace-BewAHZIo.cjs.map +0 -1
- package/dist/workspace-C06_pW4g.mjs.map +0 -1
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
2
|
-
import { s as run$1 } from "./local-CWR4Y_kW.mjs";
|
|
3
|
-
import { d as output, r as discover } from "./discover-
|
|
2
|
+
import { s as run$1, u as gkmHome } from "./local-CWR4Y_kW.mjs";
|
|
3
|
+
import { d as output, r as discover } from "./discover-BFoJWKGD.mjs";
|
|
4
4
|
import { t as appKey } from "./derive-B4vDpzgP.mjs";
|
|
5
|
-
import { O as TEST_STAGE, h as getPublicEnvPrefix, n as allConstructGlobs } from "./workspace-
|
|
5
|
+
import { O as TEST_STAGE, h as getPublicEnvPrefix, n as allConstructGlobs } from "./workspace-Be6uBhlF.mjs";
|
|
6
6
|
import { ConfigNotFound, NotInAnApp, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig } from "./config.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import { i as FileSecretsStore, n as secretsStoreFor } from "./store-CLiHnqOY.mjs";
|
|
9
|
-
import { c as runtimeOf } from "./resolve-DLkIXTct.mjs";
|
|
7
|
+
import { c as runtimeOf } from "./resolve-Db0tNlrW.mjs";
|
|
10
8
|
import { i as DEFAULT_STORAGE, n as DEFAULT_CACHE, r as DEFAULT_EVENTS$1, t as readFakes } from "./fakes-C9RiT6XG.mjs";
|
|
11
9
|
import { basename, dirname, join, parse, resolve } from "node:path";
|
|
12
|
-
import { copyFileSync, existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
13
|
-
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
14
|
-
import { networkInterfaces } from "node:os";
|
|
10
|
+
import { constants, copyFileSync, existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
11
|
+
import { chmod, copyFile, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
12
|
+
import { homedir, networkInterfaces } from "node:os";
|
|
15
13
|
import { promisify } from "node:util";
|
|
16
|
-
import { DEFAULT_POSTGRES_VERSION, cacheTable, cookieDomain, dependenciesOf, dependentsOf, externalApiUrl, kebabCase, mobileOrigins, provideKey, providedKeyFor, provisionOrder, publicEnvFor, schemeBase } from "@geekmidas/manifest";
|
|
14
|
+
import { DEFAULT_POSTGRES_VERSION, cacheTable, cookieDomain, dependenciesOf, dependentsOf, externalApiUrl, kebabCase, mobileOrigins, provideKey, providedKeyFor, provisionOrder, publicEnvFor, schemeBase, scopedName } from "@geekmidas/manifest";
|
|
17
15
|
import { execFile, spawn } from "node:child_process";
|
|
18
16
|
import { createServer } from "node:net";
|
|
19
17
|
import { config } from "dotenv";
|
|
20
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
18
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
|
|
21
19
|
import { formatKeyring, generateKeyring } from "@geekmidas/constructs/encryption";
|
|
22
20
|
import { ownerRole, readerRole, roleStatements } from "@geekmidas/db/pg/roles";
|
|
23
21
|
import * as snsUrl from "@geekmidas/events/sns/url";
|
|
@@ -28,6 +26,530 @@ import { CreateQueueCommand, GetQueueUrlCommand, SQSClient } from "@aws-sdk/clie
|
|
|
28
26
|
import { Client } from "pg";
|
|
29
27
|
import { stringify } from "yaml";
|
|
30
28
|
import { cacheTableStatements } from "@geekmidas/cache/postgres";
|
|
29
|
+
//#region src/secrets/storage.ts
|
|
30
|
+
var storage_exports = /* @__PURE__ */ __exportAll({
|
|
31
|
+
initStageSecrets: () => initStageSecrets,
|
|
32
|
+
maskPassword: () => maskPassword,
|
|
33
|
+
toEmbeddableSecrets: () => toEmbeddableSecrets,
|
|
34
|
+
validateEnvironmentVariables: () => validateEnvironmentVariables,
|
|
35
|
+
withCustomSecret: () => withCustomSecret
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Initialize an empty StageSecrets object for a stage.
|
|
39
|
+
*/
|
|
40
|
+
function initStageSecrets(stage) {
|
|
41
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
42
|
+
return {
|
|
43
|
+
stage,
|
|
44
|
+
createdAt: now,
|
|
45
|
+
updatedAt: now,
|
|
46
|
+
services: {},
|
|
47
|
+
urls: {},
|
|
48
|
+
custom: {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Convert StageSecrets to embeddable format (flat key-value pairs).
|
|
53
|
+
* This is what gets encrypted and embedded in the bundle.
|
|
54
|
+
*/
|
|
55
|
+
function toEmbeddableSecrets(secrets) {
|
|
56
|
+
return {
|
|
57
|
+
...secrets.urls,
|
|
58
|
+
...secrets.custom,
|
|
59
|
+
...secrets.services.postgres && {
|
|
60
|
+
POSTGRES_USER: secrets.services.postgres.username,
|
|
61
|
+
POSTGRES_PASSWORD: secrets.services.postgres.password,
|
|
62
|
+
POSTGRES_DB: secrets.services.postgres.database ?? "app",
|
|
63
|
+
POSTGRES_HOST: secrets.services.postgres.host,
|
|
64
|
+
POSTGRES_PORT: String(secrets.services.postgres.port)
|
|
65
|
+
},
|
|
66
|
+
...secrets.services.redis && {
|
|
67
|
+
REDIS_PASSWORD: secrets.services.redis.password,
|
|
68
|
+
REDIS_HOST: secrets.services.redis.host,
|
|
69
|
+
REDIS_PORT: String(secrets.services.redis.port)
|
|
70
|
+
},
|
|
71
|
+
...secrets.services.minio && {
|
|
72
|
+
STORAGE_ACCESS_KEY_ID: secrets.services.minio.username,
|
|
73
|
+
STORAGE_SECRET_ACCESS_KEY: secrets.services.minio.password,
|
|
74
|
+
STORAGE_BUCKET: secrets.services.minio.bucket ?? "app",
|
|
75
|
+
STORAGE_REGION: "eu-west-1",
|
|
76
|
+
STORAGE_FORCE_PATH_STYLE: "true"
|
|
77
|
+
},
|
|
78
|
+
...secrets.services.mailpit && {
|
|
79
|
+
SMTP_HOST: secrets.services.mailpit.host,
|
|
80
|
+
SMTP_PORT: String(secrets.services.mailpit.port),
|
|
81
|
+
SMTP_USER: secrets.services.mailpit.username,
|
|
82
|
+
SMTP_PASS: secrets.services.mailpit.password,
|
|
83
|
+
SMTP_SECURE: "false",
|
|
84
|
+
MAIL_FROM: "noreply@localhost"
|
|
85
|
+
},
|
|
86
|
+
...secrets.services.localstack && {
|
|
87
|
+
AWS_ACCESS_KEY_ID: secrets.services.localstack.accessKeyId ?? secrets.services.localstack.username,
|
|
88
|
+
AWS_SECRET_ACCESS_KEY: secrets.services.localstack.password,
|
|
89
|
+
AWS_REGION: secrets.services.localstack.region ?? "us-east-1",
|
|
90
|
+
AWS_ENDPOINT_URL: `http://${secrets.services.localstack.host}:${secrets.services.localstack.port}`
|
|
91
|
+
},
|
|
92
|
+
...secrets.services.pgboss && {
|
|
93
|
+
PGBOSS_DB_USER: secrets.services.pgboss.username,
|
|
94
|
+
PGBOSS_DB_PASSWORD: secrets.services.pgboss.password
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** The same secrets with one custom value set, and `updatedAt` moved on. */
|
|
99
|
+
function withCustomSecret(secrets, key, value) {
|
|
100
|
+
return {
|
|
101
|
+
...secrets,
|
|
102
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
103
|
+
custom: {
|
|
104
|
+
...secrets.custom,
|
|
105
|
+
[key]: value
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Mask a password for display (show first 4 and last 2 chars).
|
|
111
|
+
*/
|
|
112
|
+
function maskPassword(password) {
|
|
113
|
+
if (password.length <= 8) return "********";
|
|
114
|
+
return `${password.slice(0, 4)}${"*".repeat(password.length - 6)}${password.slice(-2)}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Validate that all required environment variables are present in secrets.
|
|
118
|
+
*
|
|
119
|
+
* @param requiredVars - Array of environment variable names required by the application
|
|
120
|
+
* @param secrets - Stage secrets to validate against
|
|
121
|
+
* @returns Validation result with missing and provided variables
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* const required = ['DATABASE_URL', 'API_KEY', 'JWT_SECRET'];
|
|
126
|
+
* const secrets = await secretsStoreFor(workspace, 'production').then((s) => s.read('production'));
|
|
127
|
+
* const result = validateEnvironmentVariables(required, secrets);
|
|
128
|
+
*
|
|
129
|
+
* if (!result.valid) {
|
|
130
|
+
* console.error(`Missing environment variables: ${result.missing.join(', ')}`);
|
|
131
|
+
* }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
function validateEnvironmentVariables(requiredVars, secrets) {
|
|
135
|
+
const embeddable = toEmbeddableSecrets(secrets);
|
|
136
|
+
const availableVars = new Set(Object.keys(embeddable));
|
|
137
|
+
const missing = [];
|
|
138
|
+
const provided = [];
|
|
139
|
+
for (const varName of requiredVars) if (availableVars.has(varName)) provided.push(varName);
|
|
140
|
+
else missing.push(varName);
|
|
141
|
+
return {
|
|
142
|
+
valid: missing.length === 0,
|
|
143
|
+
missing: missing.sort(),
|
|
144
|
+
provided: provided.sort(),
|
|
145
|
+
required: [...requiredVars].sort()
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/deploy/identity.ts
|
|
150
|
+
/**
|
|
151
|
+
* Who a deploy is, on a target it shares with other workspaces.
|
|
152
|
+
*
|
|
153
|
+
* A Dokploy server is one flat namespace of projects, images and Docker
|
|
154
|
+
* service names. Deploys used to identify everything they made by the
|
|
155
|
+
* workspace name alone, so two workspaces called `shop` — or `Shop` and
|
|
156
|
+
* `shop` — deploying to one server landed in the same project, pushed over
|
|
157
|
+
* each other's images and redeployed each other's applications. A
|
|
158
|
+
* `DeployIdentity` is what a deploy names and claims its resources by instead.
|
|
159
|
+
*/
|
|
160
|
+
/** A namespace that cannot be part of a Dokploy name or an image path. */
|
|
161
|
+
var DeployNamespaceInvalid = class extends Error {
|
|
162
|
+
namespace;
|
|
163
|
+
constructor(namespace) {
|
|
164
|
+
super(`deploy.namespace '${namespace}' is not usable as a name: use lowercase letters, digits and single '-' between them (e.g. 'acme' or 'acme-platform') in gkm.config.ts.`);
|
|
165
|
+
this.namespace = namespace;
|
|
166
|
+
this.name = "DeployNamespaceInvalid";
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
/** A workspace name with nothing a target can name a resource by. */
|
|
170
|
+
var DeployProjectNameInvalid = class extends Error {
|
|
171
|
+
workspaceName;
|
|
172
|
+
constructor(workspaceName) {
|
|
173
|
+
super(`The workspace name '${workspaceName}' has no letters or digits to name deployed resources by. Set "name" in gkm.config.ts.`);
|
|
174
|
+
this.workspaceName = workspaceName;
|
|
175
|
+
this.name = "DeployProjectNameInvalid";
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
179
|
+
/**
|
|
180
|
+
* Lowercase `[a-z0-9-]`, runs of anything else collapsed to one `-`: valid as
|
|
181
|
+
* a Docker image path component, a Docker service name and a Dokploy name.
|
|
182
|
+
*/
|
|
183
|
+
function slug(value) {
|
|
184
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
185
|
+
}
|
|
186
|
+
/** The namespace a workspace is in when it does not name one. */
|
|
187
|
+
function defaultNamespace(workspaceName) {
|
|
188
|
+
return slug(kebabCase(workspaceName));
|
|
189
|
+
}
|
|
190
|
+
function deployIdentity(workspace, stage) {
|
|
191
|
+
const project = slug(workspace.name);
|
|
192
|
+
if (!project) throw new DeployProjectNameInvalid(workspace.name);
|
|
193
|
+
const fallback = defaultNamespace(workspace.name);
|
|
194
|
+
const namespace = workspace.deploy?.namespace ?? fallback;
|
|
195
|
+
if (!NAME.test(namespace)) throw new DeployNamespaceInvalid(namespace);
|
|
196
|
+
return {
|
|
197
|
+
namespace,
|
|
198
|
+
project,
|
|
199
|
+
stage,
|
|
200
|
+
key: `${namespace}/${project}`,
|
|
201
|
+
scope: namespace === fallback ? project : `${namespace}-${project}`
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The token a Dokploy project's description carries to say which identity
|
|
206
|
+
* created it. A project with the right name and no marker belongs to someone
|
|
207
|
+
* else, and a deploy never adopts it.
|
|
208
|
+
*/
|
|
209
|
+
function ownershipMarker(identity) {
|
|
210
|
+
return `gkm:${identity.key}`;
|
|
211
|
+
}
|
|
212
|
+
/** Whether a project description carries `identity`'s marker. */
|
|
213
|
+
function isOwnedBy(description, identity) {
|
|
214
|
+
const marker = ownershipMarker(identity);
|
|
215
|
+
return (description ?? "").split(/\s+/).includes(marker);
|
|
216
|
+
}
|
|
217
|
+
/** Any `gkm:` marker a description carries — whoever's it is. */
|
|
218
|
+
function markerOf(description) {
|
|
219
|
+
return (description ?? "").split(/\s+/).find((t) => t.startsWith("gkm:"));
|
|
220
|
+
}
|
|
221
|
+
/** What the Dokploy project is called. */
|
|
222
|
+
function projectName(identity) {
|
|
223
|
+
return identity.scope;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* The repository an app's image is pushed to, below the registry:
|
|
227
|
+
* `<namespace>/<project>-<app>`.
|
|
228
|
+
*
|
|
229
|
+
* The namespace is a path segment of its own so that a registry that maps
|
|
230
|
+
* paths to owners (an organisation's packages, a Harbor project) can grant it
|
|
231
|
+
* as one; the app is joined to the project so the two read as one name.
|
|
232
|
+
*/
|
|
233
|
+
function imageName(identity, app) {
|
|
234
|
+
return `${identity.namespace}/${identity.project}-${slug(kebabCase(app))}`;
|
|
235
|
+
}
|
|
236
|
+
/** The full image ref an app is built, pushed and deployed as. */
|
|
237
|
+
function imageRef(identity, app, registry, tag) {
|
|
238
|
+
const name = imageName(identity, app);
|
|
239
|
+
return registry ? `${registry.replace(/\/+$/, "")}/${name}:${tag}` : `${name}:${tag}`;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* What one application is called on the target.
|
|
243
|
+
*
|
|
244
|
+
* The same rule the constructs use, through the same `scopedName`, scoped by
|
|
245
|
+
* the stage and the identity: the application beside
|
|
246
|
+
* `production-kitchen-sink-database` is `production-kitchen-sink-api`. A
|
|
247
|
+
* Dokploy application's name is also its Docker service name, which is unique
|
|
248
|
+
* on the whole server, so the namespace has to be in it too — `acme`'s and
|
|
249
|
+
* `globex`'s `shop` would otherwise both run `production-shop-api`.
|
|
250
|
+
*
|
|
251
|
+
* The app id is dropped when it repeats the project, so a project named for its
|
|
252
|
+
* one application is `production-kitchen-sink` rather than
|
|
253
|
+
* `production-kitchen-sink-kitchen-sink`.
|
|
254
|
+
*/
|
|
255
|
+
function applicationName(identity, app) {
|
|
256
|
+
return slug(app) === identity.project ? `${identity.stage}-${identity.scope}` : scopedName([identity.stage, identity.scope], app);
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/secrets/keystore.ts
|
|
260
|
+
/**
|
|
261
|
+
* The keys that open each stage's encrypted secrets file, kept outside the
|
|
262
|
+
* project in the CLI's home.
|
|
263
|
+
*
|
|
264
|
+
* Keys were kept at `~/.gkm/<folder>/<stage>.key`, named after the project
|
|
265
|
+
* directory — so two checkouts in folders with the same name (`~/work/api`
|
|
266
|
+
* and `~/oss/api`) shared one key, and the second `gkm secrets:init`
|
|
267
|
+
* overwrote the first project's. They are now kept under the project's
|
|
268
|
+
* identity, `<home>/keys/<namespace>/<project>/<stage>.key`, the same
|
|
269
|
+
* `<namespace>/<project>` a deploy claims its Dokploy project by. A key still
|
|
270
|
+
* at the old place is copied to the new one the first time it is read; the
|
|
271
|
+
* old file stays, so an older CLI on the same machine keeps working.
|
|
272
|
+
*/
|
|
273
|
+
/** Key length for AES-256 encryption */
|
|
274
|
+
const KEY_LENGTH = 32;
|
|
275
|
+
/** A keystore project key that is not `<namespace>/<project>`. */
|
|
276
|
+
var KeystoreProjectInvalid = class extends Error {
|
|
277
|
+
key;
|
|
278
|
+
constructor(key) {
|
|
279
|
+
super(`'${key}' is not a keystore project: expected '<namespace>/<project>' in lowercase letters, digits and '-', as deployIdentity() produces. Pass the workspace's identity key.`);
|
|
280
|
+
this.key = key;
|
|
281
|
+
this.name = "KeystoreProjectInvalid";
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
/** A stage has no key, here or at the place keys used to be kept. */
|
|
285
|
+
var KeyNotFound = class extends Error {
|
|
286
|
+
stage;
|
|
287
|
+
project;
|
|
288
|
+
path;
|
|
289
|
+
constructor(stage, project, path) {
|
|
290
|
+
super(`Encryption key not found for stage "${stage}" in project "${project}". Expected key at: ${path}. Run \`gkm secrets:init --stage ${stage}\` to create one, or copy the stage's key there.`);
|
|
291
|
+
this.stage = stage;
|
|
292
|
+
this.project = project;
|
|
293
|
+
this.path = path;
|
|
294
|
+
this.name = "KeyNotFound";
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const PROJECT_KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
298
|
+
/**
|
|
299
|
+
* The keystore key of a workspace: its deploy identity without the stage, so
|
|
300
|
+
* a project's keys and its Dokploy project are named by the same thing.
|
|
301
|
+
*/
|
|
302
|
+
function projectKey(workspace) {
|
|
303
|
+
return deployIdentity(workspace, "").key;
|
|
304
|
+
}
|
|
305
|
+
/** The keystore project of a loaded workspace. */
|
|
306
|
+
function keystoreProject(workspace, home) {
|
|
307
|
+
const key = projectKey(workspace);
|
|
308
|
+
const unscoped = projectKey({ name: workspace.name });
|
|
309
|
+
return {
|
|
310
|
+
key,
|
|
311
|
+
legacy: [.../* @__PURE__ */ new Set([folderName(workspace.root), workspace.name])].filter((name) => !!name),
|
|
312
|
+
...unscoped !== key ? { aliases: [unscoped] } : {},
|
|
313
|
+
...home ? { home } : {}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Whether `name` stays inside the home it is joined to. A workspace name was
|
|
318
|
+
* joined as is — `@acme/shop` is `~/.gkm/@acme/shop` — but never upwards.
|
|
319
|
+
*/
|
|
320
|
+
function isLegacyName(name) {
|
|
321
|
+
return name.split(/[\\/]/).every((part) => part !== ".." && part !== "");
|
|
322
|
+
}
|
|
323
|
+
/** The last segment of a path, as the old keystore named projects. */
|
|
324
|
+
function folderName(path) {
|
|
325
|
+
const name = path.split(/[\\/]/).filter(Boolean).at(-1);
|
|
326
|
+
return name && name !== "." && name !== ".." ? name : void 0;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get the keystore directory for a project:
|
|
330
|
+
* `<home>/keys/<namespace>/<project>`.
|
|
331
|
+
*/
|
|
332
|
+
function getKeystoreDir(project) {
|
|
333
|
+
if (!PROJECT_KEY.test(project.key)) throw new KeystoreProjectInvalid(project.key);
|
|
334
|
+
const [namespace, name] = project.key.split("/");
|
|
335
|
+
return join(project.home ?? gkmHome(), "keys", namespace, name);
|
|
336
|
+
}
|
|
337
|
+
/** Get the path to a stage's encryption key. */
|
|
338
|
+
function getKeyPath(stage, project) {
|
|
339
|
+
return join(getKeystoreDir(project), `${stage}.key`);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Where else a stage's key may be, most likely first: under an alias of the
|
|
343
|
+
* project, then where keys were kept before they were keyed by identity —
|
|
344
|
+
* under the configured home, then under `~/.gkm`, which is where every CLI
|
|
345
|
+
* before `GKM_HOME` wrote.
|
|
346
|
+
*/
|
|
347
|
+
function legacyKeyPaths(stage, project) {
|
|
348
|
+
const aliases = (project.aliases ?? []).map((key) => getKeyPath(stage, {
|
|
349
|
+
key,
|
|
350
|
+
home: project.home
|
|
351
|
+
}));
|
|
352
|
+
const homes = [.../* @__PURE__ */ new Set([project.home ?? gkmHome(), join(homedir(), ".gkm")])];
|
|
353
|
+
const names = (project.legacy ?? []).filter(isLegacyName);
|
|
354
|
+
return [...aliases, ...names.flatMap((name) => homes.map((home) => join(home, name, `${stage}.key`)))];
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Generate a new encryption key for a stage, stored owner-only at
|
|
358
|
+
* `<home>/keys/<namespace>/<project>/<stage>.key`.
|
|
359
|
+
*/
|
|
360
|
+
async function generateKey(stage, project, flag = "w") {
|
|
361
|
+
const keyDir = getKeystoreDir(project);
|
|
362
|
+
const keyPath = getKeyPath(stage, project);
|
|
363
|
+
await mkdir(keyDir, {
|
|
364
|
+
recursive: true,
|
|
365
|
+
mode: 448
|
|
366
|
+
});
|
|
367
|
+
const key = randomBytes(KEY_LENGTH).toString("hex");
|
|
368
|
+
await writeFile(keyPath, key, {
|
|
369
|
+
mode: 384,
|
|
370
|
+
encoding: "utf-8",
|
|
371
|
+
flag
|
|
372
|
+
});
|
|
373
|
+
await chmod(keyPath, 384);
|
|
374
|
+
return key;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Copy a key from where it used to be kept to where it is kept now.
|
|
378
|
+
*
|
|
379
|
+
* Copied, not moved: an older CLI on the same machine still reads the old
|
|
380
|
+
* place. Exclusive, so two processes migrating at once cannot overwrite a key
|
|
381
|
+
* one of them already put there.
|
|
382
|
+
*/
|
|
383
|
+
async function migrateLegacyKey(stage, project) {
|
|
384
|
+
const legacy = legacyKeyPaths(stage, project).find((path) => existsSync(path));
|
|
385
|
+
if (!legacy) return false;
|
|
386
|
+
const keyPath = getKeyPath(stage, project);
|
|
387
|
+
await mkdir(getKeystoreDir(project), {
|
|
388
|
+
recursive: true,
|
|
389
|
+
mode: 448
|
|
390
|
+
});
|
|
391
|
+
try {
|
|
392
|
+
await copyFile(legacy, keyPath, constants.COPYFILE_EXCL);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
if (error.code !== "EEXIST") throw error;
|
|
395
|
+
}
|
|
396
|
+
await chmod(keyPath, 384);
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Read an encryption key for a stage, or `null` when there is none — after
|
|
401
|
+
* copying it over from where keys used to be kept, if it is still there.
|
|
402
|
+
*/
|
|
403
|
+
async function readKey(stage, project) {
|
|
404
|
+
const keyPath = getKeyPath(stage, project);
|
|
405
|
+
if (!existsSync(keyPath) && !await migrateLegacyKey(stage, project)) return null;
|
|
406
|
+
return (await readFile(keyPath, "utf-8")).trim();
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Read an encryption key for a stage, throwing {@link KeyNotFound} if there
|
|
410
|
+
* is none.
|
|
411
|
+
*/
|
|
412
|
+
async function requireKey(stage, project) {
|
|
413
|
+
const key = await readKey(stage, project);
|
|
414
|
+
if (!key) throw new KeyNotFound(stage, project.key, getKeyPath(stage, project));
|
|
415
|
+
return key;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get or create a key for a stage.
|
|
419
|
+
* If the key already exists, it is returned. Otherwise, a new key is generated.
|
|
420
|
+
*/
|
|
421
|
+
async function getOrCreateKey(stage, project) {
|
|
422
|
+
const existingKey = await readKey(stage, project);
|
|
423
|
+
if (existingKey) return existingKey;
|
|
424
|
+
try {
|
|
425
|
+
return await generateKey(stage, project, "wx");
|
|
426
|
+
} catch (error) {
|
|
427
|
+
if (error.code !== "EEXIST") throw error;
|
|
428
|
+
return requireKey(stage, project);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
//#endregion
|
|
432
|
+
//#region src/secrets/file.ts
|
|
433
|
+
/** Where the files live, relative to the project root. */
|
|
434
|
+
const SECRETS_DIR = ".gkm/secrets";
|
|
435
|
+
/** AES-256-GCM configuration */
|
|
436
|
+
const ALGORITHM = "aes-256-gcm";
|
|
437
|
+
const IV_LENGTH = 12;
|
|
438
|
+
/**
|
|
439
|
+
* A stage's secrets as an encrypted file on this machine:
|
|
440
|
+
* `.gkm/secrets/<stage>.json`, with its key in the CLI's home at
|
|
441
|
+
* `keys/<namespace>/<project>/<stage>.key`.
|
|
442
|
+
*
|
|
443
|
+
* The local stage's store, always — its secrets belong to the machine running
|
|
444
|
+
* `gkm dev` — and a deployed stage's when `secrets.store` is `'file'`.
|
|
445
|
+
*/
|
|
446
|
+
var FileSecretsStore = class {
|
|
447
|
+
root;
|
|
448
|
+
name = "file";
|
|
449
|
+
project;
|
|
450
|
+
/**
|
|
451
|
+
* @param project - Whose keys: a workspace's `keystoreProject()`. Outside a
|
|
452
|
+
* workspace there is no config to name the project, so it is named the
|
|
453
|
+
* way a workspace without `name` is — its package name, else its folder.
|
|
454
|
+
*/
|
|
455
|
+
constructor(root, project) {
|
|
456
|
+
this.root = root;
|
|
457
|
+
this.project = project ?? {
|
|
458
|
+
key: projectKey({ name: packageName(root) ?? basename(root) }),
|
|
459
|
+
legacy: [basename(root)]
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
/** The file a stage's secrets are kept in. */
|
|
463
|
+
path(stage) {
|
|
464
|
+
return join(this.root, SECRETS_DIR, `${stage}.json`);
|
|
465
|
+
}
|
|
466
|
+
async read(stage) {
|
|
467
|
+
const path = this.path(stage);
|
|
468
|
+
if (!existsSync(path)) return null;
|
|
469
|
+
const data = JSON.parse(await readFile(path, "utf-8"));
|
|
470
|
+
if (!(data.version === 1 && data.encrypted && data.iv)) return data;
|
|
471
|
+
const key = await readKey(stage, this.project);
|
|
472
|
+
if (!key) throw new MissingSecretsKey(stage, this.project.key, getKeyPath(stage, this.project));
|
|
473
|
+
return decrypt(data, key);
|
|
474
|
+
}
|
|
475
|
+
async write(stage, secrets) {
|
|
476
|
+
await mkdir(join(this.root, SECRETS_DIR), { recursive: true });
|
|
477
|
+
const key = await getOrCreateKey(stage, this.project);
|
|
478
|
+
await writeFile(this.path(stage), JSON.stringify(encrypt(secrets, key), null, 2), "utf-8");
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
/** A stage's secrets file exists, and the key that opens it does not. */
|
|
482
|
+
var MissingSecretsKey = class extends Error {
|
|
483
|
+
stage;
|
|
484
|
+
project;
|
|
485
|
+
path;
|
|
486
|
+
constructor(stage, project, path) {
|
|
487
|
+
super(`Decryption key not found for stage "${stage}". Expected key at: ${path}. Copy the stage's key there from whoever created it, or set secrets.store to a store this machine can reach.`);
|
|
488
|
+
this.stage = stage;
|
|
489
|
+
this.project = project;
|
|
490
|
+
this.path = path;
|
|
491
|
+
this.name = "MissingSecretsKey";
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
/** A project's package name without its scope, as a workspace name reads it. */
|
|
495
|
+
function packageName(root) {
|
|
496
|
+
try {
|
|
497
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
|
|
498
|
+
return typeof pkg.name === "string" ? pkg.name.replace(/^@[^/]+\//, "") : void 0;
|
|
499
|
+
} catch {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function encrypt(secrets, keyHex) {
|
|
504
|
+
const key = Buffer.from(keyHex, "hex");
|
|
505
|
+
const iv = randomBytes(IV_LENGTH);
|
|
506
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
507
|
+
const ciphertext = Buffer.concat([cipher.update(JSON.stringify(secrets), "utf-8"), cipher.final()]);
|
|
508
|
+
return {
|
|
509
|
+
version: 1,
|
|
510
|
+
encrypted: Buffer.concat([ciphertext, cipher.getAuthTag()]).toString("base64"),
|
|
511
|
+
iv: iv.toString("hex")
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function decrypt(data, keyHex) {
|
|
515
|
+
const key = Buffer.from(keyHex, "hex");
|
|
516
|
+
const combined = Buffer.from(data.encrypted, "base64");
|
|
517
|
+
const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(data.iv, "hex"));
|
|
518
|
+
decipher.setAuthTag(combined.subarray(-16));
|
|
519
|
+
const plaintext = Buffer.concat([decipher.update(combined.subarray(0, -16)), decipher.final()]);
|
|
520
|
+
return JSON.parse(plaintext.toString("utf-8"));
|
|
521
|
+
}
|
|
522
|
+
//#endregion
|
|
523
|
+
//#region src/secrets/store.ts
|
|
524
|
+
var store_exports = /* @__PURE__ */ __exportAll({
|
|
525
|
+
FileSecretsStore: () => FileSecretsStore,
|
|
526
|
+
isRemoteStore: () => isRemoteStore,
|
|
527
|
+
secretsStoreFor: () => secretsStoreFor
|
|
528
|
+
});
|
|
529
|
+
/**
|
|
530
|
+
* The store a stage's secrets are kept in — what every command reads and
|
|
531
|
+
* writes, for the stage it acts on.
|
|
532
|
+
*
|
|
533
|
+
* The local stage is always the file: its secrets belong to the machine
|
|
534
|
+
* running `gkm dev`. A deployed stage uses `secrets.store`.
|
|
535
|
+
*/
|
|
536
|
+
async function secretsStoreFor(workspace, stage, options = {}) {
|
|
537
|
+
const configured = workspace.secrets.store ?? "file";
|
|
538
|
+
if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root, keystoreProject(workspace, options.home));
|
|
539
|
+
if (configured.provider !== "ssm") return configured.provider;
|
|
540
|
+
const { AwsSecretsStore } = await import("./aws-DojzQnLd.mjs");
|
|
541
|
+
return new AwsSecretsStore({
|
|
542
|
+
project: workspace.name,
|
|
543
|
+
region: configured.region,
|
|
544
|
+
...options.profile ? { profile: options.profile } : {}
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
/** Whether a stage's secrets live somewhere other than this machine. */
|
|
548
|
+
function isRemoteStore(workspace, stage) {
|
|
549
|
+
const configured = workspace.secrets.store ?? "file";
|
|
550
|
+
return stage !== workspace.stages.local && configured !== "file";
|
|
551
|
+
}
|
|
552
|
+
//#endregion
|
|
31
553
|
//#region src/credentials/index.ts
|
|
32
554
|
const logger$1 = console;
|
|
33
555
|
/**
|
|
@@ -3158,7 +3680,7 @@ function localAddress(port) {
|
|
|
3158
3680
|
return `http://localhost:${port}`;
|
|
3159
3681
|
}
|
|
3160
3682
|
//#endregion
|
|
3161
|
-
//#region src/
|
|
3683
|
+
//#region src/target/dokploy/domain.ts
|
|
3162
3684
|
/** A stage was deployed that `deploy.domains` gives no domain. */
|
|
3163
3685
|
var NoDomainForStage = class extends Error {
|
|
3164
3686
|
stage;
|
|
@@ -3772,6 +4294,12 @@ USER nextjs
|
|
|
3772
4294
|
|
|
3773
4295
|
EXPOSE ${port}
|
|
3774
4296
|
|
|
4297
|
+
# A site answers at its root; Docker, and Dokploy, read the container's
|
|
4298
|
+
# health from it rather than from the process merely running. 127.0.0.1, not
|
|
4299
|
+
# localhost: the server listens on IPv4, and localhost can resolve to ::1.
|
|
4300
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
4301
|
+
CMD wget -qO- http://127.0.0.1:${port}/ > /dev/null 2>&1 || exit 1
|
|
4302
|
+
|
|
3775
4303
|
ENTRYPOINT ["/sbin/tini", "--"]
|
|
3776
4304
|
CMD ["node", "${appPath}/server.js"]
|
|
3777
4305
|
`;
|
|
@@ -4039,6 +4567,12 @@ ENV HOSTNAME="0.0.0.0"
|
|
|
4039
4567
|
USER app
|
|
4040
4568
|
EXPOSE ${port}
|
|
4041
4569
|
|
|
4570
|
+
# A site answers at its root; Docker, and Dokploy, read the container's
|
|
4571
|
+
# health from it rather than from the process merely running. 127.0.0.1, not
|
|
4572
|
+
# localhost: the server listens on IPv4, and localhost can resolve to ::1.
|
|
4573
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
4574
|
+
CMD wget -qO- http://127.0.0.1:${port}/ > /dev/null 2>&1 || exit 1
|
|
4575
|
+
|
|
4042
4576
|
ENTRYPOINT ["/sbin/tini", "--"]
|
|
4043
4577
|
CMD ["sh", "-c", "cd ${appPath} && ${startCmd}"]
|
|
4044
4578
|
`;
|
|
@@ -4086,6 +4620,11 @@ COPY --from=builder /app/${appPath}/dist /usr/share/nginx/html
|
|
|
4086
4620
|
RUN printf 'server {\\n listen ${port};\\n root /usr/share/nginx/html;\\n index index.html;\\n location / { try_files $uri $uri/ /index.html; }\\n}\\n' \\
|
|
4087
4621
|
> /etc/nginx/conf.d/default.conf
|
|
4088
4622
|
EXPOSE ${port}
|
|
4623
|
+
# A site answers at its root; Docker, and Dokploy, read the container's
|
|
4624
|
+
# health from it rather than from the process merely running. 127.0.0.1, not
|
|
4625
|
+
# localhost: the server listens on IPv4, and localhost can resolve to ::1.
|
|
4626
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
4627
|
+
CMD wget -qO- http://127.0.0.1:${port}/ > /dev/null 2>&1 || exit 1
|
|
4089
4628
|
CMD ["nginx", "-g", "daemon off;"]
|
|
4090
4629
|
`;
|
|
4091
4630
|
}
|
|
@@ -4584,13 +5123,60 @@ function createEmptyState(stage, projectId, environmentId) {
|
|
|
4584
5123
|
function setApplicationId(state, appName, applicationId) {
|
|
4585
5124
|
state.applications[appName] = applicationId;
|
|
4586
5125
|
}
|
|
5126
|
+
/** Two records naming the same image: by digest when both have one. */
|
|
5127
|
+
function sameImage(a, b) {
|
|
5128
|
+
return a.digest && b.digest ? a.digest === b.digest : a.ref === b.ref;
|
|
5129
|
+
}
|
|
5130
|
+
/** The newest release in `history` after `index` that a rollback may restore. */
|
|
5131
|
+
function restorable(history, index, current) {
|
|
5132
|
+
return history.slice(index + 1).find((entry) => !entry.rolledBack && !sameImage(entry, current));
|
|
5133
|
+
}
|
|
4587
5134
|
/**
|
|
4588
|
-
* Record
|
|
5135
|
+
* Record that `image` went live for `appName` (mutates state): it becomes
|
|
5136
|
+
* `current`, and what was current becomes `previous`. Releasing what already
|
|
5137
|
+
* runs — a redeploy of the same digest — changes nothing.
|
|
4589
5138
|
*/
|
|
4590
|
-
function
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
5139
|
+
function recordRelease(state, appName, image, releasedAt = /* @__PURE__ */ new Date()) {
|
|
5140
|
+
const releases = state.releases?.[appName];
|
|
5141
|
+
if (releases && sameImage(releases.current, image)) return;
|
|
5142
|
+
const entry = {
|
|
5143
|
+
...image,
|
|
5144
|
+
releasedAt: releasedAt.toISOString()
|
|
5145
|
+
};
|
|
5146
|
+
const history = [entry, ...releases?.history ?? []].slice(0, 10);
|
|
5147
|
+
const previous = releases?.current;
|
|
5148
|
+
state.releases = {
|
|
5149
|
+
...state.releases,
|
|
5150
|
+
[appName]: {
|
|
5151
|
+
current: entry,
|
|
5152
|
+
...previous ? { previous } : {},
|
|
5153
|
+
history
|
|
5154
|
+
}
|
|
5155
|
+
};
|
|
5156
|
+
}
|
|
5157
|
+
/**
|
|
5158
|
+
* Record that `appName` was rolled back to `to` (mutates state): every
|
|
5159
|
+
* release newer than it is marked rolled back, so neither this rollback nor
|
|
5160
|
+
* a later one restores it, and `previous` moves to the release before `to`.
|
|
5161
|
+
*/
|
|
5162
|
+
function recordRollback(state, appName, to) {
|
|
5163
|
+
const releases = state.releases?.[appName];
|
|
5164
|
+
if (!releases) return;
|
|
5165
|
+
const index = releases.history.findIndex((entry) => !entry.rolledBack && sameImage(entry, to));
|
|
5166
|
+
if (index === -1) return;
|
|
5167
|
+
const history = releases.history.map((entry, i) => i < index ? {
|
|
5168
|
+
...entry,
|
|
5169
|
+
rolledBack: true
|
|
5170
|
+
} : entry);
|
|
5171
|
+
const current = history[index];
|
|
5172
|
+
const previous = restorable(history, index, current);
|
|
5173
|
+
state.releases = {
|
|
5174
|
+
...state.releases,
|
|
5175
|
+
[appName]: {
|
|
5176
|
+
current,
|
|
5177
|
+
...previous ? { previous } : {},
|
|
5178
|
+
history
|
|
5179
|
+
}
|
|
4594
5180
|
};
|
|
4595
5181
|
}
|
|
4596
5182
|
/**
|
|
@@ -4659,6 +5245,6 @@ function setPostgresBackupId(state, backupId) {
|
|
|
4659
5245
|
if (state.backups) state.backups.postgresBackupId = backupId;
|
|
4660
5246
|
}
|
|
4661
5247
|
//#endregion
|
|
4662
|
-
export {
|
|
5248
|
+
export { describeServices as $, reconcileWorkspace as A, withCustomSecret as At, appEnvKeys as B, validateImageRef as C, markerOf as Ct, backendsOf as D, maskPassword as Dt, FAKE_ENV as E, initStageSecrets as Et, postgresDatabaseNames as F, planFor as G, networkEnv as H, postgresStatements as I, cacheBackendFor as J, resourceName as K, quoteIdentifier as L, LOCAL_CA_PATH as M, forgetState as N, constructGlobs as O, storage_exports as Ot, applyPostgres as P, DEFAULT_IMAGES as Q, composeFor as R, generateSlimDockerfile as S, isOwnedBy as St, resolveHost as T, projectName as Tt, envFor as U, dockerfileOf as V, localRolePassword as W, providerOf as X, eventsBackendFor as Y, storageBackendFor as Z, appPackageName as _, readKey as _t, recordRelease as a, findAvailablePort as at, detectPackageManager as b, imageName as bt, setBackupState as c, loadSecretsForApp as ct, setGeneratedSecret as d, secretsStoreFor as dt, fakeKey as et, setPostgresBackupId as f, store_exports as ft, DockerPushFailed as g, projectKey as gt, DockerBuildFailed as h, keystoreProject as ht, isDnsVerified as i, createEntryWrapper as it, workspace_exports as j, derivedContainers as k, toEmbeddableSecrets as kt, setDnsRecord as l, prepareEntryCredentials as lt, withGeneratedSecrets as m, getKeyPath as mt, getBackupState as n, primaryPortKey as nt, recordRollback as o, isPortAvailable as ot, DeployJournal as p, FileSecretsStore as pt, hostFor as q, getGeneratedSecret as r, createCredentialsPreload as rt, setApplicationId as s, loadEnvFiles as st, createEmptyState as t, portKeys as tt, setDnsVerification as u, isRemoteStore as ut, dockerCommand as v, applicationName as vt, isMainFrontendApp as w, ownershipMarker as wt, generateDockerignore as x, imageRef as xt, siteDockerfile as y, deployIdentity as yt, pgClient as z };
|
|
4663
5249
|
|
|
4664
|
-
//# sourceMappingURL=state-
|
|
5250
|
+
//# sourceMappingURL=state-uuYqnRVO.mjs.map
|