@crvouga/dev-fleet 0.0.0-stage → 0.2.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/.mise/locks/aws-cdk/2.1146.1/aube-lock.yaml +24 -0
- package/.mise/locks/aws-cdk/2.1146.1/package.json +7 -0
- package/.mise/locks/azure-cli/2.91.0~a837e014/pyproject.toml +5 -0
- package/.mise/locks/azure-cli/2.91.0~a837e014/uv.lock +2794 -0
- package/.mise/locks/gemini/0.66.0~84aac8e6/aube-lock.yaml +123 -0
- package/.mise/locks/gemini/0.66.0~84aac8e6/package.json +12 -0
- package/.mise/locks/npm-devcontainers-cli/0.90.0/aube-lock.yaml +24 -0
- package/.mise/locks/npm-devcontainers-cli/0.90.0/package.json +7 -0
- package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/aube-lock.yaml +1432 -0
- package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/package.json +7 -0
- package/.mise/locks/npm-vercel/65.8.0/aube-lock.yaml +3595 -0
- package/.mise/locks/npm-vercel/65.8.0/package.json +7 -0
- package/.mise/locks/npm-wrangler/4.156.0/aube-lock.yaml +895 -0
- package/.mise/locks/npm-wrangler/4.156.0/package.json +7 -0
- package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/aube-lock.yaml +24 -0
- package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/package.json +7 -0
- package/LICENSE +21 -0
- package/README.md +357 -2
- package/agents/antigravity-proxy.ts +3 -0
- package/agents/audit.md +89 -0
- package/agents/claude-proxy +5 -0
- package/agents/claude-proxy.ts +3 -0
- package/agents/codex-proxy.ts +3 -0
- package/agents/grok-proxy.ts +3 -0
- package/agents/managed-proxy.ts +55 -0
- package/agents/opencode-proxy.ts +3 -0
- package/agents/registry.json +209 -0
- package/agents/registry.schema.json +149 -0
- package/agents/setup.md +158 -0
- package/agents/t3.json +40 -0
- package/ansible/macos/manager.yml +30 -0
- package/ansible/macos/policy.json +9 -0
- package/ansible/monitoring/README.md +215 -0
- package/ansible/monitoring/agent-darwin.yml +198 -0
- package/ansible/monitoring/agent.yml +172 -0
- package/ansible/monitoring/beszel-agent.service +42 -0
- package/ansible/monitoring/compose.yml +74 -0
- package/ansible/monitoring/hub-darwin.yml +102 -0
- package/ansible/monitoring/hub-route-darwin.yml +51 -0
- package/ansible/monitoring/lock.json +16 -0
- package/ansible/net/remote-probe.sh +13 -0
- package/ansible/pyproject.toml +12 -0
- package/ansible/ubuntu/authorize-sudo.sh +51 -0
- package/ansible/ubuntu/baseline.yml +89 -0
- package/ansible/ubuntu/disk-gc.py +336 -0
- package/ansible/ubuntu/disk-guard.py +104 -0
- package/ansible/ubuntu/policy.json +52 -0
- package/ansible/ubuntu/recovery.yml +15 -0
- package/ansible/ubuntu/t3/policy.json +7 -0
- package/ansible/ubuntu/t3-desktop.yml +220 -0
- package/ansible/ubuntu/t3-health.sh +33 -0
- package/ansible/ubuntu/t3-policy.yml +76 -0
- package/ansible/ubuntu/t3-route.yml +28 -0
- package/ansible/ubuntu/tailscale.yml +78 -0
- package/ansible/ubuntu/tasks/credentials.yml +47 -0
- package/ansible/ubuntu/tasks/docker.yml +31 -0
- package/ansible/ubuntu/tasks/packages.yml +56 -0
- package/ansible/ubuntu/tasks/power.yml +51 -0
- package/ansible/ubuntu/tasks/recovery.yml +39 -0
- package/ansible/ubuntu/tasks/resources.yml +312 -0
- package/ansible/ubuntu/tasks/services.yml +31 -0
- package/ansible/ubuntu/tasks/ssh.yml +71 -0
- package/ansible/ubuntu/tasks/user.yml +120 -0
- package/ansible/uv.lock +190 -0
- package/bin/devfleet.mjs +36 -0
- package/bootstrap/bootstrap.ps1 +50 -0
- package/bootstrap/bootstrap.sh +80 -0
- package/bootstrap/devfleet +7 -0
- package/bootstrap/docker-desktop.sh +33 -0
- package/bootstrap/doctor.ts +234 -0
- package/bootstrap/fleet-manager-run.sh +103 -0
- package/bootstrap/fleet-remote-bootstrap.sh +258 -0
- package/bootstrap/global-config.ts +171 -0
- package/bootstrap/install-launcher.sh +10 -0
- package/bootstrap/native-aws.ts +86 -0
- package/bootstrap/native-t3.ts +154 -0
- package/bootstrap/npm.ts +358 -0
- package/bootstrap/release-version.ts +157 -0
- package/bootstrap/setup-status.ts +51 -0
- package/bootstrap/setup.ts +88 -0
- package/bootstrap/shell-path.ts +105 -0
- package/bootstrap/verify-t3-archive.py +49 -0
- package/bootstrap/worker-auth.ts +65 -0
- package/bootstrap/worker-github.ts +47 -0
- package/bootstrap/worker-tools.ts +24 -0
- package/cliproxy/AGENTS.md +11 -0
- package/cliproxy/README.md +269 -0
- package/cliproxy/clients/bootstrap.py +143 -0
- package/cliproxy/clients/launcher.py +57 -0
- package/cliproxy/compose.yaml +90 -0
- package/cliproxy/config.template.yaml +70 -0
- package/cliproxy/docs/cloud-adoption.md +16 -0
- package/cliproxy/docs/deployment.png +0 -0
- package/cliproxy/docs/deployment.svg +109 -0
- package/cliproxy/docs/health-workflow.example.yml +185 -0
- package/cliproxy/docs/migration.md +131 -0
- package/cliproxy/docs/model-health-report.schema.json +364 -0
- package/cliproxy/docs/model-health.md +393 -0
- package/cliproxy/image.lock.json +5 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-backup-check.plist +38 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-backup.plist +48 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-fleet.plist +32 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-gc.plist +38 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-monitor.plist +29 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-vault-renew.plist +31 -0
- package/cliproxy/models.json +1618 -0
- package/cliproxy/requirements.lock +78 -0
- package/cliproxy/scripts/accounts.py +38 -0
- package/cliproxy/scripts/audit.py +123 -0
- package/cliproxy/scripts/backup +234 -0
- package/cliproxy/scripts/check.py +34 -0
- package/cliproxy/scripts/checkpoint.py +226 -0
- package/cliproxy/scripts/cli.py +64 -0
- package/cliproxy/scripts/cliproxy_host.py +257 -0
- package/cliproxy/scripts/cloud_state.py +150 -0
- package/cliproxy/scripts/container-health +6 -0
- package/cliproxy/scripts/docker +24 -0
- package/cliproxy/scripts/external-health.py +79 -0
- package/cliproxy/scripts/fleet_config.py +43 -0
- package/cliproxy/scripts/fleet_sync.py +274 -0
- package/cliproxy/scripts/initialize +84 -0
- package/cliproxy/scripts/install-dependencies +48 -0
- package/cliproxy/scripts/install.py +406 -0
- package/cliproxy/scripts/model_health.py +16 -0
- package/cliproxy/scripts/modelhealth/__init__.py +5 -0
- package/cliproxy/scripts/modelhealth/checks.py +429 -0
- package/cliproxy/scripts/modelhealth/cli.py +117 -0
- package/cliproxy/scripts/modelhealth/config.py +186 -0
- package/cliproxy/scripts/modelhealth/emulator.py +536 -0
- package/cliproxy/scripts/modelhealth/expect.py +88 -0
- package/cliproxy/scripts/modelhealth/findings.py +183 -0
- package/cliproxy/scripts/modelhealth/protocol.py +330 -0
- package/cliproxy/scripts/modelhealth/redact.py +28 -0
- package/cliproxy/scripts/modelhealth/report.py +150 -0
- package/cliproxy/scripts/modelhealth/runner.py +390 -0
- package/cliproxy/scripts/modelhealth/scenario.py +131 -0
- package/cliproxy/scripts/modelhealth/schema.py +63 -0
- package/cliproxy/scripts/modelhealth/simulate.py +111 -0
- package/cliproxy/scripts/modelhealth/transport.py +213 -0
- package/cliproxy/scripts/monitor +68 -0
- package/cliproxy/scripts/ops +527 -0
- package/cliproxy/scripts/public-endpoint +127 -0
- package/cliproxy/scripts/recover_node.py +177 -0
- package/cliproxy/scripts/restore-verify +60 -0
- package/cliproxy/scripts/retention.py +144 -0
- package/cliproxy/scripts/service-start +7 -0
- package/cliproxy/scripts/validate-config +56 -0
- package/cliproxy/scripts/vault-access.py +57 -0
- package/cliproxy/scripts/vault-backup-login +55 -0
- package/cliproxy/scripts/vault-recovery +56 -0
- package/cliproxy/scripts/vault-renew +30 -0
- package/cliproxy/systemd/cliproxy-backup-check.service +10 -0
- package/cliproxy/systemd/cliproxy-backup-check.timer +8 -0
- package/cliproxy/systemd/cliproxy-backup.service +13 -0
- package/cliproxy/systemd/cliproxy-backup.timer +8 -0
- package/cliproxy/systemd/cliproxy-fleet.service +17 -0
- package/cliproxy/systemd/cliproxy-gc.service +13 -0
- package/cliproxy/systemd/cliproxy-gc.timer +8 -0
- package/cliproxy/systemd/cliproxy-monitor.service +8 -0
- package/cliproxy/systemd/cliproxy-monitor.timer +7 -0
- package/cliproxy/systemd/cliproxy-vault-renew.service +8 -0
- package/cliproxy/systemd/cliproxy-vault-renew.timer +7 -0
- package/cliproxy/vendor/LICENSE +22 -0
- package/cliproxy/vendor/config.upstream.yaml +1349 -0
- package/docs/consumer-setup.md +408 -0
- package/docs/declared.md +89 -0
- package/docs/machine-setup.md +399 -0
- package/docs/operations.md +276 -0
- package/docs/releasing.md +103 -0
- package/docs/state.md +254 -0
- package/docs/topology.md +67 -0
- package/docs/ubuntu-sync.md +351 -0
- package/docs/worker-contract.md +67 -0
- package/examples/fleet.json +100 -0
- package/examples/roster.json +13 -0
- package/llms.txt +35 -0
- package/mise.lock +1521 -0
- package/mise.toml +110 -0
- package/package.json +74 -4
- package/schema/fleet.schema.json +420 -0
- package/schema/roster.schema.json +77 -0
- package/src/agents/account-relay.ts +111 -0
- package/src/agents/accounts.ts +85 -0
- package/src/agents/audit.ts +77 -0
- package/src/agents/cli.ts +13 -0
- package/src/agents/command.ts +204 -0
- package/src/agents/converge.ts +123 -0
- package/src/agents/doctor.ts +107 -0
- package/src/agents/errors.ts +2 -0
- package/src/agents/files.ts +73 -0
- package/src/agents/fleet-verify.ts +165 -0
- package/src/agents/launch.ts +290 -0
- package/src/agents/native-verify.ts +371 -0
- package/src/agents/provider-errors.ts +57 -0
- package/src/agents/registry.ts +61 -0
- package/src/agents/render.ts +110 -0
- package/src/agents/round-trip.ts +192 -0
- package/src/agents/secrets.ts +13 -0
- package/src/agents/toolchain.ts +34 -0
- package/src/agents/verify-cache.ts +103 -0
- package/src/agents/verify.ts +127 -0
- package/src/auth-checks.ts +163 -0
- package/src/cli/agent-guide.ts +129 -0
- package/src/cli/app.ts +122 -0
- package/src/cli/cli.ts +28 -0
- package/src/cli/command-index.ts +57 -0
- package/src/cli/contracts.ts +25 -0
- package/src/cli/fleet-reference.ts +17 -0
- package/src/cli/fleet-state.ts +499 -0
- package/src/cli/fleet-sync-checkout.ts +130 -0
- package/src/cli/fleet-sync-enrollment.ts +140 -0
- package/src/cli/fleet-sync-forward.ts +134 -0
- package/src/cli/fleet-sync-manager.ts +249 -0
- package/src/cli/fleet-sync-monitoring.ts +185 -0
- package/src/cli/fleet-sync-output.ts +317 -0
- package/src/cli/fleet-sync-privilege.ts +27 -0
- package/src/cli/fleet-sync-progress.ts +109 -0
- package/src/cli/fleet-sync-remote.ts +242 -0
- package/src/cli/fleet-sync-report.ts +26 -0
- package/src/cli/fleet-sync-schedule.ts +138 -0
- package/src/cli/fleet-sync-state.ts +86 -0
- package/src/cli/fleet-sync-vault.ts +166 -0
- package/src/cli/fleet-sync.ts +375 -0
- package/src/cli/fleet.ts +165 -0
- package/src/cli/managed-health.ts +219 -0
- package/src/cli/node-deployment.ts +34 -0
- package/src/cli/node-sync.ts +121 -0
- package/src/cli/node-t3.ts +31 -0
- package/src/cli/options.ts +141 -0
- package/src/cli/probe-source.ts +7 -0
- package/src/cli/program.ts +185 -0
- package/src/cli/project.ts +51 -0
- package/src/cli/register-fleet.ts +296 -0
- package/src/cli/registry.ts +185 -0
- package/src/cli/repair-apply.ts +67 -0
- package/src/cli/repair-guard.ts +11 -0
- package/src/cli/repair-inspect.ts +150 -0
- package/src/cli/repair-script.ts +76 -0
- package/src/cli/repair.ts +209 -0
- package/src/cli/roster.ts +105 -0
- package/src/cli/search.ts +35 -0
- package/src/cli/settings.ts +67 -0
- package/src/cli/source-operations.ts +102 -0
- package/src/cli/t3/command.ts +90 -0
- package/src/cli/t3/register.ts +55 -0
- package/src/cli/tailnet.ts +127 -0
- package/src/cli/ui-actions.ts +133 -0
- package/src/cli/ui-controller.ts +113 -0
- package/src/cli/ui-data.ts +106 -0
- package/src/cli/ui-execute.ts +163 -0
- package/src/cli/ui-repairs.ts +34 -0
- package/src/cli/ui-reports.ts +211 -0
- package/src/cli/ui-targets.ts +100 -0
- package/src/cli/ui-terminal.ts +109 -0
- package/src/cli/ui-types.ts +49 -0
- package/src/cli/ui-wizard.ts +101 -0
- package/src/cli/ui.ts +9 -0
- package/src/cli/worker-health-output.ts +37 -0
- package/src/config.ts +186 -0
- package/src/configure.ts +255 -0
- package/src/definition.ts +290 -0
- package/src/diagnostics.ts +92 -0
- package/src/doctor-check.ts +10 -0
- package/src/errors.ts +1 -0
- package/src/fingerprint.ts +120 -0
- package/src/fleet-config.ts +174 -0
- package/src/input-hashes.ts +73 -0
- package/src/machine/local-source.ts +52 -0
- package/src/machine/lock-error.ts +2 -0
- package/src/machine/platform.ts +44 -0
- package/src/machine/source.ts +68 -0
- package/src/machine/state.ts +173 -0
- package/src/machine-health.ts +95 -0
- package/src/managed-source.ts +47 -0
- package/src/manager-failure.ts +48 -0
- package/src/manager-sync.ts +405 -0
- package/src/manager.ts +62 -0
- package/src/monitoring/agent-evidence.ts +146 -0
- package/src/monitoring/apply.ts +183 -0
- package/src/monitoring/beszel.ts +198 -0
- package/src/monitoring/enroll.ts +111 -0
- package/src/monitoring/hub-access.ts +114 -0
- package/src/monitoring/hub-backup.ts +121 -0
- package/src/monitoring/hub-viewer.ts +142 -0
- package/src/monitoring/hub.ts +93 -0
- package/src/monitoring/manager-agent.ts +73 -0
- package/src/monitoring/manager-hub.ts +248 -0
- package/src/monitoring/remote.ts +65 -0
- package/src/network-definition.ts +83 -0
- package/src/node-converge-linux.ts +374 -0
- package/src/node-converge-shared.ts +83 -0
- package/src/node-converge.ts +40 -0
- package/src/onboard.ts +35 -0
- package/src/package-info.ts +45 -0
- package/src/platform/ansible-receipt.ts +128 -0
- package/src/platform/ansible.ts +66 -0
- package/src/platform/darwin.ts +203 -0
- package/src/platform/detect.ts +11 -0
- package/src/platform/fallback.ts +110 -0
- package/src/platform/index.ts +38 -0
- package/src/platform/launchd.ts +98 -0
- package/src/platform/linux.ts +139 -0
- package/src/platform/ps.ts +45 -0
- package/src/platform/resource-verify.ts +132 -0
- package/src/platform/systemd-user.ts +95 -0
- package/src/platform/t3-policy.ts +137 -0
- package/src/platform/t3-route.ts +68 -0
- package/src/platform/t3-service.ts +75 -0
- package/src/platform/types.ts +123 -0
- package/src/platform/ubuntu-audit.ts +66 -0
- package/src/platform/ubuntu-verify.ts +105 -0
- package/src/platform/windows.ts +148 -0
- package/src/power-recovery.ts +55 -0
- package/src/probe.ts +51 -0
- package/src/protocol.ts +119 -0
- package/src/proxy-converge.ts +85 -0
- package/src/remote-bootstrap.ts +210 -0
- package/src/remote-entry.ts +89 -0
- package/src/remote-lock.ts +12 -0
- package/src/repair-catalog.ts +206 -0
- package/src/repair-plan.ts +78 -0
- package/src/repair-runner.ts +84 -0
- package/src/report.ts +71 -0
- package/src/runner.ts +104 -0
- package/src/secrets.ts +169 -0
- package/src/service-auth.ts +115 -0
- package/src/ssh.ts +135 -0
- package/src/state/bucket.ts +208 -0
- package/src/state/check.ts +74 -0
- package/src/state/connection.ts +92 -0
- package/src/state/enrollment.ts +45 -0
- package/src/state/legacy.ts +89 -0
- package/src/state/litestream.ts +143 -0
- package/src/state/operator.ts +20 -0
- package/src/state/paths.ts +40 -0
- package/src/state/roster.ts +62 -0
- package/src/state/store.ts +169 -0
- package/src/state/sync.ts +352 -0
- package/src/sync.ts +104 -0
- package/src/t3/accounts.ts +81 -0
- package/src/t3/desired.ts +165 -0
- package/src/t3/files.ts +81 -0
- package/src/t3/names-check.ts +53 -0
- package/src/t3/names.ts +155 -0
- package/src/t3/plan.ts +82 -0
- package/src/t3/providers.ts +74 -0
- package/src/t3/reconcile.ts +123 -0
- package/src/t3-endpoint.ts +104 -0
- package/src/t3-linked-check.ts +123 -0
- package/src/tailnet-health.ts +231 -0
- package/src/tailnet-matching.ts +37 -0
- package/src/tailscale.ts +141 -0
- package/src/toolchain-checks.ts +108 -0
- package/src/topology.ts +24 -0
- package/src/transport.ts +122 -0
- package/src/types.ts +99 -0
- package/src/vault-config.ts +38 -0
- package/src/worker-access-checks.ts +168 -0
- package/src/worker-capacity.ts +34 -0
- package/src/worker-host-checks.ts +181 -0
- package/src/worker-policy.ts +64 -0
- package/src/worker-readiness.ts +64 -0
- package/src/workstation-checks.ts +29 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Manager LaunchAgent Python dependencies (hash-locked). Regenerate with:
|
|
2
|
+
# echo pyyaml==VERSION | uv pip compile - --generate-hashes --universal --no-header
|
|
3
|
+
|
|
4
|
+
pyyaml==6.0.3 \
|
|
5
|
+
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
|
|
6
|
+
--hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \
|
|
7
|
+
--hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \
|
|
8
|
+
--hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \
|
|
9
|
+
--hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \
|
|
10
|
+
--hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \
|
|
11
|
+
--hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \
|
|
12
|
+
--hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \
|
|
13
|
+
--hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \
|
|
14
|
+
--hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \
|
|
15
|
+
--hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \
|
|
16
|
+
--hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \
|
|
17
|
+
--hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \
|
|
18
|
+
--hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \
|
|
19
|
+
--hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \
|
|
20
|
+
--hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \
|
|
21
|
+
--hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \
|
|
22
|
+
--hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \
|
|
23
|
+
--hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \
|
|
24
|
+
--hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \
|
|
25
|
+
--hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \
|
|
26
|
+
--hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \
|
|
27
|
+
--hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \
|
|
28
|
+
--hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \
|
|
29
|
+
--hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \
|
|
30
|
+
--hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \
|
|
31
|
+
--hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \
|
|
32
|
+
--hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \
|
|
33
|
+
--hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \
|
|
34
|
+
--hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \
|
|
35
|
+
--hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \
|
|
36
|
+
--hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \
|
|
37
|
+
--hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \
|
|
38
|
+
--hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \
|
|
39
|
+
--hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \
|
|
40
|
+
--hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \
|
|
41
|
+
--hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \
|
|
42
|
+
--hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \
|
|
43
|
+
--hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \
|
|
44
|
+
--hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \
|
|
45
|
+
--hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \
|
|
46
|
+
--hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \
|
|
47
|
+
--hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \
|
|
48
|
+
--hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \
|
|
49
|
+
--hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \
|
|
50
|
+
--hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \
|
|
51
|
+
--hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \
|
|
52
|
+
--hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \
|
|
53
|
+
--hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \
|
|
54
|
+
--hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \
|
|
55
|
+
--hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \
|
|
56
|
+
--hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \
|
|
57
|
+
--hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \
|
|
58
|
+
--hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \
|
|
59
|
+
--hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \
|
|
60
|
+
--hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \
|
|
61
|
+
--hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \
|
|
62
|
+
--hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \
|
|
63
|
+
--hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \
|
|
64
|
+
--hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \
|
|
65
|
+
--hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \
|
|
66
|
+
--hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \
|
|
67
|
+
--hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \
|
|
68
|
+
--hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \
|
|
69
|
+
--hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \
|
|
70
|
+
--hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \
|
|
71
|
+
--hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \
|
|
72
|
+
--hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \
|
|
73
|
+
--hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \
|
|
74
|
+
--hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \
|
|
75
|
+
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \
|
|
76
|
+
--hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \
|
|
77
|
+
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
|
|
78
|
+
# via -r requirements.in
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Account routing: each signed-in provider account gets its own model prefix.
|
|
2
|
+
|
|
3
|
+
The proxy's credential files are the only source of truth for accounts. A
|
|
4
|
+
credential of a routed type is given the prefix `<type>-<email>`, so a client
|
|
5
|
+
that asks for `<type>-<email>/<model>` is served only by that account, and the
|
|
6
|
+
proxy's model list tells every client which accounts exist. Unprefixed models
|
|
7
|
+
keep using the whole fill-first pool.
|
|
8
|
+
"""
|
|
9
|
+
import json
|
|
10
|
+
import pathlib
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
# Provider credential types that T3 offers once per account.
|
|
14
|
+
ROUTED = ('claude', 'codex', 'antigravity')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def prefix(kind, email):
|
|
18
|
+
"""The model prefix for one credential, or None when it is not routed."""
|
|
19
|
+
if kind in ROUTED and isinstance(email, str) and re.fullmatch(r'[^\s@/]{1,64}@[^\s@/]{1,255}', email):
|
|
20
|
+
return kind + '-' + email
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def pending(auths):
|
|
25
|
+
"""[(credential file name, prefix)] for credentials without their prefix.
|
|
26
|
+
Reads only type, email and prefix; never a token."""
|
|
27
|
+
result = []
|
|
28
|
+
for path in sorted(pathlib.Path(auths).glob('*.json')):
|
|
29
|
+
try:
|
|
30
|
+
data = json.loads(path.read_text())
|
|
31
|
+
except (OSError, ValueError):
|
|
32
|
+
continue
|
|
33
|
+
if not isinstance(data, dict):
|
|
34
|
+
continue
|
|
35
|
+
wanted = prefix(data.get('type'), data.get('email'))
|
|
36
|
+
if wanted and data.get('prefix') != wanted:
|
|
37
|
+
result.append((path.name, wanted))
|
|
38
|
+
return result
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Require runtime code and unit links to resolve to clean, Git-tracked files."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
sys.dont_write_bytecode = True
|
|
7
|
+
|
|
8
|
+
import pathlib as _pathlib
|
|
9
|
+
|
|
10
|
+
sys.path.insert(0, str(_pathlib.Path(__file__).resolve().parent))
|
|
11
|
+
import cliproxy_host
|
|
12
|
+
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import pathlib
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def quiet(args):
|
|
22
|
+
try:
|
|
23
|
+
return (
|
|
24
|
+
subprocess.run(
|
|
25
|
+
args, capture_output=True, timeout=30, check=False
|
|
26
|
+
).returncode
|
|
27
|
+
== 0
|
|
28
|
+
)
|
|
29
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def audit(root, check_units=True, backend=None, run=quiet):
|
|
34
|
+
"""Check provenance; with check_units, also the platform's unit links (and,
|
|
35
|
+
for launchd, that the required agents are loaded in gui/$UID)."""
|
|
36
|
+
manifest = json.loads((root / "INSTALLATION.json").read_text())
|
|
37
|
+
errors = []
|
|
38
|
+
if manifest.get("schema_version") != 2:
|
|
39
|
+
return manifest, [
|
|
40
|
+
"legacy copied-code installation; reinstall from the repository"
|
|
41
|
+
]
|
|
42
|
+
worktree = pathlib.Path(manifest["worktree"])
|
|
43
|
+
command = [
|
|
44
|
+
"git",
|
|
45
|
+
"--no-optional-locks",
|
|
46
|
+
"-c",
|
|
47
|
+
"safe.directory=" + str(worktree),
|
|
48
|
+
"-C",
|
|
49
|
+
str(worktree),
|
|
50
|
+
]
|
|
51
|
+
revision = (
|
|
52
|
+
subprocess.check_output(command + ["rev-parse", "HEAD"], stderr=subprocess.PIPE)
|
|
53
|
+
.decode()
|
|
54
|
+
.strip()
|
|
55
|
+
)
|
|
56
|
+
tracked = set(
|
|
57
|
+
subprocess.check_output(
|
|
58
|
+
command + ["ls-files", "-z", "--", manifest["package"]],
|
|
59
|
+
stderr=subprocess.PIPE,
|
|
60
|
+
)
|
|
61
|
+
.decode()
|
|
62
|
+
.strip("\0")
|
|
63
|
+
.split("\0")
|
|
64
|
+
)
|
|
65
|
+
if revision != manifest["revision"]:
|
|
66
|
+
errors.append("deployment worktree revision")
|
|
67
|
+
if subprocess.check_output(
|
|
68
|
+
command + ["status", "--porcelain", "--", manifest["package"]],
|
|
69
|
+
stderr=subprocess.PIPE,
|
|
70
|
+
).strip():
|
|
71
|
+
errors.append("deployment worktree has local changes")
|
|
72
|
+
# Resolve once: macOS temporary/home paths may route through symlinks.
|
|
73
|
+
source = worktree.resolve() / manifest["package"]
|
|
74
|
+
units = (backend or cliproxy_host.backend()) if check_units else None
|
|
75
|
+
for name, expected in manifest["files"].items():
|
|
76
|
+
path = root / name
|
|
77
|
+
if (
|
|
78
|
+
manifest["package"] + "/" + name not in tracked
|
|
79
|
+
or not path.is_file()
|
|
80
|
+
or path.resolve() != source / name
|
|
81
|
+
or hashlib.sha256(path.read_bytes()).hexdigest() != expected
|
|
82
|
+
):
|
|
83
|
+
errors.append(name)
|
|
84
|
+
if units and name.startswith(units.source_directory + "/"):
|
|
85
|
+
unit = units.unit_directory / path.name
|
|
86
|
+
if not unit.is_symlink() or unit.resolve() != source / name:
|
|
87
|
+
errors.append(str(unit))
|
|
88
|
+
for name, expected in manifest["generated"].items():
|
|
89
|
+
if hashlib.sha256((root / name).read_bytes()).hexdigest() != expected:
|
|
90
|
+
errors.append(name)
|
|
91
|
+
if units and units.name == "launchd":
|
|
92
|
+
required = [units.service, *units.scheduled]
|
|
93
|
+
if not (root / "state/secrets/recovery-confirmed").exists():
|
|
94
|
+
required = [label for label in required if label not in units.recovery_gated]
|
|
95
|
+
for label in required:
|
|
96
|
+
if not units.loaded(label, run):
|
|
97
|
+
errors.append("not loaded: " + units.domain + "/" + label)
|
|
98
|
+
for folder in ["scripts", "clients", "systemd", "launchd"]:
|
|
99
|
+
for path in (root / folder).rglob("*"):
|
|
100
|
+
if (
|
|
101
|
+
(root / folder).is_dir()
|
|
102
|
+
and path.is_file()
|
|
103
|
+
and "__pycache__" not in path.parts
|
|
104
|
+
and str(path.relative_to(root)) not in manifest["files"]
|
|
105
|
+
):
|
|
106
|
+
errors.append("unmanaged: " + str(path.relative_to(root)))
|
|
107
|
+
return manifest, errors
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
try:
|
|
112
|
+
manifest, errors = audit(cliproxy_host.default_root())
|
|
113
|
+
if errors:
|
|
114
|
+
print("Code is not clean tracked repository source: " + ", ".join(errors))
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
print(
|
|
117
|
+
"Runtime code is Git-tracked at crvouga/dev-fleet commit "
|
|
118
|
+
+ manifest["revision"]
|
|
119
|
+
)
|
|
120
|
+
except (OSError, ValueError, KeyError, subprocess.CalledProcessError):
|
|
121
|
+
sys.exit(
|
|
122
|
+
"Git source unavailable or provenance invalid; reinstall from the repository."
|
|
123
|
+
)
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
sys.dont_write_bytecode = True
|
|
5
|
+
|
|
6
|
+
import pathlib as _pathlib
|
|
7
|
+
|
|
8
|
+
sys.path.insert(0, str(_pathlib.Path(__file__).resolve().parent))
|
|
9
|
+
import cliproxy_host
|
|
10
|
+
|
|
11
|
+
import datetime, json, os, pathlib, subprocess, sys, tempfile, fcntl, importlib.util
|
|
12
|
+
import checkpoint
|
|
13
|
+
import retention
|
|
14
|
+
|
|
15
|
+
root = cliproxy_host.default_root().resolve()
|
|
16
|
+
os.umask(0o077)
|
|
17
|
+
os.environ["PATH"] = str(root / "scripts") + ":" + os.environ["PATH"]
|
|
18
|
+
state = root / "state"
|
|
19
|
+
status = state / "status"
|
|
20
|
+
status.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
lock = open(status / "operation.lock", "w")
|
|
22
|
+
if os.geteuid() == 0:
|
|
23
|
+
os.chown(status / "operation.lock", 1000, 1000)
|
|
24
|
+
fcntl.flock(lock, fcntl.LOCK_EX)
|
|
25
|
+
command = sys.argv[1] if len(sys.argv) > 1 else "backup"
|
|
26
|
+
failure = status / (
|
|
27
|
+
"backup-failed"
|
|
28
|
+
if command == "backup"
|
|
29
|
+
else "backup-check-failed"
|
|
30
|
+
if command in ["check", "check-full"]
|
|
31
|
+
else "gc-failed"
|
|
32
|
+
if command == "gc"
|
|
33
|
+
else "backup-operation-failed"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def run(args, **kw):
|
|
38
|
+
return subprocess.run(args, check=True, **kw)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def write(path, text):
|
|
42
|
+
path.write_text(text)
|
|
43
|
+
if os.geteuid() == 0:
|
|
44
|
+
os.chown(path, 1000, 1000)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
retention.staging(root)
|
|
49
|
+
spec = importlib.util.spec_from_file_location(
|
|
50
|
+
"vault_access", root / "scripts/vault-access.py"
|
|
51
|
+
)
|
|
52
|
+
vault = importlib.util.module_from_spec(spec)
|
|
53
|
+
spec.loader.exec_module(vault)
|
|
54
|
+
vault.renew()
|
|
55
|
+
cfg = vault.credentials()
|
|
56
|
+
identity = {k: cfg[k] for k in ["S3_ENDPOINT", "S3_BUCKET", "S3_REGION"]}
|
|
57
|
+
destination = state / "repository.json"
|
|
58
|
+
if destination.exists() and json.loads(destination.read_text()) != identity:
|
|
59
|
+
sys.exit(
|
|
60
|
+
"Vault repository location changed. Review and update state/repository.json explicitly; refusing to silently switch repositories."
|
|
61
|
+
)
|
|
62
|
+
if not destination.exists() and command != "restore-cache":
|
|
63
|
+
write(destination, json.dumps(identity) + "\n")
|
|
64
|
+
if not cfg["S3_ENDPOINT"].startswith("https://"):
|
|
65
|
+
raise ValueError("Storage endpoint must use TLS")
|
|
66
|
+
env = os.environ.copy()
|
|
67
|
+
env.update(
|
|
68
|
+
AWS_ACCESS_KEY_ID=cfg["S3_ACCESS_KEY_ID"],
|
|
69
|
+
AWS_SECRET_ACCESS_KEY=cfg["S3_SECRET_ACCESS_KEY"],
|
|
70
|
+
AWS_DEFAULT_REGION=cfg["S3_REGION"],
|
|
71
|
+
RESTIC_REPOSITORY="s3:"
|
|
72
|
+
+ cfg["S3_ENDPOINT"].rstrip("/")
|
|
73
|
+
+ "/"
|
|
74
|
+
+ cfg["S3_BUCKET"]
|
|
75
|
+
+ "/cliproxy-fleet/restic",
|
|
76
|
+
RESTIC_PASSWORD_FILE=str(state / "secrets/restic-password"),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def restic(*args, **kw):
|
|
80
|
+
return run(
|
|
81
|
+
["restic", "-o", "s3.bucket-lookup=path", *args, "--no-cache"],
|
|
82
|
+
env=env,
|
|
83
|
+
**kw,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if command == "init":
|
|
87
|
+
restic("init")
|
|
88
|
+
elif command == "check":
|
|
89
|
+
restic("check", "--read-data-subset=10%")
|
|
90
|
+
elif command == "check-full":
|
|
91
|
+
restic("check", "--read-data")
|
|
92
|
+
elif command == "gc":
|
|
93
|
+
retention.prune(restic)
|
|
94
|
+
removed = retention.revisions(root)
|
|
95
|
+
write(
|
|
96
|
+
status / "gc-success.json",
|
|
97
|
+
json.dumps(
|
|
98
|
+
{
|
|
99
|
+
"time": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
100
|
+
"retired_worktrees": removed,
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
+ "\n",
|
|
104
|
+
)
|
|
105
|
+
print(
|
|
106
|
+
"Cloud garbage collected and integrity checked; retired clean worktrees removed:",
|
|
107
|
+
removed,
|
|
108
|
+
)
|
|
109
|
+
elif command == "snapshots":
|
|
110
|
+
restic("snapshots")
|
|
111
|
+
elif command == "restore":
|
|
112
|
+
if len(sys.argv) != 4:
|
|
113
|
+
sys.exit("Usage: backup restore SNAPSHOT EMPTY_DIRECTORY")
|
|
114
|
+
target = pathlib.Path(sys.argv[3]).resolve()
|
|
115
|
+
if target.exists() and any(target.iterdir()):
|
|
116
|
+
sys.exit("Restore target must be empty.")
|
|
117
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
restic("restore", sys.argv[2], "--target", str(target), "--verify")
|
|
119
|
+
elif command == "restore-cache":
|
|
120
|
+
if sys.argv[2:] != ["--previous-node-stopped"]:
|
|
121
|
+
raise ValueError("Explicit previous-node-stopped acknowledgement required")
|
|
122
|
+
# This local check complements operator fencing of the previous machine.
|
|
123
|
+
active = subprocess.check_output(
|
|
124
|
+
[
|
|
125
|
+
"docker",
|
|
126
|
+
"compose",
|
|
127
|
+
"--profile",
|
|
128
|
+
"portable-endpoint",
|
|
129
|
+
"--profile",
|
|
130
|
+
"public-endpoint",
|
|
131
|
+
"ps",
|
|
132
|
+
"--status",
|
|
133
|
+
"running",
|
|
134
|
+
"--services",
|
|
135
|
+
],
|
|
136
|
+
cwd=root,
|
|
137
|
+
text=True,
|
|
138
|
+
).strip()
|
|
139
|
+
if active:
|
|
140
|
+
raise ValueError("Stop the local stack before recovery")
|
|
141
|
+
with tempfile.TemporaryDirectory(
|
|
142
|
+
prefix="restore-", dir=root / "recovery"
|
|
143
|
+
) as temporary:
|
|
144
|
+
target = pathlib.Path(temporary)
|
|
145
|
+
restic(
|
|
146
|
+
"restore",
|
|
147
|
+
"latest",
|
|
148
|
+
"--tag",
|
|
149
|
+
"cliproxy-checkpoint-v1",
|
|
150
|
+
"--host",
|
|
151
|
+
"cliproxy-primary",
|
|
152
|
+
"--target",
|
|
153
|
+
str(target),
|
|
154
|
+
"--verify",
|
|
155
|
+
stdout=subprocess.PIPE,
|
|
156
|
+
stderr=subprocess.PIPE,
|
|
157
|
+
)
|
|
158
|
+
payload = checkpoint.locate(target)
|
|
159
|
+
saved_destination = payload / "state/repository.json"
|
|
160
|
+
if (
|
|
161
|
+
not saved_destination.exists()
|
|
162
|
+
or json.loads(saved_destination.read_text()) != identity
|
|
163
|
+
):
|
|
164
|
+
raise ValueError("Recovered repository identity differs from Vault")
|
|
165
|
+
checkpoint.hydrate(payload, state)
|
|
166
|
+
print(
|
|
167
|
+
"Cloud checkpoint recovered into empty cache. No services started. Run install, then health verification."
|
|
168
|
+
)
|
|
169
|
+
elif command == "backup":
|
|
170
|
+
if not (state / "secrets/recovery-confirmed").exists():
|
|
171
|
+
sys.exit(
|
|
172
|
+
"Store Restic password independently; then create state/secrets/recovery-confirmed."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
recovery = root / "recovery"
|
|
176
|
+
recovery.mkdir(mode=0o700, exist_ok=True)
|
|
177
|
+
if os.geteuid() == 0:
|
|
178
|
+
os.chown(recovery, 1000, 1000)
|
|
179
|
+
with tempfile.TemporaryDirectory(
|
|
180
|
+
prefix="checkpoint-", dir=recovery
|
|
181
|
+
) as temporary:
|
|
182
|
+
captured = pathlib.Path(temporary) / "payload"
|
|
183
|
+
# The stack keeps running: a live, verified-stable copy.
|
|
184
|
+
checkpoint.capture(root, captured)
|
|
185
|
+
result = restic(
|
|
186
|
+
"backup",
|
|
187
|
+
"--json",
|
|
188
|
+
str(captured),
|
|
189
|
+
"--tag",
|
|
190
|
+
"cliproxy-fleet",
|
|
191
|
+
"--tag",
|
|
192
|
+
"cliproxy-checkpoint-v1",
|
|
193
|
+
"--host",
|
|
194
|
+
"cliproxy-primary",
|
|
195
|
+
stdout=subprocess.PIPE,
|
|
196
|
+
stderr=subprocess.PIPE,
|
|
197
|
+
text=True,
|
|
198
|
+
)
|
|
199
|
+
summaries = [json.loads(line) for line in result.stdout.splitlines()]
|
|
200
|
+
summary = next(
|
|
201
|
+
item
|
|
202
|
+
for item in reversed(summaries)
|
|
203
|
+
if item.get("message_type") == "summary"
|
|
204
|
+
)
|
|
205
|
+
snapshot = summary["snapshot_id"]
|
|
206
|
+
if not isinstance(snapshot, str) or not snapshot:
|
|
207
|
+
raise ValueError("Missing snapshot acknowledgement")
|
|
208
|
+
print("Complete encrypted checkpoint saved:", snapshot)
|
|
209
|
+
retention.forget(restic)
|
|
210
|
+
write(
|
|
211
|
+
status / "backup-success.json",
|
|
212
|
+
json.dumps(
|
|
213
|
+
{
|
|
214
|
+
"snapshot": snapshot,
|
|
215
|
+
"time": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
216
|
+
"destination": "configured R2 bucket / cliproxy-fleet/restic",
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
+ "\n",
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
sys.exit(
|
|
223
|
+
"Use init, backup, gc, snapshots, check, check-full, restore SNAPSHOT EMPTY_DIRECTORY, or restore-cache --previous-node-stopped."
|
|
224
|
+
)
|
|
225
|
+
except (Exception, SystemExit):
|
|
226
|
+
write(failure, datetime.datetime.now(datetime.timezone.utc).isoformat() + "\n")
|
|
227
|
+
sys.exit(
|
|
228
|
+
"Backup operation failed. Inspect systemd unit status; credentials suppressed."
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
failure.unlink(missing_ok=True)
|
|
232
|
+
|
|
233
|
+
finally:
|
|
234
|
+
lock.close()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Offline syntax/config checks; no cloud access or running deployment required."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
sys.dont_write_bytecode = True
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import ast
|
|
10
|
+
import json
|
|
11
|
+
import pathlib
|
|
12
|
+
import re
|
|
13
|
+
import subprocess
|
|
14
|
+
|
|
15
|
+
root = pathlib.Path(__file__).resolve().parent.parent
|
|
16
|
+
for directory in ["scripts", "clients", "tests"]:
|
|
17
|
+
for path in (root / directory).rglob("*"):
|
|
18
|
+
if not path.is_file() or "__pycache__" in path.parts:
|
|
19
|
+
continue
|
|
20
|
+
data = path.read_text()
|
|
21
|
+
if path.suffix == ".py" or data.startswith("#!/usr/bin/env python3"):
|
|
22
|
+
ast.parse(data, filename=str(path))
|
|
23
|
+
elif data.startswith("#!/usr/bin/env bash") or data.startswith("#!/bin/bash"):
|
|
24
|
+
subprocess.run(["bash", "-n", str(path)], check=True)
|
|
25
|
+
images = json.loads((root / "image.lock.json").read_text())
|
|
26
|
+
assert all(
|
|
27
|
+
re.fullmatch(r"[a-z0-9/.-]+@sha256:[0-9a-f]{64}", image)
|
|
28
|
+
for image in images.values()
|
|
29
|
+
)
|
|
30
|
+
sys.path.insert(0, str(root / "scripts"))
|
|
31
|
+
from modelhealth import config as model_config
|
|
32
|
+
|
|
33
|
+
model_config.load(root / "models.json") # declarative model-health contract
|
|
34
|
+
print("Python/shell syntax, immutable image pins and models.json passed.")
|