@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.
Files changed (363) hide show
  1. package/.mise/locks/aws-cdk/2.1146.1/aube-lock.yaml +24 -0
  2. package/.mise/locks/aws-cdk/2.1146.1/package.json +7 -0
  3. package/.mise/locks/azure-cli/2.91.0~a837e014/pyproject.toml +5 -0
  4. package/.mise/locks/azure-cli/2.91.0~a837e014/uv.lock +2794 -0
  5. package/.mise/locks/gemini/0.66.0~84aac8e6/aube-lock.yaml +123 -0
  6. package/.mise/locks/gemini/0.66.0~84aac8e6/package.json +12 -0
  7. package/.mise/locks/npm-devcontainers-cli/0.90.0/aube-lock.yaml +24 -0
  8. package/.mise/locks/npm-devcontainers-cli/0.90.0/package.json +7 -0
  9. package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/aube-lock.yaml +1432 -0
  10. package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/package.json +7 -0
  11. package/.mise/locks/npm-vercel/65.8.0/aube-lock.yaml +3595 -0
  12. package/.mise/locks/npm-vercel/65.8.0/package.json +7 -0
  13. package/.mise/locks/npm-wrangler/4.156.0/aube-lock.yaml +895 -0
  14. package/.mise/locks/npm-wrangler/4.156.0/package.json +7 -0
  15. package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/aube-lock.yaml +24 -0
  16. package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/package.json +7 -0
  17. package/LICENSE +21 -0
  18. package/README.md +357 -2
  19. package/agents/antigravity-proxy.ts +3 -0
  20. package/agents/audit.md +89 -0
  21. package/agents/claude-proxy +5 -0
  22. package/agents/claude-proxy.ts +3 -0
  23. package/agents/codex-proxy.ts +3 -0
  24. package/agents/grok-proxy.ts +3 -0
  25. package/agents/managed-proxy.ts +55 -0
  26. package/agents/opencode-proxy.ts +3 -0
  27. package/agents/registry.json +209 -0
  28. package/agents/registry.schema.json +149 -0
  29. package/agents/setup.md +158 -0
  30. package/agents/t3.json +40 -0
  31. package/ansible/macos/manager.yml +30 -0
  32. package/ansible/macos/policy.json +9 -0
  33. package/ansible/monitoring/README.md +215 -0
  34. package/ansible/monitoring/agent-darwin.yml +198 -0
  35. package/ansible/monitoring/agent.yml +172 -0
  36. package/ansible/monitoring/beszel-agent.service +42 -0
  37. package/ansible/monitoring/compose.yml +74 -0
  38. package/ansible/monitoring/hub-darwin.yml +102 -0
  39. package/ansible/monitoring/hub-route-darwin.yml +51 -0
  40. package/ansible/monitoring/lock.json +16 -0
  41. package/ansible/net/remote-probe.sh +13 -0
  42. package/ansible/pyproject.toml +12 -0
  43. package/ansible/ubuntu/authorize-sudo.sh +51 -0
  44. package/ansible/ubuntu/baseline.yml +89 -0
  45. package/ansible/ubuntu/disk-gc.py +336 -0
  46. package/ansible/ubuntu/disk-guard.py +104 -0
  47. package/ansible/ubuntu/policy.json +52 -0
  48. package/ansible/ubuntu/recovery.yml +15 -0
  49. package/ansible/ubuntu/t3/policy.json +7 -0
  50. package/ansible/ubuntu/t3-desktop.yml +220 -0
  51. package/ansible/ubuntu/t3-health.sh +33 -0
  52. package/ansible/ubuntu/t3-policy.yml +76 -0
  53. package/ansible/ubuntu/t3-route.yml +28 -0
  54. package/ansible/ubuntu/tailscale.yml +78 -0
  55. package/ansible/ubuntu/tasks/credentials.yml +47 -0
  56. package/ansible/ubuntu/tasks/docker.yml +31 -0
  57. package/ansible/ubuntu/tasks/packages.yml +56 -0
  58. package/ansible/ubuntu/tasks/power.yml +51 -0
  59. package/ansible/ubuntu/tasks/recovery.yml +39 -0
  60. package/ansible/ubuntu/tasks/resources.yml +312 -0
  61. package/ansible/ubuntu/tasks/services.yml +31 -0
  62. package/ansible/ubuntu/tasks/ssh.yml +71 -0
  63. package/ansible/ubuntu/tasks/user.yml +120 -0
  64. package/ansible/uv.lock +190 -0
  65. package/bin/devfleet.mjs +36 -0
  66. package/bootstrap/bootstrap.ps1 +50 -0
  67. package/bootstrap/bootstrap.sh +80 -0
  68. package/bootstrap/devfleet +7 -0
  69. package/bootstrap/docker-desktop.sh +33 -0
  70. package/bootstrap/doctor.ts +234 -0
  71. package/bootstrap/fleet-manager-run.sh +103 -0
  72. package/bootstrap/fleet-remote-bootstrap.sh +258 -0
  73. package/bootstrap/global-config.ts +171 -0
  74. package/bootstrap/install-launcher.sh +10 -0
  75. package/bootstrap/native-aws.ts +86 -0
  76. package/bootstrap/native-t3.ts +154 -0
  77. package/bootstrap/npm.ts +358 -0
  78. package/bootstrap/release-version.ts +157 -0
  79. package/bootstrap/setup-status.ts +51 -0
  80. package/bootstrap/setup.ts +88 -0
  81. package/bootstrap/shell-path.ts +105 -0
  82. package/bootstrap/verify-t3-archive.py +49 -0
  83. package/bootstrap/worker-auth.ts +65 -0
  84. package/bootstrap/worker-github.ts +47 -0
  85. package/bootstrap/worker-tools.ts +24 -0
  86. package/cliproxy/AGENTS.md +11 -0
  87. package/cliproxy/README.md +269 -0
  88. package/cliproxy/clients/bootstrap.py +143 -0
  89. package/cliproxy/clients/launcher.py +57 -0
  90. package/cliproxy/compose.yaml +90 -0
  91. package/cliproxy/config.template.yaml +70 -0
  92. package/cliproxy/docs/cloud-adoption.md +16 -0
  93. package/cliproxy/docs/deployment.png +0 -0
  94. package/cliproxy/docs/deployment.svg +109 -0
  95. package/cliproxy/docs/health-workflow.example.yml +185 -0
  96. package/cliproxy/docs/migration.md +131 -0
  97. package/cliproxy/docs/model-health-report.schema.json +364 -0
  98. package/cliproxy/docs/model-health.md +393 -0
  99. package/cliproxy/image.lock.json +5 -0
  100. package/cliproxy/launchd/dev.devfleet.cliproxy-backup-check.plist +38 -0
  101. package/cliproxy/launchd/dev.devfleet.cliproxy-backup.plist +48 -0
  102. package/cliproxy/launchd/dev.devfleet.cliproxy-fleet.plist +32 -0
  103. package/cliproxy/launchd/dev.devfleet.cliproxy-gc.plist +38 -0
  104. package/cliproxy/launchd/dev.devfleet.cliproxy-monitor.plist +29 -0
  105. package/cliproxy/launchd/dev.devfleet.cliproxy-vault-renew.plist +31 -0
  106. package/cliproxy/models.json +1618 -0
  107. package/cliproxy/requirements.lock +78 -0
  108. package/cliproxy/scripts/accounts.py +38 -0
  109. package/cliproxy/scripts/audit.py +123 -0
  110. package/cliproxy/scripts/backup +234 -0
  111. package/cliproxy/scripts/check.py +34 -0
  112. package/cliproxy/scripts/checkpoint.py +226 -0
  113. package/cliproxy/scripts/cli.py +64 -0
  114. package/cliproxy/scripts/cliproxy_host.py +257 -0
  115. package/cliproxy/scripts/cloud_state.py +150 -0
  116. package/cliproxy/scripts/container-health +6 -0
  117. package/cliproxy/scripts/docker +24 -0
  118. package/cliproxy/scripts/external-health.py +79 -0
  119. package/cliproxy/scripts/fleet_config.py +43 -0
  120. package/cliproxy/scripts/fleet_sync.py +274 -0
  121. package/cliproxy/scripts/initialize +84 -0
  122. package/cliproxy/scripts/install-dependencies +48 -0
  123. package/cliproxy/scripts/install.py +406 -0
  124. package/cliproxy/scripts/model_health.py +16 -0
  125. package/cliproxy/scripts/modelhealth/__init__.py +5 -0
  126. package/cliproxy/scripts/modelhealth/checks.py +429 -0
  127. package/cliproxy/scripts/modelhealth/cli.py +117 -0
  128. package/cliproxy/scripts/modelhealth/config.py +186 -0
  129. package/cliproxy/scripts/modelhealth/emulator.py +536 -0
  130. package/cliproxy/scripts/modelhealth/expect.py +88 -0
  131. package/cliproxy/scripts/modelhealth/findings.py +183 -0
  132. package/cliproxy/scripts/modelhealth/protocol.py +330 -0
  133. package/cliproxy/scripts/modelhealth/redact.py +28 -0
  134. package/cliproxy/scripts/modelhealth/report.py +150 -0
  135. package/cliproxy/scripts/modelhealth/runner.py +390 -0
  136. package/cliproxy/scripts/modelhealth/scenario.py +131 -0
  137. package/cliproxy/scripts/modelhealth/schema.py +63 -0
  138. package/cliproxy/scripts/modelhealth/simulate.py +111 -0
  139. package/cliproxy/scripts/modelhealth/transport.py +213 -0
  140. package/cliproxy/scripts/monitor +68 -0
  141. package/cliproxy/scripts/ops +527 -0
  142. package/cliproxy/scripts/public-endpoint +127 -0
  143. package/cliproxy/scripts/recover_node.py +177 -0
  144. package/cliproxy/scripts/restore-verify +60 -0
  145. package/cliproxy/scripts/retention.py +144 -0
  146. package/cliproxy/scripts/service-start +7 -0
  147. package/cliproxy/scripts/validate-config +56 -0
  148. package/cliproxy/scripts/vault-access.py +57 -0
  149. package/cliproxy/scripts/vault-backup-login +55 -0
  150. package/cliproxy/scripts/vault-recovery +56 -0
  151. package/cliproxy/scripts/vault-renew +30 -0
  152. package/cliproxy/systemd/cliproxy-backup-check.service +10 -0
  153. package/cliproxy/systemd/cliproxy-backup-check.timer +8 -0
  154. package/cliproxy/systemd/cliproxy-backup.service +13 -0
  155. package/cliproxy/systemd/cliproxy-backup.timer +8 -0
  156. package/cliproxy/systemd/cliproxy-fleet.service +17 -0
  157. package/cliproxy/systemd/cliproxy-gc.service +13 -0
  158. package/cliproxy/systemd/cliproxy-gc.timer +8 -0
  159. package/cliproxy/systemd/cliproxy-monitor.service +8 -0
  160. package/cliproxy/systemd/cliproxy-monitor.timer +7 -0
  161. package/cliproxy/systemd/cliproxy-vault-renew.service +8 -0
  162. package/cliproxy/systemd/cliproxy-vault-renew.timer +7 -0
  163. package/cliproxy/vendor/LICENSE +22 -0
  164. package/cliproxy/vendor/config.upstream.yaml +1349 -0
  165. package/docs/consumer-setup.md +408 -0
  166. package/docs/declared.md +89 -0
  167. package/docs/machine-setup.md +399 -0
  168. package/docs/operations.md +276 -0
  169. package/docs/releasing.md +103 -0
  170. package/docs/state.md +254 -0
  171. package/docs/topology.md +67 -0
  172. package/docs/ubuntu-sync.md +351 -0
  173. package/docs/worker-contract.md +67 -0
  174. package/examples/fleet.json +100 -0
  175. package/examples/roster.json +13 -0
  176. package/llms.txt +35 -0
  177. package/mise.lock +1521 -0
  178. package/mise.toml +110 -0
  179. package/package.json +74 -4
  180. package/schema/fleet.schema.json +420 -0
  181. package/schema/roster.schema.json +77 -0
  182. package/src/agents/account-relay.ts +111 -0
  183. package/src/agents/accounts.ts +85 -0
  184. package/src/agents/audit.ts +77 -0
  185. package/src/agents/cli.ts +13 -0
  186. package/src/agents/command.ts +204 -0
  187. package/src/agents/converge.ts +123 -0
  188. package/src/agents/doctor.ts +107 -0
  189. package/src/agents/errors.ts +2 -0
  190. package/src/agents/files.ts +73 -0
  191. package/src/agents/fleet-verify.ts +165 -0
  192. package/src/agents/launch.ts +290 -0
  193. package/src/agents/native-verify.ts +371 -0
  194. package/src/agents/provider-errors.ts +57 -0
  195. package/src/agents/registry.ts +61 -0
  196. package/src/agents/render.ts +110 -0
  197. package/src/agents/round-trip.ts +192 -0
  198. package/src/agents/secrets.ts +13 -0
  199. package/src/agents/toolchain.ts +34 -0
  200. package/src/agents/verify-cache.ts +103 -0
  201. package/src/agents/verify.ts +127 -0
  202. package/src/auth-checks.ts +163 -0
  203. package/src/cli/agent-guide.ts +129 -0
  204. package/src/cli/app.ts +122 -0
  205. package/src/cli/cli.ts +28 -0
  206. package/src/cli/command-index.ts +57 -0
  207. package/src/cli/contracts.ts +25 -0
  208. package/src/cli/fleet-reference.ts +17 -0
  209. package/src/cli/fleet-state.ts +499 -0
  210. package/src/cli/fleet-sync-checkout.ts +130 -0
  211. package/src/cli/fleet-sync-enrollment.ts +140 -0
  212. package/src/cli/fleet-sync-forward.ts +134 -0
  213. package/src/cli/fleet-sync-manager.ts +249 -0
  214. package/src/cli/fleet-sync-monitoring.ts +185 -0
  215. package/src/cli/fleet-sync-output.ts +317 -0
  216. package/src/cli/fleet-sync-privilege.ts +27 -0
  217. package/src/cli/fleet-sync-progress.ts +109 -0
  218. package/src/cli/fleet-sync-remote.ts +242 -0
  219. package/src/cli/fleet-sync-report.ts +26 -0
  220. package/src/cli/fleet-sync-schedule.ts +138 -0
  221. package/src/cli/fleet-sync-state.ts +86 -0
  222. package/src/cli/fleet-sync-vault.ts +166 -0
  223. package/src/cli/fleet-sync.ts +375 -0
  224. package/src/cli/fleet.ts +165 -0
  225. package/src/cli/managed-health.ts +219 -0
  226. package/src/cli/node-deployment.ts +34 -0
  227. package/src/cli/node-sync.ts +121 -0
  228. package/src/cli/node-t3.ts +31 -0
  229. package/src/cli/options.ts +141 -0
  230. package/src/cli/probe-source.ts +7 -0
  231. package/src/cli/program.ts +185 -0
  232. package/src/cli/project.ts +51 -0
  233. package/src/cli/register-fleet.ts +296 -0
  234. package/src/cli/registry.ts +185 -0
  235. package/src/cli/repair-apply.ts +67 -0
  236. package/src/cli/repair-guard.ts +11 -0
  237. package/src/cli/repair-inspect.ts +150 -0
  238. package/src/cli/repair-script.ts +76 -0
  239. package/src/cli/repair.ts +209 -0
  240. package/src/cli/roster.ts +105 -0
  241. package/src/cli/search.ts +35 -0
  242. package/src/cli/settings.ts +67 -0
  243. package/src/cli/source-operations.ts +102 -0
  244. package/src/cli/t3/command.ts +90 -0
  245. package/src/cli/t3/register.ts +55 -0
  246. package/src/cli/tailnet.ts +127 -0
  247. package/src/cli/ui-actions.ts +133 -0
  248. package/src/cli/ui-controller.ts +113 -0
  249. package/src/cli/ui-data.ts +106 -0
  250. package/src/cli/ui-execute.ts +163 -0
  251. package/src/cli/ui-repairs.ts +34 -0
  252. package/src/cli/ui-reports.ts +211 -0
  253. package/src/cli/ui-targets.ts +100 -0
  254. package/src/cli/ui-terminal.ts +109 -0
  255. package/src/cli/ui-types.ts +49 -0
  256. package/src/cli/ui-wizard.ts +101 -0
  257. package/src/cli/ui.ts +9 -0
  258. package/src/cli/worker-health-output.ts +37 -0
  259. package/src/config.ts +186 -0
  260. package/src/configure.ts +255 -0
  261. package/src/definition.ts +290 -0
  262. package/src/diagnostics.ts +92 -0
  263. package/src/doctor-check.ts +10 -0
  264. package/src/errors.ts +1 -0
  265. package/src/fingerprint.ts +120 -0
  266. package/src/fleet-config.ts +174 -0
  267. package/src/input-hashes.ts +73 -0
  268. package/src/machine/local-source.ts +52 -0
  269. package/src/machine/lock-error.ts +2 -0
  270. package/src/machine/platform.ts +44 -0
  271. package/src/machine/source.ts +68 -0
  272. package/src/machine/state.ts +173 -0
  273. package/src/machine-health.ts +95 -0
  274. package/src/managed-source.ts +47 -0
  275. package/src/manager-failure.ts +48 -0
  276. package/src/manager-sync.ts +405 -0
  277. package/src/manager.ts +62 -0
  278. package/src/monitoring/agent-evidence.ts +146 -0
  279. package/src/monitoring/apply.ts +183 -0
  280. package/src/monitoring/beszel.ts +198 -0
  281. package/src/monitoring/enroll.ts +111 -0
  282. package/src/monitoring/hub-access.ts +114 -0
  283. package/src/monitoring/hub-backup.ts +121 -0
  284. package/src/monitoring/hub-viewer.ts +142 -0
  285. package/src/monitoring/hub.ts +93 -0
  286. package/src/monitoring/manager-agent.ts +73 -0
  287. package/src/monitoring/manager-hub.ts +248 -0
  288. package/src/monitoring/remote.ts +65 -0
  289. package/src/network-definition.ts +83 -0
  290. package/src/node-converge-linux.ts +374 -0
  291. package/src/node-converge-shared.ts +83 -0
  292. package/src/node-converge.ts +40 -0
  293. package/src/onboard.ts +35 -0
  294. package/src/package-info.ts +45 -0
  295. package/src/platform/ansible-receipt.ts +128 -0
  296. package/src/platform/ansible.ts +66 -0
  297. package/src/platform/darwin.ts +203 -0
  298. package/src/platform/detect.ts +11 -0
  299. package/src/platform/fallback.ts +110 -0
  300. package/src/platform/index.ts +38 -0
  301. package/src/platform/launchd.ts +98 -0
  302. package/src/platform/linux.ts +139 -0
  303. package/src/platform/ps.ts +45 -0
  304. package/src/platform/resource-verify.ts +132 -0
  305. package/src/platform/systemd-user.ts +95 -0
  306. package/src/platform/t3-policy.ts +137 -0
  307. package/src/platform/t3-route.ts +68 -0
  308. package/src/platform/t3-service.ts +75 -0
  309. package/src/platform/types.ts +123 -0
  310. package/src/platform/ubuntu-audit.ts +66 -0
  311. package/src/platform/ubuntu-verify.ts +105 -0
  312. package/src/platform/windows.ts +148 -0
  313. package/src/power-recovery.ts +55 -0
  314. package/src/probe.ts +51 -0
  315. package/src/protocol.ts +119 -0
  316. package/src/proxy-converge.ts +85 -0
  317. package/src/remote-bootstrap.ts +210 -0
  318. package/src/remote-entry.ts +89 -0
  319. package/src/remote-lock.ts +12 -0
  320. package/src/repair-catalog.ts +206 -0
  321. package/src/repair-plan.ts +78 -0
  322. package/src/repair-runner.ts +84 -0
  323. package/src/report.ts +71 -0
  324. package/src/runner.ts +104 -0
  325. package/src/secrets.ts +169 -0
  326. package/src/service-auth.ts +115 -0
  327. package/src/ssh.ts +135 -0
  328. package/src/state/bucket.ts +208 -0
  329. package/src/state/check.ts +74 -0
  330. package/src/state/connection.ts +92 -0
  331. package/src/state/enrollment.ts +45 -0
  332. package/src/state/legacy.ts +89 -0
  333. package/src/state/litestream.ts +143 -0
  334. package/src/state/operator.ts +20 -0
  335. package/src/state/paths.ts +40 -0
  336. package/src/state/roster.ts +62 -0
  337. package/src/state/store.ts +169 -0
  338. package/src/state/sync.ts +352 -0
  339. package/src/sync.ts +104 -0
  340. package/src/t3/accounts.ts +81 -0
  341. package/src/t3/desired.ts +165 -0
  342. package/src/t3/files.ts +81 -0
  343. package/src/t3/names-check.ts +53 -0
  344. package/src/t3/names.ts +155 -0
  345. package/src/t3/plan.ts +82 -0
  346. package/src/t3/providers.ts +74 -0
  347. package/src/t3/reconcile.ts +123 -0
  348. package/src/t3-endpoint.ts +104 -0
  349. package/src/t3-linked-check.ts +123 -0
  350. package/src/tailnet-health.ts +231 -0
  351. package/src/tailnet-matching.ts +37 -0
  352. package/src/tailscale.ts +141 -0
  353. package/src/toolchain-checks.ts +108 -0
  354. package/src/topology.ts +24 -0
  355. package/src/transport.ts +122 -0
  356. package/src/types.ts +99 -0
  357. package/src/vault-config.ts +38 -0
  358. package/src/worker-access-checks.ts +168 -0
  359. package/src/worker-capacity.ts +34 -0
  360. package/src/worker-host-checks.ts +181 -0
  361. package/src/worker-policy.ts +64 -0
  362. package/src/worker-readiness.ts +64 -0
  363. 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"))
@@ -0,0 +1,5 @@
1
+ """Declarative model validation and health checks for the CLIProxyAPI endpoint."""
2
+
3
+ import sys
4
+
5
+ sys.dont_write_bytecode = True