@crvouga/dev-fleet 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/.mise/locks/aws-cdk/2.1146.1/aube-lock.yaml +24 -0
  2. package/.mise/locks/aws-cdk/2.1146.1/package.json +7 -0
  3. package/.mise/locks/azure-cli/2.91.0~a837e014/pyproject.toml +5 -0
  4. package/.mise/locks/azure-cli/2.91.0~a837e014/uv.lock +2794 -0
  5. package/.mise/locks/gemini/0.66.0~84aac8e6/aube-lock.yaml +123 -0
  6. package/.mise/locks/gemini/0.66.0~84aac8e6/package.json +12 -0
  7. package/.mise/locks/npm-devcontainers-cli/0.90.0/aube-lock.yaml +24 -0
  8. package/.mise/locks/npm-devcontainers-cli/0.90.0/package.json +7 -0
  9. package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/aube-lock.yaml +1432 -0
  10. package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/package.json +7 -0
  11. package/.mise/locks/npm-vercel/65.8.0/aube-lock.yaml +3595 -0
  12. package/.mise/locks/npm-vercel/65.8.0/package.json +7 -0
  13. package/.mise/locks/npm-wrangler/4.156.0/aube-lock.yaml +895 -0
  14. package/.mise/locks/npm-wrangler/4.156.0/package.json +7 -0
  15. package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/aube-lock.yaml +24 -0
  16. package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/package.json +7 -0
  17. package/LICENSE +21 -0
  18. package/README.md +357 -2
  19. package/agents/antigravity-proxy.ts +3 -0
  20. package/agents/audit.md +89 -0
  21. package/agents/claude-proxy +5 -0
  22. package/agents/claude-proxy.ts +3 -0
  23. package/agents/codex-proxy.ts +3 -0
  24. package/agents/grok-proxy.ts +3 -0
  25. package/agents/managed-proxy.ts +55 -0
  26. package/agents/opencode-proxy.ts +3 -0
  27. package/agents/registry.json +209 -0
  28. package/agents/registry.schema.json +149 -0
  29. package/agents/setup.md +158 -0
  30. package/agents/t3.json +40 -0
  31. package/ansible/macos/manager.yml +30 -0
  32. package/ansible/macos/policy.json +9 -0
  33. package/ansible/monitoring/README.md +215 -0
  34. package/ansible/monitoring/agent-darwin.yml +198 -0
  35. package/ansible/monitoring/agent.yml +172 -0
  36. package/ansible/monitoring/beszel-agent.service +42 -0
  37. package/ansible/monitoring/compose.yml +74 -0
  38. package/ansible/monitoring/hub-darwin.yml +102 -0
  39. package/ansible/monitoring/hub-route-darwin.yml +51 -0
  40. package/ansible/monitoring/lock.json +16 -0
  41. package/ansible/net/remote-probe.sh +13 -0
  42. package/ansible/pyproject.toml +12 -0
  43. package/ansible/ubuntu/authorize-sudo.sh +51 -0
  44. package/ansible/ubuntu/baseline.yml +89 -0
  45. package/ansible/ubuntu/disk-gc.py +336 -0
  46. package/ansible/ubuntu/disk-guard.py +104 -0
  47. package/ansible/ubuntu/policy.json +52 -0
  48. package/ansible/ubuntu/recovery.yml +15 -0
  49. package/ansible/ubuntu/t3/policy.json +7 -0
  50. package/ansible/ubuntu/t3-desktop.yml +220 -0
  51. package/ansible/ubuntu/t3-health.sh +33 -0
  52. package/ansible/ubuntu/t3-policy.yml +76 -0
  53. package/ansible/ubuntu/t3-route.yml +28 -0
  54. package/ansible/ubuntu/tailscale.yml +78 -0
  55. package/ansible/ubuntu/tasks/credentials.yml +47 -0
  56. package/ansible/ubuntu/tasks/docker.yml +31 -0
  57. package/ansible/ubuntu/tasks/packages.yml +56 -0
  58. package/ansible/ubuntu/tasks/power.yml +51 -0
  59. package/ansible/ubuntu/tasks/recovery.yml +39 -0
  60. package/ansible/ubuntu/tasks/resources.yml +312 -0
  61. package/ansible/ubuntu/tasks/services.yml +31 -0
  62. package/ansible/ubuntu/tasks/ssh.yml +71 -0
  63. package/ansible/ubuntu/tasks/user.yml +120 -0
  64. package/ansible/uv.lock +190 -0
  65. package/bin/devfleet.mjs +36 -0
  66. package/bootstrap/bootstrap.ps1 +50 -0
  67. package/bootstrap/bootstrap.sh +80 -0
  68. package/bootstrap/devfleet +7 -0
  69. package/bootstrap/docker-desktop.sh +33 -0
  70. package/bootstrap/doctor.ts +234 -0
  71. package/bootstrap/fleet-manager-run.sh +103 -0
  72. package/bootstrap/fleet-remote-bootstrap.sh +258 -0
  73. package/bootstrap/global-config.ts +171 -0
  74. package/bootstrap/install-launcher.sh +10 -0
  75. package/bootstrap/native-aws.ts +86 -0
  76. package/bootstrap/native-t3.ts +154 -0
  77. package/bootstrap/npm.ts +358 -0
  78. package/bootstrap/release-version.ts +157 -0
  79. package/bootstrap/setup-status.ts +51 -0
  80. package/bootstrap/setup.ts +88 -0
  81. package/bootstrap/shell-path.ts +105 -0
  82. package/bootstrap/verify-t3-archive.py +49 -0
  83. package/bootstrap/worker-auth.ts +65 -0
  84. package/bootstrap/worker-github.ts +47 -0
  85. package/bootstrap/worker-tools.ts +24 -0
  86. package/cliproxy/AGENTS.md +11 -0
  87. package/cliproxy/README.md +269 -0
  88. package/cliproxy/clients/bootstrap.py +143 -0
  89. package/cliproxy/clients/launcher.py +57 -0
  90. package/cliproxy/compose.yaml +90 -0
  91. package/cliproxy/config.template.yaml +70 -0
  92. package/cliproxy/docs/cloud-adoption.md +16 -0
  93. package/cliproxy/docs/deployment.png +0 -0
  94. package/cliproxy/docs/deployment.svg +109 -0
  95. package/cliproxy/docs/health-workflow.example.yml +185 -0
  96. package/cliproxy/docs/migration.md +131 -0
  97. package/cliproxy/docs/model-health-report.schema.json +364 -0
  98. package/cliproxy/docs/model-health.md +393 -0
  99. package/cliproxy/image.lock.json +5 -0
  100. package/cliproxy/launchd/dev.devfleet.cliproxy-backup-check.plist +38 -0
  101. package/cliproxy/launchd/dev.devfleet.cliproxy-backup.plist +48 -0
  102. package/cliproxy/launchd/dev.devfleet.cliproxy-fleet.plist +32 -0
  103. package/cliproxy/launchd/dev.devfleet.cliproxy-gc.plist +38 -0
  104. package/cliproxy/launchd/dev.devfleet.cliproxy-monitor.plist +29 -0
  105. package/cliproxy/launchd/dev.devfleet.cliproxy-vault-renew.plist +31 -0
  106. package/cliproxy/models.json +1618 -0
  107. package/cliproxy/requirements.lock +78 -0
  108. package/cliproxy/scripts/accounts.py +38 -0
  109. package/cliproxy/scripts/audit.py +123 -0
  110. package/cliproxy/scripts/backup +234 -0
  111. package/cliproxy/scripts/check.py +34 -0
  112. package/cliproxy/scripts/checkpoint.py +226 -0
  113. package/cliproxy/scripts/cli.py +64 -0
  114. package/cliproxy/scripts/cliproxy_host.py +257 -0
  115. package/cliproxy/scripts/cloud_state.py +150 -0
  116. package/cliproxy/scripts/container-health +6 -0
  117. package/cliproxy/scripts/docker +24 -0
  118. package/cliproxy/scripts/external-health.py +79 -0
  119. package/cliproxy/scripts/fleet_config.py +43 -0
  120. package/cliproxy/scripts/fleet_sync.py +274 -0
  121. package/cliproxy/scripts/initialize +84 -0
  122. package/cliproxy/scripts/install-dependencies +48 -0
  123. package/cliproxy/scripts/install.py +406 -0
  124. package/cliproxy/scripts/model_health.py +16 -0
  125. package/cliproxy/scripts/modelhealth/__init__.py +5 -0
  126. package/cliproxy/scripts/modelhealth/checks.py +429 -0
  127. package/cliproxy/scripts/modelhealth/cli.py +117 -0
  128. package/cliproxy/scripts/modelhealth/config.py +186 -0
  129. package/cliproxy/scripts/modelhealth/emulator.py +536 -0
  130. package/cliproxy/scripts/modelhealth/expect.py +88 -0
  131. package/cliproxy/scripts/modelhealth/findings.py +183 -0
  132. package/cliproxy/scripts/modelhealth/protocol.py +330 -0
  133. package/cliproxy/scripts/modelhealth/redact.py +28 -0
  134. package/cliproxy/scripts/modelhealth/report.py +150 -0
  135. package/cliproxy/scripts/modelhealth/runner.py +390 -0
  136. package/cliproxy/scripts/modelhealth/scenario.py +131 -0
  137. package/cliproxy/scripts/modelhealth/schema.py +63 -0
  138. package/cliproxy/scripts/modelhealth/simulate.py +111 -0
  139. package/cliproxy/scripts/modelhealth/transport.py +213 -0
  140. package/cliproxy/scripts/monitor +68 -0
  141. package/cliproxy/scripts/ops +527 -0
  142. package/cliproxy/scripts/public-endpoint +127 -0
  143. package/cliproxy/scripts/recover_node.py +177 -0
  144. package/cliproxy/scripts/restore-verify +60 -0
  145. package/cliproxy/scripts/retention.py +144 -0
  146. package/cliproxy/scripts/service-start +7 -0
  147. package/cliproxy/scripts/validate-config +56 -0
  148. package/cliproxy/scripts/vault-access.py +57 -0
  149. package/cliproxy/scripts/vault-backup-login +55 -0
  150. package/cliproxy/scripts/vault-recovery +56 -0
  151. package/cliproxy/scripts/vault-renew +30 -0
  152. package/cliproxy/systemd/cliproxy-backup-check.service +10 -0
  153. package/cliproxy/systemd/cliproxy-backup-check.timer +8 -0
  154. package/cliproxy/systemd/cliproxy-backup.service +13 -0
  155. package/cliproxy/systemd/cliproxy-backup.timer +8 -0
  156. package/cliproxy/systemd/cliproxy-fleet.service +17 -0
  157. package/cliproxy/systemd/cliproxy-gc.service +13 -0
  158. package/cliproxy/systemd/cliproxy-gc.timer +8 -0
  159. package/cliproxy/systemd/cliproxy-monitor.service +8 -0
  160. package/cliproxy/systemd/cliproxy-monitor.timer +7 -0
  161. package/cliproxy/systemd/cliproxy-vault-renew.service +8 -0
  162. package/cliproxy/systemd/cliproxy-vault-renew.timer +7 -0
  163. package/cliproxy/vendor/LICENSE +22 -0
  164. package/cliproxy/vendor/config.upstream.yaml +1349 -0
  165. package/docs/consumer-setup.md +408 -0
  166. package/docs/declared.md +89 -0
  167. package/docs/machine-setup.md +399 -0
  168. package/docs/operations.md +276 -0
  169. package/docs/releasing.md +103 -0
  170. package/docs/state.md +254 -0
  171. package/docs/topology.md +67 -0
  172. package/docs/ubuntu-sync.md +351 -0
  173. package/docs/worker-contract.md +67 -0
  174. package/examples/fleet.json +100 -0
  175. package/examples/roster.json +13 -0
  176. package/llms.txt +35 -0
  177. package/mise.lock +1521 -0
  178. package/mise.toml +110 -0
  179. package/package.json +74 -4
  180. package/schema/fleet.schema.json +420 -0
  181. package/schema/roster.schema.json +77 -0
  182. package/src/agents/account-relay.ts +111 -0
  183. package/src/agents/accounts.ts +85 -0
  184. package/src/agents/audit.ts +77 -0
  185. package/src/agents/cli.ts +13 -0
  186. package/src/agents/command.ts +204 -0
  187. package/src/agents/converge.ts +123 -0
  188. package/src/agents/doctor.ts +107 -0
  189. package/src/agents/errors.ts +2 -0
  190. package/src/agents/files.ts +73 -0
  191. package/src/agents/fleet-verify.ts +165 -0
  192. package/src/agents/launch.ts +290 -0
  193. package/src/agents/native-verify.ts +371 -0
  194. package/src/agents/provider-errors.ts +57 -0
  195. package/src/agents/registry.ts +61 -0
  196. package/src/agents/render.ts +110 -0
  197. package/src/agents/round-trip.ts +192 -0
  198. package/src/agents/secrets.ts +13 -0
  199. package/src/agents/toolchain.ts +34 -0
  200. package/src/agents/verify-cache.ts +103 -0
  201. package/src/agents/verify.ts +127 -0
  202. package/src/auth-checks.ts +163 -0
  203. package/src/cli/agent-guide.ts +129 -0
  204. package/src/cli/app.ts +122 -0
  205. package/src/cli/cli.ts +28 -0
  206. package/src/cli/command-index.ts +57 -0
  207. package/src/cli/contracts.ts +25 -0
  208. package/src/cli/fleet-reference.ts +17 -0
  209. package/src/cli/fleet-state.ts +499 -0
  210. package/src/cli/fleet-sync-checkout.ts +130 -0
  211. package/src/cli/fleet-sync-enrollment.ts +140 -0
  212. package/src/cli/fleet-sync-forward.ts +134 -0
  213. package/src/cli/fleet-sync-manager.ts +249 -0
  214. package/src/cli/fleet-sync-monitoring.ts +185 -0
  215. package/src/cli/fleet-sync-output.ts +317 -0
  216. package/src/cli/fleet-sync-privilege.ts +27 -0
  217. package/src/cli/fleet-sync-progress.ts +109 -0
  218. package/src/cli/fleet-sync-remote.ts +242 -0
  219. package/src/cli/fleet-sync-report.ts +26 -0
  220. package/src/cli/fleet-sync-schedule.ts +138 -0
  221. package/src/cli/fleet-sync-state.ts +86 -0
  222. package/src/cli/fleet-sync-vault.ts +166 -0
  223. package/src/cli/fleet-sync.ts +375 -0
  224. package/src/cli/fleet.ts +165 -0
  225. package/src/cli/managed-health.ts +219 -0
  226. package/src/cli/node-deployment.ts +34 -0
  227. package/src/cli/node-sync.ts +121 -0
  228. package/src/cli/node-t3.ts +31 -0
  229. package/src/cli/options.ts +141 -0
  230. package/src/cli/probe-source.ts +7 -0
  231. package/src/cli/program.ts +185 -0
  232. package/src/cli/project.ts +51 -0
  233. package/src/cli/register-fleet.ts +296 -0
  234. package/src/cli/registry.ts +185 -0
  235. package/src/cli/repair-apply.ts +67 -0
  236. package/src/cli/repair-guard.ts +11 -0
  237. package/src/cli/repair-inspect.ts +150 -0
  238. package/src/cli/repair-script.ts +76 -0
  239. package/src/cli/repair.ts +209 -0
  240. package/src/cli/roster.ts +105 -0
  241. package/src/cli/search.ts +35 -0
  242. package/src/cli/settings.ts +67 -0
  243. package/src/cli/source-operations.ts +102 -0
  244. package/src/cli/t3/command.ts +90 -0
  245. package/src/cli/t3/register.ts +55 -0
  246. package/src/cli/tailnet.ts +127 -0
  247. package/src/cli/ui-actions.ts +133 -0
  248. package/src/cli/ui-controller.ts +113 -0
  249. package/src/cli/ui-data.ts +106 -0
  250. package/src/cli/ui-execute.ts +163 -0
  251. package/src/cli/ui-repairs.ts +34 -0
  252. package/src/cli/ui-reports.ts +211 -0
  253. package/src/cli/ui-targets.ts +100 -0
  254. package/src/cli/ui-terminal.ts +109 -0
  255. package/src/cli/ui-types.ts +49 -0
  256. package/src/cli/ui-wizard.ts +101 -0
  257. package/src/cli/ui.ts +9 -0
  258. package/src/cli/worker-health-output.ts +37 -0
  259. package/src/config.ts +186 -0
  260. package/src/configure.ts +255 -0
  261. package/src/definition.ts +290 -0
  262. package/src/diagnostics.ts +92 -0
  263. package/src/doctor-check.ts +10 -0
  264. package/src/errors.ts +1 -0
  265. package/src/fingerprint.ts +120 -0
  266. package/src/fleet-config.ts +174 -0
  267. package/src/input-hashes.ts +73 -0
  268. package/src/machine/local-source.ts +52 -0
  269. package/src/machine/lock-error.ts +2 -0
  270. package/src/machine/platform.ts +44 -0
  271. package/src/machine/source.ts +68 -0
  272. package/src/machine/state.ts +173 -0
  273. package/src/machine-health.ts +95 -0
  274. package/src/managed-source.ts +47 -0
  275. package/src/manager-failure.ts +48 -0
  276. package/src/manager-sync.ts +405 -0
  277. package/src/manager.ts +62 -0
  278. package/src/monitoring/agent-evidence.ts +146 -0
  279. package/src/monitoring/apply.ts +183 -0
  280. package/src/monitoring/beszel.ts +198 -0
  281. package/src/monitoring/enroll.ts +111 -0
  282. package/src/monitoring/hub-access.ts +114 -0
  283. package/src/monitoring/hub-backup.ts +121 -0
  284. package/src/monitoring/hub-viewer.ts +142 -0
  285. package/src/monitoring/hub.ts +93 -0
  286. package/src/monitoring/manager-agent.ts +73 -0
  287. package/src/monitoring/manager-hub.ts +248 -0
  288. package/src/monitoring/remote.ts +65 -0
  289. package/src/network-definition.ts +83 -0
  290. package/src/node-converge-linux.ts +374 -0
  291. package/src/node-converge-shared.ts +83 -0
  292. package/src/node-converge.ts +40 -0
  293. package/src/onboard.ts +35 -0
  294. package/src/package-info.ts +45 -0
  295. package/src/platform/ansible-receipt.ts +128 -0
  296. package/src/platform/ansible.ts +66 -0
  297. package/src/platform/darwin.ts +203 -0
  298. package/src/platform/detect.ts +11 -0
  299. package/src/platform/fallback.ts +110 -0
  300. package/src/platform/index.ts +38 -0
  301. package/src/platform/launchd.ts +98 -0
  302. package/src/platform/linux.ts +139 -0
  303. package/src/platform/ps.ts +45 -0
  304. package/src/platform/resource-verify.ts +132 -0
  305. package/src/platform/systemd-user.ts +95 -0
  306. package/src/platform/t3-policy.ts +137 -0
  307. package/src/platform/t3-route.ts +68 -0
  308. package/src/platform/t3-service.ts +75 -0
  309. package/src/platform/types.ts +123 -0
  310. package/src/platform/ubuntu-audit.ts +66 -0
  311. package/src/platform/ubuntu-verify.ts +105 -0
  312. package/src/platform/windows.ts +148 -0
  313. package/src/power-recovery.ts +55 -0
  314. package/src/probe.ts +51 -0
  315. package/src/protocol.ts +119 -0
  316. package/src/proxy-converge.ts +85 -0
  317. package/src/remote-bootstrap.ts +210 -0
  318. package/src/remote-entry.ts +89 -0
  319. package/src/remote-lock.ts +12 -0
  320. package/src/repair-catalog.ts +206 -0
  321. package/src/repair-plan.ts +78 -0
  322. package/src/repair-runner.ts +84 -0
  323. package/src/report.ts +71 -0
  324. package/src/runner.ts +104 -0
  325. package/src/secrets.ts +169 -0
  326. package/src/service-auth.ts +115 -0
  327. package/src/ssh.ts +135 -0
  328. package/src/state/bucket.ts +208 -0
  329. package/src/state/check.ts +74 -0
  330. package/src/state/connection.ts +92 -0
  331. package/src/state/enrollment.ts +45 -0
  332. package/src/state/legacy.ts +89 -0
  333. package/src/state/litestream.ts +143 -0
  334. package/src/state/operator.ts +20 -0
  335. package/src/state/paths.ts +40 -0
  336. package/src/state/roster.ts +62 -0
  337. package/src/state/store.ts +169 -0
  338. package/src/state/sync.ts +352 -0
  339. package/src/sync.ts +104 -0
  340. package/src/t3/accounts.ts +81 -0
  341. package/src/t3/desired.ts +165 -0
  342. package/src/t3/files.ts +81 -0
  343. package/src/t3/names-check.ts +53 -0
  344. package/src/t3/names.ts +155 -0
  345. package/src/t3/plan.ts +82 -0
  346. package/src/t3/providers.ts +74 -0
  347. package/src/t3/reconcile.ts +123 -0
  348. package/src/t3-endpoint.ts +104 -0
  349. package/src/t3-linked-check.ts +123 -0
  350. package/src/tailnet-health.ts +231 -0
  351. package/src/tailnet-matching.ts +37 -0
  352. package/src/tailscale.ts +141 -0
  353. package/src/toolchain-checks.ts +108 -0
  354. package/src/topology.ts +24 -0
  355. package/src/transport.ts +122 -0
  356. package/src/types.ts +99 -0
  357. package/src/vault-config.ts +38 -0
  358. package/src/worker-access-checks.ts +168 -0
  359. package/src/worker-capacity.ts +34 -0
  360. package/src/worker-host-checks.ts +181 -0
  361. package/src/worker-policy.ts +64 -0
  362. package/src/worker-readiness.ts +64 -0
  363. package/src/workstation-checks.ts +29 -0
@@ -0,0 +1,312 @@
1
+ ---
2
+ # Memory and disk safety: a runaway workload is stopped on its own before the
3
+ # machine, SSH, Tailscale or the T3 server run out of memory or disk. These
4
+ # tasks run before the capacity preflight because they matter most when a disk
5
+ # is nearly full; they only write small files and never delete user data. The
6
+ # disk GC timer they install deletes only build output and caches a build or
7
+ # install recreates (ansible/ubuntu/disk-gc.py).
8
+ - name: Inspect active swap
9
+ ansible.builtin.command:
10
+ argv: [swapon, --show=NAME, --noheadings]
11
+ changed_when: false
12
+ check_mode: false
13
+ register: active_swap
14
+ - name: Measure root free space before creating swap
15
+ ansible.builtin.command:
16
+ argv: [df, --output=avail, -BM, /]
17
+ changed_when: false
18
+ check_mode: false
19
+ register: swap_capacity
20
+ when: active_swap.stdout | trim == ''
21
+ - name: Refuse to create swap on a nearly full disk
22
+ ansible.builtin.assert:
23
+ that: (swap_capacity.stdout_lines | last | trim | regex_replace('M$', '') | int) >= resourceSafety.swapFileMiB + minimumFreeDiskMiB
24
+ fail_msg: SWAP_CAPACITY_REQUIRED; free disk space so a swap file can be created. No swap is active.
25
+ when: active_swap.stdout | trim == ''
26
+ - name: Create a swap file when the node has no swap
27
+ ansible.builtin.command:
28
+ argv: [fallocate, -l, '{{ resourceSafety.swapFileMiB }}M', /swap.fleet]
29
+ creates: /swap.fleet
30
+ when: active_swap.stdout | trim == ''
31
+ - name: Restrict the fleet swap file
32
+ ansible.builtin.file:
33
+ path: /swap.fleet
34
+ owner: root
35
+ group: root
36
+ mode: '0600'
37
+ when: active_swap.stdout | trim == ''
38
+ - name: Format the fleet swap file
39
+ ansible.builtin.command:
40
+ argv: [mkswap, /swap.fleet]
41
+ when: active_swap.stdout | trim == ''
42
+ - name: Activate the fleet swap file
43
+ ansible.builtin.command:
44
+ argv: [swapon, /swap.fleet]
45
+ when: active_swap.stdout | trim == ''
46
+ - name: Activate the fleet swap file at boot
47
+ ansible.builtin.lineinfile:
48
+ path: /etc/fstab
49
+ line: /swap.fleet none swap sw 0 0
50
+ when: active_swap.stdout | trim == ''
51
+
52
+ - name: Install the systemd userspace OOM killer
53
+ ansible.builtin.apt:
54
+ name: systemd-oomd
55
+ state: present
56
+ - name: Create memory and disk policy directories
57
+ ansible.builtin.file:
58
+ path: '{{ item }}'
59
+ state: directory
60
+ owner: root
61
+ group: root
62
+ mode: '0755'
63
+ loop:
64
+ - /etc/systemd/oomd.conf.d
65
+ - /etc/systemd/system/-.slice.d
66
+ - /etc/systemd/system/user.slice.d
67
+ - /etc/systemd/user/t3code-.scope.d
68
+ - /etc/systemd/journald.conf.d
69
+ - /etc/systemd/system/systemd-tmpfiles-clean.timer.d
70
+ - /etc/tmpfiles.d
71
+ - name: Declare systemd-oomd limits
72
+ ansible.builtin.copy:
73
+ dest: /etc/systemd/oomd.conf.d/60-personal-fleet.conf
74
+ owner: root
75
+ group: root
76
+ mode: '0644'
77
+ content: |
78
+ # Managed by personal fleet-sync: kill one cgroup before swap or memory pressure stalls the node.
79
+ [OOM]
80
+ SwapUsedLimit=90%
81
+ DefaultMemoryPressureLimit=60%
82
+ DefaultMemoryPressureDurationSec=20s
83
+ register: oomd_config
84
+ - name: Declare memory reserve and OOM policy for system and user slices
85
+ ansible.builtin.copy:
86
+ dest: '{{ item.dest }}'
87
+ owner: root
88
+ group: root
89
+ mode: '0644'
90
+ content: '{{ item.content }}'
91
+ loop:
92
+ - dest: /etc/systemd/system/-.slice.d/60-personal-fleet.conf
93
+ content: |
94
+ # Managed by personal fleet-sync: when swap is exhausted, oomd kills the largest swap user.
95
+ [Slice]
96
+ ManagedOOMSwap=kill
97
+ - dest: /etc/systemd/system/user.slice.d/60-personal-fleet.conf
98
+ content: |
99
+ # Managed by personal fleet-sync: user workloads can never take the memory SSH, Tailscale and system services need.
100
+ [Slice]
101
+ MemoryMax={{ resourceSafety.userMemoryMax }}
102
+ loop_control:
103
+ label: '{{ item.dest }}'
104
+ register: slice_policy
105
+ - name: Bound each T3 agent so a runaway loses one process, not the session or the node
106
+ ansible.builtin.copy:
107
+ dest: /etc/systemd/user/t3code-.scope.d/60-personal-fleet.conf
108
+ owner: root
109
+ group: root
110
+ mode: '0644'
111
+ content: |
112
+ # Managed by personal fleet-sync. Applies to every t3code-*.scope T3 starts for an agent.
113
+ # OOMPolicy=continue: the kernel kills the largest process in the agent; the agent itself keeps running.
114
+ [Scope]
115
+ MemoryMax={{ resourceSafety.agentMemoryMax }}
116
+ MemorySwapMax={{ resourceSafety.agentSwapMax }}
117
+ OOMPolicy=continue
118
+ register: agent_policy
119
+ - name: Apply slice limits without restarting services
120
+ ansible.builtin.systemd_service:
121
+ daemon_reload: true
122
+ when: slice_policy.changed
123
+ - name: Load the agent limits into the running user manager for new agents
124
+ become: true
125
+ become_user: '{{ development_user }}'
126
+ environment:
127
+ XDG_RUNTIME_DIR: '/run/user/{{ development_uid }}'
128
+ ansible.builtin.systemd_service:
129
+ scope: user
130
+ daemon_reload: true
131
+ when: agent_policy.changed
132
+ failed_when: false
133
+ - name: Run systemd-oomd
134
+ ansible.builtin.systemd_service:
135
+ name: systemd-oomd.service
136
+ enabled: true
137
+ state: '{{ "restarted" if oomd_config.changed else "started" }}'
138
+
139
+ - name: Cap the system journal
140
+ ansible.builtin.copy:
141
+ dest: /etc/systemd/journald.conf.d/60-personal-fleet.conf
142
+ owner: root
143
+ group: root
144
+ mode: '0644'
145
+ content: |
146
+ # Managed by personal fleet-sync.
147
+ [Journal]
148
+ SystemMaxUse={{ resourceSafety.journalMaxUse }}
149
+ SystemKeepFree={{ resourceSafety.journalKeepFree }}
150
+ RuntimeMaxUse=256M
151
+ register: journal_policy
152
+ - name: Apply the journal cap
153
+ ansible.builtin.systemd_service:
154
+ name: systemd-journald.service
155
+ state: restarted
156
+ when: journal_policy.changed
157
+ - name: Age out abandoned temporary files
158
+ ansible.builtin.copy:
159
+ dest: /etc/tmpfiles.d/tmp.conf
160
+ owner: root
161
+ group: root
162
+ mode: '0644'
163
+ content: |
164
+ # Managed by personal fleet-sync; overrides /usr/lib/tmpfiles.d/tmp.conf.
165
+ q /tmp 1777 root root {{ resourceSafety.tmpAge }}
166
+ q /var/tmp 1777 root root {{ resourceSafety.varTmpAge }}
167
+ - name: Clean temporary files hourly instead of daily
168
+ ansible.builtin.copy:
169
+ dest: /etc/systemd/system/systemd-tmpfiles-clean.timer.d/60-personal-fleet.conf
170
+ owner: root
171
+ group: root
172
+ mode: '0644'
173
+ content: |
174
+ # Managed by personal fleet-sync.
175
+ [Timer]
176
+ OnUnitActiveSec=1h
177
+ register: tmpfiles_timer
178
+ - name: Inspect the RAM-backed /tmp unit
179
+ ansible.builtin.stat:
180
+ path: /usr/lib/systemd/system/tmp.mount
181
+ register: tmp_mount
182
+ - name: Create the /tmp mount policy directory
183
+ ansible.builtin.file:
184
+ path: /etc/systemd/system/tmp.mount.d
185
+ state: directory
186
+ owner: root
187
+ group: root
188
+ mode: '0755'
189
+ when: tmp_mount.stat.exists
190
+ - name: Bound RAM-backed /tmp (effective after the next reboot)
191
+ ansible.builtin.copy:
192
+ dest: /etc/systemd/system/tmp.mount.d/60-personal-fleet.conf
193
+ owner: root
194
+ group: root
195
+ mode: '0644'
196
+ content: |
197
+ # Managed by personal fleet-sync: /tmp is RAM; keep it from taking half of memory.
198
+ [Mount]
199
+ Options=mode=1777,strictatime,nosuid,nodev,size={{ resourceSafety.tmpMaxSize }},nr_inodes=1m,x-systemd.graceful-option=usrquota
200
+ when: tmp_mount.stat.exists
201
+ register: tmp_policy
202
+ - name: Reload the cleanup timer and /tmp definitions
203
+ ansible.builtin.systemd_service:
204
+ daemon_reload: true
205
+ when: tmpfiles_timer.changed or tmp_policy.changed
206
+
207
+ - name: Read the tracked disk guard
208
+ ansible.builtin.stat:
209
+ path: '{{ development_source }}/ansible/ubuntu/disk-guard.py'
210
+ checksum_algorithm: sha256
211
+ register: disk_guard_source
212
+ when: development_source is defined
213
+ - name: Declare the disk guard service
214
+ ansible.builtin.copy:
215
+ dest: /etc/systemd/system/personal-fleet-disk-guard.service
216
+ owner: root
217
+ group: root
218
+ mode: '0644'
219
+ content: |
220
+ # Managed by personal fleet-sync: ansible/ubuntu/disk-guard.py
221
+ # Source sha256 {{ disk_guard_source.stat.checksum }}
222
+ [Unit]
223
+ Description=Personal Fleet disk guard (stops a runaway user writer before the disk fills)
224
+ StartLimitIntervalSec=0
225
+ [Service]
226
+ Type=simple
227
+ ExecStart=/usr/bin/python3 -I -B {{ disk_guard_source.stat.path }} --path / --path {{ development_home }} --floor-mib {{ resourceSafety.diskGuardFloorMiB }} --write-mib {{ resourceSafety.diskGuardWriteMiB }} --interval {{ resourceSafety.diskGuardIntervalSeconds }}
228
+ Restart=always
229
+ RestartSec=5
230
+ OOMScoreAdjust=-1000
231
+ MemoryMax=64M
232
+ MemorySwapMax=0
233
+ CPUQuota=10%
234
+ TasksMax=4
235
+ NoNewPrivileges=true
236
+ PrivateTmp=true
237
+ PrivateNetwork=true
238
+ ProtectSystem=strict
239
+ ProtectHome=read-only
240
+ ReadWritePaths=/sys/fs/cgroup
241
+ ProtectKernelModules=true
242
+ LogRateLimitIntervalSec=60
243
+ LogRateLimitBurst=20
244
+ [Install]
245
+ WantedBy=multi-user.target
246
+ when: development_source is defined and disk_guard_source.stat.exists
247
+ register: disk_guard_unit
248
+ - name: Run the disk guard
249
+ ansible.builtin.systemd_service:
250
+ name: personal-fleet-disk-guard.service
251
+ daemon_reload: '{{ disk_guard_unit.changed }}'
252
+ enabled: true
253
+ state: '{{ "restarted" if disk_guard_unit.changed else "started" }}'
254
+ when: development_source is defined and disk_guard_source.stat.exists
255
+
256
+ - name: Read the tracked disk GC
257
+ ansible.builtin.stat:
258
+ path: '{{ development_source }}/ansible/ubuntu/disk-gc.py'
259
+ checksum_algorithm: sha256
260
+ register: disk_gc_source
261
+ when: development_source is defined
262
+ - name: Declare the disk GC service and its hourly timer
263
+ ansible.builtin.copy:
264
+ dest: '{{ item.dest }}'
265
+ owner: root
266
+ group: root
267
+ mode: '0644'
268
+ content: '{{ item.content }}'
269
+ loop:
270
+ - dest: /etc/systemd/system/personal-fleet-disk-gc.service
271
+ content: |
272
+ # Managed by personal fleet-sync: ansible/ubuntu/disk-gc.py
273
+ # Source sha256 {{ disk_gc_source.stat.checksum }}
274
+ [Unit]
275
+ Description=Personal Fleet disk GC (deletes build output and caches a build recreates)
276
+ After=docker.service
277
+ [Service]
278
+ Type=oneshot
279
+ ExecStart=/usr/bin/python3 -I -B {{ disk_gc_source.stat.path }} --home {{ development_home }} --path / --path {{ development_home }} --idle-days {{ resourceSafety.diskGcIdleDays }} --pressure-mib {{ resourceSafety.diskGcPressureMiB }} --pressure-idle-hours {{ resourceSafety.diskGcPressureIdleHours }} --docker-age-hours {{ resourceSafety.diskGcDockerAgeHours }} --pressure-docker-age-hours {{ resourceSafety.diskGcPressureDockerAgeHours }}{% for cache in resourceSafety.diskGcPressureCaches %} --pressure-cache {{ cache }}{% endfor %}
280
+
281
+ TimeoutStartSec=2h
282
+ Nice=19
283
+ IOSchedulingClass=idle
284
+ CPUQuota=50%
285
+ MemoryMax=512M
286
+ NoNewPrivileges=true
287
+ PrivateTmp=true
288
+ LogRateLimitIntervalSec=60
289
+ LogRateLimitBurst=200
290
+ - dest: /etc/systemd/system/personal-fleet-disk-gc.timer
291
+ content: |
292
+ # Managed by personal fleet-sync: ansible/ubuntu/disk-gc.py
293
+ [Unit]
294
+ Description=Run the Personal Fleet disk GC hourly
295
+ [Timer]
296
+ OnBootSec=10min
297
+ OnCalendar=hourly
298
+ RandomizedDelaySec=5min
299
+ Persistent=true
300
+ [Install]
301
+ WantedBy=timers.target
302
+ loop_control:
303
+ label: '{{ item.dest }}'
304
+ when: development_source is defined and disk_gc_source.stat.exists
305
+ register: disk_gc_units
306
+ - name: Schedule the disk GC
307
+ ansible.builtin.systemd_service:
308
+ name: personal-fleet-disk-gc.timer
309
+ daemon_reload: '{{ disk_gc_units.changed }}'
310
+ enabled: true
311
+ state: started
312
+ when: development_source is defined and disk_gc_source.stat.exists
@@ -0,0 +1,31 @@
1
+ ---
2
+ # Always-on recovery for the system services every worker depends on. Drop-ins
3
+ # change restart policy only; running services are never restarted here.
4
+ - name: Create recovery drop-in directories for required system services
5
+ ansible.builtin.file:
6
+ path: '/etc/systemd/system/{{ item }}.service.d'
7
+ state: directory
8
+ owner: root
9
+ group: root
10
+ mode: '0755'
11
+ loop: '{{ alwaysOnServices }}'
12
+ - name: Declare unlimited automatic restart for required system services
13
+ ansible.builtin.copy:
14
+ dest: '/etc/systemd/system/{{ item }}.service.d/60-personal-fleet.conf'
15
+ owner: root
16
+ group: root
17
+ mode: '0644'
18
+ backup: true
19
+ content: |
20
+ # Managed by personal fleet-sync: keep this worker service always on.
21
+ [Unit]
22
+ StartLimitIntervalSec=0
23
+ [Service]
24
+ Restart=always
25
+ RestartSec=5
26
+ loop: '{{ alwaysOnServices }}'
27
+ register: recovery_dropins
28
+ - name: Reload unit definitions without restarting running services
29
+ ansible.builtin.systemd_service:
30
+ daemon_reload: true
31
+ when: recovery_dropins.changed
@@ -0,0 +1,71 @@
1
+ ---
2
+ - name: Inspect effective SSH configuration before changes
3
+ ansible.builtin.command: /usr/sbin/sshd -T
4
+ changed_when: false
5
+ register: effective_ssh
6
+ - name: Inspect any existing managed SSH fragment
7
+ ansible.builtin.stat:
8
+ path: /etc/ssh/sshd_config.d/60-personal-fleet.conf
9
+ register: ssh_owned_file
10
+ - name: Read previous fragment for validation rollback
11
+ ansible.builtin.slurp:
12
+ src: /etc/ssh/sshd_config.d/60-personal-fleet.conf
13
+ when: ssh_owned_file.stat.exists
14
+ register: ssh_previous
15
+ no_log: true
16
+ - name: Refuse to overwrite unmanaged SSH configuration
17
+ ansible.builtin.assert:
18
+ that:
19
+ - not ssh_owned_file.stat.exists or (ssh_previous.content | b64decode).startswith('# Managed by personal Fleet')
20
+ fail_msg: SSH_UNMANAGED_CONFIG_CONFLICT; review the existing fragment before adoption.
21
+ - name: Stage and validate forwarding policy while preserving listeners and authentication
22
+ block:
23
+ - name: Write forwarding-only owned fragment
24
+ ansible.builtin.copy:
25
+ dest: /etc/ssh/sshd_config.d/60-personal-fleet.conf
26
+ owner: root
27
+ group: root
28
+ mode: '0644'
29
+ backup: true
30
+ content: |
31
+ # Managed by personal Fleet; listeners and authentication remain explicit policy.
32
+ AllowTcpForwarding yes
33
+ ClientAliveInterval 30
34
+ ClientAliveCountMax 3
35
+ register: ssh_fragment
36
+ - name: Validate complete SSH configuration
37
+ ansible.builtin.command: /usr/sbin/sshd -t
38
+ changed_when: false
39
+ rescue:
40
+ - name: Restore previous fragment after validation failure
41
+ ansible.builtin.copy:
42
+ content: '{{ ssh_previous.content | b64decode }}'
43
+ dest: /etc/ssh/sshd_config.d/60-personal-fleet.conf
44
+ owner: root
45
+ group: root
46
+ mode: '0644'
47
+ when: ssh_owned_file.stat.exists
48
+ no_log: true
49
+ - name: Remove rejected new fragment
50
+ ansible.builtin.file:
51
+ path: /etc/ssh/sshd_config.d/60-personal-fleet.conf
52
+ state: absent
53
+ when: not ssh_owned_file.stat.exists
54
+ - name: Report validation failure without reloading sshd
55
+ ansible.builtin.fail:
56
+ msg: SSH_CONFIG_REJECTED; previous configuration restored, active service untouched.
57
+ - name: Enable existing SSH service at boot
58
+ ansible.builtin.systemd_service:
59
+ name: ssh.service
60
+ enabled: true
61
+ state: started
62
+ - name: Reload validated forwarding policy without terminating sessions
63
+ ansible.builtin.systemd_service:
64
+ name: ssh.service
65
+ state: reloaded
66
+ when: ssh_fragment.changed
67
+ - name: Verify effective forwarding after reload
68
+ ansible.builtin.command: /usr/sbin/sshd -T
69
+ changed_when: false
70
+ register: ssh_verified
71
+ failed_when: "'allowtcpforwarding yes' not in ssh_verified.stdout"
@@ -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