@crvouga/dev-fleet 0.7.0 → 0.9.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 (206) hide show
  1. package/README.md +88 -7
  2. package/agents/audit.md +18 -18
  3. package/ansible/agents/agents.yml +134 -0
  4. package/ansible/agents/templates/antigravity.settings.json.j2 +3 -0
  5. package/ansible/agents/templates/claude.settings.json.j2 +3 -0
  6. package/ansible/agents/templates/codex.config.toml.j2 +11 -0
  7. package/ansible/agents/templates/gemini.settings.json.j2 +14 -0
  8. package/ansible/agents/templates/grok.config.toml.j2 +19 -0
  9. package/ansible/agents/templates/opencode.json.j2 +24 -0
  10. package/ansible/agents/templates/receipt.json.j2 +10 -0
  11. package/ansible/ansible.cfg +14 -0
  12. package/ansible/github/git.yml +82 -0
  13. package/ansible/macos/manager-privilege.yml +83 -0
  14. package/ansible/macos/manager.yml +164 -9
  15. package/ansible/macos/policy.json +13 -0
  16. package/ansible/macos/t3-policy.yml +5 -3
  17. package/ansible/macos/vault-renew.yml +85 -0
  18. package/ansible/macos/worker-host-key.yml +33 -0
  19. package/ansible/monitoring/agent-darwin.yml +3 -0
  20. package/ansible/monitoring/agent.yml +2 -0
  21. package/ansible/monitoring/hub-darwin.yml +3 -0
  22. package/ansible/monitoring/hub-route-darwin.yml +1 -0
  23. package/ansible/net/remote-probe.sh +4 -1
  24. package/ansible/platforms.json +2 -2
  25. package/ansible/pyproject.toml +5 -0
  26. package/ansible/ubuntu/baseline.yml +39 -13
  27. package/ansible/ubuntu/disk-gc.py +6 -2
  28. package/ansible/ubuntu/firewall.nft.j2 +50 -0
  29. package/ansible/ubuntu/policy.json +125 -6
  30. package/ansible/ubuntu/reboot.yml +89 -0
  31. package/ansible/ubuntu/recovery.yml +13 -3
  32. package/ansible/ubuntu/t3/policy.json +3 -0
  33. package/ansible/ubuntu/t3-desktop.yml +23 -2
  34. package/ansible/ubuntu/t3-headless.yml +128 -0
  35. package/ansible/ubuntu/t3-health.sh +24 -10
  36. package/ansible/ubuntu/t3-idle.py +37 -2
  37. package/ansible/ubuntu/t3-policy.yml +4 -1
  38. package/ansible/ubuntu/t3-route.yml +1 -0
  39. package/ansible/ubuntu/tailscale.yml +54 -8
  40. package/ansible/ubuntu/tasks/browser.yml +3 -0
  41. package/ansible/ubuntu/tasks/containers-off.yml +43 -0
  42. package/ansible/ubuntu/tasks/docker.yml +53 -6
  43. package/ansible/ubuntu/tasks/dpkg.yml +4 -0
  44. package/ansible/ubuntu/tasks/features.yml +22 -0
  45. package/ansible/ubuntu/tasks/firewall.yml +172 -0
  46. package/ansible/ubuntu/tasks/install.yml +2 -1
  47. package/ansible/ubuntu/tasks/packages.yml +4 -2
  48. package/ansible/ubuntu/tasks/release.yml +6 -0
  49. package/ansible/ubuntu/tasks/resources.yml +6 -4
  50. package/ansible/ubuntu/tasks/services.yml +2 -2
  51. package/ansible/ubuntu/tasks/sizing.yml +49 -0
  52. package/ansible/ubuntu/tasks/snapshot.yml +14 -3
  53. package/ansible/ubuntu/tasks/ssh.yml +61 -4
  54. package/ansible/ubuntu/tasks/t3-restart.yml +3 -1
  55. package/ansible/ubuntu/tasks/user.yml +1 -0
  56. package/ansible/ubuntu/worker-account.yml +117 -0
  57. package/ansible/uv.lock +265 -0
  58. package/bin/devfleet.mjs +42 -4
  59. package/bootstrap/fleet-remote-bootstrap.sh +91 -25
  60. package/bootstrap/global-config.ts +29 -3
  61. package/bootstrap/package-smoke.ts +127 -0
  62. package/bootstrap/pins.ts +189 -0
  63. package/bootstrap/release-notes.ts +88 -0
  64. package/bootstrap/release-version.ts +41 -7
  65. package/bootstrap/worker-features.ts +114 -0
  66. package/bootstrap/worker-seed.yaml +65 -0
  67. package/bootstrap/worker-tools.ts +39 -9
  68. package/cliproxy/README.md +1 -1
  69. package/cliproxy/docs/health-workflow.example.yml +20 -143
  70. package/docs/authentication.md +21 -11
  71. package/docs/consumer-setup.md +74 -58
  72. package/docs/convergence.md +67 -0
  73. package/docs/declared.md +61 -34
  74. package/docs/machine-setup.md +73 -7
  75. package/docs/manager-setup.md +34 -0
  76. package/docs/operations.md +64 -9
  77. package/docs/releasing.md +49 -1
  78. package/docs/runbooks.md +112 -7
  79. package/docs/state.md +23 -11
  80. package/docs/support-matrix.md +40 -4
  81. package/docs/topology.md +82 -12
  82. package/docs/ubuntu-sync.md +98 -42
  83. package/docs/worker-contract.md +122 -19
  84. package/docs/worker-requirements.md +107 -0
  85. package/examples/fleet.json +1 -0
  86. package/examples/workers-from-infrastructure/main.tf +132 -0
  87. package/examples/workers-from-infrastructure/worker-records.json +14 -0
  88. package/llms.txt +3 -1
  89. package/mise.lock +29 -0
  90. package/mise.toml +5 -3
  91. package/package.json +2 -2
  92. package/schema/check-ids.json +1991 -162
  93. package/schema/check-ids.schema.json +34 -0
  94. package/schema/fleet.schema.json +349 -10
  95. package/schema/output/check.schema.json +1 -1
  96. package/schema/output/fleet-state-definition.schema.json +14 -0
  97. package/schema/output/fleet-sync-schedule.schema.json +26 -0
  98. package/schema/output/fleet-sync.schema.json +18 -0
  99. package/schema/output/report.schema.json +3 -2
  100. package/schema/worker-record.schema.json +76 -0
  101. package/src/agents/converge.ts +140 -24
  102. package/src/agents/files.ts +2 -32
  103. package/src/agents/native-verify.ts +34 -3
  104. package/src/agents/secrets.ts +39 -4
  105. package/src/check-owners.ts +62 -2
  106. package/src/check-registry.ts +630 -0
  107. package/src/cli/agent-guide.ts +3 -3
  108. package/src/cli/cli.ts +4 -0
  109. package/src/cli/fleet-manifest.ts +108 -0
  110. package/src/cli/fleet-node-lifecycle.ts +704 -0
  111. package/src/cli/fleet-node-park.ts +238 -0
  112. package/src/cli/fleet-node-seed.ts +167 -0
  113. package/src/cli/fleet-state.ts +2 -1
  114. package/src/cli/fleet-sync-checkout.ts +7 -0
  115. package/src/cli/fleet-sync-enrollment.ts +193 -32
  116. package/src/cli/fleet-sync-forward.ts +6 -0
  117. package/src/cli/fleet-sync-manager.ts +123 -8
  118. package/src/cli/fleet-sync-output.ts +31 -11
  119. package/src/cli/fleet-sync-plan.ts +65 -9
  120. package/src/cli/fleet-sync-records.ts +113 -0
  121. package/src/cli/fleet-sync-remote.ts +172 -15
  122. package/src/cli/fleet-sync-schedule.ts +14 -4
  123. package/src/cli/fleet-sync-state.ts +31 -10
  124. package/src/cli/fleet-sync-vault.ts +152 -26
  125. package/src/cli/fleet-sync-worker.ts +214 -0
  126. package/src/cli/fleet-sync.ts +172 -21
  127. package/src/cli/fleet-validate.ts +277 -0
  128. package/src/cli/fleet-watch.ts +7 -1
  129. package/src/cli/fleet.ts +87 -5
  130. package/src/cli/managed-health.ts +25 -1
  131. package/src/cli/register-fleet.ts +251 -4
  132. package/src/cli/registry.ts +47 -0
  133. package/src/cli/tailnet.ts +43 -10
  134. package/src/cli/ui-data.ts +3 -1
  135. package/src/cli/worker-usage-report.ts +63 -0
  136. package/src/clock.ts +42 -0
  137. package/src/definition.ts +762 -232
  138. package/src/diagnostics.ts +5 -2
  139. package/src/exposure.ts +102 -0
  140. package/src/fleet-config.ts +3 -1
  141. package/src/git-identity.ts +76 -3
  142. package/src/ide/ide.ts +5 -0
  143. package/src/ide/t3.ts +12 -2
  144. package/src/input-hashes.ts +6 -0
  145. package/src/machine/machine-class.ts +59 -0
  146. package/src/machine/platform.ts +90 -11
  147. package/src/machine/state.ts +70 -16
  148. package/src/machine/support-matrix.ts +11 -9
  149. package/src/machine-health.ts +28 -5
  150. package/src/manager-health.ts +148 -2
  151. package/src/manager-sync.ts +89 -8
  152. package/src/monitoring/beszel.ts +8 -8
  153. package/src/monitoring/enroll.ts +8 -1
  154. package/src/monitoring/hub-access.ts +10 -4
  155. package/src/monitoring/systems.ts +138 -0
  156. package/src/network-definition.ts +13 -4
  157. package/src/node-converge-linux.ts +136 -27
  158. package/src/node-converge.ts +13 -3
  159. package/src/platform/ansible-receipt.ts +17 -2
  160. package/src/platform/ansible.ts +40 -4
  161. package/src/platform/manager-host.ts +25 -8
  162. package/src/platform/resource-verify.ts +18 -7
  163. package/src/platform/t3-policy.ts +24 -10
  164. package/src/platform/t3-service.ts +109 -34
  165. package/src/platform/ubuntu-release.ts +24 -0
  166. package/src/platform/ubuntu-verify.ts +31 -3
  167. package/src/platform/ubuntu-worker.ts +58 -0
  168. package/src/platform/worker-platform.ts +72 -0
  169. package/src/protocol.ts +1 -1
  170. package/src/public-reachability.ts +186 -0
  171. package/src/remote-bootstrap.ts +50 -12
  172. package/src/remote-operation.ts +18 -4
  173. package/src/repair-plan.ts +23 -12
  174. package/src/report.ts +2 -1
  175. package/src/schedule-check.ts +21 -2
  176. package/src/secrets.ts +7 -0
  177. package/src/service-auth.ts +55 -48
  178. package/src/ssh-connection.ts +141 -0
  179. package/src/ssh.ts +63 -24
  180. package/src/state/store.ts +4 -2
  181. package/src/state/sync.ts +35 -6
  182. package/src/state/workers.ts +140 -0
  183. package/src/t3/pairing.ts +59 -0
  184. package/src/t3/peer-link.ts +333 -0
  185. package/src/t3/version.ts +1 -1
  186. package/src/tailnet-cleanup.ts +185 -0
  187. package/src/tailnet-control.ts +346 -0
  188. package/src/tailscale.ts +1 -1
  189. package/src/toolchain-checks.ts +11 -1
  190. package/src/types.ts +4 -2
  191. package/src/worker-account.ts +63 -0
  192. package/src/worker-activity.ts +303 -0
  193. package/src/worker-capacity.ts +31 -0
  194. package/src/worker-credentials.ts +201 -0
  195. package/src/worker-exposure.ts +388 -0
  196. package/src/worker-features.ts +201 -0
  197. package/src/worker-host-checks.ts +71 -20
  198. package/src/worker-manifest.ts +224 -0
  199. package/src/worker-parking.ts +70 -0
  200. package/src/worker-policy.ts +38 -36
  201. package/src/worker-readiness.ts +15 -3
  202. package/src/worker-reboot.ts +129 -0
  203. package/src/worker-records.ts +305 -0
  204. package/src/worker-seed.ts +143 -0
  205. package/src/worker-sizing.ts +76 -0
  206. package/src/cli/schedule-logs.ts +0 -36
@@ -8,14 +8,14 @@ rewrites them).
8
8
 
9
9
  ## Where credentials live
10
10
 
11
- | Credential | Lives in | Read by |
12
- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
13
- | The fleet's secrets (table below) | The provider `secrets` names in `fleet.json` | The node that needs it, through `src/secrets.ts` |
14
- | The bucket's two keys | The environment of the `devfleet` command; `~/.config/dev-fleet/bucket.json` (0600) on the manager after `fleet-state --init` | The manager only |
15
- | A node's Vault token | That node's own Vault session (`~/.vault-token`) | That node only; never copied |
16
- | A node's GitHub login | `gh`'s own store on that node | `gh`, and git through its credential helper |
17
- | Provider accounts (Claude, Codex …) | The CLI proxy's store on the manager | The proxy; nodes hold only the proxy client key |
18
- | Tailscale and T3 pairing | Each machine, signed in by a person | That machine |
11
+ | Credential | Lives in | Read by |
12
+ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------ |
13
+ | The fleet's secrets (table below) | The provider `secrets` names in `fleet.json` | The node that needs it, through `src/secrets.ts` |
14
+ | The bucket's two keys | The environment of the `devfleet` command; `~/.config/dev-fleet/bucket.json` (0600) on the manager after `fleet-state --init` | The manager only |
15
+ | A node's Vault token | That node's own Vault session (`~/.vault-token`) | That node only; never copied |
16
+ | A node's GitHub login | `gh`'s own store on that node | `gh`, and git through its credential helper |
17
+ | Provider accounts (Claude, Codex …) | The CLI proxy's store on the manager | The proxy; nodes hold only the proxy client key |
18
+ | Tailscale and T3 pairing | Each machine, signed in by a person; a replaced worker's T3 is paired by the manager with a one-time code it never writes down | That machine |
19
19
 
20
20
  ## How they reach each node
21
21
 
@@ -30,14 +30,19 @@ rewrites them).
30
30
  makes `gh` git's credential helper and writes the declared identity
31
31
  (`git` in `fleet.json`, `ansible/github/git.yml`) on every node.
32
32
  - **The proxy client key** is read by each node from its own secrets provider
33
- whenever a launcher starts an agent.
33
+ whenever a launcher starts an agent. The node keeps the last key it read in
34
+ `~/.cache/dev-fleet/proxy-client-key` (0600) and uses it only while the
35
+ provider cannot answer, so an unreachable Vault or an expired session leaves
36
+ that node's agents working until the Vault checks below are fixed.
34
37
  - **The monitoring hub's backup storage keys** are read on the manager by
35
38
  `fleet-sync` and handed to the hub's playbook in a private variables file.
36
39
 
37
40
  ## Rotation and failure
38
41
 
39
- Rotate a secret where the provider keeps it; the next read uses it, nothing
40
- caches it. A Vault token that is not renewed expires: `worker:vault-renewal`
42
+ Rotate a secret where the provider keeps it; the next read uses it. Only the
43
+ proxy client key is remembered, and only as a fallback; a rotated key reaches
44
+ every node at its next launch, and `retire-key` should wait until every node
45
+ has launched an agent since the rotation. A Vault token that is not renewed expires: `worker:vault-renewal`
41
46
  fails and the fix is one login of that machine. A GitHub token that stops
42
47
  working fails `auth:github` and `worker:git-push-auth`; store the new one in
43
48
  Vault and the worker restores itself within ten minutes. During a proxy
@@ -59,6 +64,8 @@ Every failure code is in [the runbooks](runbooks.md).
59
64
  | `monitoring-backup-secret-key` | when its feature is declared | `DEVFLEET_SECRET_MONITORING_BACKUP_SECRET_KEY` | `~/.config/dev-fleet/secrets/monitoring-backup-secret-key` |
60
65
  | `notify-webhook-url` | when its feature is declared | `DEVFLEET_SECRET_NOTIFY_WEBHOOK_URL` | `~/.config/dev-fleet/secrets/notify-webhook-url` |
61
66
  | `notify-ntfy-url` | when its feature is declared | `DEVFLEET_SECRET_NOTIFY_NTFY_URL` | `~/.config/dev-fleet/secrets/notify-ntfy-url` |
67
+ | `tailnet-auth-key` | when its feature is declared | `DEVFLEET_SECRET_TAILNET_AUTH_KEY` | `~/.config/dev-fleet/secrets/tailnet-auth-key` |
68
+ | `tailnet-admin` | when its feature is declared | `DEVFLEET_SECRET_TAILNET_ADMIN` | `~/.config/dev-fleet/secrets/tailnet-admin` |
62
69
 
63
70
  <!-- end: secrets -->
64
71
 
@@ -70,8 +77,11 @@ Every failure code is in [the runbooks](runbooks.md).
70
77
  | ----------------------------- | ------------------------------------------ |
71
78
  | `auth:github` | `src/github/checks.ts` |
72
79
  | `auth:vault` | `src/cli/fleet-sync-vault.ts` |
80
+ | `auth:vault-isolation` | `src/cli/fleet-sync-vault.ts` |
81
+ | `worker:credential-renewal` | `src/worker-host-checks.ts` |
73
82
  | `worker:git-identity` | `src/git-identity.ts` |
74
83
  | `worker:git-push-auth` | `src/definition.ts`, `src/git-identity.ts` |
84
+ | `worker:git-signing` | `src/definition.ts`, `src/git-identity.ts` |
75
85
  | `worker:github-sync` | `src/worker-access-checks.ts` |
76
86
  | `worker:proxy-authentication` | `src/worker-access-checks.ts` |
77
87
  | `worker:vault-renewal` | `src/worker-access-checks.ts` |
@@ -24,13 +24,13 @@ Nothing else is needed. You never clone dev-fleet, copy its files or write a
24
24
  wrapper script around a checkout of it: the npm package is the tool, and the
25
25
  version in `package.json` is the commit every machine runs.
26
26
 
27
- | Concept | Meaning |
28
- | ----------- | ------------------------------------------------------------------------------------------------------ |
29
- | manager | The one macOS machine you run `devfleet fleet-sync` on. Hosts the CLI proxy and the monitoring hub. |
30
- | worker | An Ubuntu 26.04 machine the manager converges over SSH. Stateless: rebuilt from Git and your secrets. |
31
- | fleet state | One SQLite database (roster, approved revision, configuration last applied) replicated to your bucket. |
32
- | bucket | Any S3-compatible bucket you own, or a directory. The source of truth for the fleet state. |
33
- | revision | The dev-fleet commit the installed package was published from (`npx devfleet --version`). |
27
+ | Concept | Meaning |
28
+ | ----------- | -------------------------------------------------------------------------------------------------------------- |
29
+ | manager | The one macOS machine you run `devfleet fleet-sync` on. Hosts the CLI proxy and the monitoring hub. |
30
+ | worker | An Ubuntu 26.04 or 24.04 machine the manager converges over SSH. Stateless: rebuilt from Git and your secrets. |
31
+ | fleet state | One SQLite database (roster, approved revision, configuration last applied) replicated to your bucket. |
32
+ | bucket | Any S3-compatible bucket you own, or a directory. The source of truth for the fleet state. |
33
+ | revision | The dev-fleet commit the installed package was published from (`npx devfleet --version`). |
34
34
 
35
35
  ## Rules for agents
36
36
 
@@ -75,25 +75,30 @@ On the manager (the Mac this project is checked out on):
75
75
  | Tailscale signed in | `tailscale status --json` (look for `"BackendState":"Running"`) |
76
76
  | An SSH alias per worker that works without a prompt | `ssh -o BatchMode=yes WORKER_ALIAS true` for each worker |
77
77
 
78
- On every worker:
79
-
80
- | Requirement | Verify, from the manager |
81
- | ------------------------------------ | ------------------------------------------------------------------ |
82
- | Ubuntu 26.04, amd64 or arm64 | `ssh WORKER_ALIAS '. /etc/os-release; echo $VERSION_ID; uname -m'` |
83
- | A non-root account that can use sudo | `ssh WORKER_ALIAS 'id -u; sudo -n true && echo sudo-ok'` |
84
- | Joined to the same tailnet | the worker appears in `tailscale status` on the manager |
85
-
86
- A worker needs nothing else beforehand: no Git, curl, Python, Node, Bun or
87
- mise. `fleet-sync` installs them. If `sudo -n true` fails, that is fine for
88
- the first, interactive run, which asks for the password once and installs a
89
- sudo rule for the fleet; it is not fine for unattended runs.
90
-
91
- The `ssh` value of a worker in `fleet.json` is an alias from the manager's
92
- `~/.ssh/config`. dev-fleet always connects with `BatchMode=yes` and
93
- `StrictHostKeyChecking=yes`, so the host key must already be in
94
- `known_hosts`. **Ask** the human to verify each fingerprint out of band and
95
- connect once by hand; never add `StrictHostKeyChecking=no` or
96
- `accept-new`. A typical alias:
78
+ On every worker, check each row of
79
+ [what a machine must offer to be a worker](worker-requirements.md): the
80
+ image, architecture and size, the access, the first-boot data and the
81
+ network. Each row says how to verify it. If `sudo -n true` fails, that is fine
82
+ for the first, interactive run, which asks for the password once and installs
83
+ a sudo rule for the fleet; it is not fine for unattended runs.
84
+
85
+ A worker is reached in one of two ways, always with `BatchMode=yes` and
86
+ `StrictHostKeyChecking=yes`; never add `StrictHostKeyChecking=no` or
87
+ `accept-new`.
88
+
89
+ - **From declared data (no alias).** Leave `ssh` out: dev-fleet connects as
90
+ `network.user` on `network.sshPort` to `network.tailscaleName`, and checks
91
+ the host key against a known_hosts file of its own, rendered from the key
92
+ the fleet state pins. The human's `~/.ssh/config` and `~/.ssh/known_hosts`
93
+ are neither needed nor changed. The key is pinned without trusting a first
94
+ connection: `npx devfleet fleet-node-seed --id ID --output PATH --apply
95
+ --yes` generates it for a new machine, `npx devfleet fleet-node-replace
96
+ --id ID --host-key "TYPE BASE64" --apply --yes` pins one the human verified
97
+ out of band, and a worker record (below) may carry it as `hostKey`.
98
+ - **Through the human's own alias.** `ssh` names a Host alias in the
99
+ manager's `~/.ssh/config`, and its host key must already be in their
100
+ `known_hosts`. **Ask** the human to verify each fingerprint out of band and
101
+ connect once by hand. A typical alias:
97
102
 
98
103
  ```
99
104
  Host worker-one
@@ -153,36 +158,37 @@ npx devfleet fleet-state --example > fleet.json
153
158
  Then replace every placeholder. Each field, from
154
159
  [`schema/fleet.schema.json`](../schema/fleet.schema.json):
155
160
 
156
- | Field | Value |
157
- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
158
- | `version` | `3` |
159
- | `source.repository` | `https://github.com/crvouga/dev-fleet.git`, or your fork if you publish one. Machines only accept code from it. |
160
- | `state` | `{ "bucket", "endpoint"?, "region"?, "prefix"? }` or `{ "directory": "/absolute/path" }`. Never credentials. |
161
- | `secrets` | See [secrets](#secrets). |
162
- | `manager.id` | A short lowercase id, `^[a-z][a-z0-9-]{0,62}$`, e.g. `manager-mac`. |
163
- | `manager.os` | `"macOS"` |
164
- | `manager.network.tailscaleName` | The manager's MagicDNS name: `tailscale status --json` → `.Self.DNSName` without the trailing dot. |
165
- | `manager.network.user` | The manager's login name (`id -un`). |
166
- | `manager.neverSleepOnPower` | `true` (workers depend on the manager's services). |
167
- | `workers[].id` | A short lowercase id. The **first** worker is the canary: a new revision must pass there before the others get it. |
168
- | `workers[].ssh` | The worker's alias in the manager's `~/.ssh/config`. |
169
- | `workers[].os` | `"linux"` |
170
- | `workers[].network.tailscaleName` | The worker's MagicDNS name (from `tailscale status --json` → `.Peer[].DNSName` on the manager). |
171
- | `workers[].network.sshPort` | `22` unless the worker's sshd listens elsewhere. |
172
- | `workers[].network.user` | The worker account (`ssh WORKER_ALIAS id -un`). |
173
- | `workers[].network.t3Port` | `3773` |
174
- | `services.proxy.endpoint` | The public HTTPS origin of the CLI proxy, e.g. `https://cli-proxy.example.com`. |
175
- | `services.monitoring.hubPort` | `8443` |
176
- | `services.monitoring.publicUrl` | The public HTTPS origin of the monitoring dashboard. |
177
- | `services.monitoring.managerAccount` | The email of the monitoring account the manager enrolls machines with. |
178
- | `services.monitoring.viewerAccount` | The email of the read-only account visitors are signed in as. |
179
- | `ubuntu` | `{ "release": "26.04", "architectures": ["amd64", "arm64"] }`: a release of the [support matrix](support-matrix.md). |
180
- | `worker.requiredCapabilities` | Normally all of `github`, `vault`, `t3`, `toolchain`, `workstation`, `worker`. |
181
- | `worker.githubLogin` | The GitHub login every worker must be signed in as. |
182
- | `github` (optional) | Who the manager signs in to GitHub as, per-node tokens, required scopes; see [GitHub](github.md). |
183
- | `worker.vaultUser` | The Vault user each worker's token is issued for. |
184
- | `worker.toolchain`, `agentProfile` | `"mise.lock"`, `"proxy"` (fixed). |
185
- | `worker.unattendedSudo`, `persistentT3`, `neverSleep` | `true` (fixed policy). |
161
+ | Field | Value |
162
+ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
163
+ | `version` | `3` |
164
+ | `source.repository` | `https://github.com/crvouga/dev-fleet.git`, or your fork if you publish one. Machines only accept code from it. |
165
+ | `state` | `{ "bucket", "endpoint"?, "region"?, "prefix"? }` or `{ "directory": "/absolute/path" }`. Never credentials. |
166
+ | `secrets` | See [secrets](#secrets). |
167
+ | `manager.id` | A short lowercase id, `^[a-z][a-z0-9-]{0,62}$`, e.g. `manager-mac`. |
168
+ | `manager.os` | `"macOS"` |
169
+ | `manager.network.tailscaleName` | The manager's MagicDNS name: `tailscale status --json` → `.Self.DNSName` without the trailing dot. |
170
+ | `manager.network.user` | The manager's login name (`id -un`). |
171
+ | `manager.neverSleepOnPower` | `true` (workers depend on the manager's services). |
172
+ | `workers[].id` | A short lowercase id. The first reachable worker is the canary: a new revision must pass there before the others get it. |
173
+ | `canary` | Optional: the id of the worker to prefer as canary. When it is unreachable, the next reachable worker is used. |
174
+ | `workers[].ssh` | The worker's alias in the manager's `~/.ssh/config`. |
175
+ | `workers[].os` | `"linux"` |
176
+ | `workers[].network.tailscaleName` | The worker's MagicDNS name (from `tailscale status --json` → `.Peer[].DNSName` on the manager). |
177
+ | `workers[].network.sshPort` | `22` unless the worker's sshd listens elsewhere. |
178
+ | `workers[].network.user` | The worker account (`ssh WORKER_ALIAS id -un`). |
179
+ | `workers[].network.t3Port` | `3773` |
180
+ | `services.proxy.endpoint` | The public HTTPS origin of the CLI proxy, e.g. `https://cli-proxy.example.com`. |
181
+ | `services.monitoring.hubPort` | `8443` |
182
+ | `services.monitoring.publicUrl` | The public HTTPS origin of the monitoring dashboard. |
183
+ | `services.monitoring.managerAccount` | The email of the monitoring account the manager enrolls machines with. |
184
+ | `services.monitoring.viewerAccount` | The email of the read-only account visitors are signed in as. |
185
+ | `ubuntu` | `{ "release": "26.04", "architectures": ["amd64", "arm64"] }`: a release of the [support matrix](support-matrix.md). |
186
+ | `worker.requiredCapabilities` | Normally all of `github`, `vault`, `t3`, `toolchain`, `workstation`, `worker`. |
187
+ | `worker.githubLogin` | The GitHub login every worker must be signed in as. |
188
+ | `github` (optional) | Who the manager signs in to GitHub as, per-node tokens, required scopes; see [GitHub](github.md). |
189
+ | `worker.vaultUser` | The Vault user each worker's token is issued for. |
190
+ | `worker.toolchain`, `agentProfile` | `"mise.lock"`, `"proxy"` (fixed). |
191
+ | `worker.unattendedSudo`, `persistentT3`, `neverSleep` | `true` (fixed policy). |
186
192
 
187
193
  Use the same values in every place they repeat. Validate before going on:
188
194
 
@@ -301,6 +307,14 @@ An agent cannot answer those prompts, and `--json`, `--non-interactive` or a
301
307
  npx devfleet fleet-sync
302
308
  ```
303
309
 
310
+ A worker that needs none of those prompts is enrolled without the human: a
311
+ run with `--json` or `--non-interactive` enrolls a declared worker when its
312
+ pinned host key matched, `sudo -n` works on it, and exactly one tailnet device
313
+ matches it (or none, when `fleet.json` declares `tailnet` and its secret
314
+ `tailnet-admin`, so the run joins it with a single-use key). The report lists
315
+ what it enrolled (`enrolled` in the JSON). A machine made with
316
+ `devfleet fleet-node-seed` meets all of these.
317
+
304
318
  It converges the manager first, then the canary worker, then the rest. It
305
319
  prints one table and the path of the full JSON report
306
320
  (`~/.local/share/personal/fleet/manager/last-sync.json`, readable by the human
@@ -363,7 +377,7 @@ Read `summary` and `remedy.action` of the failing check. The common ones:
363
377
  | `ROOT_DEVELOPMENT_USER_REJECTED` | The worker's SSH user is root. Use a normal account with sudo. |
364
378
  | `BOOTSTRAP_DISK_CAPACITY_REQUIRED` | Less than 2 GiB free (or too few inodes) where the fleet keeps its files on the worker. |
365
379
  | `FLEET_REMOTE_LOCKED` | Another operation is running on that worker. Wait and run again. |
366
- | `FLEET_PROMOTION_BLOCKED` | The canary (first worker) failed, so the new revision went no further. Fix the canary first. |
380
+ | `FLEET_PROMOTION_BLOCKED` | The canary (the report names it under `canary`) failed, so the new revision went no further. Fix the canary first. |
367
381
  | `VAULT_LOGIN_REQUIRED` | The manager is not logged in to Vault. The human logs in; the next sync issues worker tokens. |
368
382
  | `PROVIDER_UNATTENDED_CREDENTIAL_REQUIRED` | A coding agent has no credential a background service can use. The human signs in on the proxy. |
369
383
  | `BESZEL_ACCOUNT_REQUIRED` | The monitoring hub has no account yet. Follow the remedy; other workers are not blocked. |
@@ -381,7 +395,9 @@ and codes; leave out everything private.
381
395
  - **Remove a worker.** Delete its entry, commit, run `npx devfleet fleet-sync`.
382
396
  - **Upgrade the fleet.** `npm install --save-dev --save-exact @crvouga/dev-fleet@latest`,
383
397
  commit, `npx devfleet fleet-sync`. The canary proves the new revision first.
384
- Install an older version and sync to roll back.
398
+ `npx devfleet fleet-sync --rollback` goes back to the revision applied
399
+ before (the revision, tools and T3; system packages roll forward only, as
400
+ the [README](../README.md#the-version) says).
385
401
  - **Replace the manager.** Follow
386
402
  [replacing the manager](manager-recovery.md).
387
403
 
@@ -0,0 +1,67 @@
1
+ # What converges a machine, and what is not Ansible yet
2
+
3
+ The goal is one convergence engine: Ansible, on every platform, with the CLI
4
+ as an orchestrator that validates the fleet configuration, runs playbooks,
5
+ reads their results and reports checks. Today three mechanisms change a
6
+ machine: the playbooks under `ansible/`, the bootstrap scripts, and some
7
+ TypeScript. This page lists every step that is not an Ansible task yet, each
8
+ with the reason it is not, so the list can only shrink.
9
+ `features/rules/convergence.feature` fails when a file that changes a machine
10
+ is missing from it.
11
+
12
+ ## Ansible
13
+
14
+ | Playbook | Converges |
15
+ | ----------------------------------------- | ----------------------------------------------------------------------------- |
16
+ | `ansible/ubuntu/baseline.yml` | A worker's packages, Docker, services, power, user, SSH, disk |
17
+ | `ansible/ubuntu/tailscale.yml` | Tailscale on a worker |
18
+ | `ansible/ubuntu/t3-desktop.yml` | The T3 desktop app on a desktop worker |
19
+ | `ansible/ubuntu/t3-headless.yml` | The T3 server as a lingering user service on a headless worker |
20
+ | `ansible/ubuntu/t3-policy.yml` | T3's administrator policy |
21
+ | `ansible/ubuntu/t3-route.yml` | T3's private tailnet route |
22
+ | `ansible/ubuntu/recovery.yml` | Recovery of a stopped T3 desktop app or server |
23
+ | `ansible/ubuntu/worker-account.yml` | The declared account on an image that lets only root in, from the manager |
24
+ | `ansible/ubuntu/reboot.yml` | One worker's first-converge or maintenance reboot, from the manager |
25
+ | `ansible/monitoring/agent.yml` | The Beszel agent on a worker |
26
+ | `ansible/monitoring/agent-darwin.yml` | The Beszel agent on the manager |
27
+ | `ansible/monitoring/hub-darwin.yml` | The Beszel hub and its tunnel on the manager |
28
+ | `ansible/monitoring/hub-route-darwin.yml` | The hub's private tailnet route |
29
+ | `ansible/macos/manager.yml` | The manager's power settings, network time and fleet-watch job, unattended |
30
+ | `ansible/macos/t3-policy.yml` | T3's administrator policy on the manager |
31
+ | `ansible/macos/manager-privilege.yml` | The one administrator grant: sudoers, job log rotation and the T3 policy |
32
+ | `ansible/macos/worker-host-key.yml` | A worker's pinned SSH host key in the manager's known_hosts |
33
+ | `ansible/macos/vault-renew.yml` | The launch agent that renews the manager's own Vault session |
34
+ | `ansible/github/git.yml` | The Git identity and GitHub credentials of every node |
35
+ | `ansible/agents/agents.yml` | Each agent CLI's dedicated profile, rendered from `ansible/agents/templates/` |
36
+
37
+ Every playbook passes `ansible-lint` in the production profile, every command
38
+ in it says when it changes something and runs, or is skipped, in a dry run
39
+ (`features/rules/playbooks.feature`). The Beszel agent playbook is proven on a
40
+ systemd machine to report no change on a second run and in `--check`, and to
41
+ report drift, and only that drift (`features/monitoring/agent-lifecycle.feature`).
42
+
43
+ ## Not Ansible yet
44
+
45
+ | Step | Where | Why it is not a task yet |
46
+ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
47
+ | Install Git, curl, CA certificates, Python and Ansible | `bootstrap/fleet-remote-bootstrap.sh` | It must run before Ansible exists on the machine; it is the minimal bootstrap |
48
+ | Create the worker account, its host key, the privilege rule and Tailscale on first boot | `bootstrap/worker-seed.yaml` (written by `fleet-node-seed`) | cloud-init runs it before the manager can reach the machine; fleet-sync converges everything else with Ansible afterwards |
49
+ | Install mise and the pinned toolchain | `bootstrap/bootstrap.sh`, `bootstrap/setup.ts` | mise must exist before any tool does, and `mise install --locked` already converges from a lock |
50
+ | Install a worker's subset of tools | `bootstrap/worker-tools.ts`, `bootstrap/worker-features.ts` | It chooses which locked tools `mise install` gets; moving it means a toolchain role |
51
+ | Publish the global mise configuration | `bootstrap/global-config.ts` | Derived from `mise.toml` and the agent worktree roots on every run; a `toolchain` role with a template would replace it |
52
+ | `PATH` lines in shell profiles | `bootstrap/shell-path.ts` | Edits files a person owns, guarded by markers; a `blockinfile` task would replace it |
53
+ | The `devfleet` launcher link | `bootstrap/install-launcher.sh` | Points at the fleet-owned checkout of the revision, known only during the run |
54
+ | The T3 server archive | `bootstrap/native-t3.ts` | Installed with the toolchain, checked against the digest in `mise.toml`; a `t3` role would replace it |
55
+ | The AWS CLI on Windows | `bootstrap/native-aws.ts` | Windows is not a fleet role |
56
+ | Docker Desktop on the manager | `bootstrap/docker-desktop.sh` | A person installs and approves it on macOS; dev-fleet only measures it |
57
+ | A worker's service credentials from Vault | `bootstrap/worker-github.ts`, `bootstrap/worker-auth.ts` | A login belongs to a machine and a person; it is read from a session, never declared |
58
+ | The manager's own convergence: proxy, profiles, hub, agent | `src/cli/fleet-sync-manager.ts` | Orchestrates the manager playbooks and the proxy's own installer; the proxy is not a playbook yet (`docs/declared.md`) |
59
+ | Scheduled runs | `src/cli/fleet-sync-schedule.ts`, `src/platform/launchd.ts`, `src/platform/systemd-user.ts` | Writes a launchd job or systemd timer from TypeScript; listed in `docs/declared.md` as a schedule that should be a playbook |
60
+
61
+ ## Still to do
62
+
63
+ Roles with a common interface (`baseline`, `toolchain`, `agents`, `t3`,
64
+ `network`, `monitoring`, `recovery`, `manager`), an inventory generated from
65
+ the fleet configuration, and `site.yml`, `manager.yml` and `workers.yml` that
66
+ bring a fresh manager and worker to ready with no other mutating step. Each
67
+ row of the table above moves into one of those roles.
package/docs/declared.md CHANGED
@@ -12,29 +12,33 @@ may only shrink.
12
12
 
13
13
  ## Declared
14
14
 
15
- | What | Declared in | Pinned by | Applied by | Measured by |
16
- | --------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------- | -------------------------------------------- |
17
- | The fleet: machines, services, accounts | Your `fleet.json` | The dev-fleet version in your `package.json`, which names one commit | `fleet-sync` | `fleet-status` |
18
- | Tools: languages, CLIs, coding agents | `mise.toml` | `mise.lock`: version and checksum for each platform | `mise install --locked` | Tool and `agent:verification` checks |
19
- | mise itself | `min_version` and `mise_*_sha256` in `mise.toml` | The digest of its release archive, held here | `bootstrap/bootstrap.sh` | Its version, at every toolchain setup |
20
- | JavaScript dependencies | `package.json` | `bun.lock` | `bun install --frozen-lockfile` | The install fails on a difference |
21
- | T3 Code, server and desktop app | `t3_*` under `[vars]` in `mise.toml` | SHA-256 of each artifact | `bootstrap/native-t3.ts`; `ansible/ubuntu/t3-desktop.yml` | T3 checks; `t3:version` on every machine |
22
- | T3 policy on the manager (updates off) | `ansible/ubuntu/t3/policy.json`, shared with the workers | The commit | `ansible/macos/t3-policy.yml`, once, as an administrator | `t3:updates` |
23
- | Worker work-in-progress guard | `ansible/ubuntu/wip-guard.py`, `ansible/ubuntu/tasks/recovery.yml` | The commit | `fleet-sync` | `features/ide/work-in-progress.feature` |
24
- | Ubuntu packages | `packages` and `snapshot` in `ansible/ubuntu/policy.json` | Exact versions from a dated archive snapshot | `ansible/ubuntu/tasks/packages.yml` | `package:*` |
25
- | Tailscale on workers | `tailscale` in `ansible/ubuntu/policy.json` | Exact version; digest of the signing key | `ansible/ubuntu/tailscale.yml` | `package:tailscale` |
26
- | Worker policy: power, SSH, sudo, memory, disk | `ansible/ubuntu/*.yml`, values shared with checks in `policy.json` | The commit | `fleet-sync` | Host and resource checks |
27
- | Monitoring: hub, tunnel, agents | `ansible/monitoring/*.yml`, `compose.yml`, `lock.json` | Image digests; SHA-256 of each agent archive | `fleet-sync` | `beszel:*` |
28
- | The manager's private route to the hub | `ansible/monitoring/hub-route-darwin.yml` | The commit | `fleet-sync` | The route, read back |
29
- | The manager's power settings | `pmset` in `ansible/macos/policy.json` | The commit | `ansible/macos/manager.yml`, once, as an administrator | `manager:power` |
30
- | Ansible on the manager | `ansible/pyproject.toml` | `ansible/uv.lock`: one release, one Python, every dependency hashed | `uv run --locked --isolated` | A hash that differs stops the run |
31
- | Ansible on a worker | `ansible-core` in `ansible/ubuntu/policy.json` | Exact version | `ansible/ubuntu/tasks/packages.yml` | `package:ansible-core` |
32
- | The CLI proxy | `cliproxy/compose.yaml`, `image.lock.json`, `requirements.lock`, `launchd/`, `config.template.yaml` | Image digests; hashed Python dependencies | `devfleet cliproxy install`, and `fleet-sync` after it | The proxy's audit |
33
- | Agent profiles and T3 providers | `agents/registry.json`, `agents/t3.json` | The commit | `fleet-sync` | Profile receipts; the plan after every apply |
34
- | Worker time service | `ansible/ubuntu/tasks/services.yml`, offsets in `clock` in `ansible/ubuntu/policy.json` | The commit | `fleet-sync` | `worker:clock` |
35
- | The manager's container watchdog | `ansible/monitoring/container-watchdog.py`, `containerWatchdog` in `ansible/macos/policy.json` | The commit | `fleet-sync` (`hub-darwin.yml`) | `manager:container-watchdog` |
36
- | Docker Desktop on the manager | `docker_desktop_*` under `[vars]` in `mise.toml` | Version and build | You | `manager:docker` (warns) |
37
- | CI | `.github/workflows/` | Each action at a commit; tools from `mise.lock` | GitHub | `features/rules/` |
15
+ | What | Declared in | Pinned by | Applied by | Measured by |
16
+ | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
17
+ | The fleet: machines, services, accounts | Your `fleet.json` | The dev-fleet version in your `package.json`, which names one commit | `fleet-sync` | `fleet-status` |
18
+ | Tools: languages, CLIs, coding agents | `mise.toml` | `mise.lock`: version and checksum for each platform | `mise install --locked` | Tool and `agent:verification` checks |
19
+ | mise itself | `min_version` and `mise_*_sha256` in `mise.toml` | The digest of its release archive, held here | `bootstrap/bootstrap.sh` | Its version, at every toolchain setup |
20
+ | JavaScript dependencies | `package.json` | `bun.lock` | `bun install --frozen-lockfile` | The install fails on a difference |
21
+ | T3 Code, server and desktop app | `t3_*` under `[vars]` in `mise.toml` | SHA-256 of each artifact | `bootstrap/native-t3.ts`; `ansible/ubuntu/t3-desktop.yml` | T3 checks; `t3:version` on every machine |
22
+ | T3 policy on the manager (updates off, linked environments on) | `ansible/ubuntu/t3/policy.json`, shared with the workers | The commit | `ansible/macos/t3-policy.yml`, once, as an administrator (`manager-privilege.yml`) | `t3:updates` |
23
+ | Worker work-in-progress guard | `ansible/ubuntu/wip-guard.py`, `ansible/ubuntu/tasks/recovery.yml` | The commit | `fleet-sync` | `features/ide/work-in-progress.feature` |
24
+ | Ubuntu packages | `packages` and `snapshot` in `ansible/ubuntu/policy.json` | Exact versions from a dated archive snapshot | `ansible/ubuntu/tasks/packages.yml` | `package:*` |
25
+ | Tailscale on workers | `tailscale` in `ansible/ubuntu/policy.json` | Exact version; digest of the signing key | `ansible/ubuntu/tailscale.yml` | `package:tailscale` |
26
+ | Worker policy: power, SSH, sudo, memory, disk | `ansible/ubuntu/*.yml`, values shared with checks in `policy.json` | The commit | `fleet-sync` | Host and resource checks |
27
+ | Monitoring: hub, tunnel, agents | `ansible/monitoring/*.yml`, `compose.yml`, `lock.json` | Image digests; SHA-256 of each agent archive | `fleet-sync` | `beszel:*` |
28
+ | The manager's private route to the hub | `ansible/monitoring/hub-route-darwin.yml` | The commit | `fleet-sync` | The route, read back |
29
+ | The manager's power settings | `pmset` in `ansible/macos/policy.json` | The commit | `ansible/macos/manager.yml`, on every `fleet-sync`, after one grant (`manager-privilege.yml`) | `manager:power` |
30
+ | Ansible on the manager | `ansible/pyproject.toml` | `ansible/uv.lock`: one release, one Python, every dependency hashed | `uv run --locked --isolated` | A hash that differs stops the run |
31
+ | Ansible on a worker | `ansible-core` in `ansible/ubuntu/policy.json` | Exact version | `ansible/ubuntu/tasks/packages.yml` | `package:ansible-core` |
32
+ | The CLI proxy | `cliproxy/compose.yaml`, `image.lock.json`, `requirements.lock`, `launchd/`, `config.template.yaml` | Image digests; hashed Python dependencies | `devfleet cliproxy install`, and `fleet-sync` after it | The proxy's audit |
33
+ | Agent profiles and T3 providers | `agents/registry.json`, `agents/t3.json`; each CLI's configuration from `ansible/agents/templates/` | The commit | `fleet-sync` (agent profiles through `ansible/agents/agents.yml`) | Profile receipts; the plan after every apply; `agent:*:session` |
34
+ | Worker time service | `ansible/ubuntu/tasks/services.yml`, offsets in `clock` in `ansible/ubuntu/policy.json` | The commit | `fleet-sync` | `worker:clock` |
35
+ | The manager's playbook and its receipt | `ansible/macos/manager.yml` | The commit | `fleet-sync`, check mode first, then applied; re-applied at least daily | `manager:playbook` |
36
+ | The manager's network time | `ansible/macos/manager.yml` (an assert) | The commit | macOS | `manager:clock`, `worker:clock-offset` |
37
+ | fleet-watch on a schedule | `fleetWatch` in `ansible/macos/policy.json`, `manager.schedule.watchMinutes` in `fleet.json` | The commit | `fleet-sync` (`manager.yml`) | `manager:watch` |
38
+ | Rotation of the scheduled jobs' logs | `jobLogs` in `ansible/macos/policy.json` | The commit | `ansible/macos/manager-privilege.yml`, once, as an administrator | `manager:job-logs` |
39
+ | The manager's container watchdog | `ansible/monitoring/container-watchdog.py`, `containerWatchdog` in `ansible/macos/policy.json` | The commit | `fleet-sync` (`hub-darwin.yml`) | `manager:container-watchdog` |
40
+ | Docker Desktop on the manager | `docker_desktop_*` under `[vars]` in `mise.toml` | Version and build | You | `manager:docker` (warns) |
41
+ | CI | `.github/workflows/` | Each action at a commit; tools from `mise.lock` | GitHub | `features/rules/` |
38
42
 
39
43
  A value lives in a policy file when something besides the playbook reads it,
40
44
  so the check and the playbook cannot disagree. A value only a playbook uses
@@ -62,11 +66,11 @@ stays in the playbook: the playbook is its declaration.
62
66
  scripts from the declarative files above, not by a playbook.
63
67
  - **Files TypeScript and shell still write.** The installed `fleet.json`, the
64
68
  `current` and `devfleet` links, the `PATH` lines in shell profiles, the
65
- global mise configuration, the T3 server archive, agent profiles and T3
66
- settings. Each is derived
69
+ global mise configuration, the T3 server archive and T3 settings. Each is derived
67
70
  from a tracked file and rewritten on every run, but by code.
68
71
  - **Logins.** Tailscale, Vault, GitHub, each provider account and T3 pairing
69
- belong to a machine and a person. They are checked, never copied.
72
+ (except a replaced worker's, which the manager pairs itself) belong to a
73
+ machine and a person. They are checked, never copied.
70
74
  - **Windows.** `bootstrap/bootstrap.ps1` exists; Windows is not a fleet role
71
75
  and nothing tests it.
72
76
 
@@ -80,13 +84,36 @@ moving to it entirely means NixOS workers and nix-darwin on the manager, which
80
84
  replaces every unit and path that exists today. The one thing above that Nix
81
85
  would settle and the current tools do not is the rest of a worker's packages.
82
86
 
87
+ ## Keeping pins current
88
+
89
+ A pin that is only moved by hand goes stale and is then moved in a large
90
+ batch, the riskiest way to change a fleet. Each pin is moved by a machine,
91
+ one pull request at a time, which CI, the fresh workers and the VM test then
92
+ judge like any change, unless it says below why a person moves it.
93
+
94
+ | Pin | Moved by |
95
+ | ---------------------------------------- | --------------------------------------------------------------------------------------------- |
96
+ | Tools in `mise.toml` and `mise.lock` | Renovate (`mise`), one pull request per tool |
97
+ | JavaScript and Python dependencies | Renovate, with lock file maintenance |
98
+ | Actions and CI service images | Renovate, each action at a commit |
99
+ | mise itself | The [Pins workflow](../.github/workflows/pins.yml): `bun bootstrap/pins.ts mise`, weekly |
100
+ | T3 Code (nightly) | The Pins workflow: `bun bootstrap/pins.ts t3`, weekly, every artifact hashed |
101
+ | The tunnel image of the monitoring hub | Renovate, by tag and digest (a custom manager in `renovate.json`) |
102
+ | Beszel (hub image and agents) | A person, because the agents' binary digests need the archives unpacked; `pins.ts` cannot yet |
103
+ | Docker Desktop on the manager | A person, because a person installs it and dev-fleet only measures the version |
104
+ | The Ubuntu snapshot and package versions | A person, because a new snapshot moves every package at once and each needs its exact version |
105
+
106
+ The workflows never name the mise release: they install it through
107
+ [`.github/actions/mise`](../.github/actions/mise/action.yml), which reads
108
+ `min_version` from `mise.toml`.
109
+
83
110
  ## Changing a pin
84
111
 
85
- | To change | Edit | Then |
86
- | ----------------------- | -------------------------------------------------------------------- | ------------------------------------------------ |
87
- | A tool | Its version in `mise.toml` | Update `mise.lock` with `mise lock` |
88
- | mise | `min_version` and the four `mise_*_sha256` in `mise.toml` | Copy the digests from the release's `SHASUMS256` |
89
- | An Ubuntu package | `packages` in `ansible/ubuntu/policy.json`, and `snapshot` if needed | The VM test in `ansible/ubuntu/tests` |
90
- | Ansible on the manager | `ansible/pyproject.toml` | `uv lock` in `ansible/` |
91
- | Beszel | `ansible/monitoring/lock.json`, `compose.yml`, the agent unit | `bun test features/rules` |
92
- | A manager power setting | `pmset` in `ansible/macos/policy.json` | Run `manager.yml` once on the manager |
112
+ | To change | Edit | Then |
113
+ | ----------------------- | -------------------------------------------------------------------- | ------------------------------------- |
114
+ | A tool | Its version in `mise.toml` | Update `mise.lock` with `mise lock` |
115
+ | mise | `bun bootstrap/pins.ts mise` moves `min_version` and the digests | The fresh-box run |
116
+ | An Ubuntu package | `packages` in `ansible/ubuntu/policy.json`, and `snapshot` if needed | The VM test in `ansible/ubuntu/tests` |
117
+ | Ansible on the manager | `ansible/pyproject.toml` | `uv lock` in `ansible/` |
118
+ | Beszel | `ansible/monitoring/lock.json`, `compose.yml`, the agent unit | `bun test features/rules` |
119
+ | A manager power setting | `pmset` in `ansible/macos/policy.json` | The next `fleet-sync` applies it |