@geekmidas/cli 10.0.0-alpha.68 → 10.0.0-alpha.70
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/{LegacyStateStore-BZiO9-Lw.cjs → LegacyStateStore-Dd5IwiyC.cjs} +2 -2
- package/dist/{LegacyStateStore-BZiO9-Lw.cjs.map → LegacyStateStore-Dd5IwiyC.cjs.map} +1 -1
- package/dist/{LegacyStateStore-C39UKwmf.mjs → LegacyStateStore-NSHEinQi.mjs} +2 -2
- package/dist/{LegacyStateStore-C39UKwmf.mjs.map → LegacyStateStore-NSHEinQi.mjs.map} +1 -1
- package/dist/{LocalStateStore-DHVsZLyF.cjs → LocalStateStore-DTylG4T4.cjs} +2 -2
- package/dist/{LocalStateStore-DHVsZLyF.cjs.map → LocalStateStore-DTylG4T4.cjs.map} +1 -1
- package/dist/{LocalStateStore-Dk3Wbvzk.mjs → LocalStateStore-DUoG2wA_.mjs} +2 -2
- package/dist/{LocalStateStore-Dk3Wbvzk.mjs.map → LocalStateStore-DUoG2wA_.mjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-BmUAcR7d.mjs → OpenApiTsGenerator-BYD9EcJi.mjs} +44 -13
- package/dist/OpenApiTsGenerator-BYD9EcJi.mjs.map +1 -0
- package/dist/{OpenApiTsGenerator-B2zrdyP4.cjs → OpenApiTsGenerator-CpRWM0N6.cjs} +44 -13
- package/dist/OpenApiTsGenerator-CpRWM0N6.cjs.map +1 -0
- package/dist/{S3StateStore--4tYVMAL.cjs → S3StateStore-D7hrU-sy.cjs} +2 -2
- package/dist/{S3StateStore--4tYVMAL.cjs.map → S3StateStore-D7hrU-sy.cjs.map} +1 -1
- package/dist/{S3StateStore-B_ZF9eHt.mjs → S3StateStore-Dxob3PNA.mjs} +2 -2
- package/dist/{S3StateStore-B_ZF9eHt.mjs.map → S3StateStore-Dxob3PNA.mjs.map} +1 -1
- package/dist/{SSMStateStore-ACZ8hRvZ.cjs → SSMStateStore-DGIKJ4NN.cjs} +2 -2
- package/dist/{SSMStateStore-ACZ8hRvZ.cjs.map → SSMStateStore-DGIKJ4NN.cjs.map} +1 -1
- package/dist/{SSMStateStore-DjPuKqrm.mjs → SSMStateStore-Dvl4A8Up.mjs} +2 -2
- package/dist/{SSMStateStore-DjPuKqrm.mjs.map → SSMStateStore-Dvl4A8Up.mjs.map} +1 -1
- package/dist/add-BuviRszv.mjs +566 -0
- package/dist/add-BuviRszv.mjs.map +1 -0
- package/dist/add-DHIk61nK.cjs +568 -0
- package/dist/add-DHIk61nK.cjs.map +1 -0
- package/dist/{bundler-C1pnNrR0.cjs → bundler-D7dcJVgj.cjs} +5 -5
- package/dist/{bundler-C1pnNrR0.cjs.map → bundler-D7dcJVgj.cjs.map} +1 -1
- package/dist/{bundler-C-w7aGoZ.mjs → bundler-DJlsYzm7.mjs} +5 -5
- package/dist/{bundler-C-w7aGoZ.mjs.map → bundler-DJlsYzm7.mjs.map} +1 -1
- package/dist/{cli-BLyD4c0O.cjs → cli-7zCum6uN.cjs} +4 -4
- package/dist/{cli-BLyD4c0O.cjs.map → cli-7zCum6uN.cjs.map} +1 -1
- package/dist/{cli-Ce4pLL5O.mjs → cli-BjnqVnE6.mjs} +4 -4
- package/dist/{cli-Ce4pLL5O.mjs.map → cli-BjnqVnE6.mjs.map} +1 -1
- package/dist/{compose-DvsgumY2.cjs → compose-BJYHmuvx.cjs} +368 -25
- package/dist/compose-BJYHmuvx.cjs.map +1 -0
- package/dist/{compose-DFxuYhn6.mjs → compose-CByVaaeR.mjs} +363 -26
- package/dist/compose-CByVaaeR.mjs.map +1 -0
- package/dist/{config-CxEp_g7j.d.mts → config-BN6iXvyR.d.mts} +3 -3
- package/dist/{config-BnA0kPTY.d.cts.map → config-BN6iXvyR.d.mts.map} +1 -1
- package/dist/{config-BnA0kPTY.d.cts → config-j9zBKaEG.d.cts} +3 -3
- package/dist/{config-CxEp_g7j.d.mts.map → config-j9zBKaEG.d.cts.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/credentialSchemas-BcNL1N5R.cjs +271 -0
- package/dist/credentialSchemas-BcNL1N5R.cjs.map +1 -0
- package/dist/credentialSchemas-BmYxnEz_.mjs +236 -0
- package/dist/credentialSchemas-BmYxnEz_.mjs.map +1 -0
- package/dist/deploy/public.cjs +7 -7
- package/dist/deploy/public.d.cts +3 -3
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +3 -3
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +7 -7
- package/dist/{deploy-C08GONYW.cjs → deploy--ZU9gOOa.cjs} +6 -6
- package/dist/{deploy-C08GONYW.cjs.map → deploy--ZU9gOOa.cjs.map} +1 -1
- package/dist/{deploy-D-EVOWvu.mjs → deploy-CCSGr7be.mjs} +6 -6
- package/dist/{deploy-D-EVOWvu.mjs.map → deploy-CCSGr7be.mjs.map} +1 -1
- package/dist/{discover-OEfZ9OkA.cjs → discover-B4VjaFJk.cjs} +2 -2
- package/dist/{discover-OEfZ9OkA.cjs.map → discover-B4VjaFJk.cjs.map} +1 -1
- package/dist/{discover-BFoJWKGD.mjs → discover-Buwf1z4P.mjs} +2 -2
- package/dist/{discover-BFoJWKGD.mjs.map → discover-Buwf1z4P.mjs.map} +1 -1
- package/dist/{dokploy-CZ6nfSV9.cjs → dokploy-BOupc9PK.cjs} +2 -2
- package/dist/{dokploy-CZ6nfSV9.cjs.map → dokploy-BOupc9PK.cjs.map} +1 -1
- package/dist/{dokploy-DU-XJsoT.mjs → dokploy-Bskfn1g5.mjs} +2 -2
- package/dist/{dokploy-DU-XJsoT.mjs.map → dokploy-Bskfn1g5.mjs.map} +1 -1
- package/dist/{encryption-CCcBSLZs.cjs → encryption-D-VkoJG4.cjs} +3 -3
- package/dist/{encryption-CCcBSLZs.cjs.map → encryption-D-VkoJG4.cjs.map} +1 -1
- package/dist/{encryption-DSiLau1-.mjs → encryption-DR-py9GX.mjs} +3 -3
- package/dist/{encryption-DSiLau1-.mjs.map → encryption-DR-py9GX.mjs.map} +1 -1
- package/dist/{engine-BM9Dy_DT.cjs → engine-Da8dBxt5.cjs} +16 -7
- package/dist/engine-Da8dBxt5.cjs.map +1 -0
- package/dist/{engine-llWnMZH5.mjs → engine-Xbv8NNdX.mjs} +16 -7
- package/dist/{engine-llWnMZH5.mjs.map → engine-Xbv8NNdX.mjs.map} +1 -1
- package/dist/{github-yy6WpHIl.mjs → github-D2pYzY7L.mjs} +3 -3
- package/dist/{github-yy6WpHIl.mjs.map → github-D2pYzY7L.mjs.map} +1 -1
- package/dist/{github-ZoMam0OH.cjs → github-DeXzs11c.cjs} +3 -3
- package/dist/{github-ZoMam0OH.cjs.map → github-DeXzs11c.cjs.map} +1 -1
- package/dist/{index-EvZBkycu.d.cts → index-Bk3NY6DN.d.cts} +2 -2
- package/dist/{index-CkX89m6S.d.mts.map → index-Bk3NY6DN.d.cts.map} +1 -1
- package/dist/{index-CkX89m6S.d.mts → index-DvoxuTqR.d.mts} +2 -2
- package/dist/{index-EvZBkycu.d.cts.map → index-DvoxuTqR.d.mts.map} +1 -1
- package/dist/index.cjs +89 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +89 -48
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-tOwxQBTI.cjs → migrate-BURDS8vy.cjs} +4 -4
- package/dist/{migrate-tOwxQBTI.cjs.map → migrate-BURDS8vy.cjs.map} +1 -1
- package/dist/{migrate-BWPuwiXk.mjs → migrate-DiJT84g9.mjs} +4 -4
- package/dist/{migrate-BWPuwiXk.mjs.map → migrate-DiJT84g9.mjs.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/{ready-B6uHr4sm.cjs → ready-C-qNcux-.cjs} +3 -3
- package/dist/{ready-B6uHr4sm.cjs.map → ready-C-qNcux-.cjs.map} +1 -1
- package/dist/{ready-2s3-CndS.mjs → ready-CfIiMts4.mjs} +3 -3
- package/dist/{ready-2s3-CndS.mjs.map → ready-CfIiMts4.mjs.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-BymzGtuT.cjs → resolve-B7jJQmTo.cjs} +3 -3
- package/dist/{resolve-BymzGtuT.cjs.map → resolve-B7jJQmTo.cjs.map} +1 -1
- package/dist/{resolve-pIvXlZ1h.mjs → resolve-Dj5TQKoD.mjs} +3 -3
- package/dist/{resolve-pIvXlZ1h.mjs.map → resolve-Dj5TQKoD.mjs.map} +1 -1
- package/dist/{rollback-ZlsbQOvj.mjs → rollback-Bw9kESjz.mjs} +5 -5
- package/dist/{rollback-ZlsbQOvj.mjs.map → rollback-Bw9kESjz.mjs.map} +1 -1
- package/dist/{rollback-zdfi6Reg.cjs → rollback-DNK1HEHY.cjs} +5 -5
- package/dist/{rollback-zdfi6Reg.cjs.map → rollback-DNK1HEHY.cjs.map} +1 -1
- package/dist/{runtime-DWThEP-r.d.cts → runtime-CYAO4iZG.d.cts} +2 -2
- package/dist/{runtime-B3jNgeP-.d.mts.map → runtime-CYAO4iZG.d.cts.map} +1 -1
- package/dist/{runtime-B3jNgeP-.d.mts → runtime-DHJhe4QI.d.mts} +2 -2
- package/dist/{runtime-DWThEP-r.d.cts.map → runtime-DHJhe4QI.d.mts.map} +1 -1
- package/dist/sandbox/credentials-worker.cjs +54 -0
- package/dist/sandbox/credentials-worker.cjs.map +1 -0
- package/dist/sandbox/credentials-worker.d.cts +1 -0
- package/dist/sandbox/credentials-worker.d.mts +1 -0
- package/dist/sandbox/credentials-worker.mjs +55 -0
- package/dist/sandbox/credentials-worker.mjs.map +1 -0
- package/dist/sandbox/discover-worker.cjs +1 -1
- package/dist/sandbox/discover-worker.mjs +1 -1
- package/dist/sandbox/migrate-worker.cjs +1 -1
- package/dist/sandbox/migrate-worker.mjs +1 -1
- package/dist/{schemas-RUlKHu27.cjs → schemas-BObwxB_t.cjs} +164 -3
- package/dist/schemas-BObwxB_t.cjs.map +1 -0
- package/dist/{schemas-Cfhtpdo0.mjs → schemas-D-xeKJDD.mjs} +153 -4
- package/dist/schemas-D-xeKJDD.mjs.map +1 -0
- package/dist/{sniffer-D66HIvi9.cjs → sniffer-DpHpFZBD.cjs} +7 -7
- package/dist/sniffer-DpHpFZBD.cjs.map +1 -0
- package/dist/{sniffer-BdFeLSmA.mjs → sniffer-vAXsU_4G.mjs} +7 -7
- package/dist/sniffer-vAXsU_4G.mjs.map +1 -0
- package/dist/{state-Bn5JDMFT.mjs → state-527OmqvN.mjs} +156 -64
- package/dist/state-527OmqvN.mjs.map +1 -0
- package/dist/{state-CHZqCDRC.cjs → state-nS6I6s4u.cjs} +179 -63
- package/dist/state-nS6I6s4u.cjs.map +1 -0
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{terminal-C3cv-OQu.mjs → terminal-C6TBo_SA.mjs} +2 -2
- package/dist/{terminal-C3cv-OQu.mjs.map → terminal-C6TBo_SA.mjs.map} +1 -1
- package/dist/{terminal-DjzgLu6m.cjs → terminal-CFO3xWoF.cjs} +2 -2
- package/dist/{terminal-DjzgLu6m.cjs.map → terminal-CFO3xWoF.cjs.map} +1 -1
- package/dist/{types-CZdCxUCK.d.cts → types-BD1jgA85.d.cts} +70 -2
- package/dist/types-BD1jgA85.d.cts.map +1 -0
- package/dist/{types-Da0E3Qv7.d.mts → types-DkG5WW8l.d.mts} +70 -2
- package/dist/types-DkG5WW8l.d.mts.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 +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-Be6uBhlF.mjs → workspace-C7NqCsQF.mjs} +105 -3
- package/dist/workspace-C7NqCsQF.mjs.map +1 -0
- package/dist/{workspace-ON-bqiJz.cjs → workspace-vdrcRI-m.cjs} +116 -2
- package/dist/workspace-vdrcRI-m.cjs.map +1 -0
- package/package.json +16 -13
- package/dist/OpenApiTsGenerator-B2zrdyP4.cjs.map +0 -1
- package/dist/OpenApiTsGenerator-BmUAcR7d.mjs.map +0 -1
- package/dist/compose-DFxuYhn6.mjs.map +0 -1
- package/dist/compose-DvsgumY2.cjs.map +0 -1
- package/dist/engine-BM9Dy_DT.cjs.map +0 -1
- package/dist/schemas-Cfhtpdo0.mjs.map +0 -1
- package/dist/schemas-RUlKHu27.cjs.map +0 -1
- package/dist/sniffer-BdFeLSmA.mjs.map +0 -1
- package/dist/sniffer-D66HIvi9.cjs.map +0 -1
- package/dist/state-Bn5JDMFT.mjs.map +0 -1
- package/dist/state-CHZqCDRC.cjs.map +0 -1
- package/dist/types-CZdCxUCK.d.cts.map +0 -1
- package/dist/types-Da0E3Qv7.d.mts.map +0 -1
- package/dist/workspace-Be6uBhlF.mjs.map +0 -1
- package/dist/workspace-ON-bqiJz.cjs.map +0 -1
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
2
2
|
const require_run = require("./run-fVZ-rLnc.cjs");
|
|
3
|
-
const require_discover = require("./discover-
|
|
3
|
+
const require_discover = require("./discover-B4VjaFJk.cjs");
|
|
4
4
|
const require_derive = require("./derive-DAbo49sm.cjs");
|
|
5
|
+
const require_workspace = require("./workspace-vdrcRI-m.cjs");
|
|
5
6
|
const require_define = require("./define-DzRz8FgH.cjs");
|
|
6
|
-
const require_state = require("./state-
|
|
7
|
+
const require_state = require("./state-nS6I6s4u.cjs");
|
|
7
8
|
const require_fakes = require("./fakes-zW-jNAbQ.cjs");
|
|
8
9
|
const require_databases = require("./databases-C2p5LGAp.cjs");
|
|
10
|
+
const require_credentialSchemas = require("./credentialSchemas-BcNL1N5R.cjs");
|
|
9
11
|
let node_path = require("node:path");
|
|
12
|
+
let node_fs = require("node:fs");
|
|
10
13
|
let node_fs_promises = require("node:fs/promises");
|
|
14
|
+
let node_os = require("node:os");
|
|
11
15
|
let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
12
16
|
let node_child_process = require("node:child_process");
|
|
17
|
+
let node_crypto = require("node:crypto");
|
|
13
18
|
let yaml = require("yaml");
|
|
14
19
|
let node_https = require("node:https");
|
|
15
20
|
//#region src/compose/docker.ts
|
|
@@ -65,6 +70,7 @@ function compose(stack, args) {
|
|
|
65
70
|
stack.project,
|
|
66
71
|
"-f",
|
|
67
72
|
stack.file,
|
|
73
|
+
...(stack.overrides ?? []).flatMap((file) => ["-f", file]),
|
|
68
74
|
...args
|
|
69
75
|
];
|
|
70
76
|
}
|
|
@@ -171,6 +177,16 @@ const dockerCompose = {
|
|
|
171
177
|
if (!Number.isInteger(port) || port <= 0) throw new ServicePortUnknown(service, inside, output);
|
|
172
178
|
return port;
|
|
173
179
|
},
|
|
180
|
+
async health(stack, service) {
|
|
181
|
+
const { code, stdout } = await capture("docker", [...compose(stack, [
|
|
182
|
+
"ps",
|
|
183
|
+
"--format",
|
|
184
|
+
"{{.Health}}",
|
|
185
|
+
service
|
|
186
|
+
])]);
|
|
187
|
+
if (code !== 0) return void 0;
|
|
188
|
+
return stdout.trim().split("\n")[0] || void 0;
|
|
189
|
+
},
|
|
174
190
|
async copyOut(stack, service, from, to) {
|
|
175
191
|
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(to), { recursive: true });
|
|
176
192
|
await require_run.run("docker", compose(stack, [
|
|
@@ -250,6 +266,184 @@ async function assertImagesExist(docker, tag, images) {
|
|
|
250
266
|
if (missing.length > 0) throw new ImageTagNotFound(tag, missing);
|
|
251
267
|
}
|
|
252
268
|
//#endregion
|
|
269
|
+
//#region src/compose/logs.ts
|
|
270
|
+
/**
|
|
271
|
+
* The stack's log UI: OpenObserve, run beside the apps when
|
|
272
|
+
* `deploy.compose.logs` asks for it, and every backend's OpenTelemetry
|
|
273
|
+
* pointed at it.
|
|
274
|
+
*
|
|
275
|
+
* The production server already exports its traces and pino logs over
|
|
276
|
+
* OTLP/HTTP when `OTEL_EXPORTER_OTLP_ENDPOINT` is set. OpenObserve takes OTLP
|
|
277
|
+
* at `/api/<org>/v1/{logs,traces}` with HTTP basic auth, so wiring an app is
|
|
278
|
+
* two variables: the endpoint, `http://openobserve:5080/api/default`, and
|
|
279
|
+
* the header that signs in as the root user.
|
|
280
|
+
*
|
|
281
|
+
* It is reached, by default, through an SSH tunnel: published on 127.0.0.1
|
|
282
|
+
* only, because a port Docker publishes on every interface is opened in the
|
|
283
|
+
* firewall by Docker itself, past ufw's rules. Served publicly, it is a host
|
|
284
|
+
* on the stack's Caddy that refuses every address not allowed.
|
|
285
|
+
*
|
|
286
|
+
* Pure: the stack decides it, and the phases print and check it.
|
|
287
|
+
*/
|
|
288
|
+
/**
|
|
289
|
+
* The image, pinned. A log store's on-disk format is the thing that must not
|
|
290
|
+
* move under a running stack, so a new version is a change to this line.
|
|
291
|
+
*/
|
|
292
|
+
const OPENOBSERVE_IMAGE = "public.ecr.aws/zinclabs/openobserve:v1.0.4";
|
|
293
|
+
/** Its compose service name — the host the apps send to. */
|
|
294
|
+
const LOGS_SERVICE = "openobserve";
|
|
295
|
+
/** The organisation every app's telemetry lands in. */
|
|
296
|
+
const LOGS_ORG = "default";
|
|
297
|
+
/** The stage secrets that sign in as OpenObserve's root user. */
|
|
298
|
+
const LOGS_EMAIL_KEY = "ZO_ROOT_USER_EMAIL";
|
|
299
|
+
const LOGS_PASSWORD_KEY = "ZO_ROOT_USER_PASSWORD";
|
|
300
|
+
/** The stage sends its telemetry somewhere already, and logs are on too. */
|
|
301
|
+
var LogsEndpointConflict = class extends Error {
|
|
302
|
+
stage;
|
|
303
|
+
keys;
|
|
304
|
+
constructor(stage, keys) {
|
|
305
|
+
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.`);
|
|
306
|
+
this.stage = stage;
|
|
307
|
+
this.keys = keys;
|
|
308
|
+
this.name = "LogsEndpointConflict";
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
/** A root password OpenObserve would refuse to start with. */
|
|
312
|
+
var LogsPasswordWeak = class extends Error {
|
|
313
|
+
stage;
|
|
314
|
+
constructor(stage) {
|
|
315
|
+
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.`);
|
|
316
|
+
this.stage = stage;
|
|
317
|
+
this.name = "LogsPasswordWeak";
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
/** A deployed stage with logs on and no root password generated yet. */
|
|
321
|
+
var LogsPasswordMissing = class extends Error {
|
|
322
|
+
stage;
|
|
323
|
+
constructor(stage) {
|
|
324
|
+
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}`);
|
|
325
|
+
this.stage = stage;
|
|
326
|
+
this.name = "LogsPasswordMissing";
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
/** Whether OpenObserve accepts `password` for its root user. */
|
|
330
|
+
function isStrongLogsPassword(password) {
|
|
331
|
+
return password.length >= 8 && password.length <= 128 && /[a-z]/.test(password) && /[A-Z]/.test(password) && /\d/.test(password) && /[^A-Za-z0-9]/.test(password);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* A root password: 192 random bits, with a fixed tail so every class
|
|
335
|
+
* OpenObserve requires is present whatever the random part drew.
|
|
336
|
+
*/
|
|
337
|
+
function generateLogsPassword() {
|
|
338
|
+
return `${(0, node_crypto.randomBytes)(24).toString("base64url")}-Zo1`;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* The stage's secrets with a generated root password, where it has none.
|
|
342
|
+
* Pure, like `withGeneratedSecrets`: the caller keeps the result once the
|
|
343
|
+
* run goes ahead.
|
|
344
|
+
*/
|
|
345
|
+
function withLogsPassword(secrets) {
|
|
346
|
+
if (secrets.custom?.["ZO_ROOT_USER_PASSWORD"]) return {
|
|
347
|
+
secrets,
|
|
348
|
+
generated: []
|
|
349
|
+
};
|
|
350
|
+
return {
|
|
351
|
+
secrets: {
|
|
352
|
+
...secrets,
|
|
353
|
+
custom: {
|
|
354
|
+
...secrets.custom,
|
|
355
|
+
[LOGS_PASSWORD_KEY]: generateLogsPassword()
|
|
356
|
+
},
|
|
357
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
358
|
+
},
|
|
359
|
+
generated: [LOGS_PASSWORD_KEY]
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* `OTEL_EXPORTER_OTLP_HEADERS` signing in as `email`.
|
|
364
|
+
*
|
|
365
|
+
* The variable's values are percent-decoded by the SDK (they follow the W3C
|
|
366
|
+
* baggage format), so the space after `Basic` is written `%20`. Base64's own
|
|
367
|
+
* `+`, `/` and `=` pass through decoding unchanged.
|
|
368
|
+
*/
|
|
369
|
+
function otlpHeaders(email, password) {
|
|
370
|
+
return `Authorization=Basic%20${Buffer.from(`${email}:${password}`).toString("base64")}`;
|
|
371
|
+
}
|
|
372
|
+
/** The stack's OpenObserve for one stage. */
|
|
373
|
+
function stackLogs(options) {
|
|
374
|
+
const { config, stage, local, custom } = options;
|
|
375
|
+
const conflicting = Object.keys(custom).filter(require_state.isOtlpDestination).sort();
|
|
376
|
+
if (conflicting.length > 0) throw new LogsEndpointConflict(stage, conflicting);
|
|
377
|
+
if (!local && !options.domain) throw new require_state.NoDomainForStage(stage);
|
|
378
|
+
const email = custom["ZO_ROOT_USER_EMAIL"] ?? (local ? "admin@gkm.localhost" : `admin@${options.domain}`);
|
|
379
|
+
const set = custom[LOGS_PASSWORD_KEY];
|
|
380
|
+
if (!set && !local) throw new LogsPasswordMissing(stage);
|
|
381
|
+
const password = set ?? "Geekmidas-1";
|
|
382
|
+
if (!isStrongLogsPassword(password)) throw new LogsPasswordWeak(stage);
|
|
383
|
+
const host = config.public ? local ? `logs.${options.project}.localhost` : `logs.${options.domain}` : void 0;
|
|
384
|
+
const url = host ? `https://${host}${local && options.https !== 443 ? `:${options.https}` : ""}` : `http://localhost:${config.port}`;
|
|
385
|
+
return {
|
|
386
|
+
...config,
|
|
387
|
+
email,
|
|
388
|
+
password,
|
|
389
|
+
passwordFromSecrets: Boolean(set),
|
|
390
|
+
url,
|
|
391
|
+
...host ? { host } : {},
|
|
392
|
+
env: {
|
|
393
|
+
[LOGS_EMAIL_KEY]: email,
|
|
394
|
+
[LOGS_PASSWORD_KEY]: password,
|
|
395
|
+
ZO_DATA_DIR: "/data",
|
|
396
|
+
ZO_COMPACT_DATA_RETENTION_DAYS: String(config.retentionDays),
|
|
397
|
+
ZO_TELEMETRY: "false"
|
|
398
|
+
},
|
|
399
|
+
appEnv: {
|
|
400
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: `http://${LOGS_SERVICE}:${require_workspace.LOGS_PORT}/api/${LOGS_ORG}`,
|
|
401
|
+
OTEL_EXPORTER_OTLP_HEADERS: otlpHeaders(email, password)
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* OpenObserve's service. The image has no shell, `wget` or `curl`, so its
|
|
407
|
+
* health check is its own binary asking the running server for its status —
|
|
408
|
+
* which exits non-zero when nothing answers on its port.
|
|
409
|
+
*/
|
|
410
|
+
function logsService(logs) {
|
|
411
|
+
return {
|
|
412
|
+
image: OPENOBSERVE_IMAGE,
|
|
413
|
+
restart: "unless-stopped",
|
|
414
|
+
env_file: [{
|
|
415
|
+
path: `./${LOGS_SERVICE}.env`,
|
|
416
|
+
format: "raw"
|
|
417
|
+
}],
|
|
418
|
+
volumes: ["openobserve-data:/data"],
|
|
419
|
+
...logs.public ? {} : { ports: [`127.0.0.1:${logs.port}:${require_workspace.LOGS_PORT}`] },
|
|
420
|
+
healthcheck: {
|
|
421
|
+
test: [
|
|
422
|
+
"CMD",
|
|
423
|
+
"/openobserve",
|
|
424
|
+
"node",
|
|
425
|
+
"status"
|
|
426
|
+
],
|
|
427
|
+
interval: "10s",
|
|
428
|
+
timeout: "5s",
|
|
429
|
+
retries: 12,
|
|
430
|
+
start_period: "20s"
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
/** What `gkm compose` prints once the stack is up: how to open the logs. */
|
|
435
|
+
function logsAccess(logs, ctx) {
|
|
436
|
+
const login = `login: ${logs.email}, password: ${ctx.local && !logs.passwordFromSecrets ? logs.password : `gkm secrets:show --stage ${ctx.stage} --reveal → ${LOGS_PASSWORD_KEY}`}`;
|
|
437
|
+
if (logs.public) return [`📜 Logs (OpenObserve): ${logs.url} (${login})`, ` answered only for ${logs.public.allow.join(", ")}; every other address gets 403`];
|
|
438
|
+
return [
|
|
439
|
+
`📜 Logs (OpenObserve) on 127.0.0.1:${logs.port} — from your computer:`,
|
|
440
|
+
...ctx.local ? [` on this machine, open http://localhost:${logs.port} directly; from another:`] : [],
|
|
441
|
+
` ssh -N -L ${logs.port}:localhost:${logs.port} ${ctx.user}@${ctx.hostname} (user and host are guesses: this machine's)`,
|
|
442
|
+
` then open http://localhost:${logs.port} (${login})`,
|
|
443
|
+
`⚠️ 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.`
|
|
444
|
+
];
|
|
445
|
+
}
|
|
446
|
+
//#endregion
|
|
253
447
|
//#region src/compose/caddyfile.ts
|
|
254
448
|
/** The Caddyfile for a stack's edge. */
|
|
255
449
|
function edgeCaddyfile(sites, options) {
|
|
@@ -261,8 +455,13 @@ function edgeCaddyfile(sites, options) {
|
|
|
261
455
|
}` : options.email ? `{
|
|
262
456
|
email ${options.email}
|
|
263
457
|
}` : "";
|
|
264
|
-
const blocks = sites.map(({ host, upstream, rewrite }) => `https://${host} {${options.local ? `
|
|
458
|
+
const blocks = sites.map(({ host, upstream, rewrite, allow }) => `https://${host} {${options.local ? `
|
|
265
459
|
tls internal
|
|
460
|
+
` : ""}${allow ? `
|
|
461
|
+
# Only these addresses — the peer Caddy sees, never a header a client
|
|
462
|
+
# could set. Everything else is refused before it reaches the upstream.
|
|
463
|
+
@denied not remote_ip ${allow.join(" ")}
|
|
464
|
+
respond @denied 403
|
|
266
465
|
` : ""}${rewrite ? `
|
|
267
466
|
# A bucket, served at a host of its own: the bucket is a prefix on MinIO,
|
|
268
467
|
# which routes and signs on the Host header — so it is sent the upstream's.
|
|
@@ -282,8 +481,9 @@ function edgeCaddyfile(sites, options) {
|
|
|
282
481
|
}
|
|
283
482
|
const HEADER = `# Generated by gkm compose from the construct manifest — do not edit.
|
|
284
483
|
#
|
|
285
|
-
# One host per API and site, each in front of its own container,
|
|
286
|
-
# file server in front of the stack's MinIO
|
|
484
|
+
# One host per API and site, each in front of its own container, one per
|
|
485
|
+
# file server in front of the stack's MinIO, and — when its logs are served
|
|
486
|
+
# publicly — one for OpenObserve, to the addresses it allows.
|
|
287
487
|
`;
|
|
288
488
|
//#endregion
|
|
289
489
|
//#region src/compose/stack.ts
|
|
@@ -350,6 +550,20 @@ const STORAGE_KEYS = [
|
|
|
350
550
|
"AWS_SECRET_ACCESS_KEY",
|
|
351
551
|
"AWS_REGION"
|
|
352
552
|
];
|
|
553
|
+
/**
|
|
554
|
+
* Every service's Docker logs, rotated. The json-file driver keeps a
|
|
555
|
+
* container's output in one file that grows until the disk is full — on a
|
|
556
|
+
* small server, in weeks. Three files of 10 MB each per container is enough
|
|
557
|
+
* to see what happened and never fills anything. A project's own override
|
|
558
|
+
* file that sets `logging` wins over it.
|
|
559
|
+
*/
|
|
560
|
+
const LOG_ROTATION = {
|
|
561
|
+
driver: "json-file",
|
|
562
|
+
options: {
|
|
563
|
+
"max-size": "10m",
|
|
564
|
+
"max-file": "3"
|
|
565
|
+
}
|
|
566
|
+
};
|
|
353
567
|
/** A value only the stage's secrets can hold, and they do not. */
|
|
354
568
|
var StageSecretMissing = class extends Error {
|
|
355
569
|
app;
|
|
@@ -459,6 +673,16 @@ function composeStack(input) {
|
|
|
459
673
|
reads.set(app.name, site?.kind === "site" ? Object.values((0, _geekmidas_manifest.publicEnvFor)(site, manifest)) : [...require_state.appEnvKeys(manifest, app.name, input.runnables) ?? []]);
|
|
460
674
|
}
|
|
461
675
|
const domain = workspace.deploy?.domains?.[stage];
|
|
676
|
+
const logsConfig = require_workspace.resolveLogs(workspace.deploy?.compose?.logs);
|
|
677
|
+
const logs = logsConfig ? stackLogs({
|
|
678
|
+
config: logsConfig,
|
|
679
|
+
stage,
|
|
680
|
+
local,
|
|
681
|
+
project: workspace.name,
|
|
682
|
+
...domain ? { domain } : {},
|
|
683
|
+
custom,
|
|
684
|
+
https
|
|
685
|
+
}) : void 0;
|
|
462
686
|
const services = local ? {
|
|
463
687
|
missing: [],
|
|
464
688
|
minio: plan.resources.filter((r) => r.kind === "objects").map((r) => r.id),
|
|
@@ -470,7 +694,7 @@ function composeStack(input) {
|
|
|
470
694
|
allow: input.allowDevServices ?? [],
|
|
471
695
|
...domain ? { domain } : {}
|
|
472
696
|
});
|
|
473
|
-
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).sort();
|
|
697
|
+
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), ...logs ? [LOGS_SERVICE] : []].sort();
|
|
474
698
|
const storage = infra.includes("minio") ? (() => {
|
|
475
699
|
const buckets = services.minio.map((id) => byId.get(id)?.name).filter((name) => Boolean(name)).sort();
|
|
476
700
|
return {
|
|
@@ -528,7 +752,7 @@ function composeStack(input) {
|
|
|
528
752
|
if (owner.kind === "file-server") return fileServers.get(owner.id)?.url;
|
|
529
753
|
return outside[key];
|
|
530
754
|
}
|
|
531
|
-
if (owner &&
|
|
755
|
+
if (owner && require_state.suppliedOnly(owner.kind, owner.id, key, local)) {
|
|
532
756
|
const value = custom[key];
|
|
533
757
|
if (value === void 0) throw new StageSecretMissing(app.name, key, stage);
|
|
534
758
|
return value;
|
|
@@ -571,6 +795,10 @@ function composeStack(input) {
|
|
|
571
795
|
}
|
|
572
796
|
app.env = {
|
|
573
797
|
...values,
|
|
798
|
+
...require_state.otelEnv({
|
|
799
|
+
...custom,
|
|
800
|
+
...logs?.appEnv
|
|
801
|
+
}, app.name),
|
|
574
802
|
NODE_ENV: "production",
|
|
575
803
|
PORT: String(app.port),
|
|
576
804
|
STAGE: stage
|
|
@@ -585,16 +813,25 @@ function composeStack(input) {
|
|
|
585
813
|
apps,
|
|
586
814
|
master: credential.master,
|
|
587
815
|
...storage ? { storage } : {},
|
|
816
|
+
...logs ? { logs } : {},
|
|
588
817
|
https,
|
|
589
818
|
http,
|
|
590
819
|
composeDir: (0, node_path.join)(workspace.root, stackDir(stage)),
|
|
591
820
|
buildRoot: layout.buildRoot,
|
|
592
821
|
workspaceRoot: workspace.root
|
|
593
822
|
});
|
|
594
|
-
const caddyfile = edgeCaddyfile([
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
823
|
+
const caddyfile = edgeCaddyfile([
|
|
824
|
+
...apps.map((app) => ({
|
|
825
|
+
host: app.host,
|
|
826
|
+
upstream: `${app.name}:${app.port}`
|
|
827
|
+
})),
|
|
828
|
+
...[...fileServers.values()].map(({ site }) => site),
|
|
829
|
+
...logs?.host && logs.public ? [{
|
|
830
|
+
host: logs.host,
|
|
831
|
+
upstream: `${LOGS_SERVICE}:${require_workspace.LOGS_PORT}`,
|
|
832
|
+
allow: logs.public.allow
|
|
833
|
+
}] : []
|
|
834
|
+
], { local });
|
|
598
835
|
const dockerfiles = {};
|
|
599
836
|
for (const app of apps) {
|
|
600
837
|
if (!app.build) continue;
|
|
@@ -616,15 +853,9 @@ function composeStack(input) {
|
|
|
616
853
|
plan,
|
|
617
854
|
credential,
|
|
618
855
|
...storage ? { storage } : {},
|
|
619
|
-
devServices: local ? [] : require_state.devServicesUsed(services)
|
|
856
|
+
devServices: local ? [] : require_state.devServicesUsed(services),
|
|
857
|
+
...logs ? { logs } : {}
|
|
620
858
|
};
|
|
621
|
-
/** Whether only the stage's secrets can supply `key` to `owner`. */
|
|
622
|
-
function suppliedBy(owner, key, isLocal) {
|
|
623
|
-
if (owner.kind === "credential") return true;
|
|
624
|
-
if (owner.kind === "external-api") return key === (0, _geekmidas_manifest.provideKey)(owner.id, "credentials");
|
|
625
|
-
if (isLocal) return false;
|
|
626
|
-
return owner.kind === "secret" || owner.kind === "encryption";
|
|
627
|
-
}
|
|
628
859
|
}
|
|
629
860
|
/**
|
|
630
861
|
* The mail and storage constructs a deployed stage's apps read, with who
|
|
@@ -710,7 +941,8 @@ function hostOf(name, app, workspace, resource, options) {
|
|
|
710
941
|
}
|
|
711
942
|
/** The compose document: the infrastructure, the apps, and the edge. */
|
|
712
943
|
function stackFile(options) {
|
|
713
|
-
const { project, plan,
|
|
944
|
+
const { project, plan, apps } = options;
|
|
945
|
+
const infra = options.infra.filter((name) => name !== LOGS_SERVICE);
|
|
714
946
|
const derived = require_state.composeFor({
|
|
715
947
|
...plan,
|
|
716
948
|
containers: [...infra]
|
|
@@ -722,7 +954,8 @@ function stackFile(options) {
|
|
|
722
954
|
const volumes = {
|
|
723
955
|
...derived.volumes,
|
|
724
956
|
"caddy-data": {},
|
|
725
|
-
"caddy-config": {}
|
|
957
|
+
"caddy-config": {},
|
|
958
|
+
...options.logs ? { "openobserve-data": {} } : {}
|
|
726
959
|
};
|
|
727
960
|
for (const name of infra) {
|
|
728
961
|
const { ports: _ports, depends_on, profiles: _profiles, ...service } = derived.services[name];
|
|
@@ -748,6 +981,7 @@ function stackFile(options) {
|
|
|
748
981
|
};
|
|
749
982
|
minio.ports = ["127.0.0.1::9000"];
|
|
750
983
|
}
|
|
984
|
+
if (options.logs) services[LOGS_SERVICE] = logsService(options.logs);
|
|
751
985
|
for (const app of apps) services[app.name] = {
|
|
752
986
|
image: app.ref,
|
|
753
987
|
...app.build ? { build: {
|
|
@@ -790,6 +1024,10 @@ function stackFile(options) {
|
|
|
790
1024
|
retries: 5
|
|
791
1025
|
}
|
|
792
1026
|
};
|
|
1027
|
+
for (const service of Object.values(services)) service.logging = {
|
|
1028
|
+
driver: LOG_ROTATION.driver,
|
|
1029
|
+
options: { ...LOG_ROTATION.options }
|
|
1030
|
+
};
|
|
793
1031
|
return {
|
|
794
1032
|
name: project,
|
|
795
1033
|
services,
|
|
@@ -985,6 +1223,15 @@ var NoGitRevision = class extends Error {
|
|
|
985
1223
|
this.name = "NoGitRevision";
|
|
986
1224
|
}
|
|
987
1225
|
};
|
|
1226
|
+
/**
|
|
1227
|
+
* The project's own file merged over a stage's stack: `docker-compose.<stage>.yml`
|
|
1228
|
+
* at the workspace root. What the generated file cannot know — a port bound
|
|
1229
|
+
* to a tailnet address, another log driver — goes there, and is never
|
|
1230
|
+
* overwritten.
|
|
1231
|
+
*/
|
|
1232
|
+
function stackOverrideFile(root, stage) {
|
|
1233
|
+
return (0, node_path.join)(root, `docker-compose.${stage}.yml`);
|
|
1234
|
+
}
|
|
988
1235
|
/** The commit HEAD is at, short — and `-dirty` when the tree has changes. */
|
|
989
1236
|
async function gitRevision(root) {
|
|
990
1237
|
let sha;
|
|
@@ -1045,6 +1292,13 @@ async function validateCompose(ctx, deps, forced) {
|
|
|
1045
1292
|
runnables
|
|
1046
1293
|
});
|
|
1047
1294
|
const { secrets, generated } = await stageSecrets(ctx, manifest);
|
|
1295
|
+
await require_credentialSchemas.assertStageCredentials({
|
|
1296
|
+
root,
|
|
1297
|
+
patterns: require_state.constructGlobs(workspace),
|
|
1298
|
+
manifest,
|
|
1299
|
+
stage,
|
|
1300
|
+
supplied: secrets?.custom ?? {}
|
|
1301
|
+
});
|
|
1048
1302
|
const masterKeys = {};
|
|
1049
1303
|
const layout = mode === "build" ? require_state.imageLayout(workspace) : void 0;
|
|
1050
1304
|
const composed = composeStack({
|
|
@@ -1083,7 +1337,14 @@ async function validateCompose(ctx, deps, forced) {
|
|
|
1083
1337
|
}
|
|
1084
1338
|
const stack = withBuildCredentials(composed, masterKeys);
|
|
1085
1339
|
if (mode === "pull" && !ctx.dryRun) await assertImagesExist(deps.docker, tag, stack.apps);
|
|
1340
|
+
if (stack.logs && (!stack.local || stack.logs.passwordFromSecrets)) {
|
|
1341
|
+
ctx.secrets.mask(stack.logs.password);
|
|
1342
|
+
ctx.secrets.mask(stack.logs.appEnv.OTEL_EXPORTER_OTLP_HEADERS);
|
|
1343
|
+
}
|
|
1086
1344
|
const dir = (0, node_path.join)(root, stackDir(stage));
|
|
1345
|
+
const override = stackOverrideFile(root, stage);
|
|
1346
|
+
const overrides = (0, node_fs.existsSync)(override) ? [override] : [];
|
|
1347
|
+
if (overrides.length > 0) ctx.logger.info(`🧩 Merging docker-compose.${stage}.yml over the generated stack`);
|
|
1087
1348
|
const previous = Object.fromEntries(Object.entries((await ctx.state.read(stage))?.state.releases ?? {}).map(([name, releases]) => {
|
|
1088
1349
|
const { ref, tag, digest } = releases.current;
|
|
1089
1350
|
return [name, {
|
|
@@ -1099,6 +1360,7 @@ async function validateCompose(ctx, deps, forced) {
|
|
|
1099
1360
|
ref: {
|
|
1100
1361
|
project: composeProject(identity),
|
|
1101
1362
|
file: (0, node_path.join)(dir, "docker-compose.yml"),
|
|
1363
|
+
...overrides.length > 0 ? { overrides } : {},
|
|
1102
1364
|
cwd: root,
|
|
1103
1365
|
output: ctx.childOutput,
|
|
1104
1366
|
signal: ctx.signal
|
|
@@ -1124,7 +1386,13 @@ async function stageSecrets(ctx, manifest) {
|
|
|
1124
1386
|
secrets: stored,
|
|
1125
1387
|
generated: []
|
|
1126
1388
|
};
|
|
1127
|
-
const
|
|
1389
|
+
const withSeed = require_state.withGeneratedSecrets(stored ?? require_state.initStageSecrets(ctx.stage), manifest);
|
|
1390
|
+
const withLogs = require_workspace.resolveLogs(ctx.workspace.deploy?.compose?.logs) ? withLogsPassword(withSeed.secrets) : {
|
|
1391
|
+
secrets: withSeed.secrets,
|
|
1392
|
+
generated: []
|
|
1393
|
+
};
|
|
1394
|
+
const secrets = withLogs.secrets;
|
|
1395
|
+
const generated = [...withSeed.generated, ...withLogs.generated];
|
|
1128
1396
|
if (secrets.seed) ctx.secrets.mask(secrets.seed);
|
|
1129
1397
|
for (const value of Object.values(secrets.custom ?? {})) ctx.secrets.mask(value);
|
|
1130
1398
|
return {
|
|
@@ -1336,15 +1604,83 @@ async function verifyCompose(ctx, run, deps) {
|
|
|
1336
1604
|
const { stack } = run;
|
|
1337
1605
|
const ca = stack.local ? await (0, node_fs_promises.readFile)(caFile(run), "utf-8").catch(() => void 0) : void 0;
|
|
1338
1606
|
ctx.logger.info("\n🩺 Checking each app through Caddy…");
|
|
1339
|
-
const
|
|
1607
|
+
const results = await Promise.all(stack.apps.map((app) => checkApp(ctx, app, deps, {
|
|
1340
1608
|
ca,
|
|
1341
1609
|
local: stack.local
|
|
1342
|
-
})))
|
|
1610
|
+
})));
|
|
1611
|
+
if (stack.logs) results.push(await checkLogs(ctx, run, deps));
|
|
1612
|
+
const down = results.filter((result) => !result.healthy).map(({ app, url, last }) => ({
|
|
1343
1613
|
app,
|
|
1344
1614
|
url,
|
|
1345
1615
|
last
|
|
1346
1616
|
}));
|
|
1347
1617
|
if (down.length > 0) throw new ComposeAppsUnhealthy(stack.project, down);
|
|
1618
|
+
if (stack.logs) reportLogs(ctx, stack.logs, stack.local);
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* OpenObserve's own health check, as Docker reports it. It is not asked
|
|
1622
|
+
* through the edge: by default it has no host there, and served publicly it
|
|
1623
|
+
* answers only the addresses it allows — which need not include this one.
|
|
1624
|
+
*/
|
|
1625
|
+
async function checkLogs(ctx, run, deps) {
|
|
1626
|
+
const url = `docker:${LOGS_SERVICE}`;
|
|
1627
|
+
let last = "not asked";
|
|
1628
|
+
for (let attempt = 1; attempt <= deps.healthAttempts; attempt++) {
|
|
1629
|
+
ctx.signal.throwIfAborted();
|
|
1630
|
+
const health = await deps.docker.health(run.ref, LOGS_SERVICE).catch((error) => error instanceof Error ? error.message : String(error));
|
|
1631
|
+
last = health ?? "not running";
|
|
1632
|
+
const healthy = health === "healthy";
|
|
1633
|
+
ctx.emit({
|
|
1634
|
+
type: "health.checked",
|
|
1635
|
+
app: LOGS_SERVICE,
|
|
1636
|
+
url,
|
|
1637
|
+
healthy,
|
|
1638
|
+
attempt
|
|
1639
|
+
});
|
|
1640
|
+
if (healthy) {
|
|
1641
|
+
ctx.logger.info(` ✓ ${LOGS_SERVICE.padEnd(12)} ${url} (${last})`);
|
|
1642
|
+
return {
|
|
1643
|
+
app: LOGS_SERVICE,
|
|
1644
|
+
url,
|
|
1645
|
+
last,
|
|
1646
|
+
healthy
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
if (attempt < deps.healthAttempts) await new Promise((resolve) => setTimeout(resolve, deps.healthIntervalMs));
|
|
1650
|
+
}
|
|
1651
|
+
ctx.logger.warn(` ✗ ${LOGS_SERVICE.padEnd(12)} ${url} (${last})`);
|
|
1652
|
+
return {
|
|
1653
|
+
app: LOGS_SERVICE,
|
|
1654
|
+
url,
|
|
1655
|
+
last,
|
|
1656
|
+
healthy: false
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
/** How to open the logs, printed — and as an event, for a headless caller. */
|
|
1660
|
+
function reportLogs(ctx, logs, local) {
|
|
1661
|
+
const lines = logsAccess(logs, {
|
|
1662
|
+
stage: ctx.stage,
|
|
1663
|
+
local,
|
|
1664
|
+
user: currentUser(),
|
|
1665
|
+
hostname: (0, node_os.hostname)()
|
|
1666
|
+
});
|
|
1667
|
+
ctx.logger.info(`\n${lines.join("\n")}`);
|
|
1668
|
+
ctx.emit({
|
|
1669
|
+
type: "logs.ready",
|
|
1670
|
+
service: LOGS_SERVICE,
|
|
1671
|
+
access: logs.public ? "public" : "tunnel",
|
|
1672
|
+
url: logs.url,
|
|
1673
|
+
...logs.public ? { allow: [...logs.public.allow] } : { port: logs.port },
|
|
1674
|
+
email: logs.email
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
/** This machine's user — a guess at who would SSH in. */
|
|
1678
|
+
function currentUser() {
|
|
1679
|
+
try {
|
|
1680
|
+
return (0, node_os.userInfo)().username;
|
|
1681
|
+
} catch {
|
|
1682
|
+
return process.env.USER ?? "user";
|
|
1683
|
+
}
|
|
1348
1684
|
}
|
|
1349
1685
|
async function checkApp(ctx, app, deps, options) {
|
|
1350
1686
|
const url = `${app.url}${app.kind === "site" ? "/" : "/health"}`;
|
|
@@ -1449,6 +1785,7 @@ async function writeStack(root, dir, stack, credentials = {}) {
|
|
|
1449
1785
|
await write((0, node_path.join)(dir, "docker-compose.yml"), composeYaml(stack));
|
|
1450
1786
|
await write((0, node_path.join)(dir, "Caddyfile"), stack.caddyfile);
|
|
1451
1787
|
for (const app of stack.apps) if (app.env) await write((0, node_path.join)(dir, `${app.name}.env`), envFile(app.env), 384);
|
|
1788
|
+
if (stack.logs) await write((0, node_path.join)(dir, `${LOGS_SERVICE}.env`), envFile(stack.logs.env), 384);
|
|
1452
1789
|
for (const [name, payload] of Object.entries(credentials)) await write((0, node_path.join)(dir, credentialsFile(name)), require_state.credentialsFileContent(payload), 384);
|
|
1453
1790
|
for (const [path, content] of Object.entries(stack.dockerfiles)) await write((0, node_path.join)(root, path), content);
|
|
1454
1791
|
await require_state.ensureDockerignore(require_state.findBuildRoot(root));
|
|
@@ -1562,5 +1899,11 @@ Object.defineProperty(exports, "stackDir", {
|
|
|
1562
1899
|
return stackDir;
|
|
1563
1900
|
}
|
|
1564
1901
|
});
|
|
1902
|
+
Object.defineProperty(exports, "stackOverrideFile", {
|
|
1903
|
+
enumerable: true,
|
|
1904
|
+
get: function() {
|
|
1905
|
+
return stackOverrideFile;
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1565
1908
|
|
|
1566
|
-
//# sourceMappingURL=compose-
|
|
1909
|
+
//# sourceMappingURL=compose-BJYHmuvx.cjs.map
|