@geekmidas/cli 10.0.0-alpha.72 → 10.0.0-alpha.74
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/{OpenApiTsGenerator-BlP53kf2.cjs → EndpointGenerator-CP6sBvQV.cjs} +4 -510
- package/dist/EndpointGenerator-CP6sBvQV.cjs.map +1 -0
- package/dist/{OpenApiTsGenerator-Dv0gT2DL.mjs → EndpointGenerator-DftalLYg.mjs} +3 -509
- package/dist/EndpointGenerator-DftalLYg.mjs.map +1 -0
- package/dist/{LegacyStateStore-NSHEinQi.mjs → LegacyStateStore-DTo7z924.mjs} +2 -2
- package/dist/{LegacyStateStore-NSHEinQi.mjs.map → LegacyStateStore-DTo7z924.mjs.map} +1 -1
- package/dist/{LegacyStateStore-Dd5IwiyC.cjs → LegacyStateStore-D_78eaHM.cjs} +2 -2
- package/dist/{LegacyStateStore-Dd5IwiyC.cjs.map → LegacyStateStore-D_78eaHM.cjs.map} +1 -1
- package/dist/{LocalStateStore-DTylG4T4.cjs → LocalStateStore-BhrlQIXD.cjs} +2 -2
- package/dist/{LocalStateStore-DTylG4T4.cjs.map → LocalStateStore-BhrlQIXD.cjs.map} +1 -1
- package/dist/{LocalStateStore-DUoG2wA_.mjs → LocalStateStore-CNjgkRk4.mjs} +2 -2
- package/dist/{LocalStateStore-DUoG2wA_.mjs.map → LocalStateStore-CNjgkRk4.mjs.map} +1 -1
- package/dist/OpenApiTsGenerator-CKBCckKz.cjs +514 -0
- package/dist/OpenApiTsGenerator-CKBCckKz.cjs.map +1 -0
- package/dist/OpenApiTsGenerator-nu1r1NgB.mjs +509 -0
- package/dist/OpenApiTsGenerator-nu1r1NgB.mjs.map +1 -0
- package/dist/{S3StateStore-Dxob3PNA.mjs → S3StateStore-g2P7T_Mo.mjs} +2 -2
- package/dist/{S3StateStore-Dxob3PNA.mjs.map → S3StateStore-g2P7T_Mo.mjs.map} +1 -1
- package/dist/{S3StateStore-D7hrU-sy.cjs → S3StateStore-oCxRs6HM.cjs} +2 -2
- package/dist/{S3StateStore-D7hrU-sy.cjs.map → S3StateStore-oCxRs6HM.cjs.map} +1 -1
- package/dist/{SSMStateStore-Dvl4A8Up.mjs → SSMStateStore-CYenunmK.mjs} +2 -2
- package/dist/{SSMStateStore-Dvl4A8Up.mjs.map → SSMStateStore-CYenunmK.mjs.map} +1 -1
- package/dist/{SSMStateStore-DGIKJ4NN.cjs → SSMStateStore-DXp0m7XI.cjs} +2 -2
- package/dist/{SSMStateStore-DGIKJ4NN.cjs.map → SSMStateStore-DXp0m7XI.cjs.map} +1 -1
- package/dist/SubscriberGenerator-5looQ3gB.mjs +448 -0
- package/dist/SubscriberGenerator-5looQ3gB.mjs.map +1 -0
- package/dist/SubscriberGenerator-CajwOa5W.cjs +489 -0
- package/dist/SubscriberGenerator-CajwOa5W.cjs.map +1 -0
- package/dist/{add-XTLU19_E.mjs → add-BTH7WaH3.mjs} +4 -4
- package/dist/{add-XTLU19_E.mjs.map → add-BTH7WaH3.mjs.map} +1 -1
- package/dist/{add-DeC2Kyyv.cjs → add-Djrr12Xc.cjs} +4 -4
- package/dist/{add-DeC2Kyyv.cjs.map → add-Djrr12Xc.cjs.map} +1 -1
- package/dist/{bundler-CNTVLCCA.mjs → bundler-BUojOkof.mjs} +6 -6
- package/dist/bundler-BUojOkof.mjs.map +1 -0
- package/dist/{bundler-CEaR6Dpb.cjs → bundler-utHuESvL.cjs} +6 -6
- package/dist/bundler-utHuESvL.cjs.map +1 -0
- package/dist/{cli-MMgBrHx7.cjs → cli-Bx0sUzxH.cjs} +4 -4
- package/dist/{cli-MMgBrHx7.cjs.map → cli-Bx0sUzxH.cjs.map} +1 -1
- package/dist/{cli-D1arS0p1.mjs → cli-CyYceKTS.mjs} +4 -4
- package/dist/{cli-D1arS0p1.mjs.map → cli-CyYceKTS.mjs.map} +1 -1
- package/dist/{compose-T3hnnjKZ.mjs → compose-DZp8DJ0A.mjs} +419 -70
- package/dist/compose-DZp8DJ0A.mjs.map +1 -0
- package/dist/{compose-B-MME-4C.cjs → compose-iuw8wfHk.cjs} +420 -71
- package/dist/compose-iuw8wfHk.cjs.map +1 -0
- package/dist/{config-j9zBKaEG.d.cts → config-B38YWicj.d.cts} +3 -3
- package/dist/{config-BN6iXvyR.d.mts.map → config-B38YWicj.d.cts.map} +1 -1
- package/dist/{config-BN6iXvyR.d.mts → config-DA0kRTQe.d.mts} +3 -3
- package/dist/{config-j9zBKaEG.d.cts.map → config-DA0kRTQe.d.mts.map} +1 -1
- package/dist/config.cjs +4 -1
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +4 -1
- package/dist/config.mjs.map +1 -1
- package/dist/{credentialSchemas-BmYxnEz_.mjs → credentialSchemas-BJqRJNK4.mjs} +2 -2
- package/dist/{credentialSchemas-BmYxnEz_.mjs.map → credentialSchemas-BJqRJNK4.mjs.map} +1 -1
- package/dist/{credentialSchemas-BcNL1N5R.cjs → credentialSchemas-DCR4Wd1W.cjs} +2 -2
- package/dist/{credentialSchemas-BcNL1N5R.cjs.map → credentialSchemas-DCR4Wd1W.cjs.map} +1 -1
- package/dist/deploy/public.cjs +7 -7
- package/dist/deploy/public.d.cts +4 -4
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +4 -4
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +7 -7
- package/dist/deploy/sniffer-routes-worker.cjs +2 -2
- package/dist/deploy/sniffer-routes-worker.mjs +2 -2
- package/dist/{deploy-ClC53rc6.mjs → deploy-C17K7Imy.mjs} +12 -6
- package/dist/deploy-C17K7Imy.mjs.map +1 -0
- package/dist/{deploy-CZE0m6jd.cjs → deploy-eWorhvQp.cjs} +12 -6
- package/dist/deploy-eWorhvQp.cjs.map +1 -0
- package/dist/{discover-Buwf1z4P.mjs → discover-BPelzeGW.mjs} +21 -2
- package/dist/discover-BPelzeGW.mjs.map +1 -0
- package/dist/{discover-B_IkPukx.d.cts → discover-CU0paUyH.d.cts} +10 -1
- package/dist/discover-CU0paUyH.d.cts.map +1 -0
- package/dist/{discover-B4VjaFJk.cjs → discover-D1It5r_h.cjs} +21 -2
- package/dist/discover-D1It5r_h.cjs.map +1 -0
- package/dist/{discover-B6yPpvRV.d.mts → discover-DFiJxwfv.d.mts} +10 -1
- package/dist/discover-DFiJxwfv.d.mts.map +1 -0
- package/dist/{dokploy-D1UwQKm3.mjs → dokploy-DKa49Nqm.mjs} +2 -2
- package/dist/{dokploy-D1UwQKm3.mjs.map → dokploy-DKa49Nqm.mjs.map} +1 -1
- package/dist/{dokploy-lwmH05zr.cjs → dokploy-DKzcI-RL.cjs} +2 -2
- package/dist/{dokploy-lwmH05zr.cjs.map → dokploy-DKzcI-RL.cjs.map} +1 -1
- package/dist/drivers-BdvtCKc7.cjs +200 -0
- package/dist/drivers-BdvtCKc7.cjs.map +1 -0
- package/dist/drivers-Ch1rJitn.mjs +177 -0
- package/dist/drivers-Ch1rJitn.mjs.map +1 -0
- package/dist/{encryption-D05sNwED.cjs → encryption-DSkJdoyk.cjs} +2 -2
- package/dist/{encryption-D05sNwED.cjs.map → encryption-DSkJdoyk.cjs.map} +1 -1
- package/dist/{encryption-DcrxWl1s.mjs → encryption-sTMz3aWi.mjs} +2 -2
- package/dist/{encryption-DcrxWl1s.mjs.map → encryption-sTMz3aWi.mjs.map} +1 -1
- package/dist/{engine-DmQS3XHX.mjs → engine-B1b4I34Z.mjs} +117 -17
- package/dist/engine-B1b4I34Z.mjs.map +1 -0
- package/dist/{engine-Cry1vwMz.cjs → engine-Bh76sAHy.cjs} +117 -17
- package/dist/engine-Bh76sAHy.cjs.map +1 -0
- package/dist/{github-BchWoiVx.cjs → github-BvN3CfxM.cjs} +2 -2
- package/dist/{github-BchWoiVx.cjs.map → github-BvN3CfxM.cjs.map} +1 -1
- package/dist/{github-CqYOP8cI.mjs → github-C9ODoyZU.mjs} +2 -2
- package/dist/{github-CqYOP8cI.mjs.map → github-C9ODoyZU.mjs.map} +1 -1
- package/dist/{index-DvoxuTqR.d.mts → index-B-1lLGzy.d.mts} +2 -2
- package/dist/{index-Bk3NY6DN.d.cts.map → index-B-1lLGzy.d.mts.map} +1 -1
- package/dist/{index-Bk3NY6DN.d.cts → index-vMidfqhU.d.cts} +2 -2
- package/dist/{index-DvoxuTqR.d.mts.map → index-vMidfqhU.d.cts.map} +1 -1
- package/dist/index.cjs +130 -473
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +122 -465
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-qKeGYq21.cjs → migrate-B0FCZNkp.cjs} +3 -3
- package/dist/{migrate-qKeGYq21.cjs.map → migrate-B0FCZNkp.cjs.map} +1 -1
- package/dist/{migrate-guck0NJK.mjs → migrate-CTtJhGV9.mjs} +3 -3
- package/dist/{migrate-guck0NJK.mjs.map → migrate-CTtJhGV9.mjs.map} +1 -1
- package/dist/openapi.cjs +3 -2
- package/dist/openapi.cjs.map +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +2 -1
- package/dist/openapi.mjs.map +1 -1
- package/dist/ready-BB9xS4S9.cjs +402 -0
- package/dist/ready-BB9xS4S9.cjs.map +1 -0
- package/dist/ready-ZZoNskrr.mjs +373 -0
- package/dist/ready-ZZoNskrr.mjs.map +1 -0
- package/dist/reconcile/public.cjs +1 -1
- package/dist/reconcile/public.d.cts +2 -2
- package/dist/reconcile/public.d.mts +2 -2
- package/dist/reconcile/public.mjs +1 -1
- package/dist/{resolve-BsXEXAUz.mjs → resolve-DSAXj_47.mjs} +3 -3
- package/dist/{resolve-BsXEXAUz.mjs.map → resolve-DSAXj_47.mjs.map} +1 -1
- package/dist/{resolve-wEjEvbJ5.cjs → resolve-D_BU-wKA.cjs} +3 -3
- package/dist/{resolve-wEjEvbJ5.cjs.map → resolve-D_BU-wKA.cjs.map} +1 -1
- package/dist/{rollback-Bk91csFg.mjs → rollback-CWYTUhfW.mjs} +4 -4
- package/dist/{rollback-Bk91csFg.mjs.map → rollback-CWYTUhfW.mjs.map} +1 -1
- package/dist/{rollback-DmcU-efZ.cjs → rollback-DJy3t696.cjs} +4 -4
- package/dist/{rollback-DmcU-efZ.cjs.map → rollback-DJy3t696.cjs.map} +1 -1
- package/dist/{runtime-DHJhe4QI.d.mts → runtime-BzZD5HHi.d.cts} +2 -2
- package/dist/{runtime-CYAO4iZG.d.cts.map → runtime-BzZD5HHi.d.cts.map} +1 -1
- package/dist/{runtime-CYAO4iZG.d.cts → runtime-Cm6J6L7n.d.mts} +2 -2
- package/dist/{runtime-DHJhe4QI.d.mts.map → runtime-Cm6J6L7n.d.mts.map} +1 -1
- package/dist/sandbox/credentials-worker.cjs +2 -2
- package/dist/sandbox/credentials-worker.mjs +2 -2
- package/dist/sandbox/discover-worker.cjs +6 -3
- package/dist/sandbox/discover-worker.cjs.map +1 -1
- package/dist/sandbox/discover-worker.mjs +6 -3
- package/dist/sandbox/discover-worker.mjs.map +1 -1
- package/dist/sandbox/migrate-worker.cjs +1 -1
- package/dist/sandbox/migrate-worker.mjs +1 -1
- package/dist/{sniffer-vAXsU_4G.mjs → sniffer-BFmdZpZ2.mjs} +6 -6
- package/dist/{sniffer-vAXsU_4G.mjs.map → sniffer-BFmdZpZ2.mjs.map} +1 -1
- package/dist/{sniffer-DpHpFZBD.cjs → sniffer-C-T7dSMN.cjs} +6 -6
- package/dist/{sniffer-DpHpFZBD.cjs.map → sniffer-C-T7dSMN.cjs.map} +1 -1
- package/dist/{state-CCuTJNRH.cjs → state-DCwRWyh-.cjs} +954 -29
- package/dist/state-DCwRWyh-.cjs.map +1 -0
- package/dist/{state-BJFherj1.mjs → state-DpkxSwzI.mjs} +889 -24
- package/dist/state-DpkxSwzI.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-CFO3xWoF.cjs → terminal-BL5E0xbR.cjs} +2 -2
- package/dist/{terminal-CFO3xWoF.cjs.map → terminal-BL5E0xbR.cjs.map} +1 -1
- package/dist/{terminal-C6TBo_SA.mjs → terminal-BdvrmkaC.mjs} +2 -2
- package/dist/{terminal-C6TBo_SA.mjs.map → terminal-BdvrmkaC.mjs.map} +1 -1
- package/dist/{types-DkG5WW8l.d.mts → types-Dp9lgx_S.d.mts} +22 -2
- package/dist/types-Dp9lgx_S.d.mts.map +1 -0
- package/dist/{types-BD1jgA85.d.cts → types-TzCChNrA.d.cts} +22 -2
- package/dist/types-TzCChNrA.d.cts.map +1 -0
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace-C7NqCsQF.mjs.map +1 -1
- package/dist/workspace-vdrcRI-m.cjs.map +1 -1
- package/package.json +15 -15
- package/dist/OpenApiTsGenerator-BlP53kf2.cjs.map +0 -1
- package/dist/OpenApiTsGenerator-Dv0gT2DL.mjs.map +0 -1
- package/dist/bundler-CEaR6Dpb.cjs.map +0 -1
- package/dist/bundler-CNTVLCCA.mjs.map +0 -1
- package/dist/compose-B-MME-4C.cjs.map +0 -1
- package/dist/compose-T3hnnjKZ.mjs.map +0 -1
- package/dist/deploy-CZE0m6jd.cjs.map +0 -1
- package/dist/deploy-ClC53rc6.mjs.map +0 -1
- package/dist/discover-B4VjaFJk.cjs.map +0 -1
- package/dist/discover-B6yPpvRV.d.mts.map +0 -1
- package/dist/discover-B_IkPukx.d.cts.map +0 -1
- package/dist/discover-Buwf1z4P.mjs.map +0 -1
- package/dist/engine-Cry1vwMz.cjs.map +0 -1
- package/dist/engine-DmQS3XHX.mjs.map +0 -1
- package/dist/fakes-C9RiT6XG.mjs +0 -125
- package/dist/fakes-C9RiT6XG.mjs.map +0 -1
- package/dist/fakes-zW-jNAbQ.cjs +0 -148
- package/dist/fakes-zW-jNAbQ.cjs.map +0 -1
- package/dist/ready-B-7LayMF.cjs +0 -872
- package/dist/ready-B-7LayMF.cjs.map +0 -1
- package/dist/ready-ULc5BTOb.mjs +0 -819
- package/dist/ready-ULc5BTOb.mjs.map +0 -1
- package/dist/state-BJFherj1.mjs.map +0 -1
- package/dist/state-CCuTJNRH.cjs.map +0 -1
- package/dist/types-BD1jgA85.d.cts.map +0 -1
- package/dist/types-DkG5WW8l.d.mts.map +0 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
2
2
|
import { a as runOutput, i as run } from "./run-BJLIerMM.mjs";
|
|
3
|
-
import { r as discover } from "./discover-
|
|
3
|
+
import { r as discover } from "./discover-BPelzeGW.mjs";
|
|
4
4
|
import { t as appKey } from "./derive-B4vDpzgP.mjs";
|
|
5
5
|
import { M as LOGS_PORT, N as resolveLogs } from "./workspace-C7NqCsQF.mjs";
|
|
6
6
|
import { t as defineTarget } from "./define-DR8xGYfL.mjs";
|
|
7
|
-
import { $ as
|
|
8
|
-
import {
|
|
7
|
+
import { $ as constructGlobs, C as validateImageRef, Ct as composeBuildPaths, D as isOtlpDestination, E as resolveHost, Et as WORKER_PORT, F as devServicesUsed, H as workerUnits, It as portKeys, Lt as primaryPortKey, Nt as DEFAULT_IMAGES, O as otelEnv, Ot as findBuildRoot, P as assertExternalServices, St as hostFor, T as isMainFrontendApp, Tt as TURBO_VERSION, U as encryptSecrets, V as suppliedOnly, _t as workerEnvKeys, a as recordRelease, at as applyBuckets, b as ensureDockerignore, bt as planFor, ct as bucketPolicies, ft as composeFor, g as credentialsFileContent, gt as networkEnv, h as credentialsBuildArg, ht as appEnvKeys, m as withGeneratedSecrets, mt as pgClient, nn as imageRef, ot as applyPolicies, p as DeployJournal, pt as bucketClient, sn as initStageSecrets, st as applyPostgres, t as createEmptyState, ut as postgresStatements, v as appDockerfile, vt as envFor, w as NoDomainForStage, wt as imageLayout, x as workerDockerfile, yt as localRolePassword, z as reportDevServices } from "./state-DpkxSwzI.mjs";
|
|
8
|
+
import { i as DEFAULT_EVENTS } from "./SubscriberGenerator-5looQ3gB.mjs";
|
|
9
|
+
import { i as installedFrom } from "./drivers-Ch1rJitn.mjs";
|
|
9
10
|
import { u as migrateDatabases } from "./databases-hUHTuwhx.mjs";
|
|
10
|
-
import { n as assertStageCredentials } from "./credentialSchemas-
|
|
11
|
+
import { n as assertStageCredentials } from "./credentialSchemas-BJqRJNK4.mjs";
|
|
11
12
|
import { dirname, join } from "node:path";
|
|
12
13
|
import { existsSync } from "node:fs";
|
|
13
14
|
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
@@ -444,6 +445,152 @@ function logsAccess(logs, ctx) {
|
|
|
444
445
|
];
|
|
445
446
|
}
|
|
446
447
|
//#endregion
|
|
448
|
+
//#region src/compose/redis.ts
|
|
449
|
+
/**
|
|
450
|
+
* The stack's Redis: what every declared cache lives in when a workspace runs
|
|
451
|
+
* as a `gkm compose` stack, on the local stage and on a deployed one alike.
|
|
452
|
+
*
|
|
453
|
+
* A cache is the one store whose contents can always be rebuilt, so unlike
|
|
454
|
+
* Mailpit and MinIO it is a production service, not a dev stand-in: it runs
|
|
455
|
+
* for every stage, on the compose network only, with nothing published. It
|
|
456
|
+
* is bounded (`maxmemory`, evicting the least recently used key), persisted
|
|
457
|
+
* (an append-only file on a named volume, kept by `--down`) and password
|
|
458
|
+
* protected — the stage's own password, generated once like its seed.
|
|
459
|
+
*
|
|
460
|
+
* A stage that sets a cache's URL in its secrets — a managed Redis — keeps
|
|
461
|
+
* it, and a stack whose every cache is set that way runs no Redis at all.
|
|
462
|
+
*
|
|
463
|
+
* Pure: the stack decides it, and the phases keep the password.
|
|
464
|
+
*/
|
|
465
|
+
/**
|
|
466
|
+
* Where a stack's caches live: its own Redis, whatever the deploy target's
|
|
467
|
+
* default is. A stack is one machine running containers, and a Redis beside
|
|
468
|
+
* the apps is cheaper for a cache than a table in the database they share.
|
|
469
|
+
*/
|
|
470
|
+
const STACK_CACHE = "redis";
|
|
471
|
+
/** Its compose service name — the host every cache URL points at. */
|
|
472
|
+
const REDIS_SERVICE = "redis";
|
|
473
|
+
/** The port it answers on, inside the network. */
|
|
474
|
+
const REDIS_PORT = 6379;
|
|
475
|
+
/** Reconcile's pin, so `gkm dev` and the stack run one Redis. */
|
|
476
|
+
const REDIS_IMAGE = DEFAULT_IMAGES.redis;
|
|
477
|
+
/**
|
|
478
|
+
* How much it may hold before it evicts. A cache that grows until the box
|
|
479
|
+
* runs out of memory takes the apps with it; 256 MB is a lot of sessions and
|
|
480
|
+
* little of a small server. A project's `docker-compose.<stage>.yml` can
|
|
481
|
+
* replace the command to change it.
|
|
482
|
+
*/
|
|
483
|
+
const REDIS_MAXMEMORY = "256mb";
|
|
484
|
+
/** The stage secret holding its password. */
|
|
485
|
+
const REDIS_PASSWORD_KEY = "REDIS_PASSWORD";
|
|
486
|
+
/** Redis's own count of logical databases, before `--databases` raises it. */
|
|
487
|
+
const REDIS_DATABASES = 16;
|
|
488
|
+
/** A deployed stage that runs the stack's Redis and has no password yet. */
|
|
489
|
+
var RedisPasswordMissing = class extends Error {
|
|
490
|
+
stage;
|
|
491
|
+
constructor(stage) {
|
|
492
|
+
super(`The stage '${stage}' runs the stack's Redis and has no ${REDIS_PASSWORD_KEY} in its secrets. Run gkm compose --stage ${stage} without --dry-run once to generate it, or set one: gkm secrets:set ${REDIS_PASSWORD_KEY} '…' --stage ${stage}`);
|
|
493
|
+
this.stage = stage;
|
|
494
|
+
this.name = "RedisPasswordMissing";
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
/**
|
|
498
|
+
* Whether a stage's stack runs Redis: a cache is declared and the stage's
|
|
499
|
+
* secrets do not set the URL of every one.
|
|
500
|
+
*/
|
|
501
|
+
function runsRedis(manifest, custom) {
|
|
502
|
+
return Object.values(manifest).some((declaration) => declaration?.kind === "cache" && (declaration.provides ?? []).some((key) => custom[key] === void 0));
|
|
503
|
+
}
|
|
504
|
+
/** A password: 256 random bits, URL-safe, so it sits in a URL unescaped. */
|
|
505
|
+
function generateRedisPassword() {
|
|
506
|
+
return randomBytes(32).toString("base64url");
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* The stage's secrets with a generated Redis password, where it has none.
|
|
510
|
+
* Pure, like `withGeneratedSecrets`: the caller keeps the result once the run
|
|
511
|
+
* goes ahead.
|
|
512
|
+
*/
|
|
513
|
+
function withRedisPassword(secrets) {
|
|
514
|
+
if (secrets.custom?.["REDIS_PASSWORD"]) return {
|
|
515
|
+
secrets,
|
|
516
|
+
generated: []
|
|
517
|
+
};
|
|
518
|
+
return {
|
|
519
|
+
secrets: {
|
|
520
|
+
...secrets,
|
|
521
|
+
custom: {
|
|
522
|
+
...secrets.custom,
|
|
523
|
+
[REDIS_PASSWORD_KEY]: generateRedisPassword()
|
|
524
|
+
},
|
|
525
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
526
|
+
},
|
|
527
|
+
generated: [REDIS_PASSWORD_KEY]
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* The stack's Redis for one stage, or nothing where no cache needs it.
|
|
532
|
+
*
|
|
533
|
+
* Each cache it holds gets a logical database of its own, by the order of
|
|
534
|
+
* their ids, so two caches never read each other's keys.
|
|
535
|
+
*/
|
|
536
|
+
function stackRedis(options) {
|
|
537
|
+
const { plan, stage, local, custom } = options;
|
|
538
|
+
const caches = plan.resources.filter((r) => r.kind === "cache" && custom[r.envKey] === void 0).sort((a, b) => a.id.localeCompare(b.id));
|
|
539
|
+
if (caches.length === 0) return void 0;
|
|
540
|
+
const set = custom[REDIS_PASSWORD_KEY];
|
|
541
|
+
if (!set && !local) throw new RedisPasswordMissing(stage);
|
|
542
|
+
const password = set ?? "geekmidas";
|
|
543
|
+
const auth = encodeURIComponent(password);
|
|
544
|
+
return {
|
|
545
|
+
password,
|
|
546
|
+
passwordFromSecrets: Boolean(set),
|
|
547
|
+
urls: Object.fromEntries(caches.map((cache, index) => [cache.envKey, `redis://:${auth}@${REDIS_SERVICE}:${REDIS_PORT}/${index}`])),
|
|
548
|
+
env: {
|
|
549
|
+
[REDIS_PASSWORD_KEY]: password,
|
|
550
|
+
REDISCLI_AUTH: password
|
|
551
|
+
},
|
|
552
|
+
databases: Math.max(REDIS_DATABASES, caches.length)
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Redis's service: bounded, persisted and password protected, on the
|
|
557
|
+
* network alone.
|
|
558
|
+
*
|
|
559
|
+
* The command runs the image's own entrypoint with `redis-server` first, so
|
|
560
|
+
* it still drops to the `redis` user and owns `/data`; the password is read
|
|
561
|
+
* from the env file by the shell (`$$` is compose's escape for `$`), so
|
|
562
|
+
* neither the compose file nor the container's command holds it.
|
|
563
|
+
*/
|
|
564
|
+
function redisService(redis) {
|
|
565
|
+
const flags = [
|
|
566
|
+
`--requirepass "$$${REDIS_PASSWORD_KEY}"`,
|
|
567
|
+
`--maxmemory ${REDIS_MAXMEMORY}`,
|
|
568
|
+
"--maxmemory-policy allkeys-lru",
|
|
569
|
+
"--appendonly yes",
|
|
570
|
+
...redis.databases > REDIS_DATABASES ? [`--databases ${redis.databases}`] : []
|
|
571
|
+
];
|
|
572
|
+
return {
|
|
573
|
+
image: REDIS_IMAGE,
|
|
574
|
+
restart: "unless-stopped",
|
|
575
|
+
command: [
|
|
576
|
+
"sh",
|
|
577
|
+
"-c",
|
|
578
|
+
`exec docker-entrypoint.sh redis-server ${flags.join(" ")}`
|
|
579
|
+
],
|
|
580
|
+
env_file: [{
|
|
581
|
+
path: `./${REDIS_SERVICE}.env`,
|
|
582
|
+
format: "raw"
|
|
583
|
+
}],
|
|
584
|
+
volumes: ["redis-data:/data"],
|
|
585
|
+
healthcheck: {
|
|
586
|
+
test: ["CMD-SHELL", "redis-cli ping | grep -q PONG"],
|
|
587
|
+
interval: "5s",
|
|
588
|
+
timeout: "3s",
|
|
589
|
+
retries: 10
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
//#endregion
|
|
447
594
|
//#region src/compose/caddyfile.ts
|
|
448
595
|
/** The Caddyfile for a stack's edge. */
|
|
449
596
|
function edgeCaddyfile(sites, options) {
|
|
@@ -590,7 +737,7 @@ var StageSeedMissing = class extends Error {
|
|
|
590
737
|
var NothingToCompose = class extends Error {
|
|
591
738
|
root;
|
|
592
739
|
constructor(root) {
|
|
593
|
-
super(`${root} declares no RestApi and no
|
|
740
|
+
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.`);
|
|
594
741
|
this.root = root;
|
|
595
742
|
this.name = "NothingToCompose";
|
|
596
743
|
}
|
|
@@ -640,7 +787,7 @@ function composeStack(input) {
|
|
|
640
787
|
const plan = planFor(manifest, stage, provisionOrder(manifest), {
|
|
641
788
|
localStage: workspace.stages.local,
|
|
642
789
|
events: DEFAULT_EVENTS.server,
|
|
643
|
-
cache:
|
|
790
|
+
cache: STACK_CACHE,
|
|
644
791
|
edge: false
|
|
645
792
|
});
|
|
646
793
|
const byId = new Map(plan.resources.map((r) => [r.id, r]));
|
|
@@ -657,7 +804,12 @@ function composeStack(input) {
|
|
|
657
804
|
identity,
|
|
658
805
|
images: input.images
|
|
659
806
|
});
|
|
660
|
-
|
|
807
|
+
const workers = stackWorkers(workerUnits(workspace, manifest, input.background ?? {}), {
|
|
808
|
+
stage,
|
|
809
|
+
identity,
|
|
810
|
+
images: input.images
|
|
811
|
+
});
|
|
812
|
+
if (apps.length === 0 && workers.length === 0) throw new NothingToCompose(workspace.root);
|
|
661
813
|
const owners = /* @__PURE__ */ new Map();
|
|
662
814
|
for (const resource of plan.resources) {
|
|
663
815
|
owners.set(resource.envKey, resource);
|
|
@@ -672,6 +824,7 @@ function composeStack(input) {
|
|
|
672
824
|
const site = manifest[app.id];
|
|
673
825
|
reads.set(app.name, site?.kind === "site" ? Object.values(publicEnvFor(site, manifest)) : [...appEnvKeys(manifest, app.name, input.runnables) ?? []]);
|
|
674
826
|
}
|
|
827
|
+
for (const worker of workers) reads.set(worker.name, [...workerEnvKeys(manifest, worker.id, input.runnables) ?? []]);
|
|
675
828
|
const domain = workspace.deploy?.domains?.[stage];
|
|
676
829
|
const logsConfig = resolveLogs(workspace.deploy?.compose?.logs);
|
|
677
830
|
const logs = logsConfig ? stackLogs({
|
|
@@ -694,7 +847,13 @@ function composeStack(input) {
|
|
|
694
847
|
allow: input.allowDevServices ?? [],
|
|
695
848
|
...domain ? { domain } : {}
|
|
696
849
|
});
|
|
697
|
-
const
|
|
850
|
+
const redis = stackRedis({
|
|
851
|
+
plan,
|
|
852
|
+
stage,
|
|
853
|
+
local,
|
|
854
|
+
custom
|
|
855
|
+
});
|
|
856
|
+
const infra = [...plan.containers.filter((c) => !NOT_RUN[c]).filter((c) => c !== "minio" || services.minio.length > 0).filter((c) => c !== "mailpit" || services.mailpit.length > 0).filter((c) => c !== "redis" || redis !== void 0), ...logs ? [LOGS_SERVICE] : []].sort();
|
|
698
857
|
const storage = infra.includes("minio") ? (() => {
|
|
699
858
|
const buckets = services.minio.map((id) => byId.get(id)?.name).filter((name) => Boolean(name)).sort();
|
|
700
859
|
return {
|
|
@@ -758,10 +917,27 @@ function composeStack(input) {
|
|
|
758
917
|
return value;
|
|
759
918
|
}
|
|
760
919
|
if (custom[key] !== void 0) return custom[key];
|
|
920
|
+
if (owner?.kind === "cache") return redis?.urls[key];
|
|
761
921
|
const surface = surfaceUrls.get(key);
|
|
762
922
|
if (app.kind === "rest-api" && surface && surface !== app.id) return inside[key];
|
|
763
923
|
return outside[key];
|
|
764
924
|
};
|
|
925
|
+
/** Each key one backend process reads, resolved for this stage. */
|
|
926
|
+
const valuesFor = (app, keys) => {
|
|
927
|
+
const values = {};
|
|
928
|
+
for (const key of [...keys].sort()) {
|
|
929
|
+
const owner = owners.get(key) ?? storageOwner(key, plan);
|
|
930
|
+
if (!local && owner?.kind === "email" && key.endsWith("_INBOX_URL")) continue;
|
|
931
|
+
if (STORAGE_KEYS.includes(key)) {
|
|
932
|
+
const value = custom[key] ?? storageValue(key, storage);
|
|
933
|
+
if (value !== void 0) values[key] = value;
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
const value = valueFor(app, key, owner);
|
|
937
|
+
if (value !== void 0) values[key] = value;
|
|
938
|
+
}
|
|
939
|
+
return values;
|
|
940
|
+
};
|
|
765
941
|
for (const app of apps) {
|
|
766
942
|
if (app.kind === "site") {
|
|
767
943
|
const site = manifest[app.id];
|
|
@@ -775,18 +951,7 @@ function composeStack(input) {
|
|
|
775
951
|
continue;
|
|
776
952
|
}
|
|
777
953
|
const keys = appEnvKeys(manifest, app.name, input.runnables) ?? /* @__PURE__ */ new Set();
|
|
778
|
-
const values =
|
|
779
|
-
for (const key of [...keys].sort()) {
|
|
780
|
-
const owner = owners.get(key) ?? storageOwner(key, plan);
|
|
781
|
-
if (!local && owner?.kind === "email" && key.endsWith("_INBOX_URL")) continue;
|
|
782
|
-
if (STORAGE_KEYS.includes(key)) {
|
|
783
|
-
const value = custom[key] ?? storageValue(key, storage);
|
|
784
|
-
if (value !== void 0) values[key] = value;
|
|
785
|
-
continue;
|
|
786
|
-
}
|
|
787
|
-
const value = valueFor(app, key, owner);
|
|
788
|
-
if (value !== void 0) values[key] = value;
|
|
789
|
-
}
|
|
954
|
+
const values = valuesFor(app, keys);
|
|
790
955
|
const trusted = provideKey(app.id, "trustedOrigins");
|
|
791
956
|
if (keys.has(trusted)) {
|
|
792
957
|
const internal = (byId.get(app.id)?.callers ?? []).map((caller) => appById.get(caller)).filter((caller) => caller?.kind === "rest-api").map((caller) => `http://${caller.name}:${caller.port}`);
|
|
@@ -805,15 +970,35 @@ function composeStack(input) {
|
|
|
805
970
|
};
|
|
806
971
|
for (const [key, value] of Object.entries(app.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(app.name, key);
|
|
807
972
|
}
|
|
973
|
+
for (const worker of workers) {
|
|
974
|
+
const keys = workerEnvKeys(manifest, worker.id, input.runnables) ?? /* @__PURE__ */ new Set();
|
|
975
|
+
worker.env = {
|
|
976
|
+
...valuesFor({
|
|
977
|
+
name: worker.name,
|
|
978
|
+
id: worker.id,
|
|
979
|
+
kind: "rest-api"
|
|
980
|
+
}, keys),
|
|
981
|
+
...otelEnv({
|
|
982
|
+
...custom,
|
|
983
|
+
...logs?.appEnv
|
|
984
|
+
}, worker.name),
|
|
985
|
+
NODE_ENV: "production",
|
|
986
|
+
PORT: String(worker.port),
|
|
987
|
+
STAGE: stage
|
|
988
|
+
};
|
|
989
|
+
for (const [key, value] of Object.entries(worker.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(worker.name, key);
|
|
990
|
+
}
|
|
808
991
|
const layout = input.layout ?? defaultLayout(workspace.root);
|
|
809
992
|
const compose = stackFile({
|
|
810
993
|
project,
|
|
811
994
|
plan,
|
|
812
995
|
infra,
|
|
813
996
|
apps,
|
|
997
|
+
workers,
|
|
814
998
|
master: credential.master,
|
|
815
999
|
...storage ? { storage } : {},
|
|
816
1000
|
...logs ? { logs } : {},
|
|
1001
|
+
...redis ? { redis } : {},
|
|
817
1002
|
https,
|
|
818
1003
|
http,
|
|
819
1004
|
composeDir: join(workspace.root, stackDir(stage)),
|
|
@@ -838,7 +1023,20 @@ function composeStack(input) {
|
|
|
838
1023
|
dockerfiles[app.build.dockerfile] = appDockerfile(app.name, workspace.apps[app.name], {
|
|
839
1024
|
layout,
|
|
840
1025
|
workspaceRoot: workspace.root,
|
|
841
|
-
manifest
|
|
1026
|
+
manifest,
|
|
1027
|
+
cache: STACK_CACHE
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
for (const worker of workers) {
|
|
1031
|
+
if (!worker.build) continue;
|
|
1032
|
+
dockerfiles[worker.build.dockerfile] = workerDockerfile({
|
|
1033
|
+
id: worker.id,
|
|
1034
|
+
name: worker.name,
|
|
1035
|
+
app: worker.host
|
|
1036
|
+
}, workspace.apps[worker.host], {
|
|
1037
|
+
layout,
|
|
1038
|
+
workspaceRoot: workspace.root,
|
|
1039
|
+
cache: STACK_CACHE
|
|
842
1040
|
});
|
|
843
1041
|
}
|
|
844
1042
|
return {
|
|
@@ -846,6 +1044,7 @@ function composeStack(input) {
|
|
|
846
1044
|
stage,
|
|
847
1045
|
local,
|
|
848
1046
|
apps,
|
|
1047
|
+
workers,
|
|
849
1048
|
infra,
|
|
850
1049
|
compose,
|
|
851
1050
|
caddyfile,
|
|
@@ -854,7 +1053,8 @@ function composeStack(input) {
|
|
|
854
1053
|
credential,
|
|
855
1054
|
...storage ? { storage } : {},
|
|
856
1055
|
devServices: local ? [] : devServicesUsed(services),
|
|
857
|
-
...logs ? { logs } : {}
|
|
1056
|
+
...logs ? { logs } : {},
|
|
1057
|
+
...redis ? { redis } : {}
|
|
858
1058
|
};
|
|
859
1059
|
}
|
|
860
1060
|
/**
|
|
@@ -930,6 +1130,19 @@ function stackApps(workspace, manifest, plan, options) {
|
|
|
930
1130
|
}
|
|
931
1131
|
return apps.sort((a, b) => a.name.localeCompare(b.name));
|
|
932
1132
|
}
|
|
1133
|
+
/** Each worker's image, and where it is built from. */
|
|
1134
|
+
function stackWorkers(units, options) {
|
|
1135
|
+
return units.map((unit) => ({
|
|
1136
|
+
app: unit.name,
|
|
1137
|
+
name: unit.name,
|
|
1138
|
+
id: unit.id,
|
|
1139
|
+
host: unit.app,
|
|
1140
|
+
port: WORKER_PORT,
|
|
1141
|
+
ref: validateImageRef(imageRef(options.identity, unit.name, options.images.registry, options.images.tag)),
|
|
1142
|
+
tag: options.images.tag,
|
|
1143
|
+
...options.images.mode === "build" ? { build: { dockerfile: `${stackDir(options.stage)}/Dockerfile.${unit.name}` } } : {}
|
|
1144
|
+
}));
|
|
1145
|
+
}
|
|
933
1146
|
/**
|
|
934
1147
|
* The host an app answers on for a stage: the `gkm dev` edge's
|
|
935
1148
|
* `*.localhost` names for the local stage, and the stage's domain — by the
|
|
@@ -981,6 +1194,7 @@ function stackFile(options) {
|
|
|
981
1194
|
};
|
|
982
1195
|
minio.ports = ["127.0.0.1::9000"];
|
|
983
1196
|
}
|
|
1197
|
+
if (services["redis"] && options.redis) services[REDIS_SERVICE] = redisService(options.redis);
|
|
984
1198
|
if (options.logs) services[LOGS_SERVICE] = logsService(options.logs);
|
|
985
1199
|
for (const app of apps) services[app.name] = {
|
|
986
1200
|
image: app.ref,
|
|
@@ -1003,6 +1217,22 @@ function stackFile(options) {
|
|
|
1003
1217
|
} : {},
|
|
1004
1218
|
healthcheck: probe(app.port, app.kind === "site" ? "/" : "/health")
|
|
1005
1219
|
};
|
|
1220
|
+
for (const worker of options.workers) services[worker.name] = {
|
|
1221
|
+
image: worker.ref,
|
|
1222
|
+
...worker.build ? { build: composeBuildPaths({
|
|
1223
|
+
composeDir: options.composeDir,
|
|
1224
|
+
buildRoot: options.buildRoot,
|
|
1225
|
+
workspaceRoot: options.workspaceRoot,
|
|
1226
|
+
dockerfile: worker.build.dockerfile
|
|
1227
|
+
}) } : {},
|
|
1228
|
+
restart: "unless-stopped",
|
|
1229
|
+
env_file: [{
|
|
1230
|
+
path: `./${worker.name}.env`,
|
|
1231
|
+
format: "raw"
|
|
1232
|
+
}],
|
|
1233
|
+
...infra.length ? { depends_on: Object.fromEntries(infra.map((name) => [name, HEALTHY])) } : {},
|
|
1234
|
+
healthcheck: probe(worker.port, "/health")
|
|
1235
|
+
};
|
|
1006
1236
|
services.caddy = {
|
|
1007
1237
|
image: DEFAULT_IMAGES.caddy,
|
|
1008
1238
|
restart: "unless-stopped",
|
|
@@ -1098,9 +1328,38 @@ function withBuildCredentials(stack, credentials) {
|
|
|
1098
1328
|
}
|
|
1099
1329
|
};
|
|
1100
1330
|
});
|
|
1331
|
+
const workers = stack.workers.map((worker) => {
|
|
1332
|
+
const own = credentials[worker.name];
|
|
1333
|
+
const service = services[worker.name];
|
|
1334
|
+
if (!own || !service?.build) return worker;
|
|
1335
|
+
const [key, value] = own.buildArg.split("=");
|
|
1336
|
+
services[worker.name] = {
|
|
1337
|
+
...service,
|
|
1338
|
+
build: {
|
|
1339
|
+
...service.build,
|
|
1340
|
+
args: {
|
|
1341
|
+
...service.build.args,
|
|
1342
|
+
[key]: value
|
|
1343
|
+
},
|
|
1344
|
+
secrets: [{
|
|
1345
|
+
source: credentialsSecret(worker.name),
|
|
1346
|
+
target: "gkm_credentials"
|
|
1347
|
+
}]
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
secrets[credentialsSecret(worker.name)] = { file: `./${credentialsFile(worker.name)}` };
|
|
1351
|
+
return {
|
|
1352
|
+
...worker,
|
|
1353
|
+
env: {
|
|
1354
|
+
...worker.env,
|
|
1355
|
+
GKM_MASTER_KEY: own.masterKey
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
});
|
|
1101
1359
|
return {
|
|
1102
1360
|
...stack,
|
|
1103
1361
|
apps,
|
|
1362
|
+
workers,
|
|
1104
1363
|
compose: {
|
|
1105
1364
|
...stack.compose,
|
|
1106
1365
|
services,
|
|
@@ -1286,10 +1545,12 @@ async function validateCompose(ctx, deps, forced) {
|
|
|
1286
1545
|
const root = workspace.root;
|
|
1287
1546
|
const mode = forced ?? (ctx.tagGiven ? "pull" : "build");
|
|
1288
1547
|
const runnables = {};
|
|
1548
|
+
const background = {};
|
|
1289
1549
|
const manifest = await discover({
|
|
1290
1550
|
patterns: constructGlobs(workspace),
|
|
1291
1551
|
cwd: root,
|
|
1292
|
-
runnables
|
|
1552
|
+
runnables,
|
|
1553
|
+
background
|
|
1293
1554
|
});
|
|
1294
1555
|
const { secrets, generated } = await stageSecrets(ctx, manifest);
|
|
1295
1556
|
await assertStageCredentials({
|
|
@@ -1305,6 +1566,7 @@ async function validateCompose(ctx, deps, forced) {
|
|
|
1305
1566
|
workspace,
|
|
1306
1567
|
manifest,
|
|
1307
1568
|
runnables,
|
|
1569
|
+
background,
|
|
1308
1570
|
stage,
|
|
1309
1571
|
identity,
|
|
1310
1572
|
images: {
|
|
@@ -1318,25 +1580,45 @@ async function validateCompose(ctx, deps, forced) {
|
|
|
1318
1580
|
...layout ? { layout } : {}
|
|
1319
1581
|
});
|
|
1320
1582
|
reportDevServices(ctx, composed.devServices);
|
|
1583
|
+
if (mode === "build") assertRedisClient(workspace, composed);
|
|
1321
1584
|
const credentials = {};
|
|
1322
|
-
if (mode === "build")
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
iv
|
|
1328
|
-
};
|
|
1329
|
-
ctx.secrets.mask(masterKey);
|
|
1330
|
-
masterKeys[app.name] = {
|
|
1331
|
-
masterKey,
|
|
1332
|
-
buildArg: credentialsBuildArg({
|
|
1585
|
+
if (mode === "build") {
|
|
1586
|
+
for (const app of composed.apps) {
|
|
1587
|
+
if (app.kind !== "rest-api" || !app.env || !app.build) continue;
|
|
1588
|
+
const { encrypted, iv, masterKey } = encryptSecrets(app.env);
|
|
1589
|
+
credentials[app.name] = {
|
|
1333
1590
|
encrypted,
|
|
1334
1591
|
iv
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1592
|
+
};
|
|
1593
|
+
ctx.secrets.mask(masterKey);
|
|
1594
|
+
masterKeys[app.name] = {
|
|
1595
|
+
masterKey,
|
|
1596
|
+
buildArg: credentialsBuildArg({
|
|
1597
|
+
encrypted,
|
|
1598
|
+
iv
|
|
1599
|
+
})
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
for (const worker of composed.workers) {
|
|
1603
|
+
if (!worker.env || !worker.build) continue;
|
|
1604
|
+
const { encrypted, iv, masterKey } = encryptSecrets(worker.env);
|
|
1605
|
+
credentials[worker.name] = {
|
|
1606
|
+
encrypted,
|
|
1607
|
+
iv
|
|
1608
|
+
};
|
|
1609
|
+
ctx.secrets.mask(masterKey);
|
|
1610
|
+
masterKeys[worker.name] = {
|
|
1611
|
+
masterKey,
|
|
1612
|
+
buildArg: credentialsBuildArg({
|
|
1613
|
+
encrypted,
|
|
1614
|
+
iv
|
|
1615
|
+
})
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1337
1618
|
}
|
|
1338
1619
|
const stack = withBuildCredentials(composed, masterKeys);
|
|
1339
|
-
if (mode === "pull" && !ctx.dryRun) await assertImagesExist(deps.docker, tag, stack.apps);
|
|
1620
|
+
if (mode === "pull" && !ctx.dryRun) await assertImagesExist(deps.docker, tag, [...stack.apps, ...stack.workers]);
|
|
1621
|
+
if (stack.redis && !stack.local) ctx.secrets.mask(stack.redis.password);
|
|
1340
1622
|
if (stack.logs && (!stack.local || stack.logs.passwordFromSecrets)) {
|
|
1341
1623
|
ctx.secrets.mask(stack.logs.password);
|
|
1342
1624
|
ctx.secrets.mask(stack.logs.appEnv.OTEL_EXPORTER_OTLP_HEADERS);
|
|
@@ -1374,6 +1656,28 @@ async function validateCompose(ctx, deps, forced) {
|
|
|
1374
1656
|
changes: []
|
|
1375
1657
|
};
|
|
1376
1658
|
}
|
|
1659
|
+
/** A stack whose caches are in Redis, built for an app that has no client. */
|
|
1660
|
+
var RedisClientMissing = class extends Error {
|
|
1661
|
+
apps;
|
|
1662
|
+
constructor(apps) {
|
|
1663
|
+
super(`The stack keeps every cache in Redis, and ${apps.join(", ")} ${apps.length === 1 ? "does" : "do"} not depend on ioredis, the client the generated server reaches it with. Add it to the workspace: pnpm add ioredis`);
|
|
1664
|
+
this.apps = apps;
|
|
1665
|
+
this.name = "RedisClientMissing";
|
|
1666
|
+
}
|
|
1667
|
+
};
|
|
1668
|
+
/**
|
|
1669
|
+
* Every backend and worker image built for a stack with a cache can resolve
|
|
1670
|
+
* `ioredis` — listed by its app or a directory above it, the way Node and
|
|
1671
|
+
* the image's install find it.
|
|
1672
|
+
*/
|
|
1673
|
+
function assertRedisClient(workspace, stack) {
|
|
1674
|
+
if (!stack.plan.resources.some((r) => r.kind === "cache")) return;
|
|
1675
|
+
const missing = [.../* @__PURE__ */ new Set([...stack.apps.filter((app) => app.kind === "rest-api").map((a) => a.name), ...stack.workers.map((worker) => worker.host)])].filter((name) => {
|
|
1676
|
+
const app = workspace.apps[name];
|
|
1677
|
+
return app && !installedFrom(join(workspace.root, app.path), "ioredis");
|
|
1678
|
+
}).sort();
|
|
1679
|
+
if (missing.length > 0) throw new RedisClientMissing(missing);
|
|
1680
|
+
}
|
|
1377
1681
|
/**
|
|
1378
1682
|
* The stage's secrets — and, for a deployed stage, everything it generates
|
|
1379
1683
|
* once: its seed, and each declared secret and keyring. Kept only once the
|
|
@@ -1391,8 +1695,16 @@ async function stageSecrets(ctx, manifest) {
|
|
|
1391
1695
|
secrets: withSeed.secrets,
|
|
1392
1696
|
generated: []
|
|
1393
1697
|
};
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1698
|
+
const withRedis = runsRedis(manifest, withLogs.secrets.custom ?? {}) ? withRedisPassword(withLogs.secrets) : {
|
|
1699
|
+
secrets: withLogs.secrets,
|
|
1700
|
+
generated: []
|
|
1701
|
+
};
|
|
1702
|
+
const secrets = withRedis.secrets;
|
|
1703
|
+
const generated = [
|
|
1704
|
+
...withSeed.generated,
|
|
1705
|
+
...withLogs.generated,
|
|
1706
|
+
...withRedis.generated
|
|
1707
|
+
];
|
|
1396
1708
|
if (secrets.seed) ctx.secrets.mask(secrets.seed);
|
|
1397
1709
|
for (const value of Object.values(secrets.custom ?? {})) ctx.secrets.mask(value);
|
|
1398
1710
|
return {
|
|
@@ -1434,6 +1746,19 @@ async function planCompose(ctx, run) {
|
|
|
1434
1746
|
action: run.previous[app.name] ? "reuse" : "create"
|
|
1435
1747
|
});
|
|
1436
1748
|
}
|
|
1749
|
+
for (const worker of run.stack.workers) {
|
|
1750
|
+
planned({
|
|
1751
|
+
key: `image:${worker.name}`,
|
|
1752
|
+
resourceType: "image",
|
|
1753
|
+
action: run.mode === "build" ? "build" : "reuse",
|
|
1754
|
+
id: worker.ref
|
|
1755
|
+
});
|
|
1756
|
+
planned({
|
|
1757
|
+
key: `service:${worker.name}`,
|
|
1758
|
+
resourceType: "service",
|
|
1759
|
+
action: run.previous[worker.name] ? "reuse" : "create"
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1437
1762
|
planned({
|
|
1438
1763
|
key: "service:caddy",
|
|
1439
1764
|
resourceType: "service",
|
|
@@ -1517,7 +1842,8 @@ async function prepareDatabases(ctx, run, deps) {
|
|
|
1517
1842
|
*/
|
|
1518
1843
|
async function buildCompose(ctx, run, deps) {
|
|
1519
1844
|
const { stack, ref } = run;
|
|
1520
|
-
const
|
|
1845
|
+
const images = [...stack.apps, ...stack.workers];
|
|
1846
|
+
const apps = images.map((image) => image.app);
|
|
1521
1847
|
if (run.mode === "build") {
|
|
1522
1848
|
ctx.logger.info("\n🐳 Building images…");
|
|
1523
1849
|
await deps.docker.build(ref, apps);
|
|
@@ -1525,24 +1851,24 @@ async function buildCompose(ctx, run, deps) {
|
|
|
1525
1851
|
ctx.logger.info(`\n🐳 Pulling ${ctx.tag}…`);
|
|
1526
1852
|
await deps.docker.pull(ref, apps);
|
|
1527
1853
|
}
|
|
1528
|
-
for (const
|
|
1529
|
-
const digest = await deps.docker.digest(
|
|
1530
|
-
run.images[app
|
|
1531
|
-
ref:
|
|
1532
|
-
tag:
|
|
1854
|
+
for (const image of images) {
|
|
1855
|
+
const digest = await deps.docker.digest(image.ref);
|
|
1856
|
+
run.images[image.app] = {
|
|
1857
|
+
ref: image.ref,
|
|
1858
|
+
tag: image.tag,
|
|
1533
1859
|
...digest ? { digest } : {}
|
|
1534
1860
|
};
|
|
1535
1861
|
ctx.emit({
|
|
1536
1862
|
type: "artifact.built",
|
|
1537
|
-
app: app
|
|
1538
|
-
imageRef:
|
|
1863
|
+
app: image.app,
|
|
1864
|
+
imageRef: image.ref,
|
|
1539
1865
|
...digest ? { digest } : {}
|
|
1540
1866
|
});
|
|
1541
1867
|
run.changes.push({
|
|
1542
|
-
key: `image:${app
|
|
1868
|
+
key: `image:${image.app}`,
|
|
1543
1869
|
resourceType: "image",
|
|
1544
1870
|
action: run.mode === "build" ? "build" : "reuse",
|
|
1545
|
-
id: digest ??
|
|
1871
|
+
id: digest ?? image.ref
|
|
1546
1872
|
});
|
|
1547
1873
|
}
|
|
1548
1874
|
}
|
|
@@ -1573,8 +1899,16 @@ async function releaseCompose(ctx, run, deps) {
|
|
|
1573
1899
|
url: app.url
|
|
1574
1900
|
});
|
|
1575
1901
|
}
|
|
1902
|
+
for (const worker of stack.workers) applied(ctx, run, {
|
|
1903
|
+
key: `service:${worker.name}`,
|
|
1904
|
+
resourceType: "service",
|
|
1905
|
+
action: "ensure",
|
|
1906
|
+
id: `${stack.project}/${worker.name}`,
|
|
1907
|
+
via: run.previous[worker.name] ? "recorded" : "created"
|
|
1908
|
+
});
|
|
1576
1909
|
ctx.logger.info(`\n✅ ${stack.project} is running:`);
|
|
1577
1910
|
for (const app of stack.apps) ctx.logger.info(` ${app.name.padEnd(12)} ${app.url}`);
|
|
1911
|
+
for (const worker of stack.workers) ctx.logger.info(` ${worker.name.padEnd(12)} worker ${worker.id}`);
|
|
1578
1912
|
if (stack.local) ctx.logger.info(`\n🔐 Certificates are from Caddy's local CA: NODE_EXTRA_CA_CERTS=${join(stackDir(ctx.stage), "caddy-root.crt")}`);
|
|
1579
1913
|
}
|
|
1580
1914
|
/**
|
|
@@ -1584,12 +1918,12 @@ async function releaseCompose(ctx, run, deps) {
|
|
|
1584
1918
|
*/
|
|
1585
1919
|
async function recordImages(ctx, run) {
|
|
1586
1920
|
const journal = await DeployJournal.open(ctx.state, ctx.stage, () => createEmptyState(ctx.stage, "", ""));
|
|
1587
|
-
for (const app of run.stack.apps) {
|
|
1588
|
-
const image = run.images[app.
|
|
1921
|
+
for (const app of [...run.stack.apps, ...run.stack.workers]) {
|
|
1922
|
+
const image = run.images[app.app] ?? {
|
|
1589
1923
|
ref: app.ref,
|
|
1590
1924
|
tag: app.tag
|
|
1591
1925
|
};
|
|
1592
|
-
recordRelease(journal.state, app.
|
|
1926
|
+
recordRelease(journal.state, app.app, image);
|
|
1593
1927
|
}
|
|
1594
1928
|
journal.state.identity = ctx.identity.key;
|
|
1595
1929
|
await journal.save();
|
|
@@ -1608,7 +1942,8 @@ async function verifyCompose(ctx, run, deps) {
|
|
|
1608
1942
|
ca,
|
|
1609
1943
|
local: stack.local
|
|
1610
1944
|
})));
|
|
1611
|
-
|
|
1945
|
+
results.push(...await Promise.all(stack.workers.map((worker) => checkContainer(ctx, run, deps, worker.name))));
|
|
1946
|
+
if (stack.logs) results.push(await checkContainer(ctx, run, deps, LOGS_SERVICE));
|
|
1612
1947
|
const down = results.filter((result) => !result.healthy).map(({ app, url, last }) => ({
|
|
1613
1948
|
app,
|
|
1614
1949
|
url,
|
|
@@ -1618,29 +1953,30 @@ async function verifyCompose(ctx, run, deps) {
|
|
|
1618
1953
|
if (stack.logs) reportLogs(ctx, stack.logs, stack.local);
|
|
1619
1954
|
}
|
|
1620
1955
|
/**
|
|
1621
|
-
*
|
|
1622
|
-
* through the edge
|
|
1623
|
-
* answers only the addresses it allows
|
|
1956
|
+
* A container's own health check, as Docker reports it: a worker's, which
|
|
1957
|
+
* has no route through the edge, and OpenObserve's — by default it has no
|
|
1958
|
+
* host there, and served publicly it answers only the addresses it allows,
|
|
1959
|
+
* which need not include this one.
|
|
1624
1960
|
*/
|
|
1625
|
-
async function
|
|
1626
|
-
const url = `docker:${
|
|
1961
|
+
async function checkContainer(ctx, run, deps, service) {
|
|
1962
|
+
const url = `docker:${service}`;
|
|
1627
1963
|
let last = "not asked";
|
|
1628
1964
|
for (let attempt = 1; attempt <= deps.healthAttempts; attempt++) {
|
|
1629
1965
|
ctx.signal.throwIfAborted();
|
|
1630
|
-
const health = await deps.docker.health(run.ref,
|
|
1966
|
+
const health = await deps.docker.health(run.ref, service).catch((error) => error instanceof Error ? error.message : String(error));
|
|
1631
1967
|
last = health ?? "not running";
|
|
1632
1968
|
const healthy = health === "healthy";
|
|
1633
1969
|
ctx.emit({
|
|
1634
1970
|
type: "health.checked",
|
|
1635
|
-
app:
|
|
1971
|
+
app: service,
|
|
1636
1972
|
url,
|
|
1637
1973
|
healthy,
|
|
1638
1974
|
attempt
|
|
1639
1975
|
});
|
|
1640
1976
|
if (healthy) {
|
|
1641
|
-
ctx.logger.info(` ✓ ${
|
|
1977
|
+
ctx.logger.info(` ✓ ${service.padEnd(12)} ${url} (${last})`);
|
|
1642
1978
|
return {
|
|
1643
|
-
app:
|
|
1979
|
+
app: service,
|
|
1644
1980
|
url,
|
|
1645
1981
|
last,
|
|
1646
1982
|
healthy
|
|
@@ -1648,9 +1984,9 @@ async function checkLogs(ctx, run, deps) {
|
|
|
1648
1984
|
}
|
|
1649
1985
|
if (attempt < deps.healthAttempts) await new Promise((resolve) => setTimeout(resolve, deps.healthIntervalMs));
|
|
1650
1986
|
}
|
|
1651
|
-
ctx.logger.warn(` ✗ ${
|
|
1987
|
+
ctx.logger.warn(` ✗ ${service.padEnd(12)} ${url} (${last})`);
|
|
1652
1988
|
return {
|
|
1653
|
-
app:
|
|
1989
|
+
app: service,
|
|
1654
1990
|
url,
|
|
1655
1991
|
last,
|
|
1656
1992
|
healthy: false
|
|
@@ -1729,7 +2065,7 @@ async function checkApp(ctx, app, deps, options) {
|
|
|
1729
2065
|
};
|
|
1730
2066
|
}
|
|
1731
2067
|
function composeResult(ctx, run) {
|
|
1732
|
-
const apps = run.stack.apps.map((app) => {
|
|
2068
|
+
const apps = [...run.stack.apps.map((app) => {
|
|
1733
2069
|
const image = run.images[app.name];
|
|
1734
2070
|
return {
|
|
1735
2071
|
appName: app.name,
|
|
@@ -1740,7 +2076,17 @@ function composeResult(ctx, run) {
|
|
|
1740
2076
|
...image?.digest ? { digest: image.digest } : {},
|
|
1741
2077
|
url: app.url
|
|
1742
2078
|
};
|
|
1743
|
-
})
|
|
2079
|
+
}), ...run.stack.workers.map((worker) => {
|
|
2080
|
+
const image = run.images[worker.name];
|
|
2081
|
+
return {
|
|
2082
|
+
appName: worker.name,
|
|
2083
|
+
type: "backend",
|
|
2084
|
+
success: !ctx.dryRun,
|
|
2085
|
+
applicationId: `${run.stack.project}/${worker.name}`,
|
|
2086
|
+
imageRef: image?.ref ?? worker.ref,
|
|
2087
|
+
...image?.digest ? { digest: image.digest } : {}
|
|
2088
|
+
};
|
|
2089
|
+
})];
|
|
1744
2090
|
return {
|
|
1745
2091
|
apps,
|
|
1746
2092
|
projectId: run.stack.project,
|
|
@@ -1784,8 +2130,9 @@ async function writeStack(root, dir, stack, credentials = {}) {
|
|
|
1784
2130
|
};
|
|
1785
2131
|
await write(join(dir, "docker-compose.yml"), composeYaml(stack));
|
|
1786
2132
|
await write(join(dir, "Caddyfile"), stack.caddyfile);
|
|
1787
|
-
for (const app of stack.apps) if (app.env) await write(join(dir, `${app.name}.env`), envFile(app.env), 384);
|
|
2133
|
+
for (const app of [...stack.apps, ...stack.workers]) if (app.env) await write(join(dir, `${app.name}.env`), envFile(app.env), 384);
|
|
1788
2134
|
if (stack.logs) await write(join(dir, `${LOGS_SERVICE}.env`), envFile(stack.logs.env), 384);
|
|
2135
|
+
if (stack.redis) await write(join(dir, `${REDIS_SERVICE}.env`), envFile(stack.redis.env), 384);
|
|
1789
2136
|
for (const [name, payload] of Object.entries(credentials)) await write(join(dir, credentialsFile(name)), credentialsFileContent(payload), 384);
|
|
1790
2137
|
for (const [path, content] of Object.entries(stack.dockerfiles)) await write(join(root, path), content);
|
|
1791
2138
|
await ensureDockerignore(findBuildRoot(root));
|
|
@@ -1794,8 +2141,9 @@ async function writeStack(root, dir, stack, credentials = {}) {
|
|
|
1794
2141
|
function composeYaml(stack) {
|
|
1795
2142
|
const stage = ` --stage ${stack.stage}`;
|
|
1796
2143
|
return `# Generated by gkm compose from the construct manifest — do not edit.
|
|
1797
|
-
# The ${stack.stage} stage's APIs and sites behind one Caddy
|
|
1798
|
-
# reads exactly the keys in its own env file beside
|
|
2144
|
+
# The ${stack.stage} stage's APIs and sites behind one Caddy, and its workers.
|
|
2145
|
+
# Each backend and worker reads exactly the keys in its own env file beside
|
|
2146
|
+
# this one.
|
|
1799
2147
|
#
|
|
1800
2148
|
# gkm compose${stage} start or update it
|
|
1801
2149
|
# gkm compose${stage} --down stop it
|
|
@@ -1808,6 +2156,7 @@ function printPlan(ctx, run) {
|
|
|
1808
2156
|
const { stack, mode, files } = run;
|
|
1809
2157
|
ctx.logger.info(`\n🧱 ${stack.project} — stage ${stack.stage}`);
|
|
1810
2158
|
for (const app of stack.apps) ctx.logger.info(` ${app.name.padEnd(12)} ${app.url.padEnd(40)} ${mode} ${app.ref}`);
|
|
2159
|
+
for (const worker of stack.workers) ctx.logger.info(` ${worker.name.padEnd(12)} ${`worker ${worker.id} (no route)`.padEnd(40)} ${mode} ${worker.ref}`);
|
|
1811
2160
|
if (stack.infra.length > 0) ctx.logger.info(` infrastructure: ${stack.infra.join(", ")}`);
|
|
1812
2161
|
ctx.logger.info(`\n📝 Wrote ${files.length} file(s) under ${stackDir(stack.stage)}/`);
|
|
1813
2162
|
}
|
|
@@ -1871,4 +2220,4 @@ function composeTarget(options = {}) {
|
|
|
1871
2220
|
//#endregion
|
|
1872
2221
|
export { stackDir as a, composeProject as i, compose_exports as n, dockerCompose as o, stackOverrideFile as r, composeTarget as t };
|
|
1873
2222
|
|
|
1874
|
-
//# sourceMappingURL=compose-
|
|
2223
|
+
//# sourceMappingURL=compose-DZp8DJ0A.mjs.map
|