@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,406 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Install only committed package files, preserving cloud-backed runtime state."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
sys.dont_write_bytecode = True
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import fcntl
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import pathlib
|
|
15
|
+
import re
|
|
16
|
+
import subprocess
|
|
17
|
+
import shutil
|
|
18
|
+
|
|
19
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
|
|
20
|
+
import cliproxy_host
|
|
21
|
+
|
|
22
|
+
PACKAGE = "cliproxy"
|
|
23
|
+
REPOSITORY = "https://github.com/crvouga/dev-fleet.git"
|
|
24
|
+
LEGACY_FILES = [
|
|
25
|
+
"scripts/tailscale-admin",
|
|
26
|
+
"scripts/install-system",
|
|
27
|
+
"VAULT-REQUEST.md",
|
|
28
|
+
"VERIFICATION.md",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def git(repo, *args):
|
|
33
|
+
return subprocess.check_output(
|
|
34
|
+
["git", "-c", "safe.directory=" + str(repo), "-C", str(repo), *args],
|
|
35
|
+
stderr=subprocess.PIPE,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def source_files(repo, revision):
|
|
40
|
+
commit = git(repo, "rev-parse", "--verify", revision + "^{commit}").decode().strip()
|
|
41
|
+
records = git(repo, "ls-tree", "-rz", commit, "--", PACKAGE).split(b"\0")
|
|
42
|
+
files = {}
|
|
43
|
+
for record in records:
|
|
44
|
+
if not record:
|
|
45
|
+
continue
|
|
46
|
+
metadata, filename = record.split(b"\t", 1)
|
|
47
|
+
mode, kind, _ = metadata.decode().split()
|
|
48
|
+
name = pathlib.PurePosixPath(filename.decode()).relative_to(PACKAGE)
|
|
49
|
+
if kind != "blob" or mode not in ["100644", "100755"] or ".." in name.parts:
|
|
50
|
+
raise ValueError("Package must contain regular tracked files only")
|
|
51
|
+
if name.parts[0] == "state":
|
|
52
|
+
raise ValueError("Runtime state must never be tracked")
|
|
53
|
+
files[str(name)] = (
|
|
54
|
+
0o755 if mode == "100755" else 0o644,
|
|
55
|
+
git(repo, "show", commit + ":" + filename.decode()),
|
|
56
|
+
)
|
|
57
|
+
if "compose.yaml" not in files or "scripts/install.py" not in files:
|
|
58
|
+
raise ValueError("Selected revision does not contain the deployment package")
|
|
59
|
+
return commit, files
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def digest(data):
|
|
63
|
+
return hashlib.sha256(data).hexdigest()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def image_environment(files, ids=None):
|
|
67
|
+
"""Image pins; on the macOS manager also the installing user's IDs for Compose."""
|
|
68
|
+
images = json.loads(files["image.lock.json"][1])
|
|
69
|
+
mapping = {
|
|
70
|
+
"CLI_PROXY_IMAGE": images["eceasy/cli-proxy-api"],
|
|
71
|
+
"TAILSCALE_IMAGE": images["tailscale/tailscale"],
|
|
72
|
+
**(
|
|
73
|
+
{"CLOUDFLARED_IMAGE": images["cloudflare/cloudflared"]}
|
|
74
|
+
if "cloudflare/cloudflared" in images
|
|
75
|
+
else {}
|
|
76
|
+
),
|
|
77
|
+
}
|
|
78
|
+
if not all(
|
|
79
|
+
re.fullmatch(r"[a-z0-9/.-]+@sha256:[0-9a-f]{64}", value)
|
|
80
|
+
for value in mapping.values()
|
|
81
|
+
):
|
|
82
|
+
raise ValueError("Upstream images must use immutable digests")
|
|
83
|
+
if ids is not None:
|
|
84
|
+
mapping["CLIPROXY_UID"], mapping["CLIPROXY_GID"] = (str(value) for value in ids)
|
|
85
|
+
return "".join(
|
|
86
|
+
name + "=" + value + "\n" for name, value in mapping.items()
|
|
87
|
+
).encode()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def atomic_write(path, data, mode):
|
|
91
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
temporary = path.with_name(path.name + ".install-new")
|
|
93
|
+
temporary.write_bytes(data)
|
|
94
|
+
temporary.chmod(mode)
|
|
95
|
+
os.replace(temporary, path)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def prepare_source(repo, target, commit):
|
|
99
|
+
worktree = target / "revisions" / commit
|
|
100
|
+
if not worktree.exists():
|
|
101
|
+
worktree.parent.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
git(repo, "worktree", "add", "--detach", str(worktree), commit)
|
|
103
|
+
git(worktree, "sparse-checkout", "set", "--no-cone", "/" + PACKAGE + "/")
|
|
104
|
+
if git(worktree, "rev-parse", "HEAD").decode().strip() != commit:
|
|
105
|
+
raise ValueError("Deployment worktree is at the wrong revision")
|
|
106
|
+
if git(worktree, "status", "--porcelain", "--", PACKAGE).strip():
|
|
107
|
+
raise ValueError("Deployment worktree has local changes")
|
|
108
|
+
# Git creates a private index under sudo; normal users need read-only audits.
|
|
109
|
+
metadata = pathlib.Path(
|
|
110
|
+
git(worktree, "rev-parse", "--absolute-git-dir").decode().strip()
|
|
111
|
+
)
|
|
112
|
+
(metadata / "index").chmod(0o644)
|
|
113
|
+
restore_repository_ownership(repo, metadata)
|
|
114
|
+
return worktree
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def restore_repository_ownership(repo, metadata):
|
|
118
|
+
"""Worktree creation under sudo writes into the source repository's .git
|
|
119
|
+
(worktrees/<sha>/*, config for sparse checkout). Hand those files back to the
|
|
120
|
+
repository owner so its user can keep running git and read-only audits."""
|
|
121
|
+
owner = pathlib.Path(repo).stat()
|
|
122
|
+
if owner.st_uid == 0 or os.geteuid() != 0:
|
|
123
|
+
return
|
|
124
|
+
common = pathlib.Path(
|
|
125
|
+
git(repo, "rev-parse", "--git-common-dir").decode().strip()
|
|
126
|
+
)
|
|
127
|
+
if not common.is_absolute():
|
|
128
|
+
common = pathlib.Path(repo) / common
|
|
129
|
+
# The worktrees directory itself is created by the first `git worktree add`.
|
|
130
|
+
paths = [common / "config", metadata.parent, metadata]
|
|
131
|
+
for base in paths:
|
|
132
|
+
if not base.exists():
|
|
133
|
+
continue
|
|
134
|
+
targets = [base]
|
|
135
|
+
if base.is_dir() and base != metadata.parent:
|
|
136
|
+
targets.extend(base.rglob("*"))
|
|
137
|
+
for path in targets:
|
|
138
|
+
if path.is_symlink():
|
|
139
|
+
continue
|
|
140
|
+
os.chown(path, owner.st_uid, owner.st_gid)
|
|
141
|
+
mode = path.stat().st_mode & 0o777
|
|
142
|
+
path.chmod(mode | (0o755 if path.is_dir() else 0o644))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def link_path(path, destination):
|
|
146
|
+
temporary = path.with_name(path.name + ".install-link")
|
|
147
|
+
if temporary.is_symlink():
|
|
148
|
+
temporary.unlink()
|
|
149
|
+
temporary.symlink_to(destination)
|
|
150
|
+
if path.is_dir() and not path.is_symlink():
|
|
151
|
+
retired = path.with_name(path.name + ".retired-code")
|
|
152
|
+
if retired.exists():
|
|
153
|
+
raise ValueError("Previous retired directory needs review")
|
|
154
|
+
path.rename(retired)
|
|
155
|
+
os.replace(temporary, path)
|
|
156
|
+
shutil.rmtree(retired)
|
|
157
|
+
else:
|
|
158
|
+
os.replace(temporary, path)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def materialize(target, commit, files, worktree, ids=None):
|
|
162
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
163
|
+
manifest_file = target / "INSTALLATION.json"
|
|
164
|
+
previous = json.loads(manifest_file.read_text()) if manifest_file.exists() else None
|
|
165
|
+
if (
|
|
166
|
+
previous
|
|
167
|
+
and previous.get("schema_version") == 2
|
|
168
|
+
and previous["revision"] != commit
|
|
169
|
+
):
|
|
170
|
+
atomic_write(
|
|
171
|
+
target / "INSTALLATION.previous.json", manifest_file.read_bytes(), 0o644
|
|
172
|
+
)
|
|
173
|
+
stale = set(previous["files"] if previous else LEGACY_FILES) - set(files)
|
|
174
|
+
# Do not mutate old tracked worktrees through their symlinks.
|
|
175
|
+
for name in stale:
|
|
176
|
+
relative = pathlib.PurePosixPath(name)
|
|
177
|
+
if (
|
|
178
|
+
relative.is_absolute()
|
|
179
|
+
or ".." in relative.parts
|
|
180
|
+
or relative.parts[0] == "state"
|
|
181
|
+
):
|
|
182
|
+
raise ValueError("Unsafe managed manifest entry")
|
|
183
|
+
if (target / relative.parts[0]).is_symlink():
|
|
184
|
+
continue
|
|
185
|
+
file = target / name
|
|
186
|
+
if file.is_file() or file.is_symlink():
|
|
187
|
+
file.unlink()
|
|
188
|
+
source = worktree / PACKAGE
|
|
189
|
+
for name in sorted({pathlib.PurePosixPath(name).parts[0] for name in files}):
|
|
190
|
+
link_path(target / name, source / name)
|
|
191
|
+
environment = image_environment(files, ids)
|
|
192
|
+
atomic_write(target / ".env", environment, 0o644)
|
|
193
|
+
manifest = {
|
|
194
|
+
"schema_version": 2,
|
|
195
|
+
"repository": REPOSITORY,
|
|
196
|
+
"package": PACKAGE,
|
|
197
|
+
"revision": commit,
|
|
198
|
+
"worktree": str(worktree),
|
|
199
|
+
"files": {name: digest(data) for name, (_, data) in sorted(files.items())},
|
|
200
|
+
"generated": {".env": digest(environment)},
|
|
201
|
+
}
|
|
202
|
+
atomic_write(manifest_file, (json.dumps(manifest, indent=2) + "\n").encode(), 0o644)
|
|
203
|
+
return manifest
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def prepare_root(target, ids):
|
|
207
|
+
"""Private state directories; the legacy Linux host hands them to UID 1000."""
|
|
208
|
+
for directory in [
|
|
209
|
+
target / "recovery",
|
|
210
|
+
target / "state",
|
|
211
|
+
target / "state/secrets",
|
|
212
|
+
target / "state/status",
|
|
213
|
+
]:
|
|
214
|
+
directory.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
215
|
+
directory.chmod(0o700)
|
|
216
|
+
if os.geteuid() == 0:
|
|
217
|
+
os.chown(directory, *ids)
|
|
218
|
+
status = target / "state/status"
|
|
219
|
+
operation_lock = open(status / "operation.lock", "a")
|
|
220
|
+
if os.geteuid() == 0:
|
|
221
|
+
os.chown(status / "operation.lock", *ids)
|
|
222
|
+
return operation_lock
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def link_units(target, backend):
|
|
226
|
+
"""Link tracked unit files (never rendered copies) into the backend's directory."""
|
|
227
|
+
prefix, suffixes = (
|
|
228
|
+
("cliproxy-", [".service", ".timer"])
|
|
229
|
+
if backend.name == "systemd"
|
|
230
|
+
else ("dev.devfleet.cliproxy-", [".plist"])
|
|
231
|
+
)
|
|
232
|
+
backend.unit_directory.mkdir(parents=True, exist_ok=True)
|
|
233
|
+
for file in (target / backend.source_directory).iterdir():
|
|
234
|
+
if not file.name.startswith(prefix) or file.suffix not in suffixes:
|
|
235
|
+
raise ValueError("Unexpected unit name")
|
|
236
|
+
link_path(backend.unit_directory / file.name, file.resolve())
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def start_systemd(target, environment, operation_lock):
|
|
240
|
+
"""Legacy Linux owner; retained for fencing and decommissioning only."""
|
|
241
|
+
subprocess.run(["systemctl", "daemon-reload"], check=True)
|
|
242
|
+
subprocess.run(["systemctl", "enable", "--now", "docker.service"], check=True)
|
|
243
|
+
subprocess.run(
|
|
244
|
+
[
|
|
245
|
+
"systemctl",
|
|
246
|
+
"enable",
|
|
247
|
+
"cliproxy-fleet.service",
|
|
248
|
+
"cliproxy-monitor.timer",
|
|
249
|
+
"cliproxy-vault-renew.timer",
|
|
250
|
+
],
|
|
251
|
+
check=True,
|
|
252
|
+
)
|
|
253
|
+
# Startup/stop share the operation lock with checkpoints and recovery.
|
|
254
|
+
fcntl.flock(operation_lock, fcntl.LOCK_UN)
|
|
255
|
+
subprocess.run(["systemctl", "restart", "cliproxy-fleet.service"], check=True)
|
|
256
|
+
subprocess.run(
|
|
257
|
+
["systemctl", "start", "cliproxy-monitor.timer", "cliproxy-vault-renew.timer"],
|
|
258
|
+
check=True,
|
|
259
|
+
)
|
|
260
|
+
if (target / "state/secrets/recovery-confirmed").exists():
|
|
261
|
+
subprocess.run(
|
|
262
|
+
[
|
|
263
|
+
"systemctl",
|
|
264
|
+
"enable",
|
|
265
|
+
"--now",
|
|
266
|
+
"cliproxy-backup.timer",
|
|
267
|
+
"cliproxy-backup-check.timer",
|
|
268
|
+
"cliproxy-gc.timer",
|
|
269
|
+
],
|
|
270
|
+
check=True,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def launchd_plan(target, backend):
|
|
275
|
+
"""Ordered launchctl commands that (re)load the manager's agents in gui/$UID."""
|
|
276
|
+
labels = backend.units
|
|
277
|
+
if not (target / "state/secrets/recovery-confirmed").exists():
|
|
278
|
+
labels = [label for label in labels if label not in backend.recovery_gated]
|
|
279
|
+
unload = [backend.bootout(label) for label in backend.units]
|
|
280
|
+
load = [command for label in labels for command in backend.enable(label)]
|
|
281
|
+
return unload, load
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def start_launchd(target, environment, operation_lock, backend, run=subprocess.run):
|
|
285
|
+
"""macOS manager: Docker Desktop + per-user LaunchAgents, never root."""
|
|
286
|
+
try:
|
|
287
|
+
cliproxy_host.docker_invocation(["info"], str(target / "scripts/docker"))
|
|
288
|
+
except cliproxy_host.DockerUnavailable as error:
|
|
289
|
+
raise SystemExit("Installation stopped: " + str(error)) from None
|
|
290
|
+
unload, load = launchd_plan(target, backend)
|
|
291
|
+
for command in unload:
|
|
292
|
+
run(command, check=False, capture_output=True) # Not loaded is fine.
|
|
293
|
+
fcntl.flock(operation_lock, fcntl.LOCK_UN)
|
|
294
|
+
for command in load:
|
|
295
|
+
run(command, check=command[1] == "bootstrap")
|
|
296
|
+
# The service agent also starts at load; this synchronous run (serialized by
|
|
297
|
+
# the operation lock, idempotent) gives the installer a definite result.
|
|
298
|
+
run([str(target / "scripts/service-start")], env=environment, check=True)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def main():
|
|
302
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
303
|
+
parser.add_argument(
|
|
304
|
+
"--repo", type=pathlib.Path, default=pathlib.Path(__file__).resolve().parents[2]
|
|
305
|
+
)
|
|
306
|
+
parser.add_argument("--revision", default="HEAD")
|
|
307
|
+
parser.add_argument("--rollback", action="store_true")
|
|
308
|
+
parser.add_argument("--dry-run", action="store_true")
|
|
309
|
+
parser.add_argument(
|
|
310
|
+
"--prepare-only",
|
|
311
|
+
action="store_true",
|
|
312
|
+
help="Materialize committed recovery tooling without initializing state or starting services",
|
|
313
|
+
)
|
|
314
|
+
args = parser.parse_args()
|
|
315
|
+
platform = cliproxy_host.system()
|
|
316
|
+
backend = cliproxy_host.backend()
|
|
317
|
+
if platform == "linux":
|
|
318
|
+
target = cliproxy_host.LINUX_ROOT
|
|
319
|
+
else:
|
|
320
|
+
target = cliproxy_host.default_root()
|
|
321
|
+
if not target.is_absolute():
|
|
322
|
+
parser.error("CLIPROXY_ROOT must be absolute")
|
|
323
|
+
ids = cliproxy_host.runtime_ids()
|
|
324
|
+
revision = args.revision
|
|
325
|
+
if args.rollback:
|
|
326
|
+
previous = target / "INSTALLATION.previous.json"
|
|
327
|
+
if not previous.exists():
|
|
328
|
+
parser.error("No previous Git installation is recorded")
|
|
329
|
+
previous_metadata = json.loads(previous.read_text())
|
|
330
|
+
if previous_metadata.get("schema_version") != 2:
|
|
331
|
+
parser.error(
|
|
332
|
+
"Legacy copied-code revisions are not eligible for repository-only rollback"
|
|
333
|
+
)
|
|
334
|
+
revision = previous_metadata["revision"]
|
|
335
|
+
commit, files = source_files(args.repo, revision)
|
|
336
|
+
image_environment(files)
|
|
337
|
+
if "clients/launcher.py" not in files:
|
|
338
|
+
parser.error(
|
|
339
|
+
"Choose a revision supporting tracked-code launchers and worktrees"
|
|
340
|
+
)
|
|
341
|
+
if args.dry_run:
|
|
342
|
+
print(
|
|
343
|
+
json.dumps(
|
|
344
|
+
{
|
|
345
|
+
"repository": REPOSITORY,
|
|
346
|
+
"revision": commit,
|
|
347
|
+
"target": str(target),
|
|
348
|
+
"backend": backend.name,
|
|
349
|
+
"tracked_files": len(files),
|
|
350
|
+
"state": "preserved",
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
return
|
|
355
|
+
if platform == "linux" and os.geteuid() != 0:
|
|
356
|
+
parser.error("Run the installation command with sudo")
|
|
357
|
+
if platform == "macos":
|
|
358
|
+
if os.geteuid() == 0:
|
|
359
|
+
parser.error("Do not use sudo on macOS; the manager installation is per-user")
|
|
360
|
+
if "launchd/" + backend.service + ".plist" not in files:
|
|
361
|
+
parser.error("Choose a revision with the tracked macOS LaunchAgents")
|
|
362
|
+
# Tracked agents reference the default root; another root is data-only.
|
|
363
|
+
default = cliproxy_host.default_root(environ={})
|
|
364
|
+
if not args.prepare_only and target.resolve() != default.resolve():
|
|
365
|
+
parser.error("LaunchAgents require the default root " + str(default))
|
|
366
|
+
# Callers (fleet bootstrap) may run with umask 077; deployment code and the
|
|
367
|
+
# source repository metadata written here must stay readable for audits.
|
|
368
|
+
os.umask(0o022)
|
|
369
|
+
if target.is_symlink():
|
|
370
|
+
parser.error("Installation root must not be a symlink")
|
|
371
|
+
operation_lock = prepare_root(target, ids)
|
|
372
|
+
fcntl.flock(operation_lock, fcntl.LOCK_EX)
|
|
373
|
+
worktree = prepare_source(args.repo, target, commit)
|
|
374
|
+
materialize(target, commit, files, worktree, ids if platform == "macos" else None)
|
|
375
|
+
if args.prepare_only:
|
|
376
|
+
print(
|
|
377
|
+
"Prepared committed recovery tooling at "
|
|
378
|
+
+ str(target)
|
|
379
|
+
+ "; no services started."
|
|
380
|
+
)
|
|
381
|
+
return
|
|
382
|
+
environment = os.environ.copy()
|
|
383
|
+
environment["CLIPROXY_ROOT"] = str(target)
|
|
384
|
+
subprocess.run([str(target / "scripts/initialize")], env=environment, check=True)
|
|
385
|
+
link_units(target, backend)
|
|
386
|
+
if platform == "linux":
|
|
387
|
+
start_systemd(target, environment, operation_lock)
|
|
388
|
+
else:
|
|
389
|
+
start_launchd(target, environment, operation_lock, backend)
|
|
390
|
+
subprocess.run([str(target / "scripts/audit.py")], env=environment, check=True)
|
|
391
|
+
print(
|
|
392
|
+
"Installed committed revision "
|
|
393
|
+
+ commit
|
|
394
|
+
+ "; existing runtime configuration and credentials preserved."
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
if __name__ == "__main__":
|
|
399
|
+
try:
|
|
400
|
+
main()
|
|
401
|
+
except (ValueError, subprocess.CalledProcessError) as error:
|
|
402
|
+
raise SystemExit(
|
|
403
|
+
"Installation failed: "
|
|
404
|
+
+ type(error).__name__
|
|
405
|
+
+ "; no secret values printed."
|
|
406
|
+
) from None
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Entry point for `personal infra-cliproxy model-health` and `model-suite`."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
sys.dont_write_bytecode = True
|
|
7
|
+
|
|
8
|
+
import pathlib
|
|
9
|
+
|
|
10
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
|
|
11
|
+
|
|
12
|
+
from modelhealth import cli
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
command, *arguments = sys.argv[1:] or ["model-health"]
|
|
16
|
+
raise SystemExit(cli.main(arguments, "nightly" if command == "model-suite" else "light"))
|