@crvouga/dev-fleet 0.8.0 → 0.9.1

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 (205) hide show
  1. package/README.md +65 -9
  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 +55 -9
  14. package/ansible/macos/manager.yml +154 -7
  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/monitoring/agent-darwin.yml +3 -0
  19. package/ansible/monitoring/agent.yml +2 -0
  20. package/ansible/monitoring/hub-darwin.yml +3 -0
  21. package/ansible/monitoring/hub-route-darwin.yml +1 -0
  22. package/ansible/net/remote-probe.sh +4 -1
  23. package/ansible/platforms.json +2 -2
  24. package/ansible/pyproject.toml +5 -0
  25. package/ansible/ubuntu/baseline.yml +39 -13
  26. package/ansible/ubuntu/disk-gc.py +6 -2
  27. package/ansible/ubuntu/firewall.nft.j2 +50 -0
  28. package/ansible/ubuntu/policy.json +125 -6
  29. package/ansible/ubuntu/reboot.yml +89 -0
  30. package/ansible/ubuntu/recovery.yml +13 -3
  31. package/ansible/ubuntu/t3/policy.json +3 -0
  32. package/ansible/ubuntu/t3-desktop.yml +23 -2
  33. package/ansible/ubuntu/t3-headless.yml +128 -0
  34. package/ansible/ubuntu/t3-health.sh +24 -10
  35. package/ansible/ubuntu/t3-idle.py +37 -2
  36. package/ansible/ubuntu/t3-policy.yml +4 -1
  37. package/ansible/ubuntu/t3-route.yml +1 -0
  38. package/ansible/ubuntu/tailscale.yml +54 -8
  39. package/ansible/ubuntu/tasks/browser.yml +3 -0
  40. package/ansible/ubuntu/tasks/containers-off.yml +43 -0
  41. package/ansible/ubuntu/tasks/docker.yml +53 -6
  42. package/ansible/ubuntu/tasks/dpkg.yml +4 -0
  43. package/ansible/ubuntu/tasks/features.yml +22 -0
  44. package/ansible/ubuntu/tasks/firewall.yml +172 -0
  45. package/ansible/ubuntu/tasks/install.yml +2 -1
  46. package/ansible/ubuntu/tasks/packages.yml +4 -2
  47. package/ansible/ubuntu/tasks/release.yml +6 -0
  48. package/ansible/ubuntu/tasks/resources.yml +6 -4
  49. package/ansible/ubuntu/tasks/services.yml +2 -2
  50. package/ansible/ubuntu/tasks/sizing.yml +49 -0
  51. package/ansible/ubuntu/tasks/snapshot.yml +14 -3
  52. package/ansible/ubuntu/tasks/ssh.yml +61 -4
  53. package/ansible/ubuntu/tasks/t3-restart.yml +3 -1
  54. package/ansible/ubuntu/tasks/user.yml +1 -0
  55. package/ansible/ubuntu/worker-account.yml +117 -0
  56. package/ansible/uv.lock +265 -0
  57. package/bin/devfleet.mjs +42 -4
  58. package/bootstrap/fleet-remote-bootstrap.sh +91 -25
  59. package/bootstrap/global-config.ts +29 -3
  60. package/bootstrap/package-smoke.ts +127 -0
  61. package/bootstrap/pins.ts +189 -0
  62. package/bootstrap/release-notes.ts +88 -0
  63. package/bootstrap/release-version.ts +41 -7
  64. package/bootstrap/worker-features.ts +114 -0
  65. package/bootstrap/worker-seed.yaml +7 -3
  66. package/bootstrap/worker-tools.ts +39 -9
  67. package/cliproxy/README.md +1 -1
  68. package/cliproxy/docs/health-workflow.example.yml +20 -143
  69. package/docs/authentication.md +19 -11
  70. package/docs/consumer-setup.md +42 -27
  71. package/docs/convergence.md +67 -0
  72. package/docs/declared.md +61 -34
  73. package/docs/machine-setup.md +73 -7
  74. package/docs/manager-setup.md +10 -3
  75. package/docs/operations.md +64 -9
  76. package/docs/releasing.md +49 -1
  77. package/docs/runbooks.md +98 -7
  78. package/docs/state.md +23 -11
  79. package/docs/support-matrix.md +40 -4
  80. package/docs/topology.md +67 -6
  81. package/docs/ubuntu-sync.md +78 -28
  82. package/docs/worker-contract.md +122 -19
  83. package/docs/worker-requirements.md +107 -0
  84. package/examples/fleet.json +1 -0
  85. package/examples/workers-from-infrastructure/main.tf +132 -0
  86. package/examples/workers-from-infrastructure/worker-records.json +14 -0
  87. package/llms.txt +3 -1
  88. package/mise.lock +29 -0
  89. package/mise.toml +5 -3
  90. package/package.json +2 -2
  91. package/schema/check-ids.json +1967 -170
  92. package/schema/check-ids.schema.json +34 -0
  93. package/schema/fleet.schema.json +327 -8
  94. package/schema/output/check.schema.json +1 -1
  95. package/schema/output/fleet-state-definition.schema.json +14 -0
  96. package/schema/output/fleet-sync-schedule.schema.json +26 -0
  97. package/schema/output/fleet-sync.schema.json +18 -0
  98. package/schema/output/report.schema.json +3 -2
  99. package/schema/worker-record.schema.json +76 -0
  100. package/src/agents/converge.ts +140 -24
  101. package/src/agents/files.ts +2 -32
  102. package/src/agents/native-verify.ts +34 -3
  103. package/src/agents/secrets.ts +39 -4
  104. package/src/check-owners.ts +61 -2
  105. package/src/check-registry.ts +630 -0
  106. package/src/cli/agent-guide.ts +3 -3
  107. package/src/cli/cli.ts +4 -0
  108. package/src/cli/fleet-manifest.ts +9 -3
  109. package/src/cli/fleet-node-lifecycle.ts +313 -64
  110. package/src/cli/fleet-node-park.ts +238 -0
  111. package/src/cli/fleet-node-seed.ts +51 -35
  112. package/src/cli/fleet-state.ts +2 -1
  113. package/src/cli/fleet-sync-checkout.ts +7 -0
  114. package/src/cli/fleet-sync-enrollment.ts +193 -32
  115. package/src/cli/fleet-sync-forward.ts +4 -0
  116. package/src/cli/fleet-sync-manager.ts +103 -33
  117. package/src/cli/fleet-sync-output.ts +31 -11
  118. package/src/cli/fleet-sync-plan.ts +65 -9
  119. package/src/cli/fleet-sync-records.ts +113 -0
  120. package/src/cli/fleet-sync-remote.ts +151 -9
  121. package/src/cli/fleet-sync-schedule.ts +14 -4
  122. package/src/cli/fleet-sync-state.ts +28 -16
  123. package/src/cli/fleet-sync-vault.ts +77 -0
  124. package/src/cli/fleet-sync-worker.ts +214 -0
  125. package/src/cli/fleet-sync.ts +150 -19
  126. package/src/cli/fleet-validate.ts +59 -21
  127. package/src/cli/fleet-watch.ts +7 -1
  128. package/src/cli/fleet.ts +87 -5
  129. package/src/cli/managed-health.ts +25 -1
  130. package/src/cli/register-fleet.ts +115 -4
  131. package/src/cli/registry.ts +14 -0
  132. package/src/cli/tailnet.ts +43 -10
  133. package/src/cli/ui-data.ts +3 -1
  134. package/src/cli/worker-usage-report.ts +63 -0
  135. package/src/clock.ts +42 -0
  136. package/src/definition.ts +332 -9
  137. package/src/diagnostics.ts +5 -2
  138. package/src/exposure.ts +102 -0
  139. package/src/fleet-config.ts +3 -1
  140. package/src/git-identity.ts +76 -3
  141. package/src/ide/ide.ts +5 -0
  142. package/src/ide/t3.ts +12 -2
  143. package/src/input-hashes.ts +6 -0
  144. package/src/machine/machine-class.ts +59 -0
  145. package/src/machine/platform.ts +90 -11
  146. package/src/machine/state.ts +37 -15
  147. package/src/machine/support-matrix.ts +11 -9
  148. package/src/machine-health.ts +28 -5
  149. package/src/manager-health.ts +148 -2
  150. package/src/manager-sync.ts +65 -4
  151. package/src/monitoring/beszel.ts +8 -8
  152. package/src/monitoring/enroll.ts +8 -1
  153. package/src/monitoring/hub-access.ts +10 -4
  154. package/src/monitoring/systems.ts +138 -0
  155. package/src/network-definition.ts +13 -4
  156. package/src/node-converge-linux.ts +136 -27
  157. package/src/node-converge.ts +13 -3
  158. package/src/platform/ansible-receipt.ts +17 -2
  159. package/src/platform/ansible.ts +40 -4
  160. package/src/platform/manager-host.ts +25 -8
  161. package/src/platform/resource-verify.ts +18 -7
  162. package/src/platform/t3-policy.ts +24 -10
  163. package/src/platform/t3-service.ts +109 -34
  164. package/src/platform/ubuntu-release.ts +24 -0
  165. package/src/platform/ubuntu-verify.ts +31 -3
  166. package/src/platform/ubuntu-worker.ts +58 -0
  167. package/src/platform/worker-platform.ts +72 -0
  168. package/src/protocol.ts +1 -1
  169. package/src/public-reachability.ts +186 -0
  170. package/src/remote-bootstrap.ts +50 -12
  171. package/src/remote-operation.ts +18 -4
  172. package/src/repair-plan.ts +23 -12
  173. package/src/report.ts +2 -1
  174. package/src/schedule-check.ts +21 -2
  175. package/src/secrets.ts +4 -0
  176. package/src/service-auth.ts +55 -48
  177. package/src/ssh-connection.ts +141 -0
  178. package/src/ssh.ts +63 -24
  179. package/src/state/store.ts +4 -2
  180. package/src/state/sync.ts +35 -6
  181. package/src/state/workers.ts +140 -0
  182. package/src/t3/pairing.ts +59 -0
  183. package/src/t3/peer-link.ts +333 -0
  184. package/src/t3/version.ts +1 -1
  185. package/src/tailnet-cleanup.ts +185 -0
  186. package/src/tailnet-control.ts +346 -0
  187. package/src/tailscale.ts +1 -1
  188. package/src/toolchain-checks.ts +11 -1
  189. package/src/types.ts +4 -2
  190. package/src/worker-account.ts +63 -0
  191. package/src/worker-activity.ts +303 -0
  192. package/src/worker-capability-checks.ts +55 -16
  193. package/src/worker-capacity.ts +31 -0
  194. package/src/worker-exposure.ts +388 -0
  195. package/src/worker-features.ts +201 -0
  196. package/src/worker-host-checks.ts +71 -20
  197. package/src/worker-manifest.ts +27 -4
  198. package/src/worker-parking.ts +70 -0
  199. package/src/worker-policy.ts +17 -1
  200. package/src/worker-readiness.ts +15 -3
  201. package/src/worker-reboot.ts +129 -0
  202. package/src/worker-records.ts +305 -0
  203. package/src/worker-seed.ts +16 -11
  204. package/src/worker-sizing.ts +76 -0
  205. package/src/cli/schedule-logs.ts +0 -36
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "check-ids.schema.json",
4
+ "title": "The check-ID registry, version 1",
5
+ "description": "schema/check-ids.json: every check ID dev-fleet reports, generated from the code.",
6
+ "type": "object",
7
+ "required": ["version", "checks"],
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "checks": {
11
+ "type": "array",
12
+ "items": {
13
+ "type": "object",
14
+ "required": ["id", "meaning", "severity", "repair"],
15
+ "properties": {
16
+ "id": {
17
+ "type": "string",
18
+ "pattern": "^[a-z0-9-]+(:[A-Za-z0-9:.*_-]+)?$"
19
+ },
20
+ "meaning": { "type": "string" },
21
+ "severity": { "enum": ["required", "advisory"] },
22
+ "requirement": { "const": true },
23
+ "repair": {
24
+ "type": "object",
25
+ "properties": {
26
+ "recipe": { "type": "string" },
27
+ "manual": { "type": "string" }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
@@ -53,6 +53,25 @@
53
53
  "neverSleepOnPower": {
54
54
  "const": true,
55
55
  "description": "Workers depend on the manager's singleton services, so it never sleeps on AC power."
56
+ },
57
+ "schedule": {
58
+ "type": "object",
59
+ "additionalProperties": false,
60
+ "description": "How often the manager's scheduled jobs run. Each defaults to the interval in ansible/macos/policy.json.",
61
+ "properties": {
62
+ "syncMinutes": {
63
+ "type": "integer",
64
+ "minimum": 5,
65
+ "maximum": 1440,
66
+ "description": "fleet-sync --schedule converges the fleet this often (default 30)."
67
+ },
68
+ "watchMinutes": {
69
+ "type": "integer",
70
+ "minimum": 5,
71
+ "maximum": 1440,
72
+ "description": "fleet-watch checks the fleet and stores its heartbeat this often (default 15). Keep it well under the max-age-minutes of the heartbeat workflow."
73
+ }
74
+ }
56
75
  }
57
76
  }
58
77
  },
@@ -62,7 +81,7 @@
62
81
  "items": {
63
82
  "type": "object",
64
83
  "additionalProperties": false,
65
- "required": ["id", "ssh", "os"],
84
+ "required": ["id", "os"],
66
85
  "properties": {
67
86
  "id": {
68
87
  "type": "string",
@@ -72,7 +91,7 @@
72
91
  "ssh": {
73
92
  "type": "string",
74
93
  "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,252}$",
75
- "description": "The worker's Host alias in the manager's ~/.ssh/config. It must work with BatchMode=yes and a host key already in known_hosts."
94
+ "description": "Optional: a Host alias in the operator's own ~/.ssh/config, checked against their own known_hosts. Without one the worker is reached from its network block (user, sshPort, tailscaleName) with a host key pinned in the fleet state or given as network.hostKey, against a known_hosts file dev-fleet owns; the operator's ~/.ssh is neither read for it nor changed."
76
95
  },
77
96
  "os": {
78
97
  "const": "linux",
@@ -104,9 +123,38 @@
104
123
  "minimum": 1,
105
124
  "maximum": 65535,
106
125
  "description": "The port the worker T3 server listens on, normally 3773."
126
+ },
127
+ "hostKey": {
128
+ "type": "string",
129
+ "pattern": "^(ssh-ed25519|ecdsa-sha2-nistp256|ssh-rsa) [A-Za-z0-9+/=]{16,}$",
130
+ "description": "The SSH host public key, \"TYPE BASE64\", given as data (a worker record writes it). It is the pinned key; a worker without one is pinned in the fleet state (fleet-node-replace --host-key, fleet-node-seed --apply)."
107
131
  }
108
132
  }
109
133
  },
134
+ "features": {
135
+ "type": "object",
136
+ "additionalProperties": false,
137
+ "properties": {
138
+ "preset": {
139
+ "enum": ["editing"],
140
+ "description": "A named set of features, applied before the fields beside it. editing: no containers, a headless session and only the base tools (it adds no tool profile)."
141
+ },
142
+ "containers": {
143
+ "type": "boolean",
144
+ "description": "Docker on the worker: its packages, service, docker group membership, the docker-cli tool and the Docker step of the disk GC. Default true. Turning it off on an existing worker removes what dev-fleet installed for it; fleet-sync --dry-run lists what."
145
+ },
146
+ "session": {
147
+ "enum": ["desktop", "headless"],
148
+ "description": "How the worker runs T3. desktop (default): the pinned desktop app in an auto-login graphical session. headless: the pinned T3 server as a lingering user service, with no display manager. ~/.t3 is kept when this changes."
149
+ }
150
+ },
151
+ "description": "This worker's features, over the fleet-wide worker.features."
152
+ },
153
+ "powerRecoveryNote": {
154
+ "type": "string",
155
+ "pattern": "^[^\\n\\r]{1,120}$",
156
+ "description": "On a virtual machine, who restarts it after a power loss (for example: provider restarts instances). Shown with worker:power-loss-recovery, which is not applicable to a virtual machine."
157
+ },
110
158
  "toolProfiles": {
111
159
  "type": "array",
112
160
  "uniqueItems": true,
@@ -114,6 +162,75 @@
114
162
  "enum": ["web", "go", "rust", "jvm"]
115
163
  },
116
164
  "description": "Tool profiles this worker installs on top of the fleet-wide ones: web (pnpm, Deno, Yarn), go, rust (with CMake and Ninja), jvm (Temurin). Each is a locked tool set dev-fleet ships."
165
+ },
166
+ "exposure": {
167
+ "type": "object",
168
+ "additionalProperties": false,
169
+ "description": "What this worker opens beyond loopback and the tailnet. A field named here replaces the one of worker.exposure.",
170
+ "properties": {
171
+ "publicSsh": {
172
+ "type": "boolean",
173
+ "description": "Break-glass: keep the SSH port open beyond the tailnet. Default false: after enrolment SSH is reached only over the tailnet."
174
+ },
175
+ "publicPorts": {
176
+ "type": "array",
177
+ "maxItems": 32,
178
+ "uniqueItems": true,
179
+ "items": {
180
+ "type": "object",
181
+ "additionalProperties": false,
182
+ "required": ["protocol", "port"],
183
+ "properties": {
184
+ "protocol": {
185
+ "enum": ["tcp", "udp"]
186
+ },
187
+ "port": {
188
+ "type": "integer",
189
+ "minimum": 1,
190
+ "maximum": 65535
191
+ }
192
+ }
193
+ },
194
+ "description": "Ports opened on every interface; a listener on one passes worker:exposure. Default none."
195
+ },
196
+ "otherListeners": {
197
+ "enum": ["warn", "fail"],
198
+ "description": "How worker:exposure reports a listener beyond loopback and the tailnet that no fleet service owns, such as an agent's development server on 0.0.0.0. The firewall closes it either way. Default warn; a fleet service there always fails."
199
+ }
200
+ }
201
+ },
202
+ "labels": {
203
+ "type": "object",
204
+ "additionalProperties": {
205
+ "type": "string",
206
+ "pattern": "^[^\\u0000-\\u001f\\u007f]{0,128}$"
207
+ },
208
+ "description": "Free-form labels, from a worker record; only displayed. Names are lowercase, at most 32 of them."
209
+ },
210
+ "billing": {
211
+ "type": "object",
212
+ "additionalProperties": false,
213
+ "required": ["hourlyRate"],
214
+ "properties": {
215
+ "hourlyRate": {
216
+ "type": "number",
217
+ "minimum": 0,
218
+ "description": "What one hour of running costs, as you declare it. dev-fleet asks no pricing API; it only displays and sums it."
219
+ },
220
+ "currency": {
221
+ "type": "string",
222
+ "pattern": "^[A-Z]{3}$"
223
+ },
224
+ "note": {
225
+ "type": "string",
226
+ "pattern": "^[^\\u0000-\\u001f\\u007f]{1,200}$"
227
+ }
228
+ },
229
+ "description": "What the worker costs while it runs: fleet-status sums it for idle workers."
230
+ },
231
+ "origin": {
232
+ "const": "records",
233
+ "description": "Written by dev-fleet for a worker read from workerRecords, in the copy it installs and stores; do not write it."
117
234
  }
118
235
  }
119
236
  }
@@ -123,6 +240,74 @@
123
240
  "pattern": "^[a-z][a-z0-9-]{0,62}$",
124
241
  "description": "The id of the worker that proves a revision new to the fleet first. When it is unreachable, the next reachable worker proves it instead. Default: the first reachable worker."
125
242
  },
243
+ "workerRecords": {
244
+ "type": "array",
245
+ "maxItems": 8,
246
+ "items": {
247
+ "oneOf": [
248
+ {
249
+ "type": "object",
250
+ "additionalProperties": false,
251
+ "required": ["file"],
252
+ "properties": {
253
+ "file": {
254
+ "type": "string",
255
+ "description": "A JSON file of worker records (schema/worker-record.schema.json), relative to fleet.json."
256
+ }
257
+ }
258
+ },
259
+ {
260
+ "type": "object",
261
+ "additionalProperties": false,
262
+ "required": ["command"],
263
+ "properties": {
264
+ "command": {
265
+ "type": "array",
266
+ "minItems": 1,
267
+ "items": {
268
+ "type": "string"
269
+ },
270
+ "description": "A command run where fleet.json is that prints worker records, such as an infrastructure tool printing its outputs."
271
+ }
272
+ }
273
+ }
274
+ ]
275
+ },
276
+ "description": "Where generated worker records are read: their workers join the list above. An infrastructure tool writes them; dev-fleet only reads them and never creates a machine. A worker whose record disappears is retired by the next fleet-sync."
277
+ },
278
+ "parking": {
279
+ "type": "object",
280
+ "additionalProperties": false,
281
+ "properties": {
282
+ "idleAfterHours": {
283
+ "type": "number",
284
+ "minimum": 1,
285
+ "maximum": 8760,
286
+ "description": "A worker that has run no agent turn for longer is reported as worker:idle."
287
+ },
288
+ "autoPark": {
289
+ "type": "boolean",
290
+ "description": "fleet-watch parks a worker idle longer than idleAfterHours. Off unless true."
291
+ },
292
+ "stop": {
293
+ "type": "array",
294
+ "minItems": 1,
295
+ "items": {
296
+ "type": "string"
297
+ },
298
+ "description": "Run with the worker ID as its last argument when the worker is parked."
299
+ },
300
+ "start": {
301
+ "type": "array",
302
+ "minItems": 1,
303
+ "items": {
304
+ "type": "string"
305
+ },
306
+ "description": "Run with the worker ID as its last argument when the worker is resumed."
307
+ }
308
+ },
309
+ "description": "Workers that are stopped on purpose (fleet-node-park) and when an idle one is reported."
310
+ },
126
311
  "services": {
127
312
  "type": "object",
128
313
  "additionalProperties": false,
@@ -166,7 +351,9 @@
166
351
  },
167
352
  "order": {
168
353
  "type": "array",
169
- "items": { "type": "string" },
354
+ "items": {
355
+ "type": "string"
356
+ },
170
357
  "description": "Account labels (sign-in emails) first, in this order; the others follow in the order of their routing prefix."
171
358
  }
172
359
  }
@@ -263,7 +450,9 @@
263
450
  },
264
451
  "architectures": {
265
452
  "type": "array",
266
- "items": { "enum": ["amd64", "arm64"] },
453
+ "items": {
454
+ "enum": ["amd64", "arm64"]
455
+ },
267
456
  "description": "Exactly the architectures the policy names for that release."
268
457
  }
269
458
  },
@@ -323,6 +512,25 @@
323
512
  "neverSleep": {
324
513
  "const": true
325
514
  },
515
+ "features": {
516
+ "type": "object",
517
+ "additionalProperties": false,
518
+ "properties": {
519
+ "preset": {
520
+ "enum": ["editing"],
521
+ "description": "A named set of features, applied before the fields beside it. editing: no containers, a headless session and only the base tools (it adds no tool profile)."
522
+ },
523
+ "containers": {
524
+ "type": "boolean",
525
+ "description": "Docker on the worker: its packages, service, docker group membership, the docker-cli tool and the Docker step of the disk GC. Default true. Turning it off on an existing worker removes what dev-fleet installed for it; fleet-sync --dry-run lists what."
526
+ },
527
+ "session": {
528
+ "enum": ["desktop", "headless"],
529
+ "description": "How the worker runs T3. desktop (default): the pinned desktop app in an auto-login graphical session. headless: the pinned T3 server as a lingering user service, with no display manager. ~/.t3 is kept when this changes."
530
+ }
531
+ },
532
+ "description": "Features every worker has unless its own features say otherwise. Absent: containers and a desktop session, as before features existed."
533
+ },
326
534
  "toolProfiles": {
327
535
  "type": "array",
328
536
  "uniqueItems": true,
@@ -339,6 +547,42 @@
339
547
  "pattern": "^[A-Za-z0-9@:/._-]{1,75}$"
340
548
  },
341
549
  "description": "Single tools every worker installs: names of tools dev-fleet's mise.toml pins (for example fd or ripgrep). A tool it does not pin is refused; ask for it in an issue."
550
+ },
551
+ "exposure": {
552
+ "type": "object",
553
+ "additionalProperties": false,
554
+ "description": "What every worker opens beyond loopback and the tailnet. Without it inbound traffic arrives only over loopback and the tailnet, sshd takes keys only and no root login, and published container ports are closed beyond the tailnet.",
555
+ "properties": {
556
+ "publicSsh": {
557
+ "type": "boolean",
558
+ "description": "Break-glass: keep the SSH port open beyond the tailnet. Default false: after enrolment SSH is reached only over the tailnet."
559
+ },
560
+ "publicPorts": {
561
+ "type": "array",
562
+ "maxItems": 32,
563
+ "uniqueItems": true,
564
+ "items": {
565
+ "type": "object",
566
+ "additionalProperties": false,
567
+ "required": ["protocol", "port"],
568
+ "properties": {
569
+ "protocol": {
570
+ "enum": ["tcp", "udp"]
571
+ },
572
+ "port": {
573
+ "type": "integer",
574
+ "minimum": 1,
575
+ "maximum": 65535
576
+ }
577
+ }
578
+ },
579
+ "description": "Ports opened on every interface; a listener on one passes worker:exposure. Default none."
580
+ },
581
+ "otherListeners": {
582
+ "enum": ["warn", "fail"],
583
+ "description": "How worker:exposure reports a listener beyond loopback and the tailnet that no fleet service owns, such as an agent's development server on 0.0.0.0. The firewall closes it either way. Default warn; a fleet service there always fails."
584
+ }
585
+ }
342
586
  }
343
587
  },
344
588
  "description": "Policy every worker must meet. All but the two logins, the capabilities and the tools are fixed."
@@ -359,7 +603,9 @@
359
603
  "type": "array",
360
604
  "minItems": 1,
361
605
  "uniqueItems": true,
362
- "items": { "enum": ["t3"] },
606
+ "items": {
607
+ "enum": ["t3"]
608
+ },
363
609
  "description": "The IDEs every machine runs. Defaults to [\"t3\"]; the README's support matrix lists the supported ones."
364
610
  },
365
611
  "t3": {
@@ -372,16 +618,23 @@
372
618
  "description": "Top-level T3 settings every machine holds the same (tool servers, projects, selected preferences). Keys not named here stay local; a key edited on a machine since dev-fleet last wrote it is reported as a conflict, never overwritten.",
373
619
  "propertyNames": {
374
620
  "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
375
- "not": { "const": "providerInstances" }
621
+ "not": {
622
+ "const": "providerInstances"
623
+ }
376
624
  }
377
625
  },
378
626
  "requiredModels": {
379
627
  "type": "object",
380
628
  "description": "Models T3 must offer, and the proxy must serve, for every account of a tool, by tool (claude, codex, opencode).",
381
- "propertyNames": { "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$" },
629
+ "propertyNames": {
630
+ "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$"
631
+ },
382
632
  "additionalProperties": {
383
633
  "type": "array",
384
- "items": { "type": "string", "pattern": "^[A-Za-z0-9._:-]{1,128}$" }
634
+ "items": {
635
+ "type": "string",
636
+ "pattern": "^[A-Za-z0-9._:-]{1,128}$"
637
+ }
385
638
  }
386
639
  }
387
640
  }
@@ -646,6 +899,22 @@
646
899
  }
647
900
  },
648
901
  "description": "Optional: a short-lived, single-use, tagged Tailscale auth key a new worker joins the tailnet with (fleet-node-seed)."
902
+ },
903
+ "tailnet-admin": {
904
+ "type": "object",
905
+ "additionalProperties": false,
906
+ "required": ["path", "field"],
907
+ "properties": {
908
+ "path": {
909
+ "type": "string",
910
+ "pattern": "^[A-Za-z0-9._/-]+$"
911
+ },
912
+ "field": {
913
+ "type": "string",
914
+ "pattern": "^[A-Za-z0-9_]+$"
915
+ }
916
+ },
917
+ "description": "Only with tailnet: an API credential of the tailnet's control plane that creates single-use auth keys for new workers. It stays on the manager."
649
918
  }
650
919
  },
651
920
  "description": "Required for every secret except github-token, which defaults to the field GITHUB_TOKEN beside proxy-client-key."
@@ -674,6 +943,10 @@
674
943
  "type": "string",
675
944
  "pattern": "^https://github\\.com/[A-Za-z0-9_.-]{1,100}/[A-Za-z0-9_.-]{1,100}$",
676
945
  "description": "A GitHub repository the fleet's account can push to, ideally private. Each node lists it through git with its own credentials."
946
+ },
947
+ "signing": {
948
+ "enum": ["ssh"],
949
+ "description": "Sign every commit and tag. ssh: each node makes an SSH key of its own (~/.ssh/dev-fleet-git-signing), which never leaves it, and signs with it; measured as worker:git-signing. GitHub shows the commits as verified once each node's public key is added as a signing key to the account."
677
950
  }
678
951
  }
679
952
  },
@@ -760,6 +1033,52 @@
760
1033
  "description": "owner/name of a repository fleet-github-verify branches, pushes to and opens pull requests on, then cleans up."
761
1034
  }
762
1035
  }
1036
+ },
1037
+ "tailnet": {
1038
+ "description": "Optional: the tailnet control plane fleet-sync and fleet-node-seed enrol new workers with, without a login on the machine. Its credential is the secret tailnet-admin.",
1039
+ "oneOf": [
1040
+ {
1041
+ "type": "object",
1042
+ "additionalProperties": false,
1043
+ "required": ["api", "tag"],
1044
+ "properties": {
1045
+ "api": { "const": "tailscale" },
1046
+ "url": {
1047
+ "type": "string",
1048
+ "pattern": "^https://[A-Za-z0-9.-]{1,253}(:[0-9]{1,5})?$",
1049
+ "description": "The API origin. Default: https://api.tailscale.com."
1050
+ },
1051
+ "tag": {
1052
+ "type": "string",
1053
+ "pattern": "^tag:[a-z0-9][a-z0-9-]{0,62}$",
1054
+ "description": "The tag every enrolled worker carries, so access rules and key expiry apply to workers as a group."
1055
+ }
1056
+ }
1057
+ },
1058
+ {
1059
+ "type": "object",
1060
+ "additionalProperties": false,
1061
+ "required": ["api", "url", "tag", "user"],
1062
+ "properties": {
1063
+ "api": { "const": "headscale" },
1064
+ "url": {
1065
+ "type": "string",
1066
+ "pattern": "^https://[A-Za-z0-9.-]{1,253}(:[0-9]{1,5})?$",
1067
+ "description": "The Headscale server: its API, and the login server workers join."
1068
+ },
1069
+ "tag": {
1070
+ "type": "string",
1071
+ "pattern": "^tag:[a-z0-9][a-z0-9-]{0,62}$",
1072
+ "description": "The tag every enrolled worker carries, so access rules apply to workers as a group."
1073
+ },
1074
+ "user": {
1075
+ "type": "string",
1076
+ "pattern": "^[A-Za-z0-9._@-]{1,64}$",
1077
+ "description": "The Headscale user the keys belong to (its ID from Headscale 0.26)."
1078
+ }
1079
+ }
1080
+ }
1081
+ ]
763
1082
  }
764
1083
  }
765
1084
  }
@@ -7,7 +7,7 @@
7
7
  "required": ["id", "status", "summary"],
8
8
  "properties": {
9
9
  "id": { "type": "string", "pattern": "^[a-z0-9-]+(:[A-Za-z0-9:._-]+)?$" },
10
- "status": { "enum": ["pass", "warn", "fail", "skip"] },
10
+ "status": { "enum": ["pass", "warn", "fail", "skip", "not-applicable"] },
11
11
  "summary": { "type": "string" },
12
12
  "required": { "type": "boolean" },
13
13
  "phaseDurationMs": { "type": "number" },
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "fleet-state-definition.schema.json",
4
+ "title": "A fleet configuration printed by fleet-state, version 1",
5
+ "description": "What devfleet fleet-state --example and fleet-state --export print with --json: the configuration itself is definition, and follows schema/fleet.schema.json.",
6
+ "type": "object",
7
+ "required": ["version", "command", "ok", "definition"],
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "command": { "const": "fleet-state" },
11
+ "ok": { "const": true },
12
+ "definition": { "type": "object" }
13
+ }
14
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "fleet-sync-schedule.schema.json",
4
+ "title": "The fleet-sync --schedule report, version 2",
5
+ "description": "What devfleet fleet-sync --schedule ACTION prints with --json. status names whether the schedule is installed, its interval, its last three runs and the manager:schedule check; enable, pause, resume and disable say whether the change was made.",
6
+ "type": "object",
7
+ "required": ["version", "command", "scheduling"],
8
+ "properties": {
9
+ "version": { "const": 2 },
10
+ "command": { "const": "fleet-sync" },
11
+ "execution": { "const": "manager-push" },
12
+ "ok": { "type": "boolean" },
13
+ "scheduling": {
14
+ "type": ["string", "object"],
15
+ "properties": {
16
+ "installed": { "type": "boolean" },
17
+ "supported": { "type": "boolean" }
18
+ }
19
+ },
20
+ "intervalSeconds": { "type": "integer" },
21
+ "revision": { "type": "string" },
22
+ "summary": { "type": "string" },
23
+ "runs": { "type": "array", "items": { "type": "object" } },
24
+ "checks": { "type": "array", "items": { "$ref": "check.schema.json" } }
25
+ }
26
+ }
@@ -27,6 +27,12 @@
27
27
  "candidates": { "type": "integer" },
28
28
  "dashboardUrl": { "type": ["string", "null"] },
29
29
  "planFingerprint": { "type": "string" },
30
+ "canary": { "type": "string" },
31
+ "enrolled": {
32
+ "description": "The workers this run enrolled: added to the roster, or bound to their tailnet device.",
33
+ "type": "array",
34
+ "items": { "type": "string" }
35
+ },
30
36
  "nodes": {
31
37
  "type": "array",
32
38
  "items": {
@@ -105,6 +111,18 @@
105
111
  "changes": { "type": "boolean" }
106
112
  }
107
113
  }
114
+ },
115
+ "removals": {
116
+ "type": "array",
117
+ "description": "What the sync removes from a worker whose features the fleet configuration turned off since it was last applied.",
118
+ "items": {
119
+ "type": "object",
120
+ "required": ["node", "removes"],
121
+ "properties": {
122
+ "node": { "type": "string" },
123
+ "removes": { "type": "array", "items": { "type": "string" } }
124
+ }
125
+ }
108
126
  }
109
127
  }
110
128
  }
@@ -26,12 +26,13 @@
26
26
  },
27
27
  "summary": {
28
28
  "type": "object",
29
- "required": ["pass", "warn", "fail", "skip"],
29
+ "required": ["pass", "warn", "fail", "skip", "not-applicable"],
30
30
  "properties": {
31
31
  "pass": { "type": "integer" },
32
32
  "warn": { "type": "integer" },
33
33
  "fail": { "type": "integer" },
34
- "skip": { "type": "integer" }
34
+ "skip": { "type": "integer" },
35
+ "not-applicable": { "type": "integer" }
35
36
  }
36
37
  }
37
38
  }
@@ -0,0 +1,76 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "dev-fleet worker records, version 1",
4
+ "description": "Workers as the tool that created them describes them, without naming the tool or the provider. fleet.json names a file or a command that returns this document under workerRecords; dev-fleet only reads it, never creates a machine and never calls a provider. Each record is a declared worker reached from its data: its host key is pinned as given. A record needs a tailnetName or an address. A worker whose record disappears (a file that is gone holds no record) is retired by the next fleet-sync.",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["version", "workers"],
8
+ "properties": {
9
+ "$schema": { "type": "string" },
10
+ "version": { "const": 1 },
11
+ "workers": {
12
+ "type": "array",
13
+ "maxItems": 64,
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["id", "user"],
18
+ "properties": {
19
+ "id": {
20
+ "type": "string",
21
+ "pattern": "^[a-z][a-z0-9-]{0,62}$",
22
+ "description": "The worker's fleet ID. No other worker, inline or from a record, may have it."
23
+ },
24
+ "tailnetName": {
25
+ "type": "string",
26
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,252}$",
27
+ "description": "The tailnet name the machine joins under, without the trailing dot."
28
+ },
29
+ "address": {
30
+ "type": "string",
31
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,252}$",
32
+ "description": "A name or address SSH reaches the machine at, when it has no tailnet name."
33
+ },
34
+ "user": {
35
+ "type": "string",
36
+ "pattern": "^[a-z_][a-z0-9_-]{0,31}$",
37
+ "description": "The non-root account with passwordless sudo the manager signs in as."
38
+ },
39
+ "port": {
40
+ "type": "integer",
41
+ "minimum": 1,
42
+ "maximum": 65535,
43
+ "description": "The port sshd listens on. Default 22."
44
+ },
45
+ "hostKey": {
46
+ "type": "string",
47
+ "pattern": "^(ssh-ed25519|ecdsa-sha2-nistp256|ssh-rsa) [A-Za-z0-9+/=]{16,}$",
48
+ "description": "The machine's SSH host public key, \"TYPE BASE64\": generated before the machine exists (devfleet fleet-node-seed prints it) or read from the machine by the tool that created it. It is pinned, never learned on first use. Without it the key the fleet state pins is used (devfleet fleet-node-seed --apply --yes), and until there is one every session is refused."
49
+ },
50
+ "t3Port": {
51
+ "type": "integer",
52
+ "minimum": 1,
53
+ "maximum": 65535,
54
+ "description": "The port the worker's T3 server listens on. Default 3773."
55
+ },
56
+ "labels": {
57
+ "type": "object",
58
+ "additionalProperties": { "type": "string" },
59
+ "description": "Free-form labels; only displayed."
60
+ },
61
+ "billing": {
62
+ "type": "object",
63
+ "additionalProperties": false,
64
+ "required": ["hourlyRate"],
65
+ "properties": {
66
+ "hourlyRate": { "type": "number", "minimum": 0 },
67
+ "currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
68
+ "note": { "type": "string" }
69
+ },
70
+ "description": "What the machine costs while it runs, as you declare it; fleet-status sums it for idle workers."
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }