@omnitron-dev/omnitron 0.1.3 → 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 +378 -25
- 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 -24
- 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 +18 -5
- 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 +26 -4
- 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 +20 -0
- package/dist/commands/inspect-graph.d.ts.map +1 -0
- package/dist/commands/inspect-graph.js +74 -0
- package/dist/commands/inspect-graph.js.map +1 -0
- package/dist/commands/inspect.d.ts.map +1 -1
- package/dist/commands/inspect.js +115 -11
- 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 +25 -3
- 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 +532 -192
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/metrics.d.ts.map +1 -1
- package/dist/commands/metrics.js +40 -14
- package/dist/commands/metrics.js.map +1 -1
- package/dist/commands/monit.d.ts.map +1 -1
- package/dist/commands/monit.js +12 -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 +45 -0
- package/dist/commands/output.d.ts.map +1 -0
- package/dist/commands/output.js +103 -0
- package/dist/commands/output.js.map +1 -0
- 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 +341 -13
- 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 +62 -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 +20 -0
- package/dist/commands/tor.d.ts.map +1 -0
- package/dist/commands/tor.js +180 -0
- package/dist/commands/tor.js.map +1 -0
- package/dist/commands/up.d.ts +85 -1
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +181 -33
- 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 +93 -11
- 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 +425 -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 +150 -2
- package/dist/daemon/daemon-client.d.ts.map +1 -1
- package/dist/daemon/daemon-client.js +311 -21
- package/dist/daemon/daemon-client.js.map +1 -1
- package/dist/daemon/daemon-entry.js +92 -38
- 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 +207 -1
- package/dist/daemon/daemon.d.ts.map +1 -1
- package/dist/daemon/daemon.js +1254 -143
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/daemon.module.d.ts.map +1 -1
- package/dist/daemon/daemon.module.js +186 -79
- package/dist/daemon/daemon.module.js.map +1 -1
- package/dist/daemon/daemon.rpc-service.d.ts +109 -1
- package/dist/daemon/daemon.rpc-service.d.ts.map +1 -1
- package/dist/daemon/daemon.rpc-service.js +451 -95
- package/dist/daemon/daemon.rpc-service.js.map +1 -1
- package/dist/daemon/pid-manager.d.ts +69 -5
- package/dist/daemon/pid-manager.d.ts.map +1 -1
- package/dist/daemon/pid-manager.js +151 -17
- 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 +103 -19
- 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 +179 -1
- package/dist/infrastructure/container-runtime.d.ts.map +1 -1
- package/dist/infrastructure/container-runtime.js +612 -45
- 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 +247 -6
- package/dist/infrastructure/infrastructure.service.d.ts.map +1 -1
- package/dist/infrastructure/infrastructure.service.js +571 -96
- 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 +92 -0
- package/dist/infrastructure/phantom-endpoint-janitor.d.ts.map +1 -0
- package/dist/infrastructure/phantom-endpoint-janitor.js +199 -0
- package/dist/infrastructure/phantom-endpoint-janitor.js.map +1 -0
- 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/index.d.ts.map +1 -1
- package/dist/infrastructure/presets/index.js +2 -0
- package/dist/infrastructure/presets/index.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 +78 -5
- 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 +23 -4
- 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 +85 -4
- package/dist/infrastructure/presets/registry.js.map +1 -1
- package/dist/infrastructure/presets/tor.d.ts +112 -0
- package/dist/infrastructure/presets/tor.d.ts.map +1 -0
- package/dist/infrastructure/presets/tor.js +185 -0
- package/dist/infrastructure/presets/tor.js.map +1 -0
- 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 +191 -12
- package/dist/infrastructure/service-resolver.d.ts.map +1 -1
- package/dist/infrastructure/service-resolver.js +521 -60
- 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 +7 -1
- package/dist/infrastructure/stack-infra-manager.d.ts.map +1 -1
- package/dist/infrastructure/stack-infra-manager.js +32 -14
- package/dist/infrastructure/stack-infra-manager.js.map +1 -1
- package/dist/infrastructure/types.d.ts +278 -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/index.d.ts +4 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +3 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/metrics-bridge.d.ts +87 -0
- package/dist/observability/metrics-bridge.d.ts.map +1 -0
- package/dist/observability/metrics-bridge.js +295 -0
- package/dist/observability/metrics-bridge.js.map +1 -0
- package/dist/observability/metrics-server.d.ts +68 -0
- package/dist/observability/metrics-server.d.ts.map +1 -0
- package/dist/observability/metrics-server.js +146 -0
- package/dist/observability/metrics-server.js.map +1 -0
- package/dist/orchestrator/app-handle.d.ts +170 -3
- package/dist/orchestrator/app-handle.d.ts.map +1 -1
- package/dist/orchestrator/app-handle.js +177 -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 +148 -4
- package/dist/orchestrator/bootstrap-process.d.ts.map +1 -1
- package/dist/orchestrator/bootstrap-process.js +496 -18
- package/dist/orchestrator/bootstrap-process.js.map +1 -1
- package/dist/orchestrator/build-service.d.ts +50 -3
- package/dist/orchestrator/build-service.d.ts.map +1 -1
- package/dist/orchestrator/build-service.js +357 -85
- 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 +240 -43
- 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 +540 -7
- package/dist/orchestrator/orchestrator.service.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator.service.js +2106 -242
- 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 +227 -0
- package/dist/orchestrator/process-janitor.d.ts.map +1 -0
- package/dist/orchestrator/process-janitor.js +495 -0
- package/dist/orchestrator/process-janitor.js.map +1 -0
- 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 +158 -78
- 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 +46 -8
- package/dist/project/registry.d.ts.map +1 -1
- package/dist/project/registry.js +238 -40
- 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 +163 -24
- 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 +0 -4
- 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 +806 -104
- 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 +30 -46
- package/dist/services/pipeline.service.d.ts.map +1 -1
- package/dist/services/pipeline.service.js +125 -11
- package/dist/services/pipeline.service.js.map +1 -1
- package/dist/services/project.rpc-service.d.ts +137 -5
- package/dist/services/project.rpc-service.d.ts.map +1 -1
- package/dist/services/project.rpc-service.js +258 -22
- package/dist/services/project.rpc-service.js.map +1 -1
- package/dist/services/project.service.d.ts +680 -9
- package/dist/services/project.service.d.ts.map +1 -1
- package/dist/services/project.service.js +2914 -293
- 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 +143 -9
- package/dist/shared/dto/project.d.ts.map +1 -1
- package/dist/shared/dto/services.d.ts +855 -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 +156 -26
- 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 +11 -2
- package/dist/workers/types.d.ts.map +1 -1
- package/package.json +64 -29
- 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 -7
- 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/Chip-BySxTjyN.js +0 -1
- package/webapp/dist/assets/DialogContentText-YQWiAVrU.js +0 -1
- package/webapp/dist/assets/DialogTitle-CDkgUIwp.js +0 -1
- package/webapp/dist/assets/FormControlLabel-Bu3CCsBM.js +0 -1
- package/webapp/dist/assets/Grid-BFY8hAzL.js +0 -1
- package/webapp/dist/assets/InputAdornment-_Dayk-3y.js +0 -3
- package/webapp/dist/assets/Skeleton-BGj6Sw8m.js +0 -37
- package/webapp/dist/assets/Switch-CA6682vv.js +0 -1
- package/webapp/dist/assets/TableContainer-4epq10KJ.js +0 -1
- package/webapp/dist/assets/TextField-C73KHMVN.js +0 -1
- package/webapp/dist/assets/alerts-JEQALS7Y.js +0 -1
- package/webapp/dist/assets/apps-DMPysEKB.js +0 -1
- package/webapp/dist/assets/client-DlGPter5.js +0 -110
- package/webapp/dist/assets/constants-DqFQJ27U.js +0 -1
- package/webapp/dist/assets/containers-CvkC7do8.js +0 -1
- package/webapp/dist/assets/dashboard-C0Fre9pC.js +0 -1
- package/webapp/dist/assets/dashboard-builder-DAghqdpE.js +0 -1
- package/webapp/dist/assets/deployments-Cg1lI1uT.js +0 -1
- package/webapp/dist/assets/detail-CyFtEson.js +0 -1
- package/webapp/dist/assets/detail-DqoSGKT1.js +0 -8
- package/webapp/dist/assets/formatters-J3Xqn6BQ.js +0 -1
- package/webapp/dist/assets/icons-ynIWYfBP.js +0 -3
- package/webapp/dist/assets/index-CHVXOsTS.css +0 -1
- package/webapp/dist/assets/index-vKrDmEA_.js +0 -259
- package/webapp/dist/assets/isMuiElement-BRL3YuCT.js +0 -1
- package/webapp/dist/assets/logs-IovIij1C.js +0 -11
- package/webapp/dist/assets/metrics-Dufwtwjq.js +0 -1
- package/webapp/dist/assets/nodes-Brvu8W3j.js +0 -7
- package/webapp/dist/assets/pipelines-t40X4MuC.js +0 -4
- package/webapp/dist/assets/prop-types-CvVtHxp4.js +0 -1
- package/webapp/dist/assets/react-apexcharts.esm-CLO6gala.js +0 -954
- package/webapp/dist/assets/settings-DTAIST3v.js +0 -1
- package/webapp/dist/assets/sign-in-DQEAkJum.js +0 -1
- package/webapp/dist/assets/stacks-BjM1f45N.js +0 -1
- package/webapp/dist/assets/system-info-BQSbSEK-.js +0 -1
- package/webapp/dist/assets/topology-6N8-Az_Z.js +0 -12
- package/webapp/dist/assets/topology-CHpVij2M.css +0 -1
- package/webapp/dist/assets/traces-DVXyb-4m.js +0 -1
- package/webapp/dist/assets/use-stack-context-CbE8fxBA.js +0 -1
- /package/webapp/dist/assets/{fs-uP8eiZAR.js → fs-Ccl3sAcp.js} +0 -0
|
@@ -0,0 +1,1706 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `omnitron doctor` — diagnose why the platform is unhealthy.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* `omnitron health` answers "is anything wrong": it reports a status per
|
|
7
|
+
* check and stops there. Every serious defect found during the 2026-09-04
|
|
8
|
+
* audit shared the opposite problem — the system knew something was wrong
|
|
9
|
+
* and said so in a way nobody would act on:
|
|
10
|
+
*
|
|
11
|
+
* - schema migrations threw on every boot; the failure was logged as a
|
|
12
|
+
* WARNING and the daemon carried on against an empty database, so the
|
|
13
|
+
* visible symptom was `relation "alert_rules" does not exist` repeating
|
|
14
|
+
* every few seconds for fourteen days;
|
|
15
|
+
* - a database reconnect left every repository holding a destroyed driver;
|
|
16
|
+
* the only place it surfaced was a `warn` from one background task;
|
|
17
|
+
* - apps started outside `startAll` were never sampled, so the dashboard
|
|
18
|
+
* showed 0% CPU for processes using 105 MB;
|
|
19
|
+
* - `acme/dev/main` failed to start with `stderrBytes: 0` — the child died
|
|
20
|
+
* before writing anything, and the timeout message said nothing else.
|
|
21
|
+
*
|
|
22
|
+
* In each case the evidence existed somewhere: a log line, an exit code, a
|
|
23
|
+
* stderr tail, a table that should have had rows. Nothing correlated them.
|
|
24
|
+
*
|
|
25
|
+
* This command does that correlation. Each check answers three questions:
|
|
26
|
+
* WHAT is wrong, WHAT THE EVIDENCE IS, and WHAT TO DO ABOUT IT. A check that
|
|
27
|
+
* cannot say all three is not worth adding — that is exactly the "status
|
|
28
|
+
* without cause" that made the defects above survive.
|
|
29
|
+
*
|
|
30
|
+
* Exit code is 0 when nothing is wrong, 1 when any finding is an error, so
|
|
31
|
+
* the command is usable as a CI or pre-deploy gate.
|
|
32
|
+
*/
|
|
33
|
+
import fs from 'node:fs';
|
|
34
|
+
import path from 'node:path';
|
|
35
|
+
import { fileURLToPath } from 'node:url';
|
|
36
|
+
import { box, log, prism } from '@xec-sh/kit';
|
|
37
|
+
import { createDaemonClient } from '../daemon/daemon-client.js';
|
|
38
|
+
import { DEFAULT_DAEMON_CONFIG, OMNITRON_HOME } from '../config/defaults.js';
|
|
39
|
+
import { diskAtHome } from '../monitoring/host-disk.js';
|
|
40
|
+
import { emitJson, isJsonMode } from './output.js';
|
|
41
|
+
import { resolveOmnitronPgConfig } from '../database/connection.js';
|
|
42
|
+
import { OMNITRON_MIGRATIONS } from '../database/migrations/index.js';
|
|
43
|
+
import { compareTrees, listTree, processPredatesBuild } from '../shared/build-freshness.js';
|
|
44
|
+
import { describeError } from '../shared/describe-error.js';
|
|
45
|
+
import { findDominantErrors, findRepetitionLoad, findRetryLoops, findDuplicatedLogs, describeFinding, } from './log-health.js';
|
|
46
|
+
/**
|
|
47
|
+
* How long the daemon may take to answer before that is itself a finding.
|
|
48
|
+
* Generous: the call is local and normally answers in milliseconds, so this
|
|
49
|
+
* fires for a blocked supervisor, not for a busy machine.
|
|
50
|
+
*/
|
|
51
|
+
const SLOW_DAEMON_MS = 3_000;
|
|
52
|
+
/**
|
|
53
|
+
* How long an app may spend in a transitional state before it is stuck.
|
|
54
|
+
*
|
|
55
|
+
* Matches the daemon's own `resources.timeout` for a spawn (60s), doubled:
|
|
56
|
+
* a bootstrap app under load legitimately takes most of that budget, and a
|
|
57
|
+
* diagnostic that fires while the supervisor is still waiting would be
|
|
58
|
+
* reporting on the supervisor rather than on the app.
|
|
59
|
+
*/
|
|
60
|
+
const STARTUP_BUDGET_MS = 120_000;
|
|
61
|
+
/**
|
|
62
|
+
* How long after an app goes `online` its topology processes may still be
|
|
63
|
+
* starting.
|
|
64
|
+
*
|
|
65
|
+
* An app reports online from its main process; the rest come up alongside
|
|
66
|
+
* it. Thirty seconds is generous for that and short enough that a process
|
|
67
|
+
* which never starts is still reported within a minute.
|
|
68
|
+
*/
|
|
69
|
+
const SUBPROCESS_SETTLE_MS = 30_000;
|
|
70
|
+
/**
|
|
71
|
+
* How long after a container starts its published ports may still be silent.
|
|
72
|
+
*
|
|
73
|
+
* Postgres replays WAL before it listens, and an image that has to run
|
|
74
|
+
* initdb takes longer still. Fifteen seconds is past both on any machine
|
|
75
|
+
* that can run them at all.
|
|
76
|
+
*/
|
|
77
|
+
const CONTAINER_SETTLE_MS = 15_000;
|
|
78
|
+
export class Findings {
|
|
79
|
+
items = [];
|
|
80
|
+
skips = [];
|
|
81
|
+
add(finding) {
|
|
82
|
+
this.items.push(finding);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Record that an area was NOT examined.
|
|
86
|
+
*
|
|
87
|
+
* "No problems found" and "nothing was looked at" are the same output
|
|
88
|
+
* otherwise, and they lead an operator in opposite directions: the first
|
|
89
|
+
* says look elsewhere, the second says look again. A dead database takes
|
|
90
|
+
* four checks down with it, a daemon that is not answering takes seven, and
|
|
91
|
+
* before this the report for all of that was a green box with one error in
|
|
92
|
+
* it — the reader had no way to know that eleven areas had gone unread.
|
|
93
|
+
*/
|
|
94
|
+
skip(id, reason) {
|
|
95
|
+
this.skips.push({ id, reason });
|
|
96
|
+
}
|
|
97
|
+
all() {
|
|
98
|
+
// Errors first, then warnings, then info — an operator reads top-down.
|
|
99
|
+
const rank = { error: 0, warning: 1, info: 2 };
|
|
100
|
+
return [...this.items].sort((a, b) => rank[a.severity] - rank[b.severity]);
|
|
101
|
+
}
|
|
102
|
+
skipped() {
|
|
103
|
+
return [...this.skips];
|
|
104
|
+
}
|
|
105
|
+
get worst() {
|
|
106
|
+
if (this.items.some((f) => f.severity === 'error'))
|
|
107
|
+
return 'error';
|
|
108
|
+
if (this.items.some((f) => f.severity === 'warning'))
|
|
109
|
+
return 'warning';
|
|
110
|
+
return this.items.length > 0 ? 'info' : null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Checks
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
/**
|
|
117
|
+
* Every table the daemon's Postgres schema declares.
|
|
118
|
+
*
|
|
119
|
+
* This used to name five of the fifteen. A check that covers a third of the
|
|
120
|
+
* schema can miss the exact failure it was built for: `omnitron_users` was
|
|
121
|
+
* not on the list, so a database that had lost the table nobody can sign in
|
|
122
|
+
* without would have been reported healthy.
|
|
123
|
+
*
|
|
124
|
+
* Kept as a literal rather than derived, because the point is to notice a
|
|
125
|
+
* table that is GONE — deriving it from the live database would make the
|
|
126
|
+
* comparison vacuous. `doctor-required-tables.test.ts` keeps it honest from
|
|
127
|
+
* the other side, reading `OmnitronDatabase` out of `schema.ts` and failing
|
|
128
|
+
* when the two disagree, so adding a table to the schema and forgetting this
|
|
129
|
+
* list is a red test rather than a silent gap.
|
|
130
|
+
*/
|
|
131
|
+
export const REQUIRED_TABLES = [
|
|
132
|
+
'nodes',
|
|
133
|
+
'omnitron_users',
|
|
134
|
+
'omnitron_sessions',
|
|
135
|
+
'logs',
|
|
136
|
+
'alert_rules',
|
|
137
|
+
'alert_events',
|
|
138
|
+
'deployments',
|
|
139
|
+
'omnitron_audit_log',
|
|
140
|
+
'pipelines',
|
|
141
|
+
'pipeline_runs',
|
|
142
|
+
'traces',
|
|
143
|
+
'sync_buffer',
|
|
144
|
+
'sync_ingested',
|
|
145
|
+
'node_health_checks',
|
|
146
|
+
];
|
|
147
|
+
/**
|
|
148
|
+
* What a dead or empty database takes down with it.
|
|
149
|
+
*
|
|
150
|
+
* Named once, because three exits report it — connection refused, no tables,
|
|
151
|
+
* and a query that threw — and three copies of a list drift apart exactly
|
|
152
|
+
* when one of them matters.
|
|
153
|
+
*/
|
|
154
|
+
const DB_DEPENDENT_CHECKS = [
|
|
155
|
+
['db.pending-migrations', 'whether every migration has been applied'],
|
|
156
|
+
['db.missing-tables', 'whether the schema still holds every table the daemon queries'],
|
|
157
|
+
['logs.*', 'log volume, retention and ingestion'],
|
|
158
|
+
['alerts.unreadable-rule', 'whether every alert rule can still be parsed'],
|
|
159
|
+
['db.bloated', 'table size against live rows'],
|
|
160
|
+
];
|
|
161
|
+
/**
|
|
162
|
+
* The internal database: reachable, migrated, and actually holding the tables
|
|
163
|
+
* the daemon queries.
|
|
164
|
+
*
|
|
165
|
+
* Checking "did migrations run" by asking the bookkeeping table is not
|
|
166
|
+
* enough — the fourteen-day outage had an EMPTY database and no bookkeeping
|
|
167
|
+
* table at all, because the runner threw before creating one. So this looks
|
|
168
|
+
* for the tables themselves.
|
|
169
|
+
*/
|
|
170
|
+
async function checkDatabase(findings) {
|
|
171
|
+
const { host, port, database } = resolveOmnitronPgConfig();
|
|
172
|
+
const target = `${host}:${port}/${database}`;
|
|
173
|
+
let db;
|
|
174
|
+
try {
|
|
175
|
+
const { createOmnitronDb } = await import('../database/connection.js');
|
|
176
|
+
db = await createOmnitronDb({ max: 1, connectionTimeoutMillis: 5_000 });
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
findings.add({
|
|
180
|
+
id: 'db.unreachable',
|
|
181
|
+
severity: 'error',
|
|
182
|
+
title: 'Cannot connect to the Omnitron database',
|
|
183
|
+
evidence: [`target: ${target}`, `error: ${describeError(err)}`],
|
|
184
|
+
remedy: 'Start the omnitron-pg container (`omnitron up`), or point OMNITRON_DATABASE_URL at a reachable server.',
|
|
185
|
+
});
|
|
186
|
+
for (const [id, what] of DB_DEPENDENT_CHECKS)
|
|
187
|
+
findings.skip(id, `not checked: ${what} — the database is unreachable`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// What actually got looked at, so a throw below can report the gap it left
|
|
191
|
+
// instead of guessing. The first live run of this reported all five checks
|
|
192
|
+
// as "may not have been checked ... failed partway through" when the very
|
|
193
|
+
// first query had been refused and none of them had run — an honest report
|
|
194
|
+
// about itself is the one thing a diagnostic cannot get wrong.
|
|
195
|
+
const done = new Set();
|
|
196
|
+
try {
|
|
197
|
+
const { sql } = await import('kysely');
|
|
198
|
+
const tables = await sql `
|
|
199
|
+
SELECT tablename FROM pg_tables WHERE schemaname = 'public'
|
|
200
|
+
`.execute(db);
|
|
201
|
+
const present = new Set(tables.rows.map((r) => r.tablename));
|
|
202
|
+
if (present.size === 0) {
|
|
203
|
+
findings.add({
|
|
204
|
+
id: 'db.empty',
|
|
205
|
+
severity: 'error',
|
|
206
|
+
title: 'The Omnitron database has no tables',
|
|
207
|
+
evidence: [
|
|
208
|
+
`target: ${target}`,
|
|
209
|
+
`expected ${OMNITRON_MIGRATIONS.length} migration(s) to have run`,
|
|
210
|
+
],
|
|
211
|
+
remedy: 'Run `omnitron infra migrate`. If it fails, the error it prints is the real problem — the daemon only logs migration failures as warnings at boot.',
|
|
212
|
+
});
|
|
213
|
+
for (const [id, what] of DB_DEPENDENT_CHECKS.filter(([i]) => i !== 'db.pending-migrations')) {
|
|
214
|
+
findings.skip(id, `not checked: ${what} — the database has no tables`);
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
// Which migrations does the bookkeeping table say ran?
|
|
219
|
+
let applied = new Set();
|
|
220
|
+
if (present.has('migrations')) {
|
|
221
|
+
const rows = await sql `SELECT name FROM migrations`.execute(db);
|
|
222
|
+
applied = new Set(rows.rows.map((r) => r.name));
|
|
223
|
+
}
|
|
224
|
+
done.add('db.pending-migrations');
|
|
225
|
+
const pending = OMNITRON_MIGRATIONS.filter((m) => !applied.has(m.name)).map((m) => m.name);
|
|
226
|
+
if (pending.length > 0) {
|
|
227
|
+
findings.add({
|
|
228
|
+
id: 'db.pending-migrations',
|
|
229
|
+
severity: 'error',
|
|
230
|
+
title: `${pending.length} migration(s) have not been applied`,
|
|
231
|
+
evidence: [`target: ${target}`, `pending: ${pending.join(', ')}`],
|
|
232
|
+
remedy: 'Run `omnitron infra migrate`.',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
// Checked by name rather than inferred from bookkeeping: a database can
|
|
236
|
+
// carry migration rows and still be missing tables if someone dropped
|
|
237
|
+
// them — which is the outage this whole command was written for.
|
|
238
|
+
done.add('db.missing-tables');
|
|
239
|
+
const missing = REQUIRED_TABLES.filter((t) => !present.has(t));
|
|
240
|
+
if (missing.length > 0) {
|
|
241
|
+
findings.add({
|
|
242
|
+
id: 'db.missing-tables',
|
|
243
|
+
severity: 'error',
|
|
244
|
+
title: 'Tables the daemon queries are missing',
|
|
245
|
+
evidence: [`target: ${target}`, `missing: ${missing.join(', ')}`],
|
|
246
|
+
remedy: 'Run `omnitron infra migrate`. Until these exist, alert evaluation and session cleanup fail on every tick and log a warning each time.',
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
// What the log table itself says about the platform's health.
|
|
250
|
+
if (present.has('logs'))
|
|
251
|
+
await checkLogHealth(findings, db);
|
|
252
|
+
else
|
|
253
|
+
findings.skip('logs.*', 'the `logs` table does not exist');
|
|
254
|
+
done.add('logs.*');
|
|
255
|
+
if (present.has('alert_rules'))
|
|
256
|
+
await checkAlertRules(findings, db);
|
|
257
|
+
else
|
|
258
|
+
findings.skip('alerts.unreadable-rule', 'the `alert_rules` table does not exist');
|
|
259
|
+
done.add('alerts.unreadable-rule');
|
|
260
|
+
await checkTableGrowth(findings, db, target);
|
|
261
|
+
done.add('db.bloated');
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
// Which of the two findings this is depends on what the error SAYS, not
|
|
265
|
+
// on which call threw. `createOmnitronDb` builds a lazy pool, so it does
|
|
266
|
+
// not connect and does not throw — a database that is simply gone sails
|
|
267
|
+
// past the `db.unreachable` branch above and fails on the first query.
|
|
268
|
+
// Reported as "reachable but not queryable", the finding then sent an
|
|
269
|
+
// operator to read the logs of a container that was not running. Seen
|
|
270
|
+
// live, with `ECONNREFUSED` sitting in its own evidence.
|
|
271
|
+
if (isConnectionFailure(err)) {
|
|
272
|
+
findings.add({
|
|
273
|
+
id: 'db.unreachable',
|
|
274
|
+
severity: 'error',
|
|
275
|
+
title: 'Cannot connect to the Omnitron database',
|
|
276
|
+
evidence: [`target: ${target}`, `error: ${describeError(err)}`],
|
|
277
|
+
remedy: 'Start the omnitron-pg container (`omnitron up`), or point OMNITRON_DATABASE_URL at a reachable server. ' +
|
|
278
|
+
'If the container exists but will not start, this command reports its reason as `infra.not-running` — ' +
|
|
279
|
+
'a full disk inside the container runtime is the usual one, and it does not look like a disk problem from here.',
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
findings.add({
|
|
284
|
+
id: 'db.query-failed',
|
|
285
|
+
severity: 'error',
|
|
286
|
+
title: 'The Omnitron database is reachable but not queryable',
|
|
287
|
+
evidence: [`target: ${target}`, `error: ${describeError(err)}`],
|
|
288
|
+
remedy: 'Check the database logs — the connection opened, so this is a permissions or schema problem.',
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
const why = isConnectionFailure(err) ? 'the database stopped answering' : 'a database query failed';
|
|
292
|
+
for (const [id, what] of DB_DEPENDENT_CHECKS.filter(([checkId]) => !done.has(checkId))) {
|
|
293
|
+
findings.skip(id, `not checked: ${what} — ${why}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
await db.destroy().catch(() => undefined);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/** A table this large is worth a finding at all. */
|
|
301
|
+
const TABLE_WARN_BYTES = 1024 ** 3;
|
|
302
|
+
/**
|
|
303
|
+
* Bytes per live row above which the space is not the rows.
|
|
304
|
+
*
|
|
305
|
+
* A log line is a few hundred bytes and the widest row this schema holds is
|
|
306
|
+
* well under a kilobyte, so 8 KiB — one Postgres page per row — is far past
|
|
307
|
+
* anything the contents can explain, and far below the ~207 KiB per row this
|
|
308
|
+
* host was measured at.
|
|
309
|
+
*/
|
|
310
|
+
const BLOAT_BYTES_PER_ROW = 8 * 1024;
|
|
311
|
+
/** Space each surviving row is costing. `Infinity` when nothing survives. */
|
|
312
|
+
export function bytesPerLiveRow(bytes, live) {
|
|
313
|
+
return live > 0 ? bytes / live : Infinity;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Is a table's size explained by its contents?
|
|
317
|
+
*
|
|
318
|
+
* Split out so the judgement can be tested against real figures without a
|
|
319
|
+
* database: the SQL that gathers them is exercised by running the command.
|
|
320
|
+
*/
|
|
321
|
+
export function looksBloated(bytes, live) {
|
|
322
|
+
return bytes >= TABLE_WARN_BYTES && bytesPerLiveRow(bytes, live) > BLOAT_BYTES_PER_ROW;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* How much disk the database is holding, and whether the rows justify it.
|
|
326
|
+
*
|
|
327
|
+
* On this platform the largest consumer is the `logs` table, which until
|
|
328
|
+
* recently had no retention at all: 22.5 million rows and 13 GB of a 17 GB
|
|
329
|
+
* schema. Retention now runs — and the table is still 13 GB, because
|
|
330
|
+
* `DELETE` does not return space to the operating system. Plain `VACUUM`
|
|
331
|
+
* marks the pages reusable INSIDE the file; it truncates the file only when
|
|
332
|
+
* the free pages happen to sit at the end. So a table can hold 62 000 live
|
|
333
|
+
* rows in thirteen gigabytes indefinitely, and every measurement taken from
|
|
334
|
+
* its size alone — a growth rate, a projection of when the disk fills — is
|
|
335
|
+
* arithmetic on a number that stopped meaning "contents" long ago.
|
|
336
|
+
*
|
|
337
|
+
* That is why the check divides. Size on its own says "large"; size per live
|
|
338
|
+
* row says which KIND of large, and the two have opposite remedies: shorten
|
|
339
|
+
* the retention window, or reclaim the file.
|
|
340
|
+
*/
|
|
341
|
+
async function checkTableGrowth(findings, db, target) {
|
|
342
|
+
const ERROR_BYTES = 5 * 1024 ** 3;
|
|
343
|
+
/**
|
|
344
|
+
* Bytes per live row above which the space is not the rows.
|
|
345
|
+
*
|
|
346
|
+
* A log line is a few hundred bytes and the widest row this schema holds
|
|
347
|
+
* is well under a kilobyte, so 8 KB — one Postgres page per row — is far
|
|
348
|
+
* past anything the contents can explain, and far below the ~218 KB per
|
|
349
|
+
* row this host was measured at.
|
|
350
|
+
*/
|
|
351
|
+
const { sql } = await import('kysely');
|
|
352
|
+
const gib = (n) => `${(n / 1024 ** 3).toFixed(1)} GiB`;
|
|
353
|
+
const sizes = await sql `
|
|
354
|
+
SELECT c.relname,
|
|
355
|
+
pg_total_relation_size(c.oid)::text AS bytes,
|
|
356
|
+
COALESCE(st.n_live_tup, 0)::text AS live,
|
|
357
|
+
(SELECT sum(pg_total_relation_size(i.oid))::text
|
|
358
|
+
FROM pg_class i JOIN pg_namespace m ON m.oid = i.relnamespace
|
|
359
|
+
WHERE m.nspname = 'public' AND i.relkind = 'r') AS total
|
|
360
|
+
FROM pg_class c
|
|
361
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
362
|
+
LEFT JOIN pg_stat_user_tables st ON st.relid = c.oid
|
|
363
|
+
WHERE n.nspname = 'public' AND c.relkind = 'r'
|
|
364
|
+
ORDER BY pg_total_relation_size(c.oid) DESC
|
|
365
|
+
LIMIT 3
|
|
366
|
+
`.execute(db);
|
|
367
|
+
if (sizes.rows.length === 0)
|
|
368
|
+
return;
|
|
369
|
+
const total = Number(sizes.rows[0].total ?? 0);
|
|
370
|
+
if (!Number.isFinite(total) || total < TABLE_WARN_BYTES)
|
|
371
|
+
return;
|
|
372
|
+
const biggest = sizes.rows[0];
|
|
373
|
+
const bytes = Number(biggest.bytes);
|
|
374
|
+
const live = Number(biggest.live);
|
|
375
|
+
const perRow = bytesPerLiveRow(bytes, live);
|
|
376
|
+
// Bloat first: when it is the answer, the size finding below would send an
|
|
377
|
+
// operator to shorten a retention window that is already working.
|
|
378
|
+
if (looksBloated(bytes, live)) {
|
|
379
|
+
const disk = await diskAtHome();
|
|
380
|
+
findings.add({
|
|
381
|
+
id: 'db.bloated',
|
|
382
|
+
severity: 'warning',
|
|
383
|
+
title: `Table "${biggest.relname}" occupies ${gib(bytes)} for ${live.toLocaleString('en-US')} live row(s)`,
|
|
384
|
+
evidence: [
|
|
385
|
+
`target: ${target}`,
|
|
386
|
+
`${(perRow / 1024).toFixed(0)} KiB per live row — the rows are a few hundred bytes`,
|
|
387
|
+
...(disk ? [`${gib(disk.free)} free on the filesystem holding ${OMNITRON_HOME}`] : []),
|
|
388
|
+
],
|
|
389
|
+
remedy: 'Retention is doing its job; the space it frees is not being returned. `DELETE` leaves the pages inside ' +
|
|
390
|
+
'the file, and plain `VACUUM` only makes them reusable — the file shrinks only when the free pages ' +
|
|
391
|
+
`happen to be at the end. Reclaim it with \`VACUUM FULL ${biggest.relname}\` (an ACCESS EXCLUSIVE lock ` +
|
|
392
|
+
'for the duration, so schedule it) or `pg_repack` (no long lock, needs the extension). Until then this ' +
|
|
393
|
+
'space is unavailable to anything else on the disk, and a full disk on this host has been diagnosed ' +
|
|
394
|
+
'once as an auth fault and once as a Docker networking fault.',
|
|
395
|
+
});
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const evidence = [
|
|
399
|
+
`target: ${target}`,
|
|
400
|
+
`largest: ${sizes.rows.map((r) => `${r.relname} ${gib(Number(r.bytes))}`).join(', ')}`,
|
|
401
|
+
];
|
|
402
|
+
// The age of the oldest surviving row answers "is pruning happening",
|
|
403
|
+
// which the size alone does not: a large table inside its window is a busy
|
|
404
|
+
// platform, the same size outside it is a retention pass that is not running.
|
|
405
|
+
try {
|
|
406
|
+
const oldest = await sql `
|
|
407
|
+
SELECT min(timestamp) AS oldest FROM logs
|
|
408
|
+
`.execute(db);
|
|
409
|
+
const at = oldest.rows[0]?.oldest;
|
|
410
|
+
if (at) {
|
|
411
|
+
const days = (Date.now() - new Date(at).getTime()) / 86_400_000;
|
|
412
|
+
evidence.push(`oldest surviving log row is ${days < 1 ? '<1' : days.toFixed(0)} day(s) old`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// No `logs` table, or no `timestamp` column on it. The size finding
|
|
417
|
+
// stands on its own.
|
|
418
|
+
}
|
|
419
|
+
findings.add({
|
|
420
|
+
id: total >= ERROR_BYTES ? 'db.oversized' : 'db.growing',
|
|
421
|
+
severity: total >= ERROR_BYTES ? 'error' : 'warning',
|
|
422
|
+
title: `The Omnitron database occupies ${gib(total)}`,
|
|
423
|
+
evidence,
|
|
424
|
+
remedy: 'Compare the oldest row above against `logging.databaseRetentionDays` in the ecosystem config (14 by default, ' +
|
|
425
|
+
'0 disables pruning entirely). History older than that window means the retention pass is not running — it is ' +
|
|
426
|
+
'armed by the log collector at daemon start, so a daemon that failed to arm it prunes nothing and says nothing. ' +
|
|
427
|
+
'Note that `logging.maxSize` and `logging.maxFiles` govern the rotated FILES and have no effect on the table.',
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Alert rules the evaluator cannot read.
|
|
432
|
+
*
|
|
433
|
+
* An expression that matches none of the supported forms is answered with
|
|
434
|
+
* `firing: false` — the same answer a healthy platform gives — so the rule
|
|
435
|
+
* sits in the console enabled, green, and permanently inert. Somebody wrote
|
|
436
|
+
* it to catch a condition; the platform is not catching it, and the only
|
|
437
|
+
* other place that says so is a line in the daemon log, once a cycle,
|
|
438
|
+
* addressed to nobody.
|
|
439
|
+
*
|
|
440
|
+
* Checked against the evaluator's own list of forms rather than a copy, so
|
|
441
|
+
* the two cannot come to disagree about which rules work. The remedy quotes
|
|
442
|
+
* the same grammar: it said «three forms» by hand, wrong the day a fourth came.
|
|
443
|
+
*/
|
|
444
|
+
async function checkAlertRules(findings, db) {
|
|
445
|
+
const { sql } = await import('kysely');
|
|
446
|
+
const { isAlertExpressionParseable, ALERT_EXPRESSION_HELP } = await import('../shared/alert-expression.js');
|
|
447
|
+
const rows = await sql `
|
|
448
|
+
SELECT id, name, expression FROM alert_rules WHERE enabled = true
|
|
449
|
+
`.execute(db);
|
|
450
|
+
const broken = rows.rows.filter((r) => !isAlertExpressionParseable(r.expression));
|
|
451
|
+
if (broken.length === 0)
|
|
452
|
+
return;
|
|
453
|
+
findings.add({
|
|
454
|
+
id: 'alerts.unreadable-rule',
|
|
455
|
+
severity: 'warning',
|
|
456
|
+
title: `${broken.length} enabled alert rule(s) can never fire`,
|
|
457
|
+
evidence: broken.slice(0, 5).map((r) => `"${r.name}": ${r.expression}`),
|
|
458
|
+
remedy: `The evaluator reads these forms and no others: ${ALERT_EXPRESSION_HELP}. ` +
|
|
459
|
+
'Anything else evaluates to "not firing", which is indistinguishable from a healthy ' +
|
|
460
|
+
'platform — so these rules show as enabled and green while catching nothing. ' +
|
|
461
|
+
'Fix or disable them: an alert nobody can rely on is worse than an absent one.',
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* The log table as a symptom.
|
|
466
|
+
*
|
|
467
|
+
* A message repeated far more often than anything else is a loop that cannot
|
|
468
|
+
* make progress, and it is invisible from every surface an operator normally
|
|
469
|
+
* looks at: the logs page shows the most recent lines, which are all the same
|
|
470
|
+
* line; the alerts page shows rules nobody wrote for a failure nobody
|
|
471
|
+
* predicted; and the daemon reports itself healthy throughout, because it is.
|
|
472
|
+
*
|
|
473
|
+
* This host carried 2.5 million copies of one message across three days —
|
|
474
|
+
* one event retried 799 289 times — and 13 GB of log table, most of it that.
|
|
475
|
+
* Nothing said so.
|
|
476
|
+
*/
|
|
477
|
+
async function checkLogHealth(findings, db) {
|
|
478
|
+
const { sql } = await import('kysely');
|
|
479
|
+
// The Kysely instance, typed loosely: this module holds no schema type for
|
|
480
|
+
// the omnitron database and the query is raw SQL either way.
|
|
481
|
+
const database = db;
|
|
482
|
+
// A day: long enough that a loop stands out against ordinary noise, short
|
|
483
|
+
// enough that yesterday's fixed problem does not keep reporting itself.
|
|
484
|
+
const rows = await sql `
|
|
485
|
+
SELECT app,
|
|
486
|
+
left(message, 80) AS message,
|
|
487
|
+
count(*)::text AS count,
|
|
488
|
+
max((metadata->>'retryCount')::bigint)::text AS max_retry
|
|
489
|
+
FROM logs
|
|
490
|
+
WHERE level IN ('error', 'fatal')
|
|
491
|
+
AND timestamp > now() - interval '24 hours'
|
|
492
|
+
GROUP BY app, left(message, 80)
|
|
493
|
+
ORDER BY count(*) DESC
|
|
494
|
+
LIMIT 50
|
|
495
|
+
`.execute(database);
|
|
496
|
+
const counts = rows.rows.map((r) => ({
|
|
497
|
+
app: r.app,
|
|
498
|
+
message: r.message,
|
|
499
|
+
count: Number(r.count),
|
|
500
|
+
retryCount: Number(r.max_retry ?? 0),
|
|
501
|
+
}));
|
|
502
|
+
for (const finding of findDominantErrors(counts)) {
|
|
503
|
+
findings.add({
|
|
504
|
+
id: 'logs.dominant-error',
|
|
505
|
+
severity: 'warning',
|
|
506
|
+
title: `One message is ${Math.round((finding.share ?? 0) * 100)}% of the last day's errors`,
|
|
507
|
+
evidence: [
|
|
508
|
+
describeFinding(finding),
|
|
509
|
+
'a message repeating at this share is a loop that cannot make progress, not background noise',
|
|
510
|
+
],
|
|
511
|
+
remedy: `Read it: \`omnitron logs ${finding.app} --level error\`. The same line repeating means the work behind it is not getting done.`,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
// Lines stored under two app names at the same instant. A join over a
|
|
515
|
+
// short window rather than the whole table: the point is to detect the
|
|
516
|
+
// condition, not to count every instance of it.
|
|
517
|
+
const dupes = await sql `
|
|
518
|
+
SELECT a.app, b.app AS other_app, count(*)::text AS count
|
|
519
|
+
FROM logs a
|
|
520
|
+
JOIN logs b ON a.timestamp = b.timestamp AND a.message = b.message AND a.app < b.app
|
|
521
|
+
WHERE a.timestamp > now() - interval '15 minutes'
|
|
522
|
+
GROUP BY a.app, b.app
|
|
523
|
+
`.execute(database);
|
|
524
|
+
const recentTotal = await sql `
|
|
525
|
+
SELECT count(*)::text AS count FROM logs WHERE timestamp > now() - interval '15 minutes'
|
|
526
|
+
`.execute(database);
|
|
527
|
+
for (const finding of findDuplicatedLogs(dupes.rows.map((r) => ({ app: r.app, otherApp: r.other_app, count: Number(r.count) })), Number(recentTotal.rows[0]?.count ?? 0))) {
|
|
528
|
+
findings.add({
|
|
529
|
+
id: 'logs.duplicated',
|
|
530
|
+
severity: 'warning',
|
|
531
|
+
title: 'Log lines are being stored twice',
|
|
532
|
+
evidence: [
|
|
533
|
+
describeFinding(finding),
|
|
534
|
+
'titan-pm re-logs child output through the daemon logger while the orchestrator is already capturing it',
|
|
535
|
+
'the table pays for both copies, and every per-app count is wrong by whichever component emitted the line',
|
|
536
|
+
],
|
|
537
|
+
remedy: 'Nothing to do at runtime — the duplicate path is in the process spawner, not in configuration.',
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
for (const finding of findRepetitionLoad(counts)) {
|
|
541
|
+
findings.add({
|
|
542
|
+
id: 'logs.repetition',
|
|
543
|
+
severity: 'warning',
|
|
544
|
+
title: `Repetition is ${Math.round((finding.share ?? 0) * 100)}% of the last day's errors, spread over ${finding.message.split('; ').length} messages`,
|
|
545
|
+
evidence: [
|
|
546
|
+
describeFinding(finding),
|
|
547
|
+
'no single one of these is large enough to be reported on its own — each further loop enlarges the denominator the share is measured against',
|
|
548
|
+
],
|
|
549
|
+
remedy: 'Each line is a loop that cannot make progress. Take them one at a time: ' +
|
|
550
|
+
'`omnitron logs <app> --level error` shows the failing operation, and the ' +
|
|
551
|
+
'apps named above are where the work is not getting done.',
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
for (const finding of findRetryLoops(counts.filter((c) => c.retryCount > 0))) {
|
|
555
|
+
findings.add({
|
|
556
|
+
id: 'logs.retry-loop',
|
|
557
|
+
severity: 'error',
|
|
558
|
+
title: 'An operation is being retried without limit',
|
|
559
|
+
evidence: [
|
|
560
|
+
describeFinding(finding),
|
|
561
|
+
'a retry policy has a ceiling; a count this high means there is none, and the work will never complete',
|
|
562
|
+
],
|
|
563
|
+
remedy: `Find the stuck item and either fix or discard it — retrying is not going to work. \`omnitron logs ${finding.app} --level error\` carries its id.`,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Apps: what is not running, and — crucially — WHY.
|
|
569
|
+
*
|
|
570
|
+
* The daemon already captures an exit code, a signal and a stderr tail for
|
|
571
|
+
* every crash. `omnitron list` shows none of it: an app reads as `errored`
|
|
572
|
+
* with no further explanation, which is how `acme/dev/main` sat broken for a
|
|
573
|
+
* whole session.
|
|
574
|
+
*/
|
|
575
|
+
async function checkApps(findings, client, apps) {
|
|
576
|
+
for (const app of apps) {
|
|
577
|
+
if (app.status === 'online')
|
|
578
|
+
continue;
|
|
579
|
+
// A stopped app is a state, not a fault — unless it is critical.
|
|
580
|
+
if (app.status === 'stopped' && !app.critical)
|
|
581
|
+
continue;
|
|
582
|
+
const evidence = [`status: ${app.status}`];
|
|
583
|
+
let remedy = `Inspect it with \`omnitron inspect ${app.name}\`, then \`omnitron start ${app.name}\`.`;
|
|
584
|
+
try {
|
|
585
|
+
const diag = await client.inspect({ name: app.name });
|
|
586
|
+
const exit = diag.lastExit;
|
|
587
|
+
if (exit) {
|
|
588
|
+
evidence.push(`exited at ${exit.atIso}`);
|
|
589
|
+
if (exit.signal)
|
|
590
|
+
evidence.push(`killed by ${exit.signal}`);
|
|
591
|
+
else if (exit.code !== null)
|
|
592
|
+
evidence.push(`exit code ${exit.code}`);
|
|
593
|
+
if (exit.message)
|
|
594
|
+
evidence.push(`message: ${exit.message}`);
|
|
595
|
+
if (exit.stderrTail.length > 0) {
|
|
596
|
+
// Start-up stages are written to stderr as `[omnitron:boot] <stage>`
|
|
597
|
+
// (see bootstrap-process.ts). The LAST one is the stage that never
|
|
598
|
+
// completed — which turns "timed out after 30000ms" from a fact
|
|
599
|
+
// about the supervisor into a fact about the app.
|
|
600
|
+
const stages = exit.stderrTail
|
|
601
|
+
.map((line) => /\[omnitron:boot\]\s+(\S+)/.exec(line)?.[1])
|
|
602
|
+
.filter((stage) => Boolean(stage));
|
|
603
|
+
if (stages.length > 0) {
|
|
604
|
+
const stalled = stages[stages.length - 1];
|
|
605
|
+
evidence.push(`reached stage: ${stalled}`);
|
|
606
|
+
if (stalled !== 'ready') {
|
|
607
|
+
remedy = stageRemedy(stalled, app.name);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
// The last few lines are almost always the actual error.
|
|
611
|
+
const realOutput = exit.stderrTail.filter((line) => !line.includes('[omnitron:boot]'));
|
|
612
|
+
if (realOutput.length > 0) {
|
|
613
|
+
evidence.push('last stderr:');
|
|
614
|
+
for (const line of realOutput.slice(-5))
|
|
615
|
+
evidence.push(` ${line}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
// Silence is itself a finding: it narrows the cause sharply.
|
|
620
|
+
evidence.push('stderr: (empty — the process died before writing anything)');
|
|
621
|
+
remedy =
|
|
622
|
+
`Run the entry point directly to see the failure: the child produced no output, which usually means ` +
|
|
623
|
+
`it crashed during module import or blocked before its first log line. \`omnitron inspect ${app.name}\` shows the resolved command.`;
|
|
624
|
+
}
|
|
625
|
+
if (exit.signal === 'SIGKILL') {
|
|
626
|
+
remedy = 'SIGKILL usually means the OOM killer or a hard timeout. Check available memory and the app\'s startupTimeout.';
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
evidence.push('no exit record — the app has not started, rather than started and died');
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
catch (err) {
|
|
634
|
+
evidence.push(`could not read diagnostics: ${describeError(err)}`);
|
|
635
|
+
}
|
|
636
|
+
// `starting` and `stopping` are transitions, not faults. An app three
|
|
637
|
+
// seconds into a restart is doing exactly what it should, and reporting
|
|
638
|
+
// it as an error next to a crashed one teaches an operator that the
|
|
639
|
+
// error column means nothing.
|
|
640
|
+
//
|
|
641
|
+
// A transition that has outlasted the start-up budget is a different
|
|
642
|
+
// thing: nothing is coming, and the app is hung rather than starting.
|
|
643
|
+
// `uptime` on a transitional app measures how long it has been in that
|
|
644
|
+
// state, which is the only clock available here.
|
|
645
|
+
const transitional = app.status === 'starting' || app.status === 'stopping';
|
|
646
|
+
const stuck = transitional && app.uptime > STARTUP_BUDGET_MS;
|
|
647
|
+
if (transitional && !stuck) {
|
|
648
|
+
findings.add({
|
|
649
|
+
id: `app.${app.status}`,
|
|
650
|
+
severity: 'info',
|
|
651
|
+
title: `App "${app.name}" is ${app.status}`,
|
|
652
|
+
evidence: [`status: ${app.status}`, `for ${Math.round(app.uptime / 1000)}s`],
|
|
653
|
+
remedy: `Run \`omnitron doctor\` again — if it is still ${app.status}, it is stuck rather than slow.`,
|
|
654
|
+
});
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (stuck) {
|
|
658
|
+
evidence.push(`in this state for ${Math.round(app.uptime / 1000)}s`);
|
|
659
|
+
evidence.push(`the start-up budget is ${STARTUP_BUDGET_MS / 1000}s`);
|
|
660
|
+
remedy = `It is not starting slowly, it is not starting. \`omnitron logs ${app.name}\` shows how far it got.`;
|
|
661
|
+
}
|
|
662
|
+
findings.add({
|
|
663
|
+
id: `app.${app.status}`,
|
|
664
|
+
severity: app.critical ? 'error' : 'warning',
|
|
665
|
+
title: `${app.critical ? 'Critical app' : 'App'} "${app.name}" is ${stuck ? `stuck ${app.status}` : app.status}`,
|
|
666
|
+
evidence,
|
|
667
|
+
remedy,
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
// Metrics that never move are the signature of a sampler that is not
|
|
671
|
+
// running — precisely the defect where apps started outside `startAll`
|
|
672
|
+
// reported 0% CPU while holding 105 MB.
|
|
673
|
+
const online = apps.filter((a) => a.status === 'online');
|
|
674
|
+
const allZero = online.length > 0 && online.every((a) => a.cpu === 0 && a.memory === 0);
|
|
675
|
+
if (allZero) {
|
|
676
|
+
findings.add({
|
|
677
|
+
id: 'metrics.not-sampled',
|
|
678
|
+
severity: 'warning',
|
|
679
|
+
title: 'No CPU or memory readings for any running app',
|
|
680
|
+
evidence: [
|
|
681
|
+
`${online.length} app(s) online, every one reporting cpu 0 and memory 0`,
|
|
682
|
+
'a running Node process is never truly at 0 MB',
|
|
683
|
+
],
|
|
684
|
+
remedy: 'Metrics sampling is armed from the daemon config. Restart the daemon (`omnitron down && omnitron up`); if the readings stay at zero, the sampler is not running.',
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* What an app hides behind `online`.
|
|
690
|
+
*
|
|
691
|
+
* `omnitron list` reports one status per app, taken from its main process.
|
|
692
|
+
* A bootstrap app is several processes, and everything that goes wrong with
|
|
693
|
+
* the others is invisible at that level — which is how two pool workers spent
|
|
694
|
+
* a full session being killed and respawned every thirty seconds while their
|
|
695
|
+
* apps read `online` throughout. These three checks look underneath.
|
|
696
|
+
*/
|
|
697
|
+
export async function checkAppInternals(findings, apps) {
|
|
698
|
+
for (const app of apps) {
|
|
699
|
+
if (app.status !== 'online')
|
|
700
|
+
continue;
|
|
701
|
+
for (const proc of app.processes ?? []) {
|
|
702
|
+
// A sub-process whose pid the OS no longer knows. The orchestrator
|
|
703
|
+
// reports this as `crashed` rather than `stopped` precisely so it can
|
|
704
|
+
// be told apart from one that was never started.
|
|
705
|
+
if (proc.status === 'crashed') {
|
|
706
|
+
findings.add({
|
|
707
|
+
id: 'app.subprocess-crashed',
|
|
708
|
+
severity: 'error',
|
|
709
|
+
title: `"${app.name}" is online but its "${proc.name}" process is gone`,
|
|
710
|
+
evidence: [
|
|
711
|
+
`app status: online (pid ${app.pid})`,
|
|
712
|
+
`${proc.name}: ${proc.type}, last known pid ${proc.pid ?? 'none'} — no such process`,
|
|
713
|
+
],
|
|
714
|
+
remedy: `Restart the app (\`omnitron restart ${app.name}\`) and check its log for what killed that process.`,
|
|
715
|
+
});
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (proc.status === 'stopped') {
|
|
719
|
+
// An app reports `online` as soon as its main process is up, and its
|
|
720
|
+
// topology processes come up around the same time — so a child that
|
|
721
|
+
// is still stopped a few seconds in is being started, not missing.
|
|
722
|
+
// Reported either way, because it may be the last thing said before
|
|
723
|
+
// it never starts, but at the weight the age warrants.
|
|
724
|
+
const settling = app.uptime < SUBPROCESS_SETTLE_MS;
|
|
725
|
+
findings.add({
|
|
726
|
+
id: 'app.subprocess-stopped',
|
|
727
|
+
severity: settling ? 'info' : 'warning',
|
|
728
|
+
title: settling
|
|
729
|
+
? `"${app.name}" is online and its "${proc.name}" process has not started yet`
|
|
730
|
+
: `"${app.name}" is online but its "${proc.name}" process is not running`,
|
|
731
|
+
evidence: [
|
|
732
|
+
`${proc.name}: ${proc.type}, declared in the app's topology, no pid`,
|
|
733
|
+
`app online for ${Math.round(app.uptime / 1000)}s`,
|
|
734
|
+
],
|
|
735
|
+
remedy: settling
|
|
736
|
+
? `Run \`omnitron doctor\` again — after ${SUBPROCESS_SETTLE_MS / 1000}s this stops being start-up.`
|
|
737
|
+
: `Whatever this process does is not happening. \`omnitron inspect ${app.name}\` shows the resolved topology.`,
|
|
738
|
+
});
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
// A pool that no longer matches its declaration. Growth is the easy
|
|
742
|
+
// case to miss: a pool with more workers than asked for still reports
|
|
743
|
+
// itself healthy, because it does have workers.
|
|
744
|
+
if (proc.declaredInstances > 0 && proc.instances !== proc.declaredInstances) {
|
|
745
|
+
const grown = proc.instances > proc.declaredInstances;
|
|
746
|
+
findings.add({
|
|
747
|
+
id: grown ? 'pool.oversized' : 'pool.undersized',
|
|
748
|
+
severity: 'warning',
|
|
749
|
+
title: `Pool "${app.name}/${proc.name}" is running ${proc.instances} workers, not the ${proc.declaredInstances} declared`,
|
|
750
|
+
evidence: [
|
|
751
|
+
`declared instances: ${proc.declaredInstances}`,
|
|
752
|
+
`live workers: ${proc.instances}`,
|
|
753
|
+
],
|
|
754
|
+
remedy: grown
|
|
755
|
+
? 'Autoscaling only applies to a pool whose topology entry sets `scaling.strategy: "auto"`. If this one does not, the pool grew without being asked to.'
|
|
756
|
+
: 'Workers are dying faster than the pool replaces them — check the app log for their exit reason.',
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
export async function probePort(port, host = '127.0.0.1', timeoutMs = 3_000) {
|
|
763
|
+
const net = await import('node:net');
|
|
764
|
+
return new Promise((resolve) => {
|
|
765
|
+
const socket = new net.Socket();
|
|
766
|
+
const done = (answer) => {
|
|
767
|
+
socket.destroy();
|
|
768
|
+
resolve(answer);
|
|
769
|
+
};
|
|
770
|
+
socket.setTimeout(timeoutMs);
|
|
771
|
+
socket.once('connect', () => done('open'));
|
|
772
|
+
socket.once('timeout', () => done('no-answer'));
|
|
773
|
+
// ECONNREFUSED is an answer; anything else — a host that will not
|
|
774
|
+
// resolve, a descriptor limit — is the probe failing rather than the
|
|
775
|
+
// port, and is not the container's to explain.
|
|
776
|
+
socket.once('error', (err) => done(err.code === 'ECONNREFUSED' ? 'refused' : 'no-answer'));
|
|
777
|
+
socket.connect(port, host);
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
async function tcpReachable(port, host = '127.0.0.1', timeoutMs = 3_000) {
|
|
781
|
+
return (await probePort(port, host, timeoutMs)) === 'open';
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* An app that answers `online` while nothing is listening on its port.
|
|
785
|
+
*
|
|
786
|
+
* The supervisor's notion of "online" is that the process exists. Whether it
|
|
787
|
+
* ever finished binding is a different question, and the one an operator is
|
|
788
|
+
* actually asking when a request fails.
|
|
789
|
+
*/
|
|
790
|
+
export async function checkPorts(findings, apps) {
|
|
791
|
+
for (const app of apps) {
|
|
792
|
+
if (app.status !== 'online' || app.port === null)
|
|
793
|
+
continue;
|
|
794
|
+
if (await tcpReachable(app.port))
|
|
795
|
+
continue;
|
|
796
|
+
findings.add({
|
|
797
|
+
id: 'app.port-unreachable',
|
|
798
|
+
severity: 'error',
|
|
799
|
+
title: `"${app.name}" is online but nothing is listening on port ${app.port}`,
|
|
800
|
+
evidence: [
|
|
801
|
+
`app status: online (pid ${app.pid})`,
|
|
802
|
+
`TCP connect to 127.0.0.1:${app.port} refused or timed out`,
|
|
803
|
+
],
|
|
804
|
+
remedy: `The process is alive but its HTTP transport never bound, or bound elsewhere. ` +
|
|
805
|
+
`Check the app log around start-up; \`omnitron inspect ${app.name}\` shows the port it was told to use.`,
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Turn a stalled start-up stage into advice.
|
|
811
|
+
*
|
|
812
|
+
* Each stage narrows the cause sharply, which is the entire point of emitting
|
|
813
|
+
* them: "timed out after 30s" is a fact about the supervisor, "stalled at
|
|
814
|
+
* application:starting" is a fact about the app.
|
|
815
|
+
*/
|
|
816
|
+
function stageRemedy(stage, appName) {
|
|
817
|
+
if (stage.startsWith('config:')) {
|
|
818
|
+
return 'Start-up stalled while loading the bootstrap config — check that the file parses and that any imports it performs terminate.';
|
|
819
|
+
}
|
|
820
|
+
if (stage.startsWith('module:')) {
|
|
821
|
+
return 'Start-up stalled importing the app module. Top-level `await` in the module graph is the usual cause; run the built entry point directly to reproduce.';
|
|
822
|
+
}
|
|
823
|
+
if (stage === 'application:creating' || stage === 'application:created') {
|
|
824
|
+
return 'Start-up stalled in `Application.create` — a provider factory is not resolving. Try `omnitron inspect ' + appName + ' --graph` to see the DI closure.';
|
|
825
|
+
}
|
|
826
|
+
if (stage === 'application:starting') {
|
|
827
|
+
return 'Start-up stalled in `Application.start` — an `onStart` hook is not returning. A module waiting on a database or Redis that never answers is the common case.';
|
|
828
|
+
}
|
|
829
|
+
if (stage.startsWith('hook:')) {
|
|
830
|
+
return `Start-up stalled in the "${stage.slice(5)}" lifecycle hook declared by this app's bootstrap file.`;
|
|
831
|
+
}
|
|
832
|
+
return `Start-up stalled at "${stage}".`;
|
|
833
|
+
}
|
|
834
|
+
/** The daemon's own footing: uptime, and whether it is reporting its own faults. */
|
|
835
|
+
async function checkDaemon(findings, client) {
|
|
836
|
+
const status = await client.status();
|
|
837
|
+
// A daemon that restarts constantly loses the in-memory state everything
|
|
838
|
+
// else depends on, so short uptime with running apps is worth surfacing.
|
|
839
|
+
const uptimeMinutes = Math.floor((status.uptime ?? 0) / 60_000);
|
|
840
|
+
if (uptimeMinutes < 2 && status.apps.length > 0) {
|
|
841
|
+
findings.add({
|
|
842
|
+
id: 'daemon.recently-restarted',
|
|
843
|
+
severity: 'info',
|
|
844
|
+
title: 'The daemon restarted less than two minutes ago',
|
|
845
|
+
evidence: [`uptime: ${uptimeMinutes}m`, `apps tracked: ${status.apps.length}`],
|
|
846
|
+
remedy: 'If this was not deliberate, check `omnitron logs` for the shutdown that preceded it.',
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
// The daemon aggregates CPU across every managed process. Zero while apps
|
|
850
|
+
// are online is the same signature as the per-app check below, seen from
|
|
851
|
+
// the other end.
|
|
852
|
+
if (status.apps.some((a) => a.status === 'online') && status.totalMemory === 0) {
|
|
853
|
+
findings.add({
|
|
854
|
+
id: 'daemon.no-aggregate-metrics',
|
|
855
|
+
severity: 'info',
|
|
856
|
+
title: 'The daemon reports zero total memory across running apps',
|
|
857
|
+
evidence: [`apps online: ${status.apps.filter((a) => a.status === 'online').length}`],
|
|
858
|
+
remedy: 'Usually the same cause as any "no CPU or memory readings" finding below.',
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Is the daemon running the code in this checkout?
|
|
864
|
+
*
|
|
865
|
+
* Everything here loads through `dist`, and nothing else asks whether `dist`
|
|
866
|
+
* matches `src`. The two ways that goes wrong are different findings because
|
|
867
|
+
* they have different remedies: a source edited since the last build needs a
|
|
868
|
+
* build, and a daemon started before the last build needs a restart. Both
|
|
869
|
+
* present identically — as a change that appears to have had no effect.
|
|
870
|
+
*
|
|
871
|
+
* Only meaningful in a development checkout. An installed package has no
|
|
872
|
+
* `src/`, and the check produces nothing rather than guessing.
|
|
873
|
+
*/
|
|
874
|
+
async function checkBuildFreshness(findings, daemonStartedMs) {
|
|
875
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
876
|
+
// `dist/commands/` at runtime, `src/commands/` under a source runner.
|
|
877
|
+
const packageRoot = path.resolve(here, '../..');
|
|
878
|
+
const sourceRoot = path.join(packageRoot, 'src');
|
|
879
|
+
const buildRoot = path.join(packageRoot, 'dist');
|
|
880
|
+
if (!fs.existsSync(sourceRoot) || !fs.existsSync(buildRoot))
|
|
881
|
+
return;
|
|
882
|
+
const report = compareTrees(listTree(sourceRoot), listTree(buildRoot));
|
|
883
|
+
if (!report.comparable)
|
|
884
|
+
return;
|
|
885
|
+
if (report.unbuilt.length > 0) {
|
|
886
|
+
findings.add({
|
|
887
|
+
id: 'build.unbuilt-sources',
|
|
888
|
+
severity: 'warning',
|
|
889
|
+
title: `${report.unbuilt.length} source file(s) have never been built`,
|
|
890
|
+
evidence: [
|
|
891
|
+
...report.unbuilt.slice(0, 5).map((f) => `src/${f} → no dist counterpart`),
|
|
892
|
+
...(report.unbuilt.length > 5 ? [`… and ${report.unbuilt.length - 5} more`] : []),
|
|
893
|
+
'the daemon loads dist/, so this code has not run',
|
|
894
|
+
],
|
|
895
|
+
remedy: 'Run `pnpm build` in apps/omnitron.',
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
if (report.stale.length > 0) {
|
|
899
|
+
findings.add({
|
|
900
|
+
id: 'build.stale-sources',
|
|
901
|
+
severity: 'warning',
|
|
902
|
+
title: `${report.stale.length} source file(s) are newer than their build output`,
|
|
903
|
+
evidence: [
|
|
904
|
+
...report.stale.slice(0, 5).map((f) => `src/${f}`),
|
|
905
|
+
...(report.stale.length > 5 ? [`… and ${report.stale.length - 5} more`] : []),
|
|
906
|
+
'modification time is the signal, so a file copied back over itself counts as changed',
|
|
907
|
+
],
|
|
908
|
+
remedy: 'Run `pnpm build` in apps/omnitron, then restart the daemon.',
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
const predates = processPredatesBuild(daemonStartedMs ?? 0, report.newestArtifactMs);
|
|
912
|
+
if (predates === true) {
|
|
913
|
+
findings.add({
|
|
914
|
+
id: 'build.daemon-predates-build',
|
|
915
|
+
severity: 'warning',
|
|
916
|
+
title: 'The daemon started before the current build was written',
|
|
917
|
+
evidence: [
|
|
918
|
+
`daemon started: ${new Date(daemonStartedMs).toISOString()}`,
|
|
919
|
+
`newest build artifact: ${new Date(report.newestArtifactMs).toISOString()}`,
|
|
920
|
+
'it is running the previous build, which is no longer on disk to inspect',
|
|
921
|
+
],
|
|
922
|
+
remedy: 'Restart the daemon: `omnitron down && omnitron up`.',
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Every registered project's config, loaded the way the daemon loads it.
|
|
928
|
+
*
|
|
929
|
+
* A config that does not load is not a config that is absent, and the daemon
|
|
930
|
+
* used to answer both with an empty app list: it started, reported healthy,
|
|
931
|
+
* and supervised nothing, while `omnitron list` showed what it shows for a
|
|
932
|
+
* project with no apps. That path now refuses to start — which is a better
|
|
933
|
+
* failure, and a later one. This finds it before the restart that would
|
|
934
|
+
* otherwise be the first news.
|
|
935
|
+
*
|
|
936
|
+
* Reads the files directly rather than asking the daemon, for the same reason
|
|
937
|
+
* `checkDatabase` does: a daemon holding a config it loaded successfully
|
|
938
|
+
* hours ago cannot tell you the file on disk has since been edited.
|
|
939
|
+
*/
|
|
940
|
+
export async function checkProjectConfigs(findings) {
|
|
941
|
+
let projects;
|
|
942
|
+
try {
|
|
943
|
+
const { ProjectRegistry } = await import('../project/registry.js');
|
|
944
|
+
projects = ProjectRegistry.open().list();
|
|
945
|
+
}
|
|
946
|
+
catch (err) {
|
|
947
|
+
findings.skip('project.config-unloadable', `the project registry could not be opened: ${describeError(err)}`);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (projects.length === 0) {
|
|
951
|
+
findings.skip('project.config-unloadable', 'no projects are registered');
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const { loadEcosystemConfigFile, CONFIG_FILE_NAMES } = await import('../config/loader.js');
|
|
955
|
+
for (const project of projects) {
|
|
956
|
+
// `getConfigPath` names `omnitron.config.ts`; a project may legitimately
|
|
957
|
+
// use another of the accepted names, so an absent file at that exact path
|
|
958
|
+
// is not a finding — the daemon falls back to its working directory.
|
|
959
|
+
const candidates = CONFIG_FILE_NAMES.map((name) => path.join(project.path, name));
|
|
960
|
+
const configPath = candidates.find((p) => fs.existsSync(p));
|
|
961
|
+
if (!configPath) {
|
|
962
|
+
findings.skip('project.config-unloadable', `${project.name}: no config file at ${project.path}`);
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
try {
|
|
966
|
+
await loadEcosystemConfigFile(configPath);
|
|
967
|
+
}
|
|
968
|
+
catch (err) {
|
|
969
|
+
findings.add({
|
|
970
|
+
id: 'project.config-unloadable',
|
|
971
|
+
severity: 'error',
|
|
972
|
+
title: `Project "${project.name}" has a config the daemon cannot load`,
|
|
973
|
+
evidence: [
|
|
974
|
+
`file: ${path.relative(process.cwd(), configPath)}`,
|
|
975
|
+
describeError(err),
|
|
976
|
+
],
|
|
977
|
+
remedy: 'Fix the file. The daemon refuses to start against it rather than starting with no apps, ' +
|
|
978
|
+
'so this is what a failed `omnitron up` in that directory would report.',
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Free space on the filesystem holding omnitron's own state.
|
|
985
|
+
*
|
|
986
|
+
* A disk that fills does not report itself as a disk that filled. It reports
|
|
987
|
+
* itself as Postgres refusing writes, as the daemon dying without a message,
|
|
988
|
+
* as a login endpoint answering 500 — this project has diagnosed it as an
|
|
989
|
+
* auth bug once and as a Docker networking bug once, and both times the
|
|
990
|
+
* answer was `df`. Asking directly costs one syscall.
|
|
991
|
+
*
|
|
992
|
+
* The threshold is absolute rather than proportional on purpose: what fails
|
|
993
|
+
* is a WAL segment that cannot be written or an image layer that cannot be
|
|
994
|
+
* unpacked, and those need gigabytes, not percentages. A 2 TB disk at 3% free
|
|
995
|
+
* still has 60 GB and is fine; a 32 GB disk at 10% free has 3 GB and is not.
|
|
996
|
+
*
|
|
997
|
+
* What it does not catch: the container engine stops by itself near 99% used,
|
|
998
|
+
* whatever is left — at 21 GiB free of 1.8 TiB on 2026-09-29. So the alert
|
|
999
|
+
* rule seeded by migration 012 watches the same reading (`diskAtHome`, now in
|
|
1000
|
+
* monitoring/host-disk.ts) at 50 GiB, and it runs every tick, not when
|
|
1001
|
+
* somebody runs doctor.
|
|
1002
|
+
*/
|
|
1003
|
+
export async function checkDiskSpace(findings) {
|
|
1004
|
+
const ERROR_BYTES = 2 * 1024 ** 3;
|
|
1005
|
+
const WARN_BYTES = 10 * 1024 ** 3;
|
|
1006
|
+
const disk = await diskAtHome();
|
|
1007
|
+
if (!disk)
|
|
1008
|
+
return;
|
|
1009
|
+
const { free, total } = disk;
|
|
1010
|
+
if (free >= WARN_BYTES)
|
|
1011
|
+
return;
|
|
1012
|
+
const gib = (n) => `${(n / 1024 ** 3).toFixed(1)} GiB`;
|
|
1013
|
+
const evidence = [
|
|
1014
|
+
`${gib(free)} available of ${gib(total)} on the filesystem holding ${OMNITRON_HOME}`,
|
|
1015
|
+
];
|
|
1016
|
+
// Measured, not assumed. An earlier version of this check named the log
|
|
1017
|
+
// directory — the plausible culprit for a tool that writes logs. On this
|
|
1018
|
+
// host the logs are 163 MiB of a 7.4 GiB footprint, while `projects`
|
|
1019
|
+
// holds 4.4 GiB of build artefacts and `backups` 2.8 GiB, so the evidence
|
|
1020
|
+
// would have sent an operator to clean the one directory that was already
|
|
1021
|
+
// rotating properly.
|
|
1022
|
+
const parts = biggestSubdirectories(OMNITRON_HOME, 3);
|
|
1023
|
+
if (parts.length > 0) {
|
|
1024
|
+
evidence.push(`omnitron's own ${gib(parts.reduce((n, d) => n + d.bytes, 0))} sits mostly in ` +
|
|
1025
|
+
parts.map((d) => `${d.name} ${gib(d.bytes)}`).join(', '));
|
|
1026
|
+
}
|
|
1027
|
+
findings.add({
|
|
1028
|
+
id: free < ERROR_BYTES ? 'disk.exhausted' : 'disk.low',
|
|
1029
|
+
severity: free < ERROR_BYTES ? 'error' : 'warning',
|
|
1030
|
+
title: `${gib(free)} free on the disk omnitron writes to`,
|
|
1031
|
+
evidence,
|
|
1032
|
+
remedy: free < ERROR_BYTES
|
|
1033
|
+
? 'Free space before anything else. At this level Postgres refuses writes and containers fail to start, ' +
|
|
1034
|
+
'and both surface as application errors that say nothing about the disk. ' +
|
|
1035
|
+
'`omnitron logs --clean` truncates the app logs; unused Docker volumes are the other usual holder.'
|
|
1036
|
+
: 'Not yet a fault, but the margin is small enough that a single image pull or log burst crosses it.',
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* The largest immediate subdirectories of `dir`, biggest first.
|
|
1041
|
+
*
|
|
1042
|
+
* The point is to name what is actually holding the space rather than the
|
|
1043
|
+
* component the reader would have guessed.
|
|
1044
|
+
*/
|
|
1045
|
+
export function biggestSubdirectories(dir, limit) {
|
|
1046
|
+
let entries;
|
|
1047
|
+
try {
|
|
1048
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1049
|
+
}
|
|
1050
|
+
catch {
|
|
1051
|
+
return [];
|
|
1052
|
+
}
|
|
1053
|
+
return entries
|
|
1054
|
+
.filter((e) => e.isDirectory())
|
|
1055
|
+
.map((e) => ({ name: e.name, bytes: directorySize(path.join(dir, e.name)) }))
|
|
1056
|
+
.filter((d) => d.bytes > 0)
|
|
1057
|
+
.sort((a, b) => b.bytes - a.bytes)
|
|
1058
|
+
.slice(0, limit);
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Is this a failure to REACH the server, rather than one it answered with?
|
|
1062
|
+
*
|
|
1063
|
+
* The distinction decides which of two findings an operator is handed, and
|
|
1064
|
+
* they point in opposite directions: one says start the database, the other
|
|
1065
|
+
* says read its logs. Node reports a refused connection as an
|
|
1066
|
+
* `AggregateError` over the resolved addresses — one entry for IPv6, one for
|
|
1067
|
+
* IPv4 — with its own `message` empty, so the codes have to be read out of
|
|
1068
|
+
* `errors[]` as well as off the top.
|
|
1069
|
+
*/
|
|
1070
|
+
export function isConnectionFailure(err) {
|
|
1071
|
+
const CODES = [
|
|
1072
|
+
'ECONNREFUSED',
|
|
1073
|
+
'ETIMEDOUT',
|
|
1074
|
+
'ENOTFOUND',
|
|
1075
|
+
'EHOSTUNREACH',
|
|
1076
|
+
'ENETUNREACH',
|
|
1077
|
+
'ECONNRESET',
|
|
1078
|
+
'EPIPE',
|
|
1079
|
+
];
|
|
1080
|
+
const e = err;
|
|
1081
|
+
if (e?.code && CODES.includes(e.code))
|
|
1082
|
+
return true;
|
|
1083
|
+
if (Array.isArray(e?.errors) && e.errors.some((x) => x?.code && CODES.includes(x.code)))
|
|
1084
|
+
return true;
|
|
1085
|
+
// Some drivers surface the code only in the text.
|
|
1086
|
+
return typeof e?.message === 'string' && CODES.some((c) => e.message.includes(c));
|
|
1087
|
+
}
|
|
1088
|
+
/** Recursive byte total, bounded so a pathological tree cannot stall a check. */
|
|
1089
|
+
export function directorySize(dir, budget = { entries: 20_000 }) {
|
|
1090
|
+
let total = 0;
|
|
1091
|
+
// A path that is a file is worth its own size. Returning 0 instead would
|
|
1092
|
+
// make the `isDirectory()` filter in `biggestSubdirectories` unobservable —
|
|
1093
|
+
// a guard nothing could ever catch failing, which is the kind that quietly
|
|
1094
|
+
// stops being needed.
|
|
1095
|
+
try {
|
|
1096
|
+
const st = fs.statSync(dir);
|
|
1097
|
+
if (!st.isDirectory())
|
|
1098
|
+
return st.size;
|
|
1099
|
+
}
|
|
1100
|
+
catch {
|
|
1101
|
+
return 0;
|
|
1102
|
+
}
|
|
1103
|
+
const stack = [dir];
|
|
1104
|
+
while (stack.length > 0 && budget.entries > 0) {
|
|
1105
|
+
const current = stack.pop();
|
|
1106
|
+
let entries;
|
|
1107
|
+
try {
|
|
1108
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
1109
|
+
}
|
|
1110
|
+
catch {
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
for (const entry of entries) {
|
|
1114
|
+
if (budget.entries-- <= 0)
|
|
1115
|
+
break;
|
|
1116
|
+
const full = path.join(current, entry.name);
|
|
1117
|
+
if (entry.isDirectory()) {
|
|
1118
|
+
stack.push(full);
|
|
1119
|
+
}
|
|
1120
|
+
else if (entry.isFile()) {
|
|
1121
|
+
try {
|
|
1122
|
+
total += fs.statSync(full).size;
|
|
1123
|
+
}
|
|
1124
|
+
catch {
|
|
1125
|
+
// Removed between listing and stat; not worth a finding.
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return total;
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Whether the console is actually serving.
|
|
1134
|
+
*
|
|
1135
|
+
* Asked by fetching its root, not by reading the container's state. "A
|
|
1136
|
+
* container is running and its health check passes" is not the same claim as
|
|
1137
|
+
* "an operator can open the console", and the two came apart here today.
|
|
1138
|
+
*
|
|
1139
|
+
* The console is nginx serving a bind mount of `webapp/dist`. Deleting that
|
|
1140
|
+
* directory and rebuilding it — which any `rm -rf dist && pnpm build` does —
|
|
1141
|
+
* gives it a new inode, and the mount keeps pointing at the old one. Inside
|
|
1142
|
+
* the container the directory then does not exist at all and nginx answers
|
|
1143
|
+
* 404 to everything, while the container stays up. That ran for 1709
|
|
1144
|
+
* consecutive health-check failures before anything said so.
|
|
1145
|
+
*
|
|
1146
|
+
* A restart does not fix it: bind mounts are resolved when a container is
|
|
1147
|
+
* created, so it has to be recreated.
|
|
1148
|
+
*/
|
|
1149
|
+
export async function checkConsoleServing(findings, dc) {
|
|
1150
|
+
const port = dc.httpPort ?? 9800;
|
|
1151
|
+
let status;
|
|
1152
|
+
try {
|
|
1153
|
+
const res = await fetch(`http://127.0.0.1:${port}/`, {
|
|
1154
|
+
method: 'GET',
|
|
1155
|
+
signal: AbortSignal.timeout(3000),
|
|
1156
|
+
});
|
|
1157
|
+
status = res.status;
|
|
1158
|
+
}
|
|
1159
|
+
catch {
|
|
1160
|
+
// Nothing listening. `omnitron webapp` is opt-in and a daemon without a
|
|
1161
|
+
// console is a normal configuration, so this is not a finding — but it is
|
|
1162
|
+
// not a clean bill of health for the console either, and the two used to
|
|
1163
|
+
// print identically.
|
|
1164
|
+
findings.skip('webapp.not-serving', `nothing is listening on port ${port} — \`omnitron webapp\` is opt-in, so this may be intended`);
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
if (status >= 200 && status < 400) {
|
|
1168
|
+
await checkConsoleProxy(findings, port);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
findings.add({
|
|
1172
|
+
id: 'webapp.not-serving',
|
|
1173
|
+
severity: 'error',
|
|
1174
|
+
title: `The console answers ${status} on port ${port}`,
|
|
1175
|
+
evidence: [
|
|
1176
|
+
`GET http://127.0.0.1:${port}/ → ${status}`,
|
|
1177
|
+
'something is listening, so this is not "the console is switched off"',
|
|
1178
|
+
status === 404
|
|
1179
|
+
? 'a 404 on the root usually means nginx cannot see the built files: a bind mount left pointing at a directory that was deleted and rebuilt'
|
|
1180
|
+
: 'nginx is up but not serving the application',
|
|
1181
|
+
],
|
|
1182
|
+
remedy: 'Rebuild and recreate: `pnpm --filter @omnitron/console build`, then ' +
|
|
1183
|
+
'`omnitron webapp stop && omnitron webapp start`. A restart is not enough — ' +
|
|
1184
|
+
'a bind mount is resolved when the container is created.',
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Whether the console can reach the daemon THROUGH its own proxy.
|
|
1189
|
+
*
|
|
1190
|
+
* Serving the application and being able to use it are different claims, and
|
|
1191
|
+
* this platform has had them come apart: nginx handed out the bundle while
|
|
1192
|
+
* `/ws` was misconfigured, so the console loaded, could not open its event
|
|
1193
|
+
* stream, and reported the daemon offline. The operator saw a working page
|
|
1194
|
+
* telling them their platform was down.
|
|
1195
|
+
*
|
|
1196
|
+
* The proxy config is GENERATED when the container is created, so it drifts
|
|
1197
|
+
* exactly when the daemon's ports change and nobody recreated the container —
|
|
1198
|
+
* which is also when a restart will not help.
|
|
1199
|
+
*
|
|
1200
|
+
* `/api/health` is chosen because it is the one proxied path that answers
|
|
1201
|
+
* without credentials; a 502 or a timeout there means nginx cannot reach the
|
|
1202
|
+
* daemon it was pointed at, and the console's every RPC takes the same route.
|
|
1203
|
+
*/
|
|
1204
|
+
async function checkConsoleProxy(findings, port) {
|
|
1205
|
+
let status = null;
|
|
1206
|
+
let failure = '';
|
|
1207
|
+
try {
|
|
1208
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/health`, {
|
|
1209
|
+
method: 'GET',
|
|
1210
|
+
signal: AbortSignal.timeout(5000),
|
|
1211
|
+
});
|
|
1212
|
+
status = res.status;
|
|
1213
|
+
}
|
|
1214
|
+
catch (err) {
|
|
1215
|
+
failure = describeError(err);
|
|
1216
|
+
}
|
|
1217
|
+
if (status !== null && status >= 200 && status < 400)
|
|
1218
|
+
return;
|
|
1219
|
+
findings.add({
|
|
1220
|
+
id: 'webapp.proxy-unreachable',
|
|
1221
|
+
severity: 'error',
|
|
1222
|
+
title: 'The console is served, but it cannot reach the daemon through it',
|
|
1223
|
+
evidence: [
|
|
1224
|
+
`GET http://127.0.0.1:${port}/ → served`,
|
|
1225
|
+
status === null
|
|
1226
|
+
? `GET http://127.0.0.1:${port}/api/health → ${failure}`
|
|
1227
|
+
: `GET http://127.0.0.1:${port}/api/health → ${status}`,
|
|
1228
|
+
'the console reaches the daemon over this same proxy, so every RPC it makes takes this route',
|
|
1229
|
+
],
|
|
1230
|
+
remedy: 'nginx is up and serving the bundle, so this is its upstream, not the container: the proxy config ' +
|
|
1231
|
+
'names the daemon ports and is written when the container is CREATED. Recreate it — ' +
|
|
1232
|
+
'`omnitron webapp stop && omnitron webapp start` — rather than restarting. If the daemon itself is ' +
|
|
1233
|
+
'the problem, `omnitron doctor` reports it separately as `daemon.down`; a healthy daemon plus this ' +
|
|
1234
|
+
'finding means the two disagree about which port to use.',
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* What the daemon answers to a caller holding no credentials, and who can
|
|
1239
|
+
* reach it.
|
|
1240
|
+
*
|
|
1241
|
+
* Measured rather than read off the config: the question is not "which
|
|
1242
|
+
* decorators say allowAnonymous" but "what does this daemon, as configured
|
|
1243
|
+
* and running right now, hand a stranger". Two services turned out to be
|
|
1244
|
+
* fully anonymous by way of the packages that declare them —
|
|
1245
|
+
* `Health@1.0.0` from titan-health, `OmnitronMetrics` from titan-metrics —
|
|
1246
|
+
* and neither appears in apps/omnitron at all, so no amount of reading this
|
|
1247
|
+
* repository would have found them.
|
|
1248
|
+
*
|
|
1249
|
+
* `live` / `ready` / `getPrometheusText` are anonymous on purpose: a kubelet
|
|
1250
|
+
* probe and a Prometheus scrape carry no token. `getSnapshot` is a different
|
|
1251
|
+
* matter — it returns every managed app by name with its CPU, memory and
|
|
1252
|
+
* status, which is the platform's inventory.
|
|
1253
|
+
*
|
|
1254
|
+
* Severity follows reachability, because that is what decides the cost. On
|
|
1255
|
+
* loopback this is a note; bound to an interface, the inventory of
|
|
1256
|
+
* everything running on the host is readable by anyone who can route to it.
|
|
1257
|
+
*/
|
|
1258
|
+
export async function checkAnonymousSurface(findings, dc) {
|
|
1259
|
+
const httpPort = (dc.httpPort ?? 9800) + 1;
|
|
1260
|
+
const host = dc.host ?? '127.0.0.1';
|
|
1261
|
+
const reachableFromNetwork = host !== '127.0.0.1' && host !== 'localhost' && host !== '::1';
|
|
1262
|
+
/** Call a method with no Authorization header; true when it answers. */
|
|
1263
|
+
const answersAnonymously = async (service, method) => {
|
|
1264
|
+
try {
|
|
1265
|
+
const res = await fetch(`http://127.0.0.1:${httpPort}/netron/invoke`, {
|
|
1266
|
+
method: 'POST',
|
|
1267
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1268
|
+
body: JSON.stringify({ service, method, input: [], id: 'doctor-anon' }),
|
|
1269
|
+
signal: AbortSignal.timeout(3000),
|
|
1270
|
+
});
|
|
1271
|
+
const body = (await res.json());
|
|
1272
|
+
return body?.success === true;
|
|
1273
|
+
}
|
|
1274
|
+
catch {
|
|
1275
|
+
// Unreachable is not "open" — say nothing rather than guess.
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
const probes = [
|
|
1280
|
+
{ service: 'OmnitronMetrics', method: 'getSnapshot', what: 'every managed app by name, with CPU, memory and status' },
|
|
1281
|
+
{ service: 'Health@1.0.0', method: 'listIndicators', what: 'the health indicators this daemon runs' },
|
|
1282
|
+
];
|
|
1283
|
+
const open = [];
|
|
1284
|
+
for (const probe of probes) {
|
|
1285
|
+
if (await answersAnonymously(probe.service, probe.method)) {
|
|
1286
|
+
open.push(`${probe.service}.${probe.method}() → ${probe.what}`);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
if (open.length === 0)
|
|
1290
|
+
return;
|
|
1291
|
+
findings.add({
|
|
1292
|
+
id: 'auth.anonymous-surface',
|
|
1293
|
+
severity: reachableFromNetwork ? 'warning' : 'info',
|
|
1294
|
+
title: reachableFromNetwork
|
|
1295
|
+
? `${open.length} RPC method(s) answer without credentials, on a daemon bound to ${host}`
|
|
1296
|
+
: `${open.length} RPC method(s) answer without credentials (loopback only)`,
|
|
1297
|
+
evidence: [
|
|
1298
|
+
...open,
|
|
1299
|
+
`daemon.host: ${host}`,
|
|
1300
|
+
reachableFromNetwork
|
|
1301
|
+
? 'anything that can route to this host can read the above'
|
|
1302
|
+
: 'only processes on this machine can reach it',
|
|
1303
|
+
],
|
|
1304
|
+
...(reachableFromNetwork
|
|
1305
|
+
? {
|
|
1306
|
+
remedy: 'Set `daemon.host` back to 127.0.0.1 and reach the daemon through a proxy that authenticates, ' +
|
|
1307
|
+
'or accept the exposure deliberately. These methods are declared allowAnonymous in titan-metrics ' +
|
|
1308
|
+
'and titan-health, so the daemon cannot gate them on its own.',
|
|
1309
|
+
}
|
|
1310
|
+
: {}),
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* A container whose published ports the host cannot actually reach.
|
|
1315
|
+
*
|
|
1316
|
+
* Docker's `running` is a statement about the container, and its healthcheck
|
|
1317
|
+
* runs INSIDE it — both can pass while the host-side publishing is gone. That
|
|
1318
|
+
* combination is not hypothetical here: a forced network disconnect leaves
|
|
1319
|
+
* the daemon's port forwarding broken in a way that reconnecting the network
|
|
1320
|
+
* does not repair, so the container reports running, attached and healthy
|
|
1321
|
+
* while every connection from the host is refused. It cost this project a
|
|
1322
|
+
* day, diagnosed at the time as an application fault.
|
|
1323
|
+
*
|
|
1324
|
+
* The reading depends on the healthcheck, which is why it is used here rather
|
|
1325
|
+
* than only for `infra.unhealthy`: a HEALTHY container the host cannot reach
|
|
1326
|
+
* says the break is outside the container, and recreating is the fix. Without
|
|
1327
|
+
* a healthcheck the same evidence is ambiguous — the process inside may
|
|
1328
|
+
* simply not be listening — so the finding says so instead of guessing.
|
|
1329
|
+
*/
|
|
1330
|
+
export async function checkPublishedPorts(findings, c) {
|
|
1331
|
+
const ports = Object.entries(c.ports ?? {}).filter(([spec]) => spec.endsWith('/tcp'));
|
|
1332
|
+
if (ports.length === 0)
|
|
1333
|
+
return;
|
|
1334
|
+
// A container still coming up has not bound anything yet, and saying so
|
|
1335
|
+
// would be reporting on the clock rather than on the container.
|
|
1336
|
+
if (c.health === 'starting')
|
|
1337
|
+
return;
|
|
1338
|
+
const startedMs = c.startedAt ? Date.parse(c.startedAt) : NaN;
|
|
1339
|
+
if (Number.isFinite(startedMs) && Date.now() - startedMs < CONTAINER_SETTLE_MS)
|
|
1340
|
+
return;
|
|
1341
|
+
const unreachable = [];
|
|
1342
|
+
const silent = [];
|
|
1343
|
+
for (const [, hostPort] of ports) {
|
|
1344
|
+
const answer = await probePort(hostPort);
|
|
1345
|
+
if (answer === 'refused')
|
|
1346
|
+
unreachable.push(hostPort);
|
|
1347
|
+
else if (answer === 'no-answer')
|
|
1348
|
+
silent.push(hostPort);
|
|
1349
|
+
}
|
|
1350
|
+
// A port that did not answer is a measurement this machine could not
|
|
1351
|
+
// take, and saying so is the whole of what is known. Reporting it as
|
|
1352
|
+
// unreachable would be the load speaking in the container's name.
|
|
1353
|
+
if (silent.length > 0) {
|
|
1354
|
+
findings.add({
|
|
1355
|
+
id: 'infra.port-unanswered',
|
|
1356
|
+
severity: 'info',
|
|
1357
|
+
title: `Container "${c.name}" published ${silent.join(', ')} and the probe got no answer`,
|
|
1358
|
+
evidence: [
|
|
1359
|
+
`TCP connect to 127.0.0.1:${silent.join(', 127.0.0.1:')} neither connected nor was refused within 3s`,
|
|
1360
|
+
'On loopback that is this machine, not the container — run `omnitron doctor` again when it is quieter',
|
|
1361
|
+
],
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
if (unreachable.length === 0)
|
|
1365
|
+
return;
|
|
1366
|
+
const healthy = c.health === 'healthy';
|
|
1367
|
+
findings.add({
|
|
1368
|
+
id: 'infra.port-unreachable',
|
|
1369
|
+
severity: healthy ? 'error' : 'warning',
|
|
1370
|
+
title: `Container "${c.name}" publishes ${unreachable.join(', ')} but the host cannot connect`,
|
|
1371
|
+
evidence: [
|
|
1372
|
+
`status: running${c.health && c.health !== 'none' ? `, health: ${c.health}` : ''}`,
|
|
1373
|
+
`published: ${ports.map(([spec, hp]) => `${spec} -> ${hp}`).join(', ')}`,
|
|
1374
|
+
`TCP connect to 127.0.0.1:${unreachable.join(', 127.0.0.1:')} refused or timed out`,
|
|
1375
|
+
],
|
|
1376
|
+
remedy: healthy
|
|
1377
|
+
? `Its own healthcheck passes inside the container, so the service is running and the publishing is what broke. ` +
|
|
1378
|
+
`Recreate it — \`docker rm -f ${c.name}\` then \`omnitron up\`. Restarting is not enough: the forwarding is ` +
|
|
1379
|
+
`established when the container is created.`
|
|
1380
|
+
: `Either the process inside never bound the port, or the host-side publishing is broken. ` +
|
|
1381
|
+
`\`omnitron infra logs ${c.name}\` distinguishes them: a listening service with an unreachable port is the second case, ` +
|
|
1382
|
+
`and is fixed by recreating the container rather than restarting it.`,
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
/** Infrastructure containers the daemon manages. */
|
|
1386
|
+
async function checkInfrastructure(findings, client) {
|
|
1387
|
+
try {
|
|
1388
|
+
const infra = await client.service('OmnitronInfra');
|
|
1389
|
+
const containers = await infra.listContainers();
|
|
1390
|
+
if (!Array.isArray(containers))
|
|
1391
|
+
return;
|
|
1392
|
+
for (const c of containers) {
|
|
1393
|
+
if (c.status === 'running') {
|
|
1394
|
+
if (c.health === 'unhealthy') {
|
|
1395
|
+
findings.add({
|
|
1396
|
+
id: 'infra.unhealthy',
|
|
1397
|
+
severity: 'warning',
|
|
1398
|
+
title: `Container "${c.name}" is running but unhealthy`,
|
|
1399
|
+
evidence: [`image: ${c.image}`, `health: ${c.health}`],
|
|
1400
|
+
remedy: `Check its logs — the daemon will recreate it after repeated failures.`,
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
// A running container detached from every network still reports
|
|
1404
|
+
// "running" while publishing no ports — the failure mode the
|
|
1405
|
+
// phantom-endpoint janitor used to CAUSE.
|
|
1406
|
+
if (c.networkAttached === false) {
|
|
1407
|
+
findings.add({
|
|
1408
|
+
id: 'infra.detached',
|
|
1409
|
+
severity: 'error',
|
|
1410
|
+
title: `Container "${c.name}" is running but attached to no network`,
|
|
1411
|
+
evidence: ['its published ports are unreachable while detached'],
|
|
1412
|
+
remedy: 'Recreate it — a detached container cannot be reattached in place.',
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
else {
|
|
1416
|
+
// Only when the container is attached: a detached one is already
|
|
1417
|
+
// reported above, and probing its ports would name the same cause
|
|
1418
|
+
// twice.
|
|
1419
|
+
await checkPublishedPorts(findings, c);
|
|
1420
|
+
}
|
|
1421
|
+
continue;
|
|
1422
|
+
}
|
|
1423
|
+
// A container that was created and never started is a different fault
|
|
1424
|
+
// from one that ran and exited, and Docker knows which: `created` with
|
|
1425
|
+
// a non-zero exit code means the start itself was refused, and the
|
|
1426
|
+
// reason — a port already bound, an image that will not run — is in
|
|
1427
|
+
// `error`. Recreating is what fixes that; `omnitron up` will not,
|
|
1428
|
+
// because the husk still holds the name.
|
|
1429
|
+
const neverStarted = c.status === 'created';
|
|
1430
|
+
findings.add({
|
|
1431
|
+
id: neverStarted ? 'infra.start-refused' : 'infra.not-running',
|
|
1432
|
+
severity: 'warning',
|
|
1433
|
+
title: neverStarted
|
|
1434
|
+
? `Container "${c.name}" was created but never started`
|
|
1435
|
+
: `Container "${c.name}" is ${c.status}`,
|
|
1436
|
+
evidence: [`image: ${c.image}`, ...(c.error ? [`reason: ${c.error}`] : [])],
|
|
1437
|
+
remedy: containerRemedy(c.name, c.error, neverStarted),
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
catch {
|
|
1442
|
+
// Infrastructure is optional; its absence is not a fault.
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* What to tell an operator about a container that is not running.
|
|
1447
|
+
*
|
|
1448
|
+
* `omnitron up` is the right answer for a container that simply stopped. It is
|
|
1449
|
+
* the wrong one when Docker already said why it could not start, and the
|
|
1450
|
+
* reason is a condition `up` runs straight back into.
|
|
1451
|
+
*
|
|
1452
|
+
* Seen on this machine: `omnitron-pg` exited with `mkdir
|
|
1453
|
+
* /var/lib/docker/overlay2/...: no space left on device`, and the finding
|
|
1454
|
+
* printed the reason in its evidence and then advised `omnitron up` — which
|
|
1455
|
+
* fails identically, immediately, with the same message. Advice that cannot
|
|
1456
|
+
* work is worse than none: it costs a run to disprove, and it reads as though
|
|
1457
|
+
* the problem has been diagnosed.
|
|
1458
|
+
*/
|
|
1459
|
+
export function containerRemedy(name, error, neverStarted) {
|
|
1460
|
+
if (neverStarted) {
|
|
1461
|
+
return (`Remove it and let the daemon recreate it: \`docker rm -f ${name}\`, then \`omnitron up\`. ` +
|
|
1462
|
+
`A container in this state keeps its name, so the next attempt fails the same way until it is gone.`);
|
|
1463
|
+
}
|
|
1464
|
+
const reason = error ?? '';
|
|
1465
|
+
if (/no space left on device|disk quota exceeded/i.test(reason)) {
|
|
1466
|
+
return ('The container runtime is out of disk — `omnitron up` will fail with this same message. ' +
|
|
1467
|
+
'Free space inside the runtime first: `docker system df` shows what is reclaimable, and note ' +
|
|
1468
|
+
'that a large total is not the same as a large reclaimable. Check the host too (`df -h`), ' +
|
|
1469
|
+
'since the runtime image lives on it.');
|
|
1470
|
+
}
|
|
1471
|
+
if (/port is already allocated|address already in use/i.test(reason)) {
|
|
1472
|
+
return (`Something else holds the port. Find it (\`lsof -nP -iTCP -sTCP:LISTEN\`), free it, ` +
|
|
1473
|
+
`then \`omnitron up\`. Restarting the container will not move the conflict.`);
|
|
1474
|
+
}
|
|
1475
|
+
if (/endpoint with name .* already exists|network .* not found/i.test(reason)) {
|
|
1476
|
+
return (`A stale network endpoint holds the name. Recreate rather than restart: ` +
|
|
1477
|
+
`\`docker rm -f ${name}\`, then \`omnitron up\`.`);
|
|
1478
|
+
}
|
|
1479
|
+
return 'Run `omnitron up` to reconcile infrastructure.';
|
|
1480
|
+
}
|
|
1481
|
+
// ---------------------------------------------------------------------------
|
|
1482
|
+
// Rendering
|
|
1483
|
+
// ---------------------------------------------------------------------------
|
|
1484
|
+
function severityMark(severity) {
|
|
1485
|
+
if (severity === 'error')
|
|
1486
|
+
return prism.red('[x]');
|
|
1487
|
+
if (severity === 'warning')
|
|
1488
|
+
return prism.yellow('[!]');
|
|
1489
|
+
return prism.cyan('[i]');
|
|
1490
|
+
}
|
|
1491
|
+
function render(findings) {
|
|
1492
|
+
const items = findings.all();
|
|
1493
|
+
const skips = findings.skipped();
|
|
1494
|
+
const lines = [];
|
|
1495
|
+
if (items.length === 0) {
|
|
1496
|
+
// Never a bare "No problems found" while something went unread: the two
|
|
1497
|
+
// sentences below are the difference between "look elsewhere" and
|
|
1498
|
+
// "look again".
|
|
1499
|
+
lines.push(skips.length === 0
|
|
1500
|
+
? prism.green('[+] No problems found.')
|
|
1501
|
+
: prism.yellow(`[+] No problems found in what could be checked.`));
|
|
1502
|
+
}
|
|
1503
|
+
else {
|
|
1504
|
+
for (const [index, finding] of items.entries()) {
|
|
1505
|
+
if (index > 0)
|
|
1506
|
+
lines.push('');
|
|
1507
|
+
lines.push(`${severityMark(finding.severity)} ${prism.bold(finding.title)}`);
|
|
1508
|
+
for (const line of finding.evidence)
|
|
1509
|
+
lines.push(prism.dim(` ${line}`));
|
|
1510
|
+
if (finding.remedy)
|
|
1511
|
+
lines.push(` ${prism.cyan('→')} ${finding.remedy}`);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
if (skips.length > 0) {
|
|
1515
|
+
if (items.length > 0)
|
|
1516
|
+
lines.push('');
|
|
1517
|
+
lines.push(prism.yellow(`[?] ${skips.length} area(s) not examined:`));
|
|
1518
|
+
for (const skip of skips) {
|
|
1519
|
+
lines.push(prism.dim(` ${skip.id} — ${skip.reason}`));
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
box(lines.join('\n'), 'Diagnostics');
|
|
1523
|
+
}
|
|
1524
|
+
// ---------------------------------------------------------------------------
|
|
1525
|
+
// Command
|
|
1526
|
+
// ---------------------------------------------------------------------------
|
|
1527
|
+
export async function doctorCommand() {
|
|
1528
|
+
const findings = new Findings();
|
|
1529
|
+
const client = createDaemonClient();
|
|
1530
|
+
const daemonUp = await client.isReachable();
|
|
1531
|
+
/**
|
|
1532
|
+
* When the daemon started, in wall-clock ms — needed to tell whether it
|
|
1533
|
+
* predates the build on disk. Null when the daemon is not answering, which
|
|
1534
|
+
* the freshness check treats as "cannot tell" rather than "no".
|
|
1535
|
+
*/
|
|
1536
|
+
let daemonStartedMs = null;
|
|
1537
|
+
if (!daemonUp) {
|
|
1538
|
+
findings.add({
|
|
1539
|
+
id: 'daemon.down',
|
|
1540
|
+
severity: 'error',
|
|
1541
|
+
title: 'The daemon is not running',
|
|
1542
|
+
evidence: ['no response on the control socket'],
|
|
1543
|
+
remedy: 'Start it with `omnitron up`.',
|
|
1544
|
+
});
|
|
1545
|
+
// Seven checks ask the daemon and only the daemon. Without this the
|
|
1546
|
+
// report for a stopped daemon was a single error, and nothing said that
|
|
1547
|
+
// the app, port, infrastructure and console findings below it were
|
|
1548
|
+
// absent rather than clean.
|
|
1549
|
+
for (const [id, what] of [
|
|
1550
|
+
['daemon.*', 'the daemon\'s own restart history and metric sampling'],
|
|
1551
|
+
['app.*', 'every app\'s status, restarts and memory'],
|
|
1552
|
+
['app.subprocess-crashed', 'each app\'s child processes'],
|
|
1553
|
+
['app.port-unreachable', 'the ports apps claim against the ports they answer on'],
|
|
1554
|
+
['infra.*', 'infrastructure containers and their published ports'],
|
|
1555
|
+
['auth.anonymous-surface', 'which RPC methods are reachable without a token'],
|
|
1556
|
+
['webapp.not-serving', 'whether the console is being served'],
|
|
1557
|
+
]) {
|
|
1558
|
+
findings.skip(id, `not checked: ${what} — the daemon is not answering`);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
else {
|
|
1562
|
+
// One `list` serves several checks, and how long it takes is itself a
|
|
1563
|
+
// reading: the call walks every supervisor, so a supervisor that has
|
|
1564
|
+
// stopped answering shows up here as latency long before it shows up as
|
|
1565
|
+
// a status.
|
|
1566
|
+
let apps = [];
|
|
1567
|
+
const listStarted = Date.now();
|
|
1568
|
+
try {
|
|
1569
|
+
apps = await client.list();
|
|
1570
|
+
}
|
|
1571
|
+
catch {
|
|
1572
|
+
// `checkApps` will report the failure with its own evidence.
|
|
1573
|
+
}
|
|
1574
|
+
const listMs = Date.now() - listStarted;
|
|
1575
|
+
if (listMs > SLOW_DAEMON_MS) {
|
|
1576
|
+
findings.add({
|
|
1577
|
+
id: 'daemon.slow',
|
|
1578
|
+
severity: 'warning',
|
|
1579
|
+
title: `The daemon took ${(listMs / 1000).toFixed(1)}s to list ${apps.length} app(s)`,
|
|
1580
|
+
evidence: [
|
|
1581
|
+
`expected well under ${SLOW_DAEMON_MS / 1000}s`,
|
|
1582
|
+
'the call polls every supervisor, so one that has stopped answering shows up as latency here',
|
|
1583
|
+
],
|
|
1584
|
+
remedy: 'Find the app whose supervisor is blocked: `omnitron inspect <app>` on each in turn will hang on the one at fault.',
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
try {
|
|
1588
|
+
const status = await client.status();
|
|
1589
|
+
if (typeof status.uptime === 'number' && status.uptime > 0) {
|
|
1590
|
+
daemonStartedMs = Date.now() - status.uptime;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
catch {
|
|
1594
|
+
// Left null; the freshness check reports "cannot tell", not "fine".
|
|
1595
|
+
}
|
|
1596
|
+
// Each check is independent: one failing must not hide the others, which
|
|
1597
|
+
// is the whole point of running them together.
|
|
1598
|
+
for (const check of [
|
|
1599
|
+
() => checkDaemon(findings, client),
|
|
1600
|
+
() => checkApps(findings, client, apps),
|
|
1601
|
+
() => checkAppInternals(findings, apps),
|
|
1602
|
+
() => checkPorts(findings, apps),
|
|
1603
|
+
() => checkInfrastructure(findings, client),
|
|
1604
|
+
() => checkAnonymousSurface(findings, DEFAULT_DAEMON_CONFIG),
|
|
1605
|
+
() => checkConsoleServing(findings, DEFAULT_DAEMON_CONFIG),
|
|
1606
|
+
]) {
|
|
1607
|
+
try {
|
|
1608
|
+
await check();
|
|
1609
|
+
}
|
|
1610
|
+
catch (err) {
|
|
1611
|
+
findings.add({
|
|
1612
|
+
id: 'doctor.check-failed',
|
|
1613
|
+
severity: 'warning',
|
|
1614
|
+
title: 'A diagnostic check could not complete',
|
|
1615
|
+
evidence: [describeError(err)],
|
|
1616
|
+
});
|
|
1617
|
+
findings.skip('doctor.check-failed', `a check threw: ${describeError(err)}`);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
// Runs whether or not the daemon answered: a config the daemon cannot load
|
|
1622
|
+
// is a plausible reason it is not running, and the daemon's own refusal to
|
|
1623
|
+
// start against one is only visible to whoever ran `omnitron up`.
|
|
1624
|
+
try {
|
|
1625
|
+
await checkProjectConfigs(findings);
|
|
1626
|
+
}
|
|
1627
|
+
catch (err) {
|
|
1628
|
+
findings.add({
|
|
1629
|
+
id: 'doctor.check-failed',
|
|
1630
|
+
severity: 'warning',
|
|
1631
|
+
title: 'The project-config check could not complete',
|
|
1632
|
+
evidence: [describeError(err)],
|
|
1633
|
+
});
|
|
1634
|
+
findings.skip('project.config-unloadable', `the check threw: ${describeError(err)}`);
|
|
1635
|
+
}
|
|
1636
|
+
// Runs whether or not the daemon answered: a full disk is a plausible
|
|
1637
|
+
// reason the daemon is down, and it is the reason operators reach for last.
|
|
1638
|
+
try {
|
|
1639
|
+
await checkDiskSpace(findings);
|
|
1640
|
+
}
|
|
1641
|
+
catch (err) {
|
|
1642
|
+
findings.add({
|
|
1643
|
+
id: 'doctor.check-failed',
|
|
1644
|
+
severity: 'warning',
|
|
1645
|
+
title: 'The disk-space check could not complete',
|
|
1646
|
+
evidence: [describeError(err)],
|
|
1647
|
+
});
|
|
1648
|
+
findings.skip('disk.*', `the check threw: ${describeError(err)}`);
|
|
1649
|
+
}
|
|
1650
|
+
// Runs whether or not the daemon answered: a stale build is worth knowing
|
|
1651
|
+
// about even when the daemon is down, and it is a plausible reason it is.
|
|
1652
|
+
try {
|
|
1653
|
+
await checkBuildFreshness(findings, daemonStartedMs);
|
|
1654
|
+
}
|
|
1655
|
+
catch (err) {
|
|
1656
|
+
findings.add({
|
|
1657
|
+
id: 'doctor.check-failed',
|
|
1658
|
+
severity: 'warning',
|
|
1659
|
+
title: 'The build-freshness check could not complete',
|
|
1660
|
+
evidence: [describeError(err)],
|
|
1661
|
+
});
|
|
1662
|
+
findings.skip('build.*', `the check threw: ${describeError(err)}`);
|
|
1663
|
+
}
|
|
1664
|
+
// The database is checked directly rather than through the daemon: when the
|
|
1665
|
+
// schema is missing the daemon still answers RPCs perfectly well, which is
|
|
1666
|
+
// exactly why the outage went unnoticed.
|
|
1667
|
+
try {
|
|
1668
|
+
await checkDatabase(findings);
|
|
1669
|
+
}
|
|
1670
|
+
catch (err) {
|
|
1671
|
+
findings.add({
|
|
1672
|
+
id: 'doctor.check-failed',
|
|
1673
|
+
severity: 'warning',
|
|
1674
|
+
title: 'The database check could not complete',
|
|
1675
|
+
evidence: [describeError(err)],
|
|
1676
|
+
});
|
|
1677
|
+
findings.skip('db.*', `the check threw: ${describeError(err)}`);
|
|
1678
|
+
}
|
|
1679
|
+
await client.disconnect().catch(() => undefined);
|
|
1680
|
+
const items = findings.all();
|
|
1681
|
+
const skipped = findings.skipped();
|
|
1682
|
+
if (isJsonMode()) {
|
|
1683
|
+
emitJson({
|
|
1684
|
+
ok: items.length === 0,
|
|
1685
|
+
// `ok` alone cannot carry this: a run where the database was unreachable
|
|
1686
|
+
// reports `ok: false` with one finding, and a CI gate reading only `ok`
|
|
1687
|
+
// learns nothing about the four checks that never ran. `complete` says
|
|
1688
|
+
// whether the verdict covers everything it claims to.
|
|
1689
|
+
complete: skipped.length === 0,
|
|
1690
|
+
worst: findings.worst,
|
|
1691
|
+
findings: items,
|
|
1692
|
+
skipped,
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
else {
|
|
1696
|
+
render(findings);
|
|
1697
|
+
const errors = items.filter((f) => f.severity === 'error').length;
|
|
1698
|
+
if (errors > 0)
|
|
1699
|
+
log.error(`${errors} problem(s) need attention.`);
|
|
1700
|
+
}
|
|
1701
|
+
// Non-zero exit on errors so `omnitron doctor` works as a CI gate.
|
|
1702
|
+
if (items.some((f) => f.severity === 'error')) {
|
|
1703
|
+
process.exitCode = 1;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
//# sourceMappingURL=doctor.js.map
|