@omnitron-dev/omnitron 0.3.2 → 0.3.5

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 (191) hide show
  1. package/bin/pnpmfile-sdk.mjs +40 -0
  2. package/bin/sdk.d.mts +8 -0
  3. package/bin/sdk.mjs +580 -0
  4. package/dist/BUILD.json +2 -2
  5. package/dist/cli/omnitron.js +80 -14
  6. package/dist/cli/omnitron.js.map +1 -1
  7. package/dist/commands/infra.d.ts +8 -2
  8. package/dist/commands/infra.d.ts.map +1 -1
  9. package/dist/commands/infra.js +52 -59
  10. package/dist/commands/infra.js.map +1 -1
  11. package/dist/commands/node.d.ts.map +1 -1
  12. package/dist/commands/node.js +8 -1
  13. package/dist/commands/node.js.map +1 -1
  14. package/dist/commands/release-store.d.ts.map +1 -1
  15. package/dist/commands/release-store.js +6 -9
  16. package/dist/commands/release-store.js.map +1 -1
  17. package/dist/commands/release.d.ts +4 -1
  18. package/dist/commands/release.d.ts.map +1 -1
  19. package/dist/commands/release.js +125 -5
  20. package/dist/commands/release.js.map +1 -1
  21. package/dist/commands/stack.d.ts +1 -1
  22. package/dist/commands/stack.d.ts.map +1 -1
  23. package/dist/commands/stack.js +19 -9
  24. package/dist/commands/stack.js.map +1 -1
  25. package/dist/config/project-stacks.d.ts +5 -0
  26. package/dist/config/project-stacks.d.ts.map +1 -0
  27. package/dist/config/project-stacks.js +24 -0
  28. package/dist/config/project-stacks.js.map +1 -0
  29. package/dist/config/saved.d.ts +3 -0
  30. package/dist/config/saved.d.ts.map +1 -1
  31. package/dist/config/saved.js +1 -0
  32. package/dist/config/saved.js.map +1 -1
  33. package/dist/config/types.d.ts +4 -0
  34. package/dist/config/types.d.ts.map +1 -1
  35. package/dist/daemon/daemon.d.ts.map +1 -1
  36. package/dist/daemon/daemon.js +30 -6
  37. package/dist/daemon/daemon.js.map +1 -1
  38. package/dist/daemon/daemon.rpc-service.d.ts +0 -26
  39. package/dist/daemon/daemon.rpc-service.d.ts.map +1 -1
  40. package/dist/daemon/daemon.rpc-service.js +23 -114
  41. package/dist/daemon/daemon.rpc-service.js.map +1 -1
  42. package/dist/daemon/execution-client.d.ts +1 -1
  43. package/dist/daemon/execution-client.d.ts.map +1 -1
  44. package/dist/daemon/execution-client.js +17 -13
  45. package/dist/daemon/execution-client.js.map +1 -1
  46. package/dist/daemon/startup-config.d.ts +1 -0
  47. package/dist/daemon/startup-config.d.ts.map +1 -1
  48. package/dist/infrastructure/container-runtime.d.ts +8 -0
  49. package/dist/infrastructure/container-runtime.d.ts.map +1 -1
  50. package/dist/infrastructure/container-runtime.js +5 -0
  51. package/dist/infrastructure/container-runtime.js.map +1 -1
  52. package/dist/infrastructure/infrastructure.service.d.ts +5 -0
  53. package/dist/infrastructure/infrastructure.service.d.ts.map +1 -1
  54. package/dist/infrastructure/infrastructure.service.js +18 -8
  55. package/dist/infrastructure/infrastructure.service.js.map +1 -1
  56. package/dist/infrastructure/presets/postgres.js +1 -1
  57. package/dist/infrastructure/presets/postgres.js.map +1 -1
  58. package/dist/infrastructure/presets/registry.d.ts.map +1 -1
  59. package/dist/infrastructure/presets/registry.js +11 -19
  60. package/dist/infrastructure/presets/registry.js.map +1 -1
  61. package/dist/infrastructure/stack-infra-manager.d.ts +4 -0
  62. package/dist/infrastructure/stack-infra-manager.d.ts.map +1 -1
  63. package/dist/infrastructure/stack-infra-manager.js +38 -4
  64. package/dist/infrastructure/stack-infra-manager.js.map +1 -1
  65. package/dist/mcp/tool-groups/infra.tools.d.ts.map +1 -1
  66. package/dist/mcp/tool-groups/infra.tools.js +4 -1
  67. package/dist/mcp/tool-groups/infra.tools.js.map +1 -1
  68. package/dist/orchestrator/app-name.d.ts +33 -0
  69. package/dist/orchestrator/app-name.d.ts.map +1 -0
  70. package/dist/orchestrator/app-name.js +68 -0
  71. package/dist/orchestrator/app-name.js.map +1 -0
  72. package/dist/orchestrator/orchestrator.service.d.ts +1 -26
  73. package/dist/orchestrator/orchestrator.service.d.ts.map +1 -1
  74. package/dist/orchestrator/orchestrator.service.js +10 -89
  75. package/dist/orchestrator/orchestrator.service.js.map +1 -1
  76. package/dist/project/stack-diagnosis.d.ts.map +1 -1
  77. package/dist/project/stack-diagnosis.js +2 -3
  78. package/dist/project/stack-diagnosis.js.map +1 -1
  79. package/dist/release/artifact-store.d.ts +42 -7
  80. package/dist/release/artifact-store.d.ts.map +1 -1
  81. package/dist/release/artifact-store.js +176 -62
  82. package/dist/release/artifact-store.js.map +1 -1
  83. package/dist/release/build-run.d.ts.map +1 -1
  84. package/dist/release/build-run.js +8 -11
  85. package/dist/release/build-run.js.map +1 -1
  86. package/dist/release/configured-store.d.ts +6 -0
  87. package/dist/release/configured-store.d.ts.map +1 -0
  88. package/dist/release/configured-store.js +54 -0
  89. package/dist/release/configured-store.js.map +1 -0
  90. package/dist/release/framework-source.d.ts +7 -0
  91. package/dist/release/framework-source.d.ts.map +1 -1
  92. package/dist/release/framework-source.js +9 -0
  93. package/dist/release/framework-source.js.map +1 -1
  94. package/dist/release/load.d.ts +3 -0
  95. package/dist/release/load.d.ts.map +1 -1
  96. package/dist/release/load.js +8 -2
  97. package/dist/release/load.js.map +1 -1
  98. package/dist/services/infrastructure.rpc-service.d.ts +6 -2
  99. package/dist/services/infrastructure.rpc-service.d.ts.map +1 -1
  100. package/dist/services/infrastructure.rpc-service.js +43 -12
  101. package/dist/services/infrastructure.rpc-service.js.map +1 -1
  102. package/dist/services/node-deploy-lease.d.ts +4 -1
  103. package/dist/services/node-deploy-lease.d.ts.map +1 -1
  104. package/dist/services/node-deploy-lease.js +29 -4
  105. package/dist/services/node-deploy-lease.js.map +1 -1
  106. package/dist/services/node-manager.service.d.ts +5 -25
  107. package/dist/services/node-manager.service.d.ts.map +1 -1
  108. package/dist/services/node-manager.service.js +150 -139
  109. package/dist/services/node-manager.service.js.map +1 -1
  110. package/dist/services/project.service.d.ts +6 -0
  111. package/dist/services/project.service.d.ts.map +1 -1
  112. package/dist/services/project.service.js +97 -30
  113. package/dist/services/project.service.js.map +1 -1
  114. package/dist/services/release.rpc-service.d.ts +2 -1
  115. package/dist/services/release.rpc-service.d.ts.map +1 -1
  116. package/dist/services/release.rpc-service.js +19 -3
  117. package/dist/services/release.rpc-service.js.map +1 -1
  118. package/dist/services/release.service.d.ts +24 -11
  119. package/dist/services/release.service.d.ts.map +1 -1
  120. package/dist/services/release.service.js +162 -39
  121. package/dist/services/release.service.js.map +1 -1
  122. package/dist/services/remote-deployer.service.d.ts +1 -1
  123. package/dist/services/remote-deployer.service.d.ts.map +1 -1
  124. package/dist/services/remote-deployer.service.js.map +1 -1
  125. package/dist/services/secrets.service.d.ts +6 -0
  126. package/dist/services/secrets.service.d.ts.map +1 -1
  127. package/dist/services/secrets.service.js +95 -51
  128. package/dist/services/secrets.service.js.map +1 -1
  129. package/dist/shared/dto/nodes.d.ts +2 -0
  130. package/dist/shared/dto/nodes.d.ts.map +1 -1
  131. package/dist/shared/dto/project.d.ts +1 -1
  132. package/dist/shared/dto/project.d.ts.map +1 -1
  133. package/dist/shared/dto/releases.d.ts +8 -1
  134. package/dist/shared/dto/releases.d.ts.map +1 -1
  135. package/dist/shared/dto/services.d.ts +18 -1
  136. package/dist/shared/dto/services.d.ts.map +1 -1
  137. package/dist/shared/dto/services.js.map +1 -1
  138. package/dist/shared/release-reading.d.ts +6 -0
  139. package/dist/shared/release-reading.d.ts.map +1 -1
  140. package/dist/shared/release-reading.js +42 -0
  141. package/dist/shared/release-reading.js.map +1 -1
  142. package/package.json +19 -16
  143. package/webapp/dist/BUILD.json +2 -2
  144. package/webapp/dist/assets/{access-48hkC2-P.js → access-BvKK9dEv.js} +1 -1
  145. package/webapp/dist/assets/{access-jWkHxGc7.js → access-DUjabwNX.js} +1 -1
  146. package/webapp/dist/assets/{alerts-CuPQ9YQJ.js → alerts-CzN6OqX5.js} +1 -1
  147. package/webapp/dist/assets/{apps-Drio028B.js → apps-G_Lm6m4F.js} +1 -1
  148. package/webapp/dist/assets/{audit-npSFJECe.js → audit-CLMpdBzG.js} +1 -1
  149. package/webapp/dist/assets/{containers-Bl8gAyZX.js → containers-BsFwprq4.js} +1 -1
  150. package/webapp/dist/assets/{dashboard-UHlet3H9.js → dashboard-BN7rKE5n.js} +1 -1
  151. package/webapp/dist/assets/{dashboard-builder-67PZFqhE.js → dashboard-builder-BXFxWjXP.js} +1 -1
  152. package/webapp/dist/assets/deploy-progress-CUUwlYtQ.js +1 -0
  153. package/webapp/dist/assets/deploy-release-dialog-58lGZDNk.js +1 -0
  154. package/webapp/dist/assets/deployments-DEzjHerg.js +1 -0
  155. package/webapp/dist/assets/detail-CBn9pjJU.js +2 -0
  156. package/webapp/dist/assets/{detail-CYS81K6Y.js → detail-Dj3gxaH0.js} +1 -1
  157. package/webapp/dist/assets/{detail-B4TiBfxU.js → detail-pXPk4O0E.js} +1 -1
  158. package/webapp/dist/assets/index-Bo9An3pJ.js +1 -0
  159. package/webapp/dist/assets/{logs-BqHOufk8.js → logs-DrsBbLfb.js} +1 -1
  160. package/webapp/dist/assets/{metrics-QIQaF7iS.js → metrics-D5cDZTEo.js} +1 -1
  161. package/webapp/dist/assets/{mui-y44PQOfG.js → mui-59N0OVRa.js} +1 -1
  162. package/webapp/dist/assets/{mui-C_hromOc.js → mui-D5m7fDCh.js} +1 -1
  163. package/webapp/dist/assets/{mui-BIz4LSVs.js → mui-Db5J-QUd.js} +1 -1
  164. package/webapp/dist/assets/{mui-B1jrje6Z.js → mui-dLCwWoMw.js} +1 -1
  165. package/webapp/dist/assets/{nodes-BJPk02vv.js → nodes-CwwZZuu4.js} +1 -1
  166. package/webapp/dist/assets/{pipelines-T6Ch9LTw.js → pipelines-BrfTjf6Y.js} +1 -1
  167. package/webapp/dist/assets/project.store-DuCgv6Eq.js +1 -0
  168. package/webapp/dist/assets/{projects-YoBzcQ24.js → projects-TBAdpCeX.js} +1 -1
  169. package/webapp/dist/assets/{realtime.store-CBDO4Abj.js → realtime.store-hfxZD2DU.js} +1 -1
  170. package/webapp/dist/assets/release-bits-BJ6i23y0.js +1 -0
  171. package/webapp/dist/assets/release-build-panel-BNXkwNE1.js +1 -0
  172. package/webapp/dist/assets/release-deployment-state-gN8ZT3aq.js +1 -0
  173. package/webapp/dist/assets/releases-CXXKUh-v.js +2 -0
  174. package/webapp/dist/assets/{stack-start-notice-BVnMOwRQ.js → stack-start-notice-D-AH_bzn.js} +1 -1
  175. package/webapp/dist/assets/{stacks-IZ1mfizi.js → stacks-WY_ynkON.js} +1 -1
  176. package/webapp/dist/assets/{system-info-BFKNM-hf.js → system-info-DXMwo6gl.js} +1 -1
  177. package/webapp/dist/assets/{topology-CPey5dP6.js → topology-C_Qp1RqW.js} +1 -1
  178. package/webapp/dist/assets/{use-stack-context-UkBsF-l7.js → use-stack-context-8yaHaN8L.js} +1 -1
  179. package/webapp/dist/assets/{webapp-D-UhjFhS.js → webapp-BO_WoB_z.js} +3 -3
  180. package/webapp/dist/console-version.json +1 -1
  181. package/webapp/dist/index.html +12 -12
  182. package/webapp/dist/assets/deploy-release-dialog-BOXcD5Wo.js +0 -1
  183. package/webapp/dist/assets/deployments-qV20Ak0P.js +0 -1
  184. package/webapp/dist/assets/detail-B85-Wees.js +0 -2
  185. package/webapp/dist/assets/index-uq8Dkk4c.js +0 -1
  186. package/webapp/dist/assets/project.store--G5pIF-J.js +0 -1
  187. package/webapp/dist/assets/release-bits-B2a_wbft.js +0 -1
  188. package/webapp/dist/assets/release-build-panel-BQmIdKW9.js +0 -1
  189. package/webapp/dist/assets/release-deployment-state-DRrEI2pP.js +0 -1
  190. package/webapp/dist/assets/release-wire-f2VuBoBF.js +0 -1
  191. package/webapp/dist/assets/releases-Bp3CDgmo.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"node-manager.service.d.ts","sourceRoot":"","sources":["../../src/services/node-manager.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node-manager.service.d.ts","sourceRoot":"","sources":["../../src/services/node-manager.service.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,8 @@ const NODES_FILE = path.join(OMNITRON_HOME, 'nodes.json');
24
24
  const LOCAL_NODE_ID = 'local';
25
25
  /** DaemonStateStore key holding the operator-set check configuration. */
26
26
  const CHECK_CONFIG_KEY = 'nodes:check-config';
27
+ const LEGACY_IMPORT_KEY = 'nodes:legacy-import-in-progress';
28
+ const NODE_SECRET_FIELDS = ['privateKey', 'passphrase', 'password'];
27
29
  // =============================================================================
28
30
  // Service
29
31
  // =============================================================================
@@ -54,6 +56,7 @@ export class NodeManagerService extends EventEmitter {
54
56
  * report a perfectly reachable node as unreachable.
55
57
  */
56
58
  pendingSecrets = new Map();
59
+ repairing = new Map();
57
60
  /** The master's mesh — see `setSlaveConnector`. */
58
61
  mesh = null;
59
62
  constructor(logger,
@@ -236,7 +239,6 @@ export class NodeManagerService extends EventEmitter {
236
239
  sshPort: assertPort('SSH port', input.sshPort ?? 22),
237
240
  sshUser: input.sshUser ?? 'root',
238
241
  sshAuthMethod: input.sshAuthMethod ?? 'key',
239
- ...(input.sshPrivateKey && { sshPrivateKey: input.sshPrivateKey }),
240
242
  runtime: input.runtime ?? 'node',
241
243
  daemonPort: assertPort('daemon port', input.daemonPort ?? 9700),
242
244
  tags: input.tags ?? [],
@@ -248,8 +250,8 @@ export class NodeManagerService extends EventEmitter {
248
250
  if (!node.name.trim())
249
251
  throw new Error('Node name is required');
250
252
  this.assertAddressIsFree(node.host, node.daemonPort);
251
- // Store secrets encrypted (passphrase, password) — only boolean markers in nodes.json.
252
- // Must await before connectivity check, otherwise getSecret() reads stale file.
253
+ if (input.sshPrivateKey)
254
+ await this.assignPrivateKey(node, input.sshPrivateKey);
253
255
  if (input.sshPassphrase) {
254
256
  node.hasPassphrase = true;
255
257
  await this.setSecret(id, 'passphrase', input.sshPassphrase);
@@ -258,8 +260,9 @@ export class NodeManagerService extends EventEmitter {
258
260
  node.hasPassword = true;
259
261
  await this.setSecret(id, 'password', input.sshPassword);
260
262
  }
263
+ this.assertAddressIsFree(node.host, node.daemonPort);
264
+ this.persistNode(node, true);
261
265
  this.nodes.set(id, node);
262
- this.save();
263
266
  this.emit('node:added', node);
264
267
  this.logger.info({ nodeId: id, name: node.name, host: node.host }, 'Node added');
265
268
  // Check connectivity in background — secrets are already persisted.
@@ -280,6 +283,8 @@ export class NodeManagerService extends EventEmitter {
280
283
  throw new Error('Node registration is changing; retry after the current operation');
281
284
  this.changingNodes.add(id);
282
285
  try {
286
+ if (this.hasPendingCredentials(id))
287
+ await this.repairCredentials(previous);
283
288
  return await this.writeNodeUpdate(previous, input);
284
289
  }
285
290
  finally {
@@ -298,22 +303,28 @@ export class NodeManagerService extends EventEmitter {
298
303
  const id = node.id;
299
304
  if (node.isLocal && input.host)
300
305
  throw new Error('Cannot change host of local node');
301
- // Extract secrets before spreading into node (they must not be saved to nodes.json)
302
- const { sshPassphrase, sshPassword, ...safeInput } = input;
303
- if (safeInput.host !== undefined)
304
- safeInput.host = assertNodeHost(safeInput.host);
305
- if (safeInput.sshPort !== undefined)
306
- safeInput.sshPort = assertPort('SSH port', safeInput.sshPort);
307
- if (safeInput.daemonPort !== undefined)
308
- safeInput.daemonPort = assertPort('daemon port', safeInput.daemonPort);
309
- const updated = {
310
- ...node,
311
- ...safeInput,
312
- id: node.id,
313
- isLocal: node.isLocal,
314
- createdAt: node.createdAt,
315
- updatedAt: new Date().toISOString(),
316
- };
306
+ const { sshPassphrase, sshPassword, sshPrivateKey } = input;
307
+ const updated = { ...node, updatedAt: new Date().toISOString() };
308
+ if (input.name !== undefined)
309
+ updated.name = input.name;
310
+ if (input.host !== undefined)
311
+ updated.host = assertNodeHost(input.host);
312
+ if (input.sshPort !== undefined)
313
+ updated.sshPort = assertPort('SSH port', input.sshPort);
314
+ if (input.daemonPort !== undefined)
315
+ updated.daemonPort = assertPort('daemon port', input.daemonPort);
316
+ if (input.sshUser !== undefined)
317
+ updated.sshUser = input.sshUser;
318
+ if (input.sshAuthMethod !== undefined)
319
+ updated.sshAuthMethod = input.sshAuthMethod;
320
+ if (input.runtime !== undefined)
321
+ updated.runtime = input.runtime;
322
+ if (input.tags !== undefined)
323
+ updated.tags = input.tags;
324
+ if (input.offlineTimeout !== undefined)
325
+ updated.offlineTimeout = input.offlineTimeout;
326
+ if (!updated.name.trim())
327
+ throw new Error('Node name is required');
317
328
  // An edit can collide as readily as an add — correcting one node's host
318
329
  // to the address another already holds is exactly how a duplicate gets
319
330
  // made. Checked before any secret is written, so a refused edit leaves
@@ -337,17 +348,22 @@ export class NodeManagerService extends EventEmitter {
337
348
  const passwordCleared = updated.sshAuthMethod !== node.sshAuthMethod && updated.sshAuthMethod === 'key';
338
349
  const keyCleared = updated.sshAuthMethod !== node.sshAuthMethod && updated.sshAuthMethod === 'password';
339
350
  if ((!passwordCleared && node.hasPassword && sshPassword === undefined)
340
- || (!keyCleared && (node.sshPrivateKey && safeInput.sshPrivateKey === undefined
351
+ || (!keyCleared && ((node.sshPrivateKey || node.hasPrivateKey) && sshPrivateKey === undefined
341
352
  || node.hasPassphrase && sshPassphrase === undefined)))
342
353
  throw new Error('SSH destination changed. Explicitly supply or clear the SSH credential for the new host, port and account.');
343
354
  // Commit the replacement address without credentials before changing its ID-keyed vault slots.
344
355
  // A crash between these stores leaves a node that needs credentials. Xec requires explicit auth.
345
- const detached = { ...updated, hasPassword: false, hasPassphrase: false };
356
+ const detached = { ...updated, hasPassword: false, hasPassphrase: false, hasPrivateKey: false };
346
357
  delete detached.sshPrivateKey;
347
358
  this.persistNode(detached, true);
348
359
  this.nodes.set(id, detached);
349
360
  }
350
- // Update encrypted secrets — await to ensure they're persisted before connectivity check
361
+ if (updated.sshAuthMethod === 'password' && node.sshAuthMethod !== updated.sshAuthMethod) {
362
+ await this.assignPrivateKey(updated, '');
363
+ }
364
+ else if (sshPrivateKey !== undefined) {
365
+ await this.assignPrivateKey(updated, sshPrivateKey);
366
+ }
351
367
  if (sshPassphrase !== undefined) {
352
368
  if (sshPassphrase) {
353
369
  updated.hasPassphrase = true;
@@ -378,7 +394,6 @@ export class NodeManagerService extends EventEmitter {
378
394
  await this.deleteSecret(id, 'password');
379
395
  }
380
396
  if (updated.sshAuthMethod === 'password') {
381
- delete updated.sshPrivateKey;
382
397
  if (updated.hasPassphrase) {
383
398
  updated.hasPassphrase = false;
384
399
  await this.deleteSecret(id, 'passphrase');
@@ -412,6 +427,8 @@ export class NodeManagerService extends EventEmitter {
412
427
  throw new Error('Node registration is changing; retry after the current operation');
413
428
  this.changingNodes.add(id);
414
429
  try {
430
+ if (this.hasPendingCredentials(id))
431
+ await this.repairCredentials(node);
415
432
  await this.deleteNodeRegistration(node);
416
433
  }
417
434
  finally {
@@ -420,11 +437,10 @@ export class NodeManagerService extends EventEmitter {
420
437
  }
421
438
  async deleteNodeRegistration(node) {
422
439
  const id = node.id;
440
+ if (node.hasPrivateKey)
441
+ await this.deleteSecret(id, 'privateKey');
423
442
  await this.deleteSecret(id, 'passphrase');
424
443
  await this.deleteSecret(id, 'password');
425
- this.nodes.delete(id);
426
- this.statusCache.delete(id);
427
- this.summaryCache.delete(id);
428
444
  try {
429
445
  this.store.deleteNodeSync(id);
430
446
  }
@@ -432,7 +448,9 @@ export class NodeManagerService extends EventEmitter {
432
448
  this.logger.warn({ nodeId: id, error: err.message }, 'Failed to delete node row');
433
449
  throw err;
434
450
  }
435
- this.save();
451
+ this.nodes.delete(id);
452
+ this.statusCache.delete(id);
453
+ this.summaryCache.delete(id);
436
454
  this.emit('node:removed', id);
437
455
  this.logger.info({ nodeId: id, name: node.name }, 'Node removed');
438
456
  }
@@ -641,18 +659,26 @@ export class NodeManagerService extends EventEmitter {
641
659
  port: node.sshPort,
642
660
  username: node.sshUser,
643
661
  };
644
- if (node.sshPrivateKey) {
662
+ if (node.hasPrivateKey) {
663
+ const key = await this.getSecret(node.id, 'privateKey');
664
+ if (!key)
665
+ throw new Error('Stored SSH private key is unavailable');
666
+ target.privateKey = key;
667
+ }
668
+ else if (node.sshPrivateKey) {
645
669
  target.privateKey = readKeyFile(node.sshPrivateKey);
646
670
  }
647
671
  if (node.hasPassphrase) {
648
672
  const passphrase = await this.getSecret(node.id, 'passphrase');
649
- if (passphrase)
650
- target.passphrase = passphrase;
673
+ if (!passphrase)
674
+ throw new Error('Stored SSH key passphrase is unavailable');
675
+ target.passphrase = passphrase;
651
676
  }
652
677
  if (node.hasPassword) {
653
678
  const password = await this.getSecret(node.id, 'password');
654
- if (password)
655
- target.password = password;
679
+ if (!password)
680
+ throw new Error('Stored SSH password is unavailable');
681
+ target.password = password;
656
682
  }
657
683
  if (this.nodes.get(node.id) !== registered || this.changingNodes.has(node.id))
658
684
  throw new Error('SSH node registration changed during credential lookup');
@@ -853,20 +879,24 @@ export class NodeManagerService extends EventEmitter {
853
879
  // ===========================================================================
854
880
  // Secrets Helpers
855
881
  // ===========================================================================
882
+ async assignPrivateKey(node, input) {
883
+ if (isKeyMaterial(input)) {
884
+ await this.setSecret(node.id, 'privateKey', input);
885
+ node.hasPrivateKey = true;
886
+ delete node.sshPrivateKey;
887
+ return;
888
+ }
889
+ if (node.hasPrivateKey)
890
+ await this.deleteSecret(node.id, 'privateKey');
891
+ delete node.hasPrivateKey;
892
+ if (input)
893
+ node.sshPrivateKey = input;
894
+ else
895
+ delete node.sshPrivateKey;
896
+ }
856
897
  secretKey(nodeId, field) {
857
898
  return `node:${nodeId}:${field}`;
858
899
  }
859
- /** The vault's own answer, with no in-memory fallback. */
860
- async readVault(nodeId, field) {
861
- if (!this.secrets)
862
- return null;
863
- try {
864
- return await this.secrets.get(this.secretKey(nodeId, field));
865
- }
866
- catch {
867
- return null;
868
- }
869
- }
870
900
  async getSecret(nodeId, field) {
871
901
  const key = this.secretKey(nodeId, field);
872
902
  if (this.secrets) {
@@ -881,18 +911,14 @@ export class NodeManagerService extends EventEmitter {
881
911
  }
882
912
  async setSecret(nodeId, field, value) {
883
913
  if (!this.secrets) {
884
- this.logger.warn({ nodeId, field }, 'SecretsService not available — secret not stored');
885
- return;
914
+ throw new Error('SSH credential vault is unavailable');
886
915
  }
887
916
  await this.secrets.set(this.secretKey(nodeId, field), value);
888
917
  }
889
918
  async deleteSecret(nodeId, field) {
890
919
  if (!this.secrets)
891
920
  return;
892
- try {
893
- await this.secrets.delete(this.secretKey(nodeId, field));
894
- }
895
- catch { /* non-critical */ }
921
+ await this.secrets.delete(this.secretKey(nodeId, field));
896
922
  }
897
923
  /**
898
924
  * Status of the machine this daemon runs on.
@@ -1006,8 +1032,13 @@ export class NodeManagerService extends EventEmitter {
1006
1032
  // Persistence
1007
1033
  // ===========================================================================
1008
1034
  load() {
1009
- /** Rows found carrying a plaintext secret; healed after the loop. */
1010
- const leaked = [];
1035
+ const remember = (raw) => {
1036
+ const { node, strays } = splitStoredSecrets(raw);
1037
+ this.nodes.set(node.id, node);
1038
+ for (const { field, value } of strays)
1039
+ this.pendingSecrets.set(this.secretKey(node.id, field), value);
1040
+ return node;
1041
+ };
1011
1042
  try {
1012
1043
  // First-pass: read from SQLite. Each row's `metadata` column
1013
1044
  // holds the full INode JSON; the denormalised columns are for
@@ -1017,14 +1048,7 @@ export class NodeManagerService extends EventEmitter {
1017
1048
  if (!row.metadata)
1018
1049
  continue;
1019
1050
  try {
1020
- const raw = JSON.parse(row.metadata);
1021
- const { node, strays } = splitStoredSecrets(raw);
1022
- this.nodes.set(node.id, node);
1023
- if (strays.length > 0) {
1024
- for (const stray of strays)
1025
- this.pendingSecrets.set(this.secretKey(node.id, stray.field), stray.value);
1026
- leaked.push({ node, strays });
1027
- }
1051
+ const node = remember(JSON.parse(row.metadata));
1028
1052
  // Restore the last known health, but NOT a status reading: a
1029
1053
  // reachability answer from a previous daemon is not a current one,
1030
1054
  // and the console has no way to tell them apart on the dots. What
@@ -1044,77 +1068,65 @@ export class NodeManagerService extends EventEmitter {
1044
1068
  // Corrupt row — skip and let the next mutation overwrite.
1045
1069
  }
1046
1070
  }
1047
- // One-shot legacy import. If the SQLite table is empty but the
1048
- // pre-T-7 nodes.json file exists, migrate it in-place. The
1049
- // existing file is then unlinked so subsequent boots skip the
1050
- // branch.
1051
- if (this.nodes.size === 0 && fs.existsSync(NODES_FILE)) {
1052
- try {
1053
- const data = JSON.parse(fs.readFileSync(NODES_FILE, 'utf-8'));
1054
- if (Array.isArray(data.nodes)) {
1055
- for (const node of data.nodes) {
1056
- this.nodes.set(node.id, node);
1057
- this.persistNode(node);
1058
- }
1059
- }
1060
- try {
1061
- fs.unlinkSync(NODES_FILE);
1062
- }
1063
- catch { /* best-effort */ }
1064
- }
1065
- catch (err) {
1066
- this.logger.warn({ error: err.message }, 'Legacy nodes.json migration failed — leaving file in place');
1071
+ // Keep the original file until every imported credential and row is durable.
1072
+ // The marker lets an interrupted import finish after some rows already exist.
1073
+ if ((this.nodes.size === 0 || this.store.kvGetSync(LEGACY_IMPORT_KEY)) && fs.existsSync(NODES_FILE)) {
1074
+ const original = fs.readFileSync(NODES_FILE, 'utf8');
1075
+ const { nodes } = JSON.parse(original);
1076
+ if (!Array.isArray(nodes))
1077
+ throw new Error('Invalid legacy node registry');
1078
+ this.store.kvSetSync(LEGACY_IMPORT_KEY, true);
1079
+ for (const raw of nodes) {
1080
+ if (this.nodes.has(raw.id))
1081
+ continue;
1082
+ const node = remember(raw);
1083
+ if (!this.hasPendingCredentials(node.id))
1084
+ this.persistNode(node, true);
1067
1085
  }
1086
+ // Existing SQLite rows may also still need credential migration.
1087
+ const imported = [...this.nodes.values()].map(node => this.repairCredentials(node));
1088
+ void Promise.all(imported).then(() => {
1089
+ if (fs.readFileSync(NODES_FILE, 'utf8') !== original)
1090
+ throw new Error('Legacy node registry changed during import');
1091
+ fs.unlinkSync(NODES_FILE);
1092
+ this.store.kvSetSync(LEGACY_IMPORT_KEY, false);
1093
+ }).catch(() => this.logger.error('Legacy node registry retained; retry credential migration after checking the vault and database'));
1068
1094
  }
1069
1095
  }
1070
1096
  catch (err) {
1071
1097
  this.logger.warn({ error: err.message }, 'Failed to load nodes from store');
1072
1098
  }
1073
- if (leaked.length > 0)
1074
- void this.healLeakedSecrets(leaked);
1099
+ for (const node of this.nodes.values()) {
1100
+ void this.repairCredentials(node).catch(() => this.logger.error({ nodeId: node.id }, 'SSH credential migration incomplete; source retained for retry'));
1101
+ }
1075
1102
  this.loadCheckConfig();
1076
1103
  }
1077
- /**
1078
- * Move a plaintext secret out of a node row and into the vault.
1079
- *
1080
- * Observed on a live daemon: a node row whose `metadata` JSON contained
1081
- * `"sshPassphrase": "<the operator's actual passphrase>"` next to
1082
- * `"hasPassphrase": true`. An older `updateNode` spread its whole input
1083
- * into the node — the destructure that stops that was added afterwards, and
1084
- * it fixed new writes without touching rows already written. `listNodes`
1085
- * then spread the parsed row onto the wire, so the passphrase went to every
1086
- * caller with the VIEWER role, and to `omnitron node list`.
1087
- *
1088
- * Two things are needed and they are not the same: the wire shape is now
1089
- * built field by field (`toWireNode`), which stops the leak for any
1090
- * residue, present or future; and this moves the value to where it should
1091
- * have been, so the row itself stops holding it.
1092
- */
1093
- async healLeakedSecrets(leaked) {
1094
- for (const { node, strays } of leaked) {
1095
- for (const { field, value } of strays) {
1096
- this.logger.warn({ nodeId: node.id, field }, 'Node row held a plaintext SSH secret — moving it to the secret store');
1097
- // Read the VAULT, not `getSecret`: that one falls back to
1098
- // `pendingSecrets`, which is where this value already is — so asking
1099
- // it whether the secret is stored answers "yes" about the copy we are
1100
- // trying to store, and the write never happens.
1101
- const existing = await this.readVault(node.id, field);
1102
- if (!existing) {
1103
- if (!this.secrets) {
1104
- // Nothing to migrate into. The row keeps the value for now — the
1105
- // node would otherwise stop authenticating — but it no longer
1106
- // reaches the wire, which is the part that matters.
1107
- this.logger.error({ nodeId: node.id, field }, 'No secret store available — plaintext secret left in the node row');
1108
- continue;
1109
- }
1110
- await this.setSecret(node.id, field, value);
1111
- }
1112
- }
1113
- // Rewrite the row from the sanitised node.
1114
- this.persistNode(node);
1115
- for (const { field } of strays)
1116
- this.pendingSecrets.delete(this.secretKey(node.id, field));
1104
+ hasPendingCredentials(id) {
1105
+ return NODE_SECRET_FIELDS.some(field => this.pendingSecrets.has(this.secretKey(id, field)));
1106
+ }
1107
+ repairCredentials(node) {
1108
+ const active = this.repairing.get(node.id);
1109
+ if (active)
1110
+ return active;
1111
+ if (!this.hasPendingCredentials(node.id))
1112
+ return Promise.resolve();
1113
+ const repaired = this.storePendingCredentials(node).finally(() => this.repairing.delete(node.id));
1114
+ this.repairing.set(node.id, repaired);
1115
+ return repaired;
1116
+ }
1117
+ async storePendingCredentials(node) {
1118
+ if (!this.secrets)
1119
+ throw new Error('SSH credential vault is unavailable');
1120
+ for (const field of NODE_SECRET_FIELDS) {
1121
+ const key = this.secretKey(node.id, field);
1122
+ const pending = this.pendingSecrets.get(key);
1123
+ if (pending === undefined)
1124
+ continue;
1125
+ await this.secrets.setIfAbsent(key, pending);
1117
1126
  }
1127
+ this.persistNode(node, true);
1128
+ for (const field of NODE_SECRET_FIELDS)
1129
+ this.pendingSecrets.delete(this.secretKey(node.id, field));
1118
1130
  }
1119
1131
  /** Restore the operator's check configuration, if one was ever set. */
1120
1132
  loadCheckConfig() {
@@ -1156,21 +1168,11 @@ export class NodeManagerService extends EventEmitter {
1156
1168
  throw err;
1157
1169
  }
1158
1170
  }
1159
- /**
1160
- * Persist every node currently held in memory. Used after a
1161
- * batch mutation that affected multiple rows (the previous
1162
- * fs.writeFileSync semantics).
1163
- */
1164
- save() {
1165
- for (const node of this.nodes.values()) {
1166
- this.persistNode(node);
1167
- }
1168
- }
1169
1171
  ensureLocalNode() {
1170
1172
  if (this.nodes.has(LOCAL_NODE_ID))
1171
1173
  return;
1172
1174
  const now = new Date().toISOString();
1173
- this.nodes.set(LOCAL_NODE_ID, {
1175
+ const local = {
1174
1176
  id: LOCAL_NODE_ID,
1175
1177
  name: 'Local Machine',
1176
1178
  host: '127.0.0.1',
@@ -1183,8 +1185,9 @@ export class NodeManagerService extends EventEmitter {
1183
1185
  isLocal: true,
1184
1186
  createdAt: now,
1185
1187
  updatedAt: now,
1186
- });
1187
- this.save();
1188
+ };
1189
+ this.nodes.set(LOCAL_NODE_ID, local);
1190
+ this.persistNode(local);
1188
1191
  }
1189
1192
  async dispose() {
1190
1193
  await this.remoteOps.dispose();
@@ -1217,10 +1220,10 @@ function toWireNode(node) {
1217
1220
  createdAt: node.createdAt,
1218
1221
  updatedAt: node.updatedAt,
1219
1222
  };
1220
- // The key is a PATH or key content chosen by the operator; the booleans say
1221
- // only whether a secret exists. Neither is a secret itself.
1222
- if (node.sshPrivateKey !== undefined)
1223
+ if (node.sshPrivateKey && !isKeyMaterial(node.sshPrivateKey))
1223
1224
  wire.sshPrivateKey = node.sshPrivateKey;
1225
+ if (node.hasPrivateKey)
1226
+ wire.hasPrivateKey = true;
1224
1227
  if (node.hasPassphrase !== undefined)
1225
1228
  wire.hasPassphrase = node.hasPassphrase;
1226
1229
  if (node.hasPassword !== undefined)
@@ -1234,6 +1237,11 @@ function splitStoredSecrets(raw) {
1234
1237
  const { sshPassphrase, sshPassword, ...rest } = raw;
1235
1238
  const node = rest;
1236
1239
  const strays = [];
1240
+ if (node.sshPrivateKey && isKeyMaterial(node.sshPrivateKey)) {
1241
+ strays.push({ field: 'privateKey', value: node.sshPrivateKey });
1242
+ delete node.sshPrivateKey;
1243
+ node.hasPrivateKey = true;
1244
+ }
1237
1245
  if (typeof sshPassphrase === 'string' && sshPassphrase) {
1238
1246
  node.hasPassphrase = true;
1239
1247
  strays.push({ field: 'passphrase', value: sshPassphrase });
@@ -1242,7 +1250,10 @@ function splitStoredSecrets(raw) {
1242
1250
  node.hasPassword = true;
1243
1251
  strays.push({ field: 'password', value: sshPassword });
1244
1252
  }
1245
- return { node, strays };
1253
+ return { node: toWireNode(node), strays };
1254
+ }
1255
+ function isKeyMaterial(value) {
1256
+ return /[\r\n\0]|^\s*(?:-{4,}|PuTTY-User-Key-File-\d+:)/i.test(value);
1246
1257
  }
1247
1258
  /** Fallback offline timeout, matching the health monitor's own default. */
1248
1259
  const DEFAULT_OFFLINE_TIMEOUT_MS = 90_000;