@omnitron-dev/omnitron 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/BUILD.json +2 -2
- package/dist/cli/omnitron.js +26 -2
- package/dist/cli/omnitron.js.map +1 -1
- package/dist/cluster/mesh-link.js +1 -1
- package/dist/cluster/slave-connector.d.ts +2 -2
- package/dist/cluster/slave-connector.js +1 -1
- package/dist/commands/deploy.d.ts +4 -4
- package/dist/commands/deploy.js +4 -4
- package/dist/commands/discover.d.ts +2 -2
- package/dist/commands/discover.js +2 -2
- package/dist/commands/fleet-asking.d.ts +2 -2
- package/dist/commands/fleet-asking.js +2 -2
- package/dist/commands/fleet-recover.d.ts +2 -0
- package/dist/commands/fleet-recover.d.ts.map +1 -0
- package/dist/commands/fleet-recover.js +30 -0
- package/dist/commands/fleet-recover.js.map +1 -0
- package/dist/commands/host-ingress.d.ts +3 -0
- package/dist/commands/host-ingress.d.ts.map +1 -0
- package/dist/commands/host-ingress.js +31 -0
- package/dist/commands/host-ingress.js.map +1 -0
- package/dist/commands/infra.d.ts +2 -2
- package/dist/commands/infra.js +7 -7
- package/dist/commands/infra.js.map +1 -1
- package/dist/commands/logs.d.ts +1 -1
- package/dist/commands/logs.js +3 -3
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/node.d.ts +2 -14
- package/dist/commands/node.d.ts.map +1 -1
- package/dist/commands/node.js +33 -22
- package/dist/commands/node.js.map +1 -1
- package/dist/commands/pipeline.d.ts.map +1 -1
- package/dist/commands/pipeline.js +8 -1
- package/dist/commands/pipeline.js.map +1 -1
- package/dist/commands/project.d.ts +2 -2
- package/dist/commands/project.js +2 -2
- package/dist/commands/release.d.ts +1 -1
- package/dist/commands/release.js +4 -4
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/remote.d.ts +3 -3
- package/dist/commands/remote.js +3 -3
- package/dist/commands/tor.d.ts +1 -1
- package/dist/commands/tor.js +2 -2
- package/dist/config/types.d.ts +9 -6
- package/dist/config/types.d.ts.map +1 -1
- package/dist/daemon/daemon-entry.js +1 -1
- package/dist/daemon/daemon-entry.js.map +1 -1
- package/dist/daemon/daemon.d.ts +5 -19
- package/dist/daemon/daemon.d.ts.map +1 -1
- package/dist/daemon/daemon.js +54 -51
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/daemon.module.d.ts.map +1 -1
- package/dist/daemon/daemon.module.js +3 -5
- package/dist/daemon/daemon.module.js.map +1 -1
- package/dist/daemon/daemon.rpc-service.d.ts +2 -2
- package/dist/daemon/daemon.rpc-service.js +6 -6
- package/dist/daemon/daemon.rpc-service.js.map +1 -1
- package/dist/execution/data-channel.d.ts +1 -1
- package/dist/execution/data-channel.js +1 -1
- package/dist/execution/execution.service.d.ts +10 -3
- package/dist/execution/execution.service.d.ts.map +1 -1
- package/dist/execution/execution.service.js +101 -44
- package/dist/execution/execution.service.js.map +1 -1
- package/dist/infrastructure/bare-metal-plan.js +1 -1
- package/dist/infrastructure/bare-metal-plan.js.map +1 -1
- package/dist/infrastructure/config-payload.d.ts +4 -4
- package/dist/infrastructure/config-payload.js +4 -4
- package/dist/infrastructure/container-runtime.d.ts +2 -10
- package/dist/infrastructure/container-runtime.d.ts.map +1 -1
- package/dist/infrastructure/container-runtime.js +41 -20
- package/dist/infrastructure/container-runtime.js.map +1 -1
- package/dist/infrastructure/gateway-redis-db.d.ts +2 -2
- package/dist/infrastructure/gateway-redis-db.js +2 -2
- package/dist/infrastructure/host-ingress-config.d.ts +12 -0
- package/dist/infrastructure/host-ingress-config.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress-config.js +197 -0
- package/dist/infrastructure/host-ingress-config.js.map +1 -0
- package/dist/infrastructure/host-ingress-lock.d.ts +4 -0
- package/dist/infrastructure/host-ingress-lock.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress-lock.js +36 -0
- package/dist/infrastructure/host-ingress-lock.js.map +1 -0
- package/dist/infrastructure/host-ingress-runtime.d.ts +25 -0
- package/dist/infrastructure/host-ingress-runtime.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress-runtime.js +114 -0
- package/dist/infrastructure/host-ingress-runtime.js.map +1 -0
- package/dist/infrastructure/host-ingress-takeover.d.ts +18 -0
- package/dist/infrastructure/host-ingress-takeover.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress-takeover.js +142 -0
- package/dist/infrastructure/host-ingress-takeover.js.map +1 -0
- package/dist/infrastructure/host-ingress-types.d.ts +173 -0
- package/dist/infrastructure/host-ingress-types.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress-types.js +2 -0
- package/dist/infrastructure/host-ingress-types.js.map +1 -0
- package/dist/infrastructure/host-ingress.service.d.ts +47 -0
- package/dist/infrastructure/host-ingress.service.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress.service.js +514 -0
- package/dist/infrastructure/host-ingress.service.js.map +1 -0
- package/dist/infrastructure/infrastructure.service.d.ts +21 -5
- package/dist/infrastructure/infrastructure.service.d.ts.map +1 -1
- package/dist/infrastructure/infrastructure.service.js +91 -10
- package/dist/infrastructure/infrastructure.service.js.map +1 -1
- package/dist/infrastructure/orphan-containers.d.ts +7 -7
- package/dist/infrastructure/orphan-containers.js +6 -6
- package/dist/infrastructure/presets/minio.js +1 -1
- package/dist/infrastructure/presets/minio.js.map +1 -1
- package/dist/infrastructure/provisioning-outcome.d.ts +1 -1
- package/dist/infrastructure/provisioning-outcome.js +1 -1
- package/dist/infrastructure/service-binding.js +1 -1
- package/dist/infrastructure/service-resolver.d.ts +10 -4
- package/dist/infrastructure/service-resolver.d.ts.map +1 -1
- package/dist/infrastructure/service-resolver.js +88 -45
- package/dist/infrastructure/service-resolver.js.map +1 -1
- package/dist/infrastructure/shipped-config.js +4 -4
- package/dist/infrastructure/stack-infra-manager.d.ts +10 -1
- package/dist/infrastructure/stack-infra-manager.d.ts.map +1 -1
- package/dist/infrastructure/stack-infra-manager.js +53 -43
- package/dist/infrastructure/stack-infra-manager.js.map +1 -1
- package/dist/infrastructure/types.d.ts +8 -6
- package/dist/infrastructure/types.d.ts.map +1 -1
- package/dist/monitoring/log-line.d.ts +3 -3
- package/dist/monitoring/log-line.js +3 -3
- package/dist/monitoring/log-manager.d.ts +3 -3
- package/dist/monitoring/log-manager.js +3 -3
- package/dist/operations.d.ts +16 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +13 -0
- package/dist/operations.js.map +1 -0
- package/dist/orchestrator/app-handle.d.ts +3 -3
- package/dist/orchestrator/app-handle.js +3 -3
- package/dist/orchestrator/bootstrap-loader.d.ts +2 -2
- package/dist/orchestrator/bootstrap-loader.js +3 -3
- package/dist/orchestrator/bootstrap-loader.js.map +1 -1
- package/dist/orchestrator/bootstrap-process.d.ts +1 -1
- package/dist/orchestrator/bootstrap-process.d.ts.map +1 -1
- package/dist/orchestrator/bootstrap-process.js +3 -2
- package/dist/orchestrator/bootstrap-process.js.map +1 -1
- package/dist/orchestrator/build-service.d.ts +6 -12
- package/dist/orchestrator/build-service.d.ts.map +1 -1
- package/dist/orchestrator/build-service.js +120 -83
- package/dist/orchestrator/build-service.js.map +1 -1
- package/dist/orchestrator/call-exposed-service.js +1 -1
- package/dist/orchestrator/classic-launcher.d.ts.map +1 -1
- package/dist/orchestrator/classic-launcher.js +6 -1
- package/dist/orchestrator/classic-launcher.js.map +1 -1
- package/dist/orchestrator/file-watcher.d.ts +1 -0
- package/dist/orchestrator/file-watcher.d.ts.map +1 -1
- package/dist/orchestrator/file-watcher.js +14 -3
- package/dist/orchestrator/file-watcher.js.map +1 -1
- package/dist/orchestrator/http-routes.d.ts +4 -0
- package/dist/orchestrator/http-routes.d.ts.map +1 -0
- package/dist/orchestrator/http-routes.js +19 -0
- package/dist/orchestrator/http-routes.js.map +1 -0
- package/dist/orchestrator/orchestrator.service.d.ts +18 -4
- package/dist/orchestrator/orchestrator.service.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator.service.js +151 -110
- package/dist/orchestrator/orchestrator.service.js.map +1 -1
- package/dist/orchestrator/service-router.d.ts +1 -1
- package/dist/orchestrator/service-router.js +1 -1
- package/dist/orchestrator/topology-query.d.ts +1 -1
- package/dist/orchestrator/topology-query.js +1 -1
- package/dist/project/artifact-builder.js +2 -2
- package/dist/project/artifact-builder.js.map +1 -1
- package/dist/project/build-decision.d.ts +1 -1
- package/dist/project/build-decision.js +1 -1
- package/dist/project/config-resolver.js +1 -1
- package/dist/project/config-resolver.js.map +1 -1
- package/dist/project/declared-config.d.ts +2 -2
- package/dist/project/declared-config.js +2 -2
- package/dist/project/deploy-phases.d.ts +1 -1
- package/dist/project/deploy-phases.js +1 -1
- package/dist/project/exec-in-group.d.ts +1 -1
- package/dist/project/exec-in-group.js +1 -1
- package/dist/project/node-app-config.d.ts +2 -2
- package/dist/project/node-app-config.js +2 -2
- package/dist/project/node-app-config.js.map +1 -1
- package/dist/project/node-app-health.d.ts +1 -1
- package/dist/project/node-app-health.js +1 -1
- package/dist/project/operator-account.d.ts +9 -3
- package/dist/project/operator-account.d.ts.map +1 -1
- package/dist/project/operator-account.js +9 -9
- package/dist/project/operator-account.js.map +1 -1
- package/dist/project/registry.d.ts.map +1 -1
- package/dist/project/registry.js +4 -22
- package/dist/project/registry.js.map +1 -1
- package/dist/project/stack-env.d.ts +1 -1
- package/dist/project/stack-env.js +1 -1
- package/dist/project/stand-containers.d.ts +9 -2
- package/dist/project/stand-containers.d.ts.map +1 -1
- package/dist/project/stand-containers.js +17 -17
- package/dist/project/stand-containers.js.map +1 -1
- package/dist/release/artifact-store.d.ts +1 -1
- package/dist/release/artifact-store.d.ts.map +1 -1
- package/dist/release/artifact-store.js +1 -1
- package/dist/release/artifact-store.js.map +1 -1
- package/dist/release/attest-on-node.d.ts +3 -2
- package/dist/release/attest-on-node.d.ts.map +1 -1
- package/dist/release/attest-on-node.js +6 -6
- package/dist/release/attest-on-node.js.map +1 -1
- package/dist/release/attest.d.ts +3 -3
- package/dist/release/attest.js +1 -1
- package/dist/release/build-run.d.ts +2 -2
- package/dist/release/build-run.js +4 -4
- package/dist/release/build-run.js.map +1 -1
- package/dist/release/builder.d.ts +1 -1
- package/dist/release/builder.d.ts.map +1 -1
- package/dist/release/builder.js +1 -1
- package/dist/release/builder.js.map +1 -1
- package/dist/release/daemon-omni.d.ts +1 -1
- package/dist/release/daemon-omni.js +1 -1
- package/dist/release/definition-inputs.d.ts +1 -1
- package/dist/release/definition-inputs.js +1 -1
- package/dist/release/layout.d.ts +2 -2
- package/dist/release/layout.js +2 -2
- package/dist/release/manifest.d.ts +1 -1
- package/dist/release/manifest.js +1 -1
- package/dist/release/publish.d.ts +1 -1
- package/dist/release/publish.js +1 -1
- package/dist/release/store.d.ts +1 -1
- package/dist/release/store.d.ts.map +1 -1
- package/dist/release/store.js +3 -3
- package/dist/release/store.js.map +1 -1
- package/dist/services/alert.service.d.ts +2 -2
- package/dist/services/alert.service.js +2 -2
- package/dist/services/backup-pipeline.d.ts +1 -1
- package/dist/services/backup-pipeline.js +1 -1
- package/dist/services/backup.service.d.ts +3 -3
- package/dist/services/backup.service.js +3 -3
- package/dist/services/bundle-builder.d.ts +15 -12
- package/dist/services/bundle-builder.d.ts.map +1 -1
- package/dist/services/bundle-builder.js +124 -48
- package/dist/services/bundle-builder.js.map +1 -1
- package/dist/services/discovery.service.d.ts +1 -1
- package/dist/services/discovery.service.js +1 -1
- package/dist/services/host-operation-barrier.d.ts +8 -0
- package/dist/services/host-operation-barrier.d.ts.map +1 -0
- package/dist/services/host-operation-barrier.js +44 -0
- package/dist/services/host-operation-barrier.js.map +1 -0
- package/dist/services/host-upgrade.d.ts +129 -0
- package/dist/services/host-upgrade.d.ts.map +1 -0
- package/dist/services/host-upgrade.js +352 -0
- package/dist/services/host-upgrade.js.map +1 -0
- package/dist/services/infrastructure.rpc-service.d.ts +16 -1
- package/dist/services/infrastructure.rpc-service.d.ts.map +1 -1
- package/dist/services/infrastructure.rpc-service.js +71 -10
- package/dist/services/infrastructure.rpc-service.js.map +1 -1
- package/dist/services/local-bundle.d.ts +9 -0
- package/dist/services/local-bundle.d.ts.map +1 -1
- package/dist/services/local-bundle.js +11 -2
- package/dist/services/local-bundle.js.map +1 -1
- package/dist/services/log-collector.service.d.ts +1 -1
- package/dist/services/log-collector.service.js +1 -1
- package/dist/services/maintenance-lock.d.ts +2 -2
- package/dist/services/maintenance-lock.d.ts.map +1 -1
- package/dist/services/maintenance-lock.js +1 -1
- package/dist/services/migration-output.d.ts +2 -2
- package/dist/services/migration-output.js +2 -2
- package/dist/services/node-deploy-lease.d.ts +19 -2
- package/dist/services/node-deploy-lease.d.ts.map +1 -1
- package/dist/services/node-deploy-lease.js +84 -10
- package/dist/services/node-deploy-lease.js.map +1 -1
- package/dist/services/node-manager.rpc-service.d.ts +6 -1
- package/dist/services/node-manager.rpc-service.d.ts.map +1 -1
- package/dist/services/node-manager.rpc-service.js +25 -1
- package/dist/services/node-manager.rpc-service.js.map +1 -1
- package/dist/services/node-manager.service.d.ts +1 -1
- package/dist/services/node-manager.service.js +1 -1
- package/dist/services/node-upgrade.d.ts +1 -1
- package/dist/services/pipeline.rpc-service.d.ts +4 -0
- package/dist/services/pipeline.rpc-service.d.ts.map +1 -1
- package/dist/services/pipeline.rpc-service.js +9 -0
- package/dist/services/pipeline.rpc-service.js.map +1 -1
- package/dist/services/pipeline.service.d.ts +2 -0
- package/dist/services/pipeline.service.d.ts.map +1 -1
- package/dist/services/pipeline.service.js +12 -0
- package/dist/services/pipeline.service.js.map +1 -1
- package/dist/services/project.rpc-service.d.ts +7 -0
- package/dist/services/project.rpc-service.d.ts.map +1 -1
- package/dist/services/project.rpc-service.js +11 -0
- package/dist/services/project.rpc-service.js.map +1 -1
- package/dist/services/project.service.d.ts +21 -11
- package/dist/services/project.service.d.ts.map +1 -1
- package/dist/services/project.service.js +74 -59
- package/dist/services/project.service.js.map +1 -1
- package/dist/services/redeploy-decision.d.ts +2 -2
- package/dist/services/redeploy-decision.js +1 -1
- package/dist/services/release.rpc-service.d.ts +1 -1
- package/dist/services/release.rpc-service.js +1 -1
- package/dist/services/remote-deployer.service.d.ts +27 -16
- package/dist/services/remote-deployer.service.d.ts.map +1 -1
- package/dist/services/remote-deployer.service.js +269 -101
- package/dist/services/remote-deployer.service.js.map +1 -1
- package/dist/services/remote-provisioner.d.ts +2 -0
- package/dist/services/remote-provisioner.d.ts.map +1 -1
- package/dist/services/remote-provisioner.js +1 -1
- package/dist/services/remote-provisioner.js.map +1 -1
- package/dist/services/static-carry-forward.d.ts +26 -1
- package/dist/services/static-carry-forward.d.ts.map +1 -1
- package/dist/services/static-carry-forward.js +275 -1
- package/dist/services/static-carry-forward.js.map +1 -1
- package/dist/shared/dto/nodes.d.ts +2 -2
- package/dist/shared/dto/services.d.ts +27 -0
- package/dist/shared/dto/services.d.ts.map +1 -1
- package/dist/shared/local-infra-host.d.ts +2 -2
- package/dist/shared/local-infra-host.js +2 -2
- package/dist/shared/release-reading.d.ts +1 -1
- package/dist/shared/release-reading.js +1 -1
- package/package.json +16 -12
- package/webapp/dist/BUILD.json +2 -2
- package/webapp/dist/assets/FormControlLabel-BbSxpQwN.js +1 -0
- package/webapp/dist/assets/{Grid-BZ7xhoHQ.js → Grid-DizFZYue.js} +1 -1
- package/webapp/dist/assets/{Switch-DGSAxQPM.js → Switch-Bfiqzx8l.js} +1 -1
- package/webapp/dist/assets/alerts-DH_RVRS3.js +1 -0
- package/webapp/dist/assets/apps-B96355sz.js +1 -0
- package/webapp/dist/assets/audit-CYGtbi4k.js +1 -0
- package/webapp/dist/assets/client-BmqFExWT.js +1 -0
- package/webapp/dist/assets/containers-B_TpThln.js +2 -0
- package/webapp/dist/assets/dashboard-DHf2_65N.js +1 -0
- package/webapp/dist/assets/dashboard-builder-CWY2pjeG.js +1 -0
- package/webapp/dist/assets/{deploy-release-dialog-D6C-XcKY.js → deploy-release-dialog-Dp-zpSAd.js} +1 -1
- package/webapp/dist/assets/deployments-tJI6O3fK.js +1 -0
- package/webapp/dist/assets/detail-B2zP1Hto.js +8 -0
- package/webapp/dist/assets/detail-CoIrwmsy.js +1 -0
- package/webapp/dist/assets/detail-DWaGaOtL.js +2 -0
- package/webapp/dist/assets/index-Dvo0iprx.css +1 -0
- package/webapp/dist/assets/index-vMnY_4jR.js +412 -0
- package/webapp/dist/assets/logs-DTRjOfX8.js +12 -0
- package/webapp/dist/assets/maplibre-gl-DowHoZAx.js +814 -0
- package/webapp/dist/assets/{metrics-CQgjz2Lz.js → metrics-UCajtdox.js} +2 -2
- package/webapp/dist/assets/nodes-DuTVU0f6.js +7 -0
- package/webapp/dist/assets/pipelines-C8BeDcAl.js +4 -0
- package/webapp/dist/assets/projects-Dp_zFfLY.js +1 -0
- package/webapp/dist/assets/react-DlmbjZJV.js +1 -0
- package/webapp/dist/assets/react-apexcharts.esm-C8pn45ml.js +97 -0
- package/webapp/dist/assets/{release-bits-Dj4EOhhb.js → release-bits-BzTKz5fM.js} +1 -1
- package/webapp/dist/assets/release-build-panel-DcW9YcXQ.js +1 -0
- package/webapp/dist/assets/releases-B4hcr1bE.js +2 -0
- package/webapp/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
- package/webapp/dist/assets/settings-DIteZTbe.js +1 -0
- package/webapp/dist/assets/sign-in-D4kMBOHp.js +1 -0
- package/webapp/dist/assets/stacks-Brnf-EB7.js +1 -0
- package/webapp/dist/assets/{system-info-BtE3dgfw.js → system-info-CFDNVKkU.js} +1 -1
- package/webapp/dist/assets/topology-7WeTUsDN.js +12 -0
- package/webapp/dist/assets/traces-9c3gwCN-.js +1 -0
- package/webapp/dist/assets/{use-stack-context-CZUgPq88.js → use-stack-context-CnA5FTHu.js} +1 -1
- package/webapp/dist/index.html +6 -6
- package/webapp/dist/assets/FormControlLabel-BrH2diyl.js +0 -1
- package/webapp/dist/assets/alerts-sXqHJbsa.js +0 -1
- package/webapp/dist/assets/apps-B_Ah8Flh.js +0 -1
- package/webapp/dist/assets/audit-YXihwyui.js +0 -1
- package/webapp/dist/assets/client-CGbBvy9k.js +0 -1
- package/webapp/dist/assets/containers-Di4NOTLf.js +0 -2
- package/webapp/dist/assets/dashboard-Cfuf95N3.js +0 -1
- package/webapp/dist/assets/dashboard-builder-CowaYb-M.js +0 -1
- package/webapp/dist/assets/deployments-ChTq5kCw.js +0 -1
- package/webapp/dist/assets/detail-BsGWv7Dj.js +0 -1
- package/webapp/dist/assets/detail-CyVf_cw5.js +0 -2
- package/webapp/dist/assets/detail-D027j6aO.js +0 -8
- package/webapp/dist/assets/index-B8AHIPc-.css +0 -1
- package/webapp/dist/assets/index-DiZbrwxT.js +0 -414
- package/webapp/dist/assets/logs-L2b8Oqmo.js +0 -12
- package/webapp/dist/assets/maplibre-gl-MlmG2kbn.js +0 -801
- package/webapp/dist/assets/nodes-Nwia2Ty6.js +0 -7
- package/webapp/dist/assets/pipelines-DCu_rXOr.js +0 -4
- package/webapp/dist/assets/projects-Bb4aV76h.js +0 -1
- package/webapp/dist/assets/react-CwJFpaho.js +0 -1
- package/webapp/dist/assets/react-apexcharts.esm-Dy0Fez42.js +0 -1956
- package/webapp/dist/assets/release-build-panel-D6OCVlhT.js +0 -1
- package/webapp/dist/assets/releases-CnAt3yO6.js +0 -2
- package/webapp/dist/assets/rolldown-runtime-hePW80VL.js +0 -1
- package/webapp/dist/assets/settings-DU-mjiwb.js +0 -1
- package/webapp/dist/assets/sign-in-Bcvjrr4C.js +0 -1
- package/webapp/dist/assets/stacks-B-e8p50I.js +0 -1
- package/webapp/dist/assets/topology-Cwch-uF1.js +0 -12
- package/webapp/dist/assets/traces-BbShR-4P.js +0 -1
|
@@ -49,18 +49,21 @@
|
|
|
49
49
|
* to put them.
|
|
50
50
|
*/
|
|
51
51
|
import { PLATFORM_PROBE, parsePlatformProbe, planProvisioning, describePlan, } from './remote-provisioner.js';
|
|
52
|
-
import { installSteps, activateSteps, pruneSteps } from './bundle-builder.js';
|
|
52
|
+
import { installSteps, installPreflightCommand, versionDir, activateSteps, pruneSteps } from './bundle-builder.js';
|
|
53
53
|
import { reachabilityRule, reachabilityCommand } from '../infrastructure/gateway-reachability.js';
|
|
54
54
|
import { readNodeHealth, readNodeStatus } from '../project/node-app-health.js';
|
|
55
55
|
import { decideRedeploy, decideNodeStackStart, decideSlaveDaemonRestart, artifactChanged, readChecksumCommand, parseRecordedChecksum, ARTIFACT_CHECKSUM_FILE, NODE_CONFIG_HASH_FILE, } from './redeploy-decision.js';
|
|
56
56
|
import { deliveredProbeCommand, parseDeliveredProbe, checkDelivered, } from '../release/delivered.js';
|
|
57
57
|
import crypto from 'node:crypto';
|
|
58
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
59
|
+
import { assertControlledDaemonPort, assertServingBundle, bundleProbeCommand, contractPreflightCommand, executeHostUpgrade, hookCommand, inventoryCommand, planHostUpgrade, receiptCommand, verifyRestoredInventory, updateContractBindingsCommand, runtimeProbeCommand, runtimeStartCommand, runtimeStopCommand, appHealthCommand, pendingReceiptsCommand, recoverHostUpgrade, stoppedDaemonCommand, } from './host-upgrade.js';
|
|
58
60
|
import { gunzipSync } from 'node:zlib';
|
|
59
61
|
import { shellEscape } from '../shared/shell-escape.js';
|
|
60
62
|
import { fromDataChannel, throughDataChannel } from '../execution/data-channel.js';
|
|
61
63
|
/** A node that could not be asked at all — the SSH call itself failed. */
|
|
62
64
|
const UNREADABLE = -3;
|
|
63
65
|
import { withNodeLeases } from './node-deploy-lease.js';
|
|
66
|
+
import { hostOperationBarrierCommand } from './host-operation-barrier.js';
|
|
64
67
|
import { migrationOutput } from './migration-output.js';
|
|
65
68
|
/**
|
|
66
69
|
* A name that is safe to place in a remote filesystem path.
|
|
@@ -231,6 +234,7 @@ export function wholeTarGz(archive) {
|
|
|
231
234
|
export class RemoteDeployer {
|
|
232
235
|
logger;
|
|
233
236
|
execution;
|
|
237
|
+
deploymentLeases = new AsyncLocalStorage();
|
|
234
238
|
/** Active deployment progress handlers */
|
|
235
239
|
progressHandlers = [];
|
|
236
240
|
/**
|
|
@@ -417,7 +421,7 @@ export class RemoteDeployer {
|
|
|
417
421
|
// Titan package identity mismatch detected between omnitron daemon
|
|
418
422
|
// and app 'geo'. Daemon resolves @omnitron-dev/titan from
|
|
419
423
|
// /opt/omnitron/versions/…; App resolves … from /opt/omnitron/
|
|
420
|
-
// artifacts/
|
|
424
|
+
// artifacts/sample/geo/0.0.1/node_modules/@omnitron-dev/titan
|
|
421
425
|
//
|
|
422
426
|
// That is the correct refusal, and the answer is not to install less:
|
|
423
427
|
// the vendored copies are what let `npm install` resolve a coherent
|
|
@@ -449,7 +453,7 @@ export class RemoteDeployer {
|
|
|
449
453
|
// puts the link INSIDE it, leaving a directory with no package.json
|
|
450
454
|
// that node reports as a missing `index.js` — a message about a file
|
|
451
455
|
// that was never the problem.
|
|
452
|
-
const daemonRoot =
|
|
456
|
+
const daemonRoot = (await this.inspectInstalledBundle(target)).root;
|
|
453
457
|
await this.sshExec(target, `set -e; APP=${shellEscape(`${remotePath}/node_modules/@omnitron-dev`)}; ` +
|
|
454
458
|
`mkdir -p "$APP"; ` +
|
|
455
459
|
`for p in ${shellEscape(daemonRoot)}/node_modules/@omnitron-dev/*; do ` +
|
|
@@ -480,7 +484,7 @@ export class RemoteDeployer {
|
|
|
480
484
|
// 5b. No record yet. The next deployment reads `.artifact-sha256` as
|
|
481
485
|
// «the app runs this», and at this point it does not: the files are
|
|
482
486
|
// unpacked, the process still runs what it started with. The record
|
|
483
|
-
// used to be written here, and on
|
|
487
|
+
// used to be written here, and on sample/test (2026-09-24) a deployment
|
|
484
488
|
// that stalled after installing three artifacts left them recorded;
|
|
485
489
|
// the retry read «installed, running» and left all three on the code
|
|
486
490
|
// they had started with an hour earlier. It is written once the app
|
|
@@ -746,7 +750,7 @@ export class RemoteDeployer {
|
|
|
746
750
|
*
|
|
747
751
|
* Measured on the test node after six apps installed and five started:
|
|
748
752
|
* every one of the six databases existed and held ZERO tables. Five apps
|
|
749
|
-
* came up anyway, because they touch no table at boot; `
|
|
753
|
+
* came up anyway, because they touch no table at boot; `payments` does, and
|
|
750
754
|
* said so —
|
|
751
755
|
*
|
|
752
756
|
* Failed to call @PostConstruct 'bootstrap' on 'PlatformRevenueService':
|
|
@@ -757,7 +761,7 @@ export class RemoteDeployer {
|
|
|
757
761
|
* refuses to start.
|
|
758
762
|
*
|
|
759
763
|
* The artifact already carries what is needed: `dist/database/migrate.js`
|
|
760
|
-
* beside `dist/database/migrations/` — 160 compiled files for
|
|
764
|
+
* beside `dist/database/migrations/` — 160 compiled files for payments. The
|
|
761
765
|
* same build the app itself runs from, so there is no second copy of the
|
|
762
766
|
* schema to drift.
|
|
763
767
|
*
|
|
@@ -780,7 +784,7 @@ export class RemoteDeployer {
|
|
|
780
784
|
* running the batch again after the cause is fixed applies exactly what
|
|
781
785
|
* is missing. That holds because `HardenedMigrationRunner` defaults
|
|
782
786
|
* `useTransactions` to true (`titan-database/src/migration/
|
|
783
|
-
* hardened-runner.ts:171`) and no
|
|
787
|
+
* hardened-runner.ts:171`) and no sample `migrate.ts` overrides it — it is
|
|
784
788
|
* conditional in the runner (`:502`), so a migrator that turned it off
|
|
785
789
|
* would make a refused deployment leave a half-applied migration behind.
|
|
786
790
|
*/
|
|
@@ -815,9 +819,9 @@ export class RemoteDeployer {
|
|
|
815
819
|
this.emitProgress(`${target.host}:${target.daemonPort ?? 9700}`, entry.app, 'installing', 75, 'Applying database migrations...');
|
|
816
820
|
// Both spellings, because the migrators read the second one.
|
|
817
821
|
//
|
|
818
|
-
// `apps/
|
|
819
|
-
// `envPrefix: '
|
|
820
|
-
// `
|
|
822
|
+
// `apps/payments/src/database/migrate.ts` declares
|
|
823
|
+
// `envPrefix: 'PAYMENTS__DATABASE'` and builds its connection from
|
|
824
|
+
// `PAYMENTS__DATABASE__HOST` / `__PORT` / `__USER` / `__PASSWORD` /
|
|
821
825
|
// `__DATABASE`. Given only `DATABASE_URL` it falls back to its own
|
|
822
826
|
// defaults and reports `password authentication failed for user
|
|
823
827
|
// "postgres"` — against a password that is correct and that it never
|
|
@@ -1194,17 +1198,20 @@ export class RemoteDeployer {
|
|
|
1194
1198
|
async installBundle(target, archivePath, version, prefix = '/opt/omnitron') {
|
|
1195
1199
|
const nodeKey = `${target.host}:${target.daemonPort ?? 9700}`;
|
|
1196
1200
|
const layout = { prefix, version };
|
|
1197
|
-
// Under the prefix, not `/tmp`: a bundle is tens of megabytes and `/tmp`
|
|
1198
|
-
// is a tmpfs on many hosts, where a large transfer competes with memory.
|
|
1199
|
-
const remoteArchive = `${prefix}/releases/${version}.tar.gz`;
|
|
1200
1201
|
try {
|
|
1202
|
+
// Reject path traversal before any connection, upload or filesystem mutation.
|
|
1203
|
+
versionDir(layout);
|
|
1204
|
+
const fs = await import('node:fs/promises');
|
|
1205
|
+
const archiveSha256 = crypto.createHash('sha256').update(await fs.readFile(archivePath)).digest('hex');
|
|
1206
|
+
// Content-addressed transfer cannot replace an earlier archive with different bytes.
|
|
1207
|
+
const remoteArchive = `${prefix}/releases/${version}.${archiveSha256}.tar.gz`;
|
|
1201
1208
|
this.emitProgress(nodeKey, '*', 'pending', 0, 'Connecting via SSH...');
|
|
1202
1209
|
await this.verifySSH(target);
|
|
1203
1210
|
this.emitProgress(nodeKey, '*', 'transferring', 10, `Transferring ${version}...`);
|
|
1204
|
-
await this.sshExec(target,
|
|
1211
|
+
await this.sshExec(target, installPreflightCommand(layout));
|
|
1205
1212
|
await this.scpTransfer(target, archivePath, remoteArchive);
|
|
1206
1213
|
let progress = 30;
|
|
1207
|
-
for (const step of installSteps(layout, remoteArchive)) {
|
|
1214
|
+
for (const step of installSteps(layout, remoteArchive, archiveSha256)) {
|
|
1208
1215
|
this.emitProgress(nodeKey, '*', 'extracting', progress, step.what);
|
|
1209
1216
|
const output = await this.sshExec(target, step.command, step.timeoutMs);
|
|
1210
1217
|
if (step.what.includes('runs')) {
|
|
@@ -1238,83 +1245,171 @@ export class RemoteDeployer {
|
|
|
1238
1245
|
* left to expire by itself. A node with no gateway activates as before.
|
|
1239
1246
|
*/
|
|
1240
1247
|
async activateBundleHeld(target, version, prefix = '/opt/omnitron', keepVersions = 3) {
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
const
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1248
|
+
assertControlledDaemonPort(target.daemonPort);
|
|
1249
|
+
return this.underLease(target, `held host upgrade to ${version}`, async () => {
|
|
1250
|
+
await this.assertNoPendingHostOperations(target, prefix);
|
|
1251
|
+
const { runningGatewaySites, MaintenanceLock, DEFAULT_MAINTENANCE_TIMING } = await import('./maintenance-lock.js');
|
|
1252
|
+
const run = (script) => this.sshExecData(target, script, 30_000);
|
|
1253
|
+
const locks = [];
|
|
1254
|
+
for (const site of await runningGatewaySites(run, `omnitron ${version}`)) {
|
|
1255
|
+
const lock = await MaintenanceLock.open(site, this.logger, { ...DEFAULT_MAINTENANCE_TIMING, probeWithinMs: 180_000 });
|
|
1256
|
+
if (lock)
|
|
1257
|
+
locks.push(lock);
|
|
1258
|
+
}
|
|
1259
|
+
try {
|
|
1260
|
+
let activated = await this.activateBundle(target, version, prefix, keepVersions, async () => {
|
|
1261
|
+
for (const lock of locks) {
|
|
1262
|
+
const failed = await lock.probe(['/', '/api/main/health']);
|
|
1263
|
+
if (failed.length)
|
|
1264
|
+
throw new Error(`Gateway verification failed: ${failed.join(', ')}`);
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
while (locks.length > 0) {
|
|
1268
|
+
const lifted = await locks.shift().liftWhenAnswering(['/', '/api/main/health']);
|
|
1269
|
+
if (!lifted.lifted) {
|
|
1270
|
+
activated = false;
|
|
1271
|
+
this.logger.error({ host: target.host, version, failed: lifted.failed }, 'After the upgrade the platform does not answer through its gateway — maintenance expires by itself');
|
|
1272
|
+
}
|
|
1255
1273
|
}
|
|
1274
|
+
return activated;
|
|
1256
1275
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
}
|
|
1276
|
+
finally {
|
|
1277
|
+
for (const lock of locks)
|
|
1278
|
+
await lock.lift('failed').catch(() => undefined);
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1263
1281
|
}
|
|
1264
|
-
/**
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1282
|
+
/** Check all durable operator workflows; a recovery can allow only its exact active journal. */
|
|
1283
|
+
async assertNoPendingHostOperations(target, prefix = '/opt/omnitron', allowance) {
|
|
1284
|
+
await this.sshExecData(target, hostOperationBarrierCommand(prefix, allowance));
|
|
1285
|
+
}
|
|
1286
|
+
/** Require a settled host and the actual serving immutable daemon build. */
|
|
1287
|
+
async inspectInstalledBundle(target, prefix = '/opt/omnitron', allowance) {
|
|
1288
|
+
assertControlledDaemonPort(target.daemonPort);
|
|
1289
|
+
await this.assertNoPendingHostOperations(target, prefix, allowance);
|
|
1290
|
+
const pending = JSON.parse(await this.sshExecData(target, pendingReceiptsCommand(prefix)));
|
|
1291
|
+
if (pending.length)
|
|
1292
|
+
throw new Error(`Unfinished host upgrade ${pending.map(r => r.id).join(', ')}; recover it before deploying an app`);
|
|
1293
|
+
const bundle = JSON.parse(await this.sshExecData(target, bundleProbeCommand(prefix)));
|
|
1294
|
+
const status = JSON.parse(await this.sshExecData(target, inventoryCommand(bundle, false)));
|
|
1295
|
+
assertServingBundle(bundle, status);
|
|
1296
|
+
await this.sshExecData(target, runtimeProbeCommand(bundle, status.pid));
|
|
1297
|
+
return bundle;
|
|
1298
|
+
}
|
|
1299
|
+
async activateBundle(target, version, prefix = '/opt/omnitron', keepVersions = 3, verifyPlatform, allowance) {
|
|
1300
|
+
assertControlledDaemonPort(target.daemonPort);
|
|
1301
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._+-]*$/.test(version) || version.includes('..'))
|
|
1302
|
+
throw new Error('Invalid installed bundle version');
|
|
1303
|
+
if (!Number.isSafeInteger(keepVersions) || keepVersions < 1)
|
|
1304
|
+
throw new Error('keepVersions must be a positive integer');
|
|
1305
|
+
return this.underLease(target, `host upgrade to ${version}`, async () => {
|
|
1306
|
+
const nodeKey = `${target.host}:${target.daemonPort ?? 9700}`;
|
|
1307
|
+
try {
|
|
1308
|
+
await this.assertNoPendingHostOperations(target, prefix, allowance);
|
|
1309
|
+
const pending = JSON.parse(await this.sshExecData(target, pendingReceiptsCommand(prefix)));
|
|
1310
|
+
if (pending.length) {
|
|
1311
|
+
if (pending.length !== 1)
|
|
1312
|
+
throw new Error('Multiple unfinished host upgrades; recover an explicit receipt ID first');
|
|
1313
|
+
const recovered = await this.recoverBundleUpgrade(target, pending[0].id, prefix, verifyPlatform, allowance);
|
|
1314
|
+
this.logger.warn({ receipt: pending[0].id, recovered }, 'Recovered the unfinished host upgrade; retry the requested upgrade separately');
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
const previous = await this.inspectInstalledBundle(target, prefix, allowance);
|
|
1318
|
+
const candidate = JSON.parse(await this.sshExecData(target, bundleProbeCommand(prefix, version)));
|
|
1319
|
+
await this.sshExec(target, `${shellEscape(`${candidate.root}/dist/cli/omnitron.js`)} --version`, 120_000);
|
|
1320
|
+
const inventory = JSON.parse(await this.sshExecData(target, inventoryCommand(previous, true)));
|
|
1321
|
+
inventory.runtime = JSON.parse(await this.sshExecData(target, runtimeProbeCommand(previous, inventory.status.pid)));
|
|
1322
|
+
const plan = planHostUpgrade(previous, candidate, inventory);
|
|
1323
|
+
await this.sshExec(target, contractPreflightCommand(plan.apps));
|
|
1324
|
+
for (const app of plan.apps) {
|
|
1325
|
+
await this.sshExec(target, hookCommand(app, previous, 'verify'), 330_000);
|
|
1326
|
+
if (plan.online.includes(app.name))
|
|
1327
|
+
await this.sshExec(target, appHealthCommand(app, previous), 330_000);
|
|
1328
|
+
}
|
|
1329
|
+
const id = `${new Date().toISOString().replace(/[:.]/g, '-')}-${crypto.randomUUID()}`;
|
|
1330
|
+
const receiptPath = `${prefix}/upgrade-receipts/${id}.json`;
|
|
1331
|
+
const receipt = await executeHostUpgrade(plan, id, this.hostUpgradeActions(target, prefix, keepVersions, plan, verifyPlatform));
|
|
1332
|
+
const ok = receipt.outcome === 'upgraded';
|
|
1333
|
+
this.emitProgress(nodeKey, '*', ok ? 'success' : 'failed', 100, `${receipt.outcome}; recovery receipt: ${receiptPath}`);
|
|
1334
|
+
this.logger[ok ? 'info' : 'error']({ host: target.host, version, receipt: receiptPath, outcome: receipt.outcome }, 'Controlled host upgrade finished');
|
|
1335
|
+
return ok;
|
|
1277
1336
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
// activated is the difference between restarting the new version and
|
|
1283
|
-
// restarting whatever was there.
|
|
1284
|
-
const cli = shellEscape(`${prefix}/current/dist/cli/omnitron.js`);
|
|
1285
|
-
await this.sshExec(target, `${cli} down 2>/dev/null; ${cli} up --no-infra`, 180_000).catch((err) => {
|
|
1286
|
-
this.logger.warn({ host: target.host, error: err.message }, 'Restart command did not return cleanly — verifying anyway');
|
|
1287
|
-
});
|
|
1288
|
-
this.emitProgress(nodeKey, '*', 'verifying', 80, 'Verifying the daemon answers');
|
|
1289
|
-
const started = await this.awaitDaemon(target, 120_000);
|
|
1290
|
-
if (!started.ok) {
|
|
1291
|
-
// Reported, and NOT rolled back automatically. A daemon that will not
|
|
1292
|
-
// start is a decision for whoever is watching: the previous version
|
|
1293
|
-
// is still on disk and one `ln -sfn` away, and guessing that a
|
|
1294
|
-
// rollback is wanted can be as wrong as guessing it is not.
|
|
1295
|
-
this.logger.error({ host: target.host, version, detail: started.detail }, 'The new version did not answer — the previous one is still installed');
|
|
1296
|
-
this.emitProgress(nodeKey, '*', 'failed', 80, `${version} did not answer: ${started.detail}. Roll back with ` +
|
|
1297
|
-
`\`ln -sfn ${prefix}/versions/<previous> ${prefix}/current\` and restart.`);
|
|
1337
|
+
catch (error) {
|
|
1338
|
+
const message = error.message;
|
|
1339
|
+
this.emitProgress(nodeKey, '*', 'failed', 0, message);
|
|
1340
|
+
this.logger.error({ host: target.host, version, error: message }, 'Host upgrade refused or preparation failed');
|
|
1298
1341
|
return false;
|
|
1299
1342
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
this.
|
|
1316
|
-
|
|
1317
|
-
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
/** Recover a recorded interrupted upgrade under the same node lease; never prunes or changes app release versions. */
|
|
1346
|
+
async recoverBundleUpgrade(target, id, prefix = '/opt/omnitron', verifyPlatform, allowance) {
|
|
1347
|
+
assertControlledDaemonPort(target.daemonPort);
|
|
1348
|
+
return this.underLease(target, `recover host upgrade ${id}`, async () => {
|
|
1349
|
+
await this.assertNoPendingHostOperations(target, prefix, allowance);
|
|
1350
|
+
const records = JSON.parse(await this.sshExecData(target, pendingReceiptsCommand(prefix, id)));
|
|
1351
|
+
if (records.length !== 1 || records[0].id !== id)
|
|
1352
|
+
throw new Error('No unfinished host upgrade with that receipt ID');
|
|
1353
|
+
const receipt = records[0];
|
|
1354
|
+
const plan = planHostUpgrade(receipt.plan.previous, receipt.plan.candidate, receipt.plan.inventory);
|
|
1355
|
+
const installed = JSON.parse(await this.sshExecData(target, bundleProbeCommand(prefix, plan.previous.version)));
|
|
1356
|
+
if (JSON.stringify(installed) !== JSON.stringify(plan.previous))
|
|
1357
|
+
throw new Error('Recorded previous bundle changed; recovery refused');
|
|
1358
|
+
await this.sshExec(target, contractPreflightCommand(plan.apps));
|
|
1359
|
+
const result = await recoverHostUpgrade(receipt, this.hostUpgradeActions(target, prefix, 3, plan, verifyPlatform));
|
|
1360
|
+
this.logger[result.outcome === 'rolled-back' ? 'info' : 'error']({ receipt: `${prefix}/upgrade-receipts/${id}.json`, outcome: result.outcome }, 'Host upgrade recovery finished');
|
|
1361
|
+
return result.outcome === 'rolled-back';
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
hostUpgradeActions(target, prefix, keepVersions, plan, verifyPlatform) {
|
|
1365
|
+
return {
|
|
1366
|
+
save: async (receipt) => { await this.sshExec(target, receiptCommand(prefix, receipt)); },
|
|
1367
|
+
stop: async (bundle) => {
|
|
1368
|
+
await this.sshExec(target, runtimeStopCommand(plan.inventory.runtime, bundle), 180_000);
|
|
1369
|
+
await this.sshExec(target, stoppedDaemonCommand(bundle), 30_000);
|
|
1370
|
+
},
|
|
1371
|
+
activate: async (bundle) => {
|
|
1372
|
+
for (const step of activateSteps({ prefix, version: bundle.version }))
|
|
1373
|
+
await this.sshExec(target, step.command, step.timeoutMs);
|
|
1374
|
+
},
|
|
1375
|
+
rebind: async (app, bundle) => { await this.sshExec(target, hookCommand(app, bundle, 'rebind'), 330_000); },
|
|
1376
|
+
start: async (bundle) => {
|
|
1377
|
+
await this.sshExec(target, runtimeStartCommand(plan.inventory.runtime, plan.previous, bundle, [plan.previous.root, plan.candidate.root]), 180_000);
|
|
1378
|
+
},
|
|
1379
|
+
verify: async (bundle) => {
|
|
1380
|
+
const deadline = Date.now() + 180_000;
|
|
1381
|
+
let lastError;
|
|
1382
|
+
do {
|
|
1383
|
+
try {
|
|
1384
|
+
const status = JSON.parse(await this.sshExecData(target, inventoryCommand(bundle, false), 30_000));
|
|
1385
|
+
verifyRestoredInventory(plan, bundle, status);
|
|
1386
|
+
const runtime = JSON.parse(await this.sshExecData(target, runtimeProbeCommand(bundle, status.pid)));
|
|
1387
|
+
if (runtime.kind !== plan.inventory.runtime.kind || (runtime.kind === 'systemd' && plan.inventory.runtime.kind === 'systemd' &&
|
|
1388
|
+
(runtime.scope !== plan.inventory.runtime.scope || runtime.enabled !== plan.inventory.runtime.enabled)))
|
|
1389
|
+
throw new Error('Supervisor state was not restored');
|
|
1390
|
+
for (const app of plan.apps) {
|
|
1391
|
+
await this.sshExec(target, hookCommand(app, bundle, 'verify'), 330_000);
|
|
1392
|
+
if (plan.online.includes(app.name))
|
|
1393
|
+
await this.sshExec(target, appHealthCommand(app, bundle), 330_000);
|
|
1394
|
+
}
|
|
1395
|
+
if (verifyPlatform)
|
|
1396
|
+
await verifyPlatform();
|
|
1397
|
+
await this.sshExec(target, updateContractBindingsCommand(plan, bundle));
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
catch (error) {
|
|
1401
|
+
lastError = error;
|
|
1402
|
+
}
|
|
1403
|
+
if (Date.now() < deadline)
|
|
1404
|
+
await new Promise(resolve => setTimeout(resolve, 2_000));
|
|
1405
|
+
} while (Date.now() < deadline);
|
|
1406
|
+
throw new Error(`Host and application verification failed: ${String(lastError)}`);
|
|
1407
|
+
},
|
|
1408
|
+
prune: async (protectedRoots) => {
|
|
1409
|
+
for (const step of pruneSteps({ prefix, version: plan.candidate.version }, keepVersions, protectedRoots))
|
|
1410
|
+
await this.sshExec(target, step.command, step.timeoutMs);
|
|
1411
|
+
},
|
|
1412
|
+
};
|
|
1318
1413
|
}
|
|
1319
1414
|
async verifySSH(target) {
|
|
1320
1415
|
await this.sshExec(target, 'echo ok', 10_000);
|
|
@@ -1338,7 +1433,14 @@ export class RemoteDeployer {
|
|
|
1338
1433
|
// Through the data channel: a lease answer carries the holder's record,
|
|
1339
1434
|
// `{"token":…}`, which the masker rewrote into `"token": [REDACTED]` on
|
|
1340
1435
|
// every read — a refusal could never say who held the node.
|
|
1341
|
-
return (script) =>
|
|
1436
|
+
return async (script) => {
|
|
1437
|
+
// Confirmation itself must use the raw channel, never recursively ask
|
|
1438
|
+
// for another confirmation of the lease being acquired or renewed.
|
|
1439
|
+
const result = await this.readFromNodeRaw(target, script, 30_000);
|
|
1440
|
+
if (result.code !== 0)
|
|
1441
|
+
throw new Error(`Lease transport failed: ${result.stderr || result.stdout}`);
|
|
1442
|
+
return result.stdout.trim();
|
|
1443
|
+
};
|
|
1342
1444
|
}
|
|
1343
1445
|
/**
|
|
1344
1446
|
* Run `work` holding the machine's deploy lease — for what changes a node
|
|
@@ -1348,15 +1450,32 @@ export class RemoteDeployer {
|
|
|
1348
1450
|
*/
|
|
1349
1451
|
async underLease(target, purpose, work) {
|
|
1350
1452
|
const machine = `${target.host}:${target.sshPort ?? 22}`;
|
|
1453
|
+
const held = this.deploymentLeases.getStore();
|
|
1454
|
+
if (held?.has(machine))
|
|
1455
|
+
return work();
|
|
1351
1456
|
return withNodeLeases([{ node: machine, machine, run: this.leaseRunner(target) }], purpose, this.logger, async (leases) => {
|
|
1352
1457
|
if (!leases.has(machine)) {
|
|
1353
1458
|
throw new Error(`Could not reach ${machine} to lease it for ${purpose}: ${leases.unreachable.get(machine) ?? 'no answer'}`);
|
|
1354
1459
|
}
|
|
1355
|
-
|
|
1460
|
+
const context = new Map(held);
|
|
1461
|
+
context.set(machine, { confirm: step => leases.confirm(machine, step), guard: (command, timeoutMs) => leases.guard(machine, command, timeoutMs) });
|
|
1462
|
+
return this.deploymentLeases.run(context, work);
|
|
1356
1463
|
});
|
|
1357
1464
|
}
|
|
1465
|
+
/** Confirm a held lease before the next node operation; outside a lease this is a no-op. */
|
|
1466
|
+
async confirmLease(target, step) {
|
|
1467
|
+
const machine = `${target.host}:${target.sshPort ?? 22}`;
|
|
1468
|
+
await this.deploymentLeases.getStore()?.get(machine)?.confirm(step);
|
|
1469
|
+
}
|
|
1470
|
+
/** The node holds its execution fence for the command lifetime, beyond a lost controller. */
|
|
1471
|
+
guardedCommand(target, command, timeout) {
|
|
1472
|
+
const lease = this.deploymentLeases.getStore()?.get(`${target.host}:${target.sshPort ?? 22}`);
|
|
1473
|
+
return lease ? { command: lease.guard(command, timeout), timeout: timeout + 45_000 } : { command, timeout };
|
|
1474
|
+
}
|
|
1358
1475
|
async sshExec(target, command, timeout = 60_000) {
|
|
1359
|
-
|
|
1476
|
+
await this.confirmLease(target, 'remote command');
|
|
1477
|
+
const guarded = this.guardedCommand(target, command, timeout);
|
|
1478
|
+
const result = await this.execution.ssh(sshTargetOf(target), guarded.command, { timeout: guarded.timeout });
|
|
1360
1479
|
if (result.exitCode !== 0) {
|
|
1361
1480
|
const detail = result.stderr || result.stdout || `exit ${result.exitCode}`;
|
|
1362
1481
|
throw new Error(`ssh ${target.username ?? 'root'}@${target.host}: ${detail}`);
|
|
@@ -1381,7 +1500,9 @@ export class RemoteDeployer {
|
|
|
1381
1500
|
* keep), and a throw would flatten the two into one.
|
|
1382
1501
|
*/
|
|
1383
1502
|
async runOnNode(target, command, timeoutMs = 600_000) {
|
|
1384
|
-
|
|
1503
|
+
await this.confirmLease(target, 'remote command');
|
|
1504
|
+
const guarded = this.guardedCommand(target, command, timeoutMs);
|
|
1505
|
+
const result = await this.execution.ssh(sshTargetOf(target), guarded.command, { timeout: guarded.timeout });
|
|
1385
1506
|
return { stdout: result.stdout, stderr: result.stderr, code: result.exitCode ?? -1 };
|
|
1386
1507
|
}
|
|
1387
1508
|
/**
|
|
@@ -1396,6 +1517,11 @@ export class RemoteDeployer {
|
|
|
1396
1517
|
* failure with its own code, -2, and its words on stderr — never data.
|
|
1397
1518
|
*/
|
|
1398
1519
|
async readFromNode(target, command, timeoutMs = 600_000) {
|
|
1520
|
+
await this.confirmLease(target, 'remote data command');
|
|
1521
|
+
const guarded = this.guardedCommand(target, command, timeoutMs);
|
|
1522
|
+
return this.readFromNodeRaw(target, guarded.command, guarded.timeout);
|
|
1523
|
+
}
|
|
1524
|
+
async readFromNodeRaw(target, command, timeoutMs) {
|
|
1399
1525
|
const result = await this.execution.ssh(sshTargetOf(target), throughDataChannel(command), { timeout: timeoutMs });
|
|
1400
1526
|
const data = fromDataChannel(result.stdout);
|
|
1401
1527
|
if (!data.ok) {
|
|
@@ -1403,8 +1529,23 @@ export class RemoteDeployer {
|
|
|
1403
1529
|
}
|
|
1404
1530
|
return { stdout: data.text, stderr: result.stderr, code: result.exitCode ?? -1 };
|
|
1405
1531
|
}
|
|
1532
|
+
/** Use this inside underLease so uploads share the command confirmation boundary. */
|
|
1533
|
+
async uploadToNode(target, localPath, remotePath) {
|
|
1534
|
+
await this.confirmLease(target, 'file upload');
|
|
1535
|
+
const lease = this.deploymentLeases.getStore()?.get(`${target.host}:${target.sshPort ?? 22}`);
|
|
1536
|
+
if (!lease) {
|
|
1537
|
+
await this.execution.uploadFile(sshTargetOf(target), localPath, remotePath);
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
await this.execution.uploadFile(sshTargetOf(target), localPath, remotePath, {
|
|
1541
|
+
runControl: async (command, timeout) => {
|
|
1542
|
+
const result = await this.runOnNode(target, command, timeout);
|
|
1543
|
+
return { stdout: result.stdout, stderr: result.stderr, exitCode: result.code };
|
|
1544
|
+
},
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1406
1547
|
async scpTransfer(target, localPath, remotePath) {
|
|
1407
|
-
await this.
|
|
1548
|
+
await this.uploadToNode(target, localPath, remotePath);
|
|
1408
1549
|
}
|
|
1409
1550
|
/**
|
|
1410
1551
|
* Ask the node's daemon to run the app whose artifact just landed.
|
|
@@ -1499,7 +1640,7 @@ export class RemoteDeployer {
|
|
|
1499
1640
|
// this pipeline's status is gzip's alone, so a `tar` that stopped
|
|
1500
1641
|
// halfway left a short archive that was then summed, sent, checked
|
|
1501
1642
|
// against its own short sum — and failed only on the node: «gzip:
|
|
1502
|
-
// stdin: unexpected end of file» (
|
|
1643
|
+
// stdin: unexpected end of file» (sample-202609221949-23dce2f6 to test,
|
|
1503
1644
|
// 2026-09-22). The sum proves the file ARRIVED as it left; nothing
|
|
1504
1645
|
// proved it left whole.
|
|
1505
1646
|
const content = await fsp.readFile(archive).catch(() => null);
|
|
@@ -1560,7 +1701,7 @@ export class RemoteDeployer {
|
|
|
1560
1701
|
if (!alreadyThere) {
|
|
1561
1702
|
await this.sshExec(target, `rm -rf ${shellEscape(remoteDir)} ${shellEscape(marker)}`).catch(() => undefined);
|
|
1562
1703
|
await this.sshExec(target, `mkdir -p ${shellEscape(remoteDir)}`);
|
|
1563
|
-
await this.
|
|
1704
|
+
await this.uploadToNode(target, archive, remoteFile);
|
|
1564
1705
|
// `uploadFile` already refuses a file that landed SHORT. That catches
|
|
1565
1706
|
// the likely failure and not the other one: a transfer that completes
|
|
1566
1707
|
// and corrupts in the middle has the right length and the wrong
|
|
@@ -1625,14 +1766,41 @@ export class RemoteDeployer {
|
|
|
1625
1766
|
await this.sshExec(target, `ln -s ${shellEscape(digest)} ${shellEscape(tmp)} && mv -T ${shellEscape(tmp)} ${shellEscape(`${root}/${link}`)}`);
|
|
1626
1767
|
this.logger.info({ host: target.host, link, digest }, 'The gateway now serves this build through its link');
|
|
1627
1768
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1769
|
+
async carryPreviousBuildForward(target, input, fresh) {
|
|
1770
|
+
if (typeof input !== 'string') {
|
|
1771
|
+
const { immutableAssetsRoot, immutableAssetsScript } = await import('./static-carry-forward.js');
|
|
1772
|
+
const expectedRoot = immutableAssetsRoot(input.owner);
|
|
1773
|
+
// The native execution fence serializes every publisher, including reentrant calls.
|
|
1774
|
+
return this.underLease(target, 'retain immutable public assets', async () => {
|
|
1775
|
+
await this.assertNoPendingHostOperations(target);
|
|
1776
|
+
const fs = await import('node:fs/promises');
|
|
1777
|
+
const { tmpdir } = await import('node:os');
|
|
1778
|
+
const { join } = await import('node:path');
|
|
1779
|
+
const { randomUUID } = await import('node:crypto');
|
|
1780
|
+
const temporary = await fs.mkdtemp(join(tmpdir(), 'omnitron-static-assets-'));
|
|
1781
|
+
const local = join(temporary, 'request.json');
|
|
1782
|
+
const remote = '/tmp/omnitron-static-assets-' + randomUUID() + '.json';
|
|
1783
|
+
try {
|
|
1784
|
+
await fs.writeFile(local, JSON.stringify(input), { mode: 0o600, flag: 'wx' });
|
|
1785
|
+
await this.uploadToNode(target, local, remote);
|
|
1786
|
+
const output = await this.sshExec(target, immutableAssetsScript(remote), 300_000);
|
|
1787
|
+
const receipt = JSON.parse(output);
|
|
1788
|
+
if (receipt.assetsRoot !== expectedRoot || !/^[a-f0-9]{64}$/.test(receipt.manifestSha256) ||
|
|
1789
|
+
![receipt.files, receipt.added, receipt.reused, receipt.bytes].every((n) => Number.isSafeInteger(n) && n >= 0)) {
|
|
1790
|
+
throw new Error('Invalid immutable asset publication receipt');
|
|
1791
|
+
}
|
|
1792
|
+
this.logger.info({ host: target.host, owner: input.owner, ...receipt }, 'Immutable public asset bytes retained');
|
|
1793
|
+
return receipt;
|
|
1794
|
+
}
|
|
1795
|
+
finally {
|
|
1796
|
+
await fs.rm(temporary, { recursive: true, force: true });
|
|
1797
|
+
await this.sshExec(target, 'rm -f -- ' + shellEscape(remote)).catch(() => undefined);
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
const root = input;
|
|
1802
|
+
if (!fresh)
|
|
1803
|
+
throw new Error('Missing legacy static build');
|
|
1636
1804
|
try {
|
|
1637
1805
|
const { GATEWAY_MOUNTS_COMMAND, readGatewayMounts, carryForwardScript } = await import('./static-carry-forward.js');
|
|
1638
1806
|
const mounts = readGatewayMounts(await this.sshExec(target, GATEWAY_MOUNTS_COMMAND).catch(() => ''));
|