@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.
- package/dist/BUILD.json +5 -0
- package/dist/cli/omnitron.js +351 -29
- package/dist/cli/omnitron.js.map +1 -1
- package/dist/cluster/cluster.rpc-service.d.ts +40 -0
- package/dist/cluster/cluster.rpc-service.d.ts.map +1 -1
- package/dist/cluster/cluster.rpc-service.js +49 -3
- package/dist/cluster/cluster.rpc-service.js.map +1 -1
- package/dist/cluster/config-sync.d.ts.map +1 -1
- package/dist/cluster/config-sync.js.map +1 -1
- package/dist/cluster/leader-election.d.ts +90 -2
- package/dist/cluster/leader-election.d.ts.map +1 -1
- package/dist/cluster/leader-election.js +176 -22
- package/dist/cluster/leader-election.js.map +1 -1
- package/dist/cluster/mesh-link.d.ts +136 -0
- package/dist/cluster/mesh-link.d.ts.map +1 -0
- package/dist/cluster/mesh-link.js +212 -0
- package/dist/cluster/mesh-link.js.map +1 -0
- package/dist/cluster/mesh.d.ts +59 -0
- package/dist/cluster/mesh.d.ts.map +1 -0
- package/dist/cluster/mesh.js +117 -0
- package/dist/cluster/mesh.js.map +1 -0
- package/dist/cluster/slave-connector.d.ts +193 -9
- package/dist/cluster/slave-connector.d.ts.map +1 -1
- package/dist/cluster/slave-connector.js +409 -37
- package/dist/cluster/slave-connector.js.map +1 -1
- package/dist/commands/audit.d.ts +65 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +233 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/backup.d.ts +4 -0
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +255 -33
- package/dist/commands/backup.js.map +1 -1
- package/dist/commands/cluster.d.ts.map +1 -1
- package/dist/commands/cluster.js +78 -47
- package/dist/commands/cluster.js.map +1 -1
- package/dist/commands/daemon-cmd.d.ts +7 -1
- package/dist/commands/daemon-cmd.d.ts.map +1 -1
- package/dist/commands/daemon-cmd.js +87 -21
- package/dist/commands/daemon-cmd.js.map +1 -1
- package/dist/commands/daemon-required.d.ts +34 -0
- package/dist/commands/daemon-required.d.ts.map +1 -0
- package/dist/commands/daemon-required.js +92 -0
- package/dist/commands/daemon-required.js.map +1 -0
- package/dist/commands/deploy.d.ts +70 -8
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +99 -64
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/discover.d.ts +25 -4
- package/dist/commands/discover.d.ts.map +1 -1
- package/dist/commands/discover.js +155 -102
- package/dist/commands/discover.js.map +1 -1
- package/dist/commands/doctor.d.ts +293 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +1706 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/env.d.ts +8 -1
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +13 -8
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/exec.d.ts.map +1 -1
- package/dist/commands/exec.js +3 -2
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/fleet-asking.d.ts +87 -0
- package/dist/commands/fleet-asking.d.ts.map +1 -0
- package/dist/commands/fleet-asking.js +176 -0
- package/dist/commands/fleet-asking.js.map +1 -0
- package/dist/commands/fleet.d.ts +18 -0
- package/dist/commands/fleet.d.ts.map +1 -1
- package/dist/commands/fleet.js +314 -74
- package/dist/commands/fleet.js.map +1 -1
- package/dist/commands/health-check.d.ts +8 -6
- package/dist/commands/health-check.d.ts.map +1 -1
- package/dist/commands/health-check.js +26 -23
- package/dist/commands/health-check.js.map +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +22 -8
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/infra.d.ts +42 -1
- package/dist/commands/infra.d.ts.map +1 -1
- package/dist/commands/infra.js +418 -49
- package/dist/commands/infra.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +21 -36
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/inspect-graph.d.ts.map +1 -1
- package/dist/commands/inspect-graph.js +2 -6
- package/dist/commands/inspect-graph.js.map +1 -1
- package/dist/commands/inspect.d.ts.map +1 -1
- package/dist/commands/inspect.js +110 -14
- package/dist/commands/inspect.js.map +1 -1
- package/dist/commands/k8s.d.ts.map +1 -1
- package/dist/commands/k8s.js +23 -18
- package/dist/commands/k8s.js.map +1 -1
- package/dist/commands/kb.d.ts.map +1 -1
- package/dist/commands/kb.js +88 -29
- package/dist/commands/kb.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +21 -9
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/log-health.d.ts +120 -0
- package/dist/commands/log-health.d.ts.map +1 -0
- package/dist/commands/log-health.js +212 -0
- package/dist/commands/log-health.js.map +1 -0
- package/dist/commands/logs.d.ts +96 -2
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +522 -196
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/metrics.d.ts.map +1 -1
- package/dist/commands/metrics.js +35 -17
- package/dist/commands/metrics.js.map +1 -1
- package/dist/commands/monit.d.ts.map +1 -1
- package/dist/commands/monit.js +3 -2
- package/dist/commands/monit.js.map +1 -1
- package/dist/commands/node.d.ts +68 -0
- package/dist/commands/node.d.ts.map +1 -1
- package/dist/commands/node.js +285 -68
- package/dist/commands/node.js.map +1 -1
- package/dist/commands/output.d.ts +10 -0
- package/dist/commands/output.d.ts.map +1 -1
- package/dist/commands/output.js +41 -1
- package/dist/commands/output.js.map +1 -1
- package/dist/commands/pipeline.d.ts.map +1 -1
- package/dist/commands/pipeline.js +26 -19
- package/dist/commands/pipeline.js.map +1 -1
- package/dist/commands/project.d.ts +19 -0
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +125 -27
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/release-store.d.ts +28 -0
- package/dist/commands/release-store.d.ts.map +1 -0
- package/dist/commands/release-store.js +158 -0
- package/dist/commands/release-store.js.map +1 -0
- package/dist/commands/release.d.ts +61 -0
- package/dist/commands/release.d.ts.map +1 -0
- package/dist/commands/release.js +555 -0
- package/dist/commands/release.js.map +1 -0
- package/dist/commands/reload.d.ts +16 -0
- package/dist/commands/reload.d.ts.map +1 -1
- package/dist/commands/reload.js +21 -5
- package/dist/commands/reload.js.map +1 -1
- package/dist/commands/remote.d.ts +40 -0
- package/dist/commands/remote.d.ts.map +1 -1
- package/dist/commands/remote.js +146 -35
- package/dist/commands/remote.js.map +1 -1
- package/dist/commands/restart.d.ts +16 -0
- package/dist/commands/restart.d.ts.map +1 -1
- package/dist/commands/restart.js +21 -5
- package/dist/commands/restart.js.map +1 -1
- package/dist/commands/scale.d.ts.map +1 -1
- package/dist/commands/scale.js +4 -3
- package/dist/commands/scale.js.map +1 -1
- package/dist/commands/secret.d.ts +54 -0
- package/dist/commands/secret.d.ts.map +1 -1
- package/dist/commands/secret.js +236 -34
- package/dist/commands/secret.js.map +1 -1
- package/dist/commands/service-units.d.ts +181 -0
- package/dist/commands/service-units.d.ts.map +1 -0
- package/dist/commands/service-units.js +291 -0
- package/dist/commands/service-units.js.map +1 -0
- package/dist/commands/service.d.ts +39 -0
- package/dist/commands/service.d.ts.map +1 -0
- package/dist/commands/service.js +370 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/spinner.d.ts +29 -0
- package/dist/commands/spinner.d.ts.map +1 -0
- package/dist/commands/spinner.js +44 -0
- package/dist/commands/spinner.js.map +1 -0
- package/dist/commands/stack.d.ts +49 -1
- package/dist/commands/stack.d.ts.map +1 -1
- package/dist/commands/stack.js +325 -12
- package/dist/commands/stack.js.map +1 -1
- package/dist/commands/start.d.ts +16 -0
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +20 -3
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +45 -29
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/stop.d.ts +16 -0
- package/dist/commands/stop.d.ts.map +1 -1
- package/dist/commands/stop.js +34 -6
- package/dist/commands/stop.js.map +1 -1
- package/dist/commands/tor.d.ts +14 -7
- package/dist/commands/tor.d.ts.map +1 -1
- package/dist/commands/tor.js +113 -33
- package/dist/commands/tor.js.map +1 -1
- package/dist/commands/up.d.ts +85 -1
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +180 -32
- package/dist/commands/up.js.map +1 -1
- package/dist/commands/webapp.d.ts.map +1 -1
- package/dist/commands/webapp.js +2 -1
- package/dist/commands/webapp.js.map +1 -1
- package/dist/config/defaults.d.ts +16 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +26 -3
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/define-ecosystem.d.ts.map +1 -1
- package/dist/config/define-ecosystem.js +2 -0
- package/dist/config/define-ecosystem.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/loader.d.ts +45 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +64 -6
- package/dist/config/loader.js.map +1 -1
- package/dist/config/omnitron-config.schema.d.ts.map +1 -1
- package/dist/config/omnitron-config.schema.js +16 -0
- package/dist/config/omnitron-config.schema.js.map +1 -1
- package/dist/config/types.d.ts +411 -42
- package/dist/config/types.d.ts.map +1 -1
- package/dist/daemon/config-reload.d.ts +27 -0
- package/dist/daemon/config-reload.d.ts.map +1 -0
- package/dist/daemon/config-reload.js +42 -0
- package/dist/daemon/config-reload.js.map +1 -0
- package/dist/daemon/daemon-client.d.ts +136 -2
- package/dist/daemon/daemon-client.d.ts.map +1 -1
- package/dist/daemon/daemon-client.js +307 -21
- package/dist/daemon/daemon-client.js.map +1 -1
- package/dist/daemon/daemon-entry.js +90 -30
- package/dist/daemon/daemon-entry.js.map +1 -1
- package/dist/daemon/daemon-scheduler.d.ts +4 -0
- package/dist/daemon/daemon-scheduler.d.ts.map +1 -1
- package/dist/daemon/daemon-scheduler.js +137 -7
- package/dist/daemon/daemon-scheduler.js.map +1 -1
- package/dist/daemon/daemon-state-store.service.d.ts +263 -0
- package/dist/daemon/daemon-state-store.service.d.ts.map +1 -0
- package/dist/daemon/daemon-state-store.service.js +446 -0
- package/dist/daemon/daemon-state-store.service.js.map +1 -0
- package/dist/daemon/daemon.d.ts +205 -1
- package/dist/daemon/daemon.d.ts.map +1 -1
- package/dist/daemon/daemon.js +1139 -139
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/daemon.module.d.ts.map +1 -1
- package/dist/daemon/daemon.module.js +163 -83
- package/dist/daemon/daemon.module.js.map +1 -1
- package/dist/daemon/daemon.rpc-service.d.ts +90 -1
- package/dist/daemon/daemon.rpc-service.d.ts.map +1 -1
- package/dist/daemon/daemon.rpc-service.js +438 -96
- package/dist/daemon/daemon.rpc-service.js.map +1 -1
- package/dist/daemon/pid-manager.d.ts +16 -3
- package/dist/daemon/pid-manager.d.ts.map +1 -1
- package/dist/daemon/pid-manager.js +31 -4
- package/dist/daemon/pid-manager.js.map +1 -1
- package/dist/daemon/spawn-daemon.d.ts +73 -0
- package/dist/daemon/spawn-daemon.d.ts.map +1 -0
- package/dist/daemon/spawn-daemon.js +101 -0
- package/dist/daemon/spawn-daemon.js.map +1 -0
- package/dist/daemon/state-store.d.ts +65 -3
- package/dist/daemon/state-store.d.ts.map +1 -1
- package/dist/daemon/state-store.js +100 -58
- package/dist/daemon/state-store.js.map +1 -1
- package/dist/database/connection.d.ts +71 -0
- package/dist/database/connection.d.ts.map +1 -0
- package/dist/database/connection.js +129 -0
- package/dist/database/connection.js.map +1 -0
- package/dist/database/dialect.d.ts +60 -0
- package/dist/database/dialect.d.ts.map +1 -0
- package/dist/database/dialect.js +80 -0
- package/dist/database/dialect.js.map +1 -0
- package/dist/database/migrate.d.ts +5 -2
- package/dist/database/migrate.d.ts.map +1 -1
- package/dist/database/migrate.js +23 -33
- package/dist/database/migrate.js.map +1 -1
- package/dist/database/migration-runner.d.ts +50 -0
- package/dist/database/migration-runner.d.ts.map +1 -0
- package/dist/database/migration-runner.js +143 -0
- package/dist/database/migration-runner.js.map +1 -0
- package/dist/database/migrations/006_login_throttle.d.ts +17 -0
- package/dist/database/migrations/006_login_throttle.d.ts.map +1 -0
- package/dist/database/migrations/006_login_throttle.js +33 -0
- package/dist/database/migrations/006_login_throttle.js.map +1 -0
- package/dist/database/migrations/007_sync_ingested.d.ts +21 -0
- package/dist/database/migrations/007_sync_ingested.d.ts.map +1 -0
- package/dist/database/migrations/007_sync_ingested.js +39 -0
- package/dist/database/migrations/007_sync_ingested.js.map +1 -0
- package/dist/database/migrations/008_node_health_version_text.d.ts +24 -0
- package/dist/database/migrations/008_node_health_version_text.d.ts.map +1 -0
- package/dist/database/migrations/008_node_health_version_text.js +35 -0
- package/dist/database/migrations/008_node_health_version_text.js.map +1 -0
- package/dist/database/migrations/009_node_health_unmeasured.d.ts +40 -0
- package/dist/database/migrations/009_node_health_unmeasured.d.ts.map +1 -0
- package/dist/database/migrations/009_node_health_unmeasured.js +63 -0
- package/dist/database/migrations/009_node_health_unmeasured.js.map +1 -0
- package/dist/database/migrations/010_metrics_raw_had_no_reader.d.ts +22 -0
- package/dist/database/migrations/010_metrics_raw_had_no_reader.d.ts.map +1 -0
- package/dist/database/migrations/010_metrics_raw_had_no_reader.js +26 -0
- package/dist/database/migrations/010_metrics_raw_had_no_reader.js.map +1 -0
- package/dist/database/migrations/011_alerts_nobody_had_to_write.d.ts +56 -0
- package/dist/database/migrations/011_alerts_nobody_had_to_write.d.ts.map +1 -0
- package/dist/database/migrations/011_alerts_nobody_had_to_write.js +79 -0
- package/dist/database/migrations/011_alerts_nobody_had_to_write.js.map +1 -0
- package/dist/database/migrations/012_a_disk_nobody_watched.d.ts +26 -0
- package/dist/database/migrations/012_a_disk_nobody_watched.d.ts.map +1 -0
- package/dist/database/migrations/012_a_disk_nobody_watched.js +40 -0
- package/dist/database/migrations/012_a_disk_nobody_watched.js.map +1 -0
- package/dist/database/migrations/013_an_alarm_delivered.d.ts +14 -0
- package/dist/database/migrations/013_an_alarm_delivered.d.ts.map +1 -0
- package/dist/database/migrations/013_an_alarm_delivered.js +28 -0
- package/dist/database/migrations/013_an_alarm_delivered.js.map +1 -0
- package/dist/database/migrations/index.d.ts +21 -0
- package/dist/database/migrations/index.d.ts.map +1 -0
- package/dist/database/migrations/index.js +47 -0
- package/dist/database/migrations/index.js.map +1 -0
- package/dist/database/schema.d.ts +27 -11
- package/dist/database/schema.d.ts.map +1 -1
- package/dist/database/sqlite-date-binding.d.ts +71 -0
- package/dist/database/sqlite-date-binding.d.ts.map +1 -0
- package/dist/database/sqlite-date-binding.js +174 -0
- package/dist/database/sqlite-date-binding.js.map +1 -0
- package/dist/execution/data-channel.d.ts +48 -0
- package/dist/execution/data-channel.d.ts.map +1 -0
- package/dist/execution/data-channel.js +55 -0
- package/dist/execution/data-channel.js.map +1 -0
- package/dist/execution/execution.service.d.ts +139 -0
- package/dist/execution/execution.service.d.ts.map +1 -1
- package/dist/execution/execution.service.js +273 -10
- package/dist/execution/execution.service.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/infrastructure/bare-metal-plan.d.ts +208 -0
- package/dist/infrastructure/bare-metal-plan.d.ts.map +1 -0
- package/dist/infrastructure/bare-metal-plan.js +363 -0
- package/dist/infrastructure/bare-metal-plan.js.map +1 -0
- package/dist/infrastructure/bare-metal-runner.d.ts +65 -0
- package/dist/infrastructure/bare-metal-runner.d.ts.map +1 -0
- package/dist/infrastructure/bare-metal-runner.js +354 -0
- package/dist/infrastructure/bare-metal-runner.js.map +1 -0
- package/dist/infrastructure/config-payload.d.ts +149 -0
- package/dist/infrastructure/config-payload.d.ts.map +1 -0
- package/dist/infrastructure/config-payload.js +235 -0
- package/dist/infrastructure/config-payload.js.map +1 -0
- package/dist/infrastructure/container-runtime.d.ts +169 -1
- package/dist/infrastructure/container-runtime.d.ts.map +1 -1
- package/dist/infrastructure/container-runtime.js +525 -33
- package/dist/infrastructure/container-runtime.js.map +1 -1
- package/dist/infrastructure/gateway-reachability.d.ts +85 -0
- package/dist/infrastructure/gateway-reachability.d.ts.map +1 -0
- package/dist/infrastructure/gateway-reachability.js +116 -0
- package/dist/infrastructure/gateway-reachability.js.map +1 -0
- package/dist/infrastructure/gateway-redis-db.d.ts +29 -0
- package/dist/infrastructure/gateway-redis-db.d.ts.map +1 -0
- package/dist/infrastructure/gateway-redis-db.js +32 -0
- package/dist/infrastructure/gateway-redis-db.js.map +1 -0
- package/dist/infrastructure/host-inspection.d.ts +223 -0
- package/dist/infrastructure/host-inspection.d.ts.map +1 -0
- package/dist/infrastructure/host-inspection.js +501 -0
- package/dist/infrastructure/host-inspection.js.map +1 -0
- package/dist/infrastructure/infrastructure-gate.d.ts.map +1 -1
- package/dist/infrastructure/infrastructure.service.d.ts +206 -6
- package/dist/infrastructure/infrastructure.service.d.ts.map +1 -1
- package/dist/infrastructure/infrastructure.service.js +415 -89
- package/dist/infrastructure/infrastructure.service.js.map +1 -1
- package/dist/infrastructure/known-machines.d.ts +76 -0
- package/dist/infrastructure/known-machines.d.ts.map +1 -0
- package/dist/infrastructure/known-machines.js +94 -0
- package/dist/infrastructure/known-machines.js.map +1 -0
- package/dist/infrastructure/node-credentials.d.ts +45 -0
- package/dist/infrastructure/node-credentials.d.ts.map +1 -0
- package/dist/infrastructure/node-credentials.js +80 -0
- package/dist/infrastructure/node-credentials.js.map +1 -0
- package/dist/infrastructure/orphan-containers.d.ts +99 -0
- package/dist/infrastructure/orphan-containers.d.ts.map +1 -0
- package/dist/infrastructure/orphan-containers.js +106 -0
- package/dist/infrastructure/orphan-containers.js.map +1 -0
- package/dist/infrastructure/phantom-endpoint-janitor.d.ts +20 -0
- package/dist/infrastructure/phantom-endpoint-janitor.d.ts.map +1 -1
- package/dist/infrastructure/phantom-endpoint-janitor.js +56 -6
- package/dist/infrastructure/phantom-endpoint-janitor.js.map +1 -1
- package/dist/infrastructure/presets/gateway-openresty.d.ts.map +1 -1
- package/dist/infrastructure/presets/gateway-openresty.js +29 -3
- package/dist/infrastructure/presets/gateway-openresty.js.map +1 -1
- package/dist/infrastructure/presets/minio.d.ts.map +1 -1
- package/dist/infrastructure/presets/minio.js +36 -3
- package/dist/infrastructure/presets/minio.js.map +1 -1
- package/dist/infrastructure/presets/postgres.d.ts.map +1 -1
- package/dist/infrastructure/presets/postgres.js +69 -2
- package/dist/infrastructure/presets/postgres.js.map +1 -1
- package/dist/infrastructure/presets/redis.d.ts.map +1 -1
- package/dist/infrastructure/presets/redis.js +13 -0
- package/dist/infrastructure/presets/redis.js.map +1 -1
- package/dist/infrastructure/presets/registry.d.ts.map +1 -1
- package/dist/infrastructure/presets/registry.js +81 -12
- package/dist/infrastructure/presets/registry.js.map +1 -1
- package/dist/infrastructure/presets/tor.d.ts +38 -0
- package/dist/infrastructure/presets/tor.d.ts.map +1 -1
- package/dist/infrastructure/presets/tor.js +78 -69
- package/dist/infrastructure/presets/tor.js.map +1 -1
- package/dist/infrastructure/presets/torrc.d.ts +54 -0
- package/dist/infrastructure/presets/torrc.d.ts.map +1 -0
- package/dist/infrastructure/presets/torrc.js +153 -0
- package/dist/infrastructure/presets/torrc.js.map +1 -0
- package/dist/infrastructure/presets/types.d.ts +17 -0
- package/dist/infrastructure/presets/types.d.ts.map +1 -1
- package/dist/infrastructure/provisioning-outcome.d.ts +85 -0
- package/dist/infrastructure/provisioning-outcome.d.ts.map +1 -0
- package/dist/infrastructure/provisioning-outcome.js +122 -0
- package/dist/infrastructure/provisioning-outcome.js.map +1 -0
- package/dist/infrastructure/server-registry.d.ts +27 -0
- package/dist/infrastructure/server-registry.d.ts.map +1 -1
- package/dist/infrastructure/server-registry.js +60 -5
- package/dist/infrastructure/server-registry.js.map +1 -1
- package/dist/infrastructure/service-binding.d.ts +60 -0
- package/dist/infrastructure/service-binding.d.ts.map +1 -0
- package/dist/infrastructure/service-binding.js +90 -0
- package/dist/infrastructure/service-binding.js.map +1 -0
- package/dist/infrastructure/service-credentials.d.ts +105 -0
- package/dist/infrastructure/service-credentials.d.ts.map +1 -0
- package/dist/infrastructure/service-credentials.js +141 -0
- package/dist/infrastructure/service-credentials.js.map +1 -0
- package/dist/infrastructure/service-resolver.d.ts +160 -12
- package/dist/infrastructure/service-resolver.d.ts.map +1 -1
- package/dist/infrastructure/service-resolver.js +463 -53
- package/dist/infrastructure/service-resolver.js.map +1 -1
- package/dist/infrastructure/shipped-config.d.ts +43 -0
- package/dist/infrastructure/shipped-config.d.ts.map +1 -0
- package/dist/infrastructure/shipped-config.js +159 -0
- package/dist/infrastructure/shipped-config.js.map +1 -0
- package/dist/infrastructure/stack-infra-manager.d.ts +6 -0
- package/dist/infrastructure/stack-infra-manager.d.ts.map +1 -1
- package/dist/infrastructure/stack-infra-manager.js +31 -13
- package/dist/infrastructure/stack-infra-manager.js.map +1 -1
- package/dist/infrastructure/types.d.ts +261 -6
- package/dist/infrastructure/types.d.ts.map +1 -1
- package/dist/mcp/mcp-bridge.d.ts.map +1 -1
- package/dist/mcp/mcp-bridge.js.map +1 -1
- package/dist/mcp/tool-groups/apps.tools.d.ts +2 -1
- package/dist/mcp/tool-groups/apps.tools.d.ts.map +1 -1
- package/dist/mcp/tool-groups/apps.tools.js +2 -2
- package/dist/mcp/tool-groups/apps.tools.js.map +1 -1
- package/dist/mcp/tool-groups/infra.tools.d.ts +16 -2
- package/dist/mcp/tool-groups/infra.tools.d.ts.map +1 -1
- package/dist/mcp/tool-groups/infra.tools.js +51 -41
- package/dist/mcp/tool-groups/infra.tools.js.map +1 -1
- package/dist/mcp/tool-groups/kb.tools.d.ts.map +1 -1
- package/dist/mcp/tool-groups/kb.tools.js +4 -6
- package/dist/mcp/tool-groups/kb.tools.js.map +1 -1
- package/dist/mcp/tool-groups/management.tools.d.ts +20 -3
- package/dist/mcp/tool-groups/management.tools.d.ts.map +1 -1
- package/dist/mcp/tool-groups/management.tools.js +158 -125
- package/dist/mcp/tool-groups/management.tools.js.map +1 -1
- package/dist/mcp/tool-groups/monitoring.tools.d.ts +10 -1
- package/dist/mcp/tool-groups/monitoring.tools.d.ts.map +1 -1
- package/dist/mcp/tool-groups/monitoring.tools.js +65 -18
- package/dist/mcp/tool-groups/monitoring.tools.js.map +1 -1
- package/dist/mcp/unavailable-tools.d.ts +35 -0
- package/dist/mcp/unavailable-tools.d.ts.map +1 -0
- package/dist/mcp/unavailable-tools.js +59 -0
- package/dist/mcp/unavailable-tools.js.map +1 -0
- package/dist/monitoring/app-health.indicator.d.ts.map +1 -1
- package/dist/monitoring/app-health.indicator.js +12 -0
- package/dist/monitoring/app-health.indicator.js.map +1 -1
- package/dist/monitoring/desktop-notifier.d.ts +26 -0
- package/dist/monitoring/desktop-notifier.d.ts.map +1 -0
- package/dist/monitoring/desktop-notifier.js +49 -0
- package/dist/monitoring/desktop-notifier.js.map +1 -0
- package/dist/monitoring/docker-health.indicator.d.ts +17 -1
- package/dist/monitoring/docker-health.indicator.d.ts.map +1 -1
- package/dist/monitoring/docker-health.indicator.js +38 -8
- package/dist/monitoring/docker-health.indicator.js.map +1 -1
- package/dist/monitoring/event-loop-stall.indicator.d.ts +26 -0
- package/dist/monitoring/event-loop-stall.indicator.d.ts.map +1 -0
- package/dist/monitoring/event-loop-stall.indicator.js +47 -0
- package/dist/monitoring/event-loop-stall.indicator.js.map +1 -0
- package/dist/monitoring/event-loop-watch.d.ts +124 -0
- package/dist/monitoring/event-loop-watch.d.ts.map +1 -0
- package/dist/monitoring/event-loop-watch.js +179 -0
- package/dist/monitoring/event-loop-watch.js.map +1 -0
- package/dist/monitoring/host-disk.d.ts +6 -0
- package/dist/monitoring/host-disk.d.ts.map +1 -0
- package/dist/monitoring/host-disk.js +26 -0
- package/dist/monitoring/host-disk.js.map +1 -0
- package/dist/monitoring/log-line.d.ts +188 -0
- package/dist/monitoring/log-line.d.ts.map +1 -0
- package/dist/monitoring/log-line.js +458 -0
- package/dist/monitoring/log-line.js.map +1 -0
- package/dist/monitoring/log-manager.d.ts +89 -8
- package/dist/monitoring/log-manager.d.ts.map +1 -1
- package/dist/monitoring/log-manager.js +246 -77
- package/dist/monitoring/log-manager.js.map +1 -1
- package/dist/observability/metrics-bridge.d.ts.map +1 -1
- package/dist/observability/metrics-server.d.ts.map +1 -1
- package/dist/orchestrator/app-handle.d.ts +157 -2
- package/dist/orchestrator/app-handle.d.ts.map +1 -1
- package/dist/orchestrator/app-handle.js +165 -3
- package/dist/orchestrator/app-handle.js.map +1 -1
- package/dist/orchestrator/app-health.d.ts +33 -0
- package/dist/orchestrator/app-health.d.ts.map +1 -0
- package/dist/orchestrator/app-health.js +74 -0
- package/dist/orchestrator/app-health.js.map +1 -0
- package/dist/orchestrator/app-traffic.d.ts +14 -0
- package/dist/orchestrator/app-traffic.d.ts.map +1 -0
- package/dist/orchestrator/app-traffic.js +33 -0
- package/dist/orchestrator/app-traffic.js.map +1 -0
- package/dist/orchestrator/bootstrap-loader-child.d.ts +19 -0
- package/dist/orchestrator/bootstrap-loader-child.d.ts.map +1 -0
- package/dist/orchestrator/bootstrap-loader-child.js +43 -0
- package/dist/orchestrator/bootstrap-loader-child.js.map +1 -0
- package/dist/orchestrator/bootstrap-loader.d.ts +72 -0
- package/dist/orchestrator/bootstrap-loader.d.ts.map +1 -1
- package/dist/orchestrator/bootstrap-loader.js +350 -5
- package/dist/orchestrator/bootstrap-loader.js.map +1 -1
- package/dist/orchestrator/bootstrap-process.d.ts +99 -4
- package/dist/orchestrator/bootstrap-process.d.ts.map +1 -1
- package/dist/orchestrator/bootstrap-process.js +330 -19
- package/dist/orchestrator/bootstrap-process.js.map +1 -1
- package/dist/orchestrator/build-service.d.ts.map +1 -1
- package/dist/orchestrator/build-service.js.map +1 -1
- package/dist/orchestrator/call-exposed-service.d.ts +7 -0
- package/dist/orchestrator/call-exposed-service.d.ts.map +1 -0
- package/dist/orchestrator/call-exposed-service.js +50 -0
- package/dist/orchestrator/call-exposed-service.js.map +1 -0
- package/dist/orchestrator/classic-launcher.d.ts +1 -0
- package/dist/orchestrator/classic-launcher.d.ts.map +1 -1
- package/dist/orchestrator/classic-launcher.js +34 -3
- package/dist/orchestrator/classic-launcher.js.map +1 -1
- package/dist/orchestrator/file-watcher.d.ts +78 -1
- package/dist/orchestrator/file-watcher.d.ts.map +1 -1
- package/dist/orchestrator/file-watcher.js +181 -6
- package/dist/orchestrator/file-watcher.js.map +1 -1
- package/dist/orchestrator/module-worker-process.d.ts.map +1 -1
- package/dist/orchestrator/module-worker-process.js +10 -0
- package/dist/orchestrator/module-worker-process.js.map +1 -1
- package/dist/orchestrator/orchestrator.service.d.ts +371 -11
- package/dist/orchestrator/orchestrator.service.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator.service.js +1602 -269
- package/dist/orchestrator/orchestrator.service.js.map +1 -1
- package/dist/orchestrator/owned-pids.d.ts +83 -0
- package/dist/orchestrator/owned-pids.d.ts.map +1 -0
- package/dist/orchestrator/owned-pids.js +93 -0
- package/dist/orchestrator/owned-pids.js.map +1 -0
- package/dist/orchestrator/port-conflict.d.ts +102 -0
- package/dist/orchestrator/port-conflict.d.ts.map +1 -0
- package/dist/orchestrator/port-conflict.js +131 -0
- package/dist/orchestrator/port-conflict.js.map +1 -0
- package/dist/orchestrator/process-janitor.d.ts +78 -4
- package/dist/orchestrator/process-janitor.d.ts.map +1 -1
- package/dist/orchestrator/process-janitor.js +246 -30
- package/dist/orchestrator/process-janitor.js.map +1 -1
- package/dist/orchestrator/ps-metrics.d.ts +37 -0
- package/dist/orchestrator/ps-metrics.d.ts.map +1 -0
- package/dist/orchestrator/ps-metrics.js +60 -0
- package/dist/orchestrator/ps-metrics.js.map +1 -0
- package/dist/orchestrator/service-router.d.ts +58 -2
- package/dist/orchestrator/service-router.d.ts.map +1 -1
- package/dist/orchestrator/service-router.js +157 -25
- package/dist/orchestrator/service-router.js.map +1 -1
- package/dist/orchestrator/topology-query.d.ts +49 -0
- package/dist/orchestrator/topology-query.d.ts.map +1 -0
- package/dist/orchestrator/topology-query.js +54 -0
- package/dist/orchestrator/topology-query.js.map +1 -0
- package/dist/project/artifact-builder.d.ts +174 -3
- package/dist/project/artifact-builder.d.ts.map +1 -1
- package/dist/project/artifact-builder.js +454 -42
- package/dist/project/artifact-builder.js.map +1 -1
- package/dist/project/build-decision.d.ts +58 -0
- package/dist/project/build-decision.d.ts.map +1 -0
- package/dist/project/build-decision.js +59 -0
- package/dist/project/build-decision.js.map +1 -0
- package/dist/project/config-resolver.d.ts +6 -4
- package/dist/project/config-resolver.d.ts.map +1 -1
- package/dist/project/config-resolver.js +128 -74
- package/dist/project/config-resolver.js.map +1 -1
- package/dist/project/declared-config.d.ts +42 -0
- package/dist/project/declared-config.d.ts.map +1 -0
- package/dist/project/declared-config.js +60 -0
- package/dist/project/declared-config.js.map +1 -0
- package/dist/project/dependency-analyzer.d.ts.map +1 -1
- package/dist/project/dependency-analyzer.js.map +1 -1
- package/dist/project/deploy-phases.d.ts +35 -0
- package/dist/project/deploy-phases.d.ts.map +1 -0
- package/dist/project/deploy-phases.js +75 -0
- package/dist/project/deploy-phases.js.map +1 -0
- package/dist/project/exec-in-group.d.ts +49 -0
- package/dist/project/exec-in-group.d.ts.map +1 -0
- package/dist/project/exec-in-group.js +100 -0
- package/dist/project/exec-in-group.js.map +1 -0
- package/dist/project/node-app-config.d.ts +101 -0
- package/dist/project/node-app-config.d.ts.map +1 -0
- package/dist/project/node-app-config.js +134 -0
- package/dist/project/node-app-config.js.map +1 -0
- package/dist/project/node-app-health.d.ts +76 -0
- package/dist/project/node-app-health.d.ts.map +1 -0
- package/dist/project/node-app-health.js +119 -0
- package/dist/project/node-app-health.js.map +1 -0
- package/dist/project/operator-account.d.ts +287 -0
- package/dist/project/operator-account.d.ts.map +1 -0
- package/dist/project/operator-account.js +373 -0
- package/dist/project/operator-account.js.map +1 -0
- package/dist/project/registry.d.ts +45 -8
- package/dist/project/registry.d.ts.map +1 -1
- package/dist/project/registry.js +217 -49
- package/dist/project/registry.js.map +1 -1
- package/dist/project/requirements-scanner.d.ts +1 -0
- package/dist/project/requirements-scanner.d.ts.map +1 -1
- package/dist/project/requirements-scanner.js +28 -3
- package/dist/project/requirements-scanner.js.map +1 -1
- package/dist/project/stack-env.d.ts +37 -0
- package/dist/project/stack-env.d.ts.map +1 -0
- package/dist/project/stack-env.js +85 -0
- package/dist/project/stack-env.js.map +1 -0
- package/dist/project/stand-containers.d.ts +4 -0
- package/dist/project/stand-containers.d.ts.map +1 -0
- package/dist/project/stand-containers.js +27 -0
- package/dist/project/stand-containers.js.map +1 -0
- package/dist/project/working-tree.d.ts +62 -0
- package/dist/project/working-tree.d.ts.map +1 -0
- package/dist/project/working-tree.js +101 -0
- package/dist/project/working-tree.js.map +1 -0
- package/dist/release/artifact-store.d.ts +360 -0
- package/dist/release/artifact-store.d.ts.map +1 -0
- package/dist/release/artifact-store.js +1040 -0
- package/dist/release/artifact-store.js.map +1 -0
- package/dist/release/attest-on-node.d.ts +92 -0
- package/dist/release/attest-on-node.d.ts.map +1 -0
- package/dist/release/attest-on-node.js +207 -0
- package/dist/release/attest-on-node.js.map +1 -0
- package/dist/release/attest.d.ts +135 -0
- package/dist/release/attest.d.ts.map +1 -0
- package/dist/release/attest.js +199 -0
- package/dist/release/attest.js.map +1 -0
- package/dist/release/build-run.d.ts +151 -0
- package/dist/release/build-run.d.ts.map +1 -0
- package/dist/release/build-run.js +609 -0
- package/dist/release/build-run.js.map +1 -0
- package/dist/release/builder.d.ts +57 -0
- package/dist/release/builder.d.ts.map +1 -0
- package/dist/release/builder.js +120 -0
- package/dist/release/builder.js.map +1 -0
- package/dist/release/daemon-omni.d.ts +41 -0
- package/dist/release/daemon-omni.d.ts.map +1 -0
- package/dist/release/daemon-omni.js +59 -0
- package/dist/release/daemon-omni.js.map +1 -0
- package/dist/release/definition-inputs.d.ts +34 -0
- package/dist/release/definition-inputs.d.ts.map +1 -0
- package/dist/release/definition-inputs.js +90 -0
- package/dist/release/definition-inputs.js.map +1 -0
- package/dist/release/delivered.d.ts +53 -0
- package/dist/release/delivered.d.ts.map +1 -0
- package/dist/release/delivered.js +81 -0
- package/dist/release/delivered.js.map +1 -0
- package/dist/release/layout.d.ts +61 -0
- package/dist/release/layout.d.ts.map +1 -0
- package/dist/release/layout.js +114 -0
- package/dist/release/layout.js.map +1 -0
- package/dist/release/load.d.ts +44 -0
- package/dist/release/load.d.ts.map +1 -0
- package/dist/release/load.js +84 -0
- package/dist/release/load.js.map +1 -0
- package/dist/release/manifest.d.ts +299 -0
- package/dist/release/manifest.d.ts.map +1 -0
- package/dist/release/manifest.js +176 -0
- package/dist/release/manifest.js.map +1 -0
- package/dist/release/migrations.d.ts +32 -0
- package/dist/release/migrations.d.ts.map +1 -0
- package/dist/release/migrations.js +51 -0
- package/dist/release/migrations.js.map +1 -0
- package/dist/release/publish.d.ts +71 -0
- package/dist/release/publish.d.ts.map +1 -0
- package/dist/release/publish.js +102 -0
- package/dist/release/publish.js.map +1 -0
- package/dist/release/store.d.ts +174 -0
- package/dist/release/store.d.ts.map +1 -0
- package/dist/release/store.js +257 -0
- package/dist/release/store.js.map +1 -0
- package/dist/services/alert.rpc-service.d.ts +11 -15
- package/dist/services/alert.rpc-service.d.ts.map +1 -1
- package/dist/services/alert.rpc-service.js +29 -14
- package/dist/services/alert.rpc-service.js.map +1 -1
- package/dist/services/alert.service.d.ts +96 -59
- package/dist/services/alert.service.d.ts.map +1 -1
- package/dist/services/alert.service.js +455 -70
- package/dist/services/alert.service.js.map +1 -1
- package/dist/services/anonymous-input.d.ts +26 -0
- package/dist/services/anonymous-input.d.ts.map +1 -0
- package/dist/services/anonymous-input.js +44 -0
- package/dist/services/anonymous-input.js.map +1 -0
- package/dist/services/audit.rpc-service.d.ts +27 -0
- package/dist/services/audit.rpc-service.d.ts.map +1 -0
- package/dist/services/audit.rpc-service.js +57 -0
- package/dist/services/audit.rpc-service.js.map +1 -0
- package/dist/services/audit.service.d.ts +165 -0
- package/dist/services/audit.service.d.ts.map +1 -0
- package/dist/services/audit.service.js +242 -0
- package/dist/services/audit.service.js.map +1 -0
- package/dist/services/auth-context.d.ts +21 -1
- package/dist/services/auth-context.d.ts.map +1 -1
- package/dist/services/auth-context.js +51 -5
- package/dist/services/auth-context.js.map +1 -1
- package/dist/services/auth.rpc-service.d.ts +0 -1
- package/dist/services/auth.rpc-service.d.ts.map +1 -1
- package/dist/services/auth.rpc-service.js +23 -9
- package/dist/services/auth.rpc-service.js.map +1 -1
- package/dist/services/auth.service.d.ts +47 -33
- package/dist/services/auth.service.d.ts.map +1 -1
- package/dist/services/auth.service.js +155 -23
- package/dist/services/auth.service.js.map +1 -1
- package/dist/services/backup-pipeline.d.ts +97 -0
- package/dist/services/backup-pipeline.d.ts.map +1 -0
- package/dist/services/backup-pipeline.js +286 -0
- package/dist/services/backup-pipeline.js.map +1 -0
- package/dist/services/backup-schedule.d.ts +69 -0
- package/dist/services/backup-schedule.d.ts.map +1 -0
- package/dist/services/backup-schedule.js +132 -0
- package/dist/services/backup-schedule.js.map +1 -0
- package/dist/services/backup.rpc-service.d.ts +31 -2
- package/dist/services/backup.rpc-service.d.ts.map +1 -1
- package/dist/services/backup.rpc-service.js +53 -0
- package/dist/services/backup.rpc-service.js.map +1 -1
- package/dist/services/backup.service.d.ts +458 -14
- package/dist/services/backup.service.d.ts.map +1 -1
- package/dist/services/backup.service.js +1368 -170
- package/dist/services/backup.service.js.map +1 -1
- package/dist/services/bundle-build-worker.d.ts +19 -0
- package/dist/services/bundle-build-worker.d.ts.map +1 -0
- package/dist/services/bundle-build-worker.js +40 -0
- package/dist/services/bundle-build-worker.js.map +1 -0
- package/dist/services/bundle-builder.d.ts +487 -0
- package/dist/services/bundle-builder.d.ts.map +1 -0
- package/dist/services/bundle-builder.js +1083 -0
- package/dist/services/bundle-builder.js.map +1 -0
- package/dist/services/bundle-worker-protocol.d.ts +19 -0
- package/dist/services/bundle-worker-protocol.d.ts.map +1 -0
- package/dist/services/bundle-worker-protocol.js +32 -0
- package/dist/services/bundle-worker-protocol.js.map +1 -0
- package/dist/services/deploy.rpc-service.d.ts +4 -1
- package/dist/services/deploy.rpc-service.d.ts.map +1 -1
- package/dist/services/deploy.rpc-service.js +10 -0
- package/dist/services/deploy.rpc-service.js.map +1 -1
- package/dist/services/deploy.service.d.ts +14 -24
- package/dist/services/deploy.service.d.ts.map +1 -1
- package/dist/services/deploy.service.js +46 -5
- package/dist/services/deploy.service.js.map +1 -1
- package/dist/services/discovery.rpc-service.d.ts +2 -1
- package/dist/services/discovery.rpc-service.d.ts.map +1 -1
- package/dist/services/discovery.rpc-service.js.map +1 -1
- package/dist/services/discovery.service.d.ts +32 -28
- package/dist/services/discovery.service.d.ts.map +1 -1
- package/dist/services/discovery.service.js +108 -133
- package/dist/services/discovery.service.js.map +1 -1
- package/dist/services/event-broadcaster.rpc-service.d.ts +36 -23
- package/dist/services/event-broadcaster.rpc-service.d.ts.map +1 -1
- package/dist/services/event-broadcaster.rpc-service.js +70 -32
- package/dist/services/event-broadcaster.rpc-service.js.map +1 -1
- package/dist/services/event-broadcaster.service.d.ts.map +1 -1
- package/dist/services/event-broadcaster.service.js.map +1 -1
- package/dist/services/fleet.rpc-service.d.ts +2 -1
- package/dist/services/fleet.rpc-service.d.ts.map +1 -1
- package/dist/services/fleet.rpc-service.js +3 -1
- package/dist/services/fleet.rpc-service.js.map +1 -1
- package/dist/services/fleet.service.d.ts +32 -34
- package/dist/services/fleet.service.d.ts.map +1 -1
- package/dist/services/fleet.service.js +66 -10
- package/dist/services/fleet.service.js.map +1 -1
- package/dist/services/health-check.rpc-service.d.ts +2 -1
- package/dist/services/health-check.rpc-service.d.ts.map +1 -1
- package/dist/services/health-check.rpc-service.js.map +1 -1
- package/dist/services/health-check.service.d.ts +50 -36
- package/dist/services/health-check.service.d.ts.map +1 -1
- package/dist/services/health-check.service.js +179 -182
- package/dist/services/health-check.service.js.map +1 -1
- package/dist/services/infrastructure.rpc-service.d.ts +297 -2
- package/dist/services/infrastructure.rpc-service.d.ts.map +1 -1
- package/dist/services/infrastructure.rpc-service.js +523 -11
- package/dist/services/infrastructure.rpc-service.js.map +1 -1
- package/dist/services/kubernetes.rpc-service.d.ts +2 -1
- package/dist/services/kubernetes.rpc-service.d.ts.map +1 -1
- package/dist/services/kubernetes.rpc-service.js.map +1 -1
- package/dist/services/kubernetes.service.d.ts +5 -39
- package/dist/services/kubernetes.service.d.ts.map +1 -1
- package/dist/services/kubernetes.service.js +29 -5
- package/dist/services/kubernetes.service.js.map +1 -1
- package/dist/services/local-bundle.d.ts +151 -0
- package/dist/services/local-bundle.d.ts.map +1 -0
- package/dist/services/local-bundle.js +233 -0
- package/dist/services/local-bundle.js.map +1 -0
- package/dist/services/lockout.d.ts +28 -0
- package/dist/services/lockout.d.ts.map +1 -0
- package/dist/services/lockout.js +41 -0
- package/dist/services/lockout.js.map +1 -0
- package/dist/services/log-collector.rpc-service.d.ts +16 -0
- package/dist/services/log-collector.rpc-service.d.ts.map +1 -1
- package/dist/services/log-collector.rpc-service.js +16 -0
- package/dist/services/log-collector.rpc-service.js.map +1 -1
- package/dist/services/log-collector.service.d.ts +172 -54
- package/dist/services/log-collector.service.d.ts.map +1 -1
- package/dist/services/log-collector.service.js +492 -102
- package/dist/services/log-collector.service.js.map +1 -1
- package/dist/services/log-retention.d.ts +50 -0
- package/dist/services/log-retention.d.ts.map +1 -0
- package/dist/services/log-retention.js +52 -0
- package/dist/services/log-retention.js.map +1 -0
- package/dist/services/maintenance-lock.d.ts +162 -0
- package/dist/services/maintenance-lock.d.ts.map +1 -0
- package/dist/services/maintenance-lock.js +316 -0
- package/dist/services/maintenance-lock.js.map +1 -0
- package/dist/services/master-address.d.ts +107 -0
- package/dist/services/master-address.d.ts.map +1 -0
- package/dist/services/master-address.js +154 -0
- package/dist/services/master-address.js.map +1 -0
- package/dist/services/migration-output.d.ts +16 -0
- package/dist/services/migration-output.d.ts.map +1 -0
- package/dist/services/migration-output.js +22 -0
- package/dist/services/migration-output.js.map +1 -0
- package/dist/services/netron-telemetry-transport.d.ts.map +1 -1
- package/dist/services/netron-telemetry-transport.js.map +1 -1
- package/dist/services/node-deploy-lease.d.ts +154 -0
- package/dist/services/node-deploy-lease.d.ts.map +1 -0
- package/dist/services/node-deploy-lease.js +386 -0
- package/dist/services/node-deploy-lease.js.map +1 -0
- package/dist/services/node-health.repository.d.ts +63 -4
- package/dist/services/node-health.repository.d.ts.map +1 -1
- package/dist/services/node-health.repository.js +108 -67
- package/dist/services/node-health.repository.js.map +1 -1
- package/dist/services/node-manager.rpc-service.d.ts +355 -10
- package/dist/services/node-manager.rpc-service.d.ts.map +1 -1
- package/dist/services/node-manager.rpc-service.js +730 -34
- package/dist/services/node-manager.rpc-service.js.map +1 -1
- package/dist/services/node-manager.service.d.ts +267 -95
- package/dist/services/node-manager.service.d.ts.map +1 -1
- package/dist/services/node-manager.service.js +805 -103
- package/dist/services/node-manager.service.js.map +1 -1
- package/dist/services/node-upgrade.d.ts +129 -0
- package/dist/services/node-upgrade.d.ts.map +1 -0
- package/dist/services/node-upgrade.js +161 -0
- package/dist/services/node-upgrade.js.map +1 -0
- package/dist/services/node-upgrade.service.d.ts +232 -0
- package/dist/services/node-upgrade.service.d.ts.map +1 -0
- package/dist/services/node-upgrade.service.js +468 -0
- package/dist/services/node-upgrade.service.js.map +1 -0
- package/dist/services/pipeline.rpc-service.d.ts +2 -1
- package/dist/services/pipeline.rpc-service.d.ts.map +1 -1
- package/dist/services/pipeline.rpc-service.js.map +1 -1
- package/dist/services/pipeline.service.d.ts +15 -46
- package/dist/services/pipeline.service.d.ts.map +1 -1
- package/dist/services/pipeline.service.js +72 -9
- package/dist/services/pipeline.service.js.map +1 -1
- package/dist/services/project.rpc-service.d.ts +133 -5
- package/dist/services/project.rpc-service.d.ts.map +1 -1
- package/dist/services/project.rpc-service.js +249 -22
- package/dist/services/project.rpc-service.js.map +1 -1
- package/dist/services/project.service.d.ts +677 -15
- package/dist/services/project.service.d.ts.map +1 -1
- package/dist/services/project.service.js +2798 -304
- package/dist/services/project.service.js.map +1 -1
- package/dist/services/redeploy-decision.d.ts +176 -0
- package/dist/services/redeploy-decision.d.ts.map +1 -0
- package/dist/services/redeploy-decision.js +142 -0
- package/dist/services/redeploy-decision.js.map +1 -0
- package/dist/services/release.rpc-service.d.ts +159 -0
- package/dist/services/release.rpc-service.d.ts.map +1 -0
- package/dist/services/release.rpc-service.js +395 -0
- package/dist/services/release.rpc-service.js.map +1 -0
- package/dist/services/release.service.d.ts +114 -0
- package/dist/services/release.service.d.ts.map +1 -0
- package/dist/services/release.service.js +208 -0
- package/dist/services/release.service.js.map +1 -0
- package/dist/services/remote-deployer.service.d.ts +569 -8
- package/dist/services/remote-deployer.service.d.ts.map +1 -1
- package/dist/services/remote-deployer.service.js +1618 -169
- package/dist/services/remote-deployer.service.js.map +1 -1
- package/dist/services/remote-ops.service.d.ts +51 -9
- package/dist/services/remote-ops.service.d.ts.map +1 -1
- package/dist/services/remote-ops.service.js +235 -39
- package/dist/services/remote-ops.service.js.map +1 -1
- package/dist/services/remote-provisioner.d.ts +122 -0
- package/dist/services/remote-provisioner.d.ts.map +1 -0
- package/dist/services/remote-provisioner.js +258 -0
- package/dist/services/remote-provisioner.js.map +1 -0
- package/dist/services/secrets.rpc-service.d.ts +51 -2
- package/dist/services/secrets.rpc-service.d.ts.map +1 -1
- package/dist/services/secrets.rpc-service.js +81 -2
- package/dist/services/secrets.rpc-service.js.map +1 -1
- package/dist/services/secrets.service.d.ts +108 -9
- package/dist/services/secrets.service.d.ts.map +1 -1
- package/dist/services/secrets.service.js +313 -48
- package/dist/services/secrets.service.js.map +1 -1
- package/dist/services/slave-storage.service.d.ts +107 -0
- package/dist/services/slave-storage.service.d.ts.map +1 -1
- package/dist/services/slave-storage.service.js +171 -2
- package/dist/services/slave-storage.service.js.map +1 -1
- package/dist/services/static-carry-forward.d.ts +85 -0
- package/dist/services/static-carry-forward.d.ts.map +1 -0
- package/dist/services/static-carry-forward.js +159 -0
- package/dist/services/static-carry-forward.js.map +1 -0
- package/dist/services/sync-policy.d.ts +167 -0
- package/dist/services/sync-policy.d.ts.map +1 -0
- package/dist/services/sync-policy.js +161 -0
- package/dist/services/sync-policy.js.map +1 -0
- package/dist/services/sync-summary.d.ts +39 -0
- package/dist/services/sync-summary.d.ts.map +1 -0
- package/dist/services/sync-summary.js +62 -0
- package/dist/services/sync-summary.js.map +1 -0
- package/dist/services/sync.rpc-service.d.ts +19 -7
- package/dist/services/sync.rpc-service.d.ts.map +1 -1
- package/dist/services/sync.rpc-service.js +36 -5
- package/dist/services/sync.rpc-service.js.map +1 -1
- package/dist/services/sync.service.d.ts +350 -18
- package/dist/services/sync.service.d.ts.map +1 -1
- package/dist/services/sync.service.js +864 -116
- package/dist/services/sync.service.js.map +1 -1
- package/dist/services/system-info.rpc-service.d.ts +2 -1
- package/dist/services/system-info.rpc-service.d.ts.map +1 -1
- package/dist/services/system-info.rpc-service.js.map +1 -1
- package/dist/services/system-info.service.d.ts +56 -68
- package/dist/services/system-info.service.d.ts.map +1 -1
- package/dist/services/system-info.service.js +240 -127
- package/dist/services/system-info.service.js.map +1 -1
- package/dist/services/telemetry.rpc-service.d.ts.map +1 -1
- package/dist/services/telemetry.rpc-service.js +9 -3
- package/dist/services/telemetry.rpc-service.js.map +1 -1
- package/dist/services/telemetry.service.d.ts +4 -2
- package/dist/services/telemetry.service.d.ts.map +1 -1
- package/dist/services/telemetry.service.js +4 -2
- package/dist/services/telemetry.service.js.map +1 -1
- package/dist/services/trace-collector.rpc-service.d.ts +2 -1
- package/dist/services/trace-collector.rpc-service.d.ts.map +1 -1
- package/dist/services/trace-collector.rpc-service.js.map +1 -1
- package/dist/services/trace-collector.service.d.ts +2 -41
- package/dist/services/trace-collector.service.d.ts.map +1 -1
- package/dist/services/trace-collector.service.js +27 -7
- package/dist/services/trace-collector.service.js.map +1 -1
- package/dist/services/wait-for-postgres.d.ts +28 -0
- package/dist/services/wait-for-postgres.d.ts.map +1 -0
- package/dist/services/wait-for-postgres.js +77 -0
- package/dist/services/wait-for-postgres.js.map +1 -0
- package/dist/shared/alert-expression.d.ts +60 -0
- package/dist/shared/alert-expression.d.ts.map +1 -0
- package/dist/shared/alert-expression.js +124 -0
- package/dist/shared/alert-expression.js.map +1 -0
- package/dist/shared/audit-outcome.d.ts +39 -0
- package/dist/shared/audit-outcome.d.ts.map +1 -0
- package/dist/shared/audit-outcome.js +45 -0
- package/dist/shared/audit-outcome.js.map +1 -0
- package/dist/shared/build-freshness.d.ts +59 -0
- package/dist/shared/build-freshness.d.ts.map +1 -0
- package/dist/shared/build-freshness.js +113 -0
- package/dist/shared/build-freshness.js.map +1 -0
- package/dist/shared/build-stamp.d.ts +26 -0
- package/dist/shared/build-stamp.d.ts.map +1 -0
- package/dist/shared/build-stamp.js +36 -0
- package/dist/shared/build-stamp.js.map +1 -0
- package/dist/shared/describe-error.d.ts +22 -0
- package/dist/shared/describe-error.d.ts.map +1 -0
- package/dist/shared/describe-error.js +39 -0
- package/dist/shared/describe-error.js.map +1 -0
- package/dist/shared/dto/alerts.d.ts +101 -0
- package/dist/shared/dto/alerts.d.ts.map +1 -0
- package/dist/shared/dto/alerts.js +9 -0
- package/dist/shared/dto/alerts.js.map +1 -0
- package/dist/shared/dto/auth.d.ts +49 -0
- package/dist/shared/dto/auth.d.ts.map +1 -0
- package/dist/shared/dto/auth.js +14 -0
- package/dist/shared/dto/auth.js.map +1 -0
- package/dist/shared/dto/backups.d.ts +16 -0
- package/dist/shared/dto/backups.d.ts.map +1 -0
- package/dist/shared/dto/backups.js +9 -0
- package/dist/shared/dto/backups.js.map +1 -0
- package/dist/shared/dto/deploy.d.ts +28 -0
- package/dist/shared/dto/deploy.d.ts.map +1 -0
- package/dist/shared/dto/deploy.js +8 -0
- package/dist/shared/dto/deploy.js.map +1 -0
- package/dist/shared/dto/discovery.d.ts +24 -0
- package/dist/shared/dto/discovery.d.ts.map +1 -0
- package/dist/shared/dto/discovery.js +9 -0
- package/dist/shared/dto/discovery.js.map +1 -0
- package/dist/shared/dto/fleet.d.ts +35 -0
- package/dist/shared/dto/fleet.d.ts.map +1 -0
- package/dist/shared/dto/fleet.js +8 -0
- package/dist/shared/dto/fleet.js.map +1 -0
- package/dist/shared/dto/health.d.ts +26 -0
- package/dist/shared/dto/health.d.ts.map +1 -0
- package/dist/shared/dto/health.js +9 -0
- package/dist/shared/dto/health.js.map +1 -0
- package/dist/shared/dto/kubernetes.d.ts +44 -0
- package/dist/shared/dto/kubernetes.d.ts.map +1 -0
- package/dist/shared/dto/kubernetes.js +9 -0
- package/dist/shared/dto/kubernetes.js.map +1 -0
- package/dist/shared/dto/logs.d.ts +77 -0
- package/dist/shared/dto/logs.d.ts.map +1 -0
- package/dist/shared/dto/logs.js +10 -0
- package/dist/shared/dto/logs.js.map +1 -0
- package/dist/shared/dto/metrics.d.ts +65 -0
- package/dist/shared/dto/metrics.d.ts.map +1 -0
- package/dist/shared/dto/metrics.js +17 -0
- package/dist/shared/dto/metrics.js.map +1 -0
- package/dist/shared/dto/nodes.d.ts +347 -0
- package/dist/shared/dto/nodes.d.ts.map +1 -0
- package/dist/shared/dto/nodes.js +8 -0
- package/dist/shared/dto/nodes.js.map +1 -0
- package/dist/shared/dto/pipelines.d.ts +53 -0
- package/dist/shared/dto/pipelines.d.ts.map +1 -0
- package/dist/shared/dto/pipelines.js +9 -0
- package/dist/shared/dto/pipelines.js.map +1 -0
- package/dist/shared/dto/project.d.ts +142 -8
- package/dist/shared/dto/project.d.ts.map +1 -1
- package/dist/shared/dto/services.d.ts +831 -7
- package/dist/shared/dto/services.d.ts.map +1 -1
- package/dist/shared/dto/system-info.d.ts +93 -0
- package/dist/shared/dto/system-info.d.ts.map +1 -0
- package/dist/shared/dto/system-info.js +9 -0
- package/dist/shared/dto/system-info.js.map +1 -0
- package/dist/shared/dto/traces.d.ts +49 -0
- package/dist/shared/dto/traces.d.ts.map +1 -0
- package/dist/shared/dto/traces.js +9 -0
- package/dist/shared/dto/traces.js.map +1 -0
- package/dist/shared/env-config.d.ts +74 -0
- package/dist/shared/env-config.d.ts.map +1 -0
- package/dist/shared/env-config.js +99 -0
- package/dist/shared/env-config.js.map +1 -0
- package/dist/shared/events.d.ts +45 -39
- package/dist/shared/events.d.ts.map +1 -1
- package/dist/shared/events.js +6 -0
- package/dist/shared/events.js.map +1 -1
- package/dist/shared/format.d.ts +1 -1
- package/dist/shared/format.d.ts.map +1 -1
- package/dist/shared/format.js +4 -0
- package/dist/shared/format.js.map +1 -1
- package/dist/shared/local-infra-host.d.ts +28 -0
- package/dist/shared/local-infra-host.d.ts.map +1 -0
- package/dist/shared/local-infra-host.js +28 -0
- package/dist/shared/local-infra-host.js.map +1 -0
- package/dist/shared/node-check.d.ts +37 -0
- package/dist/shared/node-check.d.ts.map +1 -0
- package/dist/shared/node-check.js +41 -0
- package/dist/shared/node-check.js.map +1 -0
- package/dist/shared/paths.d.ts +29 -0
- package/dist/shared/paths.d.ts.map +1 -0
- package/dist/shared/paths.js +54 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/shared/pnpm.d.ts +29 -0
- package/dist/shared/pnpm.d.ts.map +1 -0
- package/dist/shared/pnpm.js +57 -0
- package/dist/shared/pnpm.js.map +1 -0
- package/dist/shared/private-files.d.ts +29 -0
- package/dist/shared/private-files.d.ts.map +1 -0
- package/dist/shared/private-files.js +109 -0
- package/dist/shared/private-files.js.map +1 -0
- package/dist/shared/redact-env.d.ts +24 -0
- package/dist/shared/redact-env.d.ts.map +1 -0
- package/dist/shared/redact-env.js +36 -0
- package/dist/shared/redact-env.js.map +1 -0
- package/dist/shared/release-reading.d.ts +85 -0
- package/dist/shared/release-reading.d.ts.map +1 -0
- package/dist/shared/release-reading.js +81 -0
- package/dist/shared/release-reading.js.map +1 -0
- package/dist/shared/roles.d.ts +58 -3
- package/dist/shared/roles.d.ts.map +1 -1
- package/dist/shared/roles.js +55 -0
- package/dist/shared/roles.js.map +1 -1
- package/dist/shared/rpcauth.d.ts +13 -0
- package/dist/shared/rpcauth.d.ts.map +1 -0
- package/dist/shared/rpcauth.js +23 -0
- package/dist/shared/rpcauth.js.map +1 -0
- package/dist/shared/shell-escape.d.ts +9 -0
- package/dist/shared/shell-escape.d.ts.map +1 -0
- package/dist/shared/shell-escape.js +11 -0
- package/dist/shared/shell-escape.js.map +1 -0
- package/dist/shared/sync-reading.d.ts +45 -0
- package/dist/shared/sync-reading.d.ts.map +1 -0
- package/dist/shared/sync-reading.js +42 -0
- package/dist/shared/sync-reading.js.map +1 -0
- package/dist/shared/tokens.d.ts +19 -0
- package/dist/shared/tokens.d.ts.map +1 -1
- package/dist/shared/tokens.js +28 -0
- package/dist/shared/tokens.js.map +1 -1
- package/dist/webapp/webapp.service.d.ts +33 -2
- package/dist/webapp/webapp.service.d.ts.map +1 -1
- package/dist/webapp/webapp.service.js +143 -31
- package/dist/webapp/webapp.service.js.map +1 -1
- package/dist/workers/health-monitor-process.d.ts.map +1 -1
- package/dist/workers/health-monitor-process.js +10 -0
- package/dist/workers/health-monitor-process.js.map +1 -1
- package/dist/workers/health-monitor.service.d.ts +45 -3
- package/dist/workers/health-monitor.service.d.ts.map +1 -1
- package/dist/workers/health-monitor.service.js +152 -25
- package/dist/workers/health-monitor.service.js.map +1 -1
- package/dist/workers/system-worker-manager.d.ts +43 -0
- package/dist/workers/system-worker-manager.d.ts.map +1 -1
- package/dist/workers/system-worker-manager.js +150 -3
- package/dist/workers/system-worker-manager.js.map +1 -1
- package/dist/workers/types.d.ts +8 -2
- package/dist/workers/types.d.ts.map +1 -1
- package/package.json +64 -30
- package/webapp/dist/BUILD.json +5 -0
- package/webapp/dist/assets/FormControlLabel-BrH2diyl.js +1 -0
- package/webapp/dist/assets/Grid-BZ7xhoHQ.js +1 -0
- package/webapp/dist/assets/Switch-DGSAxQPM.js +1 -0
- package/webapp/dist/assets/alerts-sXqHJbsa.js +1 -0
- package/webapp/dist/assets/apps-B_Ah8Flh.js +1 -0
- package/webapp/dist/assets/audit-YXihwyui.js +1 -0
- package/webapp/dist/assets/client-CGbBvy9k.js +1 -0
- package/webapp/dist/assets/constants-veKGFkC6.js +1 -0
- package/webapp/dist/assets/containers-Di4NOTLf.js +2 -0
- package/webapp/dist/assets/daemon-memory-CCPZ4cV_.js +1 -0
- package/webapp/dist/assets/dashboard-Cfuf95N3.js +1 -0
- package/webapp/dist/assets/dashboard-builder-CowaYb-M.js +1 -0
- package/webapp/dist/assets/deploy-release-dialog-D6C-XcKY.js +1 -0
- package/webapp/dist/assets/deployments-ChTq5kCw.js +1 -0
- package/webapp/dist/assets/detail-BsGWv7Dj.js +1 -0
- package/webapp/dist/assets/detail-CyVf_cw5.js +2 -0
- package/webapp/dist/assets/detail-D027j6aO.js +8 -0
- package/webapp/dist/assets/formatters-BqjL73N3.js +1 -0
- package/webapp/dist/assets/index-B8AHIPc-.css +1 -0
- package/webapp/dist/assets/index-DiZbrwxT.js +414 -0
- package/webapp/dist/assets/logs-L2b8Oqmo.js +12 -0
- package/webapp/dist/assets/maplibre-gl-MlmG2kbn.js +801 -0
- package/webapp/dist/assets/metrics-CQgjz2Lz.js +2 -0
- package/webapp/dist/assets/nodes-Nwia2Ty6.js +7 -0
- package/webapp/dist/assets/pipelines-DCu_rXOr.js +4 -0
- package/webapp/dist/assets/projects-Bb4aV76h.js +1 -0
- package/webapp/dist/assets/react-CwJFpaho.js +1 -0
- package/webapp/dist/assets/react-apexcharts.esm-Dy0Fez42.js +1956 -0
- package/webapp/dist/assets/release-bits-Dj4EOhhb.js +1 -0
- package/webapp/dist/assets/release-build-panel-D6OCVlhT.js +1 -0
- package/webapp/dist/assets/releases-CnAt3yO6.js +2 -0
- package/webapp/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/webapp/dist/assets/settings-DU-mjiwb.js +1 -0
- package/webapp/dist/assets/settled-pair-C7SjZuns.js +1 -0
- package/webapp/dist/assets/sign-in-Bcvjrr4C.js +1 -0
- package/webapp/dist/assets/stacks-B-e8p50I.js +1 -0
- package/webapp/dist/assets/sync-reading-C3PnJ7AX.js +1 -0
- package/webapp/dist/assets/system-info-BtE3dgfw.js +1 -0
- package/webapp/dist/assets/topology-Cwch-uF1.js +12 -0
- package/webapp/dist/assets/topology-DLioOiRN.css +1 -0
- package/webapp/dist/assets/traces-BbShR-4P.js +1 -0
- package/webapp/dist/assets/use-stack-context-CZUgPq88.js +1 -0
- package/webapp/dist/index.html +6 -6
- package/dist/commands/dev.d.ts +0 -23
- package/dist/commands/dev.d.ts.map +0 -1
- package/dist/commands/dev.js +0 -76
- package/dist/commands/dev.js.map +0 -1
- package/dist/config/define-app.d.ts +0 -6
- package/dist/config/define-app.d.ts.map +0 -1
- package/dist/config/define-app.js +0 -38
- package/dist/config/define-app.js.map +0 -1
- package/dist/infrastructure/gateway-generator.d.ts +0 -63
- package/dist/infrastructure/gateway-generator.d.ts.map +0 -1
- package/dist/infrastructure/gateway-generator.js +0 -253
- package/dist/infrastructure/gateway-generator.js.map +0 -1
- package/dist/monitoring/health.service.d.ts +0 -14
- package/dist/monitoring/health.service.d.ts.map +0 -1
- package/dist/monitoring/health.service.js +0 -50
- package/dist/monitoring/health.service.js.map +0 -1
- package/dist/monitoring/metrics.service.d.ts +0 -14
- package/dist/monitoring/metrics.service.d.ts.map +0 -1
- package/dist/monitoring/metrics.service.js +0 -40
- package/dist/monitoring/metrics.service.js.map +0 -1
- package/dist/orchestrator/bootstrap-runner.d.ts +0 -15
- package/dist/orchestrator/bootstrap-runner.d.ts.map +0 -1
- package/dist/orchestrator/bootstrap-runner.js +0 -152
- package/dist/orchestrator/bootstrap-runner.js.map +0 -1
- package/dist/orchestrator/topology-proxy.d.ts +0 -65
- package/dist/orchestrator/topology-proxy.d.ts.map +0 -1
- package/dist/orchestrator/topology-proxy.js +0 -209
- package/dist/orchestrator/topology-proxy.js.map +0 -1
- package/dist/orchestrator/topology-relay.d.ts +0 -111
- package/dist/orchestrator/topology-relay.d.ts.map +0 -1
- package/dist/orchestrator/topology-relay.js +0 -255
- package/dist/orchestrator/topology-relay.js.map +0 -1
- package/dist/services/auth-middleware.d.ts +0 -47
- package/dist/services/auth-middleware.d.ts.map +0 -1
- package/dist/services/auth-middleware.js +0 -158
- package/dist/services/auth-middleware.js.map +0 -1
- package/dist/services/environment.rpc-service.d.ts +0 -22
- package/dist/services/environment.rpc-service.d.ts.map +0 -1
- package/dist/services/environment.rpc-service.js +0 -65
- package/dist/services/environment.rpc-service.js.map +0 -1
- package/dist/services/environment.service.d.ts +0 -87
- package/dist/services/environment.service.d.ts.map +0 -1
- package/dist/services/environment.service.js +0 -220
- package/dist/services/environment.service.js.map +0 -1
- package/dist/services/metrics-collector.service.d.ts +0 -103
- package/dist/services/metrics-collector.service.d.ts.map +0 -1
- package/dist/services/metrics-collector.service.js +0 -235
- package/dist/services/metrics-collector.service.js.map +0 -1
- package/dist/services/metrics.rpc-service.d.ts +0 -41
- package/dist/services/metrics.rpc-service.d.ts.map +0 -1
- package/dist/services/metrics.rpc-service.js +0 -90
- package/dist/services/metrics.rpc-service.js.map +0 -1
- package/dist/services/metrics.service.d.ts +0 -72
- package/dist/services/metrics.service.d.ts.map +0 -1
- package/dist/services/metrics.service.js +0 -185
- package/dist/services/metrics.service.js.map +0 -1
- package/dist/supervisor/omnitron-supervisor.d.ts +0 -21
- package/dist/supervisor/omnitron-supervisor.d.ts.map +0 -1
- package/dist/supervisor/omnitron-supervisor.js +0 -43
- package/dist/supervisor/omnitron-supervisor.js.map +0 -1
- package/dist/webapp/static-server.d.ts +0 -24
- package/dist/webapp/static-server.d.ts.map +0 -1
- package/dist/webapp/static-server.js +0 -152
- package/dist/webapp/static-server.js.map +0 -1
- package/webapp/dist/assets/Badge-D2ZBP5iK.js +0 -1
- package/webapp/dist/assets/Card-vO88g06B.js +0 -1
- package/webapp/dist/assets/CardActions-C9h7Q2wW.js +0 -1
- package/webapp/dist/assets/CardContent-BA_Xmmhk.js +0 -1
- package/webapp/dist/assets/CardHeader-ceIs__6x.js +0 -1
- package/webapp/dist/assets/Chip-Ckk0xUhK.js +0 -1
- package/webapp/dist/assets/DialogActions-BhHM7hov.js +0 -1
- package/webapp/dist/assets/DialogContentText-BT2JQRmK.js +0 -1
- package/webapp/dist/assets/FormControlLabel-CcQByVS8.js +0 -1
- package/webapp/dist/assets/Grid-14PETRQW.js +0 -1
- package/webapp/dist/assets/InputAdornment-B8aHIsQ7.js +0 -1
- package/webapp/dist/assets/Skeleton-B74-Ds6O.js +0 -37
- package/webapp/dist/assets/Switch-DvWD2wh4.js +0 -1
- package/webapp/dist/assets/SwitchBase-CDU4nY8O.js +0 -1
- package/webapp/dist/assets/Tab-DcCOYYp5.js +0 -1
- package/webapp/dist/assets/TableContainer-CzTh4mu-.js +0 -1
- package/webapp/dist/assets/TableRow-BySEreZR.js +0 -2
- package/webapp/dist/assets/TextField-BLf-BHz2.js +0 -1
- package/webapp/dist/assets/alerts-DnRbT41C.js +0 -1
- package/webapp/dist/assets/apps-CdR0VH2d.js +0 -1
- package/webapp/dist/assets/chunk-DtcAfS_W.js +0 -1
- package/webapp/dist/assets/client-DU8I_lDc.js +0 -2
- package/webapp/dist/assets/constants-BgnT-Xdy.js +0 -1
- package/webapp/dist/assets/containers-Dw-rRXV1.js +0 -1
- package/webapp/dist/assets/dashboard-CzgB0uZz.js +0 -1
- package/webapp/dist/assets/dashboard-builder-DsA0yg33.js +0 -1
- package/webapp/dist/assets/deployments-DtQAoIcQ.js +0 -1
- package/webapp/dist/assets/detail-BJR-G7JG.js +0 -1
- package/webapp/dist/assets/detail-nlCeSltl.js +0 -8
- package/webapp/dist/assets/formatters-Do41SRmE.js +0 -1
- package/webapp/dist/assets/index-ByQDwp2r.js +0 -566
- package/webapp/dist/assets/index-CHVXOsTS.css +0 -1
- package/webapp/dist/assets/isMuiElement-DwF2naaF.js +0 -1
- package/webapp/dist/assets/logs-Kn8o4H7k.js +0 -11
- package/webapp/dist/assets/metrics-DN8Zu5wT.js +0 -2
- package/webapp/dist/assets/nodes-Bgch7AtN.js +0 -7
- package/webapp/dist/assets/pipelines-Cry3qm-S.js +0 -4
- package/webapp/dist/assets/popper-BJFSj6rc.js +0 -1
- package/webapp/dist/assets/preload-helper-DSXbuxSR.js +0 -1
- package/webapp/dist/assets/projects-Dym2GI1U.js +0 -1
- package/webapp/dist/assets/react-apexcharts.esm-CSGK5Cxn.js +0 -958
- package/webapp/dist/assets/react-apexcharts.esm-DEAWOwx5.js +0 -1003
- package/webapp/dist/assets/settings-va3zTcow.js +0 -1
- package/webapp/dist/assets/sign-in-ik35umbp.js +0 -1
- package/webapp/dist/assets/stacks-CVFOnxbF.js +0 -1
- package/webapp/dist/assets/system-info-npIXAjX2.js +0 -1
- package/webapp/dist/assets/topology-CHpVij2M.css +0 -1
- package/webapp/dist/assets/topology-bDe70vLs.js +0 -12
- package/webapp/dist/assets/traces-DIIcT8DF.js +0 -1
- package/webapp/dist/assets/use-stack-context-DHLwZv1r.js +0 -1
- /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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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 = `${
|
|
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
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
880
|
+
this.migrateLegacyMetaIfPresent();
|
|
881
|
+
const rows = this.store.selectBackupsSync(database);
|
|
77
882
|
const backups = [];
|
|
78
|
-
for (const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
|
908
|
+
return backups;
|
|
95
909
|
}
|
|
96
910
|
// ===========================================================================
|
|
97
911
|
// Restore backup
|
|
98
912
|
// ===========================================================================
|
|
99
913
|
async restoreBackup(backupId) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
-
//
|
|
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
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|