@omnitron-dev/omnitron 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1247) hide show
  1. package/dist/BUILD.json +5 -0
  2. package/dist/cli/omnitron.js +351 -29
  3. package/dist/cli/omnitron.js.map +1 -1
  4. package/dist/cluster/cluster.rpc-service.d.ts +40 -0
  5. package/dist/cluster/cluster.rpc-service.d.ts.map +1 -1
  6. package/dist/cluster/cluster.rpc-service.js +49 -3
  7. package/dist/cluster/cluster.rpc-service.js.map +1 -1
  8. package/dist/cluster/config-sync.d.ts.map +1 -1
  9. package/dist/cluster/config-sync.js.map +1 -1
  10. package/dist/cluster/leader-election.d.ts +90 -2
  11. package/dist/cluster/leader-election.d.ts.map +1 -1
  12. package/dist/cluster/leader-election.js +176 -22
  13. package/dist/cluster/leader-election.js.map +1 -1
  14. package/dist/cluster/mesh-link.d.ts +136 -0
  15. package/dist/cluster/mesh-link.d.ts.map +1 -0
  16. package/dist/cluster/mesh-link.js +212 -0
  17. package/dist/cluster/mesh-link.js.map +1 -0
  18. package/dist/cluster/mesh.d.ts +59 -0
  19. package/dist/cluster/mesh.d.ts.map +1 -0
  20. package/dist/cluster/mesh.js +117 -0
  21. package/dist/cluster/mesh.js.map +1 -0
  22. package/dist/cluster/slave-connector.d.ts +193 -9
  23. package/dist/cluster/slave-connector.d.ts.map +1 -1
  24. package/dist/cluster/slave-connector.js +409 -37
  25. package/dist/cluster/slave-connector.js.map +1 -1
  26. package/dist/commands/audit.d.ts +65 -0
  27. package/dist/commands/audit.d.ts.map +1 -0
  28. package/dist/commands/audit.js +233 -0
  29. package/dist/commands/audit.js.map +1 -0
  30. package/dist/commands/backup.d.ts +4 -0
  31. package/dist/commands/backup.d.ts.map +1 -1
  32. package/dist/commands/backup.js +255 -33
  33. package/dist/commands/backup.js.map +1 -1
  34. package/dist/commands/cluster.d.ts.map +1 -1
  35. package/dist/commands/cluster.js +78 -47
  36. package/dist/commands/cluster.js.map +1 -1
  37. package/dist/commands/daemon-cmd.d.ts +7 -1
  38. package/dist/commands/daemon-cmd.d.ts.map +1 -1
  39. package/dist/commands/daemon-cmd.js +87 -21
  40. package/dist/commands/daemon-cmd.js.map +1 -1
  41. package/dist/commands/daemon-required.d.ts +34 -0
  42. package/dist/commands/daemon-required.d.ts.map +1 -0
  43. package/dist/commands/daemon-required.js +92 -0
  44. package/dist/commands/daemon-required.js.map +1 -0
  45. package/dist/commands/deploy.d.ts +70 -8
  46. package/dist/commands/deploy.d.ts.map +1 -1
  47. package/dist/commands/deploy.js +99 -64
  48. package/dist/commands/deploy.js.map +1 -1
  49. package/dist/commands/discover.d.ts +25 -4
  50. package/dist/commands/discover.d.ts.map +1 -1
  51. package/dist/commands/discover.js +155 -102
  52. package/dist/commands/discover.js.map +1 -1
  53. package/dist/commands/doctor.d.ts +293 -0
  54. package/dist/commands/doctor.d.ts.map +1 -0
  55. package/dist/commands/doctor.js +1706 -0
  56. package/dist/commands/doctor.js.map +1 -0
  57. package/dist/commands/env.d.ts +8 -1
  58. package/dist/commands/env.d.ts.map +1 -1
  59. package/dist/commands/env.js +13 -8
  60. package/dist/commands/env.js.map +1 -1
  61. package/dist/commands/exec.d.ts.map +1 -1
  62. package/dist/commands/exec.js +3 -2
  63. package/dist/commands/exec.js.map +1 -1
  64. package/dist/commands/fleet-asking.d.ts +87 -0
  65. package/dist/commands/fleet-asking.d.ts.map +1 -0
  66. package/dist/commands/fleet-asking.js +176 -0
  67. package/dist/commands/fleet-asking.js.map +1 -0
  68. package/dist/commands/fleet.d.ts +18 -0
  69. package/dist/commands/fleet.d.ts.map +1 -1
  70. package/dist/commands/fleet.js +314 -74
  71. package/dist/commands/fleet.js.map +1 -1
  72. package/dist/commands/health-check.d.ts +8 -6
  73. package/dist/commands/health-check.d.ts.map +1 -1
  74. package/dist/commands/health-check.js +26 -23
  75. package/dist/commands/health-check.js.map +1 -1
  76. package/dist/commands/health.d.ts.map +1 -1
  77. package/dist/commands/health.js +22 -8
  78. package/dist/commands/health.js.map +1 -1
  79. package/dist/commands/infra.d.ts +42 -1
  80. package/dist/commands/infra.d.ts.map +1 -1
  81. package/dist/commands/infra.js +418 -49
  82. package/dist/commands/infra.js.map +1 -1
  83. package/dist/commands/init.d.ts.map +1 -1
  84. package/dist/commands/init.js +21 -36
  85. package/dist/commands/init.js.map +1 -1
  86. package/dist/commands/inspect-graph.d.ts.map +1 -1
  87. package/dist/commands/inspect-graph.js +2 -6
  88. package/dist/commands/inspect-graph.js.map +1 -1
  89. package/dist/commands/inspect.d.ts.map +1 -1
  90. package/dist/commands/inspect.js +110 -14
  91. package/dist/commands/inspect.js.map +1 -1
  92. package/dist/commands/k8s.d.ts.map +1 -1
  93. package/dist/commands/k8s.js +23 -18
  94. package/dist/commands/k8s.js.map +1 -1
  95. package/dist/commands/kb.d.ts.map +1 -1
  96. package/dist/commands/kb.js +88 -29
  97. package/dist/commands/kb.js.map +1 -1
  98. package/dist/commands/list.d.ts.map +1 -1
  99. package/dist/commands/list.js +21 -9
  100. package/dist/commands/list.js.map +1 -1
  101. package/dist/commands/log-health.d.ts +120 -0
  102. package/dist/commands/log-health.d.ts.map +1 -0
  103. package/dist/commands/log-health.js +212 -0
  104. package/dist/commands/log-health.js.map +1 -0
  105. package/dist/commands/logs.d.ts +96 -2
  106. package/dist/commands/logs.d.ts.map +1 -1
  107. package/dist/commands/logs.js +522 -196
  108. package/dist/commands/logs.js.map +1 -1
  109. package/dist/commands/metrics.d.ts.map +1 -1
  110. package/dist/commands/metrics.js +35 -17
  111. package/dist/commands/metrics.js.map +1 -1
  112. package/dist/commands/monit.d.ts.map +1 -1
  113. package/dist/commands/monit.js +3 -2
  114. package/dist/commands/monit.js.map +1 -1
  115. package/dist/commands/node.d.ts +68 -0
  116. package/dist/commands/node.d.ts.map +1 -1
  117. package/dist/commands/node.js +285 -68
  118. package/dist/commands/node.js.map +1 -1
  119. package/dist/commands/output.d.ts +10 -0
  120. package/dist/commands/output.d.ts.map +1 -1
  121. package/dist/commands/output.js +41 -1
  122. package/dist/commands/output.js.map +1 -1
  123. package/dist/commands/pipeline.d.ts.map +1 -1
  124. package/dist/commands/pipeline.js +26 -19
  125. package/dist/commands/pipeline.js.map +1 -1
  126. package/dist/commands/project.d.ts +19 -0
  127. package/dist/commands/project.d.ts.map +1 -1
  128. package/dist/commands/project.js +125 -27
  129. package/dist/commands/project.js.map +1 -1
  130. package/dist/commands/release-store.d.ts +28 -0
  131. package/dist/commands/release-store.d.ts.map +1 -0
  132. package/dist/commands/release-store.js +158 -0
  133. package/dist/commands/release-store.js.map +1 -0
  134. package/dist/commands/release.d.ts +61 -0
  135. package/dist/commands/release.d.ts.map +1 -0
  136. package/dist/commands/release.js +555 -0
  137. package/dist/commands/release.js.map +1 -0
  138. package/dist/commands/reload.d.ts +16 -0
  139. package/dist/commands/reload.d.ts.map +1 -1
  140. package/dist/commands/reload.js +21 -5
  141. package/dist/commands/reload.js.map +1 -1
  142. package/dist/commands/remote.d.ts +40 -0
  143. package/dist/commands/remote.d.ts.map +1 -1
  144. package/dist/commands/remote.js +146 -35
  145. package/dist/commands/remote.js.map +1 -1
  146. package/dist/commands/restart.d.ts +16 -0
  147. package/dist/commands/restart.d.ts.map +1 -1
  148. package/dist/commands/restart.js +21 -5
  149. package/dist/commands/restart.js.map +1 -1
  150. package/dist/commands/scale.d.ts.map +1 -1
  151. package/dist/commands/scale.js +4 -3
  152. package/dist/commands/scale.js.map +1 -1
  153. package/dist/commands/secret.d.ts +54 -0
  154. package/dist/commands/secret.d.ts.map +1 -1
  155. package/dist/commands/secret.js +236 -34
  156. package/dist/commands/secret.js.map +1 -1
  157. package/dist/commands/service-units.d.ts +181 -0
  158. package/dist/commands/service-units.d.ts.map +1 -0
  159. package/dist/commands/service-units.js +291 -0
  160. package/dist/commands/service-units.js.map +1 -0
  161. package/dist/commands/service.d.ts +39 -0
  162. package/dist/commands/service.d.ts.map +1 -0
  163. package/dist/commands/service.js +370 -0
  164. package/dist/commands/service.js.map +1 -0
  165. package/dist/commands/spinner.d.ts +29 -0
  166. package/dist/commands/spinner.d.ts.map +1 -0
  167. package/dist/commands/spinner.js +44 -0
  168. package/dist/commands/spinner.js.map +1 -0
  169. package/dist/commands/stack.d.ts +49 -1
  170. package/dist/commands/stack.d.ts.map +1 -1
  171. package/dist/commands/stack.js +325 -12
  172. package/dist/commands/stack.js.map +1 -1
  173. package/dist/commands/start.d.ts +16 -0
  174. package/dist/commands/start.d.ts.map +1 -1
  175. package/dist/commands/start.js +20 -3
  176. package/dist/commands/start.js.map +1 -1
  177. package/dist/commands/status.d.ts +9 -0
  178. package/dist/commands/status.d.ts.map +1 -1
  179. package/dist/commands/status.js +45 -29
  180. package/dist/commands/status.js.map +1 -1
  181. package/dist/commands/stop.d.ts +16 -0
  182. package/dist/commands/stop.d.ts.map +1 -1
  183. package/dist/commands/stop.js +34 -6
  184. package/dist/commands/stop.js.map +1 -1
  185. package/dist/commands/tor.d.ts +14 -7
  186. package/dist/commands/tor.d.ts.map +1 -1
  187. package/dist/commands/tor.js +113 -33
  188. package/dist/commands/tor.js.map +1 -1
  189. package/dist/commands/up.d.ts +85 -1
  190. package/dist/commands/up.d.ts.map +1 -1
  191. package/dist/commands/up.js +180 -32
  192. package/dist/commands/up.js.map +1 -1
  193. package/dist/commands/webapp.d.ts.map +1 -1
  194. package/dist/commands/webapp.js +2 -1
  195. package/dist/commands/webapp.js.map +1 -1
  196. package/dist/config/defaults.d.ts +16 -0
  197. package/dist/config/defaults.d.ts.map +1 -1
  198. package/dist/config/defaults.js +26 -3
  199. package/dist/config/defaults.js.map +1 -1
  200. package/dist/config/define-ecosystem.d.ts.map +1 -1
  201. package/dist/config/define-ecosystem.js +2 -0
  202. package/dist/config/define-ecosystem.js.map +1 -1
  203. package/dist/config/index.d.ts +1 -1
  204. package/dist/config/index.d.ts.map +1 -1
  205. package/dist/config/index.js +1 -1
  206. package/dist/config/index.js.map +1 -1
  207. package/dist/config/loader.d.ts +45 -1
  208. package/dist/config/loader.d.ts.map +1 -1
  209. package/dist/config/loader.js +64 -6
  210. package/dist/config/loader.js.map +1 -1
  211. package/dist/config/omnitron-config.schema.d.ts.map +1 -1
  212. package/dist/config/omnitron-config.schema.js +16 -0
  213. package/dist/config/omnitron-config.schema.js.map +1 -1
  214. package/dist/config/types.d.ts +411 -42
  215. package/dist/config/types.d.ts.map +1 -1
  216. package/dist/daemon/config-reload.d.ts +27 -0
  217. package/dist/daemon/config-reload.d.ts.map +1 -0
  218. package/dist/daemon/config-reload.js +42 -0
  219. package/dist/daemon/config-reload.js.map +1 -0
  220. package/dist/daemon/daemon-client.d.ts +136 -2
  221. package/dist/daemon/daemon-client.d.ts.map +1 -1
  222. package/dist/daemon/daemon-client.js +307 -21
  223. package/dist/daemon/daemon-client.js.map +1 -1
  224. package/dist/daemon/daemon-entry.js +90 -30
  225. package/dist/daemon/daemon-entry.js.map +1 -1
  226. package/dist/daemon/daemon-scheduler.d.ts +4 -0
  227. package/dist/daemon/daemon-scheduler.d.ts.map +1 -1
  228. package/dist/daemon/daemon-scheduler.js +137 -7
  229. package/dist/daemon/daemon-scheduler.js.map +1 -1
  230. package/dist/daemon/daemon-state-store.service.d.ts +263 -0
  231. package/dist/daemon/daemon-state-store.service.d.ts.map +1 -0
  232. package/dist/daemon/daemon-state-store.service.js +446 -0
  233. package/dist/daemon/daemon-state-store.service.js.map +1 -0
  234. package/dist/daemon/daemon.d.ts +205 -1
  235. package/dist/daemon/daemon.d.ts.map +1 -1
  236. package/dist/daemon/daemon.js +1139 -139
  237. package/dist/daemon/daemon.js.map +1 -1
  238. package/dist/daemon/daemon.module.d.ts.map +1 -1
  239. package/dist/daemon/daemon.module.js +163 -83
  240. package/dist/daemon/daemon.module.js.map +1 -1
  241. package/dist/daemon/daemon.rpc-service.d.ts +90 -1
  242. package/dist/daemon/daemon.rpc-service.d.ts.map +1 -1
  243. package/dist/daemon/daemon.rpc-service.js +438 -96
  244. package/dist/daemon/daemon.rpc-service.js.map +1 -1
  245. package/dist/daemon/pid-manager.d.ts +16 -3
  246. package/dist/daemon/pid-manager.d.ts.map +1 -1
  247. package/dist/daemon/pid-manager.js +31 -4
  248. package/dist/daemon/pid-manager.js.map +1 -1
  249. package/dist/daemon/spawn-daemon.d.ts +73 -0
  250. package/dist/daemon/spawn-daemon.d.ts.map +1 -0
  251. package/dist/daemon/spawn-daemon.js +101 -0
  252. package/dist/daemon/spawn-daemon.js.map +1 -0
  253. package/dist/daemon/state-store.d.ts +65 -3
  254. package/dist/daemon/state-store.d.ts.map +1 -1
  255. package/dist/daemon/state-store.js +100 -58
  256. package/dist/daemon/state-store.js.map +1 -1
  257. package/dist/database/connection.d.ts +71 -0
  258. package/dist/database/connection.d.ts.map +1 -0
  259. package/dist/database/connection.js +129 -0
  260. package/dist/database/connection.js.map +1 -0
  261. package/dist/database/dialect.d.ts +60 -0
  262. package/dist/database/dialect.d.ts.map +1 -0
  263. package/dist/database/dialect.js +80 -0
  264. package/dist/database/dialect.js.map +1 -0
  265. package/dist/database/migrate.d.ts +5 -2
  266. package/dist/database/migrate.d.ts.map +1 -1
  267. package/dist/database/migrate.js +23 -33
  268. package/dist/database/migrate.js.map +1 -1
  269. package/dist/database/migration-runner.d.ts +50 -0
  270. package/dist/database/migration-runner.d.ts.map +1 -0
  271. package/dist/database/migration-runner.js +143 -0
  272. package/dist/database/migration-runner.js.map +1 -0
  273. package/dist/database/migrations/006_login_throttle.d.ts +17 -0
  274. package/dist/database/migrations/006_login_throttle.d.ts.map +1 -0
  275. package/dist/database/migrations/006_login_throttle.js +33 -0
  276. package/dist/database/migrations/006_login_throttle.js.map +1 -0
  277. package/dist/database/migrations/007_sync_ingested.d.ts +21 -0
  278. package/dist/database/migrations/007_sync_ingested.d.ts.map +1 -0
  279. package/dist/database/migrations/007_sync_ingested.js +39 -0
  280. package/dist/database/migrations/007_sync_ingested.js.map +1 -0
  281. package/dist/database/migrations/008_node_health_version_text.d.ts +24 -0
  282. package/dist/database/migrations/008_node_health_version_text.d.ts.map +1 -0
  283. package/dist/database/migrations/008_node_health_version_text.js +35 -0
  284. package/dist/database/migrations/008_node_health_version_text.js.map +1 -0
  285. package/dist/database/migrations/009_node_health_unmeasured.d.ts +40 -0
  286. package/dist/database/migrations/009_node_health_unmeasured.d.ts.map +1 -0
  287. package/dist/database/migrations/009_node_health_unmeasured.js +63 -0
  288. package/dist/database/migrations/009_node_health_unmeasured.js.map +1 -0
  289. package/dist/database/migrations/010_metrics_raw_had_no_reader.d.ts +22 -0
  290. package/dist/database/migrations/010_metrics_raw_had_no_reader.d.ts.map +1 -0
  291. package/dist/database/migrations/010_metrics_raw_had_no_reader.js +26 -0
  292. package/dist/database/migrations/010_metrics_raw_had_no_reader.js.map +1 -0
  293. package/dist/database/migrations/011_alerts_nobody_had_to_write.d.ts +56 -0
  294. package/dist/database/migrations/011_alerts_nobody_had_to_write.d.ts.map +1 -0
  295. package/dist/database/migrations/011_alerts_nobody_had_to_write.js +79 -0
  296. package/dist/database/migrations/011_alerts_nobody_had_to_write.js.map +1 -0
  297. package/dist/database/migrations/012_a_disk_nobody_watched.d.ts +26 -0
  298. package/dist/database/migrations/012_a_disk_nobody_watched.d.ts.map +1 -0
  299. package/dist/database/migrations/012_a_disk_nobody_watched.js +40 -0
  300. package/dist/database/migrations/012_a_disk_nobody_watched.js.map +1 -0
  301. package/dist/database/migrations/013_an_alarm_delivered.d.ts +14 -0
  302. package/dist/database/migrations/013_an_alarm_delivered.d.ts.map +1 -0
  303. package/dist/database/migrations/013_an_alarm_delivered.js +28 -0
  304. package/dist/database/migrations/013_an_alarm_delivered.js.map +1 -0
  305. package/dist/database/migrations/index.d.ts +21 -0
  306. package/dist/database/migrations/index.d.ts.map +1 -0
  307. package/dist/database/migrations/index.js +47 -0
  308. package/dist/database/migrations/index.js.map +1 -0
  309. package/dist/database/schema.d.ts +27 -11
  310. package/dist/database/schema.d.ts.map +1 -1
  311. package/dist/database/sqlite-date-binding.d.ts +71 -0
  312. package/dist/database/sqlite-date-binding.d.ts.map +1 -0
  313. package/dist/database/sqlite-date-binding.js +174 -0
  314. package/dist/database/sqlite-date-binding.js.map +1 -0
  315. package/dist/execution/data-channel.d.ts +48 -0
  316. package/dist/execution/data-channel.d.ts.map +1 -0
  317. package/dist/execution/data-channel.js +55 -0
  318. package/dist/execution/data-channel.js.map +1 -0
  319. package/dist/execution/execution.service.d.ts +139 -0
  320. package/dist/execution/execution.service.d.ts.map +1 -1
  321. package/dist/execution/execution.service.js +273 -10
  322. package/dist/execution/execution.service.js.map +1 -1
  323. package/dist/index.d.ts +1 -1
  324. package/dist/index.d.ts.map +1 -1
  325. package/dist/infrastructure/bare-metal-plan.d.ts +208 -0
  326. package/dist/infrastructure/bare-metal-plan.d.ts.map +1 -0
  327. package/dist/infrastructure/bare-metal-plan.js +363 -0
  328. package/dist/infrastructure/bare-metal-plan.js.map +1 -0
  329. package/dist/infrastructure/bare-metal-runner.d.ts +65 -0
  330. package/dist/infrastructure/bare-metal-runner.d.ts.map +1 -0
  331. package/dist/infrastructure/bare-metal-runner.js +354 -0
  332. package/dist/infrastructure/bare-metal-runner.js.map +1 -0
  333. package/dist/infrastructure/config-payload.d.ts +149 -0
  334. package/dist/infrastructure/config-payload.d.ts.map +1 -0
  335. package/dist/infrastructure/config-payload.js +235 -0
  336. package/dist/infrastructure/config-payload.js.map +1 -0
  337. package/dist/infrastructure/container-runtime.d.ts +169 -1
  338. package/dist/infrastructure/container-runtime.d.ts.map +1 -1
  339. package/dist/infrastructure/container-runtime.js +525 -33
  340. package/dist/infrastructure/container-runtime.js.map +1 -1
  341. package/dist/infrastructure/gateway-reachability.d.ts +85 -0
  342. package/dist/infrastructure/gateway-reachability.d.ts.map +1 -0
  343. package/dist/infrastructure/gateway-reachability.js +116 -0
  344. package/dist/infrastructure/gateway-reachability.js.map +1 -0
  345. package/dist/infrastructure/gateway-redis-db.d.ts +29 -0
  346. package/dist/infrastructure/gateway-redis-db.d.ts.map +1 -0
  347. package/dist/infrastructure/gateway-redis-db.js +32 -0
  348. package/dist/infrastructure/gateway-redis-db.js.map +1 -0
  349. package/dist/infrastructure/host-inspection.d.ts +223 -0
  350. package/dist/infrastructure/host-inspection.d.ts.map +1 -0
  351. package/dist/infrastructure/host-inspection.js +501 -0
  352. package/dist/infrastructure/host-inspection.js.map +1 -0
  353. package/dist/infrastructure/infrastructure-gate.d.ts.map +1 -1
  354. package/dist/infrastructure/infrastructure.service.d.ts +206 -6
  355. package/dist/infrastructure/infrastructure.service.d.ts.map +1 -1
  356. package/dist/infrastructure/infrastructure.service.js +415 -89
  357. package/dist/infrastructure/infrastructure.service.js.map +1 -1
  358. package/dist/infrastructure/known-machines.d.ts +76 -0
  359. package/dist/infrastructure/known-machines.d.ts.map +1 -0
  360. package/dist/infrastructure/known-machines.js +94 -0
  361. package/dist/infrastructure/known-machines.js.map +1 -0
  362. package/dist/infrastructure/node-credentials.d.ts +45 -0
  363. package/dist/infrastructure/node-credentials.d.ts.map +1 -0
  364. package/dist/infrastructure/node-credentials.js +80 -0
  365. package/dist/infrastructure/node-credentials.js.map +1 -0
  366. package/dist/infrastructure/orphan-containers.d.ts +99 -0
  367. package/dist/infrastructure/orphan-containers.d.ts.map +1 -0
  368. package/dist/infrastructure/orphan-containers.js +106 -0
  369. package/dist/infrastructure/orphan-containers.js.map +1 -0
  370. package/dist/infrastructure/phantom-endpoint-janitor.d.ts +20 -0
  371. package/dist/infrastructure/phantom-endpoint-janitor.d.ts.map +1 -1
  372. package/dist/infrastructure/phantom-endpoint-janitor.js +56 -6
  373. package/dist/infrastructure/phantom-endpoint-janitor.js.map +1 -1
  374. package/dist/infrastructure/presets/gateway-openresty.d.ts.map +1 -1
  375. package/dist/infrastructure/presets/gateway-openresty.js +29 -3
  376. package/dist/infrastructure/presets/gateway-openresty.js.map +1 -1
  377. package/dist/infrastructure/presets/minio.d.ts.map +1 -1
  378. package/dist/infrastructure/presets/minio.js +36 -3
  379. package/dist/infrastructure/presets/minio.js.map +1 -1
  380. package/dist/infrastructure/presets/postgres.d.ts.map +1 -1
  381. package/dist/infrastructure/presets/postgres.js +69 -2
  382. package/dist/infrastructure/presets/postgres.js.map +1 -1
  383. package/dist/infrastructure/presets/redis.d.ts.map +1 -1
  384. package/dist/infrastructure/presets/redis.js +13 -0
  385. package/dist/infrastructure/presets/redis.js.map +1 -1
  386. package/dist/infrastructure/presets/registry.d.ts.map +1 -1
  387. package/dist/infrastructure/presets/registry.js +81 -12
  388. package/dist/infrastructure/presets/registry.js.map +1 -1
  389. package/dist/infrastructure/presets/tor.d.ts +38 -0
  390. package/dist/infrastructure/presets/tor.d.ts.map +1 -1
  391. package/dist/infrastructure/presets/tor.js +78 -69
  392. package/dist/infrastructure/presets/tor.js.map +1 -1
  393. package/dist/infrastructure/presets/torrc.d.ts +54 -0
  394. package/dist/infrastructure/presets/torrc.d.ts.map +1 -0
  395. package/dist/infrastructure/presets/torrc.js +153 -0
  396. package/dist/infrastructure/presets/torrc.js.map +1 -0
  397. package/dist/infrastructure/presets/types.d.ts +17 -0
  398. package/dist/infrastructure/presets/types.d.ts.map +1 -1
  399. package/dist/infrastructure/provisioning-outcome.d.ts +85 -0
  400. package/dist/infrastructure/provisioning-outcome.d.ts.map +1 -0
  401. package/dist/infrastructure/provisioning-outcome.js +122 -0
  402. package/dist/infrastructure/provisioning-outcome.js.map +1 -0
  403. package/dist/infrastructure/server-registry.d.ts +27 -0
  404. package/dist/infrastructure/server-registry.d.ts.map +1 -1
  405. package/dist/infrastructure/server-registry.js +60 -5
  406. package/dist/infrastructure/server-registry.js.map +1 -1
  407. package/dist/infrastructure/service-binding.d.ts +60 -0
  408. package/dist/infrastructure/service-binding.d.ts.map +1 -0
  409. package/dist/infrastructure/service-binding.js +90 -0
  410. package/dist/infrastructure/service-binding.js.map +1 -0
  411. package/dist/infrastructure/service-credentials.d.ts +105 -0
  412. package/dist/infrastructure/service-credentials.d.ts.map +1 -0
  413. package/dist/infrastructure/service-credentials.js +141 -0
  414. package/dist/infrastructure/service-credentials.js.map +1 -0
  415. package/dist/infrastructure/service-resolver.d.ts +160 -12
  416. package/dist/infrastructure/service-resolver.d.ts.map +1 -1
  417. package/dist/infrastructure/service-resolver.js +463 -53
  418. package/dist/infrastructure/service-resolver.js.map +1 -1
  419. package/dist/infrastructure/shipped-config.d.ts +43 -0
  420. package/dist/infrastructure/shipped-config.d.ts.map +1 -0
  421. package/dist/infrastructure/shipped-config.js +159 -0
  422. package/dist/infrastructure/shipped-config.js.map +1 -0
  423. package/dist/infrastructure/stack-infra-manager.d.ts +6 -0
  424. package/dist/infrastructure/stack-infra-manager.d.ts.map +1 -1
  425. package/dist/infrastructure/stack-infra-manager.js +31 -13
  426. package/dist/infrastructure/stack-infra-manager.js.map +1 -1
  427. package/dist/infrastructure/types.d.ts +261 -6
  428. package/dist/infrastructure/types.d.ts.map +1 -1
  429. package/dist/mcp/mcp-bridge.d.ts.map +1 -1
  430. package/dist/mcp/mcp-bridge.js.map +1 -1
  431. package/dist/mcp/tool-groups/apps.tools.d.ts +2 -1
  432. package/dist/mcp/tool-groups/apps.tools.d.ts.map +1 -1
  433. package/dist/mcp/tool-groups/apps.tools.js +2 -2
  434. package/dist/mcp/tool-groups/apps.tools.js.map +1 -1
  435. package/dist/mcp/tool-groups/infra.tools.d.ts +16 -2
  436. package/dist/mcp/tool-groups/infra.tools.d.ts.map +1 -1
  437. package/dist/mcp/tool-groups/infra.tools.js +51 -41
  438. package/dist/mcp/tool-groups/infra.tools.js.map +1 -1
  439. package/dist/mcp/tool-groups/kb.tools.d.ts.map +1 -1
  440. package/dist/mcp/tool-groups/kb.tools.js +4 -6
  441. package/dist/mcp/tool-groups/kb.tools.js.map +1 -1
  442. package/dist/mcp/tool-groups/management.tools.d.ts +20 -3
  443. package/dist/mcp/tool-groups/management.tools.d.ts.map +1 -1
  444. package/dist/mcp/tool-groups/management.tools.js +158 -125
  445. package/dist/mcp/tool-groups/management.tools.js.map +1 -1
  446. package/dist/mcp/tool-groups/monitoring.tools.d.ts +10 -1
  447. package/dist/mcp/tool-groups/monitoring.tools.d.ts.map +1 -1
  448. package/dist/mcp/tool-groups/monitoring.tools.js +65 -18
  449. package/dist/mcp/tool-groups/monitoring.tools.js.map +1 -1
  450. package/dist/mcp/unavailable-tools.d.ts +35 -0
  451. package/dist/mcp/unavailable-tools.d.ts.map +1 -0
  452. package/dist/mcp/unavailable-tools.js +59 -0
  453. package/dist/mcp/unavailable-tools.js.map +1 -0
  454. package/dist/monitoring/app-health.indicator.d.ts.map +1 -1
  455. package/dist/monitoring/app-health.indicator.js +12 -0
  456. package/dist/monitoring/app-health.indicator.js.map +1 -1
  457. package/dist/monitoring/desktop-notifier.d.ts +26 -0
  458. package/dist/monitoring/desktop-notifier.d.ts.map +1 -0
  459. package/dist/monitoring/desktop-notifier.js +49 -0
  460. package/dist/monitoring/desktop-notifier.js.map +1 -0
  461. package/dist/monitoring/docker-health.indicator.d.ts +17 -1
  462. package/dist/monitoring/docker-health.indicator.d.ts.map +1 -1
  463. package/dist/monitoring/docker-health.indicator.js +38 -8
  464. package/dist/monitoring/docker-health.indicator.js.map +1 -1
  465. package/dist/monitoring/event-loop-stall.indicator.d.ts +26 -0
  466. package/dist/monitoring/event-loop-stall.indicator.d.ts.map +1 -0
  467. package/dist/monitoring/event-loop-stall.indicator.js +47 -0
  468. package/dist/monitoring/event-loop-stall.indicator.js.map +1 -0
  469. package/dist/monitoring/event-loop-watch.d.ts +124 -0
  470. package/dist/monitoring/event-loop-watch.d.ts.map +1 -0
  471. package/dist/monitoring/event-loop-watch.js +179 -0
  472. package/dist/monitoring/event-loop-watch.js.map +1 -0
  473. package/dist/monitoring/host-disk.d.ts +6 -0
  474. package/dist/monitoring/host-disk.d.ts.map +1 -0
  475. package/dist/monitoring/host-disk.js +26 -0
  476. package/dist/monitoring/host-disk.js.map +1 -0
  477. package/dist/monitoring/log-line.d.ts +188 -0
  478. package/dist/monitoring/log-line.d.ts.map +1 -0
  479. package/dist/monitoring/log-line.js +458 -0
  480. package/dist/monitoring/log-line.js.map +1 -0
  481. package/dist/monitoring/log-manager.d.ts +89 -8
  482. package/dist/monitoring/log-manager.d.ts.map +1 -1
  483. package/dist/monitoring/log-manager.js +246 -77
  484. package/dist/monitoring/log-manager.js.map +1 -1
  485. package/dist/observability/metrics-bridge.d.ts.map +1 -1
  486. package/dist/observability/metrics-server.d.ts.map +1 -1
  487. package/dist/orchestrator/app-handle.d.ts +157 -2
  488. package/dist/orchestrator/app-handle.d.ts.map +1 -1
  489. package/dist/orchestrator/app-handle.js +165 -3
  490. package/dist/orchestrator/app-handle.js.map +1 -1
  491. package/dist/orchestrator/app-health.d.ts +33 -0
  492. package/dist/orchestrator/app-health.d.ts.map +1 -0
  493. package/dist/orchestrator/app-health.js +74 -0
  494. package/dist/orchestrator/app-health.js.map +1 -0
  495. package/dist/orchestrator/app-traffic.d.ts +14 -0
  496. package/dist/orchestrator/app-traffic.d.ts.map +1 -0
  497. package/dist/orchestrator/app-traffic.js +33 -0
  498. package/dist/orchestrator/app-traffic.js.map +1 -0
  499. package/dist/orchestrator/bootstrap-loader-child.d.ts +19 -0
  500. package/dist/orchestrator/bootstrap-loader-child.d.ts.map +1 -0
  501. package/dist/orchestrator/bootstrap-loader-child.js +43 -0
  502. package/dist/orchestrator/bootstrap-loader-child.js.map +1 -0
  503. package/dist/orchestrator/bootstrap-loader.d.ts +72 -0
  504. package/dist/orchestrator/bootstrap-loader.d.ts.map +1 -1
  505. package/dist/orchestrator/bootstrap-loader.js +350 -5
  506. package/dist/orchestrator/bootstrap-loader.js.map +1 -1
  507. package/dist/orchestrator/bootstrap-process.d.ts +99 -4
  508. package/dist/orchestrator/bootstrap-process.d.ts.map +1 -1
  509. package/dist/orchestrator/bootstrap-process.js +330 -19
  510. package/dist/orchestrator/bootstrap-process.js.map +1 -1
  511. package/dist/orchestrator/build-service.d.ts.map +1 -1
  512. package/dist/orchestrator/build-service.js.map +1 -1
  513. package/dist/orchestrator/call-exposed-service.d.ts +7 -0
  514. package/dist/orchestrator/call-exposed-service.d.ts.map +1 -0
  515. package/dist/orchestrator/call-exposed-service.js +50 -0
  516. package/dist/orchestrator/call-exposed-service.js.map +1 -0
  517. package/dist/orchestrator/classic-launcher.d.ts +1 -0
  518. package/dist/orchestrator/classic-launcher.d.ts.map +1 -1
  519. package/dist/orchestrator/classic-launcher.js +34 -3
  520. package/dist/orchestrator/classic-launcher.js.map +1 -1
  521. package/dist/orchestrator/file-watcher.d.ts +78 -1
  522. package/dist/orchestrator/file-watcher.d.ts.map +1 -1
  523. package/dist/orchestrator/file-watcher.js +181 -6
  524. package/dist/orchestrator/file-watcher.js.map +1 -1
  525. package/dist/orchestrator/module-worker-process.d.ts.map +1 -1
  526. package/dist/orchestrator/module-worker-process.js +10 -0
  527. package/dist/orchestrator/module-worker-process.js.map +1 -1
  528. package/dist/orchestrator/orchestrator.service.d.ts +371 -11
  529. package/dist/orchestrator/orchestrator.service.d.ts.map +1 -1
  530. package/dist/orchestrator/orchestrator.service.js +1602 -269
  531. package/dist/orchestrator/orchestrator.service.js.map +1 -1
  532. package/dist/orchestrator/owned-pids.d.ts +83 -0
  533. package/dist/orchestrator/owned-pids.d.ts.map +1 -0
  534. package/dist/orchestrator/owned-pids.js +93 -0
  535. package/dist/orchestrator/owned-pids.js.map +1 -0
  536. package/dist/orchestrator/port-conflict.d.ts +102 -0
  537. package/dist/orchestrator/port-conflict.d.ts.map +1 -0
  538. package/dist/orchestrator/port-conflict.js +131 -0
  539. package/dist/orchestrator/port-conflict.js.map +1 -0
  540. package/dist/orchestrator/process-janitor.d.ts +78 -4
  541. package/dist/orchestrator/process-janitor.d.ts.map +1 -1
  542. package/dist/orchestrator/process-janitor.js +246 -30
  543. package/dist/orchestrator/process-janitor.js.map +1 -1
  544. package/dist/orchestrator/ps-metrics.d.ts +37 -0
  545. package/dist/orchestrator/ps-metrics.d.ts.map +1 -0
  546. package/dist/orchestrator/ps-metrics.js +60 -0
  547. package/dist/orchestrator/ps-metrics.js.map +1 -0
  548. package/dist/orchestrator/service-router.d.ts +58 -2
  549. package/dist/orchestrator/service-router.d.ts.map +1 -1
  550. package/dist/orchestrator/service-router.js +157 -25
  551. package/dist/orchestrator/service-router.js.map +1 -1
  552. package/dist/orchestrator/topology-query.d.ts +49 -0
  553. package/dist/orchestrator/topology-query.d.ts.map +1 -0
  554. package/dist/orchestrator/topology-query.js +54 -0
  555. package/dist/orchestrator/topology-query.js.map +1 -0
  556. package/dist/project/artifact-builder.d.ts +174 -3
  557. package/dist/project/artifact-builder.d.ts.map +1 -1
  558. package/dist/project/artifact-builder.js +454 -42
  559. package/dist/project/artifact-builder.js.map +1 -1
  560. package/dist/project/build-decision.d.ts +58 -0
  561. package/dist/project/build-decision.d.ts.map +1 -0
  562. package/dist/project/build-decision.js +59 -0
  563. package/dist/project/build-decision.js.map +1 -0
  564. package/dist/project/config-resolver.d.ts +6 -4
  565. package/dist/project/config-resolver.d.ts.map +1 -1
  566. package/dist/project/config-resolver.js +128 -74
  567. package/dist/project/config-resolver.js.map +1 -1
  568. package/dist/project/declared-config.d.ts +42 -0
  569. package/dist/project/declared-config.d.ts.map +1 -0
  570. package/dist/project/declared-config.js +60 -0
  571. package/dist/project/declared-config.js.map +1 -0
  572. package/dist/project/dependency-analyzer.d.ts.map +1 -1
  573. package/dist/project/dependency-analyzer.js.map +1 -1
  574. package/dist/project/deploy-phases.d.ts +35 -0
  575. package/dist/project/deploy-phases.d.ts.map +1 -0
  576. package/dist/project/deploy-phases.js +75 -0
  577. package/dist/project/deploy-phases.js.map +1 -0
  578. package/dist/project/exec-in-group.d.ts +49 -0
  579. package/dist/project/exec-in-group.d.ts.map +1 -0
  580. package/dist/project/exec-in-group.js +100 -0
  581. package/dist/project/exec-in-group.js.map +1 -0
  582. package/dist/project/node-app-config.d.ts +101 -0
  583. package/dist/project/node-app-config.d.ts.map +1 -0
  584. package/dist/project/node-app-config.js +134 -0
  585. package/dist/project/node-app-config.js.map +1 -0
  586. package/dist/project/node-app-health.d.ts +76 -0
  587. package/dist/project/node-app-health.d.ts.map +1 -0
  588. package/dist/project/node-app-health.js +119 -0
  589. package/dist/project/node-app-health.js.map +1 -0
  590. package/dist/project/operator-account.d.ts +287 -0
  591. package/dist/project/operator-account.d.ts.map +1 -0
  592. package/dist/project/operator-account.js +373 -0
  593. package/dist/project/operator-account.js.map +1 -0
  594. package/dist/project/registry.d.ts +45 -8
  595. package/dist/project/registry.d.ts.map +1 -1
  596. package/dist/project/registry.js +217 -49
  597. package/dist/project/registry.js.map +1 -1
  598. package/dist/project/requirements-scanner.d.ts +1 -0
  599. package/dist/project/requirements-scanner.d.ts.map +1 -1
  600. package/dist/project/requirements-scanner.js +28 -3
  601. package/dist/project/requirements-scanner.js.map +1 -1
  602. package/dist/project/stack-env.d.ts +37 -0
  603. package/dist/project/stack-env.d.ts.map +1 -0
  604. package/dist/project/stack-env.js +85 -0
  605. package/dist/project/stack-env.js.map +1 -0
  606. package/dist/project/stand-containers.d.ts +4 -0
  607. package/dist/project/stand-containers.d.ts.map +1 -0
  608. package/dist/project/stand-containers.js +27 -0
  609. package/dist/project/stand-containers.js.map +1 -0
  610. package/dist/project/working-tree.d.ts +62 -0
  611. package/dist/project/working-tree.d.ts.map +1 -0
  612. package/dist/project/working-tree.js +101 -0
  613. package/dist/project/working-tree.js.map +1 -0
  614. package/dist/release/artifact-store.d.ts +360 -0
  615. package/dist/release/artifact-store.d.ts.map +1 -0
  616. package/dist/release/artifact-store.js +1040 -0
  617. package/dist/release/artifact-store.js.map +1 -0
  618. package/dist/release/attest-on-node.d.ts +92 -0
  619. package/dist/release/attest-on-node.d.ts.map +1 -0
  620. package/dist/release/attest-on-node.js +207 -0
  621. package/dist/release/attest-on-node.js.map +1 -0
  622. package/dist/release/attest.d.ts +135 -0
  623. package/dist/release/attest.d.ts.map +1 -0
  624. package/dist/release/attest.js +199 -0
  625. package/dist/release/attest.js.map +1 -0
  626. package/dist/release/build-run.d.ts +151 -0
  627. package/dist/release/build-run.d.ts.map +1 -0
  628. package/dist/release/build-run.js +609 -0
  629. package/dist/release/build-run.js.map +1 -0
  630. package/dist/release/builder.d.ts +57 -0
  631. package/dist/release/builder.d.ts.map +1 -0
  632. package/dist/release/builder.js +120 -0
  633. package/dist/release/builder.js.map +1 -0
  634. package/dist/release/daemon-omni.d.ts +41 -0
  635. package/dist/release/daemon-omni.d.ts.map +1 -0
  636. package/dist/release/daemon-omni.js +59 -0
  637. package/dist/release/daemon-omni.js.map +1 -0
  638. package/dist/release/definition-inputs.d.ts +34 -0
  639. package/dist/release/definition-inputs.d.ts.map +1 -0
  640. package/dist/release/definition-inputs.js +90 -0
  641. package/dist/release/definition-inputs.js.map +1 -0
  642. package/dist/release/delivered.d.ts +53 -0
  643. package/dist/release/delivered.d.ts.map +1 -0
  644. package/dist/release/delivered.js +81 -0
  645. package/dist/release/delivered.js.map +1 -0
  646. package/dist/release/layout.d.ts +61 -0
  647. package/dist/release/layout.d.ts.map +1 -0
  648. package/dist/release/layout.js +114 -0
  649. package/dist/release/layout.js.map +1 -0
  650. package/dist/release/load.d.ts +44 -0
  651. package/dist/release/load.d.ts.map +1 -0
  652. package/dist/release/load.js +84 -0
  653. package/dist/release/load.js.map +1 -0
  654. package/dist/release/manifest.d.ts +299 -0
  655. package/dist/release/manifest.d.ts.map +1 -0
  656. package/dist/release/manifest.js +176 -0
  657. package/dist/release/manifest.js.map +1 -0
  658. package/dist/release/migrations.d.ts +32 -0
  659. package/dist/release/migrations.d.ts.map +1 -0
  660. package/dist/release/migrations.js +51 -0
  661. package/dist/release/migrations.js.map +1 -0
  662. package/dist/release/publish.d.ts +71 -0
  663. package/dist/release/publish.d.ts.map +1 -0
  664. package/dist/release/publish.js +102 -0
  665. package/dist/release/publish.js.map +1 -0
  666. package/dist/release/store.d.ts +174 -0
  667. package/dist/release/store.d.ts.map +1 -0
  668. package/dist/release/store.js +257 -0
  669. package/dist/release/store.js.map +1 -0
  670. package/dist/services/alert.rpc-service.d.ts +11 -15
  671. package/dist/services/alert.rpc-service.d.ts.map +1 -1
  672. package/dist/services/alert.rpc-service.js +29 -14
  673. package/dist/services/alert.rpc-service.js.map +1 -1
  674. package/dist/services/alert.service.d.ts +96 -59
  675. package/dist/services/alert.service.d.ts.map +1 -1
  676. package/dist/services/alert.service.js +455 -70
  677. package/dist/services/alert.service.js.map +1 -1
  678. package/dist/services/anonymous-input.d.ts +26 -0
  679. package/dist/services/anonymous-input.d.ts.map +1 -0
  680. package/dist/services/anonymous-input.js +44 -0
  681. package/dist/services/anonymous-input.js.map +1 -0
  682. package/dist/services/audit.rpc-service.d.ts +27 -0
  683. package/dist/services/audit.rpc-service.d.ts.map +1 -0
  684. package/dist/services/audit.rpc-service.js +57 -0
  685. package/dist/services/audit.rpc-service.js.map +1 -0
  686. package/dist/services/audit.service.d.ts +165 -0
  687. package/dist/services/audit.service.d.ts.map +1 -0
  688. package/dist/services/audit.service.js +242 -0
  689. package/dist/services/audit.service.js.map +1 -0
  690. package/dist/services/auth-context.d.ts +21 -1
  691. package/dist/services/auth-context.d.ts.map +1 -1
  692. package/dist/services/auth-context.js +51 -5
  693. package/dist/services/auth-context.js.map +1 -1
  694. package/dist/services/auth.rpc-service.d.ts +0 -1
  695. package/dist/services/auth.rpc-service.d.ts.map +1 -1
  696. package/dist/services/auth.rpc-service.js +23 -9
  697. package/dist/services/auth.rpc-service.js.map +1 -1
  698. package/dist/services/auth.service.d.ts +47 -33
  699. package/dist/services/auth.service.d.ts.map +1 -1
  700. package/dist/services/auth.service.js +155 -23
  701. package/dist/services/auth.service.js.map +1 -1
  702. package/dist/services/backup-pipeline.d.ts +97 -0
  703. package/dist/services/backup-pipeline.d.ts.map +1 -0
  704. package/dist/services/backup-pipeline.js +286 -0
  705. package/dist/services/backup-pipeline.js.map +1 -0
  706. package/dist/services/backup-schedule.d.ts +69 -0
  707. package/dist/services/backup-schedule.d.ts.map +1 -0
  708. package/dist/services/backup-schedule.js +132 -0
  709. package/dist/services/backup-schedule.js.map +1 -0
  710. package/dist/services/backup.rpc-service.d.ts +31 -2
  711. package/dist/services/backup.rpc-service.d.ts.map +1 -1
  712. package/dist/services/backup.rpc-service.js +53 -0
  713. package/dist/services/backup.rpc-service.js.map +1 -1
  714. package/dist/services/backup.service.d.ts +458 -14
  715. package/dist/services/backup.service.d.ts.map +1 -1
  716. package/dist/services/backup.service.js +1368 -170
  717. package/dist/services/backup.service.js.map +1 -1
  718. package/dist/services/bundle-build-worker.d.ts +19 -0
  719. package/dist/services/bundle-build-worker.d.ts.map +1 -0
  720. package/dist/services/bundle-build-worker.js +40 -0
  721. package/dist/services/bundle-build-worker.js.map +1 -0
  722. package/dist/services/bundle-builder.d.ts +487 -0
  723. package/dist/services/bundle-builder.d.ts.map +1 -0
  724. package/dist/services/bundle-builder.js +1083 -0
  725. package/dist/services/bundle-builder.js.map +1 -0
  726. package/dist/services/bundle-worker-protocol.d.ts +19 -0
  727. package/dist/services/bundle-worker-protocol.d.ts.map +1 -0
  728. package/dist/services/bundle-worker-protocol.js +32 -0
  729. package/dist/services/bundle-worker-protocol.js.map +1 -0
  730. package/dist/services/deploy.rpc-service.d.ts +4 -1
  731. package/dist/services/deploy.rpc-service.d.ts.map +1 -1
  732. package/dist/services/deploy.rpc-service.js +10 -0
  733. package/dist/services/deploy.rpc-service.js.map +1 -1
  734. package/dist/services/deploy.service.d.ts +14 -24
  735. package/dist/services/deploy.service.d.ts.map +1 -1
  736. package/dist/services/deploy.service.js +46 -5
  737. package/dist/services/deploy.service.js.map +1 -1
  738. package/dist/services/discovery.rpc-service.d.ts +2 -1
  739. package/dist/services/discovery.rpc-service.d.ts.map +1 -1
  740. package/dist/services/discovery.rpc-service.js.map +1 -1
  741. package/dist/services/discovery.service.d.ts +32 -28
  742. package/dist/services/discovery.service.d.ts.map +1 -1
  743. package/dist/services/discovery.service.js +108 -133
  744. package/dist/services/discovery.service.js.map +1 -1
  745. package/dist/services/event-broadcaster.rpc-service.d.ts +36 -23
  746. package/dist/services/event-broadcaster.rpc-service.d.ts.map +1 -1
  747. package/dist/services/event-broadcaster.rpc-service.js +70 -32
  748. package/dist/services/event-broadcaster.rpc-service.js.map +1 -1
  749. package/dist/services/event-broadcaster.service.d.ts.map +1 -1
  750. package/dist/services/event-broadcaster.service.js.map +1 -1
  751. package/dist/services/fleet.rpc-service.d.ts +2 -1
  752. package/dist/services/fleet.rpc-service.d.ts.map +1 -1
  753. package/dist/services/fleet.rpc-service.js +3 -1
  754. package/dist/services/fleet.rpc-service.js.map +1 -1
  755. package/dist/services/fleet.service.d.ts +32 -34
  756. package/dist/services/fleet.service.d.ts.map +1 -1
  757. package/dist/services/fleet.service.js +66 -10
  758. package/dist/services/fleet.service.js.map +1 -1
  759. package/dist/services/health-check.rpc-service.d.ts +2 -1
  760. package/dist/services/health-check.rpc-service.d.ts.map +1 -1
  761. package/dist/services/health-check.rpc-service.js.map +1 -1
  762. package/dist/services/health-check.service.d.ts +50 -36
  763. package/dist/services/health-check.service.d.ts.map +1 -1
  764. package/dist/services/health-check.service.js +179 -182
  765. package/dist/services/health-check.service.js.map +1 -1
  766. package/dist/services/infrastructure.rpc-service.d.ts +297 -2
  767. package/dist/services/infrastructure.rpc-service.d.ts.map +1 -1
  768. package/dist/services/infrastructure.rpc-service.js +523 -11
  769. package/dist/services/infrastructure.rpc-service.js.map +1 -1
  770. package/dist/services/kubernetes.rpc-service.d.ts +2 -1
  771. package/dist/services/kubernetes.rpc-service.d.ts.map +1 -1
  772. package/dist/services/kubernetes.rpc-service.js.map +1 -1
  773. package/dist/services/kubernetes.service.d.ts +5 -39
  774. package/dist/services/kubernetes.service.d.ts.map +1 -1
  775. package/dist/services/kubernetes.service.js +29 -5
  776. package/dist/services/kubernetes.service.js.map +1 -1
  777. package/dist/services/local-bundle.d.ts +151 -0
  778. package/dist/services/local-bundle.d.ts.map +1 -0
  779. package/dist/services/local-bundle.js +233 -0
  780. package/dist/services/local-bundle.js.map +1 -0
  781. package/dist/services/lockout.d.ts +28 -0
  782. package/dist/services/lockout.d.ts.map +1 -0
  783. package/dist/services/lockout.js +41 -0
  784. package/dist/services/lockout.js.map +1 -0
  785. package/dist/services/log-collector.rpc-service.d.ts +16 -0
  786. package/dist/services/log-collector.rpc-service.d.ts.map +1 -1
  787. package/dist/services/log-collector.rpc-service.js +16 -0
  788. package/dist/services/log-collector.rpc-service.js.map +1 -1
  789. package/dist/services/log-collector.service.d.ts +172 -54
  790. package/dist/services/log-collector.service.d.ts.map +1 -1
  791. package/dist/services/log-collector.service.js +492 -102
  792. package/dist/services/log-collector.service.js.map +1 -1
  793. package/dist/services/log-retention.d.ts +50 -0
  794. package/dist/services/log-retention.d.ts.map +1 -0
  795. package/dist/services/log-retention.js +52 -0
  796. package/dist/services/log-retention.js.map +1 -0
  797. package/dist/services/maintenance-lock.d.ts +162 -0
  798. package/dist/services/maintenance-lock.d.ts.map +1 -0
  799. package/dist/services/maintenance-lock.js +316 -0
  800. package/dist/services/maintenance-lock.js.map +1 -0
  801. package/dist/services/master-address.d.ts +107 -0
  802. package/dist/services/master-address.d.ts.map +1 -0
  803. package/dist/services/master-address.js +154 -0
  804. package/dist/services/master-address.js.map +1 -0
  805. package/dist/services/migration-output.d.ts +16 -0
  806. package/dist/services/migration-output.d.ts.map +1 -0
  807. package/dist/services/migration-output.js +22 -0
  808. package/dist/services/migration-output.js.map +1 -0
  809. package/dist/services/netron-telemetry-transport.d.ts.map +1 -1
  810. package/dist/services/netron-telemetry-transport.js.map +1 -1
  811. package/dist/services/node-deploy-lease.d.ts +154 -0
  812. package/dist/services/node-deploy-lease.d.ts.map +1 -0
  813. package/dist/services/node-deploy-lease.js +386 -0
  814. package/dist/services/node-deploy-lease.js.map +1 -0
  815. package/dist/services/node-health.repository.d.ts +63 -4
  816. package/dist/services/node-health.repository.d.ts.map +1 -1
  817. package/dist/services/node-health.repository.js +108 -67
  818. package/dist/services/node-health.repository.js.map +1 -1
  819. package/dist/services/node-manager.rpc-service.d.ts +355 -10
  820. package/dist/services/node-manager.rpc-service.d.ts.map +1 -1
  821. package/dist/services/node-manager.rpc-service.js +730 -34
  822. package/dist/services/node-manager.rpc-service.js.map +1 -1
  823. package/dist/services/node-manager.service.d.ts +267 -95
  824. package/dist/services/node-manager.service.d.ts.map +1 -1
  825. package/dist/services/node-manager.service.js +805 -103
  826. package/dist/services/node-manager.service.js.map +1 -1
  827. package/dist/services/node-upgrade.d.ts +129 -0
  828. package/dist/services/node-upgrade.d.ts.map +1 -0
  829. package/dist/services/node-upgrade.js +161 -0
  830. package/dist/services/node-upgrade.js.map +1 -0
  831. package/dist/services/node-upgrade.service.d.ts +232 -0
  832. package/dist/services/node-upgrade.service.d.ts.map +1 -0
  833. package/dist/services/node-upgrade.service.js +468 -0
  834. package/dist/services/node-upgrade.service.js.map +1 -0
  835. package/dist/services/pipeline.rpc-service.d.ts +2 -1
  836. package/dist/services/pipeline.rpc-service.d.ts.map +1 -1
  837. package/dist/services/pipeline.rpc-service.js.map +1 -1
  838. package/dist/services/pipeline.service.d.ts +15 -46
  839. package/dist/services/pipeline.service.d.ts.map +1 -1
  840. package/dist/services/pipeline.service.js +72 -9
  841. package/dist/services/pipeline.service.js.map +1 -1
  842. package/dist/services/project.rpc-service.d.ts +133 -5
  843. package/dist/services/project.rpc-service.d.ts.map +1 -1
  844. package/dist/services/project.rpc-service.js +249 -22
  845. package/dist/services/project.rpc-service.js.map +1 -1
  846. package/dist/services/project.service.d.ts +677 -15
  847. package/dist/services/project.service.d.ts.map +1 -1
  848. package/dist/services/project.service.js +2798 -304
  849. package/dist/services/project.service.js.map +1 -1
  850. package/dist/services/redeploy-decision.d.ts +176 -0
  851. package/dist/services/redeploy-decision.d.ts.map +1 -0
  852. package/dist/services/redeploy-decision.js +142 -0
  853. package/dist/services/redeploy-decision.js.map +1 -0
  854. package/dist/services/release.rpc-service.d.ts +159 -0
  855. package/dist/services/release.rpc-service.d.ts.map +1 -0
  856. package/dist/services/release.rpc-service.js +395 -0
  857. package/dist/services/release.rpc-service.js.map +1 -0
  858. package/dist/services/release.service.d.ts +114 -0
  859. package/dist/services/release.service.d.ts.map +1 -0
  860. package/dist/services/release.service.js +208 -0
  861. package/dist/services/release.service.js.map +1 -0
  862. package/dist/services/remote-deployer.service.d.ts +569 -8
  863. package/dist/services/remote-deployer.service.d.ts.map +1 -1
  864. package/dist/services/remote-deployer.service.js +1618 -169
  865. package/dist/services/remote-deployer.service.js.map +1 -1
  866. package/dist/services/remote-ops.service.d.ts +51 -9
  867. package/dist/services/remote-ops.service.d.ts.map +1 -1
  868. package/dist/services/remote-ops.service.js +235 -39
  869. package/dist/services/remote-ops.service.js.map +1 -1
  870. package/dist/services/remote-provisioner.d.ts +122 -0
  871. package/dist/services/remote-provisioner.d.ts.map +1 -0
  872. package/dist/services/remote-provisioner.js +258 -0
  873. package/dist/services/remote-provisioner.js.map +1 -0
  874. package/dist/services/secrets.rpc-service.d.ts +51 -2
  875. package/dist/services/secrets.rpc-service.d.ts.map +1 -1
  876. package/dist/services/secrets.rpc-service.js +81 -2
  877. package/dist/services/secrets.rpc-service.js.map +1 -1
  878. package/dist/services/secrets.service.d.ts +108 -9
  879. package/dist/services/secrets.service.d.ts.map +1 -1
  880. package/dist/services/secrets.service.js +313 -48
  881. package/dist/services/secrets.service.js.map +1 -1
  882. package/dist/services/slave-storage.service.d.ts +107 -0
  883. package/dist/services/slave-storage.service.d.ts.map +1 -1
  884. package/dist/services/slave-storage.service.js +171 -2
  885. package/dist/services/slave-storage.service.js.map +1 -1
  886. package/dist/services/static-carry-forward.d.ts +85 -0
  887. package/dist/services/static-carry-forward.d.ts.map +1 -0
  888. package/dist/services/static-carry-forward.js +159 -0
  889. package/dist/services/static-carry-forward.js.map +1 -0
  890. package/dist/services/sync-policy.d.ts +167 -0
  891. package/dist/services/sync-policy.d.ts.map +1 -0
  892. package/dist/services/sync-policy.js +161 -0
  893. package/dist/services/sync-policy.js.map +1 -0
  894. package/dist/services/sync-summary.d.ts +39 -0
  895. package/dist/services/sync-summary.d.ts.map +1 -0
  896. package/dist/services/sync-summary.js +62 -0
  897. package/dist/services/sync-summary.js.map +1 -0
  898. package/dist/services/sync.rpc-service.d.ts +19 -7
  899. package/dist/services/sync.rpc-service.d.ts.map +1 -1
  900. package/dist/services/sync.rpc-service.js +36 -5
  901. package/dist/services/sync.rpc-service.js.map +1 -1
  902. package/dist/services/sync.service.d.ts +350 -18
  903. package/dist/services/sync.service.d.ts.map +1 -1
  904. package/dist/services/sync.service.js +864 -116
  905. package/dist/services/sync.service.js.map +1 -1
  906. package/dist/services/system-info.rpc-service.d.ts +2 -1
  907. package/dist/services/system-info.rpc-service.d.ts.map +1 -1
  908. package/dist/services/system-info.rpc-service.js.map +1 -1
  909. package/dist/services/system-info.service.d.ts +56 -68
  910. package/dist/services/system-info.service.d.ts.map +1 -1
  911. package/dist/services/system-info.service.js +240 -127
  912. package/dist/services/system-info.service.js.map +1 -1
  913. package/dist/services/telemetry.rpc-service.d.ts.map +1 -1
  914. package/dist/services/telemetry.rpc-service.js +9 -3
  915. package/dist/services/telemetry.rpc-service.js.map +1 -1
  916. package/dist/services/telemetry.service.d.ts +4 -2
  917. package/dist/services/telemetry.service.d.ts.map +1 -1
  918. package/dist/services/telemetry.service.js +4 -2
  919. package/dist/services/telemetry.service.js.map +1 -1
  920. package/dist/services/trace-collector.rpc-service.d.ts +2 -1
  921. package/dist/services/trace-collector.rpc-service.d.ts.map +1 -1
  922. package/dist/services/trace-collector.rpc-service.js.map +1 -1
  923. package/dist/services/trace-collector.service.d.ts +2 -41
  924. package/dist/services/trace-collector.service.d.ts.map +1 -1
  925. package/dist/services/trace-collector.service.js +27 -7
  926. package/dist/services/trace-collector.service.js.map +1 -1
  927. package/dist/services/wait-for-postgres.d.ts +28 -0
  928. package/dist/services/wait-for-postgres.d.ts.map +1 -0
  929. package/dist/services/wait-for-postgres.js +77 -0
  930. package/dist/services/wait-for-postgres.js.map +1 -0
  931. package/dist/shared/alert-expression.d.ts +60 -0
  932. package/dist/shared/alert-expression.d.ts.map +1 -0
  933. package/dist/shared/alert-expression.js +124 -0
  934. package/dist/shared/alert-expression.js.map +1 -0
  935. package/dist/shared/audit-outcome.d.ts +39 -0
  936. package/dist/shared/audit-outcome.d.ts.map +1 -0
  937. package/dist/shared/audit-outcome.js +45 -0
  938. package/dist/shared/audit-outcome.js.map +1 -0
  939. package/dist/shared/build-freshness.d.ts +59 -0
  940. package/dist/shared/build-freshness.d.ts.map +1 -0
  941. package/dist/shared/build-freshness.js +113 -0
  942. package/dist/shared/build-freshness.js.map +1 -0
  943. package/dist/shared/build-stamp.d.ts +26 -0
  944. package/dist/shared/build-stamp.d.ts.map +1 -0
  945. package/dist/shared/build-stamp.js +36 -0
  946. package/dist/shared/build-stamp.js.map +1 -0
  947. package/dist/shared/describe-error.d.ts +22 -0
  948. package/dist/shared/describe-error.d.ts.map +1 -0
  949. package/dist/shared/describe-error.js +39 -0
  950. package/dist/shared/describe-error.js.map +1 -0
  951. package/dist/shared/dto/alerts.d.ts +101 -0
  952. package/dist/shared/dto/alerts.d.ts.map +1 -0
  953. package/dist/shared/dto/alerts.js +9 -0
  954. package/dist/shared/dto/alerts.js.map +1 -0
  955. package/dist/shared/dto/auth.d.ts +49 -0
  956. package/dist/shared/dto/auth.d.ts.map +1 -0
  957. package/dist/shared/dto/auth.js +14 -0
  958. package/dist/shared/dto/auth.js.map +1 -0
  959. package/dist/shared/dto/backups.d.ts +16 -0
  960. package/dist/shared/dto/backups.d.ts.map +1 -0
  961. package/dist/shared/dto/backups.js +9 -0
  962. package/dist/shared/dto/backups.js.map +1 -0
  963. package/dist/shared/dto/deploy.d.ts +28 -0
  964. package/dist/shared/dto/deploy.d.ts.map +1 -0
  965. package/dist/shared/dto/deploy.js +8 -0
  966. package/dist/shared/dto/deploy.js.map +1 -0
  967. package/dist/shared/dto/discovery.d.ts +24 -0
  968. package/dist/shared/dto/discovery.d.ts.map +1 -0
  969. package/dist/shared/dto/discovery.js +9 -0
  970. package/dist/shared/dto/discovery.js.map +1 -0
  971. package/dist/shared/dto/fleet.d.ts +35 -0
  972. package/dist/shared/dto/fleet.d.ts.map +1 -0
  973. package/dist/shared/dto/fleet.js +8 -0
  974. package/dist/shared/dto/fleet.js.map +1 -0
  975. package/dist/shared/dto/health.d.ts +26 -0
  976. package/dist/shared/dto/health.d.ts.map +1 -0
  977. package/dist/shared/dto/health.js +9 -0
  978. package/dist/shared/dto/health.js.map +1 -0
  979. package/dist/shared/dto/kubernetes.d.ts +44 -0
  980. package/dist/shared/dto/kubernetes.d.ts.map +1 -0
  981. package/dist/shared/dto/kubernetes.js +9 -0
  982. package/dist/shared/dto/kubernetes.js.map +1 -0
  983. package/dist/shared/dto/logs.d.ts +77 -0
  984. package/dist/shared/dto/logs.d.ts.map +1 -0
  985. package/dist/shared/dto/logs.js +10 -0
  986. package/dist/shared/dto/logs.js.map +1 -0
  987. package/dist/shared/dto/metrics.d.ts +65 -0
  988. package/dist/shared/dto/metrics.d.ts.map +1 -0
  989. package/dist/shared/dto/metrics.js +17 -0
  990. package/dist/shared/dto/metrics.js.map +1 -0
  991. package/dist/shared/dto/nodes.d.ts +347 -0
  992. package/dist/shared/dto/nodes.d.ts.map +1 -0
  993. package/dist/shared/dto/nodes.js +8 -0
  994. package/dist/shared/dto/nodes.js.map +1 -0
  995. package/dist/shared/dto/pipelines.d.ts +53 -0
  996. package/dist/shared/dto/pipelines.d.ts.map +1 -0
  997. package/dist/shared/dto/pipelines.js +9 -0
  998. package/dist/shared/dto/pipelines.js.map +1 -0
  999. package/dist/shared/dto/project.d.ts +142 -8
  1000. package/dist/shared/dto/project.d.ts.map +1 -1
  1001. package/dist/shared/dto/services.d.ts +831 -7
  1002. package/dist/shared/dto/services.d.ts.map +1 -1
  1003. package/dist/shared/dto/system-info.d.ts +93 -0
  1004. package/dist/shared/dto/system-info.d.ts.map +1 -0
  1005. package/dist/shared/dto/system-info.js +9 -0
  1006. package/dist/shared/dto/system-info.js.map +1 -0
  1007. package/dist/shared/dto/traces.d.ts +49 -0
  1008. package/dist/shared/dto/traces.d.ts.map +1 -0
  1009. package/dist/shared/dto/traces.js +9 -0
  1010. package/dist/shared/dto/traces.js.map +1 -0
  1011. package/dist/shared/env-config.d.ts +74 -0
  1012. package/dist/shared/env-config.d.ts.map +1 -0
  1013. package/dist/shared/env-config.js +99 -0
  1014. package/dist/shared/env-config.js.map +1 -0
  1015. package/dist/shared/events.d.ts +45 -39
  1016. package/dist/shared/events.d.ts.map +1 -1
  1017. package/dist/shared/events.js +6 -0
  1018. package/dist/shared/events.js.map +1 -1
  1019. package/dist/shared/format.d.ts +1 -1
  1020. package/dist/shared/format.d.ts.map +1 -1
  1021. package/dist/shared/format.js +4 -0
  1022. package/dist/shared/format.js.map +1 -1
  1023. package/dist/shared/local-infra-host.d.ts +28 -0
  1024. package/dist/shared/local-infra-host.d.ts.map +1 -0
  1025. package/dist/shared/local-infra-host.js +28 -0
  1026. package/dist/shared/local-infra-host.js.map +1 -0
  1027. package/dist/shared/node-check.d.ts +37 -0
  1028. package/dist/shared/node-check.d.ts.map +1 -0
  1029. package/dist/shared/node-check.js +41 -0
  1030. package/dist/shared/node-check.js.map +1 -0
  1031. package/dist/shared/paths.d.ts +29 -0
  1032. package/dist/shared/paths.d.ts.map +1 -0
  1033. package/dist/shared/paths.js +54 -0
  1034. package/dist/shared/paths.js.map +1 -0
  1035. package/dist/shared/pnpm.d.ts +29 -0
  1036. package/dist/shared/pnpm.d.ts.map +1 -0
  1037. package/dist/shared/pnpm.js +57 -0
  1038. package/dist/shared/pnpm.js.map +1 -0
  1039. package/dist/shared/private-files.d.ts +29 -0
  1040. package/dist/shared/private-files.d.ts.map +1 -0
  1041. package/dist/shared/private-files.js +109 -0
  1042. package/dist/shared/private-files.js.map +1 -0
  1043. package/dist/shared/redact-env.d.ts +24 -0
  1044. package/dist/shared/redact-env.d.ts.map +1 -0
  1045. package/dist/shared/redact-env.js +36 -0
  1046. package/dist/shared/redact-env.js.map +1 -0
  1047. package/dist/shared/release-reading.d.ts +85 -0
  1048. package/dist/shared/release-reading.d.ts.map +1 -0
  1049. package/dist/shared/release-reading.js +81 -0
  1050. package/dist/shared/release-reading.js.map +1 -0
  1051. package/dist/shared/roles.d.ts +58 -3
  1052. package/dist/shared/roles.d.ts.map +1 -1
  1053. package/dist/shared/roles.js +55 -0
  1054. package/dist/shared/roles.js.map +1 -1
  1055. package/dist/shared/rpcauth.d.ts +13 -0
  1056. package/dist/shared/rpcauth.d.ts.map +1 -0
  1057. package/dist/shared/rpcauth.js +23 -0
  1058. package/dist/shared/rpcauth.js.map +1 -0
  1059. package/dist/shared/shell-escape.d.ts +9 -0
  1060. package/dist/shared/shell-escape.d.ts.map +1 -0
  1061. package/dist/shared/shell-escape.js +11 -0
  1062. package/dist/shared/shell-escape.js.map +1 -0
  1063. package/dist/shared/sync-reading.d.ts +45 -0
  1064. package/dist/shared/sync-reading.d.ts.map +1 -0
  1065. package/dist/shared/sync-reading.js +42 -0
  1066. package/dist/shared/sync-reading.js.map +1 -0
  1067. package/dist/shared/tokens.d.ts +19 -0
  1068. package/dist/shared/tokens.d.ts.map +1 -1
  1069. package/dist/shared/tokens.js +28 -0
  1070. package/dist/shared/tokens.js.map +1 -1
  1071. package/dist/webapp/webapp.service.d.ts +33 -2
  1072. package/dist/webapp/webapp.service.d.ts.map +1 -1
  1073. package/dist/webapp/webapp.service.js +143 -31
  1074. package/dist/webapp/webapp.service.js.map +1 -1
  1075. package/dist/workers/health-monitor-process.d.ts.map +1 -1
  1076. package/dist/workers/health-monitor-process.js +10 -0
  1077. package/dist/workers/health-monitor-process.js.map +1 -1
  1078. package/dist/workers/health-monitor.service.d.ts +45 -3
  1079. package/dist/workers/health-monitor.service.d.ts.map +1 -1
  1080. package/dist/workers/health-monitor.service.js +152 -25
  1081. package/dist/workers/health-monitor.service.js.map +1 -1
  1082. package/dist/workers/system-worker-manager.d.ts +43 -0
  1083. package/dist/workers/system-worker-manager.d.ts.map +1 -1
  1084. package/dist/workers/system-worker-manager.js +150 -3
  1085. package/dist/workers/system-worker-manager.js.map +1 -1
  1086. package/dist/workers/types.d.ts +8 -2
  1087. package/dist/workers/types.d.ts.map +1 -1
  1088. package/package.json +64 -30
  1089. package/webapp/dist/BUILD.json +5 -0
  1090. package/webapp/dist/assets/FormControlLabel-BrH2diyl.js +1 -0
  1091. package/webapp/dist/assets/Grid-BZ7xhoHQ.js +1 -0
  1092. package/webapp/dist/assets/Switch-DGSAxQPM.js +1 -0
  1093. package/webapp/dist/assets/alerts-sXqHJbsa.js +1 -0
  1094. package/webapp/dist/assets/apps-B_Ah8Flh.js +1 -0
  1095. package/webapp/dist/assets/audit-YXihwyui.js +1 -0
  1096. package/webapp/dist/assets/client-CGbBvy9k.js +1 -0
  1097. package/webapp/dist/assets/constants-veKGFkC6.js +1 -0
  1098. package/webapp/dist/assets/containers-Di4NOTLf.js +2 -0
  1099. package/webapp/dist/assets/daemon-memory-CCPZ4cV_.js +1 -0
  1100. package/webapp/dist/assets/dashboard-Cfuf95N3.js +1 -0
  1101. package/webapp/dist/assets/dashboard-builder-CowaYb-M.js +1 -0
  1102. package/webapp/dist/assets/deploy-release-dialog-D6C-XcKY.js +1 -0
  1103. package/webapp/dist/assets/deployments-ChTq5kCw.js +1 -0
  1104. package/webapp/dist/assets/detail-BsGWv7Dj.js +1 -0
  1105. package/webapp/dist/assets/detail-CyVf_cw5.js +2 -0
  1106. package/webapp/dist/assets/detail-D027j6aO.js +8 -0
  1107. package/webapp/dist/assets/formatters-BqjL73N3.js +1 -0
  1108. package/webapp/dist/assets/index-B8AHIPc-.css +1 -0
  1109. package/webapp/dist/assets/index-DiZbrwxT.js +414 -0
  1110. package/webapp/dist/assets/logs-L2b8Oqmo.js +12 -0
  1111. package/webapp/dist/assets/maplibre-gl-MlmG2kbn.js +801 -0
  1112. package/webapp/dist/assets/metrics-CQgjz2Lz.js +2 -0
  1113. package/webapp/dist/assets/nodes-Nwia2Ty6.js +7 -0
  1114. package/webapp/dist/assets/pipelines-DCu_rXOr.js +4 -0
  1115. package/webapp/dist/assets/projects-Bb4aV76h.js +1 -0
  1116. package/webapp/dist/assets/react-CwJFpaho.js +1 -0
  1117. package/webapp/dist/assets/react-apexcharts.esm-Dy0Fez42.js +1956 -0
  1118. package/webapp/dist/assets/release-bits-Dj4EOhhb.js +1 -0
  1119. package/webapp/dist/assets/release-build-panel-D6OCVlhT.js +1 -0
  1120. package/webapp/dist/assets/releases-CnAt3yO6.js +2 -0
  1121. package/webapp/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
  1122. package/webapp/dist/assets/settings-DU-mjiwb.js +1 -0
  1123. package/webapp/dist/assets/settled-pair-C7SjZuns.js +1 -0
  1124. package/webapp/dist/assets/sign-in-Bcvjrr4C.js +1 -0
  1125. package/webapp/dist/assets/stacks-B-e8p50I.js +1 -0
  1126. package/webapp/dist/assets/sync-reading-C3PnJ7AX.js +1 -0
  1127. package/webapp/dist/assets/system-info-BtE3dgfw.js +1 -0
  1128. package/webapp/dist/assets/topology-Cwch-uF1.js +12 -0
  1129. package/webapp/dist/assets/topology-DLioOiRN.css +1 -0
  1130. package/webapp/dist/assets/traces-BbShR-4P.js +1 -0
  1131. package/webapp/dist/assets/use-stack-context-CZUgPq88.js +1 -0
  1132. package/webapp/dist/index.html +6 -6
  1133. package/dist/commands/dev.d.ts +0 -23
  1134. package/dist/commands/dev.d.ts.map +0 -1
  1135. package/dist/commands/dev.js +0 -76
  1136. package/dist/commands/dev.js.map +0 -1
  1137. package/dist/config/define-app.d.ts +0 -6
  1138. package/dist/config/define-app.d.ts.map +0 -1
  1139. package/dist/config/define-app.js +0 -38
  1140. package/dist/config/define-app.js.map +0 -1
  1141. package/dist/infrastructure/gateway-generator.d.ts +0 -63
  1142. package/dist/infrastructure/gateway-generator.d.ts.map +0 -1
  1143. package/dist/infrastructure/gateway-generator.js +0 -253
  1144. package/dist/infrastructure/gateway-generator.js.map +0 -1
  1145. package/dist/monitoring/health.service.d.ts +0 -14
  1146. package/dist/monitoring/health.service.d.ts.map +0 -1
  1147. package/dist/monitoring/health.service.js +0 -50
  1148. package/dist/monitoring/health.service.js.map +0 -1
  1149. package/dist/monitoring/metrics.service.d.ts +0 -14
  1150. package/dist/monitoring/metrics.service.d.ts.map +0 -1
  1151. package/dist/monitoring/metrics.service.js +0 -40
  1152. package/dist/monitoring/metrics.service.js.map +0 -1
  1153. package/dist/orchestrator/bootstrap-runner.d.ts +0 -15
  1154. package/dist/orchestrator/bootstrap-runner.d.ts.map +0 -1
  1155. package/dist/orchestrator/bootstrap-runner.js +0 -152
  1156. package/dist/orchestrator/bootstrap-runner.js.map +0 -1
  1157. package/dist/orchestrator/topology-proxy.d.ts +0 -65
  1158. package/dist/orchestrator/topology-proxy.d.ts.map +0 -1
  1159. package/dist/orchestrator/topology-proxy.js +0 -209
  1160. package/dist/orchestrator/topology-proxy.js.map +0 -1
  1161. package/dist/orchestrator/topology-relay.d.ts +0 -111
  1162. package/dist/orchestrator/topology-relay.d.ts.map +0 -1
  1163. package/dist/orchestrator/topology-relay.js +0 -255
  1164. package/dist/orchestrator/topology-relay.js.map +0 -1
  1165. package/dist/services/auth-middleware.d.ts +0 -47
  1166. package/dist/services/auth-middleware.d.ts.map +0 -1
  1167. package/dist/services/auth-middleware.js +0 -158
  1168. package/dist/services/auth-middleware.js.map +0 -1
  1169. package/dist/services/environment.rpc-service.d.ts +0 -22
  1170. package/dist/services/environment.rpc-service.d.ts.map +0 -1
  1171. package/dist/services/environment.rpc-service.js +0 -65
  1172. package/dist/services/environment.rpc-service.js.map +0 -1
  1173. package/dist/services/environment.service.d.ts +0 -87
  1174. package/dist/services/environment.service.d.ts.map +0 -1
  1175. package/dist/services/environment.service.js +0 -220
  1176. package/dist/services/environment.service.js.map +0 -1
  1177. package/dist/services/metrics-collector.service.d.ts +0 -103
  1178. package/dist/services/metrics-collector.service.d.ts.map +0 -1
  1179. package/dist/services/metrics-collector.service.js +0 -235
  1180. package/dist/services/metrics-collector.service.js.map +0 -1
  1181. package/dist/services/metrics.rpc-service.d.ts +0 -41
  1182. package/dist/services/metrics.rpc-service.d.ts.map +0 -1
  1183. package/dist/services/metrics.rpc-service.js +0 -90
  1184. package/dist/services/metrics.rpc-service.js.map +0 -1
  1185. package/dist/services/metrics.service.d.ts +0 -72
  1186. package/dist/services/metrics.service.d.ts.map +0 -1
  1187. package/dist/services/metrics.service.js +0 -185
  1188. package/dist/services/metrics.service.js.map +0 -1
  1189. package/dist/supervisor/omnitron-supervisor.d.ts +0 -21
  1190. package/dist/supervisor/omnitron-supervisor.d.ts.map +0 -1
  1191. package/dist/supervisor/omnitron-supervisor.js +0 -43
  1192. package/dist/supervisor/omnitron-supervisor.js.map +0 -1
  1193. package/dist/webapp/static-server.d.ts +0 -24
  1194. package/dist/webapp/static-server.d.ts.map +0 -1
  1195. package/dist/webapp/static-server.js +0 -152
  1196. package/dist/webapp/static-server.js.map +0 -1
  1197. package/webapp/dist/assets/Badge-D2ZBP5iK.js +0 -1
  1198. package/webapp/dist/assets/Card-vO88g06B.js +0 -1
  1199. package/webapp/dist/assets/CardActions-C9h7Q2wW.js +0 -1
  1200. package/webapp/dist/assets/CardContent-BA_Xmmhk.js +0 -1
  1201. package/webapp/dist/assets/CardHeader-ceIs__6x.js +0 -1
  1202. package/webapp/dist/assets/Chip-Ckk0xUhK.js +0 -1
  1203. package/webapp/dist/assets/DialogActions-BhHM7hov.js +0 -1
  1204. package/webapp/dist/assets/DialogContentText-BT2JQRmK.js +0 -1
  1205. package/webapp/dist/assets/FormControlLabel-CcQByVS8.js +0 -1
  1206. package/webapp/dist/assets/Grid-14PETRQW.js +0 -1
  1207. package/webapp/dist/assets/InputAdornment-B8aHIsQ7.js +0 -1
  1208. package/webapp/dist/assets/Skeleton-B74-Ds6O.js +0 -37
  1209. package/webapp/dist/assets/Switch-DvWD2wh4.js +0 -1
  1210. package/webapp/dist/assets/SwitchBase-CDU4nY8O.js +0 -1
  1211. package/webapp/dist/assets/Tab-DcCOYYp5.js +0 -1
  1212. package/webapp/dist/assets/TableContainer-CzTh4mu-.js +0 -1
  1213. package/webapp/dist/assets/TableRow-BySEreZR.js +0 -2
  1214. package/webapp/dist/assets/TextField-BLf-BHz2.js +0 -1
  1215. package/webapp/dist/assets/alerts-DnRbT41C.js +0 -1
  1216. package/webapp/dist/assets/apps-CdR0VH2d.js +0 -1
  1217. package/webapp/dist/assets/chunk-DtcAfS_W.js +0 -1
  1218. package/webapp/dist/assets/client-DU8I_lDc.js +0 -2
  1219. package/webapp/dist/assets/constants-BgnT-Xdy.js +0 -1
  1220. package/webapp/dist/assets/containers-Dw-rRXV1.js +0 -1
  1221. package/webapp/dist/assets/dashboard-CzgB0uZz.js +0 -1
  1222. package/webapp/dist/assets/dashboard-builder-DsA0yg33.js +0 -1
  1223. package/webapp/dist/assets/deployments-DtQAoIcQ.js +0 -1
  1224. package/webapp/dist/assets/detail-BJR-G7JG.js +0 -1
  1225. package/webapp/dist/assets/detail-nlCeSltl.js +0 -8
  1226. package/webapp/dist/assets/formatters-Do41SRmE.js +0 -1
  1227. package/webapp/dist/assets/index-ByQDwp2r.js +0 -566
  1228. package/webapp/dist/assets/index-CHVXOsTS.css +0 -1
  1229. package/webapp/dist/assets/isMuiElement-DwF2naaF.js +0 -1
  1230. package/webapp/dist/assets/logs-Kn8o4H7k.js +0 -11
  1231. package/webapp/dist/assets/metrics-DN8Zu5wT.js +0 -2
  1232. package/webapp/dist/assets/nodes-Bgch7AtN.js +0 -7
  1233. package/webapp/dist/assets/pipelines-Cry3qm-S.js +0 -4
  1234. package/webapp/dist/assets/popper-BJFSj6rc.js +0 -1
  1235. package/webapp/dist/assets/preload-helper-DSXbuxSR.js +0 -1
  1236. package/webapp/dist/assets/projects-Dym2GI1U.js +0 -1
  1237. package/webapp/dist/assets/react-apexcharts.esm-CSGK5Cxn.js +0 -958
  1238. package/webapp/dist/assets/react-apexcharts.esm-DEAWOwx5.js +0 -1003
  1239. package/webapp/dist/assets/settings-va3zTcow.js +0 -1
  1240. package/webapp/dist/assets/sign-in-ik35umbp.js +0 -1
  1241. package/webapp/dist/assets/stacks-CVFOnxbF.js +0 -1
  1242. package/webapp/dist/assets/system-info-npIXAjX2.js +0 -1
  1243. package/webapp/dist/assets/topology-CHpVij2M.css +0 -1
  1244. package/webapp/dist/assets/topology-bDe70vLs.js +0 -12
  1245. package/webapp/dist/assets/traces-DIIcT8DF.js +0 -1
  1246. package/webapp/dist/assets/use-stack-context-DHLwZv1r.js +0 -1
  1247. /package/webapp/dist/assets/{fs-CgZLtHiQ.js → fs-Ccl3sAcp.js} +0 -0
@@ -12,33 +12,409 @@
12
12
  * - Scheduled automated backups (cron expressions)
13
13
  * - Automatic cleanup of old backups
14
14
  */
15
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
16
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
17
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
18
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
19
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
20
+ };
21
+ var __metadata = (this && this.__metadata) || function (k, v) {
22
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
23
+ };
24
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
25
+ return function (target, key) { decorator(target, key, paramIndex); }
26
+ };
27
+ var BackupService_1;
15
28
  import { randomUUID } from 'node:crypto';
16
29
  import fs from 'node:fs';
17
30
  import path from 'node:path';
31
+ import { Injectable, Inject, Optional } from '@omnitron-dev/titan/decorators';
32
+ import { LOGGER_SERVICE_TOKEN } from '@omnitron-dev/titan/module/logger';
33
+ import { DAEMON_STATE_STORE_TOKEN, PROJECT_SERVICE_TOKEN } from '../shared/tokens.js';
34
+ import { expandPath } from '../shared/paths.js';
35
+ import { getEnv } from '../shared/env-config.js';
36
+ import { resolveOmnitronPgConfig } from '../database/connection.js';
37
+ import { ensurePrivateDir, sealFile, sealDirContents } from '../shared/private-files.js';
38
+ import { dumpToFile, restoreFromFile, formatBackupSize, formatUtc, resolveBackupId } from './backup-pipeline.js';
39
+ import { parseSchedule, nextCronDelay, describeSchedule, } from './backup-schedule.js';
40
+ /**
41
+ * A schedule's first run after a start is never sooner than this — the
42
+ * stacks whose databases it dumps are still coming up — and every further
43
+ * schedule armed by the same start waits one `FIRST_RUN_STAGGER_MS` more, so
44
+ * two overdue passes do not begin in the same second.
45
+ */
46
+ const FIRST_RUN_GRACE_MS = 60_000;
47
+ const FIRST_RUN_STAGGER_MS = 60_000;
48
+ /**
49
+ * After a pass that backed up nothing, the next attempt comes this soon (or
50
+ * at the interval, when that is sooner). Running overdue passes at start is
51
+ * what makes this necessary: a daily pass that finds its stacks not yet up
52
+ * would otherwise wait a whole day to try again.
53
+ */
54
+ const RETRY_AFTER_NOTHING_MS = 15 * 60_000;
55
+ /** The daemon's own control-plane database: the audit log, deployments, runs. */
56
+ const CONTROL_PLANE_DB = 'omnitron';
57
+ /** A failure's own words, without the "Backup failed for 'x':" every wrapper adds. */
58
+ function reasonOf(err) {
59
+ const e = err;
60
+ const inner = e?.cause instanceof Error ? e.cause.message : undefined;
61
+ return (inner ?? e?.message ?? String(err)).slice(0, 500);
62
+ }
63
+ /**
64
+ * The outcome of a pass, from its entries. No entries is `empty`, never `ok`:
65
+ * «0 of 0 backed up» is a pass that protected nothing.
66
+ */
67
+ export function passOutcome(entries) {
68
+ const attempted = entries.filter((e) => !e.skipped);
69
+ if (attempted.length === 0)
70
+ return 'empty';
71
+ const ok = attempted.filter((e) => e.ok).length;
72
+ if (ok === attempted.length)
73
+ return 'ok';
74
+ return ok === 0 ? 'failed' : 'partial';
75
+ }
76
+ function metadataOf(raw) {
77
+ if (!raw)
78
+ return {};
79
+ try {
80
+ return JSON.parse(raw);
81
+ }
82
+ catch {
83
+ return {};
84
+ }
85
+ }
86
+ /** A pass entry named so that two stacks' `main` cannot be taken for each other. */
87
+ function qualifiedEntry(e) {
88
+ const { project, stack, ...rest } = e;
89
+ return project && stack && !e.skipped ? { ...rest, target: `${project}/${stack}/${e.target}` } : rest;
90
+ }
91
+ /** The index/listing fields that say where a backup came from. */
92
+ function originFields(origin) {
93
+ return origin === 'control-plane'
94
+ ? { scope: 'control-plane' }
95
+ : { project: origin.project, stack: origin.stack, scope: 'stack' };
96
+ }
97
+ /**
98
+ * Non-database artefacts that only a `full` pass produces, and therefore
99
+ * only a `full` pass used to prune.
100
+ */
101
+ const FULL_BACKUP_ARTEFACTS = ['storage-objects', 'tor-keys', 'daemon-state'];
102
+ /**
103
+ * Tor state that is re-fetched from the network, excluded from the key backup.
104
+ *
105
+ * These are the directory cache and its scratch files. They are identical
106
+ * across every Tor client, replaced on a schedule, and useless in a restore —
107
+ * a restored Tor re-downloads them before it does anything else.
108
+ */
109
+ const TOR_REGENERABLE_STATE = [
110
+ './cached-*',
111
+ './diff-cache',
112
+ './unverified-*',
113
+ './lock',
114
+ ];
115
+ /** SQLite leaves these beside a database it opens (`-journal` in rollback mode). */
116
+ const SQLITE_SIDECARS = ['-shm', '-wal', '-journal'];
117
+ /** Scratch directories `createStorageBackup` and `restoreStorageBackup` remove in a `finally`. */
118
+ const STORAGE_STAGING_DIR = /^\.storage-(stage|restore)-[0-9a-f]{8}$/;
119
+ /** The database file a SQLite sidecar belongs to, or null for any other file. */
120
+ function sidecarOwner(name) {
121
+ for (const suffix of SQLITE_SIDECARS) {
122
+ if (name.endsWith(suffix))
123
+ return name.slice(0, -suffix.length);
124
+ }
125
+ return null;
126
+ }
127
+ function bytesUnder(target) {
128
+ try {
129
+ const stat = fs.lstatSync(target);
130
+ if (!stat.isDirectory())
131
+ return stat.size;
132
+ let total = 0;
133
+ for (const child of fs.readdirSync(target))
134
+ total += bytesUnder(path.join(target, child));
135
+ return total;
136
+ }
137
+ catch {
138
+ return 0;
139
+ }
140
+ }
141
+ /**
142
+ * Remove what an interrupted pass leaves in the backup directory.
143
+ *
144
+ * Measured on the master, 2026-09-23: 28 files, 79 300 253 B (75.6 MiB), that
145
+ * no index row names — so `backup list` never showed them and retention never
146
+ * pruned them:
147
+ *
148
+ * 23 zero-byte files 22 `tor-keys`, 1 `storage` (July–September)
149
+ * 2 `main_…sql.gz.partial` 7 150 144 B and 70 477 299 B — the daemon
150
+ * restarted mid-dump at 13:15:43Z and
151
+ * 21:15:47Z on 09-21, and no catch of ours
152
+ * runs in a process that is gone
153
+ * 3 truncated archives the ones named on `execToFile`
154
+ *
155
+ * A `.partial` is by construction not a backup (`dumpToFile`, `execToFile`
156
+ * write under it and rename only a finished file), and a zero-byte file
157
+ * restores nothing, so both go. Nothing is in flight when this runs: it is
158
+ * called from the constructor, once, before any schedule is armed.
159
+ *
160
+ * The SQLite sidecars are more careful, because something may have a
161
+ * database open. Measured: 102 of them (51 `-shm` of 32 768 B, 51 `-wal` of
162
+ * 0 B). Apple's `sqlite3` keeps both files after it closes a WAL database, and
163
+ * a `daemon-state_*.db` backup IS one — `.backup` copies the WAL flag in the
164
+ * header from the live store. So every open of a backup left a pair: 3 pairs
165
+ * `….db.partial-shm/-wal` from our own integrity check, which ran on the
166
+ * staging name and then renamed the file away from them; 4 pairs whose `.db`
167
+ * retention had deleted, because `deleteBackup` removed only the named file;
168
+ * and 44 pairs beside backups that still exist, written by bulk opens at
169
+ * 2026-09-11 23:50 and 09-14 15:27 local — not by this code.
170
+ *
171
+ * Only an ORPHAN pair is removed — its database is gone, so nothing can open
172
+ * it — and only while its WAL holds no frames. A pair beside a live database
173
+ * is counted and kept: that one is not provably idle.
174
+ */
175
+ export function sweepBackupLeftovers(dir) {
176
+ const report = { removed: [], besideBackups: { files: 0, bytes: 0 }, keptOrphans: [] };
177
+ let entries;
178
+ try {
179
+ entries = fs.readdirSync(dir, { withFileTypes: true });
180
+ }
181
+ catch {
182
+ return report;
183
+ }
184
+ const present = new Set(entries.map((e) => e.name));
185
+ const sizeOf = (name) => {
186
+ try {
187
+ return fs.statSync(path.join(dir, name)).size;
188
+ }
189
+ catch {
190
+ return 0;
191
+ }
192
+ };
193
+ const remove = (name, reason) => {
194
+ const target = path.join(dir, name);
195
+ const bytes = bytesUnder(target);
196
+ try {
197
+ fs.rmSync(target, { recursive: true, force: true });
198
+ }
199
+ catch {
200
+ return; // not ours to remove — it stays, and stays out of the count
201
+ }
202
+ present.delete(name);
203
+ report.removed.push({ name, bytes, reason });
204
+ };
205
+ // Files first: removing a `.partial` is what orphans the sidecars beside it.
206
+ for (const e of entries) {
207
+ if (e.isDirectory()) {
208
+ if (STORAGE_STAGING_DIR.test(e.name))
209
+ remove(e.name, 'the scratch directory of an interrupted storage pass');
210
+ continue;
211
+ }
212
+ if (!e.isFile() || sidecarOwner(e.name) !== null)
213
+ continue;
214
+ if (e.name.endsWith('.partial'))
215
+ remove(e.name, 'an interrupted dump');
216
+ else if (sizeOf(e.name) === 0)
217
+ remove(e.name, 'empty');
218
+ }
219
+ for (const e of entries) {
220
+ if (!e.isFile())
221
+ continue;
222
+ const owner = sidecarOwner(e.name);
223
+ if (owner === null)
224
+ continue;
225
+ if (present.has(owner)) {
226
+ report.besideBackups.files++;
227
+ report.besideBackups.bytes += sizeOf(e.name);
228
+ continue;
229
+ }
230
+ if (present.has(`${owner}-wal`) && sizeOf(`${owner}-wal`) > 0) {
231
+ report.keptOrphans.push(e.name);
232
+ continue;
233
+ }
234
+ remove(e.name, 'the SQLite sidecar of a database that is gone');
235
+ }
236
+ return report;
237
+ }
238
+ /**
239
+ * Which databases a sweep should bound, given what is scheduled and what
240
+ * actually has backups.
241
+ *
242
+ * Split out because the choice is the whole point: pruning only the
243
+ * scheduled set means retention runs where the producer still runs and
244
+ * nowhere else, so anything dropped from the configuration keeps its history
245
+ * for ever.
246
+ */
247
+ export function databasesToPrune(scheduled, onDisk) {
248
+ return [...new Set([...scheduled, ...onDisk, ...FULL_BACKUP_ARTEFACTS])];
249
+ }
18
250
  // =============================================================================
19
251
  // Service
20
252
  // =============================================================================
21
- export class BackupService {
22
- logger;
253
+ let BackupService = class BackupService {
254
+ static { BackupService_1 = this; }
255
+ store;
256
+ projects;
257
+ static SCHEDULES_KV_KEY = 'backup:schedules';
258
+ /** Target → the last pass it ran. Beside the schedules, in the same store. */
259
+ static PASSES_KV_KEY = 'backup:last-pass';
23
260
  backupDir;
261
+ logger;
24
262
  schedules = new Map();
25
- constructor(logger, backupDir) {
26
- this.logger = logger;
27
- this.backupDir = backupDir ?? path.join(process.env['HOME'] ?? '/tmp', '.omnitron', 'backups');
28
- fs.mkdirSync(this.backupDir, { recursive: true });
263
+ /**
264
+ * True once the legacy .meta.json scan + import has run for this
265
+ * process. Subsequent list/restore calls skip the directory walk.
266
+ */
267
+ legacyMigrated = false;
268
+ /** The tail of the scheduled passes, which run one at a time — see `runExclusive`. */
269
+ passQueue = Promise.resolve();
270
+ // T-2 — @Inject decorators bind tokens to ctor positions; framework
271
+ // reads metadata directly, no inject:[] array drift possible.
272
+ constructor(loggerModule,
273
+ /**
274
+ * T-7 — backup metadata persistence moved off side-car .meta.json
275
+ * files onto the SQLite-backed `backups` table in
276
+ * DaemonStateStore. The .sql.gz bytes themselves stay on disk
277
+ * (large + streaming pg_restore needs random access), but the
278
+ * INDEX of `which backup exists, for which app, when, how big`
279
+ * lives in SQLite — atomic upserts, no torn-write window on
280
+ * kill -9 mid-write. One-shot migration of legacy .meta.json
281
+ * files happens on the first list/restore call.
282
+ */
283
+ store,
284
+ /**
285
+ * Optional so the daemon still boots (and omnitron-pg self-backup still
286
+ * works) even if the project subsystem is unavailable. When present it is
287
+ * the source of truth for the running stacks' real DB topology, replacing
288
+ * the old hardcoded omnitron-pg-only mappings.
289
+ */
290
+ projects) {
291
+ this.store = store;
292
+ this.projects = projects;
293
+ this.logger = loggerModule.logger;
294
+ this.backupDir = expandPath('~/.omnitron/backups');
295
+ // 0700 on the directory, 0600 on every file in it — see `private-files`.
296
+ // The sweep reaches backwards on purpose: the files written before this
297
+ // existed are the ones that have been world-readable the longest, and a
298
+ // fix that only protects future backups protects nothing that matters.
299
+ ensurePrivateDir(this.backupDir);
300
+ const tightened = sealDirContents(this.backupDir);
301
+ if (tightened > 0) {
302
+ this.logger.warn({ dir: this.backupDir, files: tightened }, 'Backup files were readable beyond their owner — permissions tightened');
303
+ }
304
+ this.clearLeftovers();
305
+ }
306
+ /**
307
+ * Once per start: remove what interrupted passes left behind, and name what
308
+ * is still on disk without an index row. See `sweepBackupLeftovers`.
309
+ */
310
+ clearLeftovers() {
311
+ const swept = sweepBackupLeftovers(this.backupDir);
312
+ if (swept.removed.length > 0) {
313
+ const bytes = swept.removed.reduce((sum, r) => sum + r.bytes, 0);
314
+ this.logger.warn({
315
+ dir: this.backupDir,
316
+ files: swept.removed.length,
317
+ bytes,
318
+ removed: swept.removed.map((r) => `${r.name} (${r.reason}, ${r.bytes} B)`),
319
+ }, `Removed ${swept.removed.length} leftover file(s) from the backup directory, ${formatBackupSize(bytes)}`);
320
+ }
321
+ if (swept.keptOrphans.length > 0) {
322
+ this.logger.warn({ dir: this.backupDir, files: swept.keptOrphans }, 'SQLite sidecars of a deleted database still hold WAL frames — left in place');
323
+ }
324
+ if (swept.besideBackups.files > 0) {
325
+ this.logger.info({ dir: this.backupDir, files: swept.besideBackups.files, bytes: swept.besideBackups.bytes }, 'SQLite sidecars sit beside backups that still exist — left in place; retention removes them with their database');
326
+ }
327
+ // What is left and still in no index: a file restore cannot name, no
328
+ // listing shows and retention never prunes. Not removed — a file with
329
+ // content may be a good backup whose row was lost — but not silent either.
330
+ try {
331
+ this.migrateLegacyMetaIfPresent();
332
+ const indexed = new Set(this.store.selectBackupsSync().map((r) => path.basename(r.path)));
333
+ const stray = [];
334
+ for (const e of fs.readdirSync(this.backupDir, { withFileTypes: true })) {
335
+ if (!e.isFile() || indexed.has(e.name))
336
+ continue;
337
+ if (sidecarOwner(e.name) !== null || e.name.endsWith('.meta.json'))
338
+ continue;
339
+ stray.push({ name: e.name, bytes: bytesUnder(path.join(this.backupDir, e.name)) });
340
+ }
341
+ if (stray.length > 0) {
342
+ const bytes = stray.reduce((sum, s) => sum + s.bytes, 0);
343
+ this.logger.warn({ dir: this.backupDir, files: stray.length, bytes, names: stray.map((s) => `${s.name} (${s.bytes} B)`) }, `${stray.length} file(s) in the backup directory are in no index (${formatBackupSize(bytes)}) — ` +
344
+ 'not listed, not restorable by id, never pruned');
345
+ }
346
+ }
347
+ catch (err) {
348
+ this.logger.warn({ err: err.message }, 'Could not compare the backup directory with its index');
349
+ }
350
+ }
351
+ /**
352
+ * Lazy one-shot import of legacy .meta.json side-cars into the
353
+ * SQLite `backups` table. Idempotent — flag-guarded so multiple
354
+ * concurrent listBackups() calls don't double-import. After this
355
+ * runs the .meta.json files are unlinked (the SQLite row is
356
+ * authoritative).
357
+ */
358
+ migrateLegacyMetaIfPresent() {
359
+ if (this.legacyMigrated)
360
+ return;
361
+ this.legacyMigrated = true;
362
+ try {
363
+ const files = fs.readdirSync(this.backupDir).filter((f) => f.endsWith('.meta.json'));
364
+ for (const file of files) {
365
+ try {
366
+ const content = fs.readFileSync(path.join(this.backupDir, file), 'utf-8');
367
+ const info = JSON.parse(content);
368
+ const backupFile = path.join(this.backupDir, info.filename);
369
+ if (!fs.existsSync(backupFile))
370
+ continue;
371
+ const stats = fs.statSync(backupFile);
372
+ // upsert isn't available; insert is — but the migration
373
+ // can run multiple times across crashes, so swallow
374
+ // duplicate-id errors (better-sqlite3 throws on UNIQUE).
375
+ try {
376
+ this.store.insertBackupSync({
377
+ id: info.id,
378
+ app: info.database,
379
+ path: backupFile,
380
+ size_bytes: stats.size,
381
+ created_at: info.createdAt,
382
+ metadata: { filename: info.filename, compressed: info.compressed },
383
+ });
384
+ }
385
+ catch {
386
+ // Already imported — skip.
387
+ }
388
+ try {
389
+ fs.unlinkSync(path.join(this.backupDir, file));
390
+ }
391
+ catch { /* */ }
392
+ }
393
+ catch (err) {
394
+ this.logger.warn({ file, err: err.message }, 'Backup metadata migration: skipping corrupt entry');
395
+ }
396
+ }
397
+ }
398
+ catch {
399
+ // backup dir doesn't exist or unreadable — nothing to migrate.
400
+ }
29
401
  }
30
402
  // ===========================================================================
31
403
  // Create backup
32
404
  // ===========================================================================
33
405
  async createBackup(database, options) {
34
406
  const compress = options?.compress !== false;
407
+ // Resolved before the file is named: the name is the database's, never
408
+ // the `<project>/<stack>/<db>` key, whose slashes would be directories.
409
+ const { dbConfig, isDocker, containerName, origin } = this.resolveDbTarget(database);
410
+ const name = origin === 'control-plane' ? CONTROL_PLANE_DB : dbConfig.database;
411
+ const from = originFields(origin);
35
412
  const id = randomUUID();
36
413
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
37
414
  const ext = compress ? '.sql.gz' : '.sql';
38
- const filename = `${database}_${timestamp}_${id.slice(0, 8)}${ext}`;
415
+ const filename = `${name}_${timestamp}_${id.slice(0, 8)}${ext}`;
39
416
  const filepath = path.join(this.backupDir, filename);
40
- this.logger.info({ database, filename, compress }, 'Creating backup');
41
- const { dbConfig, isDocker, containerName } = this.resolveDbConfig(database);
417
+ this.logger.info({ database: name, ...from, filename, compress }, 'Creating backup');
42
418
  try {
43
419
  if (isDocker) {
44
420
  await this.pgDumpDocker(containerName, dbConfig, filepath, compress);
@@ -46,18 +422,33 @@ export class BackupService {
46
422
  else {
47
423
  await this.pgDumpLocal(dbConfig, filepath, compress);
48
424
  }
425
+ // Before it is indexed, and therefore before anything else can learn
426
+ // the path: a pg_dump redirected into a file lands at whatever the
427
+ // umask says, which for a normal login shell is 0644.
428
+ sealFile(filepath);
49
429
  const stats = fs.statSync(filepath);
50
430
  const info = {
51
431
  id,
52
- database,
432
+ database: name,
53
433
  filename,
54
434
  size: stats.size,
55
435
  createdAt: new Date().toISOString(),
56
436
  compressed: compress,
437
+ ...from,
57
438
  };
58
- // Write metadata
59
- fs.writeFileSync(path.join(this.backupDir, `${filename}.meta.json`), JSON.stringify(info, null, 2));
60
- this.logger.info({ database, filename, size: stats.size }, 'Backup created');
439
+ // Persist metadata transactionally to SQLite. Pre-T-7 this
440
+ // was a side-car .meta.json fs.writeFileSync — torn-write
441
+ // risk if the daemon was SIGKILL'd between the dump and the
442
+ // meta write. The stack travels with the row — see `BackupOrigin`.
443
+ this.store.insertBackupSync({
444
+ id: info.id,
445
+ app: name,
446
+ path: filepath,
447
+ size_bytes: stats.size,
448
+ created_at: info.createdAt,
449
+ metadata: { filename: info.filename, compressed: info.compressed, type: 'postgres', ...from },
450
+ });
451
+ this.logger.info({ database: name, ...from, filename, size: stats.size }, 'Backup created');
61
452
  return info;
62
453
  }
63
454
  catch (err) {
@@ -70,49 +461,488 @@ export class BackupService {
70
461
  }
71
462
  }
72
463
  // ===========================================================================
464
+ // Non-DB backup targets: storage objects, tor keys, daemon-state (secrets)
465
+ // ===========================================================================
466
+ /** First running stack's provisioned InfrastructureService (for minio/tor). */
467
+ getRunningInfra() {
468
+ const projects = this.projects;
469
+ if (!projects)
470
+ return null;
471
+ try {
472
+ const mgr = projects.getInfraManager();
473
+ for (const p of projects.listProjects()) {
474
+ let stacks = [];
475
+ try {
476
+ stacks = projects.getRunningStacks(p.name);
477
+ }
478
+ catch {
479
+ continue;
480
+ }
481
+ for (const stack of stacks) {
482
+ const infra = mgr.getInstance(p.name, stack);
483
+ if (infra)
484
+ return { infra, project: p.name, stack };
485
+ }
486
+ }
487
+ }
488
+ catch { /* no running stack */ }
489
+ return null;
490
+ }
491
+ /** Index a already-written backup file into the SQLite backups table. */
492
+ indexBackupFile(app, filepath, type, origin) {
493
+ // Every non-DB target — storage objects, tor keys, daemon-state — finishes
494
+ // here, which makes this the one place their mode can be set once.
495
+ sealFile(filepath);
496
+ const stats = fs.statSync(filepath);
497
+ const id = randomUUID();
498
+ const createdAt = new Date().toISOString();
499
+ const filename = path.basename(filepath);
500
+ const from = originFields(origin);
501
+ this.store.insertBackupSync({
502
+ id, app, path: filepath, size_bytes: stats.size, created_at: createdAt,
503
+ metadata: { filename, compressed: filename.endsWith('.gz'), type, ...from },
504
+ });
505
+ return { id, database: app, filename, size: stats.size, createdAt, compressed: filename.endsWith('.gz'), ...from };
506
+ }
507
+ /**
508
+ * Run a script inside a container with the MinIO credentials in its
509
+ * ENVIRONMENT rather than in the command text.
510
+ *
511
+ * `mc alias set _bk http://localhost:9000 ${ak} ${sk}` put a secret into a
512
+ * string that `/bin/sh -c` then parsed — twice, since the outer command
513
+ * wrapped it in `sh -c '<inner>'`. A secret containing a single quote ends
514
+ * the inner quoting and the remainder runs as shell, on the host, as the
515
+ * daemon user. Nothing validates what a MinIO secret may contain, and a
516
+ * generated one is exactly the kind of string that eventually holds a
517
+ * quote.
518
+ *
519
+ * `execFile` with an argv array means the arguments never meet a shell, and
520
+ * the script reads `"$MC_AK"` / `"$MC_SK"` — expanded by the inner shell,
521
+ * not re-parsed by it.
522
+ */
523
+ async execInMinio(container, ak, sk, script, timeoutMs = 600_000) {
524
+ const { execFile } = await import('node:child_process');
525
+ const args = [
526
+ 'exec',
527
+ '-e', `MC_AK=${ak}`,
528
+ '-e', `MC_SK=${sk}`,
529
+ container,
530
+ 'sh', '-c', script,
531
+ ];
532
+ await new Promise((resolve, reject) => {
533
+ execFile('docker', args, { timeout: timeoutMs, maxBuffer: 200 * 1024 * 1024 }, (err) => {
534
+ if (err)
535
+ reject(err);
536
+ else
537
+ resolve();
538
+ });
539
+ });
540
+ }
541
+ /** Run a shell command, rejecting on a non-zero exit. Returns its stdout. */
542
+ async execShell(cmd, timeoutMs = 600_000) {
543
+ const { execFile } = await import('node:child_process');
544
+ return new Promise((resolve, reject) => {
545
+ execFile('/bin/sh', ['-c', cmd], { timeout: timeoutMs, maxBuffer: 200 * 1024 * 1024 }, (err, stdout) => {
546
+ if (err)
547
+ reject(err);
548
+ else
549
+ resolve(String(stdout ?? ''));
550
+ });
551
+ });
552
+ }
553
+ /**
554
+ * Run a command that produces a backup file, and publish the name only if
555
+ * the file is READABLE.
556
+ *
557
+ * The check was `size === 0`, and "not empty" is not "not corrupt".
558
+ * Measured in `~/.omnitron/backups` 2026-09-14, four files that passed it:
559
+ *
560
+ * main_2026-09-14T07-51…sql.gz 98 304 B gzip header, body cut off
561
+ * priceverse_2026-09-10T06-27…sql.gz 1.5 MB same
562
+ * storage-objects_2026-07-02T10-22…gz 122 B «OCI runtime exec failed:
563
+ * "tar": not found in $PATH»
564
+ *
565
+ * The last one is the shape in miniature: a shell error message, sitting
566
+ * under a `.tar.gz` name, 122 bytes long and therefore "not empty". The
567
+ * property that matters is whether the archive opens; the property measured
568
+ * was how many bytes exist.
569
+ *
570
+ * The write also lands on the final name from its first byte, so a command
571
+ * interrupted half-way — a full disk, a killed container — leaves a
572
+ * truncated archive under the name a restore would pick up. Today's
573
+ * ENOSPC-mid-write is exactly that case.
574
+ *
575
+ * So: write to `<name>.partial`, verify, then rename. Rename within a
576
+ * directory is atomic, which makes the visible state binary — either the
577
+ * name is absent, or it names an archive that opened.
578
+ */
579
+ async execToFile(cmd, outputPath, timeoutMs = 600_000) {
580
+ const staging = `${outputPath}.partial`;
581
+ fs.rmSync(staging, { force: true });
582
+ try {
583
+ await this.execShell(cmd.split(outputPath).join(staging), timeoutMs);
584
+ if (!fs.existsSync(staging) || fs.statSync(staging).size === 0) {
585
+ throw new Error('backup produced an empty file');
586
+ }
587
+ await this.assertReadable(staging, outputPath);
588
+ fs.renameSync(staging, outputPath);
589
+ }
590
+ catch (err) {
591
+ // The empty-file check was already here and works — it kept these out
592
+ // of the index. What it did not do was clean up: 22 zero-byte
593
+ // `tor-keys` files had accumulated in the backup directory, on disk but
594
+ // in no listing, which is the worst place for a file to be.
595
+ fs.rmSync(staging, { force: true });
596
+ fs.rmSync(outputPath, { force: true });
597
+ throw err;
598
+ }
599
+ }
600
+ /**
601
+ * Open the archive and fail if it does not open.
602
+ *
603
+ * Keyed on the FINAL name's extension, not the staging one — `.partial` is
604
+ * an implementation detail and would otherwise match nothing and verify
605
+ * nothing, which is the failure mode this whole method exists to remove.
606
+ *
607
+ * An extension nobody has taught this to check passes: the alternative is
608
+ * refusing a backup that was taken correctly, and a verification gap is
609
+ * better than losing a good archive. The three formats produced here are
610
+ * all covered.
611
+ */
612
+ async assertReadable(staging, finalPath) {
613
+ if (finalPath.endsWith('.gz')) {
614
+ await this.execShell(`gzip -t "${staging}"`, 300_000);
615
+ return;
616
+ }
617
+ if (finalPath.endsWith('.db')) {
618
+ // `.backup` produces a consistent snapshot, but a disk that filled
619
+ // during the copy produces a short file that sqlite still opens.
620
+ //
621
+ // The copy is switched out of WAL mode first. `.backup` carries the
622
+ // live store's WAL flag into the copy's header, and Apple's `sqlite3`
623
+ // keeps `-shm` and `-wal` after it closes a WAL database — so this very
624
+ // check left `<name>.db.partial-shm` (32 768 B) and `-wal` (0 B) on
625
+ // every `full` pass, named after a staging file the rename then took
626
+ // away: three such pairs on the master, from 09-17, 09-18 and 09-19.
627
+ // In rollback mode a backup is one file, whoever opens it later. The
628
+ // store puts it back into WAL itself when a restored copy is opened.
629
+ try {
630
+ const out = await this.execShell(`sqlite3 "${staging}" "PRAGMA journal_mode=DELETE;" "PRAGMA integrity_check;"`, 300_000);
631
+ const verdict = out.trim().split('\n').pop()?.trim();
632
+ if (verdict !== 'ok') {
633
+ throw new Error(`backup failed its integrity check: ${out.trim().slice(0, 200)}`);
634
+ }
635
+ }
636
+ finally {
637
+ // Leaving WAL mode checkpoints and removes the WAL, but Apple's build
638
+ // keeps the `-shm`, which means nothing to a rollback-mode database.
639
+ // A WAL that is still there is removed only if it holds no frames.
640
+ fs.rmSync(`${staging}-shm`, { force: true });
641
+ const wal = `${staging}-wal`;
642
+ if (fs.existsSync(wal) && fs.statSync(wal).size === 0)
643
+ fs.rmSync(wal, { force: true });
644
+ }
645
+ }
646
+ }
647
+ async restoreStorageBackup(filepath) {
648
+ const running = this.getRunningInfra();
649
+ const conn = running?.infra.getConnectionInfo('minio');
650
+ const container = running?.infra.getResolvedContainerName('minio');
651
+ if (!running || !conn || !container)
652
+ throw new Error('minio not found in any running stack');
653
+ const ak = conn.accessKey ?? 'minioadmin';
654
+ const sk = conn.secretKey ?? 'minioadmin';
655
+ const stage = path.join(this.backupDir, `.storage-restore-${randomUUID().slice(0, 8)}`);
656
+ // Untar on the host, docker cp into the container, then mirror back into the
657
+ // bucket (minio has no tar, so staging happens host-side).
658
+ const inner = `mc alias set _bk http://localhost:9000 "$MC_AK" "$MC_SK" >/dev/null 2>&1; ` +
659
+ `mc mb --ignore-existing _bk/storage >/dev/null 2>&1; ` +
660
+ `mc mirror --overwrite --quiet /tmp/_bk_storage _bk/storage >/dev/null 2>&1; true`;
661
+ try {
662
+ await this.execShell(`rm -rf "${stage}" && mkdir -p "${stage}" && tar xzf "${filepath}" -C "${stage}" && ` +
663
+ `docker exec ${container} rm -rf /tmp/_bk_storage && docker cp "${stage}/_bk_storage" ${container}:/tmp/_bk_storage`);
664
+ await this.execInMinio(container, ak, sk, inner);
665
+ }
666
+ finally {
667
+ await this.execShell(`rm -rf "${stage}"`).catch(() => { });
668
+ }
669
+ }
670
+ async restoreTorKeysBackup(filepath) {
671
+ const container = this.getRunningInfra()?.infra.getResolvedContainerName('tor');
672
+ if (!container)
673
+ throw new Error('tor not found in any running stack');
674
+ await this.execShell(`docker exec -i ${container} sh -c 'tar xzf - -C /var/lib/tor' < "${filepath}"`);
675
+ this.logger.warn({}, 'Tor keys restored — restart the tor container to serve the restored onion');
676
+ }
677
+ /** Object-level backup of the minio `storage` bucket (mc mirror → tar.gz). */
678
+ async createStorageBackup() {
679
+ const running = this.getRunningInfra();
680
+ const conn = running?.infra.getConnectionInfo('minio');
681
+ const container = running?.infra.getResolvedContainerName('minio');
682
+ if (!running || !conn || !container)
683
+ throw new Error('minio not found in any running stack');
684
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
685
+ const filepath = path.join(this.backupDir, `storage-objects_${ts}_${randomUUID().slice(0, 8)}.tar.gz`);
686
+ const stage = path.join(this.backupDir, `.storage-stage-${randomUUID().slice(0, 8)}`);
687
+ const ak = conn.accessKey ?? 'minioadmin';
688
+ const sk = conn.secretKey ?? 'minioadmin';
689
+ // mc is bundled in the minio image (tar is NOT): mirror the bucket into a
690
+ // container temp dir, `docker cp` it to the host, then tar on the host.
691
+ // Object-level (not raw volume) so it survives minio storage-format changes.
692
+ const inner = `mc alias set _bk http://localhost:9000 "$MC_AK" "$MC_SK" >/dev/null 2>&1; ` +
693
+ `mc mb --ignore-existing _bk/storage >/dev/null 2>&1; ` +
694
+ `rm -rf /tmp/_bk_storage && mkdir -p /tmp/_bk_storage && ` +
695
+ `mc mirror --overwrite --quiet _bk/storage /tmp/_bk_storage >/dev/null 2>&1; true`;
696
+ this.logger.info({ container }, 'Backing up minio storage bucket');
697
+ try {
698
+ await this.execInMinio(container, ak, sk, inner);
699
+ await this.execToFile(`rm -rf "${stage}" && mkdir -p "${stage}" && docker cp ${container}:/tmp/_bk_storage "${stage}/" && ` +
700
+ `tar czf "${filepath}" -C "${stage}" _bk_storage`, filepath);
701
+ }
702
+ finally {
703
+ await this.execShell(`rm -rf "${stage}"`).catch(() => { });
704
+ }
705
+ return this.indexBackupFile('storage-objects', filepath, 'storage-objects', { project: running.project, stack: running.stack });
706
+ }
707
+ /**
708
+ * Snapshot the Tor hidden-service keys (the .onion identity).
709
+ *
710
+ * `-C /var/lib/tor .` took the whole data directory, and what dominates that
711
+ * directory is Tor's DIRECTORY CACHE, not key material. Measured on this
712
+ * host: a 29 MiB archive of which `cached-microdescs` was 42.5 MiB
713
+ * uncompressed, `cached-microdescs.new` 16 MiB and
714
+ * `cached-microdesc-consensus` 3.5 MiB — against `hs_ed25519_secret_key`
715
+ * files of 96 bytes each. Forty such archives held 400 MiB, essentially all
716
+ * of it a public consensus any Tor client re-downloads in minutes, on a
717
+ * machine whose disk has filled before and taken the database and the onion
718
+ * with it.
719
+ *
720
+ * The exclusions are Tor's own regenerable state, by name. Everything else
721
+ * is kept, so a hidden-service directory added later is still captured
722
+ * without anyone remembering to update a list.
723
+ */
724
+ async createTorKeysBackup() {
725
+ const running = this.getRunningInfra();
726
+ const container = running?.infra.getResolvedContainerName('tor');
727
+ if (!running || !container)
728
+ throw new Error('tor not found in any running stack');
729
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
730
+ const filepath = path.join(this.backupDir, `tor-keys_${ts}_${randomUUID().slice(0, 8)}.tar.gz`);
731
+ this.logger.info({ container }, 'Backing up tor hidden-service keys');
732
+ const excludes = TOR_REGENERABLE_STATE.map((g) => `--exclude='${g}'`).join(' ');
733
+ await this.execToFile(`docker exec ${container} tar czf - ${excludes} -C /var/lib/tor . > "${filepath}"`, filepath);
734
+ return this.indexBackupFile('tor-keys', filepath, 'tor-keys', { project: running.project, stack: running.stack });
735
+ }
736
+ /** Online snapshot of the daemon-state DB (encrypted secrets + backup index). */
737
+ async createSecretsBackup() {
738
+ const src = expandPath('~/.omnitron/data/daemon-state.db');
739
+ if (!fs.existsSync(src))
740
+ throw new Error('daemon-state.db not found');
741
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
742
+ const filepath = path.join(this.backupDir, `daemon-state_${ts}_${randomUUID().slice(0, 8)}.db`);
743
+ // `.backup` is SQLite's online-consistent snapshot — safe while the daemon
744
+ // holds the DB open in WAL mode.
745
+ this.logger.info({}, 'Backing up daemon-state.db (secrets + backup index)');
746
+ await this.execToFile(`sqlite3 "${src}" ".backup '${filepath}'"`, filepath);
747
+ return this.indexBackupFile('daemon-state', filepath, 'daemon-state', 'control-plane');
748
+ }
749
+ /**
750
+ * Full backup: every stack DB + the control-plane DB + minio storage
751
+ * objects + tor keys + daemon-state (secrets). Per-target failures are
752
+ * captured, not fatal.
753
+ */
754
+ async createFullBackup(trigger = 'manual') {
755
+ const startedAt = new Date();
756
+ const results = [];
757
+ for (const r of await this.backUpStackDatabases()) {
758
+ const { database, ...rest } = r;
759
+ results.push({ target: database, ...rest });
760
+ }
761
+ const extras = [
762
+ // The control plane — the audit log among it. Neither `all` nor `full`
763
+ // took it: its one copy on the master, 2026-09-05T07:39Z, was made by
764
+ // hand. `omnitron-pg` names it whatever the stacks' databases are called.
765
+ [CONTROL_PLANE_DB, () => this.createBackup('omnitron-pg', { compress: true })],
766
+ ['storage-objects', () => this.createStorageBackup()],
767
+ ['tor-keys', () => this.createTorKeysBackup()],
768
+ ['daemon-state', () => this.createSecretsBackup()],
769
+ ];
770
+ for (const [target, fn] of extras) {
771
+ try {
772
+ const info = await fn();
773
+ results.push({ target, ok: true, id: info.id, size: info.size });
774
+ }
775
+ catch (err) {
776
+ results.push({ target, ok: false, error: err.message });
777
+ this.logger.error({ target, error: reasonOf(err) }, 'Backup failed');
778
+ }
779
+ }
780
+ this.finishPass('full', trigger, startedAt, results.map(qualifiedEntry));
781
+ return results;
782
+ }
783
+ /**
784
+ * Close a pass: one summary line at a level that matches what happened, and
785
+ * a persisted record `backup schedules` shows.
786
+ *
787
+ * The summary was always `info` — `createAllBackups complete
788
+ * {"total":6,"ok":5}` for the 06:04Z pass on the master that lost `main`,
789
+ * the same level and message as a pass that lost nothing — and
790
+ * `runScheduledBackup` discarded the results, its `error` reachable only by
791
+ * a throw that a per-entry catch never let happen.
792
+ */
793
+ finishPass(target, trigger, startedAt, entries) {
794
+ const attempted = entries.filter((e) => !e.skipped);
795
+ const failures = attempted
796
+ .filter((e) => !e.ok)
797
+ .map((e) => ({ target: e.target, error: (e.error ?? 'failed').slice(0, 500) }));
798
+ const notBackedUp = entries.filter((e) => e.skipped).map((e) => ({ stack: e.target, reason: e.error ?? 'not backed up' }));
799
+ const record = {
800
+ target,
801
+ trigger,
802
+ startedAt: startedAt.toISOString(),
803
+ finishedAt: new Date().toISOString(),
804
+ outcome: passOutcome(entries),
805
+ ok: attempted.length - failures.length,
806
+ total: attempted.length,
807
+ failures,
808
+ ...(notBackedUp.length > 0 ? { notBackedUp } : {}),
809
+ };
810
+ const summary = { target, trigger, total: record.total, ok: record.ok, outcome: record.outcome };
811
+ const msg = target === 'full' ? 'createFullBackup complete' : target === 'all' ? 'createAllBackups complete' : 'Backup pass complete';
812
+ // Every pass says which running stacks it could not reach. «all» reading
813
+ // as «every stack» is how the test stack went unbacked-up with nothing
814
+ // anywhere saying so.
815
+ if (notBackedUp.length > 0) {
816
+ this.logger.warn({ target, notBackedUp: notBackedUp.map((n) => n.stack) }, `Running stacks this pass could not back up: ${notBackedUp.map((n) => `${n.stack} (${n.reason})`).join('; ')}`);
817
+ }
818
+ if (record.outcome === 'ok') {
819
+ this.logger.info(summary, msg);
820
+ }
821
+ else if (record.outcome === 'empty') {
822
+ this.logger.warn(summary, `${msg} — nothing was backed up: no database or artefact was found`);
823
+ }
824
+ else {
825
+ this.logger.error({ ...summary, failed: failures.map((f) => f.target) }, `${msg} — ${failures.length} of ${record.total} failed: ${failures.map((f) => f.target).join(', ')}`);
826
+ }
827
+ try {
828
+ const passes = this.store.kvGetSync(BackupService_1.PASSES_KV_KEY) ?? {};
829
+ passes[target] = record;
830
+ this.store.kvSetSync(BackupService_1.PASSES_KV_KEY, passes);
831
+ }
832
+ catch (err) {
833
+ this.logger.warn({ target, err: err.message }, 'Could not record the backup pass');
834
+ }
835
+ return record;
836
+ }
837
+ /** The last recorded pass of every target. */
838
+ lastPasses() {
839
+ try {
840
+ return this.store.kvGetSync(BackupService_1.PASSES_KV_KEY) ?? {};
841
+ }
842
+ catch {
843
+ return {};
844
+ }
845
+ }
846
+ /**
847
+ * What the schedules are and what each last did — what `backup schedules`
848
+ * prints, and what `backup list` warns from.
849
+ */
850
+ async getStatus() {
851
+ const specs = await this.listSchedules();
852
+ const passes = this.lastPasses();
853
+ const schedules = Object.entries(specs).map(([target, spec]) => {
854
+ let schedule = null;
855
+ let error;
856
+ try {
857
+ schedule = describeSchedule(parseSchedule(spec));
858
+ }
859
+ catch (err) {
860
+ error = err.message;
861
+ }
862
+ const last = this.lastBackupOf(target);
863
+ return {
864
+ target,
865
+ spec,
866
+ schedule,
867
+ ...(error ? { error } : {}),
868
+ armed: this.schedules.has(target),
869
+ lastPass: passes[target] ?? null,
870
+ lastBackupAt: last === null ? null : new Date(last).toISOString(),
871
+ nextRunAt: this.schedules.get(target)?.nextRunAt ?? null,
872
+ };
873
+ });
874
+ return { schedules, stacks: this.surveyStacks().stacks };
875
+ }
876
+ // ===========================================================================
73
877
  // List backups
74
878
  // ===========================================================================
75
879
  async listBackups(database) {
76
- const files = fs.readdirSync(this.backupDir).filter((f) => f.endsWith('.meta.json'));
880
+ this.migrateLegacyMetaIfPresent();
881
+ const rows = this.store.selectBackupsSync(database);
77
882
  const backups = [];
78
- for (const file of files) {
79
- try {
80
- const content = fs.readFileSync(path.join(this.backupDir, file), 'utf-8');
81
- const info = JSON.parse(content);
82
- if (!database || info.database === database) {
83
- // Verify the actual backup file still exists
84
- const backupFile = path.join(this.backupDir, info.filename);
85
- if (fs.existsSync(backupFile)) {
86
- backups.push(info);
87
- }
883
+ for (const row of rows) {
884
+ // The .sql.gz bytes still live on disk; if they were deleted
885
+ // externally, drop the row (self-cleaning index) so a stale
886
+ // entry doesn't haunt the listing.
887
+ if (!fs.existsSync(row.path)) {
888
+ try {
889
+ this.store.deleteBackupSync(row.id);
88
890
  }
891
+ catch { /* best-effort */ }
892
+ continue;
89
893
  }
90
- catch {
91
- // Skip corrupt metadata files
92
- }
894
+ const meta = metadataOf(row.metadata);
895
+ const filename = meta.filename ?? path.basename(row.path);
896
+ backups.push({
897
+ id: row.id,
898
+ database: row.app,
899
+ filename,
900
+ size: row.size_bytes,
901
+ createdAt: row.created_at,
902
+ compressed: meta.compressed ?? filename.endsWith('.gz'),
903
+ // Where it came from, when the row recorded it — see `BackupOrigin`.
904
+ ...(meta.project && meta.stack ? { project: meta.project, stack: meta.stack } : {}),
905
+ ...(meta.scope ? { scope: meta.scope } : {}),
906
+ });
93
907
  }
94
- return backups.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
908
+ return backups;
95
909
  }
96
910
  // ===========================================================================
97
911
  // Restore backup
98
912
  // ===========================================================================
99
913
  async restoreBackup(backupId) {
100
- const backups = await this.listBackups();
101
- const backup = backups.find((b) => b.id === backupId);
102
- if (!backup)
103
- throw new Error(`Backup '${backupId}' not found`);
104
- const filepath = path.join(this.backupDir, backup.filename);
105
- if (!fs.existsSync(filepath))
106
- throw new Error(`Backup file not found: ${backup.filename}`);
107
- this.logger.info({ database: backup.database, filename: backup.filename }, 'Restoring backup');
108
- const { dbConfig, isDocker, containerName } = this.resolveDbConfig(backup.database);
109
- if (isDocker) {
110
- await this.pgRestoreDocker(containerName, dbConfig, filepath, backup.compressed);
111
- }
112
- else {
113
- await this.pgRestoreLocal(dbConfig, filepath, backup.compressed);
914
+ this.migrateLegacyMetaIfPresent();
915
+ // The full id, or a prefix of it that names one backup — what the CLI
916
+ // prints. An ambiguous prefix is refused with every candidate named.
917
+ const row = resolveBackupId(this.store.selectBackupsSync(), backupId, (r) => `${r.id} (${r.app}, ${formatUtc(r.created_at)})`);
918
+ if (!fs.existsSync(row.path))
919
+ throw new Error(`Backup file not found: ${row.path}`);
920
+ const meta = metadataOf(row.metadata);
921
+ const type = meta.type ?? 'postgres';
922
+ this.logger.info({ app: row.app, type, path: row.path }, 'Restoring backup');
923
+ switch (type) {
924
+ case 'storage-objects':
925
+ await this.restoreStorageBackup(row.path);
926
+ break;
927
+ case 'tor-keys':
928
+ await this.restoreTorKeysBackup(row.path);
929
+ break;
930
+ case 'daemon-state':
931
+ throw new Error('Refusing to restore daemon-state.db into a running daemon. Stop the daemon (`omnitron down`), ' +
932
+ `copy ${row.path} to ~/.omnitron/data/daemon-state.db, then start it (\`omnitron up\`).`);
933
+ default: {
934
+ // Postgres — into the database it was TAKEN from. By its name alone
935
+ // that was whichever running stack came first, so a `main` of one
936
+ // stack could be restored over another's.
937
+ const { dbConfig, isDocker, containerName } = this.restoreTargetOf(row.app, meta);
938
+ const compressed = meta.compressed ?? row.path.endsWith('.gz');
939
+ if (isDocker)
940
+ await this.pgRestoreDocker(containerName, dbConfig, row.path, compressed);
941
+ else
942
+ await this.pgRestoreLocal(dbConfig, row.path, compressed);
943
+ }
114
944
  }
115
- this.logger.info({ database: backup.database }, 'Backup restored');
945
+ this.logger.info({ app: row.app, type }, 'Backup restored');
116
946
  }
117
947
  // ===========================================================================
118
948
  // Delete backup
@@ -123,46 +953,312 @@ export class BackupService {
123
953
  if (!backup)
124
954
  throw new Error(`Backup '${backupId}' not found`);
125
955
  const filepath = path.join(this.backupDir, backup.filename);
126
- const metapath = path.join(this.backupDir, `${backup.filename}.meta.json`);
956
+ // Drop the SQLite row first so a concurrent listBackups call
957
+ // doesn't return a record whose .sql.gz file is about to be
958
+ // unlinked. Backup-file unlink is best-effort — a missing
959
+ // file just means the operator already removed it.
960
+ this.store.deleteBackupSync(backup.id);
127
961
  try {
128
962
  fs.unlinkSync(filepath);
129
963
  }
130
964
  catch { /* ignore */ }
131
- try {
132
- fs.unlinkSync(metapath);
965
+ // And whatever SQLite left beside it. Removing only the named file is how
966
+ // four `daemon-state_*.db-shm/-wal` pairs on the master came to outlive
967
+ // the backups retention had pruned — belonging to nothing, in no listing.
968
+ for (const suffix of SQLITE_SIDECARS) {
969
+ try {
970
+ fs.unlinkSync(filepath + suffix);
971
+ }
972
+ catch { /* none, or not ours */ }
133
973
  }
134
- catch { /* ignore */ }
135
974
  this.logger.info({ database: backup.database, filename: backup.filename }, 'Backup deleted');
136
975
  }
137
976
  // ===========================================================================
138
977
  // Schedule
139
978
  // ===========================================================================
979
+ /**
980
+ * Arm and PERSIST a recurring backup. `database` may be a specific DB name,
981
+ * a "<project>/<stack>/<db>" key, or the special target "all" — which backs
982
+ * up every database of every running stack each tick. Persisted to state_kv
983
+ * so it survives daemon restarts (re-armed via restoreSchedules on boot).
984
+ */
140
985
  async setSchedule(database, cron) {
141
- // Cancel existing schedule
986
+ // Parse before arming and before persisting: a specification the daemon
987
+ // cannot read must reach the operator as an error now, not as a backup
988
+ // running at a time nobody chose. `parseSchedule` throws rather than
989
+ // defaulting, which is the entire contract.
990
+ const plan = parseSchedule(cron);
991
+ this.armSchedule(database, plan);
992
+ const map = this.store.kvGetSync(BackupService_1.SCHEDULES_KV_KEY) ?? {};
993
+ map[database] = cron;
994
+ this.store.kvSetSync(BackupService_1.SCHEDULES_KV_KEY, map);
995
+ this.logger.info({ database, cron, schedule: describeSchedule(plan) }, 'Backup schedule set + persisted');
996
+ }
997
+ /** Cancel + un-persist a schedule. */
998
+ async removeSchedule(database) {
142
999
  const existing = this.schedules.get(database);
143
- if (existing?.timer)
144
- clearInterval(existing.timer);
145
- // Parse simple cron-like interval (for MVP: interpret as interval in ms)
146
- const intervalMs = this.parseCronInterval(cron);
147
- const timer = setInterval(async () => {
1000
+ if (existing) {
1001
+ // Order matters: a cron tick already running re-arms itself when it
1002
+ // finishes, and it checks this flag to decide not to.
1003
+ existing.cancelled = true;
1004
+ if (existing.timer)
1005
+ clearTimeout(existing.timer);
1006
+ }
1007
+ this.schedules.delete(database);
1008
+ const map = this.store.kvGetSync(BackupService_1.SCHEDULES_KV_KEY) ?? {};
1009
+ delete map[database];
1010
+ this.store.kvSetSync(BackupService_1.SCHEDULES_KV_KEY, map);
1011
+ this.logger.info({ database }, 'Backup schedule removed');
1012
+ }
1013
+ /** Re-arm every persisted schedule. Called once at daemon start. */
1014
+ async restoreSchedules() {
1015
+ const map = this.store.kvGetSync(BackupService_1.SCHEDULES_KV_KEY) ?? {};
1016
+ let n = 0;
1017
+ const rejected = [];
1018
+ for (const [database, cron] of Object.entries(map)) {
1019
+ // A stored row can be unreadable — written before this was validated,
1020
+ // or hand-edited. Neither reaction is obvious, so both are wrong on
1021
+ // their own: refusing to boot over one bad backup schedule is out of
1022
+ // proportion, and quietly arming a default would put the backup at a
1023
+ // time nobody chose, which is what this whole change is about. The row
1024
+ // is skipped and named at `error` level; `backup schedules` still lists
1025
+ // it, so it stays visible as configured-but-not-running.
148
1026
  try {
149
- await this.createBackup(database, { compress: true });
1027
+ this.armSchedule(database, parseSchedule(cron), n);
1028
+ n++;
1029
+ const last = this.lastBackupOf(database);
1030
+ this.logger.info({
1031
+ database,
1032
+ schedule: cron,
1033
+ lastBackupAt: last === null ? null : new Date(last).toISOString(),
1034
+ nextRunAt: this.schedules.get(database)?.nextRunAt ?? null,
1035
+ }, 'Backup schedule armed');
150
1036
  }
151
1037
  catch (err) {
152
- this.logger.error({ database, error: err.message }, 'Scheduled backup failed');
1038
+ rejected.push(database);
1039
+ this.logger.error({ database, cron, error: err.message }, 'Persisted backup schedule is unreadable — NOT armed; this database is not being backed up');
153
1040
  }
154
- }, intervalMs);
155
- timer.unref();
156
- this.schedules.set(database, { database, cron, timer });
157
- this.logger.info({ database, cron, intervalMs }, 'Backup schedule set');
1041
+ }
1042
+ if (n > 0)
1043
+ this.logger.info({ count: n }, 'Restored persisted backup schedules');
1044
+ if (rejected.length > 0) {
1045
+ this.logger.error({ databases: rejected }, 'Backup schedules skipped — fix them with `omnitron backup schedule`');
1046
+ }
1047
+ }
1048
+ async listSchedules() {
1049
+ return this.store.kvGetSync(BackupService_1.SCHEDULES_KV_KEY) ?? {};
158
1050
  }
159
1051
  async getSchedule(database) {
160
- return this.schedules.get(database)?.cron ?? null;
1052
+ return this.schedules.get(database)?.cron ?? (await this.listSchedules())[database] ?? null;
1053
+ }
1054
+ /**
1055
+ * In-memory timer arm (no persistence) — shared by setSchedule/restore.
1056
+ *
1057
+ * An interval schedule used to be `setInterval(run, intervalMs)` from the
1058
+ * moment it was armed — the moment the daemon started — whatever had run
1059
+ * before. Measured on the master: 83 daemon starts between 09-20 07:23Z and
1060
+ * 09-23, the longest uptime 10 h 51 min, so the `full` daily pass never
1061
+ * fired, and its newest `tor-keys`, `storage-objects` and `daemon-state`
1062
+ * copies were 3 d 15 h old; the hourly `all` pass left 13 gaps over two
1063
+ * hours in 74 h (the longest 5 h 43 min, 18 restarts inside it).
1064
+ *
1065
+ * Now the first run is due one interval after the target's newest backup
1066
+ * in the index — immediately if that is already past, which after days of
1067
+ * restarts it is — but never sooner than `FIRST_RUN_GRACE_MS`, and each
1068
+ * further schedule armed by the same start one `FIRST_RUN_STAGGER_MS` later
1069
+ * (`slot`). Every later run is measured from when the previous one STARTED,
1070
+ * so a long pass does not push the cadence back, and passes never overlap.
1071
+ */
1072
+ armSchedule(database, plan, slot = 0) {
1073
+ const existing = this.schedules.get(database);
1074
+ if (existing) {
1075
+ existing.cancelled = true;
1076
+ if (existing.timer)
1077
+ clearTimeout(existing.timer);
1078
+ }
1079
+ const entry = { database, cron: plan.spec, plan };
1080
+ this.schedules.set(database, entry);
1081
+ const arm = (delay) => {
1082
+ if (entry.cancelled)
1083
+ return;
1084
+ entry.nextRunAt = new Date(Date.now() + delay).toISOString();
1085
+ entry.timer = setTimeout(() => {
1086
+ const startedAt = Date.now();
1087
+ void this.runExclusive(() => this.runScheduledBackup(database)).then((outcome) => {
1088
+ if (plan.kind === 'interval') {
1089
+ const backedUpSomething = outcome === 'ok' || outcome === 'partial';
1090
+ const wait = backedUpSomething ? plan.intervalMs : Math.min(plan.intervalMs, RETRY_AFTER_NOTHING_MS);
1091
+ arm(Math.max(0, startedAt + wait - Date.now()));
1092
+ }
1093
+ else {
1094
+ // Cron occurrences are not evenly spaced — "0 3 * * *" is 23 or
1095
+ // 25 hours apart across a DST boundary, and "0 0 1 * *" is 28 to
1096
+ // 31 days. Each run computes the next one from the clock rather
1097
+ // than adding a fixed interval, so the schedule cannot drift off
1098
+ // its stated time.
1099
+ arm(nextCronDelay(plan.expression));
1100
+ }
1101
+ });
1102
+ }, delay);
1103
+ entry.timer.unref();
1104
+ };
1105
+ if (plan.kind === 'interval') {
1106
+ const last = this.lastBackupOf(database);
1107
+ const due = last === null ? 0 : last + plan.intervalMs - Date.now();
1108
+ arm(Math.max(due, FIRST_RUN_GRACE_MS + slot * FIRST_RUN_STAGGER_MS));
1109
+ }
1110
+ else {
1111
+ arm(nextCronDelay(plan.expression));
1112
+ }
1113
+ this.logger.debug({ database, schedule: describeSchedule(plan), nextRunAt: entry.nextRunAt }, 'Backup schedule armed');
1114
+ }
1115
+ /**
1116
+ * Scheduled passes run one at a time. Two schedules due together — which is
1117
+ * what every start after a long gap produces — would otherwise dump the same
1118
+ * databases twice, concurrently, on a machine already under load.
1119
+ */
1120
+ runExclusive(run) {
1121
+ const turn = this.passQueue.then(run, run);
1122
+ this.passQueue = turn.catch(() => undefined);
1123
+ return turn;
1124
+ }
1125
+ /**
1126
+ * When `target` last produced a backup, by the index — the newest row among
1127
+ * the artefacts that target writes: `full` → the ones only it produces;
1128
+ * `all` → any stack database; a database → that database. Null when none.
1129
+ */
1130
+ lastBackupOf(target) {
1131
+ let rows;
1132
+ try {
1133
+ rows = this.store.selectBackupsSync();
1134
+ }
1135
+ catch {
1136
+ return null;
1137
+ }
1138
+ const fullOnly = new Set(FULL_BACKUP_ARTEFACTS);
1139
+ // `<project>/<stack>/<db>` counts that stack's rows, and rows from before
1140
+ // the stack was recorded; a bare name counts every row of that name.
1141
+ const parts = target.split('/');
1142
+ const name = parts[parts.length - 1];
1143
+ const stackOf = parts.length === 3 ? { project: parts[0], stack: parts[1] } : null;
1144
+ let newest = null;
1145
+ for (const row of rows) {
1146
+ const meta = metadataOf(row.metadata);
1147
+ const counts = target === 'full' ? fullOnly.has(row.app)
1148
+ : target === 'all'
1149
+ ? (meta.type === undefined || meta.type === 'postgres') && meta.scope !== 'control-plane' &&
1150
+ !fullOnly.has(row.app) && row.app !== CONTROL_PLANE_DB
1151
+ : row.app === name &&
1152
+ (!stackOf || !meta.project || (meta.project === stackOf.project && meta.stack === stackOf.stack));
1153
+ if (!counts)
1154
+ continue;
1155
+ const at = Date.parse(row.created_at);
1156
+ if (Number.isFinite(at) && (newest === null || at > newest))
1157
+ newest = at;
1158
+ }
1159
+ return newest;
1160
+ }
1161
+ /**
1162
+ * One scheduled tick. Extracted so both timer shapes share it, and so a
1163
+ * failure is reported the same way from either. Returns how it ended, which
1164
+ * decides how soon the next attempt comes.
1165
+ */
1166
+ async runScheduledBackup(database) {
1167
+ let outcome = 'failed';
1168
+ try {
1169
+ if (database === 'full')
1170
+ outcome = passOutcome(await this.createFullBackup('schedule'));
1171
+ else if (database === 'all')
1172
+ outcome = passOutcome(await this.createAllBackups('schedule'));
1173
+ else {
1174
+ // A single database is a pass of one, recorded like the others; its
1175
+ // failure is caught here rather than thrown past the record.
1176
+ const startedAt = new Date();
1177
+ let entry;
1178
+ try {
1179
+ const info = await this.createBackup(database, { compress: true });
1180
+ entry = { target: database, ok: true, id: info.id, size: info.size };
1181
+ }
1182
+ catch (err) {
1183
+ entry = { target: database, ok: false, error: err.message };
1184
+ this.logger.error({ database, error: reasonOf(err) }, 'Backup failed');
1185
+ }
1186
+ outcome = this.finishPass(database, 'schedule', startedAt, [entry]).outcome;
1187
+ }
1188
+ await this.pruneOldBackups(database).catch(() => { });
1189
+ }
1190
+ catch (err) {
1191
+ this.logger.error({ database, error: err.message }, 'Scheduled backup failed');
1192
+ }
1193
+ return outcome;
1194
+ }
1195
+ /**
1196
+ * Retention: keep the most recent `keep` backups per database, delete older.
1197
+ * Bounds unbounded growth from hourly schedules (~168/week/db otherwise).
1198
+ *
1199
+ * Per database OF EACH STACK, now that a row says which stack it came from:
1200
+ * two stacks' `main` are two histories, and one count across both would
1201
+ * keep half of each. A row from before the stack was recorded is pruned as
1202
+ * rows always were — once `keep` newer rows of its database name exist —
1203
+ * so the history already on disk is bounded exactly as before.
1204
+ */
1205
+ async pruneOldBackups(database, keep = 48) {
1206
+ const rows = await this.listBackups();
1207
+ let dbs;
1208
+ if (database === 'all' || database === 'full') {
1209
+ // Union of what is scheduled and what is ON DISK.
1210
+ //
1211
+ // The schedule-derived set alone prunes only the databases the current
1212
+ // configuration still backs up, so anything dropped from it keeps its
1213
+ // history for ever — retention that runs where the producer still runs
1214
+ // and nowhere else. Measured on this host: `tor-keys` held 37 files
1215
+ // and 343 MiB, none newer than two months, because they are named only
1216
+ // in the `full` pass and that pass no longer runs; `storage-objects`
1217
+ // held fifteen more.
1218
+ //
1219
+ // Reading the backup index instead means a sweep bounds everything it
1220
+ // finds, including the leftovers of a schedule someone removed.
1221
+ const scheduled = [...new Set(this.surveyStacks().databases.map((d) => d.database))];
1222
+ dbs = databasesToPrune(scheduled, rows.map((b) => b.database));
1223
+ }
1224
+ else {
1225
+ dbs = [database.slice(database.lastIndexOf('/') + 1)];
1226
+ }
1227
+ const newestFirst = (a, b) => b.createdAt.localeCompare(a.createdAt);
1228
+ for (const db of dbs) {
1229
+ const ofName = rows.filter((r) => r.database === db).sort(newestFirst);
1230
+ const byOrigin = new Map();
1231
+ for (const r of ofName) {
1232
+ const origin = r.project && r.stack ? `${r.project}/${r.stack}` : r.scope === 'control-plane' ? 'control-plane' : '';
1233
+ byOrigin.set(origin, [...(byOrigin.get(origin) ?? []), r]);
1234
+ }
1235
+ const doomed = [];
1236
+ for (const [origin, group] of byOrigin) {
1237
+ if (origin === '') {
1238
+ // Unrecorded: judged against every row of the name, as before.
1239
+ const rank = new Map(ofName.map((r, i) => [r.id, i]));
1240
+ doomed.push(...group.filter((r) => (rank.get(r.id) ?? 0) >= keep));
1241
+ }
1242
+ else {
1243
+ doomed.push(...group.slice(keep));
1244
+ }
1245
+ }
1246
+ for (const old of doomed) {
1247
+ try {
1248
+ await this.deleteBackup(old.id);
1249
+ }
1250
+ catch { /* best-effort */ }
1251
+ }
1252
+ }
161
1253
  }
162
1254
  dispose() {
163
1255
  for (const entry of this.schedules.values()) {
1256
+ // Flag first: clearing the timer does not stop a cron tick that is
1257
+ // already running, and that tick re-arms itself when it finishes.
1258
+ // Without the flag a disposed service keeps scheduling backups.
1259
+ entry.cancelled = true;
164
1260
  if (entry.timer)
165
- clearInterval(entry.timer);
1261
+ clearTimeout(entry.timer);
166
1262
  }
167
1263
  this.schedules.clear();
168
1264
  }
@@ -170,130 +1266,232 @@ export class BackupService {
170
1266
  // Private — pg_dump via Docker
171
1267
  // ===========================================================================
172
1268
  async pgDumpDocker(container, config, outputPath, compress) {
173
- const { execFile } = await import('node:child_process');
174
- const { promisify } = await import('node:util');
175
- const exec = promisify(execFile);
176
- if (compress) {
177
- // pg_dump inside container, pipe through gzip, write to host
178
- const cmd = `docker exec ${container} pg_dump -U ${config.user} -d ${config.database} | gzip > "${outputPath}"`;
179
- const { execFile: ef } = await import('node:child_process');
180
- await new Promise((resolve, reject) => {
181
- ef('/bin/sh', ['-c', cmd], { timeout: 600_000, maxBuffer: 100 * 1024 * 1024 }, (err) => {
182
- if (err)
183
- reject(err);
184
- else
185
- resolve();
186
- });
187
- });
188
- }
189
- else {
190
- const { stdout } = await exec('docker', [
191
- 'exec', container, 'pg_dump', '-U', config.user, '-d', config.database,
192
- ], { timeout: 600_000, maxBuffer: 100 * 1024 * 1024 });
193
- fs.writeFileSync(outputPath, stdout);
194
- }
1269
+ await dumpToFile('docker', ['exec', container, 'pg_dump', '-U', config.user, '-d', config.database], outputPath, compress);
195
1270
  }
196
1271
  async pgRestoreDocker(container, config, inputPath, compressed) {
197
- const cmd = compressed
198
- ? `gunzip -c "${inputPath}" | docker exec -i ${container} psql -U ${config.user} -d ${config.database}`
199
- : `docker exec -i ${container} psql -U ${config.user} -d ${config.database} < "${inputPath}"`;
200
- const { execFile } = await import('node:child_process');
201
- await new Promise((resolve, reject) => {
202
- execFile('/bin/sh', ['-c', cmd], { timeout: 600_000, maxBuffer: 100 * 1024 * 1024 }, (err) => {
203
- if (err)
204
- reject(err);
205
- else
206
- resolve();
207
- });
208
- });
1272
+ await restoreFromFile('docker', ['exec', '-i', container, 'psql', '-U', config.user, '-d', config.database], inputPath, compressed);
209
1273
  }
210
1274
  // ===========================================================================
211
1275
  // Private — pg_dump local
212
1276
  // ===========================================================================
213
1277
  async pgDumpLocal(config, outputPath, compress) {
214
- const { execFile } = await import('node:child_process');
215
- const { promisify } = await import('node:util');
216
- const exec = promisify(execFile);
217
- const env = { ...process.env, PGPASSWORD: config.password };
218
- const pgArgs = ['-h', config.host, '-p', String(config.port), '-U', config.user, '-d', config.database];
219
- if (compress) {
220
- const cmd = `pg_dump ${pgArgs.join(' ')} | gzip > "${outputPath}"`;
221
- await new Promise((resolve, reject) => {
222
- execFile('/bin/sh', ['-c', cmd], { timeout: 600_000, maxBuffer: 100 * 1024 * 1024, env }, (err) => {
223
- if (err)
224
- reject(err);
225
- else
226
- resolve();
227
- });
228
- });
229
- }
230
- else {
231
- const { stdout } = await exec('pg_dump', pgArgs, { timeout: 600_000, maxBuffer: 100 * 1024 * 1024, env });
232
- fs.writeFileSync(outputPath, stdout);
233
- }
1278
+ // The uncompressed path used to buffer the whole dump in memory with a
1279
+ // 100 MB ceiling, which an ordinary database exceeds. Both paths stream.
1280
+ await dumpToFile('pg_dump', ['-h', config.host, '-p', String(config.port), '-U', config.user, '-d', config.database], outputPath, compress, { env: { ...process.env, PGPASSWORD: config.password } });
234
1281
  }
235
1282
  async pgRestoreLocal(config, inputPath, compressed) {
236
- const env = { ...process.env, PGPASSWORD: config.password };
237
- const pgArgs = `-h ${config.host} -p ${config.port} -U ${config.user} -d ${config.database}`;
238
- const cmd = compressed
239
- ? `gunzip -c "${inputPath}" | psql ${pgArgs}`
240
- : `psql ${pgArgs} < "${inputPath}"`;
241
- const { execFile } = await import('node:child_process');
242
- await new Promise((resolve, reject) => {
243
- execFile('/bin/sh', ['-c', cmd], { timeout: 600_000, maxBuffer: 100 * 1024 * 1024, env }, (err) => {
244
- if (err)
245
- reject(err);
246
- else
247
- resolve();
248
- });
249
- });
1283
+ await restoreFromFile('psql', ['-h', config.host, '-p', String(config.port), '-U', config.user, '-d', config.database], inputPath, compressed, { env: { ...process.env, PGPASSWORD: config.password } });
250
1284
  }
251
1285
  // ===========================================================================
252
1286
  // Private — helpers
253
1287
  // ===========================================================================
254
- resolveDbConfig(database) {
255
- // Known database mappings for Omnitron infrastructure
256
- const configs = {
257
- omnitron: {
258
- dbConfig: { host: 'localhost', port: 5480, user: 'omnitron', password: 'omnitron', database: 'omnitron' },
259
- containerName: 'omnitron-pg',
260
- },
261
- main: {
262
- dbConfig: { host: 'localhost', port: 5432, user: 'omnitron', password: 'omnitron', database: 'omnitron_main' },
263
- containerName: 'omnitron-pg',
264
- },
265
- storage: {
266
- dbConfig: { host: 'localhost', port: 5432, user: 'omnitron', password: 'omnitron', database: 'omnitron_storage' },
267
- containerName: 'omnitron-pg',
268
- },
269
- };
270
- const entry = configs[database];
271
- if (!entry) {
272
- // Default: treat as omnitron-pg database name
273
- return {
274
- dbConfig: { host: 'localhost', port: 5480, user: 'omnitron', password: 'omnitron', database },
275
- isDocker: true,
276
- containerName: 'omnitron-pg',
277
- };
1288
+ /**
1289
+ * The database a backup target names, resolved LIVE from the running
1290
+ * stacks' real infrastructure (container, credentials, DB list):
1291
+ *
1292
+ * `<project>/<stack>/<db>` that database of that stack;
1293
+ * `omnitron-pg` the control plane, always;
1294
+ * a bare name the one running stack that has it — and a
1295
+ * refusal naming every candidate when more than
1296
+ * one does. It was "first stack wins", which
1297
+ * dumps, and restores into, whichever stack the
1298
+ * iteration happened to reach first;
1299
+ * `omnitron` the control plane, when no stack has one.
1300
+ */
1301
+ resolveDbTarget(database) {
1302
+ const { databases } = this.surveyStacks();
1303
+ const keyOf = (d) => `${d.project}/${d.stack}/${d.database}`;
1304
+ const fromStack = (d) => ({
1305
+ dbConfig: { host: d.host, port: d.port, user: d.user, password: d.password, database: d.database },
1306
+ isDocker: !!d.container,
1307
+ containerName: d.container ?? '',
1308
+ origin: { project: d.project, stack: d.stack },
1309
+ });
1310
+ const exact = databases.find((d) => keyOf(d) === database);
1311
+ if (exact)
1312
+ return fromStack(exact);
1313
+ if (database === 'omnitron-pg')
1314
+ return this.controlPlaneTarget();
1315
+ const named = databases.filter((d) => d.database === database);
1316
+ if (named.length === 1)
1317
+ return fromStack(named[0]);
1318
+ if (named.length > 1) {
1319
+ throw new Error(`'${database}' is a database of ${named.length} running stacks — name one: ${named.map(keyOf).join(', ')}`);
278
1320
  }
279
- return { dbConfig: entry.dbConfig, isDocker: true, containerName: entry.containerName };
1321
+ if (database === CONTROL_PLANE_DB)
1322
+ return this.controlPlaneTarget();
1323
+ // Unknown — fail loudly rather than silently dumping the wrong DB (the
1324
+ // pre-fix behaviour that made `backup create main` hit the wrong database
1325
+ // entirely).
1326
+ const known = [...new Set(databases.map((d) => d.database))];
1327
+ throw new Error(`Unknown backup target '${database}'. Known databases: ${known.length ? known.join(', ') : '(no running stack)'}, omnitron`);
280
1328
  }
281
- parseCronInterval(cron) {
282
- // Simple interval parsing for MVP:
283
- // "daily" = 24h, "hourly" = 1h, "weekly" = 7d, or numeric ms
284
- const presets = {
285
- 'hourly': 3_600_000,
286
- 'daily': 86_400_000,
287
- 'weekly': 604_800_000,
1329
+ /**
1330
+ * The control-plane database, from the resolver the daemon itself connects
1331
+ * with rather than a second copy of its credentials — `localhost:5480`,
1332
+ * `omnitron`/`omnitron` were written out here by hand. Dumped through the
1333
+ * `omnitron-pg` container unless OMNITRON_DATABASE_URL points elsewhere.
1334
+ */
1335
+ controlPlaneTarget() {
1336
+ const pg = resolveOmnitronPgConfig();
1337
+ const elsewhere = !!getEnv().OMNITRON_DATABASE_URL;
1338
+ return {
1339
+ dbConfig: { host: pg.host, port: pg.port, user: pg.user, password: pg.password, database: pg.database },
1340
+ isDocker: !elsewhere,
1341
+ containerName: elsewhere ? '' : 'omnitron-pg',
1342
+ origin: 'control-plane',
288
1343
  };
289
- const preset = presets[cron.toLowerCase()];
290
- if (preset)
291
- return preset;
292
- const num = parseInt(cron, 10);
293
- if (!isNaN(num) && num > 0)
294
- return num;
295
- // Default: daily
296
- return 86_400_000;
297
1344
  }
298
- }
1345
+ /** Where a backup being restored goes: where it was taken from. */
1346
+ restoreTargetOf(app, meta) {
1347
+ if (meta.scope === 'control-plane')
1348
+ return this.controlPlaneTarget();
1349
+ if (meta.project && meta.stack) {
1350
+ const key = `${meta.project}/${meta.stack}/${app}`;
1351
+ if (this.surveyStacks().databases.some((d) => `${d.project}/${d.stack}/${d.database}` === key)) {
1352
+ return this.resolveDbTarget(key);
1353
+ }
1354
+ throw new Error(`This backup of '${app}' was taken from ${meta.project}/${meta.stack}, ` +
1355
+ `which has no running database '${app}' on this host — refusing to restore it anywhere else`);
1356
+ }
1357
+ // Taken before the stack was recorded: the one running stack that has a
1358
+ // database of this name, or a refusal naming them all.
1359
+ return this.resolveDbTarget(app);
1360
+ }
1361
+ /**
1362
+ * Every running stack, and every database of it this host can reach — from
1363
+ * the provisioned InfrastructureService, so the backup set is always in
1364
+ * lock-step with what omnitron provisioned.
1365
+ *
1366
+ * A stack whose infrastructure is not on this host used to be skipped with
1367
+ * `if (!infra) continue`, and nothing else ever mentioned it. On the master
1368
+ * that is daos/test, whose database container runs on 37.27.130.185: every
1369
+ * `all` pass was `{"total":6}` — daos/dev's six — while `backup list`
1370
+ * printed «Found 380 backup(s)» and `backup schedules` «all hourly».
1371
+ * Remote stacks are still not dumped from here; they are now named.
1372
+ */
1373
+ surveyStacks() {
1374
+ const databases = [];
1375
+ const stacks = [];
1376
+ const projects = this.projects;
1377
+ if (!projects)
1378
+ return { databases, stacks };
1379
+ try {
1380
+ const infraManager = projects.getInfraManager();
1381
+ for (const p of projects.listProjects()) {
1382
+ let running = [];
1383
+ try {
1384
+ running = projects.getRunningStacks(p.name);
1385
+ }
1386
+ catch {
1387
+ continue;
1388
+ }
1389
+ for (const stack of running) {
1390
+ const infra = infraManager.getInstance(p.name, stack);
1391
+ if (!infra) {
1392
+ stacks.push({ project: p.name, stack, databases: [], notBackedUp: this.whyNotHere(p.name, stack) });
1393
+ continue;
1394
+ }
1395
+ const conn = infra.getConnectionInfo('postgres');
1396
+ if (!conn) {
1397
+ // No postgres in this stack: nothing to dump, and nothing missed.
1398
+ stacks.push({ project: p.name, stack, databases: [] });
1399
+ continue;
1400
+ }
1401
+ const container = infra.getResolvedContainerName('postgres') ?? undefined;
1402
+ const names = infra.getPostgresDatabases();
1403
+ for (const db of names) {
1404
+ databases.push({
1405
+ container,
1406
+ host: String(conn.host ?? 'localhost'),
1407
+ port: Number(conn.port ?? 5432),
1408
+ user: String(conn.user ?? 'postgres'),
1409
+ password: String(conn.password ?? 'postgres'),
1410
+ database: db,
1411
+ project: p.name,
1412
+ stack,
1413
+ });
1414
+ }
1415
+ stacks.push({ project: p.name, stack, databases: [...names] });
1416
+ }
1417
+ }
1418
+ }
1419
+ catch (err) {
1420
+ this.logger.warn({ err: err.message }, 'Failed to resolve stack DB topology for backup');
1421
+ }
1422
+ return { databases, stacks };
1423
+ }
1424
+ /** Why a running stack's databases cannot be reached from this host. */
1425
+ whyNotHere(project, stack) {
1426
+ try {
1427
+ const info = this.projects.getStack(project, stack);
1428
+ if (info.type === 'local')
1429
+ return 'its infrastructure is not provisioned on this host';
1430
+ const hosts = [...new Set((info.nodes ?? []).map((n) => n.host).filter(Boolean))];
1431
+ if (hosts.length > 0)
1432
+ return `its databases are on ${hosts.join(', ')}`;
1433
+ }
1434
+ catch { /* its configuration is not loaded — say what is known */ }
1435
+ return 'its infrastructure is not on this host';
1436
+ }
1437
+ /**
1438
+ * Back up every database of every running stack. A single DB failure is
1439
+ * captured per-entry and does not abort the rest. Returns per-DB results,
1440
+ * and — marked `skipped` — every running stack it could not reach.
1441
+ */
1442
+ async createAllBackups(trigger = 'manual') {
1443
+ const startedAt = new Date();
1444
+ const results = await this.backUpStackDatabases();
1445
+ this.finishPass('all', trigger, startedAt, results.map(({ database, ...rest }) => qualifiedEntry({ target: database, ...rest })));
1446
+ return results;
1447
+ }
1448
+ /**
1449
+ * The database half of `all` and `full`, stack by stack. Each failure is
1450
+ * logged where it happens, with the database and its reason: the 06:04Z
1451
+ * pass on the master lost `main` to the 600 s dump timeout, and the only
1452
+ * trace was a database missing from the next listing.
1453
+ *
1454
+ * Every database of every stack, by its `<project>/<stack>/<db>` key: by
1455
+ * bare name, a second stack's `main` was never dumped at all.
1456
+ */
1457
+ async backUpStackDatabases() {
1458
+ const { databases, stacks } = this.surveyStacks();
1459
+ const results = [];
1460
+ for (const d of databases) {
1461
+ const where = { project: d.project, stack: d.stack };
1462
+ try {
1463
+ const info = await this.createBackup(`${d.project}/${d.stack}/${d.database}`, { compress: true });
1464
+ results.push({ database: d.database, ...where, ok: true, id: info.id, size: info.size });
1465
+ }
1466
+ catch (err) {
1467
+ results.push({ database: d.database, ...where, ok: false, error: err.message });
1468
+ this.logger.error({ database: d.database, ...where, error: reasonOf(err) }, 'Backup failed');
1469
+ }
1470
+ }
1471
+ // The stacks nothing here reaches, in the results as well: a caller that
1472
+ // counts `ok` has to see that «all» did not mean every stack.
1473
+ for (const s of stacks) {
1474
+ if (!s.notBackedUp)
1475
+ continue;
1476
+ results.push({
1477
+ database: `${s.project}/${s.stack}`,
1478
+ project: s.project,
1479
+ stack: s.stack,
1480
+ ok: false,
1481
+ skipped: true,
1482
+ error: `not backed up — ${s.notBackedUp}`,
1483
+ });
1484
+ }
1485
+ return results;
1486
+ }
1487
+ };
1488
+ BackupService = BackupService_1 = __decorate([
1489
+ Injectable(),
1490
+ __param(0, Inject(LOGGER_SERVICE_TOKEN)),
1491
+ __param(1, Inject(DAEMON_STATE_STORE_TOKEN)),
1492
+ __param(2, Optional()),
1493
+ __param(2, Inject(PROJECT_SERVICE_TOKEN)),
1494
+ __metadata("design:paramtypes", [Object, Function, Function])
1495
+ ], BackupService);
1496
+ export { BackupService };
299
1497
  //# sourceMappingURL=backup.service.js.map