@pleri/olam-cli 0.1.151 → 0.1.153

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 (51) hide show
  1. package/dist/commands/doctor.d.ts +53 -2
  2. package/dist/commands/doctor.d.ts.map +1 -1
  3. package/dist/commands/doctor.js +74 -11
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/host-cp.d.ts.map +1 -1
  6. package/dist/commands/host-cp.js +17 -0
  7. package/dist/commands/host-cp.js.map +1 -1
  8. package/dist/commands/services.d.ts.map +1 -1
  9. package/dist/commands/services.js +9 -26
  10. package/dist/commands/services.js.map +1 -1
  11. package/dist/commands/substrate.d.ts +27 -0
  12. package/dist/commands/substrate.d.ts.map +1 -1
  13. package/dist/commands/substrate.js +27 -8
  14. package/dist/commands/substrate.js.map +1 -1
  15. package/dist/commands/upgrade.d.ts.map +1 -1
  16. package/dist/commands/upgrade.js +11 -0
  17. package/dist/commands/upgrade.js.map +1 -1
  18. package/dist/image-digests.json +7 -7
  19. package/dist/index.js +1747 -1293
  20. package/dist/lib/auth-refresh-kubernetes.d.ts +3 -0
  21. package/dist/lib/auth-refresh-kubernetes.d.ts.map +1 -1
  22. package/dist/lib/auth-refresh-kubernetes.js +6 -17
  23. package/dist/lib/auth-refresh-kubernetes.js.map +1 -1
  24. package/dist/lib/health-probes.d.ts +43 -0
  25. package/dist/lib/health-probes.d.ts.map +1 -1
  26. package/dist/lib/health-probes.js +106 -0
  27. package/dist/lib/health-probes.js.map +1 -1
  28. package/dist/lib/k8s-bootstrap.d.ts +120 -0
  29. package/dist/lib/k8s-bootstrap.d.ts.map +1 -0
  30. package/dist/lib/k8s-bootstrap.js +193 -0
  31. package/dist/lib/k8s-bootstrap.js.map +1 -0
  32. package/dist/lib/k8s-secret-render.d.ts +139 -0
  33. package/dist/lib/k8s-secret-render.d.ts.map +1 -0
  34. package/dist/lib/k8s-secret-render.js +281 -0
  35. package/dist/lib/k8s-secret-render.js.map +1 -0
  36. package/dist/lib/kubectl-context.d.ts +38 -0
  37. package/dist/lib/kubectl-context.d.ts.map +1 -0
  38. package/dist/lib/kubectl-context.js +43 -0
  39. package/dist/lib/kubectl-context.js.map +1 -0
  40. package/dist/lib/upgrade-kubernetes.d.ts +24 -1
  41. package/dist/lib/upgrade-kubernetes.d.ts.map +1 -1
  42. package/dist/lib/upgrade-kubernetes.js +90 -38
  43. package/dist/lib/upgrade-kubernetes.js.map +1 -1
  44. package/host-cp/k8s/manifests/50-deployment.yaml +8 -1
  45. package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +4 -1
  46. package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +4 -1
  47. package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +4 -1
  48. package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +4 -1
  49. package/host-cp/src/docker-events.mjs +19 -4
  50. package/host-cp/src/server.mjs +18 -1
  51. package/package.json +1 -1
@@ -0,0 +1,193 @@
1
+ /**
2
+ * k8s-bootstrap.ts — apply the `olam` namespace + RBAC + secret set on a
3
+ * fresh kubernetes cluster.
4
+ *
5
+ * B4 of olam-issue-npm-only-k3s. Without this an npm-only operator running
6
+ * `olam upgrade --substrate=kubernetes` against a fresh cluster fails with:
7
+ *
8
+ * Error from server (NotFound): namespaces "olam" not found
9
+ * Secret "olam-host-cp-secret" not found in namespace "olam"
10
+ *
11
+ * The 8-step upgrade flow (upgrade-kubernetes.ts) already runs
12
+ * `seedManifestsFromBundle` to put manifest YAML at ~/.olam/k8s/manifests/,
13
+ * but no one APPLIES them on first install — operators were expected to
14
+ * `kubectl apply -f` by hand. That barrier is the npm-only blocker this
15
+ * module closes.
16
+ *
17
+ * Idempotency: kubectl apply is idempotent by design for manifests
18
+ * (server-side merge keys deduplicate). For secrets we render once and
19
+ * persist the values to ~/.olam/k8s-secrets-state.json — subsequent runs
20
+ * reuse the same values so worlds that have cached tokens don't break.
21
+ *
22
+ * Order (matches the seed manifest filename numeric prefix):
23
+ * 1. 00-namespace.yaml — host-cp namespace
24
+ * 2. 10-serviceaccount.yaml — host-cp ServiceAccount
25
+ * 3. 20-rbac.yaml — host-cp Role/RoleBinding
26
+ * 4. 30-configmap.yaml — host-cp env ConfigMap
27
+ * 5. 45-pvc.yaml — host-cp PersistentVolumeClaim
28
+ * 6. host-cp Secret — RENDERED from templates/40-secret-template.yaml
29
+ * 7. per-peripheral manifests + secrets (auth-service, mcp-auth-service, kg-service, memory-service)
30
+ *
31
+ * Deployments + Services (50-deployment.yaml, 60-service.yaml) are NOT
32
+ * applied here — that is upgrade-kubernetes.ts's job (it manages rollout
33
+ * status + port-forward + audit log). ensureK8sBootstrap returns once the
34
+ * "prerequisites" (namespace + RBAC + secrets) are in place.
35
+ */
36
+ import { spawnSync } from 'node:child_process';
37
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
38
+ import { join } from 'node:path';
39
+ import { kubectlWrap } from './kubectl-wrap.js';
40
+ import { installRoot } from '../install-root.js';
41
+ import { OLAM_HOME } from './config.js';
42
+ import { renderAllSecrets, SECRET_TEMPLATE_BINDINGS, } from './k8s-secret-render.js';
43
+ export const K8S_NAMESPACE = 'olam';
44
+ /**
45
+ * Manifest files inside `<install-root>/host-cp/k8s/manifests/` that
46
+ * ensureK8sBootstrap applies. Ordered numerically by filename prefix.
47
+ * Deployment + Service are intentionally absent — upgrade-kubernetes.ts
48
+ * applies those alongside its rollout-status step.
49
+ */
50
+ const HOST_CP_PREREQ_MANIFESTS = [
51
+ '00-namespace.yaml',
52
+ '10-serviceaccount.yaml',
53
+ '20-rbac.yaml',
54
+ '30-configmap.yaml',
55
+ '45-pvc.yaml',
56
+ ];
57
+ /**
58
+ * Peripheral subdirs under `<install-root>/host-cp/k8s/manifests/`. Each
59
+ * carries its own 10/20/30/45 prerequisite YAMLs. 50-deployment + 60-service
60
+ * are skipped here (upgrade.ts owns rollout).
61
+ */
62
+ const PERIPHERAL_SUBDIRS = ['auth-service', 'mcp-auth-service', 'kg-service', 'memory-service'];
63
+ const PERIPHERAL_PREREQ_FILES = ['10-serviceaccount.yaml', '20-rbac.yaml', '30-configmap.yaml', '45-pvc.yaml'];
64
+ /**
65
+ * kubectl apply wrapper — accepts a YAML string and applies via stdin (D20
66
+ * stdin-safe; values never inlined into argv). When `manifestPath` is given
67
+ * the file is applied via `-f <path>` instead (manifests stay on disk).
68
+ */
69
+ async function kubectlApply(context, source, deps) {
70
+ const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
71
+ if ('path' in source) {
72
+ const r = await wrap(['--context', context, 'apply', '-f', source.path], { timeout: 30_000 });
73
+ return { ok: r.ok, stderr: r.stderr };
74
+ }
75
+ const r = await wrap(['--context', context, 'apply', '-f', '-'], { timeout: 30_000, stdin: source.stdinYaml });
76
+ return { ok: r.ok, stderr: r.stderr };
77
+ }
78
+ /**
79
+ * Locate the bundled k8s assets directory. In install-mode this is
80
+ * `<install-root>/host-cp/k8s/`. In dev-mode it falls back to the
81
+ * monorepo's `packages/host-cp/k8s/`.
82
+ */
83
+ export function resolveK8sAssetsRoot(installRootDir = installRoot()) {
84
+ const installed = join(installRootDir, 'host-cp', 'k8s');
85
+ if (existsSync(installed))
86
+ return installed;
87
+ // Dev-mode fallback. installRoot points at <repo>/packages/cli; walk up.
88
+ const repoRoot = join(installRootDir, '..', '..');
89
+ const monorepo = join(repoRoot, 'packages', 'host-cp', 'k8s');
90
+ if (existsSync(monorepo))
91
+ return monorepo;
92
+ return null;
93
+ }
94
+ /**
95
+ * Run the bootstrap. Idempotent on re-run.
96
+ *
97
+ * Apply order (numeric filename prefix mirrors apply ORDER):
98
+ * 1. <root>/manifests/00-namespace.yaml + 10/20/30/45 (host-cp prereqs)
99
+ * 2. Per-peripheral 10/20/30/45 prereqs (auth-service / mcp-auth-service / kg-service / memory-service)
100
+ * 3. host-cp Secret (rendered from templates/40-secret-template.yaml)
101
+ * 4. Per-peripheral Secrets (rendered from each *-secret-template.yaml)
102
+ *
103
+ * On the first failed apply we surface the kubectl stderr and stop —
104
+ * partial-apply state is left in the cluster (kubectl apply is idempotent;
105
+ * re-running picks up where we stopped).
106
+ */
107
+ export async function ensureK8sBootstrap(opts, deps = {}) {
108
+ const stdout = deps.stdout ?? process.stdout;
109
+ const stderr = deps.stderr ?? process.stderr;
110
+ const namespace = opts.namespace ?? K8S_NAMESPACE;
111
+ const apply = deps.applyImpl ?? kubectlApply;
112
+ const assetsRoot = deps.k8sAssetsRoot ?? resolveK8sAssetsRoot();
113
+ if (assetsRoot === null || !existsSync(assetsRoot)) {
114
+ return {
115
+ exitCode: 1,
116
+ result: { applied: [], skipped: [], secretResults: [] },
117
+ error: 'Could not find bundled k8s assets. Expected <install-root>/host-cp/k8s/ or <repo>/packages/host-cp/k8s/. ' +
118
+ 'Reinstall the CLI: `npm install -g @pleri/olam-cli@latest`.',
119
+ };
120
+ }
121
+ const manifestsRoot = join(assetsRoot, 'manifests');
122
+ const applied = [];
123
+ const skipped = [];
124
+ // 1+2. host-cp prereq manifests + per-peripheral prereqs.
125
+ const manifestPaths = [];
126
+ for (const f of HOST_CP_PREREQ_MANIFESTS) {
127
+ manifestPaths.push(join(manifestsRoot, f));
128
+ }
129
+ for (const sub of PERIPHERAL_SUBDIRS) {
130
+ for (const f of PERIPHERAL_PREREQ_FILES) {
131
+ const p = join(manifestsRoot, sub, f);
132
+ if (existsSync(p))
133
+ manifestPaths.push(p);
134
+ }
135
+ }
136
+ for (const mp of manifestPaths) {
137
+ if (opts.dryRun) {
138
+ applied.push({ kind: 'manifest', path: mp });
139
+ continue;
140
+ }
141
+ if (!existsSync(mp)) {
142
+ skipped.push({ kind: 'manifest', ref: mp, reason: 'file not found in bundle (older package?)' });
143
+ continue;
144
+ }
145
+ stdout.write(` → applying ${mp.replace(assetsRoot, '<k8s-assets>')}\n`);
146
+ const r = await apply(opts.context, { path: mp }, deps);
147
+ if (!r.ok) {
148
+ stderr.write(`error: kubectl apply -f ${mp} failed:\n${r.stderr}\n`);
149
+ return {
150
+ exitCode: 1,
151
+ result: { applied, skipped, secretResults: [] },
152
+ error: `kubectl apply of ${mp} failed`,
153
+ };
154
+ }
155
+ applied.push({ kind: 'manifest', path: mp });
156
+ }
157
+ // 3+4. Render + apply secrets.
158
+ const { results } = renderAllSecrets(assetsRoot, { rotate: opts.rotateSecrets === true, context: opts.context, namespace }, deps.renderDeps ?? {});
159
+ for (const r of results) {
160
+ if (r.status === 'skipped') {
161
+ const reason = `unresolved source(s): ${r.missingSources.join('; ')}`;
162
+ skipped.push({ kind: 'secret', ref: r.secretName, reason });
163
+ stderr.write(`warn: Secret ${r.secretName} skipped — ${reason}\n`);
164
+ stderr.write(` Once you have the source value, apply manually:\n`);
165
+ stderr.write(` kubectl --context ${opts.context} apply -n ${namespace} -f - <<EOF\n (rendered secret YAML)\nEOF\n`);
166
+ continue;
167
+ }
168
+ if (opts.dryRun) {
169
+ applied.push({ kind: 'secret', name: r.secretName });
170
+ continue;
171
+ }
172
+ stdout.write(` → applying Secret ${r.secretName}\n`);
173
+ const a = await apply(opts.context, { stdinYaml: r.renderedYaml }, deps);
174
+ if (!a.ok) {
175
+ stderr.write(`error: kubectl apply Secret ${r.secretName} failed:\n${a.stderr}\n`);
176
+ return {
177
+ exitCode: 1,
178
+ result: { applied, skipped, secretResults: results },
179
+ error: `kubectl apply Secret ${r.secretName} failed`,
180
+ };
181
+ }
182
+ applied.push({ kind: 'secret', name: r.secretName });
183
+ }
184
+ return { exitCode: 0, result: { applied, skipped, secretResults: results } };
185
+ }
186
+ /**
187
+ * Read the list of expected Secret names — used by probeK8sRequiredSecrets
188
+ * so the doctor probe stays in lockstep with the bootstrap module.
189
+ */
190
+ export function expectedSecretNames() {
191
+ return SECRET_TEMPLATE_BINDINGS.map((b) => b.secretName);
192
+ }
193
+ //# sourceMappingURL=k8s-bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"k8s-bootstrap.js","sourceRoot":"","sources":["../../src/lib/k8s-bootstrap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * k8s-secret-render.ts — render `REPLACE_ME_FROM_*` placeholders in the
3
+ * shipped k8s Secret templates with real values, and persist the rendered
4
+ * set so reapply is idempotent.
5
+ *
6
+ * B5 of olam-issue-npm-only-k3s (npm-only operator bootstrap completion).
7
+ *
8
+ * The templates at `packages/host-cp/k8s/templates/*-secret-template.yaml`
9
+ * carry placeholders like `REPLACE_ME_FROM_HOME_DOTOLAM_AUTH_SECRET`. The
10
+ * source-of-truth value for each placeholder is documented in the template
11
+ * comment block; it's either a host-side file in `~/.olam/` (the compose
12
+ * bootstrap path) or a subprocess output (e.g. `gh auth token`).
13
+ *
14
+ * Source resolution per placeholder:
15
+ *
16
+ * placeholder → source
17
+ * REPLACE_ME_FROM_HOME_DOTOLAM_AUTH_SECRET → file ~/.olam/auth-secret (generate if missing)
18
+ * REPLACE_ME_FROM_GH_AUTH_TOKEN → subprocess `gh auth token` (warn + skip if absent)
19
+ * REPLACE_ME_FROM_HOME_DOTOLAM_AUTH_DB_SECRET → file ~/.olam/auth-db-secret (generate if missing)
20
+ * REPLACE_ME_FROM_HOME_DOTOLAM_MCP_AUTH_JWT_SECRET → file ~/.olam/mcp-auth-jwt-secret (generate if missing)
21
+ * REPLACE_ME_FROM_HOME_DOTOLAM_KG_BEARER_TOKEN → file ~/.olam/kg-bearer-token (generate if missing)
22
+ * REPLACE_ME_FROM_HOME_DOTOLAM_MEMORY_BEARER_SECRET → file ~/.olam/memory-bearer-secret (generate if missing)
23
+ *
24
+ * State persistence: `~/.olam/k8s-secrets-state.json` records the rendered
25
+ * Secret set so re-runs (idempotent reapply) reuse the same values. Without
26
+ * the state file every `olam upgrade` would rotate tokens silently, breaking
27
+ * worlds that have cached the previous value. Operator opts in to rotation
28
+ * via `--rotate-secrets`.
29
+ *
30
+ * D20 stdin-safe design: rendered YAML is built in-process and applied via
31
+ * stdin to `kubectl apply -f -`. Placeholder values are NEVER inlined into
32
+ * subprocess argv (audit:auth-callers + general security hygiene).
33
+ */
34
+ export declare const K8S_SECRETS_STATE_PATH: string;
35
+ /**
36
+ * One template's placeholder set + the per-host-file source it reads from.
37
+ * Hardcoded mapping — every secret template's `# Source:` comment names the
38
+ * file; the audit:cli-bundle-k8s gate ensures the templates stay in sync.
39
+ */
40
+ export interface SecretTemplateBinding {
41
+ readonly secretName: string;
42
+ readonly templateRelPath: string;
43
+ readonly placeholders: ReadonlyArray<{
44
+ readonly placeholder: string;
45
+ readonly key: string;
46
+ readonly source: {
47
+ readonly kind: 'file';
48
+ readonly hostFile: string;
49
+ } | {
50
+ readonly kind: 'gh-token';
51
+ };
52
+ }>;
53
+ }
54
+ /** Canonical mapping for the 5 secret templates that ship with the CLI. */
55
+ export declare const SECRET_TEMPLATE_BINDINGS: ReadonlyArray<SecretTemplateBinding>;
56
+ /** Per-secret state record — keys map to RENDERED values reused on re-apply. */
57
+ export interface RenderedSecret {
58
+ readonly keys: Record<string, string>;
59
+ readonly skipped?: ReadonlyArray<string>;
60
+ }
61
+ /** State file shape. version=1 lets us migrate the schema without breaking older state. */
62
+ export interface K8sSecretsState {
63
+ readonly version: 1;
64
+ readonly context: string;
65
+ readonly namespace: string;
66
+ readonly generatedAt: string;
67
+ readonly secrets: Record<string, RenderedSecret>;
68
+ }
69
+ /** Injectable deps so tests don't touch the real filesystem / shell. */
70
+ export interface RenderDeps {
71
+ readonly olamHome?: string;
72
+ readonly statePath?: string;
73
+ readonly readFile?: (path: string, enc: 'utf8') => string;
74
+ readonly writeFile?: (path: string, data: string, mode: number) => void;
75
+ readonly fileExists?: (path: string) => boolean;
76
+ readonly genRandomHex?: () => string;
77
+ readonly runGhTokenCmd?: () => {
78
+ ok: boolean;
79
+ token: string;
80
+ };
81
+ readonly readState?: () => K8sSecretsState | null;
82
+ readonly writeState?: (state: K8sSecretsState) => void;
83
+ readonly stderr?: NodeJS.WritableStream;
84
+ }
85
+ /**
86
+ * Read the state file. Returns null when missing OR when the version doesn't match (forward-compat).
87
+ */
88
+ export declare function readSecretsState(statePath?: string): K8sSecretsState | null;
89
+ /** Atomically write the state file (0600). */
90
+ export declare function writeSecretsState(state: K8sSecretsState, statePath?: string): void;
91
+ /**
92
+ * Result of rendering one Secret. Either:
93
+ * - resolved: every placeholder got a value (rendered YAML is ready to apply).
94
+ * - skipped: at least one source was unavailable AND not generatable (e.g.
95
+ * gh auth token absent); the operator must apply this secret
96
+ * manually OR install gh + retry.
97
+ */
98
+ export interface RenderResult {
99
+ readonly secretName: string;
100
+ readonly status: 'resolved' | 'skipped';
101
+ readonly renderedYaml?: string;
102
+ readonly keys: Record<string, string>;
103
+ readonly missingSources: ReadonlyArray<string>;
104
+ }
105
+ /**
106
+ * Substitute the placeholder strings in the template YAML with real values.
107
+ * Builds a fresh Secret YAML string (does NOT touch the template on disk).
108
+ *
109
+ * The templates hold ONE placeholder per stringData key; literal string
110
+ * substitution suffices. We avoid YAML parsing to keep the template comment
111
+ * block (which carries operator-facing documentation) intact in the rendered
112
+ * output.
113
+ */
114
+ export declare function substitutePlaceholders(templateYaml: string, values: Record<string, string>): string;
115
+ /**
116
+ * Render ONE secret template. Reads the template YAML from `templatesRoot`
117
+ * (resolved by the caller to either source or install-mode location),
118
+ * resolves each placeholder, persists generated values to host files,
119
+ * substitutes, and returns the rendered YAML.
120
+ *
121
+ * `reuse` carries the prior state's per-key values when an unrotated
122
+ * re-apply is requested; null means "this is the first apply or operator
123
+ * asked for rotation."
124
+ */
125
+ export declare function renderOneSecret(binding: SecretTemplateBinding, templatesRoot: string, reuse: RenderedSecret | null, deps?: RenderDeps, rotate?: boolean): RenderResult;
126
+ /**
127
+ * Render the full set of shipped Secret templates. `rotate=true` discards
128
+ * the on-disk state and regenerates every value; `rotate=false` reuses
129
+ * state for unchanged secrets so worlds caching old tokens don't break.
130
+ */
131
+ export declare function renderAllSecrets(templatesRoot: string, opts: {
132
+ rotate: boolean;
133
+ context: string;
134
+ namespace: string;
135
+ }, deps?: RenderDeps): {
136
+ results: ReadonlyArray<RenderResult>;
137
+ nextState: K8sSecretsState;
138
+ };
139
+ //# sourceMappingURL=k8s-secret-render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"k8s-secret-render.d.ts","sourceRoot":"","sources":["../../src/lib/k8s-secret-render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * k8s-secret-render.ts — render `REPLACE_ME_FROM_*` placeholders in the
3
+ * shipped k8s Secret templates with real values, and persist the rendered
4
+ * set so reapply is idempotent.
5
+ *
6
+ * B5 of olam-issue-npm-only-k3s (npm-only operator bootstrap completion).
7
+ *
8
+ * The templates at `packages/host-cp/k8s/templates/*-secret-template.yaml`
9
+ * carry placeholders like `REPLACE_ME_FROM_HOME_DOTOLAM_AUTH_SECRET`. The
10
+ * source-of-truth value for each placeholder is documented in the template
11
+ * comment block; it's either a host-side file in `~/.olam/` (the compose
12
+ * bootstrap path) or a subprocess output (e.g. `gh auth token`).
13
+ *
14
+ * Source resolution per placeholder:
15
+ *
16
+ * placeholder → source
17
+ * REPLACE_ME_FROM_HOME_DOTOLAM_AUTH_SECRET → file ~/.olam/auth-secret (generate if missing)
18
+ * REPLACE_ME_FROM_GH_AUTH_TOKEN → subprocess `gh auth token` (warn + skip if absent)
19
+ * REPLACE_ME_FROM_HOME_DOTOLAM_AUTH_DB_SECRET → file ~/.olam/auth-db-secret (generate if missing)
20
+ * REPLACE_ME_FROM_HOME_DOTOLAM_MCP_AUTH_JWT_SECRET → file ~/.olam/mcp-auth-jwt-secret (generate if missing)
21
+ * REPLACE_ME_FROM_HOME_DOTOLAM_KG_BEARER_TOKEN → file ~/.olam/kg-bearer-token (generate if missing)
22
+ * REPLACE_ME_FROM_HOME_DOTOLAM_MEMORY_BEARER_SECRET → file ~/.olam/memory-bearer-secret (generate if missing)
23
+ *
24
+ * State persistence: `~/.olam/k8s-secrets-state.json` records the rendered
25
+ * Secret set so re-runs (idempotent reapply) reuse the same values. Without
26
+ * the state file every `olam upgrade` would rotate tokens silently, breaking
27
+ * worlds that have cached the previous value. Operator opts in to rotation
28
+ * via `--rotate-secrets`.
29
+ *
30
+ * D20 stdin-safe design: rendered YAML is built in-process and applied via
31
+ * stdin to `kubectl apply -f -`. Placeholder values are NEVER inlined into
32
+ * subprocess argv (audit:auth-callers + general security hygiene).
33
+ */
34
+ import { spawnSync } from 'node:child_process';
35
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, statSync } from 'node:fs';
36
+ import { join, dirname } from 'node:path';
37
+ import { randomBytes } from 'node:crypto';
38
+ import { OLAM_HOME } from './config.js';
39
+ export const K8S_SECRETS_STATE_PATH = join(OLAM_HOME, 'k8s-secrets-state.json');
40
+ const SECRET_HEX_BYTES = 32; // 64-char hex tokens — mirrors memory-secret.ts SECRET_LEN_BYTES.
41
+ /** Canonical mapping for the 5 secret templates that ship with the CLI. */
42
+ export const SECRET_TEMPLATE_BINDINGS = [
43
+ {
44
+ secretName: 'olam-host-cp-secret',
45
+ templateRelPath: 'templates/40-secret-template.yaml',
46
+ placeholders: [
47
+ {
48
+ placeholder: 'REPLACE_ME_FROM_HOME_DOTOLAM_AUTH_SECRET',
49
+ key: 'OLAM_AUTH_SECRET',
50
+ source: { kind: 'file', hostFile: 'auth-secret' },
51
+ },
52
+ {
53
+ placeholder: 'REPLACE_ME_FROM_GH_AUTH_TOKEN',
54
+ key: 'GH_TOKEN',
55
+ source: { kind: 'gh-token' },
56
+ },
57
+ ],
58
+ },
59
+ {
60
+ secretName: 'olam-auth-service-secret',
61
+ templateRelPath: 'templates/auth-service-secret-template.yaml',
62
+ placeholders: [
63
+ {
64
+ placeholder: 'REPLACE_ME_FROM_HOME_DOTOLAM_AUTH_DB_SECRET',
65
+ key: 'OLAM_AUTH_DB_SECRET',
66
+ source: { kind: 'file', hostFile: 'auth-db-secret' },
67
+ },
68
+ ],
69
+ },
70
+ {
71
+ secretName: 'olam-mcp-auth-service-secret',
72
+ templateRelPath: 'templates/mcp-auth-service-secret-template.yaml',
73
+ placeholders: [
74
+ {
75
+ placeholder: 'REPLACE_ME_FROM_HOME_DOTOLAM_MCP_AUTH_JWT_SECRET',
76
+ key: 'OLAM_MCP_AUTH_JWT_SECRET',
77
+ source: { kind: 'file', hostFile: 'mcp-auth-jwt-secret' },
78
+ },
79
+ ],
80
+ },
81
+ {
82
+ secretName: 'olam-kg-service-secret',
83
+ templateRelPath: 'templates/kg-service-secret-template.yaml',
84
+ placeholders: [
85
+ {
86
+ placeholder: 'REPLACE_ME_FROM_HOME_DOTOLAM_KG_BEARER_TOKEN',
87
+ key: 'OLAM_KG_BEARER_TOKEN',
88
+ source: { kind: 'file', hostFile: 'kg-bearer-token' },
89
+ },
90
+ ],
91
+ },
92
+ {
93
+ secretName: 'olam-memory-service-secret',
94
+ templateRelPath: 'templates/memory-service-secret-template.yaml',
95
+ placeholders: [
96
+ {
97
+ placeholder: 'REPLACE_ME_FROM_HOME_DOTOLAM_MEMORY_BEARER_SECRET',
98
+ key: 'OLAM_MEMORY_BEARER_SECRET',
99
+ source: { kind: 'file', hostFile: 'memory-bearer-secret' },
100
+ },
101
+ ],
102
+ },
103
+ ];
104
+ const defaultGenRandomHex = () => randomBytes(SECRET_HEX_BYTES).toString('hex');
105
+ const defaultRunGhTokenCmd = () => {
106
+ const r = spawnSync('gh', ['auth', 'token'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
107
+ if (r.status === 0 && r.stdout.trim().length > 0) {
108
+ return { ok: true, token: r.stdout.trim() };
109
+ }
110
+ return { ok: false, token: '' };
111
+ };
112
+ const defaultReadFile = (p, enc) => readFileSync(p, enc);
113
+ const defaultWriteFile = (p, data, mode) => {
114
+ mkdirSync(dirname(p), { recursive: true });
115
+ writeFileSync(p, data, { encoding: 'utf8', mode });
116
+ // writeFileSync's mode is umask-respecting; chmod explicitly to be safe.
117
+ chmodSync(p, mode);
118
+ };
119
+ const defaultFileExists = (p) => existsSync(p);
120
+ /**
121
+ * Read the state file. Returns null when missing OR when the version doesn't match (forward-compat).
122
+ */
123
+ export function readSecretsState(statePath = K8S_SECRETS_STATE_PATH) {
124
+ if (!existsSync(statePath))
125
+ return null;
126
+ try {
127
+ const raw = readFileSync(statePath, 'utf8');
128
+ const parsed = JSON.parse(raw);
129
+ if (parsed.version !== 1)
130
+ return null;
131
+ return parsed;
132
+ }
133
+ catch {
134
+ return null;
135
+ }
136
+ }
137
+ /** Atomically write the state file (0600). */
138
+ export function writeSecretsState(state, statePath = K8S_SECRETS_STATE_PATH) {
139
+ mkdirSync(dirname(statePath), { recursive: true });
140
+ const tmp = `${statePath}.tmp.${process.pid}`;
141
+ writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
142
+ chmodSync(tmp, 0o600);
143
+ // renameSync is atomic on POSIX
144
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
145
+ require('node:fs').renameSync(tmp, statePath);
146
+ }
147
+ /**
148
+ * Resolve a single placeholder's value. Returns null when the source is
149
+ * absent AND not generatable (gh-token only). For file-sourced placeholders
150
+ * we always generate if missing — that's the npm-only operator path.
151
+ */
152
+ function resolveSourceValue(source, olamHome, reuseFromState, rotate, deps) {
153
+ if (!rotate && reuseFromState !== undefined && reuseFromState !== '') {
154
+ return { ok: true, value: reuseFromState };
155
+ }
156
+ if (source.kind === 'file') {
157
+ const filePath = join(olamHome, source.hostFile);
158
+ // Rotation: always generate fresh value, overwrite the host file.
159
+ if (rotate) {
160
+ const fresh = deps.genRandomHex();
161
+ deps.writeFile(filePath, fresh, 0o600);
162
+ return { ok: true, value: fresh };
163
+ }
164
+ if (deps.fileExists(filePath)) {
165
+ const value = deps.readFile(filePath, 'utf8').trim();
166
+ if (value.length > 0)
167
+ return { ok: true, value };
168
+ }
169
+ // Missing or empty — generate, persist, return.
170
+ const fresh = deps.genRandomHex();
171
+ deps.writeFile(filePath, fresh, 0o600);
172
+ return { ok: true, value: fresh };
173
+ }
174
+ // kind === 'gh-token' — not generatable; warn + skip if absent.
175
+ // Rotation reads gh CLI again (gh manages its own session rotation).
176
+ const r = deps.runGhTokenCmd();
177
+ if (r.ok)
178
+ return { ok: true, value: r.token };
179
+ return { ok: false, reason: 'gh CLI not authenticated; run `gh auth login` then re-run' };
180
+ }
181
+ /**
182
+ * Substitute the placeholder strings in the template YAML with real values.
183
+ * Builds a fresh Secret YAML string (does NOT touch the template on disk).
184
+ *
185
+ * The templates hold ONE placeholder per stringData key; literal string
186
+ * substitution suffices. We avoid YAML parsing to keep the template comment
187
+ * block (which carries operator-facing documentation) intact in the rendered
188
+ * output.
189
+ */
190
+ export function substitutePlaceholders(templateYaml, values) {
191
+ let out = templateYaml;
192
+ for (const [placeholder, value] of Object.entries(values)) {
193
+ // Use split/join rather than .replace to avoid regex escaping pitfalls.
194
+ out = out.split(`"${placeholder}"`).join(`"${value}"`);
195
+ }
196
+ return out;
197
+ }
198
+ /**
199
+ * Render ONE secret template. Reads the template YAML from `templatesRoot`
200
+ * (resolved by the caller to either source or install-mode location),
201
+ * resolves each placeholder, persists generated values to host files,
202
+ * substitutes, and returns the rendered YAML.
203
+ *
204
+ * `reuse` carries the prior state's per-key values when an unrotated
205
+ * re-apply is requested; null means "this is the first apply or operator
206
+ * asked for rotation."
207
+ */
208
+ export function renderOneSecret(binding, templatesRoot, reuse, deps = {}, rotate = false) {
209
+ const olamHome = deps.olamHome ?? OLAM_HOME;
210
+ const readFile = deps.readFile ?? defaultReadFile;
211
+ const writeFile = deps.writeFile ?? defaultWriteFile;
212
+ const fileExists = deps.fileExists ?? defaultFileExists;
213
+ const genRandomHex = deps.genRandomHex ?? defaultGenRandomHex;
214
+ const runGhTokenCmd = deps.runGhTokenCmd ?? defaultRunGhTokenCmd;
215
+ const templatePath = join(templatesRoot, binding.templateRelPath);
216
+ const templateYaml = readFile(templatePath, 'utf8');
217
+ const keys = {};
218
+ const placeholderToValue = {};
219
+ const missingSources = [];
220
+ for (const p of binding.placeholders) {
221
+ const resolved = resolveSourceValue(p.source, olamHome, reuse?.keys[p.key], rotate, { readFile, writeFile, fileExists, genRandomHex, runGhTokenCmd });
222
+ if (!resolved.ok) {
223
+ missingSources.push(`${p.key}: ${resolved.reason}`);
224
+ continue;
225
+ }
226
+ keys[p.key] = resolved.value;
227
+ placeholderToValue[p.placeholder] = resolved.value;
228
+ }
229
+ if (missingSources.length > 0) {
230
+ return {
231
+ secretName: binding.secretName,
232
+ status: 'skipped',
233
+ keys,
234
+ missingSources,
235
+ };
236
+ }
237
+ const renderedYaml = substitutePlaceholders(templateYaml, placeholderToValue);
238
+ return {
239
+ secretName: binding.secretName,
240
+ status: 'resolved',
241
+ renderedYaml,
242
+ keys,
243
+ missingSources: [],
244
+ };
245
+ }
246
+ /**
247
+ * Render the full set of shipped Secret templates. `rotate=true` discards
248
+ * the on-disk state and regenerates every value; `rotate=false` reuses
249
+ * state for unchanged secrets so worlds caching old tokens don't break.
250
+ */
251
+ export function renderAllSecrets(templatesRoot, opts, deps = {}) {
252
+ const readState = deps.readState ?? (() => readSecretsState(deps.statePath ?? K8S_SECRETS_STATE_PATH));
253
+ const writeState = deps.writeState ?? ((s) => writeSecretsState(s, deps.statePath ?? K8S_SECRETS_STATE_PATH));
254
+ const prior = opts.rotate ? null : readState();
255
+ const results = [];
256
+ const nextSecrets = {};
257
+ for (const binding of SECRET_TEMPLATE_BINDINGS) {
258
+ const reuse = prior?.secrets[binding.secretName] ?? null;
259
+ const r = renderOneSecret(binding, templatesRoot, reuse, deps, opts.rotate);
260
+ results.push(r);
261
+ nextSecrets[binding.secretName] = {
262
+ keys: r.keys,
263
+ ...(r.missingSources.length > 0 ? { skipped: r.missingSources } : {}),
264
+ };
265
+ }
266
+ const nextState = {
267
+ version: 1,
268
+ context: opts.context,
269
+ namespace: opts.namespace,
270
+ generatedAt: new Date().toISOString(),
271
+ secrets: nextSecrets,
272
+ };
273
+ // Only persist when at least one secret resolved — avoids an empty state file
274
+ // on a wholly-failed first run (where every gh-token source was absent and
275
+ // the operator hadn't yet generated `~/.olam/*` files).
276
+ if (results.some((r) => r.status === 'resolved')) {
277
+ writeState(nextState);
278
+ }
279
+ return { results, nextState };
280
+ }
281
+ //# sourceMappingURL=k8s-secret-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"k8s-secret-render.js","sourceRoot":"","sources":["../../src/lib/k8s-secret-render.ts"],"names":[],"mappings":"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"}