@crvouga/dev-fleet 0.0.0-stage → 0.1.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 (333) 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 +157 -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 +128 -0
  30. package/agents/t3.json +40 -0
  31. package/ansible/macos/manager.yml +30 -0
  32. package/ansible/macos/policy.json +17 -0
  33. package/ansible/monitoring/README.md +154 -0
  34. package/ansible/monitoring/agent-darwin.yml +198 -0
  35. package/ansible/monitoring/agent.yml +134 -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/lock.json +11 -0
  40. package/ansible/net/remote-probe.sh +13 -0
  41. package/ansible/ubuntu/authorize-sudo.sh +51 -0
  42. package/ansible/ubuntu/baseline.yml +89 -0
  43. package/ansible/ubuntu/disk-guard.py +104 -0
  44. package/ansible/ubuntu/policy.json +43 -0
  45. package/ansible/ubuntu/recovery.yml +15 -0
  46. package/ansible/ubuntu/t3/policy.json +7 -0
  47. package/ansible/ubuntu/t3-desktop.yml +220 -0
  48. package/ansible/ubuntu/t3-health.sh +33 -0
  49. package/ansible/ubuntu/t3-policy.yml +76 -0
  50. package/ansible/ubuntu/t3-route.yml +28 -0
  51. package/ansible/ubuntu/tailscale.yml +75 -0
  52. package/ansible/ubuntu/tasks/credentials.yml +47 -0
  53. package/ansible/ubuntu/tasks/docker.yml +31 -0
  54. package/ansible/ubuntu/tasks/packages.yml +56 -0
  55. package/ansible/ubuntu/tasks/power.yml +51 -0
  56. package/ansible/ubuntu/tasks/recovery.yml +39 -0
  57. package/ansible/ubuntu/tasks/resources.yml +252 -0
  58. package/ansible/ubuntu/tasks/services.yml +31 -0
  59. package/ansible/ubuntu/tasks/ssh.yml +71 -0
  60. package/ansible/ubuntu/tasks/user.yml +120 -0
  61. package/bin/devfleet.mjs +36 -0
  62. package/bootstrap/bootstrap.ps1 +50 -0
  63. package/bootstrap/bootstrap.sh +73 -0
  64. package/bootstrap/devfleet +7 -0
  65. package/bootstrap/docker-desktop.sh +33 -0
  66. package/bootstrap/doctor.ts +189 -0
  67. package/bootstrap/fleet-manager-run.sh +76 -0
  68. package/bootstrap/fleet-remote-bootstrap.sh +185 -0
  69. package/bootstrap/global-config.ts +113 -0
  70. package/bootstrap/install-launcher.sh +7 -0
  71. package/bootstrap/native-aws.ts +86 -0
  72. package/bootstrap/native-t3.ts +154 -0
  73. package/bootstrap/npm.ts +350 -0
  74. package/bootstrap/setup-status.ts +51 -0
  75. package/bootstrap/setup.ts +88 -0
  76. package/bootstrap/shell-path.ts +105 -0
  77. package/bootstrap/verify-t3-archive.py +49 -0
  78. package/bootstrap/worker-auth.ts +65 -0
  79. package/bootstrap/worker-github.ts +47 -0
  80. package/bootstrap/worker-tools.ts +24 -0
  81. package/cliproxy/AGENTS.md +11 -0
  82. package/cliproxy/README.md +269 -0
  83. package/cliproxy/clients/bootstrap.py +143 -0
  84. package/cliproxy/clients/launcher.py +57 -0
  85. package/cliproxy/compose.yaml +90 -0
  86. package/cliproxy/config.template.yaml +70 -0
  87. package/cliproxy/docs/cloud-adoption.md +16 -0
  88. package/cliproxy/docs/deployment.png +0 -0
  89. package/cliproxy/docs/deployment.svg +109 -0
  90. package/cliproxy/docs/health-workflow.example.yml +185 -0
  91. package/cliproxy/docs/migration.md +131 -0
  92. package/cliproxy/docs/model-health-report.schema.json +364 -0
  93. package/cliproxy/docs/model-health.md +393 -0
  94. package/cliproxy/image.lock.json +5 -0
  95. package/cliproxy/launchd/dev.devfleet.cliproxy-backup-check.plist +38 -0
  96. package/cliproxy/launchd/dev.devfleet.cliproxy-backup.plist +48 -0
  97. package/cliproxy/launchd/dev.devfleet.cliproxy-fleet.plist +32 -0
  98. package/cliproxy/launchd/dev.devfleet.cliproxy-gc.plist +38 -0
  99. package/cliproxy/launchd/dev.devfleet.cliproxy-monitor.plist +29 -0
  100. package/cliproxy/launchd/dev.devfleet.cliproxy-vault-renew.plist +31 -0
  101. package/cliproxy/models.json +1618 -0
  102. package/cliproxy/requirements.lock +78 -0
  103. package/cliproxy/scripts/accounts.py +38 -0
  104. package/cliproxy/scripts/audit.py +123 -0
  105. package/cliproxy/scripts/backup +234 -0
  106. package/cliproxy/scripts/check.py +34 -0
  107. package/cliproxy/scripts/checkpoint.py +226 -0
  108. package/cliproxy/scripts/cli.py +64 -0
  109. package/cliproxy/scripts/cliproxy_host.py +257 -0
  110. package/cliproxy/scripts/cloud_state.py +150 -0
  111. package/cliproxy/scripts/container-health +6 -0
  112. package/cliproxy/scripts/docker +24 -0
  113. package/cliproxy/scripts/external-health.py +79 -0
  114. package/cliproxy/scripts/fleet_config.py +43 -0
  115. package/cliproxy/scripts/fleet_sync.py +274 -0
  116. package/cliproxy/scripts/initialize +84 -0
  117. package/cliproxy/scripts/install-dependencies +48 -0
  118. package/cliproxy/scripts/install.py +406 -0
  119. package/cliproxy/scripts/model_health.py +16 -0
  120. package/cliproxy/scripts/modelhealth/__init__.py +5 -0
  121. package/cliproxy/scripts/modelhealth/checks.py +429 -0
  122. package/cliproxy/scripts/modelhealth/cli.py +117 -0
  123. package/cliproxy/scripts/modelhealth/config.py +186 -0
  124. package/cliproxy/scripts/modelhealth/emulator.py +536 -0
  125. package/cliproxy/scripts/modelhealth/expect.py +88 -0
  126. package/cliproxy/scripts/modelhealth/findings.py +183 -0
  127. package/cliproxy/scripts/modelhealth/protocol.py +330 -0
  128. package/cliproxy/scripts/modelhealth/redact.py +28 -0
  129. package/cliproxy/scripts/modelhealth/report.py +150 -0
  130. package/cliproxy/scripts/modelhealth/runner.py +390 -0
  131. package/cliproxy/scripts/modelhealth/scenario.py +131 -0
  132. package/cliproxy/scripts/modelhealth/schema.py +63 -0
  133. package/cliproxy/scripts/modelhealth/simulate.py +111 -0
  134. package/cliproxy/scripts/modelhealth/transport.py +213 -0
  135. package/cliproxy/scripts/monitor +68 -0
  136. package/cliproxy/scripts/ops +527 -0
  137. package/cliproxy/scripts/public-endpoint +127 -0
  138. package/cliproxy/scripts/recover_node.py +177 -0
  139. package/cliproxy/scripts/restore-verify +60 -0
  140. package/cliproxy/scripts/retention.py +144 -0
  141. package/cliproxy/scripts/service-start +7 -0
  142. package/cliproxy/scripts/validate-config +56 -0
  143. package/cliproxy/scripts/vault-access.py +57 -0
  144. package/cliproxy/scripts/vault-backup-login +55 -0
  145. package/cliproxy/scripts/vault-recovery +56 -0
  146. package/cliproxy/scripts/vault-renew +30 -0
  147. package/cliproxy/systemd/cliproxy-backup-check.service +10 -0
  148. package/cliproxy/systemd/cliproxy-backup-check.timer +8 -0
  149. package/cliproxy/systemd/cliproxy-backup.service +13 -0
  150. package/cliproxy/systemd/cliproxy-backup.timer +8 -0
  151. package/cliproxy/systemd/cliproxy-fleet.service +17 -0
  152. package/cliproxy/systemd/cliproxy-gc.service +13 -0
  153. package/cliproxy/systemd/cliproxy-gc.timer +8 -0
  154. package/cliproxy/systemd/cliproxy-monitor.service +8 -0
  155. package/cliproxy/systemd/cliproxy-monitor.timer +7 -0
  156. package/cliproxy/systemd/cliproxy-vault-renew.service +8 -0
  157. package/cliproxy/systemd/cliproxy-vault-renew.timer +7 -0
  158. package/cliproxy/vendor/LICENSE +22 -0
  159. package/cliproxy/vendor/config.upstream.yaml +1349 -0
  160. package/examples/fleet.json +95 -0
  161. package/examples/roster.json +13 -0
  162. package/mise.lock +1492 -0
  163. package/mise.toml +99 -0
  164. package/package.json +65 -4
  165. package/schema/fleet.schema.json +364 -0
  166. package/schema/roster.schema.json +77 -0
  167. package/src/agents/account-relay.ts +111 -0
  168. package/src/agents/accounts.ts +85 -0
  169. package/src/agents/audit.ts +77 -0
  170. package/src/agents/cli.ts +13 -0
  171. package/src/agents/command.ts +204 -0
  172. package/src/agents/converge.ts +123 -0
  173. package/src/agents/doctor.ts +107 -0
  174. package/src/agents/errors.ts +2 -0
  175. package/src/agents/files.ts +73 -0
  176. package/src/agents/fleet-verify.ts +156 -0
  177. package/src/agents/launch.ts +290 -0
  178. package/src/agents/native-verify.ts +226 -0
  179. package/src/agents/provider-errors.ts +50 -0
  180. package/src/agents/registry.ts +55 -0
  181. package/src/agents/render.ts +110 -0
  182. package/src/agents/round-trip.ts +192 -0
  183. package/src/agents/secrets.ts +13 -0
  184. package/src/agents/toolchain.ts +34 -0
  185. package/src/agents/verify-cache.ts +103 -0
  186. package/src/agents/verify.ts +127 -0
  187. package/src/auth-checks.ts +163 -0
  188. package/src/cli/agent-guide.ts +95 -0
  189. package/src/cli/app.ts +115 -0
  190. package/src/cli/cli.ts +21 -0
  191. package/src/cli/command-index.ts +50 -0
  192. package/src/cli/contracts.ts +25 -0
  193. package/src/cli/fleet-reference.ts +17 -0
  194. package/src/cli/fleet-sync-checkout.ts +91 -0
  195. package/src/cli/fleet-sync-enrollment.ts +144 -0
  196. package/src/cli/fleet-sync-forward.ts +134 -0
  197. package/src/cli/fleet-sync-manager.ts +193 -0
  198. package/src/cli/fleet-sync-monitoring.ts +185 -0
  199. package/src/cli/fleet-sync-output.ts +314 -0
  200. package/src/cli/fleet-sync-privilege.ts +27 -0
  201. package/src/cli/fleet-sync-progress.ts +109 -0
  202. package/src/cli/fleet-sync-remote.ts +236 -0
  203. package/src/cli/fleet-sync-report.ts +26 -0
  204. package/src/cli/fleet-sync-schedule.ts +138 -0
  205. package/src/cli/fleet-sync-vault.ts +166 -0
  206. package/src/cli/fleet-sync.ts +309 -0
  207. package/src/cli/fleet.ts +163 -0
  208. package/src/cli/managed-health.ts +219 -0
  209. package/src/cli/node-deployment.ts +34 -0
  210. package/src/cli/node-sync.ts +114 -0
  211. package/src/cli/node-t3.ts +31 -0
  212. package/src/cli/options.ts +141 -0
  213. package/src/cli/probe-source.ts +7 -0
  214. package/src/cli/program.ts +169 -0
  215. package/src/cli/register-fleet.ts +246 -0
  216. package/src/cli/registry.ts +158 -0
  217. package/src/cli/repair-apply.ts +67 -0
  218. package/src/cli/repair-guard.ts +11 -0
  219. package/src/cli/repair-inspect.ts +150 -0
  220. package/src/cli/repair-script.ts +76 -0
  221. package/src/cli/repair.ts +209 -0
  222. package/src/cli/roster.ts +89 -0
  223. package/src/cli/search.ts +35 -0
  224. package/src/cli/settings.ts +64 -0
  225. package/src/cli/source-operations.ts +78 -0
  226. package/src/cli/t3/command.ts +90 -0
  227. package/src/cli/t3/register.ts +55 -0
  228. package/src/cli/tailnet.ts +127 -0
  229. package/src/cli/ui-actions.ts +133 -0
  230. package/src/cli/ui-controller.ts +113 -0
  231. package/src/cli/ui-data.ts +106 -0
  232. package/src/cli/ui-execute.ts +163 -0
  233. package/src/cli/ui-repairs.ts +34 -0
  234. package/src/cli/ui-reports.ts +211 -0
  235. package/src/cli/ui-targets.ts +100 -0
  236. package/src/cli/ui-terminal.ts +109 -0
  237. package/src/cli/ui-types.ts +49 -0
  238. package/src/cli/ui-wizard.ts +101 -0
  239. package/src/cli/ui.ts +9 -0
  240. package/src/cli/worker-health-output.ts +37 -0
  241. package/src/config.ts +186 -0
  242. package/src/configure.ts +255 -0
  243. package/src/definition.ts +260 -0
  244. package/src/diagnostics.ts +92 -0
  245. package/src/doctor-check.ts +10 -0
  246. package/src/errors.ts +1 -0
  247. package/src/fingerprint.ts +120 -0
  248. package/src/fleet-config.ts +88 -0
  249. package/src/index.ts +6 -0
  250. package/src/input-hashes.ts +73 -0
  251. package/src/machine/local-source.ts +52 -0
  252. package/src/machine/lock-error.ts +2 -0
  253. package/src/machine/platform.ts +44 -0
  254. package/src/machine/source.ts +68 -0
  255. package/src/machine/state.ts +185 -0
  256. package/src/machine-health.ts +95 -0
  257. package/src/managed-source.ts +47 -0
  258. package/src/manager-failure.ts +48 -0
  259. package/src/manager-sync.ts +400 -0
  260. package/src/manager.ts +62 -0
  261. package/src/monitoring/apply.ts +171 -0
  262. package/src/monitoring/beszel.ts +191 -0
  263. package/src/monitoring/enroll.ts +110 -0
  264. package/src/monitoring/hub-access.ts +113 -0
  265. package/src/monitoring/hub-viewer.ts +142 -0
  266. package/src/monitoring/hub.ts +93 -0
  267. package/src/monitoring/manager-agent.ts +73 -0
  268. package/src/monitoring/manager-hub.ts +232 -0
  269. package/src/monitoring/remote.ts +65 -0
  270. package/src/network-definition.ts +83 -0
  271. package/src/node-converge-linux.ts +372 -0
  272. package/src/node-converge-shared.ts +83 -0
  273. package/src/node-converge.ts +40 -0
  274. package/src/onboard.ts +35 -0
  275. package/src/platform/ansible-receipt.ts +128 -0
  276. package/src/platform/ansible.ts +62 -0
  277. package/src/platform/darwin.ts +203 -0
  278. package/src/platform/detect.ts +11 -0
  279. package/src/platform/fallback.ts +110 -0
  280. package/src/platform/index.ts +38 -0
  281. package/src/platform/launchd.ts +98 -0
  282. package/src/platform/linux.ts +139 -0
  283. package/src/platform/ps.ts +45 -0
  284. package/src/platform/resource-verify.ts +128 -0
  285. package/src/platform/systemd-user.ts +95 -0
  286. package/src/platform/t3-policy.ts +137 -0
  287. package/src/platform/t3-route.ts +68 -0
  288. package/src/platform/t3-service.ts +75 -0
  289. package/src/platform/types.ts +123 -0
  290. package/src/platform/ubuntu-audit.ts +63 -0
  291. package/src/platform/ubuntu-verify.ts +105 -0
  292. package/src/platform/windows.ts +148 -0
  293. package/src/power-recovery.ts +55 -0
  294. package/src/probe.ts +51 -0
  295. package/src/protocol.ts +119 -0
  296. package/src/proxy-converge.ts +85 -0
  297. package/src/remote-bootstrap.ts +212 -0
  298. package/src/remote-entry.ts +89 -0
  299. package/src/remote-lock.ts +12 -0
  300. package/src/repair-catalog.ts +206 -0
  301. package/src/repair-plan.ts +78 -0
  302. package/src/repair-runner.ts +84 -0
  303. package/src/report.ts +71 -0
  304. package/src/runner.ts +104 -0
  305. package/src/secrets.ts +169 -0
  306. package/src/service-auth.ts +115 -0
  307. package/src/ssh.ts +135 -0
  308. package/src/sync.ts +104 -0
  309. package/src/t3/accounts.ts +81 -0
  310. package/src/t3/desired.ts +165 -0
  311. package/src/t3/files.ts +81 -0
  312. package/src/t3/names-check.ts +53 -0
  313. package/src/t3/names.ts +155 -0
  314. package/src/t3/plan.ts +82 -0
  315. package/src/t3/providers.ts +74 -0
  316. package/src/t3/reconcile.ts +123 -0
  317. package/src/t3-endpoint.ts +48 -0
  318. package/src/t3-linked-check.ts +101 -0
  319. package/src/tailnet-health.ts +231 -0
  320. package/src/tailnet-matching.ts +37 -0
  321. package/src/tailscale.ts +141 -0
  322. package/src/test-fixtures.ts +36 -0
  323. package/src/toolchain-checks.ts +83 -0
  324. package/src/topology.ts +24 -0
  325. package/src/transport.ts +122 -0
  326. package/src/types.ts +95 -0
  327. package/src/vault-config.ts +38 -0
  328. package/src/worker-access-checks.ts +115 -0
  329. package/src/worker-capacity.ts +34 -0
  330. package/src/worker-host-checks.ts +176 -0
  331. package/src/worker-policy.ts +64 -0
  332. package/src/worker-readiness.ts +64 -0
  333. package/src/workstation-checks.ts +29 -0
@@ -0,0 +1,90 @@
1
+ name: cliproxy-fleet
2
+ services:
3
+ proxy:
4
+ image: ${CLI_PROXY_IMAGE:?image must be pinned}
5
+ restart: unless-stopped
6
+ user: '${CLIPROXY_UID:-1000}:${CLIPROXY_GID:-1000}'
7
+ working_dir: /state
8
+ command: ['/CLIProxyAPI/CLIProxyAPI', '-config', '/state/config.yaml']
9
+ environment:
10
+ HOME: /state
11
+ TZ: UTC
12
+ MANAGEMENT_STATIC_PATH: /state/static
13
+ ports:
14
+ - '127.0.0.1:8317:8317'
15
+ volumes:
16
+ - ./state/runtime:/state
17
+ - ./scripts/container-health:/health:ro
18
+ read_only: true
19
+ tmpfs:
20
+ - /tmp:rw,noexec,nosuid,size=64m,mode=1777
21
+ cap_drop: [ALL]
22
+ security_opt: [no-new-privileges:true]
23
+ mem_limit: 2g
24
+ cpus: 2
25
+ pids_limit: 256
26
+ stop_grace_period: 45s
27
+ healthcheck:
28
+ test: ['CMD', '/health']
29
+ interval: 30s
30
+ timeout: 5s
31
+ retries: 3
32
+ start_period: 30s
33
+ logging:
34
+ driver: local
35
+ options:
36
+ max-size: '10m'
37
+ max-file: '3'
38
+ tailnet:
39
+ profiles: [portable-endpoint]
40
+ user: '${CLIPROXY_UID:-1000}:${CLIPROXY_GID:-1000}'
41
+ image: ${TAILSCALE_IMAGE:?image must be pinned}
42
+ restart: unless-stopped
43
+ entrypoint: ['/usr/local/bin/tailscaled']
44
+ command:
45
+ [
46
+ '--tun=userspace-networking',
47
+ '--state=/var/lib/tailscale/tailscaled.state',
48
+ '--socket=/tmp/tailscaled.sock',
49
+ ]
50
+ volumes:
51
+ - ./state/tailscale:/var/lib/tailscale
52
+ cap_drop: [ALL]
53
+ security_opt: [no-new-privileges:true]
54
+ mem_limit: 256m
55
+ cpus: 0.5
56
+ pids_limit: 128
57
+ logging:
58
+ driver: local
59
+ options:
60
+ max-size: '5m'
61
+ max-file: '2'
62
+ cloudflared:
63
+ profiles: [public-endpoint]
64
+ image: ${CLOUDFLARED_IMAGE:?image must be pinned}
65
+ user: '${CLIPROXY_UID:-1000}:${CLIPROXY_GID:-1000}'
66
+ restart: unless-stopped
67
+ command:
68
+ [
69
+ 'tunnel',
70
+ '--no-autoupdate',
71
+ 'run',
72
+ '--token-file',
73
+ '/run/secrets/tunnel-token',
74
+ ]
75
+ volumes:
76
+ - ./state/secrets/tunnel-token:/run/secrets/tunnel-token:ro
77
+ depends_on:
78
+ proxy:
79
+ condition: service_healthy
80
+ read_only: true
81
+ cap_drop: [ALL]
82
+ security_opt: [no-new-privileges:true]
83
+ mem_limit: 256m
84
+ cpus: 0.5
85
+ pids_limit: 128
86
+ logging:
87
+ driver: local
88
+ options:
89
+ max-size: '5m'
90
+ max-file: '2'
@@ -0,0 +1,70 @@
1
+ config-version: 8
2
+ server:
3
+ host: '0.0.0.0'
4
+ port: 8317
5
+ max-request-body-bytes: 33554432
6
+ trusted-proxies: []
7
+ discovery:
8
+ enabled: false
9
+ management:
10
+ allow-remote: true
11
+ secret-key: REPLACE_MANAGEMENT_KEY
12
+ disable-control-panel: false
13
+ disable-auto-update-panel: true
14
+ panel-github-repository: https://github.com/router-for-me/Cli-Proxy-API-Management-Center
15
+ access:
16
+ api-keys:
17
+ - REPLACE_CLIENT_KEY
18
+ oauth:
19
+ auth-dir: /state/auths
20
+ # Fleet-owned (applied by `ops declared`): the Antigravity CLI sends its
21
+ # catalog ids; serve them from the Antigravity account's matching models.
22
+ model-alias:
23
+ antigravity:
24
+ - name: gemini-3.8-flash-high
25
+ alias: gemini-3.8-flash
26
+ fork: true
27
+ - name: gemini-3.1-flash-lite
28
+ alias: gemini-3.1-flash-lite-preview
29
+ fork: true
30
+ routing:
31
+ # Fill first: use one account until its quota is exhausted (it then cools down), then the next.
32
+ # Sessions stay bound to their account while it is available; new sessions start on the first
33
+ # available account, so usage is not spread across accounts.
34
+ strategy: fill-first
35
+ session-affinity: true
36
+ retry:
37
+ request-retry: 2
38
+ # 0 tries every eligible credential, so exhausting several accounts still reaches the next one.
39
+ max-retry-credentials: 0
40
+ max-retry-interval: 30
41
+ cooldown:
42
+ disable-cooling: false
43
+ save-cooldown-status: true
44
+ upstream:
45
+ claude:
46
+ # Fleet-owned (applied by `ops declared`): the proxy presents itself to
47
+ # Anthropic as this Claude Code version, and Anthropic refuses newer models
48
+ # to an older one. fleet-sync replaces the version with the `claude` pin in
49
+ # mise.toml on every run, so it follows the harness every node runs.
50
+ header-defaults:
51
+ user-agent: 'claude-cli/2.1.314 (external, cli)'
52
+ # T3 runs Claude Code under its own entrypoint label.
53
+ native-entrypoints: ['t3-code']
54
+ # Legacy compatibility field intentionally retained: never switch model capability/cost.
55
+ quota-exceeded:
56
+ switch-project: true
57
+ switch-preview-model: false
58
+ observability:
59
+ logs:
60
+ debug: false
61
+ logging-to-file: false
62
+ request-log: false
63
+ logs-max-total-size-mb: 50
64
+ error-logs-max-files: 2
65
+ usage:
66
+ usage-statistics-enabled: true
67
+ pprof:
68
+ enable: false
69
+ plugins:
70
+ enabled: false
@@ -0,0 +1,16 @@
1
+ # Cloud ownership and initial adoption
2
+
3
+ The initial workstation setup created runtime/cloud state before this package existed. Its operational code is now maintained here; installed copies are generated from committed blobs. The one-off Tailscale API provisioning and Vault secret-writing helpers are retired and removed during first package installation. They must not return as parallel infrastructure controllers.
4
+
5
+ Existing external resources are retained:
6
+
7
+ - Tailscale Service `svc:ai-proxy`, endpoint `tcp:443`, stable MagicDNS origin `https://ai-proxy.tail0a1b2c.ts.net`.
8
+ - Dedicated host tag `tag:ai-proxy` and optional client tag `tag:ai-client`, with owner/service approval and private access rules already in the tailnet. The original workstation identity/SSH/service route was preserved.
9
+ - Existing production R2 bucket and prefix `cliproxy-fleet/restic`; bucket and credentials already belong to the OpenTofu platform.
10
+ - Existing Vault `secret/personal/prd` fields `CLIPROXY_FLEET_RESTIC_PASSWORD`, `CLIPROXY_FLEET_CLIENT_API_KEY`, `CLIPROXY_FLEET_MANAGEMENT_KEY`, and operator-added `TAILSCALE_API_TOKEN`.
11
+
12
+ The new Vault field names still require reconciliation into `packages/infra/tofu/modules/inventory/inventory.tf.json` and the **complete** OpenTofu `vault_inputs` map before a later Vault apply. Preserve their existing values and every unrelated field. Use the established `bun run vault:fix`/`vault:edit` adoption workflow and a reviewed saved plan; never seed secrets directly from this package. The Tailscale admin token is no longer consumed by the runtime package and is not mounted into containers.
13
+
14
+ Before changing existing Tailscale cloud resources, adopt their identities and full policy through the platform's OpenTofu workflow. Do not replace the tailnet policy wholesale, recreate the Service/TailVIP, retag the workstation, or delete existing grants. Local `tailscale up` authorization and `serve` advertisement of an already defined resource are runtime operations; policy, tag ownership and Service definitions are infrastructure.
15
+
16
+ These are outstanding cloud-IaC reconciliation items, not hidden local scripts. Provider/authentication state, source-manifest metadata and the dedicated node identity are encrypted in R2 backups; recovery credentials are independently in Vault. Do not put live state, secrets, provider tokens or infrastructure plans into Git.
Binary file
@@ -0,0 +1,109 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1120" viewBox="0 0 1440 1120" role="img" aria-labelledby="title description">
2
+ <title id="title">CLIProxyAPI deployment and integrations</title>
3
+ <desc id="description">Clients reach Cloudflare HTTPS with API keys. An outbound tunnel connects to cloudflared and CLIProxyAPI on the workstation. CLIProxyAPI calls the upstream AI provider. GitHub supplies tracked runtime code and OpenTofu cloud configuration. Vault supplies credentials; Restic sends encrypted backups to R2. A private Tailscale endpoint also reaches the proxy.</desc>
4
+ <defs>
5
+ <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="#667897"/></marker>
6
+ <marker id="blue-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/></marker>
7
+ <style>
8
+ text { font-family: Inter, Arial, sans-serif; fill: #15243b; }
9
+ .heading { font-size: 32px; font-weight: 700; }
10
+ .subtitle { font-size: 16px; fill: #53637b; }
11
+ .label { font-size: 18px; font-weight: 700; }
12
+ .detail { font-size: 15px; fill: #53637b; }
13
+ .edge-label { font-size: 14px; fill: #53637b; }
14
+ .box { fill: white; stroke: #ccd7e6; stroke-width: 1.5; }
15
+ .traffic { fill: none; stroke: #2563eb; stroke-width: 3; marker-end: url(#blue-arrow); }
16
+ .control { fill: none; stroke: #667897; stroke-width: 2; stroke-dasharray: 7 6; marker-end: url(#arrow); }
17
+ .storage { fill: none; stroke: #667897; stroke-width: 2; marker-end: url(#arrow); }
18
+ </style>
19
+ </defs>
20
+ <rect width="1440" height="1120" rx="24" fill="#f5f8fc"/>
21
+ <text x="54" y="65" class="heading">CLIProxyAPI · deployment &amp; integrations</text>
22
+ <text x="54" y="96" class="subtitle">Stable public HTTPS · API-key authentication · GitHub-owned configuration</text>
23
+
24
+ <rect x="54" y="130" width="1332" height="104" rx="16" class="box"/>
25
+ <text x="80" y="164" class="label">YOUR CLIENTS</text>
26
+ <text x="80" y="196" class="detail">Codex CLI · Claude Code · OpenCode · other API clients</text>
27
+ <text x="900" y="164" class="label">MANAGEMENT DASHBOARD</text>
28
+ <text x="900" y="196" class="detail">Browser + separate management key</text>
29
+
30
+ <path d="M 720 234 V 290" class="traffic"/>
31
+ <text x="742" y="269" class="edge-label">HTTPS + client API key / management key</text>
32
+ <rect x="395" y="290" width="650" height="110" rx="16" fill="#eaf1ff" stroke="#9dbaf5" stroke-width="1.5"/>
33
+ <text x="720" y="328" text-anchor="middle" class="label">cli-proxy.example.com</text>
34
+ <text x="720" y="359" text-anchor="middle" class="detail">Cloudflare DNS + HTTPS + managed Tunnel</text>
35
+ <text x="720" y="383" text-anchor="middle" class="detail">API: /v1 · Dashboard: /management.html</text>
36
+
37
+ <rect x="330" y="462" width="770" height="392" rx="20" fill="#edf5ff" stroke="#9dbaf5" stroke-width="1.5"/>
38
+ <text x="356" y="499" class="label">THIS WORKSTATION</text>
39
+ <text x="356" y="523" class="detail">systemd + Docker Compose · pinned Git code</text>
40
+
41
+ <path d="M 720 400 V 560" class="traffic"/>
42
+ <rect x="550" y="419" width="340" height="27" rx="6" fill="#f5f8fc"/>
43
+ <text x="720" y="438" text-anchor="middle" class="edge-label">Encrypted tunnel · initiated outbound by host</text>
44
+ <rect x="550" y="560" width="330" height="82" rx="13" class="box"/>
45
+ <text x="715" y="592" text-anchor="middle" class="label">cloudflared container</text>
46
+ <text x="715" y="619" text-anchor="middle" class="detail">Private token file · no added host port</text>
47
+ <path d="M 715 642 V 686" class="traffic"/>
48
+ <text x="735" y="669" class="edge-label">Docker network</text>
49
+ <rect x="550" y="686" width="330" height="85" rx="13" fill="#2563eb"/>
50
+ <text x="715" y="720" text-anchor="middle" style="fill:white;font-size:21px;font-weight:700">CLIProxyAPI</text>
51
+ <text x="715" y="748" text-anchor="middle" style="fill:#dbeafe;font-size:15px">Port 8317 · authentication + API routing</text>
52
+ <rect x="355" y="686" width="161" height="85" rx="13" class="box"/>
53
+ <text x="435" y="720" text-anchor="middle" class="label">Working cache</text>
54
+ <text x="435" y="746" text-anchor="middle" class="detail">OAuth + config</text>
55
+ <path d="M 550 728 H 516" class="storage" marker-start="url(#arrow)"/>
56
+ <text x="356" y="818" class="detail">Health monitoring · Vault renewal · cloud checkpoint every 15 minutes</text>
57
+
58
+ <rect x="1140" y="686" width="246" height="104" rx="15" class="box"/>
59
+ <text x="1263" y="719" text-anchor="middle" class="label">AI provider</text>
60
+ <text x="1263" y="746" text-anchor="middle" class="detail">Currently: Codex OAuth</text>
61
+ <text x="1263" y="771" text-anchor="middle" class="detail">Streaming + tool calls</text>
62
+ <path d="M 880 729 H 1140" class="traffic" marker-start="url(#blue-arrow)"/>
63
+ <text x="1008" y="714" text-anchor="middle" class="edge-label">Provider authentication</text>
64
+
65
+ <rect x="54" y="540" width="235" height="110" rx="15" class="box"/>
66
+ <text x="171" y="574" text-anchor="middle" class="label">Vault</text>
67
+ <text x="171" y="601" text-anchor="middle" class="detail">Tunnel, API + backup keys</text>
68
+ <text x="171" y="626" text-anchor="middle" class="detail">Current S3 credentials</text>
69
+ <path d="M 289 580 H 550" class="control"/>
70
+ <rect x="345" y="557" width="170" height="24" rx="5" fill="#edf5ff"/>
71
+ <text x="430" y="574" text-anchor="middle" class="edge-label">Token on startup</text>
72
+
73
+ <rect x="1140" y="540" width="246" height="110" rx="15" class="box"/>
74
+ <text x="1263" y="574" text-anchor="middle" class="label">Tailscale endpoint</text>
75
+ <text x="1263" y="601" text-anchor="middle" class="detail">Existing private alternative</text>
76
+ <text x="1263" y="626" text-anchor="middle" class="detail">ai-proxy.tail0a1b2c.ts.net</text>
77
+ <path d="M 1140 600 H 1048 V 704 H 880" class="storage"/>
78
+
79
+ <rect x="54" y="903" width="330" height="112" rx="15" class="box"/>
80
+ <text x="219" y="938" text-anchor="middle" class="label">GitHub · crvouga/dev-fleet</text>
81
+ <text x="219" y="967" text-anchor="middle" class="detail">Tracked runtime code + OpenTofu</text>
82
+ <text x="219" y="992" text-anchor="middle" class="detail">Reviewed main-branch CI deployment</text>
83
+ <path d="M 384 935 H 470 V 854" class="control"/>
84
+ <text x="488" y="900" class="edge-label">Pinned code install</text>
85
+ <path d="M 105 903 H 35 V 345 H 395" class="control"/>
86
+ <text x="122" y="330" class="edge-label">OpenTofu owns DNS + tunnel</text>
87
+ <path d="M 219 903 V 650" class="control"/>
88
+ <rect x="129" y="828" width="179" height="43" rx="6" fill="#f5f8fc"/>
89
+ <text x="219" y="847" text-anchor="middle" class="edge-label">OpenTofu owns</text>
90
+ <text x="219" y="865" text-anchor="middle" class="edge-label">credential delivery</text>
91
+
92
+ <rect x="680" y="903" width="220" height="112" rx="15" class="box"/>
93
+ <text x="790" y="938" text-anchor="middle" class="label">Restic</text>
94
+ <text x="790" y="967" text-anchor="middle" class="detail">15-minute checkpoints</text>
95
+ <text x="790" y="992" text-anchor="middle" class="detail">Runtime state + revision</text>
96
+ <path d="M 790 854 V 903" class="storage"/>
97
+ <rect x="1140" y="903" width="246" height="112" rx="15" class="box"/>
98
+ <text x="1263" y="938" text-anchor="middle" class="label">Cloudflare R2</text>
99
+ <text x="1263" y="967" text-anchor="middle" class="detail">Durable recovery source</text>
100
+ <text x="1263" y="992" text-anchor="middle" class="detail">cliproxy-fleet/restic</text>
101
+ <path d="M 900 959 H 1140" class="storage"/>
102
+ <text x="1020" y="941" text-anchor="middle" class="edge-label">S3-compatible API</text>
103
+
104
+ <path d="M 55 1065 H 107" class="traffic"/>
105
+ <text x="120" y="1070" class="detail">Inference traffic · responses return along the same path</text>
106
+ <path d="M 710 1065 H 762" class="control"/>
107
+ <text x="775" y="1070" class="detail">Configuration / credential delivery</text>
108
+ <text x="54" y="1100" class="detail">The public URL stays stable when the primary host moves. This workstation must remain online to serve requests.</text>
109
+ </svg>
@@ -0,0 +1,185 @@
1
+ name: CLI proxy health
2
+ # One workflow, five entry points, deliberately cheap by default:
3
+ # every 15 min readiness: free (no inference) + cloud checkpoint freshness
4
+ # hourly light: 2 tiny requests per model (about 200 requests a day)
5
+ # 03:41 nightly comprehensive suite (bounded by models.json budgets)
6
+ # pull request contract: offline, no credentials, no inference
7
+ # manual any profile, one model or one check, optional force-probe
8
+ on:
9
+ schedule:
10
+ - cron: '7,22,37,52 * * * *'
11
+ - cron: '17 * * * *'
12
+ - cron: '41 3 * * *'
13
+ pull_request:
14
+ paths:
15
+ - cliproxy/**
16
+ - .github/workflows/cliproxy-health.yml
17
+ workflow_dispatch:
18
+ inputs:
19
+ profile:
20
+ description: readiness is free; light is the hourly check; nightly is comprehensive; manual adds wrong-key probes and fault simulations.
21
+ type: choice
22
+ default: light
23
+ options: [liveness, readiness, light, nightly, manual]
24
+ suite:
25
+ description: Which declared checks to run.
26
+ type: choice
27
+ default: all
28
+ options: [all, core, cursor]
29
+ model:
30
+ description: Optional single model id from cliproxy/models.json (partial runs never open or close the tracking issue).
31
+ type: string
32
+ default: ''
33
+ check:
34
+ description: Optional capability glob such as chat.tools or responses.* (cheap reproduction of one finding).
35
+ type: string
36
+ default: ''
37
+ max_requests:
38
+ description: Optional lower request budget for this run.
39
+ type: string
40
+ default: ''
41
+ force_probe:
42
+ description: Probe models even if they were rate limited recently (normally paused to protect quota).
43
+ type: boolean
44
+ default: false
45
+ permissions:
46
+ contents: read
47
+ concurrency:
48
+ group: cliproxy-model-health-${{ github.event_name == 'pull_request' && format('pr-{0}', github.ref) || github.event_name == 'workflow_dispatch' && inputs.profile || github.event.schedule }}
49
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
50
+ env:
51
+ PROFILE: ${{ github.event_name == 'workflow_dispatch' && inputs.profile || github.event.schedule == '41 3 * * *' && 'nightly' || github.event.schedule == '17 * * * *' && 'light' || 'readiness' }}
52
+ jobs:
53
+ contract:
54
+ # Pull requests: deterministic protocol, routing and report contracts. No credentials, no inference.
55
+ if: github.event_name == 'pull_request'
56
+ runs-on: ubuntu-latest
57
+ timeout-minutes: 10
58
+ steps:
59
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
60
+ - name: Install PyYAML for the validate-config contract
61
+ run: |
62
+ sudo apt-get update
63
+ sudo apt-get install -y --no-install-recommends python3-yaml
64
+ - name: Lint models.json and run the offline contract tests
65
+ run: |
66
+ python3 -B cliproxy/scripts/check.py
67
+ python3 -B -m unittest discover -s cliproxy/tests -p "test_model_*.py"
68
+ - name: Run the full declarative suite against the local emulator
69
+ run: |
70
+ set +e
71
+ python3 -B cliproxy/scripts/model_health.py model-suite --target mock --profile manual \
72
+ --fail-on degraded --output model-health-report --seed 1 > /dev/null
73
+ code=$?
74
+ if [ -f model-health-report/model-health.md ]; then cat model-health-report/model-health.md >> "$GITHUB_STEP_SUMMARY"; fi
75
+ exit $code
76
+ - name: Keep structured report
77
+ if: ${{ always() && hashFiles('model-health-report/model-health.json') != '' }}
78
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
79
+ with:
80
+ name: model-health-contract-${{ github.run_id }}
81
+ path: model-health-report
82
+ retention-days: 14
83
+ health:
84
+ if: github.event_name != 'pull_request' && github.repository == 'crvouga/dev-fleet' && github.ref == 'refs/heads/main'
85
+ runs-on: ubuntu-latest
86
+ timeout-minutes: 60
87
+ permissions:
88
+ contents: read
89
+ id-token: write
90
+ issues: write
91
+ steps:
92
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
93
+ - name: Install read-only backup client
94
+ if: env.PROFILE == 'readiness'
95
+ run: |
96
+ sudo apt-get update
97
+ sudo apt-get install -y --no-install-recommends restic
98
+ - name: Read-only Vault login using GitHub OIDC
99
+ uses: hashicorp/vault-action@892a26828f195e65540a40b4768ae4571f51ebfc # v4.0.0
100
+ with:
101
+ url: https://vault.example.com
102
+ method: jwt
103
+ path: jwt
104
+ role: github-actions
105
+ jwtGithubAudience: https://vault.example.com
106
+ exportToken: true
107
+ - name: Verify endpoint and cloud recovery freshness
108
+ if: env.PROFILE == 'readiness'
109
+ run: python3 -B cliproxy/scripts/external-health.py
110
+ - name: Restore last-success and rate-limit state
111
+ uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
112
+ with:
113
+ path: model-health-state.json
114
+ key: cliproxy-model-health-state-${{ github.run_id }}
115
+ restore-keys: cliproxy-model-health-state-
116
+ - name: Validate every required model
117
+ # Bounded spend: the profile's request/token budget, concurrency and timeouts come from models.json;
118
+ # inference is never retried; a model rate limited recently is not probed again until its pause ends.
119
+ # The client key is read in-process from Vault and never printed.
120
+ if: ${{ !cancelled() }}
121
+ env:
122
+ MODEL: ${{ inputs.model }}
123
+ CHECK: ${{ inputs.check }}
124
+ MAX_REQUESTS: ${{ inputs.max_requests }}
125
+ SUITE: ${{ inputs.suite || 'all' }}
126
+ FORCE_PROBE: ${{ inputs.force_probe }}
127
+ run: |
128
+ command=model-health
129
+ fail_on=unhealthy
130
+ case "$PROFILE" in nightly | manual) command=model-suite; fail_on=degraded ;; esac
131
+ args=(--profile "$PROFILE" --suite "$SUITE" --fail-on "$fail_on"
132
+ --output model-health-report --state model-health-state.json)
133
+ if [ -n "$MODEL" ]; then args+=(--model "$MODEL"); fi
134
+ if [ -n "$CHECK" ]; then args+=(--check "$CHECK"); fi
135
+ if [ -n "$MAX_REQUESTS" ]; then args+=(--max-requests "$MAX_REQUESTS"); fi
136
+ if [ "$FORCE_PROBE" = "true" ]; then args+=(--force-probe); fi
137
+ set +e
138
+ python3 -B cliproxy/scripts/model_health.py "$command" "${args[@]}" > /dev/null
139
+ code=$?
140
+ if [ -f model-health-report/agent-brief.md ]; then cat model-health-report/agent-brief.md >> "$GITHUB_STEP_SUMMARY"; fi
141
+ if [ -f model-health-report/model-health.md ]; then cat model-health-report/model-health.md >> "$GITHUB_STEP_SUMMARY"; fi
142
+ exit $code
143
+ - name: Save last-success and rate-limit state
144
+ if: ${{ always() && hashFiles('model-health-state.json') != '' }}
145
+ uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
146
+ with:
147
+ path: model-health-state.json
148
+ key: cliproxy-model-health-state-${{ github.run_id }}
149
+ - name: Keep structured report
150
+ if: ${{ always() && hashFiles('model-health-report/model-health.json') != '' }}
151
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
152
+ with:
153
+ name: model-health-${{ env.PROFILE }}-${{ github.run_id }}
154
+ path: model-health-report
155
+ retention-days: 30
156
+ - name: Track findings in one issue per scope
157
+ # Opens or updates a single tracking issue when the findings change, and closes it when a full
158
+ # run is healthy. The body is the redacted agent brief: structure and numbers, no prompts or keys.
159
+ if: ${{ always() && hashFiles('model-health-report/model-health.json') != '' && inputs.model == '' && inputs.check == '' }}
160
+ env:
161
+ GH_TOKEN: ${{ github.token }}
162
+ RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
163
+ run: |
164
+ scope=live
165
+ case "$PROFILE" in nightly | manual) scope=nightly ;; esac
166
+ title="[ops] CLI proxy model health ($scope)"
167
+ report=model-health-report/model-health.json
168
+ status=$(jq -r .summary.status "$report")
169
+ fingerprint=$(jq -r .summary.findings.fingerprint "$report")
170
+ number=$(gh issue list --state open --search "\"$title\" in:title" --json number,title \
171
+ --jq "map(select(.title==\"$title\"))[0].number // empty")
172
+ if [ "$status" = unhealthy ] || [ "$status" = degraded ]; then
173
+ {
174
+ head -c 58000 model-health-report/agent-brief.md
175
+ printf '\n\nRun: %s (artifact model-health-%s-%s)\n<!-- fingerprint: %s -->\n' "$RUN_URL" "$PROFILE" "$GITHUB_RUN_ID" "$fingerprint"
176
+ } > model-health-report/issue.md
177
+ if [ -z "$number" ]; then
178
+ gh issue create --title "$title" --body-file model-health-report/issue.md
179
+ elif ! gh issue view "$number" --json body --jq .body | grep -q "fingerprint: $fingerprint"; then
180
+ gh issue edit "$number" --body-file model-health-report/issue.md
181
+ gh issue comment "$number" --body "Findings changed: $RUN_URL"
182
+ fi
183
+ elif [ "$status" = healthy ] && [ -n "$number" ]; then
184
+ gh issue close "$number" --comment "Healthy again: $RUN_URL"
185
+ fi
@@ -0,0 +1,131 @@
1
+ # Restore and migration
2
+
3
+ Exactly one proxy instance runs, on the **manager** (`manager.id` in `fleet.json`). Workers are clients and never host it. Never activate duplicated OAuth or Tailscale identities concurrently. Source code comes from this package at a reviewed Git revision; secrets and state come from Vault and encrypted R2 snapshots.
4
+
5
+ `INSTALL_ROOT` below is `~/Library/Application Support/cliproxy-fleet` on the manager and `/opt/cliproxy-fleet` on the legacy Linux host. Run every `devfleet cliproxy …` command from a repository checkout on the host named in the step.
6
+
7
+ ## Move the proxy from a Linux worker to the manager
8
+
9
+ Today the proxy still runs on the worker `worker-one` (legacy systemd, `/opt/cliproxy-fleet`). This one-time, operator-approved move fences that host and recovers its latest checkpoint on the manager `manager-mac`. The acknowledgement flags below are operator fencing, not a distributed lock. Plan a short outage: workers have no proxy between the source stop and the manager health check.
10
+
11
+ ### 1. Prepare the manager (no outage)
12
+
13
+ On the manager, logged in, on AC power, with Docker Desktop running (enable **Start Docker Desktop when you sign in**):
14
+
15
+ ```bash
16
+ cliproxy/scripts/install-dependencies
17
+ devfleet cliproxy install --prepare-only --revision HEAD
18
+ devfleet cliproxy vault-login
19
+ devfleet cliproxy recover
20
+ devfleet cliproxy snapshots
21
+ ```
22
+
23
+ `install-dependencies` builds the pinned tools directory (mise Python + hash-locked PyYAML, mise `restic`) that the LaunchAgents run with; it never uses sudo or Homebrew. `--prepare-only` creates the worktree and private directories without starting anything. `recover` reads the existing Restic password from Vault and confirms it; it never generates or writes cloud secrets. Do not run `initialize` before recovery.
24
+
25
+ ### 2. Fence the source (outage starts)
26
+
27
+ On `worker-one`, stop scheduling, drain the dedicated Tailscale Service advertisement, stop and disable the stack, then take the final consistent checkpoint:
28
+
29
+ ```bash
30
+ sudo systemctl disable --now cliproxy-backup.timer cliproxy-backup-check.timer cliproxy-gc.timer
31
+ devfleet cliproxy drain
32
+ devfleet cliproxy stop
33
+ sudo systemctl disable cliproxy-fleet.service
34
+ devfleet cliproxy backup
35
+ devfleet cliproxy snapshots
36
+ sudo systemctl disable --now cliproxy-monitor.timer cliproxy-vault-renew.timer
37
+ ```
38
+
39
+ An already stopped stack remains stopped after backup. Record the **latest successful snapshot ID** from `/opt/cliproxy-fleet/state/status/backup-success.json`; do not choose an incomplete snapshot from a failed backup. Confirm the fence before continuing:
40
+
41
+ ```bash
42
+ systemctl is-active cliproxy-fleet.service # inactive
43
+ systemctl is-enabled cliproxy-fleet.service # disabled
44
+ python3 -B cliproxy/scripts/fleet_sync.py --definition fleet.json \
45
+ --node worker-one --revision "$(git rev-parse HEAD)" # PROXY_DECOMMISSION_PENDING, not PROXY_DUPLICATE_OWNER
46
+ ```
47
+
48
+ Keep the source powered off or fenced until the move is verified or rolled back.
49
+
50
+ ### 3. Recover and start on the manager
51
+
52
+ On the manager:
53
+
54
+ ```bash
55
+ devfleet cliproxy restore-cache --previous-node-stopped
56
+ devfleet cliproxy install --revision HEAD
57
+ devfleet cliproxy audit
58
+ devfleet cliproxy health
59
+ devfleet cliproxy activate
60
+ devfleet cliproxy public-endpoint verify
61
+ python3 -B cliproxy/tests/live.py --endpoint https://cli-proxy.example.com --codex
62
+ devfleet cliproxy backup
63
+ ```
64
+
65
+ `restore-cache` downloads the latest modern checkpoint, validates it and populates the empty cache without starting services. `install` (never sudo on macOS) regenerates secret caches from Vault, records the user's UID/GID for Compose, links and loads the `dev.devfleet.cliproxy-*` LaunchAgents in `gui/$UID`, and starts the recovered stack; it refuses missing runtime config and stops with `DOCKER_DESKTOP_NOT_RUNNING` if Docker Desktop is down. `audit` must show clean provenance and loaded agents. `activate` resumes the existing `svc:ai-proxy` advertisement from the restored dedicated identity. A restored `state/public-endpoint-enabled` marker starts the public connector automatically; otherwise run `devfleet cliproxy public-endpoint enable` first.
66
+
67
+ Then confirm the fleet view and clients:
68
+
69
+ ```bash
70
+ python3 -B cliproxy/scripts/fleet_sync.py --definition fleet.json \
71
+ --node manager-mac --revision "$(git rev-parse HEAD)" # ok: true, role manager, backend launchd
72
+ ```
73
+
74
+ From each worker, run the client bootstrap's `--validate` and an actual client/tool request through `https://cli-proxy.example.com` and `https://ai-proxy.tail0a1b2c.ts.net`. Remove temporary plaintext restore copies after verification.
75
+
76
+ ### 4. Decommission the source
77
+
78
+ After the manager has run cleanly through several scheduled backups, retire the old host's installation. It contains private provider/Tailscale state, so remove it rather than leaving it enabled:
79
+
80
+ ```bash
81
+ sudo docker compose --project-directory /opt/cliproxy-fleet \
82
+ --profile portable-endpoint --profile public-endpoint down
83
+ sudo rm /etc/systemd/system/cliproxy-*.service /etc/systemd/system/cliproxy-*.timer
84
+ sudo systemctl daemon-reload
85
+ sudo rm -rf /opt/cliproxy-fleet
86
+ git worktree prune
87
+ ```
88
+
89
+ `fleet_sync.py --node worker-one` then reports `PROXY_CLIENT_ROLE`. Fleet-sync never performs these steps itself.
90
+
91
+ ### Rollback a failed move
92
+
93
+ 1. On the manager, stop and unload it, keeping any newer state:
94
+
95
+ ```bash
96
+ devfleet cliproxy drain
97
+ devfleet cliproxy stop
98
+ devfleet cliproxy backup # only if the manager refreshed credentials or changed config
99
+ for label in fleet monitor vault-renew backup backup-check gc; do
100
+ launchctl bootout "gui/$(id -u)/dev.devfleet.cliproxy-$label"
101
+ launchctl disable "gui/$(id -u)/dev.devfleet.cliproxy-$label"
102
+ done
103
+ ```
104
+
105
+ Move `INSTALL_ROOT/state/runtime`, `state/tailscale` and `state/endpoint.json` into a private directory under `INSTALL_ROOT/recovery/`. With the runtime cache gone, `fleet-sync` reports `PROXY_RECOVERY_REQUIRED` and can never restart the manager copy.
106
+
107
+ 2. On the source, if the manager took a newer backup, move the source's `state/runtime`, `state/tailscale` and `state/endpoint.json` aside the same way and run `devfleet cliproxy restore-cache --previous-node-stopped`; otherwise keep the original source state (old OAuth refresh tokens may be invalid after the manager refreshed them).
108
+ 3. Restart the source with `sudo systemctl enable cliproxy-fleet.service`, `devfleet cliproxy install --revision REV` (the revision it ran before), `devfleet cliproxy activate`, then re-enable its timers with `sudo systemctl enable --now cliproxy-monitor.timer cliproxy-vault-renew.timer cliproxy-backup.timer cliproxy-backup-check.timer cliproxy-gc.timer`.
109
+ 4. Verify the stable endpoints from fleet clients. Until the move is retried, fleet-sync reports `PROXY_DUPLICATE_OWNER` on the source; that is expected and is never auto-remediated.
110
+
111
+ ## One-command offline recovery
112
+
113
+ On a host with the tracked package installed and a valid private Vault token, `devfleet cliproxy recover-node NEW_DIRECTORY` builds a complete new cache using fresh Vault secrets and a verified R2 checkpoint. Supply `--vault-token-file FILE` to use an explicit existing login. It refuses existing destinations and never changes or stops the running stack. This is also a safe recovery rehearsal while the manager remains online: the new identity files are never activated. Remove rehearsal caches after inspection, since they contain private credentials.
114
+
115
+ On a replacement manager without installed tooling, invoke `python3 -B cliproxy/scripts/recover_node.py "$HOME/Library/Application Support/cliproxy-fleet" --vault-token-file FILE` from a full Git clone after `install-dependencies` passes. The destination must not yet exist. After explicitly fencing the old host, `devfleet cliproxy install --revision REV` installs tracked code and activates the recovered cache. This route replaces the separate prepare/login/password/restore steps above when an existing Vault token is already available. It does not obtain a new machine identity or bypass old-node fencing.
116
+
117
+ ## Recovery details
118
+
119
+ Recovery uses the current trusted Git checkout, validates decrypted data, and preserves the new Vault login and independently recovered Restic password. It never executes backed-up code. To reproduce the original software version, read the recovered checkpoint's `INSTALLATION.json` in an offline restore first and choose that reviewed Git revision instead of HEAD.
120
+
121
+ `restore-cache` intentionally refuses an existing `runtime`, `tailscale` or endpoint cache. An existing repository pin must match the restored destination. For recovery on the same host, stop the service/agents and move these cache entries to a private rollback directory first. Never move `state/status/operation.lock` while an operation is active. An interrupted recovery leaves `state/recovery-incomplete`; inspect/remove the partial cache before retrying. Keep `state/secrets` (new Vault login/password) and the lock directory intact.
122
+
123
+ For offline inspection or historical recovery, use `devfleet cliproxy restore SNAPSHOT_ID EMPTY_DIRECTORY`, then `python3 -B INSTALL_ROOT/scripts/restore-verify EMPTY_DIRECTORY`. Modern checkpoints are located automatically despite different source paths. Old snapshots without `CHECKPOINT.json` require the original explicit restored deployment path and manual state recovery. No automatic fallback to an older snapshot occurs when validation fails.
124
+
125
+ The existing Tailscale Service remains in the cloud. Reuse the restored dedicated identity only while the original is stopped. If a drained identity requires re-advertising, use the pinned Tailscale CLI's `serve advertise` command for `svc:ai-proxy`, then inspect `serve status`. Do not change the manager's ordinary Tailscale identity or assume hostnames transfer. A new tagged identity/service definition requires the platform's OpenTofu-managed enrollment/approval workflow; no cloud provisioning helper exists in this package.
126
+
127
+ A normal code/image rollback uses `devfleet cliproxy install --rollback`. That restores a recorded Git revision, not application data. For incompatible state migrations, restore a matching pre-upgrade snapshot while stopped.
128
+
129
+ ## Public hostname cutover
130
+
131
+ The public DNS/tunnel resources remain owned by OpenTofu. A restored `state/public-endpoint-enabled` marker makes service startup fetch the current isolated tunnel credential from Vault and start the connector automatically. The tunnel token itself is excluded from snapshots. If the marker is absent, run `devfleet cliproxy public-endpoint enable` after restoring and stopping the old host. Verify with `devfleet cliproxy public-endpoint verify` and `tests/live.py --endpoint https://cli-proxy.example.com --codex`. Client API and management keys do not change; keep the old host stopped during recovery.