@geekmidas/cli 10.0.0-alpha.80 → 10.0.0-alpha.82

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.
Files changed (105) hide show
  1. package/dist/{EndpointGenerator-DftalLYg.mjs → EndpointGenerator-DtnIGafh.mjs} +71 -8
  2. package/dist/EndpointGenerator-DtnIGafh.mjs.map +1 -0
  3. package/dist/{EndpointGenerator-CP6sBvQV.cjs → EndpointGenerator-cKlnllIu.cjs} +88 -7
  4. package/dist/EndpointGenerator-cKlnllIu.cjs.map +1 -0
  5. package/dist/{OpenApiTsGenerator-nu1r1NgB.mjs → OpenApiTsGenerator-ByRPMbBN.mjs} +34 -7
  6. package/dist/OpenApiTsGenerator-ByRPMbBN.mjs.map +1 -0
  7. package/dist/{OpenApiTsGenerator-CKBCckKz.cjs → OpenApiTsGenerator-CdUM6Hvv.cjs} +34 -7
  8. package/dist/OpenApiTsGenerator-CdUM6Hvv.cjs.map +1 -0
  9. package/dist/{SubscriberGenerator-CajwOa5W.cjs → SubscriberGenerator-CYriX5BV.cjs} +2 -2
  10. package/dist/{SubscriberGenerator-CajwOa5W.cjs.map → SubscriberGenerator-CYriX5BV.cjs.map} +1 -1
  11. package/dist/{SubscriberGenerator-5looQ3gB.mjs → SubscriberGenerator-DQZeGkDW.mjs} +2 -2
  12. package/dist/{SubscriberGenerator-5looQ3gB.mjs.map → SubscriberGenerator-DQZeGkDW.mjs.map} +1 -1
  13. package/dist/{add-DJ0VE-y-.cjs → add-1T3y6E4M.cjs} +2 -2
  14. package/dist/{add-DJ0VE-y-.cjs.map → add-1T3y6E4M.cjs.map} +1 -1
  15. package/dist/{add-CIxT-Iq6.mjs → add-DnYHJOap.mjs} +2 -2
  16. package/dist/{add-CIxT-Iq6.mjs.map → add-DnYHJOap.mjs.map} +1 -1
  17. package/dist/{bundler-D4pl87A5.mjs → bundler-DlKdxCGh.mjs} +3 -3
  18. package/dist/{bundler-D4pl87A5.mjs.map → bundler-DlKdxCGh.mjs.map} +1 -1
  19. package/dist/{bundler-BlbSLFLn.cjs → bundler-YNvLdvlh.cjs} +3 -3
  20. package/dist/{bundler-BlbSLFLn.cjs.map → bundler-YNvLdvlh.cjs.map} +1 -1
  21. package/dist/{cli-BkNZBe_-.cjs → cli-CWee1-wU.cjs} +3 -3
  22. package/dist/{cli-BkNZBe_-.cjs.map → cli-CWee1-wU.cjs.map} +1 -1
  23. package/dist/{cli-7aLfgIGt.mjs → cli-qMszGvDu.mjs} +3 -3
  24. package/dist/{cli-7aLfgIGt.mjs.map → cli-qMszGvDu.mjs.map} +1 -1
  25. package/dist/{compose-n7K8H5j8.mjs → compose-D4sD4Lgr.mjs} +83 -351
  26. package/dist/compose-D4sD4Lgr.mjs.map +1 -0
  27. package/dist/{compose-Bn4OJONR.cjs → compose-qnDyPJNp.cjs} +101 -369
  28. package/dist/compose-qnDyPJNp.cjs.map +1 -0
  29. package/dist/deploy/public.cjs +5 -5
  30. package/dist/deploy/public.d.cts.map +1 -1
  31. package/dist/deploy/public.d.mts.map +1 -1
  32. package/dist/deploy/public.mjs +5 -5
  33. package/dist/{deploy-DK4Y6ev4.mjs → deploy-C00DAKOt.mjs} +3 -3
  34. package/dist/{deploy-DK4Y6ev4.mjs.map → deploy-C00DAKOt.mjs.map} +1 -1
  35. package/dist/{deploy-Ppnh8MHS.cjs → deploy-CXP2fXvK.cjs} +3 -3
  36. package/dist/{deploy-Ppnh8MHS.cjs.map → deploy-CXP2fXvK.cjs.map} +1 -1
  37. package/dist/{dokploy-Bjq05wkw.mjs → dokploy-B3zoCmJb.mjs} +2 -2
  38. package/dist/{dokploy-Bjq05wkw.mjs.map → dokploy-B3zoCmJb.mjs.map} +1 -1
  39. package/dist/{dokploy-CJvdoNc3.cjs → dokploy-BHcSmghb.cjs} +2 -2
  40. package/dist/{dokploy-CJvdoNc3.cjs.map → dokploy-BHcSmghb.cjs.map} +1 -1
  41. package/dist/{drivers-Ch1rJitn.mjs → drivers-B4RP2B2O.mjs} +2 -2
  42. package/dist/{drivers-Ch1rJitn.mjs.map → drivers-B4RP2B2O.mjs.map} +1 -1
  43. package/dist/{drivers-BdvtCKc7.cjs → drivers-CJx76mcb.cjs} +2 -2
  44. package/dist/{drivers-BdvtCKc7.cjs.map → drivers-CJx76mcb.cjs.map} +1 -1
  45. package/dist/{encryption-CAyR6D7B.cjs → encryption-DLin89I_.cjs} +2 -2
  46. package/dist/{encryption-CAyR6D7B.cjs.map → encryption-DLin89I_.cjs.map} +1 -1
  47. package/dist/{encryption-BrsOJFFz.mjs → encryption-DPFljXtM.mjs} +2 -2
  48. package/dist/{encryption-BrsOJFFz.mjs.map → encryption-DPFljXtM.mjs.map} +1 -1
  49. package/dist/{engine-DzBZR5D0.cjs → engine-BS3iX9Mu.cjs} +2 -2
  50. package/dist/{engine-DzBZR5D0.cjs.map → engine-BS3iX9Mu.cjs.map} +1 -1
  51. package/dist/{engine-ba9CvjTo.mjs → engine-CgzgIg-_.mjs} +2 -2
  52. package/dist/{engine-ba9CvjTo.mjs.map → engine-CgzgIg-_.mjs.map} +1 -1
  53. package/dist/{github-BLjU59Ye.cjs → github-D8KVmNRg.cjs} +2 -2
  54. package/dist/{github-BLjU59Ye.cjs.map → github-D8KVmNRg.cjs.map} +1 -1
  55. package/dist/{github-CVdbVlT5.mjs → github-DL4zwJsR.mjs} +2 -2
  56. package/dist/{github-CVdbVlT5.mjs.map → github-DL4zwJsR.mjs.map} +1 -1
  57. package/dist/index.cjs +225 -49
  58. package/dist/index.cjs.map +1 -1
  59. package/dist/index.mjs +226 -50
  60. package/dist/index.mjs.map +1 -1
  61. package/dist/{migrate-iEdyEx4S.cjs → migrate-BdKhIJgT.cjs} +2 -2
  62. package/dist/{migrate-iEdyEx4S.cjs.map → migrate-BdKhIJgT.cjs.map} +1 -1
  63. package/dist/{migrate-DlBgATa-.mjs → migrate-CCejlgU0.mjs} +2 -2
  64. package/dist/{migrate-DlBgATa-.mjs.map → migrate-CCejlgU0.mjs.map} +1 -1
  65. package/dist/{migrate-pfu4aYzX.mjs → migrate-Cd3kcI7o.mjs} +2 -2
  66. package/dist/{migrate-pfu4aYzX.mjs.map → migrate-Cd3kcI7o.mjs.map} +1 -1
  67. package/dist/{migrate-DkB833Jj.cjs → migrate-DIjG8-QR.cjs} +2 -2
  68. package/dist/{migrate-DkB833Jj.cjs.map → migrate-DIjG8-QR.cjs.map} +1 -1
  69. package/dist/openapi.cjs +16 -11
  70. package/dist/openapi.cjs.map +1 -1
  71. package/dist/openapi.d.cts +28 -0
  72. package/dist/openapi.d.cts.map +1 -1
  73. package/dist/openapi.d.mts +28 -0
  74. package/dist/openapi.d.mts.map +1 -1
  75. package/dist/openapi.mjs +16 -12
  76. package/dist/openapi.mjs.map +1 -1
  77. package/dist/{ready-BB9xS4S9.cjs → ready-4kx2pAhm.cjs} +5 -5
  78. package/dist/{ready-BB9xS4S9.cjs.map → ready-4kx2pAhm.cjs.map} +1 -1
  79. package/dist/{ready-ZZoNskrr.mjs → ready-DhZbZtFw.mjs} +5 -5
  80. package/dist/{ready-ZZoNskrr.mjs.map → ready-DhZbZtFw.mjs.map} +1 -1
  81. package/dist/{resolve-4_5N67Fk.cjs → resolve-ApSUK_HB.cjs} +3 -3
  82. package/dist/{resolve-4_5N67Fk.cjs.map → resolve-ApSUK_HB.cjs.map} +1 -1
  83. package/dist/{resolve-CQPn66jr.mjs → resolve-DY9TaVeV.mjs} +3 -3
  84. package/dist/{resolve-CQPn66jr.mjs.map → resolve-DY9TaVeV.mjs.map} +1 -1
  85. package/dist/{rollback-CgYUPLbq.cjs → rollback-CdGhCk1d.cjs} +3 -3
  86. package/dist/{rollback-CgYUPLbq.cjs.map → rollback-CdGhCk1d.cjs.map} +1 -1
  87. package/dist/{rollback-C0cbdsM-.mjs → rollback-DXU6cr7q.mjs} +3 -3
  88. package/dist/{rollback-C0cbdsM-.mjs.map → rollback-DXU6cr7q.mjs.map} +1 -1
  89. package/dist/{seeds-ZljwYV_p.cjs → seeds-BLzrSwEr.cjs} +3889 -2953
  90. package/dist/seeds-BLzrSwEr.cjs.map +1 -0
  91. package/dist/{seeds-BdkFpW0P.mjs → seeds-DbyqaXW4.mjs} +2344 -1528
  92. package/dist/seeds-DbyqaXW4.mjs.map +1 -0
  93. package/dist/target/public.cjs +1 -1
  94. package/dist/target/public.mjs +1 -1
  95. package/dist/vitest.cjs +1 -1
  96. package/dist/vitest.mjs +1 -1
  97. package/package.json +15 -15
  98. package/dist/EndpointGenerator-CP6sBvQV.cjs.map +0 -1
  99. package/dist/EndpointGenerator-DftalLYg.mjs.map +0 -1
  100. package/dist/OpenApiTsGenerator-CKBCckKz.cjs.map +0 -1
  101. package/dist/OpenApiTsGenerator-nu1r1NgB.mjs.map +0 -1
  102. package/dist/compose-Bn4OJONR.cjs.map +0 -1
  103. package/dist/compose-n7K8H5j8.mjs.map +0 -1
  104. package/dist/seeds-BdkFpW0P.mjs.map +0 -1
  105. package/dist/seeds-ZljwYV_p.cjs.map +0 -1
@@ -4,9 +4,9 @@ const require_discover = require("./discover-D1It5r_h.cjs");
4
4
  const require_derive = require("./derive-DAbo49sm.cjs");
5
5
  const require_workspace = require("./workspace-Bp8FEkF-.cjs");
6
6
  const require_define = require("./define-DzRz8FgH.cjs");
7
- const require_seeds = require("./seeds-ZljwYV_p.cjs");
8
- const require_SubscriberGenerator = require("./SubscriberGenerator-CajwOa5W.cjs");
9
- const require_drivers = require("./drivers-BdvtCKc7.cjs");
7
+ const require_seeds = require("./seeds-BLzrSwEr.cjs");
8
+ const require_SubscriberGenerator = require("./SubscriberGenerator-CYriX5BV.cjs");
9
+ const require_drivers = require("./drivers-CJx76mcb.cjs");
10
10
  const require_databases = require("./databases-BY-pEImp.cjs");
11
11
  const require_credentialSchemas = require("./credentialSchemas-DCR4Wd1W.cjs");
12
12
  let node_path = require("node:path");
@@ -792,330 +792,6 @@ function pinImages(file, digests, images) {
792
792
  });
793
793
  }
794
794
  //#endregion
795
- //#region src/compose/logs.ts
796
- /**
797
- * The stack's log UI: OpenObserve, run beside the apps when
798
- * `deploy.compose.logs` asks for it, and every backend's OpenTelemetry
799
- * pointed at it.
800
- *
801
- * The production server already exports its traces and pino logs over
802
- * OTLP/HTTP when `OTEL_EXPORTER_OTLP_ENDPOINT` is set. OpenObserve takes OTLP
803
- * at `/api/<org>/v1/{logs,traces}` with HTTP basic auth, so wiring an app is
804
- * two variables: the endpoint, `http://openobserve:5080/api/default`, and
805
- * the header that signs in as the root user.
806
- *
807
- * It is reached, by default, through an SSH tunnel: published on 127.0.0.1
808
- * only, because a port Docker publishes on every interface is opened in the
809
- * firewall by Docker itself, past ufw's rules. Served publicly, it is a host
810
- * on the stack's Caddy that refuses every address not allowed.
811
- *
812
- * Pure: the stack decides it, and the phases print and check it.
813
- */
814
- /**
815
- * The image, pinned. A log store's on-disk format is the thing that must not
816
- * move under a running stack, so a new version is a change to this line.
817
- */
818
- const OPENOBSERVE_IMAGE = "public.ecr.aws/zinclabs/openobserve:v1.0.4";
819
- /** Its compose service name — the host the apps send to. */
820
- const LOGS_SERVICE = "openobserve";
821
- /** The organisation every app's telemetry lands in. */
822
- const LOGS_ORG = "default";
823
- /** The stage secrets that sign in as OpenObserve's root user. */
824
- const LOGS_EMAIL_KEY = "ZO_ROOT_USER_EMAIL";
825
- const LOGS_PASSWORD_KEY = "ZO_ROOT_USER_PASSWORD";
826
- /** The stage sends its telemetry somewhere already, and logs are on too. */
827
- var LogsEndpointConflict = class extends Error {
828
- stage;
829
- keys;
830
- constructor(stage, keys) {
831
- super(`The stage '${stage}' sets ${keys.join(", ")} in its secrets, and deploy.compose.logs also runs OpenObserve for it — each backend can send its telemetry to one place. Either keep your own OTLP backend: remove logs from deploy.compose in gkm.config.ts; or use the bundled OpenObserve: remove ${keys.join(", ")} from the stage's secrets, and every backend is pointed at it.`);
832
- this.stage = stage;
833
- this.keys = keys;
834
- this.name = "LogsEndpointConflict";
835
- }
836
- };
837
- /** A root password OpenObserve would refuse to start with. */
838
- var LogsPasswordWeak = class extends Error {
839
- stage;
840
- constructor(stage) {
841
- super(`The stage '${stage}' sets ${LOGS_PASSWORD_KEY}, and OpenObserve refuses it: a root password must be 8 to 128 characters with a lowercase letter, an uppercase letter, a digit and a symbol. Set another with gkm secrets:set ${LOGS_PASSWORD_KEY} '…' --stage ${stage}, or unset it to have one generated.`);
842
- this.stage = stage;
843
- this.name = "LogsPasswordWeak";
844
- }
845
- };
846
- /** A deployed stage with logs on and no root password generated yet. */
847
- var LogsPasswordMissing = class extends Error {
848
- stage;
849
- constructor(stage) {
850
- super(`The stage '${stage}' runs OpenObserve and has no ${LOGS_PASSWORD_KEY} in its secrets. Run gkm compose --stage ${stage} without --dry-run once to generate it, or set one: gkm secrets:set ${LOGS_PASSWORD_KEY} '…' --stage ${stage}`);
851
- this.stage = stage;
852
- this.name = "LogsPasswordMissing";
853
- }
854
- };
855
- /** Whether OpenObserve accepts `password` for its root user. */
856
- function isStrongLogsPassword(password) {
857
- return password.length >= 8 && password.length <= 128 && /[a-z]/.test(password) && /[A-Z]/.test(password) && /\d/.test(password) && /[^A-Za-z0-9]/.test(password);
858
- }
859
- /**
860
- * A root password: 192 random bits, with a fixed tail so every class
861
- * OpenObserve requires is present whatever the random part drew.
862
- */
863
- function generateLogsPassword() {
864
- return `${(0, node_crypto.randomBytes)(24).toString("base64url")}-Zo1`;
865
- }
866
- /**
867
- * The stage's secrets with a generated root password, where it has none.
868
- * Pure, like `withGeneratedSecrets`: the caller keeps the result once the
869
- * run goes ahead.
870
- */
871
- function withLogsPassword(secrets) {
872
- if (secrets.custom?.["ZO_ROOT_USER_PASSWORD"]) return {
873
- secrets,
874
- generated: []
875
- };
876
- return {
877
- secrets: {
878
- ...secrets,
879
- custom: {
880
- ...secrets.custom,
881
- [LOGS_PASSWORD_KEY]: generateLogsPassword()
882
- },
883
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
884
- },
885
- generated: [LOGS_PASSWORD_KEY]
886
- };
887
- }
888
- /**
889
- * `OTEL_EXPORTER_OTLP_HEADERS` signing in as `email`.
890
- *
891
- * The variable's values are percent-decoded by the SDK (they follow the W3C
892
- * baggage format), so the space after `Basic` is written `%20`. Base64's own
893
- * `+`, `/` and `=` pass through decoding unchanged.
894
- */
895
- function otlpHeaders(email, password) {
896
- return `Authorization=Basic%20${Buffer.from(`${email}:${password}`).toString("base64")}`;
897
- }
898
- /** The stack's OpenObserve for one stage. */
899
- function stackLogs(options) {
900
- const { config, stage, local, custom } = options;
901
- const conflicting = Object.keys(custom).filter(require_seeds.isOtlpDestination).sort();
902
- if (conflicting.length > 0) throw new LogsEndpointConflict(stage, conflicting);
903
- if (!local && !options.domain) throw new require_seeds.NoDomainForStage(stage);
904
- const email = custom["ZO_ROOT_USER_EMAIL"] ?? (local ? "admin@gkm.localhost" : `admin@${options.domain}`);
905
- const set = custom[LOGS_PASSWORD_KEY];
906
- if (!set && !local) throw new LogsPasswordMissing(stage);
907
- const password = set ?? "Geekmidas-1";
908
- if (!isStrongLogsPassword(password)) throw new LogsPasswordWeak(stage);
909
- const host = config.public ? local ? `logs.${options.project}.localhost` : `logs.${options.domain}` : void 0;
910
- const url = host ? `https://${host}${local && options.https !== 443 ? `:${options.https}` : ""}` : `http://localhost:${config.port}`;
911
- return {
912
- ...config,
913
- email,
914
- password,
915
- passwordFromSecrets: Boolean(set),
916
- url,
917
- ...host ? { host } : {},
918
- env: {
919
- [LOGS_EMAIL_KEY]: email,
920
- [LOGS_PASSWORD_KEY]: password,
921
- ZO_DATA_DIR: "/data",
922
- ZO_COMPACT_DATA_RETENTION_DAYS: String(config.retentionDays),
923
- ZO_TELEMETRY: "false"
924
- },
925
- appEnv: {
926
- OTEL_EXPORTER_OTLP_ENDPOINT: `http://${LOGS_SERVICE}:${require_workspace.LOGS_PORT}/api/${LOGS_ORG}`,
927
- OTEL_EXPORTER_OTLP_HEADERS: otlpHeaders(email, password)
928
- }
929
- };
930
- }
931
- /**
932
- * OpenObserve's service. The image has no shell, `wget` or `curl`, so its
933
- * health check is its own binary asking the running server for its status —
934
- * which exits non-zero when nothing answers on its port.
935
- */
936
- function logsService(logs) {
937
- return {
938
- image: OPENOBSERVE_IMAGE,
939
- restart: "unless-stopped",
940
- env_file: [{
941
- path: `./${LOGS_SERVICE}.env`,
942
- format: "raw"
943
- }],
944
- volumes: ["openobserve-data:/data"],
945
- ...logs.public ? {} : { ports: [`127.0.0.1:${logs.port}:${require_workspace.LOGS_PORT}`] },
946
- healthcheck: {
947
- test: [
948
- "CMD",
949
- "/openobserve",
950
- "node",
951
- "status"
952
- ],
953
- interval: "10s",
954
- timeout: "5s",
955
- retries: 12,
956
- start_period: "20s"
957
- }
958
- };
959
- }
960
- /** What `gkm compose` prints once the stack is up: how to open the logs. */
961
- function logsAccess(logs, ctx) {
962
- const login = `login: ${logs.email}, password: ${ctx.local && !logs.passwordFromSecrets ? logs.password : `gkm secrets:show --stage ${ctx.stage} --reveal → ${LOGS_PASSWORD_KEY}`}`;
963
- if (logs.public) return [`📜 Logs (OpenObserve): ${logs.url} (${login})`, ` answered only for ${logs.public.allow.join(", ")}; every other address gets 403`];
964
- return [
965
- `📜 Logs (OpenObserve) on 127.0.0.1:${logs.port} — from your computer:`,
966
- ...ctx.local ? [` on this machine, open http://localhost:${logs.port} directly; from another:`] : [],
967
- ` ssh -N -L ${logs.port}:localhost:${logs.port} ${ctx.user}@${ctx.hostname} (user and host are guesses: this machine's)`,
968
- ` then open http://localhost:${logs.port} (${login})`,
969
- `⚠️ Docker-published ports bypass ufw, so OpenObserve is bound to 127.0.0.1 only — reach it through the tunnel, not by opening the port.`
970
- ];
971
- }
972
- //#endregion
973
- //#region src/compose/redis.ts
974
- /**
975
- * The stack's Redis: what every declared cache lives in when a workspace runs
976
- * as a `gkm compose` stack, on the local stage and on a deployed one alike.
977
- *
978
- * A cache is the one store whose contents can always be rebuilt, so unlike
979
- * Mailpit and MinIO it is a production service, not a dev stand-in: it runs
980
- * for every stage, on the compose network only, with nothing published. It
981
- * is bounded (`maxmemory`, evicting the least recently used key), persisted
982
- * (an append-only file on a named volume, kept by `--down`) and password
983
- * protected — the stage's own password, generated once like its seed.
984
- *
985
- * A stage that sets a cache's URL in its secrets — a managed Redis — keeps
986
- * it, and a stack whose every cache is set that way runs no Redis at all.
987
- *
988
- * Pure: the stack decides it, and the phases keep the password.
989
- */
990
- /**
991
- * Where a stack's caches live: its own Redis, whatever the deploy target's
992
- * default is. A stack is one machine running containers, and a Redis beside
993
- * the apps is cheaper for a cache than a table in the database they share.
994
- */
995
- const STACK_CACHE = "redis";
996
- /** Its compose service name — the host every cache URL points at. */
997
- const REDIS_SERVICE = "redis";
998
- /** The port it answers on, inside the network. */
999
- const REDIS_PORT = 6379;
1000
- /** Reconcile's pin, so `gkm dev` and the stack run one Redis. */
1001
- const REDIS_IMAGE = require_seeds.DEFAULT_IMAGES.redis;
1002
- /**
1003
- * How much it may hold before it evicts. A cache that grows until the box
1004
- * runs out of memory takes the apps with it; 256 MB is a lot of sessions and
1005
- * little of a small server. A project's `docker-compose.<stage>.yml` can
1006
- * replace the command to change it.
1007
- */
1008
- const REDIS_MAXMEMORY = "256mb";
1009
- /** The stage secret holding its password. */
1010
- const REDIS_PASSWORD_KEY = "REDIS_PASSWORD";
1011
- /** Redis's own count of logical databases, before `--databases` raises it. */
1012
- const REDIS_DATABASES = 16;
1013
- /** A deployed stage that runs the stack's Redis and has no password yet. */
1014
- var RedisPasswordMissing = class extends Error {
1015
- stage;
1016
- constructor(stage) {
1017
- 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}`);
1018
- this.stage = stage;
1019
- this.name = "RedisPasswordMissing";
1020
- }
1021
- };
1022
- /**
1023
- * Whether a stage's stack runs Redis: a cache is declared and the stage's
1024
- * secrets do not set the URL of every one.
1025
- */
1026
- function runsRedis(manifest, custom) {
1027
- return Object.values(manifest).some((declaration) => declaration?.kind === "cache" && (declaration.provides ?? []).some((key) => custom[key] === void 0));
1028
- }
1029
- /** A password: 256 random bits, URL-safe, so it sits in a URL unescaped. */
1030
- function generateRedisPassword() {
1031
- return (0, node_crypto.randomBytes)(32).toString("base64url");
1032
- }
1033
- /**
1034
- * The stage's secrets with a generated Redis password, where it has none.
1035
- * Pure, like `withGeneratedSecrets`: the caller keeps the result once the run
1036
- * goes ahead.
1037
- */
1038
- function withRedisPassword(secrets) {
1039
- if (secrets.custom?.["REDIS_PASSWORD"]) return {
1040
- secrets,
1041
- generated: []
1042
- };
1043
- return {
1044
- secrets: {
1045
- ...secrets,
1046
- custom: {
1047
- ...secrets.custom,
1048
- [REDIS_PASSWORD_KEY]: generateRedisPassword()
1049
- },
1050
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1051
- },
1052
- generated: [REDIS_PASSWORD_KEY]
1053
- };
1054
- }
1055
- /**
1056
- * The stack's Redis for one stage, or nothing where no cache needs it.
1057
- *
1058
- * Each cache it holds gets a logical database of its own, by the order of
1059
- * their ids, so two caches never read each other's keys.
1060
- */
1061
- function stackRedis(options) {
1062
- const { plan, stage, local, custom } = options;
1063
- const caches = plan.resources.filter((r) => r.kind === "cache" && custom[r.envKey] === void 0).sort((a, b) => a.id.localeCompare(b.id));
1064
- if (caches.length === 0) return void 0;
1065
- const set = custom[REDIS_PASSWORD_KEY];
1066
- if (!set && !local) throw new RedisPasswordMissing(stage);
1067
- const password = set ?? "geekmidas";
1068
- const auth = encodeURIComponent(password);
1069
- return {
1070
- password,
1071
- passwordFromSecrets: Boolean(set),
1072
- urls: Object.fromEntries(caches.map((cache, index) => [cache.envKey, `redis://:${auth}@${REDIS_SERVICE}:${REDIS_PORT}/${index}`])),
1073
- env: {
1074
- [REDIS_PASSWORD_KEY]: password,
1075
- REDISCLI_AUTH: password
1076
- },
1077
- databases: Math.max(REDIS_DATABASES, caches.length)
1078
- };
1079
- }
1080
- /**
1081
- * Redis's service: bounded, persisted and password protected, on the
1082
- * network alone.
1083
- *
1084
- * The command runs the image's own entrypoint with `redis-server` first, so
1085
- * it still drops to the `redis` user and owns `/data`; the password is read
1086
- * from the env file by the shell (`$$` is compose's escape for `$`), so
1087
- * neither the compose file nor the container's command holds it.
1088
- */
1089
- function redisService(redis) {
1090
- const flags = [
1091
- `--requirepass "$$${REDIS_PASSWORD_KEY}"`,
1092
- `--maxmemory ${REDIS_MAXMEMORY}`,
1093
- "--maxmemory-policy allkeys-lru",
1094
- "--appendonly yes",
1095
- ...redis.databases > REDIS_DATABASES ? [`--databases ${redis.databases}`] : []
1096
- ];
1097
- return {
1098
- image: REDIS_IMAGE,
1099
- restart: "unless-stopped",
1100
- command: [
1101
- "sh",
1102
- "-c",
1103
- `exec docker-entrypoint.sh redis-server ${flags.join(" ")}`
1104
- ],
1105
- env_file: [{
1106
- path: `./${REDIS_SERVICE}.env`,
1107
- format: "raw"
1108
- }],
1109
- volumes: ["redis-data:/data"],
1110
- healthcheck: {
1111
- test: ["CMD-SHELL", "redis-cli ping | grep -q PONG"],
1112
- interval: "5s",
1113
- timeout: "3s",
1114
- retries: 10
1115
- }
1116
- };
1117
- }
1118
- //#endregion
1119
795
  //#region src/compose/caddyfile.ts
1120
796
  /** Where Caddy reads a stage's own certificate, inside its container. */
1121
797
  const CADDY_TLS_DIR = "/etc/caddy/tls";
@@ -1246,6 +922,43 @@ const LOG_ROTATION = {
1246
922
  "max-file": "3"
1247
923
  }
1248
924
  };
925
+ /** The local stage's stack, without the logins `gkm dev` generates. */
926
+ var LocalCredentialsMissing = class extends Error {
927
+ stage;
928
+ constructor(stage) {
929
+ super(`The local stage '${stage}' runs with this machine's generated logins, and none were passed. Load them with loadLocalCredentials(workspace) and pass them as localCredentials.`);
930
+ this.stage = stage;
931
+ this.name = "LocalCredentialsMissing";
932
+ }
933
+ };
934
+ /**
935
+ * A deployed stage's container logins, derived from its seed like its role
936
+ * passwords — so every deploy of the stage computes the same ones, and no one
937
+ * reading the repo can.
938
+ */
939
+ function derivedCredentials(project, plan, seed) {
940
+ const derive = (purpose) => require_seeds.localRolePassword(project, plan, purpose, seed);
941
+ return {
942
+ postgres: {
943
+ user: require_seeds.postgresSuperuser(project),
944
+ password: derive("master")
945
+ },
946
+ minio: {
947
+ user: `${project}-minio`,
948
+ password: derive("minio")
949
+ },
950
+ rabbitmq: {
951
+ user: require_seeds.LOCAL_RABBITMQ_USER,
952
+ password: derive("rabbitmq")
953
+ },
954
+ redis: { password: derive("redis") },
955
+ cacheToken: derive("cache-token"),
956
+ emulator: {
957
+ accessKeyId: require_seeds.EMULATOR_ACCESS_KEY_ID,
958
+ secretAccessKey: derive("emulator")
959
+ }
960
+ };
961
+ }
1249
962
  /** A value only the stage's secrets can hold, and they do not. */
1250
963
  var StageSecretMissing = class extends Error {
1251
964
  app;
@@ -1324,15 +1037,23 @@ function composeStack(input) {
1324
1037
  const plan = require_seeds.planFor(manifest, stage, (0, _geekmidas_manifest.provisionOrder)(manifest), {
1325
1038
  localStage: workspace.stages.local,
1326
1039
  events: require_SubscriberGenerator.DEFAULT_EVENTS.server,
1327
- cache: STACK_CACHE,
1040
+ cache: require_seeds.STACK_CACHE,
1328
1041
  edge: false
1329
1042
  });
1330
1043
  const byId = new Map(plan.resources.map((r) => [r.id, r]));
1331
- const seed = local ? void 0 : input.secrets?.seed;
1332
- if (!local && !seed) throw new StageSeedMissing(stage);
1044
+ if (local && !input.localCredentials) throw new LocalCredentialsMissing(stage);
1045
+ const seed = local ? input.localCredentials.seed : input.secrets?.seed;
1046
+ if (!seed) throw new StageSeedMissing(stage);
1047
+ const containers = local ? input.localCredentials : derivedCredentials(workspace.name, plan, seed);
1333
1048
  const credential = {
1334
- master: local ? "geekmidas" : require_seeds.localRolePassword(workspace.name, plan, "master", seed),
1335
- ...seed ? { seed } : {}
1049
+ containers: {
1050
+ ...containers,
1051
+ minio: {
1052
+ user: custom.AWS_ACCESS_KEY_ID ?? containers.minio.user,
1053
+ password: custom.AWS_SECRET_ACCESS_KEY ?? containers.minio.password
1054
+ }
1055
+ },
1056
+ seed
1336
1057
  };
1337
1058
  const apps = stackApps(workspace, manifest, plan, {
1338
1059
  local,
@@ -1364,14 +1085,15 @@ function composeStack(input) {
1364
1085
  for (const worker of workers) reads.set(worker.name, [...require_seeds.workerEnvKeys(manifest, worker.id, input.runnables) ?? []]);
1365
1086
  const domain = workspace.deploy?.domains?.[stage];
1366
1087
  const logsConfig = require_workspace.resolveLogs(workspace.deploy?.compose?.logs);
1367
- const logsBase = logsConfig ? stackLogs({
1088
+ const logsBase = logsConfig ? require_seeds.stackLogs({
1368
1089
  config: logsConfig,
1369
1090
  stage,
1370
1091
  local,
1371
1092
  project: workspace.name,
1372
1093
  ...domain ? { domain } : {},
1373
1094
  custom,
1374
- https
1095
+ https,
1096
+ ...local ? { localLogin: input.localCredentials.logs } : {}
1375
1097
  }) : void 0;
1376
1098
  const services = local ? {
1377
1099
  missing: [],
@@ -1384,18 +1106,18 @@ function composeStack(input) {
1384
1106
  allow: input.allowDevServices ?? [],
1385
1107
  ...domain ? { domain } : {}
1386
1108
  });
1387
- const redis = stackRedis({
1109
+ const redis = require_seeds.stackRedis({
1388
1110
  plan,
1389
1111
  stage,
1390
1112
  local,
1391
- custom
1113
+ custom,
1114
+ ...local ? { localPassword: input.localCredentials.redis.password } : {}
1392
1115
  });
1393
- 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), ...logsBase ? [LOGS_SERVICE] : []].sort();
1116
+ 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), ...logsBase ? [require_seeds.LOGS_SERVICE] : []].sort();
1394
1117
  const storage = infra.includes("minio") ? (() => {
1395
1118
  const buckets = services.minio.map((id) => byId.get(id)?.name).filter((name) => Boolean(name)).sort();
1396
1119
  return {
1397
- user: custom.AWS_ACCESS_KEY_ID ?? (local ? "geekmidas" : `${workspace.name}-minio`),
1398
- password: custom.AWS_SECRET_ACCESS_KEY ?? (local ? "geekmidas" : require_seeds.localRolePassword(workspace.name, plan, "minio", seed)),
1120
+ ...credential.containers.minio,
1399
1121
  buckets,
1400
1122
  policies: require_seeds.bucketPolicies(plan).filter((p) => buckets.includes(p.bucket))
1401
1123
  };
@@ -1430,7 +1152,7 @@ function composeStack(input) {
1430
1152
  const edgeServices = new Set(proxy === "traefik" ? [
1431
1153
  ...apps.map((app) => app.name),
1432
1154
  ...fileServers.size > 0 ? ["minio"] : [],
1433
- ...logsBase?.host && logsBase.public ? [LOGS_SERVICE] : []
1155
+ ...logsBase?.host && logsBase.public ? [require_seeds.LOGS_SERVICE] : []
1434
1156
  ] : []);
1435
1157
  const hostOf = (service) => edgeServices.has(service) ? edgeAlias(project, service) : service;
1436
1158
  const logs = logsBase && {
@@ -1442,8 +1164,8 @@ function composeStack(input) {
1442
1164
  };
1443
1165
  const derivation = {
1444
1166
  project: workspace.name,
1445
- master: credential.master,
1446
- ...credential.seed ? { seed: credential.seed } : {},
1167
+ credentials: credential.containers,
1168
+ seed: credential.seed,
1447
1169
  ...!local && domain ? { mailFrom: `noreply@${domain}` } : {}
1448
1170
  };
1449
1171
  const outside = require_seeds.networkEnv(plan, {
@@ -1559,7 +1281,7 @@ function composeStack(input) {
1559
1281
  infra,
1560
1282
  apps,
1561
1283
  workers,
1562
- master: credential.master,
1284
+ credentials: credential.containers,
1563
1285
  ...storage ? { storage } : {},
1564
1286
  ...logs ? { logs } : {},
1565
1287
  ...redis ? { redis } : {},
@@ -1586,10 +1308,10 @@ function composeStack(input) {
1586
1308
  })),
1587
1309
  ...[...fileServers.values()].map(({ route }) => route),
1588
1310
  ...logs?.host && logs.public ? [{
1589
- name: LOGS_SERVICE,
1311
+ name: require_seeds.LOGS_SERVICE,
1590
1312
  host: logs.host,
1591
1313
  upstreams: [{
1592
- service: LOGS_SERVICE,
1314
+ service: require_seeds.LOGS_SERVICE,
1593
1315
  port: require_workspace.LOGS_PORT
1594
1316
  }],
1595
1317
  streaming: true,
@@ -1604,7 +1326,7 @@ function composeStack(input) {
1604
1326
  workspaceRoot: workspace.root,
1605
1327
  apps: workspace.apps,
1606
1328
  manifest,
1607
- cache: STACK_CACHE
1329
+ cache: require_seeds.STACK_CACHE
1608
1330
  });
1609
1331
  }
1610
1332
  for (const worker of workers) {
@@ -1616,7 +1338,7 @@ function composeStack(input) {
1616
1338
  }, workspace.apps[worker.host], {
1617
1339
  layout,
1618
1340
  workspaceRoot: workspace.root,
1619
- cache: STACK_CACHE
1341
+ cache: require_seeds.STACK_CACHE
1620
1342
  });
1621
1343
  }
1622
1344
  return {
@@ -1741,13 +1463,14 @@ function hostOf(name, app, workspace, resource, options) {
1741
1463
  /** The compose document: the infrastructure, the apps, and the edge. */
1742
1464
  function stackFile(options) {
1743
1465
  const { project, plan, apps } = options;
1744
- const infra = options.infra.filter((name) => name !== LOGS_SERVICE);
1466
+ const infra = options.infra.filter((name) => name !== require_seeds.LOGS_SERVICE);
1745
1467
  const derived = require_seeds.composeFor({
1746
1468
  ...plan,
1747
1469
  containers: [...infra]
1748
1470
  }, {
1749
1471
  project,
1750
- ports: Object.fromEntries(require_seeds.portKeys(infra, plan.fakes).map((key) => [key, 0]))
1472
+ ports: Object.fromEntries(require_seeds.portKeys(infra, plan.fakes).map((key) => [key, 0])),
1473
+ credentials: options.credentials
1751
1474
  });
1752
1475
  const services = {};
1753
1476
  const volumes = {
@@ -1766,13 +1489,7 @@ function stackFile(options) {
1766
1489
  };
1767
1490
  }
1768
1491
  const postgres = services.postgres;
1769
- if (postgres) {
1770
- postgres.environment = {
1771
- ...postgres.environment,
1772
- POSTGRES_PASSWORD: options.master
1773
- };
1774
- postgres.ports = ["127.0.0.1::5432"];
1775
- }
1492
+ if (postgres) postgres.ports = ["127.0.0.1::5432"];
1776
1493
  const minio = services.minio;
1777
1494
  if (minio && options.storage) {
1778
1495
  minio.environment = {
@@ -1782,8 +1499,8 @@ function stackFile(options) {
1782
1499
  };
1783
1500
  minio.ports = ["127.0.0.1::9000"];
1784
1501
  }
1785
- if (services["redis"] && options.redis) services[REDIS_SERVICE] = redisService(options.redis);
1786
- if (options.logs) services[LOGS_SERVICE] = logsService(options.logs);
1502
+ if (services["redis"] && options.redis) services[require_seeds.REDIS_SERVICE] = require_seeds.redisService(options.redis);
1503
+ if (options.logs) services[require_seeds.LOGS_SERVICE] = require_seeds.logsService(options.logs);
1787
1504
  for (const app of apps) services[app.name] = {
1788
1505
  image: app.ref,
1789
1506
  ...app.build ? { build: {
@@ -2064,6 +1781,7 @@ const defaultDeps = {
2064
1781
  revision: gitRevision,
2065
1782
  sql: require_seeds.pgClient,
2066
1783
  buckets: require_seeds.bucketClient,
1784
+ logins: require_seeds.ensurePostgresLogin,
2067
1785
  migrate: require_databases.migrateDatabases,
2068
1786
  seed: require_databases.seedDatabases,
2069
1787
  probe: httpsProbe,
@@ -2122,6 +1840,7 @@ async function validateCompose(ctx, deps, options = {}) {
2122
1840
  ...registry ? { registry } : {}
2123
1841
  },
2124
1842
  secrets,
1843
+ ...stage === workspace.stages.local ? { localCredentials: (await require_seeds.loadLocalCredentials(workspace)).credentials } : {},
2125
1844
  allowDevServices: ctx.allowDevServices,
2126
1845
  ports: edgePorts(deps.env),
2127
1846
  ...layout ? { layout } : {},
@@ -2208,11 +1927,11 @@ async function stageSecrets(ctx, manifest) {
2208
1927
  generated: []
2209
1928
  };
2210
1929
  const withSeed = require_seeds.withGeneratedSecrets(stored ?? require_seeds.initStageSecrets(ctx.stage), manifest);
2211
- const withLogs = require_workspace.resolveLogs(ctx.workspace.deploy?.compose?.logs) ? withLogsPassword(withSeed.secrets) : {
1930
+ const withLogs = require_workspace.resolveLogs(ctx.workspace.deploy?.compose?.logs) ? require_seeds.withLogsPassword(withSeed.secrets) : {
2212
1931
  secrets: withSeed.secrets,
2213
1932
  generated: []
2214
1933
  };
2215
- const withRedis = runsRedis(manifest, withLogs.secrets.custom ?? {}) ? withRedisPassword(withLogs.secrets) : {
1934
+ const withRedis = require_seeds.runsRedis(manifest, withLogs.secrets.custom ?? {}) ? require_seeds.withRedisPassword(withLogs.secrets) : {
2216
1935
  secrets: withLogs.secrets,
2217
1936
  generated: []
2218
1937
  };
@@ -2390,10 +2109,20 @@ async function prepareDatabases(ctx, run, deps) {
2390
2109
  const { stack } = run;
2391
2110
  const workspace = ctx.workspace;
2392
2111
  const port = await deps.docker.port(run.ref, "postgres", 5432);
2112
+ const superuser = stack.credential.containers.postgres;
2113
+ const login = await deps.logins({
2114
+ port,
2115
+ login: superuser,
2116
+ legacy: [{
2117
+ user: require_seeds.LEGACY_LOCAL_LOGIN.user,
2118
+ password: superuser.password
2119
+ }, ...stack.local ? [require_seeds.LEGACY_LOCAL_LOGIN] : []]
2120
+ });
2121
+ if (login.notice) ctx.logger.info(login.notice);
2393
2122
  const statements = require_seeds.postgresStatements(stack.plan, workspace.name, stack.credential.seed);
2394
2123
  if (statements.length > 0) {
2395
2124
  ctx.logger.info("🗄️ Creating databases, roles and grants…");
2396
- await require_seeds.applyPostgres(deps.sql(port, stack.credential.master), statements);
2125
+ await require_seeds.applyPostgres(deps.sql(port, login.login), statements);
2397
2126
  }
2398
2127
  const sources = {};
2399
2128
  const manifest = await require_discover.discover({
@@ -2404,8 +2133,11 @@ async function prepareDatabases(ctx, run, deps) {
2404
2133
  const env = require_seeds.envFor(stack.plan, {
2405
2134
  ports: { [require_seeds.primaryPortKey("postgres")]: port },
2406
2135
  project: workspace.name,
2407
- master: stack.credential.master,
2408
- ...stack.credential.seed ? { seed: stack.credential.seed } : {}
2136
+ credentials: {
2137
+ ...stack.credential.containers,
2138
+ postgres: login.login
2139
+ },
2140
+ seed: stack.credential.seed
2409
2141
  });
2410
2142
  const options = {
2411
2143
  root: workspace.root,
@@ -2557,7 +2289,7 @@ async function verifyCompose(ctx, run, deps) {
2557
2289
  edge
2558
2290
  })));
2559
2291
  results.push(...await Promise.all(stack.workers.map((worker) => checkContainer(ctx, run, deps, worker.name))));
2560
- if (stack.logs) results.push(await checkContainer(ctx, run, deps, LOGS_SERVICE));
2292
+ if (stack.logs) results.push(await checkContainer(ctx, run, deps, require_seeds.LOGS_SERVICE));
2561
2293
  const down = results.filter((result) => !result.healthy).map(({ app, url, last }) => ({
2562
2294
  app,
2563
2295
  url,
@@ -2608,7 +2340,7 @@ async function checkContainer(ctx, run, deps, service) {
2608
2340
  }
2609
2341
  /** How to open the logs, printed — and as an event, for a headless caller. */
2610
2342
  function reportLogs(ctx, logs, local) {
2611
- const lines = logsAccess(logs, {
2343
+ const lines = require_seeds.logsAccess(logs, {
2612
2344
  stage: ctx.stage,
2613
2345
  local,
2614
2346
  user: currentUser(),
@@ -2617,7 +2349,7 @@ function reportLogs(ctx, logs, local) {
2617
2349
  ctx.logger.info(`\n${lines.join("\n")}`);
2618
2350
  ctx.emit({
2619
2351
  type: "logs.ready",
2620
- service: LOGS_SERVICE,
2352
+ service: require_seeds.LOGS_SERVICE,
2621
2353
  access: logs.public ? "public" : "tunnel",
2622
2354
  url: logs.url,
2623
2355
  ...logs.public ? { allow: [...logs.public.allow] } : { port: logs.port },
@@ -2760,8 +2492,8 @@ async function writeStack(root, dir, stack) {
2760
2492
  force: true
2761
2493
  });
2762
2494
  for (const app of [...stack.apps, ...stack.workers]) if (app.env) await write((0, node_path.join)(dir, `${app.name}.env`), envFile(app.env), 384);
2763
- if (stack.logs && stack.compose.services["openobserve"]?.env_file) await write((0, node_path.join)(dir, `${LOGS_SERVICE}.env`), envFile(stack.logs.env), 384);
2764
- if (stack.redis && stack.compose.services["redis"]?.env_file) await write((0, node_path.join)(dir, `${REDIS_SERVICE}.env`), envFile(stack.redis.env), 384);
2495
+ if (stack.logs && stack.compose.services["openobserve"]?.env_file) await write((0, node_path.join)(dir, `${require_seeds.LOGS_SERVICE}.env`), envFile(stack.logs.env), 384);
2496
+ if (stack.redis && stack.compose.services["redis"]?.env_file) await write((0, node_path.join)(dir, `${require_seeds.REDIS_SERVICE}.env`), envFile(stack.redis.env), 384);
2765
2497
  for (const entry of await (0, node_fs_promises.readdir)(dir)) if (entry.endsWith(".credentials")) await (0, node_fs_promises.rm)((0, node_path.join)(dir, entry));
2766
2498
  for (const [path, content] of Object.entries(stack.dockerfiles)) await write((0, node_path.join)(root, path), content);
2767
2499
  await require_seeds.ensureDockerignore(require_seeds.findBuildRoot(root));
@@ -2933,4 +2665,4 @@ Object.defineProperty(exports, "stackOverrideFile", {
2933
2665
  }
2934
2666
  });
2935
2667
 
2936
- //# sourceMappingURL=compose-Bn4OJONR.cjs.map
2668
+ //# sourceMappingURL=compose-qnDyPJNp.cjs.map