@celilo/cli 0.3.30 → 0.4.0-alpha.1

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 (155) hide show
  1. package/drizzle/0005_module_operations.sql +12 -0
  2. package/drizzle/0006_base_module_aspects.sql +15 -0
  3. package/drizzle/0007_module_systems.sql +17 -0
  4. package/drizzle/meta/_journal.json +21 -0
  5. package/package.json +6 -5
  6. package/schemas/system_config.json +14 -28
  7. package/src/ansible/inventory.test.ts +46 -62
  8. package/src/ansible/inventory.ts +48 -25
  9. package/src/capabilities/registration.ts +25 -7
  10. package/src/capabilities/validation.test.ts +30 -0
  11. package/src/capabilities/validation.ts +8 -0
  12. package/src/cli/backup-rename.test.ts +95 -0
  13. package/src/cli/cli.test.ts +17 -23
  14. package/src/cli/command-registry.ts +199 -0
  15. package/src/cli/commands/backup-list.ts +1 -1
  16. package/src/cli/commands/events.ts +96 -0
  17. package/src/cli/commands/machine-add.ts +103 -59
  18. package/src/cli/commands/module-import.ts +153 -4
  19. package/src/cli/commands/module-remove.ts +86 -17
  20. package/src/cli/commands/module-status.ts +6 -2
  21. package/src/cli/commands/publish/alpha.test.ts +185 -0
  22. package/src/cli/commands/publish/alpha.ts +226 -0
  23. package/src/cli/commands/publish/changesets.test.ts +89 -0
  24. package/src/cli/commands/publish/changesets.ts +144 -0
  25. package/src/cli/commands/publish/consumer-pins.test.ts +155 -0
  26. package/src/cli/commands/publish/consumer-pins.ts +149 -0
  27. package/src/cli/commands/publish/execute.ts +131 -0
  28. package/src/cli/commands/publish/global-install.test.ts +154 -0
  29. package/src/cli/commands/publish/global-install.ts +171 -0
  30. package/src/cli/commands/publish/helpers.ts +227 -0
  31. package/src/cli/commands/publish/index.ts +365 -0
  32. package/src/cli/commands/publish/module-registry.test.ts +40 -0
  33. package/src/cli/commands/publish/module-registry.ts +64 -0
  34. package/src/cli/commands/publish/plan.ts +107 -0
  35. package/src/cli/commands/publish/preflight.ts +238 -0
  36. package/src/cli/commands/publish/types.ts +264 -0
  37. package/src/cli/commands/publish/workspace.test.ts +323 -0
  38. package/src/cli/commands/publish/workspace.ts +596 -0
  39. package/src/cli/commands/restore.ts +126 -0
  40. package/src/cli/commands/storage-add-local.ts +1 -1
  41. package/src/cli/commands/storage-add-s3.ts +1 -1
  42. package/src/cli/commands/subscribers-add.ts +68 -0
  43. package/src/cli/commands/subscribers-list.ts +48 -0
  44. package/src/cli/commands/subscribers-remove.ts +38 -0
  45. package/src/cli/commands/subscribers-serve.ts +77 -0
  46. package/src/cli/commands/subscribers-status.ts +33 -0
  47. package/src/cli/commands/subscribers-test.ts +71 -0
  48. package/src/cli/commands/system-apply-config-equivalence.test.ts +108 -0
  49. package/src/cli/commands/system-apply-config.test.ts +70 -0
  50. package/src/cli/commands/system-apply-config.ts +130 -0
  51. package/src/cli/commands/system-audit.ts +2 -1
  52. package/src/cli/commands/system-init-deprecation.test.ts +90 -0
  53. package/src/cli/commands/system-init.ts +36 -70
  54. package/src/cli/commands/system-update.ts +3 -2
  55. package/src/cli/completion.ts +22 -1
  56. package/src/cli/index.ts +214 -6
  57. package/src/cli/interactive-config.test.ts +19 -0
  58. package/src/cli/restore-command.test.ts +131 -0
  59. package/src/db/client.ts +42 -0
  60. package/src/db/schema.test.ts +13 -16
  61. package/src/db/schema.ts +161 -9
  62. package/src/hooks/capability-loader-firewall.test.ts +6 -15
  63. package/src/hooks/capability-loader.test.ts +2 -3
  64. package/src/hooks/capability-loader.ts +36 -2
  65. package/src/hooks/define-hook.test.ts +4 -0
  66. package/src/hooks/executor.test.ts +18 -0
  67. package/src/hooks/executor.ts +21 -2
  68. package/src/hooks/load-hook-config.test.ts +26 -24
  69. package/src/hooks/load-hook-config.ts +11 -2
  70. package/src/hooks/run-named-hook.ts +16 -0
  71. package/src/hooks/types.ts +9 -1
  72. package/src/manifest/contracts/v1.ts +70 -0
  73. package/src/manifest/schema.ts +262 -16
  74. package/src/manifest/validate-privileged.test.ts +84 -0
  75. package/src/manifest/validate.test.ts +156 -0
  76. package/src/manifest/validate.ts +69 -0
  77. package/src/module/import.ts +12 -0
  78. package/src/services/aspect-approvals.test.ts +231 -0
  79. package/src/services/aspect-approvals.ts +120 -0
  80. package/src/services/aspect-runner.test.ts +493 -0
  81. package/src/services/aspect-runner.ts +438 -0
  82. package/src/services/aspect-template-resolver.test.ts +101 -0
  83. package/src/services/aspect-template-resolver.ts +122 -0
  84. package/src/services/backup-create.ts +104 -25
  85. package/src/services/backup-envelope-roundtrip.test.ts +199 -0
  86. package/src/services/backup-in-flight-refusal.test.ts +163 -0
  87. package/src/services/backup-manifest.test.ts +115 -0
  88. package/src/services/backup-manifest.ts +163 -0
  89. package/src/services/backup-restore.ts +154 -19
  90. package/src/services/build-bus/delivery-events.ts +92 -0
  91. package/src/services/build-bus/event-factory.ts +54 -0
  92. package/src/services/build-bus/fan-out.test.ts +279 -0
  93. package/src/services/build-bus/fan-out.ts +161 -0
  94. package/src/services/build-bus/hook-dispatch-mgmt.test.ts +157 -0
  95. package/src/services/build-bus/hook-dispatch.test.ts +207 -0
  96. package/src/services/build-bus/hook-dispatch.ts +198 -0
  97. package/src/services/build-bus/hook-dispatcher.ts +115 -0
  98. package/src/services/build-bus/index.ts +41 -0
  99. package/src/services/build-bus/receiver-server.test.ts +179 -0
  100. package/src/services/build-bus/receiver-server.ts +159 -0
  101. package/src/services/build-bus/status.test.ts +212 -0
  102. package/src/services/build-bus/status.ts +213 -0
  103. package/src/services/build-bus/subscriber-store.ts +113 -0
  104. package/src/services/celilo-events.test.ts +70 -0
  105. package/src/services/celilo-events.ts +92 -0
  106. package/src/services/celilo-mgmt-hooks.test.ts +296 -0
  107. package/src/services/config-interview.ts +13 -95
  108. package/src/services/cross-module-data-manager.ts +2 -31
  109. package/src/services/cross-module-read.test.ts +250 -0
  110. package/src/services/cross-module-read.ts +232 -0
  111. package/src/services/deploy-validation.ts +7 -0
  112. package/src/services/deployed-systems.test.ts +235 -0
  113. package/src/services/deployed-systems.ts +308 -0
  114. package/src/services/dns-provider-backfill.ts +75 -0
  115. package/src/services/health-runner.ts +19 -3
  116. package/src/services/infrastructure-variable-resolver.test.ts +6 -32
  117. package/src/services/infrastructure-variable-resolver.ts +3 -13
  118. package/src/services/machine-detector.ts +104 -48
  119. package/src/services/machine-pool.ts +145 -2
  120. package/src/services/module-config.ts +78 -120
  121. package/src/services/module-deploy.ts +113 -40
  122. package/src/services/module-operations.test.ts +154 -0
  123. package/src/services/module-operations.ts +154 -0
  124. package/src/services/module-subscriptions.test.ts +58 -0
  125. package/src/services/module-subscriptions.ts +24 -1
  126. package/src/services/module-types-generator.test.ts +3 -3
  127. package/src/services/module-types-generator.ts +7 -2
  128. package/src/services/proxmox-reconcile.test.ts +333 -0
  129. package/src/services/proxmox-reconcile.ts +156 -0
  130. package/src/services/proxmox-state-recovery.ts +3 -24
  131. package/src/services/restore-from-file.test.ts +177 -0
  132. package/src/services/restore-from-file.ts +355 -0
  133. package/src/services/restore-preflight.test.ts +127 -0
  134. package/src/services/restore-preflight.ts +118 -0
  135. package/src/services/storage-providers/s3.ts +10 -2
  136. package/src/services/system-identity.ts +30 -0
  137. package/src/services/system-init.test.ts +64 -21
  138. package/src/services/system-init.ts +28 -26
  139. package/src/templates/generator.test.ts +7 -16
  140. package/src/templates/generator.ts +28 -115
  141. package/src/test-utils/integration.ts +5 -2
  142. package/src/types/infrastructure.ts +8 -0
  143. package/src/variables/computed/computed-integration.test.ts +191 -0
  144. package/src/variables/computed/computed.test.ts +177 -0
  145. package/src/variables/computed/evaluate.ts +271 -0
  146. package/src/variables/computed/marker.ts +53 -0
  147. package/src/variables/computed/parse.ts +262 -0
  148. package/src/variables/computed/provider-lookup.ts +130 -0
  149. package/src/variables/context.test.ts +89 -28
  150. package/src/variables/context.ts +196 -191
  151. package/src/variables/parser.ts +3 -3
  152. package/src/variables/resolver.test.ts +61 -0
  153. package/src/variables/resolver.ts +81 -0
  154. package/src/variables/types.ts +23 -1
  155. package/src/services/dns-auto-register.ts +0 -211
@@ -0,0 +1,130 @@
1
+ /**
2
+ * `celilo system apply-config` — headless write to systemConfig.
3
+ *
4
+ * Phase 2 of v2/MANAGEMENT_AS_NETAPP.md: the celilo-mgmt module's
5
+ * on_install hook needs a CLI surface it can shell out to that writes
6
+ * the operator-chosen network/DNS/SSH config without the interactive
7
+ * framing of `celilo system init`. This is that command.
8
+ *
9
+ * Shape:
10
+ *
11
+ * celilo system apply-config <key=value> ...
12
+ * celilo system apply-config --from-stdin # JSON map on stdin
13
+ *
14
+ * No prompts. No "next steps" guidance. Just writes the keys and
15
+ * reports a count. Suitable for module hooks, CI workflows, and any
16
+ * other automation that needs to seed systemConfig at deploy time.
17
+ *
18
+ * `celilo system init --accept-defaults` continues to work and now
19
+ * delegates to this same `initializeSystem()` plumbing — that's the
20
+ * operator-facing surface; this is the automation-facing surface.
21
+ */
22
+
23
+ import { getDb } from '../../db/client';
24
+ import { initializeSystem } from '../../services/system-init';
25
+ import type { CommandResult } from '../types';
26
+
27
+ interface ParsedInput {
28
+ overrides: Record<string, string>;
29
+ errors: string[];
30
+ }
31
+
32
+ /**
33
+ * Pure parser for the positional key=value arguments. Exported so the
34
+ * test suite can exercise it without spinning up the CLI.
35
+ *
36
+ * - Keys must contain a literal `=`; bare positionals are errors.
37
+ * - Empty values are allowed (`network.dmz.subnet=`) — caller decides
38
+ * what to do with them (the writer skips them).
39
+ * - Values containing `=` are preserved (split on FIRST `=` only).
40
+ */
41
+ export function parseKeyValueArgs(args: string[]): ParsedInput {
42
+ const overrides: Record<string, string> = {};
43
+ const errors: string[] = [];
44
+ for (const arg of args) {
45
+ const eqIndex = arg.indexOf('=');
46
+ if (eqIndex <= 0) {
47
+ errors.push(`Expected key=value (got "${arg}"). Use --from-stdin for JSON input.`);
48
+ continue;
49
+ }
50
+ const key = arg.slice(0, eqIndex);
51
+ const value = arg.slice(eqIndex + 1);
52
+ overrides[key] = value;
53
+ }
54
+ return { overrides, errors };
55
+ }
56
+
57
+ /**
58
+ * Read JSON config from stdin. Returns the parsed key→value map; the
59
+ * caller validates structure. Values are coerced to strings to match
60
+ * systemConfig's schema (which stores everything as TEXT).
61
+ */
62
+ async function readJsonStdin(): Promise<Record<string, string>> {
63
+ const chunks: Buffer[] = [];
64
+ for await (const chunk of process.stdin) {
65
+ chunks.push(chunk as Buffer);
66
+ }
67
+ const body = Buffer.concat(chunks).toString('utf-8').trim();
68
+ if (!body) return {};
69
+ const parsed = JSON.parse(body) as unknown;
70
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
71
+ throw new Error('--from-stdin expects a JSON object mapping config keys to values');
72
+ }
73
+ const out: Record<string, string> = {};
74
+ for (const [key, value] of Object.entries(parsed)) {
75
+ if (value === null || value === undefined) continue;
76
+ out[key] = String(value);
77
+ }
78
+ return out;
79
+ }
80
+
81
+ export async function handleSystemApplyConfig(
82
+ args: string[],
83
+ flags: Record<string, boolean | string> = {},
84
+ ): Promise<CommandResult> {
85
+ const fromStdin = flags['from-stdin'] === true;
86
+
87
+ let overrides: Record<string, string> = {};
88
+ if (fromStdin) {
89
+ try {
90
+ overrides = await readJsonStdin();
91
+ } catch (err) {
92
+ return {
93
+ success: false,
94
+ error: `Could not read --from-stdin: ${err instanceof Error ? err.message : String(err)}`,
95
+ };
96
+ }
97
+ } else {
98
+ const parsed = parseKeyValueArgs(args);
99
+ if (parsed.errors.length > 0) {
100
+ return {
101
+ success: false,
102
+ error: parsed.errors.join('\n'),
103
+ };
104
+ }
105
+ overrides = parsed.overrides;
106
+ }
107
+
108
+ if (Object.keys(overrides).length === 0) {
109
+ return {
110
+ success: false,
111
+ error:
112
+ 'No config values supplied.\n\nUsage:\n celilo system apply-config <key=value> ...\n celilo system apply-config --from-stdin',
113
+ };
114
+ }
115
+
116
+ const db = getDb();
117
+ try {
118
+ const applied = initializeSystem(db, overrides);
119
+ const writtenCount = Object.keys(applied).length;
120
+ return {
121
+ success: true,
122
+ message: `Applied ${writtenCount} config value(s) to systemConfig.`,
123
+ };
124
+ } catch (err) {
125
+ return {
126
+ success: false,
127
+ error: `Config write failed: ${err instanceof Error ? err.message : String(err)}`,
128
+ };
129
+ }
130
+ }
@@ -52,6 +52,7 @@ import type { TerraformPlanRunner } from '../../services/audit/terraform-plan';
52
52
  import { getServiceCredentials, listContainerServices } from '../../services/container-service';
53
53
  import { runAllHealthChecks } from '../../services/health-runner';
54
54
  import { listMachines } from '../../services/machine-pool';
55
+ import { parseStoredConfigValue } from '../../services/module-config';
55
56
  import { buildTerraformEnvForModule } from '../../services/terraform-env';
56
57
  import { hasFlag } from '../parser';
57
58
  import type { CommandResult } from '../types';
@@ -183,7 +184,7 @@ async function buildAuditDeps(onProgress?: (msg: string) => void) {
183
184
  const configsByModule = new Map<string, Record<string, unknown>>();
184
185
  for (const c of allConfigs) {
185
186
  const map = configsByModule.get(c.moduleId) ?? {};
186
- map[c.key] = c.valueJson ? JSON.parse(c.valueJson) : c.value;
187
+ map[c.key] = parseStoredConfigValue(c);
187
188
  configsByModule.set(c.moduleId, map);
188
189
  }
189
190
 
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Phase 5 of v2/MANAGEMENT_AS_NETAPP.md — confirm `celilo system init`
3
+ * surfaces a deprecation banner pointing at the new paths
4
+ * (bootstrap.sh + `system apply-config`), and that
5
+ * CELILO_SUPPRESS_DEPRECATION=1 silences it.
6
+ *
7
+ * The banner is operator-facing UX; we verify the contract rather than
8
+ * pin specific wording.
9
+ */
10
+
11
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
12
+ import { mkdtempSync, rmSync } from 'node:fs';
13
+ import { tmpdir } from 'node:os';
14
+ import { join } from 'node:path';
15
+ import { handleSystemInit } from './system-init';
16
+
17
+ interface CapturedStream {
18
+ out: string[];
19
+ restore: () => void;
20
+ }
21
+
22
+ function captureStderr(): CapturedStream {
23
+ const original = console.warn;
24
+ const captured: string[] = [];
25
+ console.warn = (...args: unknown[]) => {
26
+ captured.push(args.map(String).join(' '));
27
+ };
28
+ return {
29
+ out: captured,
30
+ restore: () => {
31
+ console.warn = original;
32
+ },
33
+ };
34
+ }
35
+
36
+ describe('celilo system init deprecation banner', () => {
37
+ let tmpDir: string;
38
+ let savedDbPath: string | undefined;
39
+ let savedSuppress: string | undefined;
40
+
41
+ beforeEach(() => {
42
+ tmpDir = mkdtempSync(join(tmpdir(), 'celilo-deprecation-test-'));
43
+ savedDbPath = process.env.CELILO_DB_PATH;
44
+ savedSuppress = process.env.CELILO_SUPPRESS_DEPRECATION;
45
+ process.env.CELILO_DB_PATH = join(tmpDir, 'init.db');
46
+ process.env.CELILO_SUPPRESS_DEPRECATION = undefined;
47
+ });
48
+
49
+ afterEach(() => {
50
+ process.env.CELILO_DB_PATH = savedDbPath;
51
+ process.env.CELILO_SUPPRESS_DEPRECATION = savedSuppress;
52
+ rmSync(tmpDir, { recursive: true, force: true });
53
+ });
54
+
55
+ test('prints a deprecation banner on stderr by default', async () => {
56
+ const captured = captureStderr();
57
+ try {
58
+ await handleSystemInit([], { 'accept-defaults': true });
59
+ } finally {
60
+ captured.restore();
61
+ }
62
+ const all = captured.out.join('\n');
63
+ expect(all).toContain('deprecated');
64
+ expect(all).toContain('bootstrap.sh');
65
+ expect(all).toContain('apply-config');
66
+ });
67
+
68
+ test('CELILO_SUPPRESS_DEPRECATION=1 silences the banner', async () => {
69
+ process.env.CELILO_SUPPRESS_DEPRECATION = '1';
70
+ const captured = captureStderr();
71
+ try {
72
+ await handleSystemInit([], { 'accept-defaults': true });
73
+ } finally {
74
+ captured.restore();
75
+ }
76
+ const all = captured.out.join('\n');
77
+ expect(all).not.toContain('deprecated');
78
+ });
79
+
80
+ test('the command still executes successfully when the banner fires', async () => {
81
+ const captured = captureStderr();
82
+ let result: Awaited<ReturnType<typeof handleSystemInit>>;
83
+ try {
84
+ result = await handleSystemInit([], { 'accept-defaults': true });
85
+ } finally {
86
+ captured.restore();
87
+ }
88
+ expect(result.success).toBe(true);
89
+ });
90
+ });
@@ -16,12 +16,7 @@ import {
16
16
  } from '../../services/system-init';
17
17
  import { celiloIntro, celiloOutro, promptConfirm, promptText } from '../prompts';
18
18
  import type { CommandResult } from '../types';
19
- import {
20
- validateIpAddress,
21
- validateIpAddressList,
22
- validateRequired,
23
- validateSubnet,
24
- } from '../validators';
19
+ import { validateRequired } from '../validators';
25
20
 
26
21
  /**
27
22
  * Parse key=value pairs from positional arguments
@@ -51,6 +46,28 @@ export async function handleSystemInit(
51
46
  const cliOverrides = parseOverrides(args);
52
47
  const db = getDb();
53
48
 
49
+ // Phase 5 of v2/MANAGEMENT_AS_NETAPP.md — surface that this command
50
+ // is on its way out so operators have time to migrate to the new
51
+ // paths. Keeps working unchanged; the banner just points at the
52
+ // replacements. CELILO_SUPPRESS_DEPRECATION=1 silences for callers
53
+ // that already know (e.g. the celilo-mgmt module's on_install hook
54
+ // — wait, that one shells to apply-config now, but other tooling
55
+ // that legitimately wants the interactive interview can still opt
56
+ // out of the banner).
57
+ if (process.env.CELILO_SUPPRESS_DEPRECATION !== '1') {
58
+ console.warn('⚠ celilo system init is deprecated.');
59
+ console.warn(' Recommended paths:');
60
+ console.warn(
61
+ ' • Fresh management server: curl -fsSL https://celilo.computer/bootstrap.sh | bash',
62
+ );
63
+ console.warn(
64
+ ' • Headless config write (CI / hooks): celilo system apply-config key=value ...',
65
+ );
66
+ console.warn(
67
+ ' This command still works; suppress this banner with CELILO_SUPPRESS_DEPRECATION=1.\n',
68
+ );
69
+ }
70
+
54
71
  // Check if already initialized
55
72
  const alreadyInitialized = isSystemInitialized(db);
56
73
  if (alreadyInitialized && !acceptDefaults && Object.keys(cliOverrides).length === 0) {
@@ -61,7 +78,7 @@ export async function handleSystemInit(
61
78
 
62
79
  try {
63
80
  if (acceptDefaults) {
64
- return await initWithDefaults(cliOverrides);
81
+ return initWithDefaults(cliOverrides);
65
82
  }
66
83
  return await initInteractive(cliOverrides);
67
84
  } catch (error) {
@@ -91,8 +108,10 @@ function initWithDefaults(overrides: Record<string, string> = {}): CommandResult
91
108
 
92
109
  const sshKeyDetected = config['ssh.public_key'] !== undefined;
93
110
 
94
- console.log('✓ Network zones (dmz, app, secure, internal)');
95
- console.log(' - Gateway IPs auto-computed from subnets');
111
+ console.log('✓ Celilo state initialized');
112
+ console.log(' Network addressing is NOT defaulted: the `internal` zone is');
113
+ console.log(' discovered when you deploy celilo-mgmt, and dmz/app/secure');
114
+ console.log(' appear when you deploy a firewall module.');
96
115
 
97
116
  if (sshKeyDetected) {
98
117
  console.log('✓ SSH key (auto-detected)');
@@ -146,65 +165,12 @@ async function initInteractive(cliOverrides: Record<string, string> = {}): Promi
146
165
 
147
166
  await celiloIntro('🎛️ Welcome to Celilo System Setup');
148
167
 
149
- // Network Configuration
150
- if (!has('network.dmz.subnet')) {
151
- overrides['network.dmz.subnet'] = await promptText({
152
- message: 'DMZ network (public services)',
153
- defaultValue: String(defaults['network.dmz.subnet']),
154
- placeholder: String(defaults['network.dmz.subnet']) || '10.0.10.0/24',
155
- validate: validateSubnet,
156
- });
157
- }
158
-
159
- if (!has('network.app.subnet')) {
160
- overrides['network.app.subnet'] = await promptText({
161
- message: 'App network (internal apps)',
162
- defaultValue: String(defaults['network.app.subnet']),
163
- placeholder: String(defaults['network.app.subnet']) || '10.0.20.0/24',
164
- validate: validateSubnet,
165
- });
166
- }
167
-
168
- if (!has('network.secure.subnet')) {
169
- overrides['network.secure.subnet'] = await promptText({
170
- message: 'Secure network (databases, auth)',
171
- defaultValue: String(defaults['network.secure.subnet']),
172
- placeholder: String(defaults['network.secure.subnet']) || '10.0.30.0/24',
173
- validate: validateSubnet,
174
- });
175
- }
176
-
177
- if (!has('network.internal.subnet')) {
178
- overrides['network.internal.subnet'] = await promptText({
179
- message: 'Internal network (home devices)',
180
- defaultValue: String(defaults['network.internal.subnet']),
181
- placeholder: String(defaults['network.internal.subnet']) || '192.168.1.0/24',
182
- validate: validateSubnet,
183
- });
184
- }
185
-
186
- // System Settings
187
- // primary_domain and admin.email are no longer system config — modules
188
- // (dns_registrar, authentik, caddy) own those values directly. See
189
- // design/TECHNICAL_DESIGN_MANIFEST_V2.md D9.
190
-
191
- if (!has('dns.primary')) {
192
- overrides['dns.primary'] = await promptText({
193
- message: 'Primary DNS server',
194
- defaultValue: String(defaults['dns.primary']),
195
- placeholder: String(defaults['dns.primary']) || '1.1.1.1',
196
- validate: validateIpAddress,
197
- });
198
- }
199
-
200
- if (!has('dns.fallback')) {
201
- overrides['dns.fallback'] = await promptText({
202
- message: 'Fallback DNS servers (space-separated)',
203
- defaultValue: String(defaults['dns.fallback']),
204
- placeholder: String(defaults['dns.fallback']) || '8.8.8.8 1.1.1.1',
205
- validate: validateIpAddressList,
206
- });
207
- }
168
+ // Network and DNS addressing are intentionally NOT prompted here.
169
+ // Network topology is no longer owned by `system init`
170
+ // (v2/NETWORK_CONFIG_TO_FIREWALL.md): the `internal` zone and DNS are
171
+ // discovered when celilo-mgmt is deployed, and `dmz`/`app`/`secure`
172
+ // come from a firewall module. The only thing left to capture
173
+ // interactively is the SSH key celilo uses to reach managed machines.
208
174
 
209
175
  // SSH Key — skip if provided via CLI
210
176
  if (!has('ssh.public_key')) {
@@ -311,8 +277,8 @@ async function initInteractive(cliOverrides: Record<string, string> = {}): Promi
311
277
  }
312
278
  }
313
279
 
314
- // Apply configuration
315
- const _config = initializeSystem(db, overrides);
280
+ // Apply configuration (called for its DB side effects).
281
+ initializeSystem(db, overrides);
316
282
 
317
283
  await celiloOutro('✅ System initialization complete!');
318
284
 
@@ -30,6 +30,7 @@ import { fetchLatestCliVersion } from '../../services/audit/cli-version';
30
30
  import { makeJournalReader, readAppliedMigrations } from '../../services/audit/schema';
31
31
  import { createModuleBackup, createSystemStateBackup } from '../../services/backup-create';
32
32
  import { runAllHealthChecks, runModuleHealthCheck } from '../../services/health-runner';
33
+ import { parseStoredConfigValue } from '../../services/module-config';
33
34
  import { deployModule } from '../../services/module-deploy';
34
35
  import { buildModuleGraph } from '../../services/update/dep-graph';
35
36
  import {
@@ -511,7 +512,7 @@ export async function handleSystemUpdate(
511
512
  const configsByModule = new Map<string, Record<string, unknown>>();
512
513
  for (const c of allConfigs) {
513
514
  const m = configsByModule.get(c.moduleId) ?? {};
514
- m[c.key] = c.valueJson ? JSON.parse(c.valueJson) : c.value;
515
+ m[c.key] = parseStoredConfigValue(c);
515
516
  configsByModule.set(c.moduleId, m);
516
517
  }
517
518
 
@@ -741,7 +742,7 @@ export function rebuildAuditDepsForRerun(
741
742
  const configsByModule = new Map<string, Record<string, unknown>>();
742
743
  for (const c of allConfigs) {
743
744
  const m = configsByModule.get(c.moduleId) ?? {};
744
- m[c.key] = c.valueJson ? JSON.parse(c.valueJson) : c.value;
745
+ m[c.key] = parseStoredConfigValue(c);
745
746
  configsByModule.set(c.moduleId, m);
746
747
  }
747
748
 
@@ -39,9 +39,12 @@ export async function getCompletions(words: string[], current: number): Promise<
39
39
  'machine',
40
40
  'module',
41
41
  'package',
42
+ 'publish',
43
+ 'restore',
42
44
  'service',
43
45
  'status',
44
46
  'storage',
47
+ 'subscribers',
45
48
  'system',
46
49
  'version',
47
50
  ];
@@ -76,6 +79,7 @@ export async function getCompletions(words: string[], current: number): Promise<
76
79
  'list-pending',
77
80
  'drain',
78
81
  'run',
82
+ 'run-hook',
79
83
  'emit',
80
84
  'ack',
81
85
  'fail',
@@ -130,6 +134,7 @@ export async function getCompletions(words: string[], current: number): Promise<
130
134
  'update',
131
135
  'verify',
132
136
  'audit',
137
+ 'backup',
133
138
  'config',
134
139
  'show-config',
135
140
  'show-zone',
@@ -333,6 +338,7 @@ export async function getCompletions(words: string[], current: number): Promise<
333
338
  'logs',
334
339
  'remove',
335
340
  'build',
341
+ 'backup',
336
342
  'run-hook',
337
343
  'status',
338
344
  'terraform-unlock',
@@ -362,6 +368,12 @@ export async function getCompletions(words: string[], current: number): Promise<
362
368
  }
363
369
  }
364
370
 
371
+ // Subscribers subcommands
372
+ if (command === 'subscribers' && currentIndex === 1) {
373
+ const subcommands = ['list', 'add', 'remove', 'test', 'serve', 'status'];
374
+ return filterSuggestions(subcommands, args[1] || '');
375
+ }
376
+
365
377
  // Storage subcommands
366
378
  if (command === 'storage' && currentIndex === 1) {
367
379
  const subcommands = ['add', 'list', 'remove', 'verify', 'set-default'];
@@ -431,7 +443,16 @@ export async function getCompletions(words: string[], current: number): Promise<
431
443
 
432
444
  // System subcommands
433
445
  if (command === 'system' && currentIndex === 1) {
434
- const subcommands = ['init', 'config', 'secret', 'vault-password', 'audit', 'update', 'doctor'];
446
+ const subcommands = [
447
+ 'init',
448
+ 'apply-config',
449
+ 'config',
450
+ 'secret',
451
+ 'vault-password',
452
+ 'audit',
453
+ 'update',
454
+ 'doctor',
455
+ ];
435
456
  return filterSuggestions(subcommands, args[1] || '');
436
457
  }
437
458