@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,120 @@
1
+ ---
2
+ - name: Inspect existing designated-user sudo policy ownership
3
+ ansible.builtin.stat:
4
+ path: '/etc/sudoers.d/personal-fleet-{{ development_user }}'
5
+ register: sudo_owned_file
6
+ - name: Read existing designated-user sudo policy for ownership validation
7
+ ansible.builtin.slurp:
8
+ src: '/etc/sudoers.d/personal-fleet-{{ development_user }}'
9
+ when: sudo_owned_file.stat.exists
10
+ register: sudo_previous
11
+ no_log: true
12
+ - name: Preserve conflicting sudo policy until a reviewed migration
13
+ ansible.builtin.assert:
14
+ that:
15
+ - not sudo_owned_file.stat.islnk | default(false)
16
+ - "not sudo_owned_file.stat.exists or (sudo_previous.content | b64decode).startswith('# Managed by personal Fleet: designated development user')"
17
+ fail_msg: SUDOERS_OWNERSHIP_CONFLICT; preserve and review the existing fragment before adoption.
18
+ - name: Validate and install designated-user sudo policy
19
+ ansible.builtin.copy:
20
+ dest: '/etc/sudoers.d/personal-fleet-{{ development_user }}'
21
+ content: |
22
+ # Managed by personal Fleet: designated development user
23
+ {{ development_user }} ALL=(ALL:ALL) NOPASSWD: ALL
24
+ owner: root
25
+ group: root
26
+ mode: '0440'
27
+ validate: /usr/sbin/visudo -cf %s
28
+ backup: true
29
+ - name: Enable lingering without requiring an open desktop session
30
+ ansible.builtin.command:
31
+ argv: [loginctl, enable-linger, '{{ development_user }}']
32
+ creates: '/var/lib/systemd/linger/{{ development_user }}'
33
+ - name: Check whether GNOME settings schemas are installed
34
+ ansible.builtin.stat:
35
+ path: /usr/share/glib-2.0/schemas/org.gnome.desktop.screensaver.gschema.xml
36
+ register: gnome_schema
37
+ - name: Check whether the GNOME power plugin schema is installed
38
+ ansible.builtin.stat:
39
+ path: /usr/share/glib-2.0/schemas/org.gnome.settings-daemon.plugins.power.gschema.xml
40
+ register: gnome_power_schema
41
+ - name: Ensure user configuration directory exists
42
+ ansible.builtin.file:
43
+ path: '{{ development_home }}/.config'
44
+ state: directory
45
+ owner: '{{ development_user }}'
46
+ mode: '0700'
47
+ when: gnome_schema.stat.exists
48
+ - name: Inspect designated-user GNOME lock and idle policy
49
+ become: true
50
+ become_user: '{{ development_user }}'
51
+ ansible.builtin.command:
52
+ argv:
53
+ [
54
+ dbus-run-session,
55
+ '--',
56
+ gsettings,
57
+ get,
58
+ '{{ item.schema }}',
59
+ '{{ item.key }}',
60
+ ]
61
+ environment:
62
+ HOME: '{{ development_home }}'
63
+ XDG_CONFIG_HOME: '{{ development_home }}/.config'
64
+ loop: '{{ gnome_session_settings + (gnome_power_settings if gnome_power_schema.stat.exists else []) }}'
65
+ vars:
66
+ gnome_session_settings:
67
+ - {
68
+ schema: org.gnome.desktop.screensaver,
69
+ key: lock-enabled,
70
+ value: 'false',
71
+ observed: 'false',
72
+ }
73
+ - {
74
+ schema: org.gnome.desktop.session,
75
+ key: idle-delay,
76
+ value: '0',
77
+ observed: 'uint32 0',
78
+ }
79
+ gnome_power_settings:
80
+ - {
81
+ schema: org.gnome.settings-daemon.plugins.power,
82
+ key: sleep-inactive-ac-type,
83
+ value: 'nothing',
84
+ observed: "'nothing'",
85
+ }
86
+ - {
87
+ schema: org.gnome.settings-daemon.plugins.power,
88
+ key: sleep-inactive-battery-type,
89
+ value: 'nothing',
90
+ observed: "'nothing'",
91
+ }
92
+ - {
93
+ schema: org.gnome.settings-daemon.plugins.power,
94
+ key: idle-dim,
95
+ value: 'false',
96
+ observed: 'false',
97
+ }
98
+ when: gnome_schema.stat.exists
99
+ changed_when: false
100
+ register: gnome_settings
101
+ - name: Repair only changed designated-user settings
102
+ become: true
103
+ become_user: '{{ development_user }}'
104
+ ansible.builtin.command:
105
+ argv:
106
+ [
107
+ dbus-run-session,
108
+ '--',
109
+ gsettings,
110
+ set,
111
+ '{{ item.item.schema }}',
112
+ '{{ item.item.key }}',
113
+ '{{ item.item.value }}',
114
+ ]
115
+ environment:
116
+ HOME: '{{ development_home }}'
117
+ XDG_CONFIG_HOME: '{{ development_home }}/.config'
118
+ loop: '{{ gnome_settings.results }}'
119
+ when: gnome_schema.stat.exists and item.stdout | default('') != item.item.observed
120
+ changed_when: true
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ // The npm entry point: `npx @crvouga/dev-fleet <command>`. The CLI is TypeScript
3
+ // run by Bun, and Node refuses to run TypeScript from node_modules, so this
4
+ // plain-JavaScript launcher hands the arguments to Bun and nothing else.
5
+ import { spawnSync } from 'node:child_process';
6
+ import { createRequire } from 'node:module';
7
+ import process from 'node:process';
8
+ import { fileURLToPath, URL } from 'node:url';
9
+
10
+ const cli = fileURLToPath(new URL('../src/cli/cli.ts', import.meta.url));
11
+
12
+ /** The Bun this release pins (a dependency), then any Bun on PATH. The
13
+ * dependency's binary is a stub when its install script was skipped.
14
+ */
15
+ function bunCandidates() {
16
+ const candidates = ['bun'];
17
+ try {
18
+ candidates.unshift(
19
+ createRequire(import.meta.url).resolve('bun/bin/bun.exe')
20
+ );
21
+ } catch {
22
+ // Not installed beside this package.
23
+ }
24
+ return candidates;
25
+ }
26
+
27
+ for (const bun of bunCandidates()) {
28
+ const child = spawnSync(bun, [cli, ...process.argv.slice(2)], {
29
+ stdio: 'inherit',
30
+ });
31
+ if (!child.error) process.exit(child.status ?? 1);
32
+ }
33
+ process.stderr.write(
34
+ 'dev-fleet needs Bun and could not start it. Install it from https://bun.sh and retry.\n'
35
+ );
36
+ process.exit(127);
@@ -0,0 +1,50 @@
1
+ # Native Windows, PowerShell 5.1+. Download and run this file (not a bash script).
2
+ param([string]$WorkspaceDir = "$HOME\workspace")
3
+ $ErrorActionPreference = 'Stop'
4
+ $MiseVersion = '2026.10.24'
5
+ function Invoke-Checked([string]$Program, [string[]]$Arguments) {
6
+ & $Program @Arguments
7
+ if ($LASTEXITCODE -ne 0) { throw "$Program failed with exit code $LASTEXITCODE" }
8
+ }
9
+ if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
10
+ if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
11
+ throw 'Install Git for Windows (https://git-scm.com/download/win), reopen PowerShell and rerun. WinGet is available through Microsoft App Installer.'
12
+ }
13
+ Invoke-Checked winget @('install', '--id', 'Git.Git', '--exact', '--source', 'winget', '--accept-source-agreements', '--accept-package-agreements')
14
+ $env:Path = [Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('Path', 'User')
15
+ if (-not (Get-Command git -ErrorAction SilentlyContinue)) { throw 'Reopen PowerShell and rerun to pick up Git on PATH.' }
16
+ }
17
+ $LocalRoot = Join-Path $PSScriptRoot '..'
18
+ if (Test-Path (Join-Path $LocalRoot 'mise.toml')) {
19
+ $WorkspaceDir = (Resolve-Path $LocalRoot).Path
20
+ } else {
21
+ if (-not (Test-Path (Join-Path $WorkspaceDir '.git'))) {
22
+ Invoke-Checked git @('clone', 'https://github.com/crvouga/dev-fleet.git', $WorkspaceDir)
23
+ }
24
+ $Origin = & git -C $WorkspaceDir remote get-url origin
25
+ if ($LASTEXITCODE -ne 0 -or $Origin -ne 'https://github.com/crvouga/dev-fleet.git') { throw "Unexpected checkout at $WorkspaceDir; choose an empty -WorkspaceDir." }
26
+ }
27
+ # The baseline uses Windows x64; ARM machines can use an x64 PowerShell/process.
28
+ if ($env:PROCESSOR_ARCHITECTURE -ne 'AMD64') { throw 'Use Windows x64 PowerShell. Native Windows ARM64 tool coverage is not yet validated.' }
29
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
30
+ $Temp = Join-Path ([IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString())
31
+ New-Item -ItemType Directory -Path $Temp | Out-Null
32
+ try {
33
+ $Asset = "mise-v$MiseVersion-windows-x64.zip"
34
+ $Base = "https://github.com/jdx/mise/releases/download/v$MiseVersion"
35
+ Invoke-WebRequest "$Base/$Asset" -OutFile "$Temp\$Asset" -UseBasicParsing
36
+ Invoke-WebRequest "$Base/SHASUMS256.txt" -OutFile "$Temp\SHASUMS256.txt" -UseBasicParsing
37
+ $Line = Get-Content "$Temp\SHASUMS256.txt" | Where-Object { $_ -match ('\s+\./' + [regex]::Escape($Asset) + '$') }
38
+ if (-not $Line) { throw 'Missing mise checksum.' }
39
+ $Expected = ($Line -split '\s+')[0]
40
+ if ((Get-FileHash "$Temp\$Asset" -Algorithm SHA256).Hash -ne $Expected) { throw 'mise checksum mismatch.' }
41
+ Expand-Archive "$Temp\$Asset" -DestinationPath $Temp
42
+ $Bin = "$HOME\.local\bin"
43
+ New-Item -ItemType Directory -Force -Path $Bin | Out-Null
44
+ Copy-Item "$Temp\mise\bin\*.exe" $Bin -Force
45
+ $env:Path = "$Bin;$env:Path"
46
+ Set-Location $WorkspaceDir
47
+ Invoke-Checked mise @('trust', $WorkspaceDir)
48
+ Invoke-Checked mise @('install', '--locked', 'bun')
49
+ Invoke-Checked mise @('exec', '--locked', 'bun', '--', 'bun', 'bootstrap/setup.ts')
50
+ } finally { Remove-Item $Temp -Recurse -Force }
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env bash
2
+ # Download to a file and run with bash; also works directly from a checkout.
3
+ set -euo pipefail
4
+ MISE_VERSION=2026.10.24
5
+ repo=https://github.com/crvouga/dev-fleet.git
6
+ os=$(uname -s)
7
+ case "$os" in
8
+ Darwin) platform=macos ;;
9
+ Linux) platform=linux ;;
10
+ *) echo 'Use bootstrap.ps1 for native Windows.' >&2; exit 1 ;;
11
+ esac
12
+ case "$(uname -m)" in
13
+ arm64|aarch64) arch=arm64 ;;
14
+ x86_64|amd64) arch=x64 ;;
15
+ *) echo 'Supported: x64 and arm64.' >&2; exit 1 ;;
16
+ esac
17
+ # Git, TLS roots and extraction are OS prerequisites, outside the mise lock.
18
+ if ! git --version >/dev/null 2>&1; then
19
+ case "$os" in
20
+ Darwin)
21
+ xcode-select --install || true
22
+ echo 'Finish installing Apple Command Line Tools, then rerun this script.' >&2
23
+ exit 1 ;;
24
+ Linux)
25
+ if command -v apt-get >/dev/null; then
26
+ sudo apt-get update
27
+ sudo apt-get install -y git curl ca-certificates unzip xz-utils build-essential
28
+ elif command -v dnf >/dev/null; then
29
+ sudo dnf install -y git curl ca-certificates unzip xz gcc gcc-c++ make
30
+ else
31
+ echo 'Install git, curl, CA certificates, unzip, xz and a C/C++ toolchain, then rerun.' >&2
32
+ exit 1
33
+ fi ;;
34
+ esac
35
+ fi
36
+ for tool in curl tar unzip; do
37
+ command -v "$tool" >/dev/null || { echo "Install $tool, then rerun." >&2; exit 1; }
38
+ done
39
+ script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
40
+ if [[ -f "$script_dir/../mise.toml" ]]; then
41
+ checkout=$(cd "$script_dir/.." && pwd)
42
+ else
43
+ checkout=${WORKSPACE_DIR:-"$HOME/workspace"}
44
+ if [[ ! -d "$checkout/.git" ]]; then git clone "$repo" "$checkout"; fi
45
+ [[ "$(git -C "$checkout" remote get-url origin)" == "$repo" ]] || {
46
+ echo "Unexpected checkout at $checkout; choose an empty WORKSPACE_DIR." >&2; exit 1;
47
+ }
48
+ fi
49
+ cd "$checkout"
50
+ # Reuse an observed matching managed executable. Repeated reconciliation must
51
+ # not download/reinstall mise solely because a previous run finished.
52
+ installed_mise=$("$HOME/.local/bin/mise" --version 2>/dev/null | awk '{print $1}') || installed_mise=''
53
+ if [[ "$installed_mise" != "$MISE_VERSION" ]]; then
54
+ temp=$(mktemp -d)
55
+ trap 'rm -rf "$temp"' EXIT
56
+ asset="mise-v${MISE_VERSION}-${platform}-${arch}.tar.gz"
57
+ # Alpine/musl is not part of the supported workstation baseline.
58
+ base="https://github.com/jdx/mise/releases/download/v${MISE_VERSION}"
59
+ curl -fsSL "$base/$asset" -o "$temp/$asset"
60
+ curl -fsSL "$base/SHASUMS256.txt" -o "$temp/SHASUMS256.txt"
61
+ (cd "$temp"; awk -v file="./$asset" '$2 == file { print }' SHASUMS256.txt > check.txt
62
+ test -s check.txt
63
+ if command -v sha256sum >/dev/null; then sha256sum -c check.txt; else shasum -a 256 -c check.txt; fi)
64
+ tar -xzf "$temp/$asset" -C "$temp"
65
+ mkdir -p "$HOME/.local/bin"
66
+ install -m 0755 "$temp/mise/bin/mise" "$HOME/.local/bin/mise"
67
+ fi
68
+ export PATH="$HOME/.local/bin:$PATH"
69
+ # Trust the checkout directory, not only its file, so `mise trust --show` there
70
+ # reports trusted.
71
+ mise trust "$checkout"
72
+ mise install --locked bun
73
+ mise exec --locked bun -- bun bootstrap/setup.ts "$@"
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+ # The devfleet command. ~/.local/bin/devfleet links here; nothing is copied.
3
+ set -eu
4
+ self=$(readlink -f "$0")
5
+ root=$(cd "$(dirname "$self")/.." && pwd)
6
+ export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:$PATH"
7
+ exec bun "$root/src/cli/cli.ts" "$@"
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+ # Private macOS workstation installer; invoke with dev-fleet tools docker-setup.
3
+ set -euo pipefail
4
+ cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
5
+ case "$(uname -s)/$(uname -m)" in
6
+ Darwin/arm64) docker_arch=arm64 ;;
7
+ Darwin/x86_64) docker_arch=amd64 ;;
8
+ *)
9
+ echo 'Docker Desktop setup is supported here only on macOS.' >&2
10
+ exit 2
11
+ ;;
12
+ esac
13
+ if [[ ! -d /Applications/Docker.app ]]; then
14
+ read -r docker_build docker_hash < <(mise exec -- bun -e '
15
+ const {vars}=Bun.TOML.parse(await Bun.file("mise.toml").text());
16
+ console.log(vars.docker_desktop_build,vars["docker_desktop_"+process.argv[1]+"_sha256"]);
17
+ ' "$docker_arch")
18
+ [[ "$docker_build" =~ ^[0-9]+$ && "$docker_hash" =~ ^[a-f0-9]{64}$ ]]
19
+ docker_cache="${XDG_CACHE_HOME:-$HOME/.cache}/personal/docker-desktop/$docker_build/$docker_arch"
20
+ mkdir -p "$docker_cache"
21
+ docker_image="$docker_cache/Docker.dmg"
22
+ if ! echo "$docker_hash $docker_image" | shasum -a 256 -c - >/dev/null 2>&1; then
23
+ curl --fail --location --retry 3 "https://desktop.docker.com/mac/main/$docker_arch/$docker_build/Docker.dmg" -o "$docker_image"
24
+ fi
25
+ echo "$docker_hash $docker_image" | shasum -a 256 -c -
26
+ docker_mount=$(mktemp -d)
27
+ trap 'hdiutil detach "$docker_mount" >/dev/null 2>&1 || true; rmdir "$docker_mount" 2>/dev/null || true' EXIT
28
+ hdiutil attach "$docker_image" -nobrowse -mountpoint "$docker_mount"
29
+ codesign --verify --deep --strict "$docker_mount/Docker.app"
30
+ ditto "$docker_mount/Docker.app" /Applications/Docker.app
31
+ fi
32
+ open -a /Applications/Docker.app
33
+ printf '%s\n' 'Docker Desktop opened. Complete any first-run agreement/setup in the app, then rerun dev-fleet tools check --docker.'
@@ -0,0 +1,189 @@
1
+ import { workerTools } from './worker-tools';
2
+ /** Read-only toolchain checks; no logins, installations or changes to Docker. */
3
+ import { spawnSync } from 'node:child_process';
4
+ import { readFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { homedir, tmpdir } from 'node:os';
7
+ import { AGENT_WORKTREE_ROOTS, workspaceRoot } from './global-config';
8
+
9
+ type Tool = string | { version: string; lazy?: boolean; os?: string[] };
10
+ type Config = {
11
+ tools: Record<string, Tool>;
12
+ vars?: { worker_tools?: unknown };
13
+ };
14
+ export type Check = {
15
+ id: string;
16
+ status: 'pass' | 'warn' | 'fail';
17
+ detail: string;
18
+ };
19
+ export type Runner = (command: string[]) => { ok: boolean; output: string };
20
+ const COMMANDS: Record<string, string> = {
21
+ 'npm:@yarnpkg/cli-dist': 'yarn',
22
+ 'npm:vercel': 'vercel',
23
+ 'npm:wrangler': 'wrangler',
24
+ 'aqua:anomalyco/opencode': 'opencode',
25
+ 'docker-cli': 'docker',
26
+ opentofu: 'tofu',
27
+ ripgrep: 'rg',
28
+ rust: 'rustc',
29
+ 'npm:@devcontainers/cli': 'devcontainer',
30
+ 'http:grok': 'grok',
31
+ 'http:antigravity': 'antigravity',
32
+ };
33
+ const VERSION_ARGS: Record<string, string[]> = {
34
+ go: ['version'],
35
+ gitleaks: ['version'],
36
+ kubectl: ['version', '--client=true', '--output=json'],
37
+ helm: ['version', '--short'],
38
+ };
39
+ const run: Runner = (command) => {
40
+ const result = spawnSync(command[0]!, command.slice(1), {
41
+ encoding: 'utf8',
42
+ timeout: 30_000,
43
+ windowsHide: true,
44
+ cwd: tmpdir(),
45
+ });
46
+ return {
47
+ ok: result.status === 0,
48
+ output: `${result.stdout ?? ''}${result.stderr ?? ''}`.trim(),
49
+ };
50
+ };
51
+
52
+ /** mise's `os` names for the platforms it installs a tool on. */
53
+ const MISE_OS: Record<string, string> = {
54
+ darwin: 'macos',
55
+ linux: 'linux',
56
+ win32: 'windows',
57
+ };
58
+ /** Installed on Windows by a native installer (native-aws.ts), not by mise. */
59
+ const NATIVE_WINDOWS = new Set(['aws']);
60
+
61
+ function unavailable(id: string, tool: Tool, platform: string): boolean {
62
+ if (typeof tool === 'string' || !tool.os) return false;
63
+ if (platform === 'win32' && NATIVE_WINDOWS.has(id)) return false;
64
+ return !tool.os.includes(MISE_OS[platform] ?? platform);
65
+ }
66
+
67
+ export function toolChecks(
68
+ tools: Record<string, Tool>,
69
+ execute: Runner = run,
70
+ platform: string = process.platform
71
+ ): Check[] {
72
+ const checks: Check[] = [];
73
+ for (const [id, tool] of Object.entries(tools)) {
74
+ if (typeof tool !== 'string' && tool.lazy) continue;
75
+ // A tool pinned only for other platforms has no artifact to verify here.
76
+ if (unavailable(id, tool, platform)) continue;
77
+ const version = typeof tool === 'string' ? tool : tool.version;
78
+ const command = COMMANDS[id] ?? id;
79
+ const result = execute([command, ...(VERSION_ARGS[id] ?? ['--version'])]);
80
+ const exact = new RegExp(
81
+ `(?:^|[^0-9])${version.replaceAll('.', '\\.')}($|(?![0-9]|\\.[0-9]))`
82
+ );
83
+ const matches = result.ok && exact.test(result.output);
84
+ checks.push({
85
+ id,
86
+ status: matches ? 'pass' : 'fail',
87
+ detail: matches
88
+ ? version
89
+ : `Expected ${command} ${version}; run dev-fleet tools sync and reopen the shell. Check for competing PATH entries.`,
90
+ });
91
+ }
92
+ for (const command of ['git', 'npm']) {
93
+ const result = execute([command, '--version']);
94
+ checks.push({
95
+ id: command,
96
+ status: result.ok ? 'pass' : 'fail',
97
+ detail: result.ok
98
+ ? result.output
99
+ : `Missing ${command}; rerun the OS bootstrap.`,
100
+ });
101
+ }
102
+ return checks;
103
+ }
104
+
105
+ export function dockerChecks(
106
+ required: boolean,
107
+ execute: Runner = run
108
+ ): Check[] {
109
+ return [
110
+ ['docker-engine', ['docker', 'info', '--format', '{{.ServerVersion}}']],
111
+ ['docker-compose', ['docker', 'compose', 'version']],
112
+ ['docker-buildx', ['docker', 'buildx', 'version']],
113
+ ].map(([id, command]) => {
114
+ const result = execute(command as string[]);
115
+ return {
116
+ id: id as string,
117
+ status: result.ok ? 'pass' : required ? 'fail' : 'warn',
118
+ detail: result.ok
119
+ ? result.output
120
+ : 'Install/start Docker Desktop or Docker Engine with Compose + Buildx. See docs/machine-setup.md.',
121
+ };
122
+ });
123
+ }
124
+
125
+ /** Agent worktree roots must be trusted, or every mise shim fails there. */
126
+ export function worktreeTrustCheck(
127
+ execute: Runner = run,
128
+ home: string = homedir()
129
+ ): Check {
130
+ const expand = (path: string) =>
131
+ path === '~' || path.startsWith('~/') ? home + path.slice(1) : path;
132
+ const result = execute(['mise', 'settings', 'get', 'trusted_config_paths']);
133
+ let trusted: string[] = [];
134
+ try {
135
+ const parsed: unknown = result.ok ? JSON.parse(result.output) : [];
136
+ if (Array.isArray(parsed))
137
+ trusted = parsed.filter((p): p is string => typeof p === 'string');
138
+ } catch {
139
+ trusted = [];
140
+ }
141
+ const have = new Set(trusted.map(expand));
142
+ const missing = AGENT_WORKTREE_ROOTS.filter(
143
+ (root) => !have.has(expand(root))
144
+ );
145
+ return {
146
+ id: 'agent-worktree-trust',
147
+ status: missing.length ? 'fail' : 'pass',
148
+ detail: missing.length
149
+ ? `mise does not trust ${missing.join(', ')}; agent CLIs fail in new worktrees there. Run dev-fleet tools sync.`
150
+ : AGENT_WORKTREE_ROOTS.join(', '),
151
+ };
152
+ }
153
+
154
+ export function checkMachine(requireDocker = false, worker = false): Check[] {
155
+ const config = Bun.TOML.parse(
156
+ readFileSync(join(workspaceRoot, 'mise.toml'), 'utf8')
157
+ ) as Config;
158
+ const auth = run(['gh', 'auth', 'status']);
159
+ return [
160
+ ...toolChecks(
161
+ worker
162
+ ? workerTools(config.tools, config.vars?.worker_tools)
163
+ : config.tools
164
+ ),
165
+ ...dockerChecks(requireDocker),
166
+ worktreeTrustCheck(),
167
+ {
168
+ id: 'github-auth',
169
+ status: auth.ok ? 'pass' : 'warn',
170
+ detail: auth.ok
171
+ ? 'Authenticated'
172
+ : 'Run gh auth login, then gh auth setup-git.',
173
+ },
174
+ ];
175
+ }
176
+
177
+ if (import.meta.main) {
178
+ const checks = checkMachine(
179
+ process.argv.includes('--docker'),
180
+ process.argv.includes('--worker')
181
+ );
182
+ const ok = checks.every((check) => check.status !== 'fail');
183
+ if (process.argv.includes('--json'))
184
+ console.log(JSON.stringify({ ok, checks }, null, 2));
185
+ else
186
+ for (const check of checks)
187
+ console.log(`${check.status.toUpperCase()} ${check.id}: ${check.detail}`);
188
+ process.exitCode = ok ? 0 : 1;
189
+ }
@@ -0,0 +1,76 @@
1
+ #!/bin/sh
2
+ # Every fleet-sync on the manager runs through this, whether started there or
3
+ # handed off from a worker over strict SSH. Runs fleet-sync from a clean,
4
+ # fleet-owned checkout of the exact approved revision, so the result never
5
+ # depends on the working tree it was started in (uncommitted edits, another
6
+ # branch, a removed worktree). Checkouts are reused when warm and pruned to the
7
+ # three most recent plus the one T3's providers launch from. Never touches the
8
+ # human's working tree.
9
+ set -eu
10
+ revision=$1
11
+ shift
12
+ case "$revision" in *[!a-f0-9]*|'') exit 2;; esac
13
+ [ "${#revision}" = 40 ] || exit 2
14
+ say() { printf 'fleet-sync (manager): %s\n' "$1" >&2; }
15
+ # The dev-fleet clone to take the revision from: named by the caller, or the
16
+ # one the devfleet launcher is linked into.
17
+ repo=${DEVFLEET_REPO:-}
18
+ if [ -z "$repo" ]; then
19
+ launcher=$(command -v devfleet) || { say 'set DEVFLEET_REPO to a dev-fleet clone, or install the launcher with sh bootstrap/install-launcher.sh.'; exit 3; }
20
+ repo=$(dirname "$(readlink -f "$launcher")")
21
+ fi
22
+ repo=$(git -C "$repo" rev-parse --show-toplevel) || { say 'cannot locate the dev-fleet clone on the manager.'; exit 3; }
23
+ runs="$HOME/.local/share/personal/fleet/manager-runs"
24
+ dir="$runs/$revision"
25
+ ready() {
26
+ [ "$(git -C "$dir" rev-parse HEAD 2>/dev/null)" = "$revision" ] &&
27
+ [ -z "$(git -C "$dir" status --porcelain 2>/dev/null)" ]
28
+ }
29
+ if ! ready; then
30
+ git -C "$repo" cat-file -e "$revision^{commit}" 2>/dev/null || git -C "$repo" fetch --quiet origin || true
31
+ git -C "$repo" cat-file -e "$revision^{commit}" 2>/dev/null || { say "revision $revision is neither on the manager nor on origin; push it first."; exit 4; }
32
+ mkdir -p "$runs"
33
+ chmod 700 "$runs"
34
+ # A fleet-owned, disposable run checkout: recreate it when stale or dirty.
35
+ if [ -e "$dir" ]; then git -C "$repo" worktree remove --force "$dir" 2>/dev/null || rm -rf -- "$dir"; fi
36
+ git -C "$repo" worktree prune
37
+ git -C "$repo" worktree add --quiet --detach "$dir" "$revision"
38
+ fi
39
+ touch "$dir"
40
+ cd "$dir"
41
+ mise trust --quiet "$dir" >/dev/null 2>&1 || true
42
+ # The manager runs the same locked toolchain (and so the same agent versions)
43
+ # as every worker: publish this revision's global config, install it and the
44
+ # frozen dependencies. Warm: the toolchain is skipped when the same inputs
45
+ # (tool pins, locks, setup code) were installed within a day, so a revision
46
+ # that changes none of them only installs its own dependencies.
47
+ # Stamp and log sit beside the checkout: inside it they would make it dirty.
48
+ stamp="$runs/toolchain-ready"
49
+ log="$runs/$revision.setup.log"
50
+ key=$(git rev-parse HEAD:mise.toml HEAD:mise.lock HEAD:.mise HEAD:bun.lock HEAD:package.json HEAD:bootstrap 2>/dev/null | tr '\n' ' ')
51
+ [ -n "$key" ] || key=$revision
52
+ failed() {
53
+ tail -n 20 "$log" >&2
54
+ say "toolchain or dependency install failed in the run checkout; full log: $log"
55
+ exit 5
56
+ }
57
+ if [ "$(cat "$stamp" 2>/dev/null)" != "$key" ] || [ -z "$(find "$stamp" -mmin -1440 2>/dev/null)" ]; then
58
+ rm -f -- "$stamp"
59
+ mise exec --locked bun -- bun bootstrap/setup.ts --tools-only >"$log" 2>&1 || failed
60
+ printf '%s' "$key" >"$stamp"
61
+ fi
62
+ if [ ! -d node_modules ]; then
63
+ mise exec --locked bun -- bun install --frozen-lockfile --silent >>"$log" 2>&1 || failed
64
+ fi
65
+ # Keep the three most recent run checkouts (names are commit SHAs) and the
66
+ # applied one: fleet/current points at it and T3's providers launch from there.
67
+ applied=$(basename "$(readlink "$(dirname "$runs")/current" 2>/dev/null || true)")
68
+ # shellcheck disable=SC2012
69
+ for old in $(ls -1td "$runs"/*/ | tail -n +4); do
70
+ [ "$(basename "$old")" != "$applied" ] || continue
71
+ git -C "$repo" worktree remove --force "$old" 2>/dev/null || true
72
+ rm -f -- "${old%/}.setup.log"
73
+ done
74
+ git -C "$repo" worktree prune
75
+ export PERSONAL_WORKSPACE="$dir" PERSONAL_FLEET_RUN_CHECKOUT="$revision"
76
+ exec mise exec --locked bun -- bun src/cli/cli.ts fleet-sync --revision "$revision" "$@"