@monster-orange/den 0.2.7 → 0.3.0

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 (139) hide show
  1. package/CHANGELOG.md +1655 -0
  2. package/README.md +4 -2
  3. package/bin/den +2 -1
  4. package/docs/distributions.md +619 -3
  5. package/make/den.mk +19 -4
  6. package/make/journeys.mk +2 -1
  7. package/make/local_dev.mk +6 -13
  8. package/make/secrets-1password.mk +18 -0
  9. package/make/test.mk +1 -1
  10. package/package.json +9 -2
  11. package/schemas/den.plugin.v1.json +156 -0
  12. package/schemas/den.theme.v1.json +269 -0
  13. package/scripts/auth-configure.ts +16 -8
  14. package/scripts/heal-config.ts +0 -5
  15. package/scripts/setup-1password.ts +41 -19
  16. package/scripts/setup-secrets.ts +67 -101
  17. package/src/pillars/api/global-setup.ts +1 -1
  18. package/src/pillars/journeys/sandbox.ts +23 -18
  19. package/src/pillars/journeys/tools/browser-session.ts +2 -2
  20. package/src/scripts/setup-default-workspace.ts +2 -2
  21. package/src/shared/agent-actor.ts +38 -3
  22. package/src/shared/auth/headless-login.ts +1 -1
  23. package/src/shared/auth/sudo-vault.ts +93 -31
  24. package/src/shared/auth/token-providers/index.ts +7 -3
  25. package/src/shared/brand.ts +7 -0
  26. package/src/shared/config-error.ts +63 -0
  27. package/src/shared/constants.ts +3 -24
  28. package/src/shared/den-config.ts +185 -7
  29. package/src/shared/edge/config.ts +147 -21
  30. package/src/shared/edge/exec.ts +31 -0
  31. package/src/shared/edge/included.ts +169 -0
  32. package/src/shared/edge/render.ts +69 -14
  33. package/src/shared/edge/runtime.ts +128 -121
  34. package/src/shared/edge/status.ts +68 -8
  35. package/src/shared/edge/tls.ts +328 -0
  36. package/src/shared/edge/types.ts +25 -9
  37. package/src/shared/environment-manager/catalog.ts +52 -1
  38. package/src/shared/environment-manager/default-workspace.ts +26 -3
  39. package/src/shared/environment-manager/git-remote.ts +49 -0
  40. package/src/shared/environment-manager/health.ts +73 -38
  41. package/src/shared/environment-manager/scope.ts +168 -0
  42. package/src/shared/environment-manager/service-runner.ts +789 -43
  43. package/src/shared/environment-manager/up-targets.ts +132 -0
  44. package/src/shared/environment-manager/workspace-manager.ts +67 -25
  45. package/src/shared/environment-manager/workspace-org.ts +26 -11
  46. package/src/shared/health/rollup.ts +48 -0
  47. package/src/shared/manifest/service-links.ts +20 -8
  48. package/src/shared/open-url.ts +21 -2
  49. package/src/shared/org-sources.ts +328 -0
  50. package/src/shared/org.ts +53 -5
  51. package/src/shared/plugins/manifest.ts +406 -0
  52. package/src/shared/plugins/store.ts +358 -0
  53. package/src/shared/process/legacy-adopt.ts +285 -0
  54. package/src/shared/process/process-group.ts +202 -0
  55. package/src/shared/process/spawn-streaming.ts +7 -0
  56. package/src/shared/secrets/doppler.ts +1 -1
  57. package/src/shared/secrets/index.ts +279 -50
  58. package/src/shared/secrets/none.ts +56 -0
  59. package/src/shared/secrets/strategies.ts +202 -0
  60. package/src/shared/secrets/types.ts +1 -1
  61. package/src/shared/smoke/git-remote.ts +40 -0
  62. package/src/shared/stacks/manifest-commands.ts +1 -1
  63. package/src/shared/stacks/namespace.ts +178 -0
  64. package/src/shared/stacks/owners.ts +303 -0
  65. package/src/shared/stacks/render.ts +8 -3
  66. package/src/shared/stacks/runtime.ts +100 -16
  67. package/src/shared/stacks/status.ts +63 -12
  68. package/src/shared/strategies/registry.ts +89 -0
  69. package/src/shared/theme/contract.ts +538 -0
  70. package/src/shared/types/index.ts +24 -5
  71. package/src/ui/cli/clone-plan.ts +154 -23
  72. package/src/ui/cli/create.ts +24 -7
  73. package/src/ui/cli/doctor-command.ts +131 -0
  74. package/src/ui/cli/edge-command.ts +56 -10
  75. package/src/ui/cli/flag-parser.ts +62 -3
  76. package/src/ui/cli/health-display.ts +25 -0
  77. package/src/ui/cli/index.ts +649 -72
  78. package/src/ui/cli/notice.ts +20 -0
  79. package/src/ui/cli/org-command.ts +12 -2
  80. package/src/ui/cli/plugin-command.ts +849 -0
  81. package/src/ui/cli/stack-command.ts +63 -2
  82. package/src/ui/cli/test-runner.ts +11 -9
  83. package/src/ui/cli/theme-command.ts +136 -0
  84. package/src/ui/cli/up-args.ts +15 -0
  85. package/src/ui/cli/web-command.ts +47 -23
  86. package/src/ui/mcp/api.ts +28 -2
  87. package/src/ui/mcp/scope.ts +298 -0
  88. package/src/ui/mcp/server.ts +70 -8
  89. package/src/ui/mcp/tools.ts +151 -24
  90. package/src/ui/server/appearance-prefs.ts +95 -0
  91. package/src/ui/server/audit.ts +90 -3
  92. package/src/ui/server/daemon-reaper.ts +29 -1
  93. package/src/ui/server/engine-stamp.ts +83 -0
  94. package/src/ui/server/github-service.ts +29 -45
  95. package/src/ui/server/graph-page.ts +20 -0
  96. package/src/ui/server/graph-prefs.ts +44 -1
  97. package/src/ui/server/http-errors.ts +293 -0
  98. package/src/ui/server/http-helpers.ts +22 -3
  99. package/src/ui/server/index.ts +590 -45
  100. package/src/ui/server/public/appearance.js +531 -0
  101. package/src/ui/server/public/edge-page.js +248 -0
  102. package/src/ui/server/public/edge.html +251 -0
  103. package/src/ui/server/public/environments.html +65 -83
  104. package/src/ui/server/public/error-page.js +247 -0
  105. package/src/ui/server/public/error.html +260 -0
  106. package/src/ui/server/public/graph-model.js +332 -14
  107. package/src/ui/server/public/graph.css +141 -47
  108. package/src/ui/server/public/graph.js +223 -44
  109. package/src/ui/server/public/header.js +396 -600
  110. package/src/ui/server/public/health-fit.js +248 -0
  111. package/src/ui/server/public/health-rollup.js +128 -0
  112. package/src/ui/server/public/i18n/es.js +287 -0
  113. package/src/ui/server/public/i18n/ja.js +276 -0
  114. package/src/ui/server/public/index.html +139 -438
  115. package/src/ui/server/public/local-dev.html +3468 -4044
  116. package/src/ui/server/public/logs.html +71 -208
  117. package/src/ui/server/public/panel.js +15 -3
  118. package/src/ui/server/public/release.html +175 -389
  119. package/src/ui/server/public/services.html +596 -770
  120. package/src/ui/server/public/settings-page.js +759 -0
  121. package/src/ui/server/public/settings-registry.js +521 -0
  122. package/src/ui/server/public/settings.html +590 -0
  123. package/src/ui/server/public/shell.js +3869 -0
  124. package/src/ui/server/public/topbar.js +1413 -0
  125. package/src/ui/server/public/version-label.js +12 -0
  126. package/src/ui/server/public/workspace-picker.js +74 -34
  127. package/src/ui/server/readonly-routes.ts +24 -4
  128. package/src/ui/server/release-service.ts +25 -3
  129. package/src/ui/server/services-list.ts +94 -17
  130. package/src/ui/server/settings-distribution.ts +74 -0
  131. package/src/ui/server/shell-prefs.ts +192 -0
  132. package/src/ui/server/theme-routes.ts +292 -0
  133. package/src/ui/server/theme-runtime.ts +407 -0
  134. package/src/ui/server/ui-assets.ts +16 -1
  135. package/src/ui/server/ui-language.ts +50 -0
  136. package/src/ui/server/workspace-routes.ts +55 -9
  137. package/themes/den/den-theme.json +72 -0
  138. package/themes/den/favicon.ico +0 -0
  139. package/themes/den/mark.svg +6 -0
@@ -1,47 +1,30 @@
1
1
  /**
2
- * Side effects for the edge: docker, mkcert, files. Thin on purpose — every
3
- * routing decision lives in render.ts.
2
+ * Side effects for the edge: docker, files, and the TLS strategy's (tls.ts).
3
+ * Thin on purpose — every routing decision lives in render.ts.
4
4
  */
5
- import { spawnSync } from "child_process";
6
5
  import * as fs from "fs";
7
6
  import * as net from "net";
8
7
  import * as path from "path";
8
+ import { must, sh } from "./exec.js";
9
9
  import {
10
- certNames,
10
+ EDGE_ROOT_LABEL,
11
11
  edgeNames,
12
12
  renderEdgeCompose,
13
13
  renderEdgeDynamic,
14
14
  } from "./render.js";
15
+ import {
16
+ assertCaUsable,
17
+ caDir,
18
+ caTrusted,
19
+ edgeTrust,
20
+ edgeUntrust,
21
+ sharedCa,
22
+ tlsStrategyFor,
23
+ warnImplicitTlsOnce,
24
+ } from "./tls.js";
15
25
  import type { EdgeConfig } from "./types.js";
16
26
 
17
- function sh(
18
- cmd: string,
19
- args: string[],
20
- opts: { env?: NodeJS.ProcessEnv; inherit?: boolean } = {},
21
- ) {
22
- const r = spawnSync(cmd, args, {
23
- encoding: "utf-8",
24
- env: { ...process.env, ...opts.env },
25
- stdio: opts.inherit ? "inherit" : "pipe",
26
- });
27
- if (r.error) throw new Error(`${cmd} is not installed (${r.error.message})`);
28
- return {
29
- code: r.status ?? 1,
30
- out: (r.stdout ?? "").trim(),
31
- err: (r.stderr ?? "").trim(),
32
- };
33
- }
34
-
35
- function must(
36
- cmd: string,
37
- args: string[],
38
- opts: { env?: NodeJS.ProcessEnv } = {},
39
- ): string {
40
- const r = sh(cmd, args, opts);
41
- if (r.code !== 0)
42
- throw new Error(`${cmd} ${args.join(" ")} failed:\n${r.err || r.out}`);
43
- return r.out;
44
- }
27
+ export { assertCaUsable, caDir, caTrusted, sharedCa };
45
28
 
46
29
  function write(file: string, content: string): void {
47
30
  fs.mkdirSync(path.dirname(file), { recursive: true });
@@ -68,102 +51,25 @@ export function portInUse(port: number): Promise<boolean> {
68
51
  });
69
52
  }
70
53
 
71
- // ── Certificates (org-scoped CA) ─────────────────────────────────────────────
54
+ // ── Certificates (the edge.tls strategy, see tls.ts) ────────────────────────
72
55
 
73
- export function caDir(cfg: EdgeConfig): string {
74
- return cfg.caroot ?? path.join(cfg.stateDir, "ca");
75
- }
76
-
77
- /** True when the edge uses an existing CA (`caroot`) rather than its own. */
78
- export function sharedCa(cfg: EdgeConfig): boolean {
79
- return cfg.caroot !== null;
80
- }
81
-
82
- /**
83
- * A shared CA must already exist with its key: mkcert silently creates a new
84
- * CA in an empty CAROOT, and devices that trust the shared one would not
85
- * trust that. The org's own CA may be created on first use.
86
- */
87
- export function assertCaUsable(cfg: EdgeConfig): void {
88
- if (!sharedCa(cfg)) return;
89
- const dir = caDir(cfg);
90
- if (!fs.existsSync(dir))
91
- throw new Error(
92
- `edge.caroot ${dir} does not exist. The edge never creates a CA there; ` +
93
- `point caroot at the existing CA (\`mkcert -CAROOT\`).`,
94
- );
95
- for (const f of ["rootCA.pem", "rootCA-key.pem"])
96
- if (!fs.existsSync(path.join(dir, f)))
97
- throw new Error(
98
- `edge.caroot ${dir} has no ${f}. Refusing to run: mkcert would mint a ` +
99
- `new CA that other devices don't trust.`,
100
- );
101
- }
102
-
103
- function mkcertEnv(cfg: EdgeConfig): NodeJS.ProcessEnv {
104
- return { CAROOT: caDir(cfg) };
105
- }
106
-
107
- /** Issue (or re-issue when names changed) the edge cert from the org's own CA. */
56
+ /** Make sure the edge's cert exists, as its TLS strategy does it. */
108
57
  export function ensureCerts(cfg: EdgeConfig): {
109
58
  issued: boolean;
110
59
  names: string[];
111
60
  } {
112
- const names = certNames(cfg);
113
- if (!cfg.issueCert) return { issued: false, names: [] };
114
- assertCaUsable(cfg);
115
- const dir = path.join(cfg.stateDir, "certs");
116
- const stamp = path.join(dir, "names.txt");
117
- const want = names.join("\n");
118
- if (
119
- fs.existsSync(path.join(dir, "edge.pem")) &&
120
- fs.existsSync(stamp) &&
121
- fs.readFileSync(stamp, "utf-8") === want
122
- )
123
- return { issued: false, names };
124
- fs.mkdirSync(dir, { recursive: true });
125
- if (!sharedCa(cfg))
126
- fs.mkdirSync(caDir(cfg), { recursive: true, mode: 0o700 });
127
- must(
128
- "mkcert",
129
- [
130
- "-cert-file",
131
- path.join(dir, "edge.pem"),
132
- "-key-file",
133
- path.join(dir, "edge-key.pem"),
134
- ...names,
135
- ],
136
- { env: mkcertEnv(cfg) },
137
- );
138
- write(stamp, want);
139
- return { issued: true, names };
61
+ const r = tlsStrategyFor(cfg).capabilities.ensure(cfg);
62
+ return { issued: r.issued, names: r.names };
140
63
  }
141
64
 
142
- /** Install the org CA into the system + browser trust stores (prompts for a password). */
65
+ /** `edge trust`: install the CA (mkcert only; prompts for a password). */
143
66
  export function trustCa(cfg: EdgeConfig): number {
144
- assertCaUsable(cfg);
145
- if (!sharedCa(cfg))
146
- fs.mkdirSync(caDir(cfg), { recursive: true, mode: 0o700 });
147
- return sh("mkcert", ["-install"], { env: mkcertEnv(cfg), inherit: true })
148
- .code;
67
+ return edgeTrust(cfg);
149
68
  }
150
69
 
70
+ /** `edge untrust` (mkcert only; never a shared CA). */
151
71
  export function untrustCa(cfg: EdgeConfig): number {
152
- if (sharedCa(cfg))
153
- throw new Error(
154
- `edge.caroot is a shared CA (${caDir(cfg)}); other tools and devices ` +
155
- `rely on it being trusted, so the edge never uninstalls it.`,
156
- );
157
- return sh("mkcert", ["-uninstall"], { env: mkcertEnv(cfg), inherit: true })
158
- .code;
159
- }
160
-
161
- /** Whether the org CA is in the macOS system keychain (null: can't tell). */
162
- export function caTrusted(cfg: EdgeConfig): boolean | null {
163
- const pem = path.join(caDir(cfg), "rootCA.pem");
164
- if (!fs.existsSync(pem)) return false;
165
- if (process.platform !== "darwin") return null;
166
- return sh("security", ["verify-cert", "-c", pem]).code === 0;
72
+ return edgeUntrust(cfg);
167
73
  }
168
74
 
169
75
  // ── Included dynamic config ─────────────────────────────────────────────────
@@ -188,9 +94,13 @@ export function includeFiles(dir: string | null): string[] {
188
94
  */
189
95
  const INCLUDED_LEDGER = "included.json";
190
96
 
97
+ /** A stack's own route file (`stack-<project>.yml`, stacks/runtime.ts). */
98
+ export const isStackRoutes = (f: string): boolean =>
99
+ /^stack-.*\.ya?ml$/.test(f);
100
+
191
101
  /** Files other writers own in the dynamic directory: never copied over or pruned. */
192
- const isOwnedElsewhere = (f: string): boolean =>
193
- f === GENERATED || /^stack-.*\.ya?ml$/.test(f);
102
+ export const isOwnedElsewhere = (f: string): boolean =>
103
+ f === GENERATED || isStackRoutes(f);
194
104
 
195
105
  function readLedger(stateDir: string): string[] {
196
106
  try {
@@ -228,7 +138,101 @@ export function syncIncluded(cfg: EdgeConfig): string[] {
228
138
 
229
139
  // ── Edge ─────────────────────────────────────────────────────────────────────
230
140
 
231
- export async function edgeUp(cfg: EdgeConfig) {
141
+ /** Where the dynamic config mounts in the edge container (render.ts). */
142
+ const DYNAMIC_TARGET = "/etc/edge/dynamic";
143
+
144
+ /** An existing container of the edge's name: who owns it, as it says. */
145
+ export interface ExistingEdge {
146
+ /** Its `den.edge.root` label, or null for an unlabelled (legacy) edge. */
147
+ root: string | null;
148
+ /** The dir its dynamic-config mount comes from, when it has one. */
149
+ mountsDir: string | null;
150
+ }
151
+
152
+ /** The container named `name`, any state; null when there is none. */
153
+ export function existingEdge(name: string): ExistingEdge | null {
154
+ const r = sh("docker", [
155
+ "inspect",
156
+ "--type",
157
+ "container",
158
+ "-f",
159
+ `{{index .Config.Labels "${EDGE_ROOT_LABEL}"}}|{{range .Mounts}}{{.Source}}>{{.Destination}};{{end}}`,
160
+ name,
161
+ ]);
162
+ // A container always prints the separator; nothing at all is no answer.
163
+ if (r.code !== 0 || !r.out.includes("|")) return null;
164
+ const [label, mounts] = r.out.split("|");
165
+ const root = label && label !== "<no value>" ? label : null;
166
+ let mountsDir: string | null = null;
167
+ for (const m of (mounts ?? "").split(";")) {
168
+ const [src, dst] = m.split(">");
169
+ if (src && dst === DYNAMIC_TARGET) mountsDir = path.dirname(src);
170
+ }
171
+ return { root, mountsDir };
172
+ }
173
+
174
+ function realOrSelf(p: string): string {
175
+ try {
176
+ return fs.realpathSync(p);
177
+ } catch {
178
+ return path.resolve(p);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Whether this invocation may create, recreate or stop the edge
184
+ * (0.3.0-dev.40, P1: a scratch root with its own DEN_EDGE_DIR but the real
185
+ * edge.org recreated the user's real edge with the scratch certs and
186
+ * routes; the container and project are named by org). An existing edge is
187
+ * ours only when its `den.edge.root` label is this state dir; an
188
+ * unlabelled (legacy) one is adopted only from the default state dir, never
189
+ * under a DEN_EDGE_DIR that overrides it. `takeOver` (`edge up
190
+ * --take-over`) is the only override. Nothing is touched; only paths are
191
+ * said.
192
+ */
193
+ export function assertEdgeOwnedHere(
194
+ cfg: EdgeConfig,
195
+ opts: {
196
+ takeOver?: boolean;
197
+ warn?: (line: string) => void;
198
+ /** What a refusal says it won't do: "replacing" (up), "stopping" (down). */
199
+ verb?: "replacing" | "stopping";
200
+ } = {},
201
+ ): void {
202
+ const n = edgeNames(cfg.org);
203
+ const found = existingEdge(n.container);
204
+ if (!found) return;
205
+ const here = realOrSelf(cfg.stateDir);
206
+ const ours = found.root
207
+ ? realOrSelf(found.root) === here
208
+ : !cfg.defaultStateDir; // legacy: only from the default state dir
209
+ if (ours) return;
210
+ const other = found.root ?? found.mountsDir ?? "another state dir";
211
+ const warn = opts.warn ?? ((l: string) => console.warn(l));
212
+ // A scratch root on the real org: the name is the org's, so only another
213
+ // org isolates it.
214
+ if (cfg.defaultStateDir)
215
+ warn(
216
+ `Warning: DEN_EDGE_DIR (${cfg.stateDir}) is not this config's edge dir, ` +
217
+ `and edge.org "${cfg.org}" names an edge that exists already. ` +
218
+ `Give this root a distinct edge.org.`,
219
+ );
220
+ if (opts.takeOver) {
221
+ warn(`Taking over edge ${n.container} from ${other} (--take-over).`);
222
+ return;
223
+ }
224
+ throw new Error(
225
+ `edge ${n.container} belongs to ${other}; not ${opts.verb ?? "replacing"} it`,
226
+ );
227
+ }
228
+
229
+ export async function edgeUp(
230
+ cfg: EdgeConfig,
231
+ opts: { takeOver?: boolean } = {},
232
+ ) {
233
+ // Before anything is written or started: an edge of this name that
234
+ // another state dir owns is never replaced.
235
+ assertEdgeOwnedHere(cfg, opts);
232
236
  const n = edgeNames(cfg.org);
233
237
  if (cfg.ports && containerState(n.container) !== "running") {
234
238
  for (const p of [cfg.ports.http, cfg.ports.https])
@@ -240,6 +244,7 @@ export async function edgeUp(cfg: EdgeConfig) {
240
244
  );
241
245
  }
242
246
  assertCaUsable(cfg);
247
+ warnImplicitTlsOnce(cfg);
243
248
  ensureNetwork(n.network);
244
249
  for (const net of [
245
250
  ...(cfg.dockerLabels ? [cfg.dockerLabels.network] : []),
@@ -256,12 +261,14 @@ export async function edgeUp(cfg: EdgeConfig) {
256
261
  const included = syncIncluded(cfg);
257
262
  write(path.join(cfg.stateDir, "dynamic", GENERATED), renderEdgeDynamic(cfg));
258
263
  const compose = path.join(cfg.stateDir, "compose.yml");
259
- write(compose, renderEdgeCompose(cfg));
264
+ write(compose, renderEdgeCompose(cfg, realOrSelf(cfg.stateDir)));
260
265
  must("docker", ["compose", "-f", compose, "up", "-d", "--remove-orphans"]);
261
266
  return { certs, included };
262
267
  }
263
268
 
264
269
  export function edgeDown(cfg: EdgeConfig): void {
270
+ // The project is named by org too: another dir's edge is not ours to stop.
271
+ assertEdgeOwnedHere(cfg, { verb: "stopping" });
265
272
  const compose = path.join(cfg.stateDir, "compose.yml");
266
273
  if (fs.existsSync(compose))
267
274
  must("docker", ["compose", "-f", compose, "down"]);
@@ -6,7 +6,13 @@
6
6
  import * as fs from "fs";
7
7
  import * as path from "path";
8
8
  import { certNames, edgeDomains, edgeNames, routeUrl } from "./render.js";
9
+ import {
10
+ includedRouters,
11
+ stackRouters,
12
+ type IncludedRouters,
13
+ } from "./included.js";
9
14
  import { caTrusted, containerState } from "./runtime.js";
15
+ import { caDir } from "./tls.js";
10
16
  import type { EdgeConfig, EdgePorts } from "./types.js";
11
17
 
12
18
  export interface EdgeStatus {
@@ -23,8 +29,34 @@ export interface EdgeStatus {
23
29
  /** Names on the issued cert, or the planned ones when none is issued yet. */
24
30
  certNames: string[];
25
31
  certIssued: boolean;
26
- /** Whether the CA is trusted on this machine (null: can't tell). */
32
+ /** Whether the CA is trusted on this machine (null: can't tell, or none). */
27
33
  caTrusted: boolean | null;
34
+ /**
35
+ * The edge.tls strategy; whether nothing chose it (implicit mkcert);
36
+ * where it came from (edge.tls, the legacy caroot/issue_cert keys, or
37
+ * neither); and the CA directory (mkcert: the one it issues from; files:
38
+ * `ca` when set; none: null).
39
+ */
40
+ tls: {
41
+ strategy: EdgeConfig["tls"]["strategy"];
42
+ implicit: boolean;
43
+ from: EdgeConfig["tlsFrom"];
44
+ ca: string | null;
45
+ };
46
+ /**
47
+ * Routers in the included dynamic files (`edge include`), each with its
48
+ * source file: Traefik serves them, Den doesn't generate them.
49
+ */
50
+ included: IncludedRouters["routers"];
51
+ /** Included files that could not be parsed (listed, never fatal). */
52
+ includedErrors: IncludedRouters["errors"];
53
+ /**
54
+ * Routers in the stacks' route files (`stack-*.yml`, written by `stack
55
+ * up` into the edge's dynamic directory), each with its file (0.3.0-dev.32).
56
+ */
57
+ stacks: IncludedRouters["routers"];
58
+ /** Stack route files that could not be parsed (listed, never fatal). */
59
+ stackErrors: IncludedRouters["errors"];
28
60
  }
29
61
 
30
62
  export interface EdgeStatusDeps {
@@ -32,6 +64,10 @@ export interface EdgeStatusDeps {
32
64
  caTrusted: (cfg: EdgeConfig) => boolean | null;
33
65
  /** Names on the issued cert (its stamp file), or null when none is issued. */
34
66
  issuedNames: (cfg: EdgeConfig) => string[] | null;
67
+ /** The included files' routers (read from disk by default). */
68
+ included?: (cfg: EdgeConfig) => IncludedRouters;
69
+ /** The stacks' route files' routers (read from disk by default). */
70
+ stacks?: (cfg: EdgeConfig) => IncludedRouters;
35
71
  }
36
72
 
37
73
  /** The names stamp `ensureCerts` writes beside the cert. */
@@ -45,8 +81,19 @@ const defaultDeps: EdgeStatusDeps = {
45
81
  containerState,
46
82
  caTrusted,
47
83
  issuedNames: issuedCertNames,
84
+ included: includedRouters,
85
+ stacks: stackRouters,
48
86
  };
49
87
 
88
+ /** Den's own routes: `<name>.<domain>` → upstream (no Docker asked). */
89
+ export function edgeRoutes(cfg: EdgeConfig): EdgeStatus["routes"] {
90
+ return Object.entries(cfg.routes).map(([name, upstream]) => ({
91
+ name,
92
+ url: routeUrl(name, cfg),
93
+ upstream,
94
+ }));
95
+ }
96
+
50
97
  export function edgeStatus(
51
98
  cfg: EdgeConfig,
52
99
  deps: EdgeStatusDeps = defaultDeps,
@@ -54,6 +101,8 @@ export function edgeStatus(
54
101
  const n = edgeNames(cfg.org);
55
102
  const state = deps.containerState(n.container);
56
103
  const issued = deps.issuedNames(cfg);
104
+ const inc = (deps.included ?? includedRouters)(cfg);
105
+ const stk = (deps.stacks ?? stackRouters)(cfg);
57
106
  return {
58
107
  org: cfg.org,
59
108
  container: n.container,
@@ -62,13 +111,24 @@ export function edgeStatus(
62
111
  ports: cfg.ports,
63
112
  network: n.network,
64
113
  domains: edgeDomains(cfg),
65
- routes: Object.entries(cfg.routes).map(([name, upstream]) => ({
66
- name,
67
- url: routeUrl(name, cfg),
68
- upstream,
69
- })),
70
- certNames: issued ?? certNames(cfg),
114
+ routes: edgeRoutes(cfg),
115
+ certNames: issued ?? (cfg.tls.strategy === "none" ? [] : certNames(cfg)),
71
116
  certIssued: issued !== null,
72
- caTrusted: deps.caTrusted(cfg),
117
+ caTrusted: cfg.tls.strategy === "none" ? null : deps.caTrusted(cfg),
118
+ tls: {
119
+ strategy: cfg.tls.strategy,
120
+ implicit: cfg.tlsFrom === "implicit",
121
+ from: cfg.tlsFrom,
122
+ ca:
123
+ cfg.tls.strategy === "mkcert"
124
+ ? caDir(cfg)
125
+ : cfg.tls.strategy === "files"
126
+ ? cfg.tls.ca
127
+ : null,
128
+ },
129
+ included: inc.routers,
130
+ includedErrors: inc.errors,
131
+ stacks: stk.routers,
132
+ stackErrors: stk.errors,
73
133
  };
74
134
  }