@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.
- package/README.md +65 -9
- package/agents/audit.md +18 -18
- package/ansible/agents/agents.yml +134 -0
- package/ansible/agents/templates/antigravity.settings.json.j2 +3 -0
- package/ansible/agents/templates/claude.settings.json.j2 +3 -0
- package/ansible/agents/templates/codex.config.toml.j2 +11 -0
- package/ansible/agents/templates/gemini.settings.json.j2 +14 -0
- package/ansible/agents/templates/grok.config.toml.j2 +19 -0
- package/ansible/agents/templates/opencode.json.j2 +24 -0
- package/ansible/agents/templates/receipt.json.j2 +10 -0
- package/ansible/ansible.cfg +14 -0
- package/ansible/github/git.yml +82 -0
- package/ansible/macos/manager-privilege.yml +55 -9
- package/ansible/macos/manager.yml +154 -7
- package/ansible/macos/policy.json +13 -0
- package/ansible/macos/t3-policy.yml +5 -3
- package/ansible/macos/vault-renew.yml +85 -0
- package/ansible/monitoring/agent-darwin.yml +3 -0
- package/ansible/monitoring/agent.yml +2 -0
- package/ansible/monitoring/hub-darwin.yml +3 -0
- package/ansible/monitoring/hub-route-darwin.yml +1 -0
- package/ansible/net/remote-probe.sh +4 -1
- package/ansible/platforms.json +2 -2
- package/ansible/pyproject.toml +5 -0
- package/ansible/ubuntu/baseline.yml +39 -13
- package/ansible/ubuntu/disk-gc.py +6 -2
- package/ansible/ubuntu/firewall.nft.j2 +50 -0
- package/ansible/ubuntu/policy.json +125 -6
- package/ansible/ubuntu/reboot.yml +89 -0
- package/ansible/ubuntu/recovery.yml +13 -3
- package/ansible/ubuntu/t3/policy.json +3 -0
- package/ansible/ubuntu/t3-desktop.yml +23 -2
- package/ansible/ubuntu/t3-headless.yml +128 -0
- package/ansible/ubuntu/t3-health.sh +24 -10
- package/ansible/ubuntu/t3-idle.py +37 -2
- package/ansible/ubuntu/t3-policy.yml +4 -1
- package/ansible/ubuntu/t3-route.yml +1 -0
- package/ansible/ubuntu/tailscale.yml +54 -8
- package/ansible/ubuntu/tasks/browser.yml +3 -0
- package/ansible/ubuntu/tasks/containers-off.yml +43 -0
- package/ansible/ubuntu/tasks/docker.yml +53 -6
- package/ansible/ubuntu/tasks/dpkg.yml +4 -0
- package/ansible/ubuntu/tasks/features.yml +22 -0
- package/ansible/ubuntu/tasks/firewall.yml +172 -0
- package/ansible/ubuntu/tasks/install.yml +2 -1
- package/ansible/ubuntu/tasks/packages.yml +4 -2
- package/ansible/ubuntu/tasks/release.yml +6 -0
- package/ansible/ubuntu/tasks/resources.yml +6 -4
- package/ansible/ubuntu/tasks/services.yml +2 -2
- package/ansible/ubuntu/tasks/sizing.yml +49 -0
- package/ansible/ubuntu/tasks/snapshot.yml +14 -3
- package/ansible/ubuntu/tasks/ssh.yml +61 -4
- package/ansible/ubuntu/tasks/t3-restart.yml +3 -1
- package/ansible/ubuntu/tasks/user.yml +1 -0
- package/ansible/ubuntu/worker-account.yml +117 -0
- package/ansible/uv.lock +265 -0
- package/bin/devfleet.mjs +42 -4
- package/bootstrap/fleet-remote-bootstrap.sh +91 -25
- package/bootstrap/global-config.ts +29 -3
- package/bootstrap/package-smoke.ts +127 -0
- package/bootstrap/pins.ts +189 -0
- package/bootstrap/release-notes.ts +88 -0
- package/bootstrap/release-version.ts +41 -7
- package/bootstrap/worker-features.ts +114 -0
- package/bootstrap/worker-seed.yaml +7 -3
- package/bootstrap/worker-tools.ts +39 -9
- package/cliproxy/README.md +1 -1
- package/cliproxy/docs/health-workflow.example.yml +20 -143
- package/docs/authentication.md +19 -11
- package/docs/consumer-setup.md +42 -27
- package/docs/convergence.md +67 -0
- package/docs/declared.md +61 -34
- package/docs/machine-setup.md +73 -7
- package/docs/manager-setup.md +10 -3
- package/docs/operations.md +64 -9
- package/docs/releasing.md +49 -1
- package/docs/runbooks.md +98 -7
- package/docs/state.md +23 -11
- package/docs/support-matrix.md +40 -4
- package/docs/topology.md +67 -6
- package/docs/ubuntu-sync.md +78 -28
- package/docs/worker-contract.md +122 -19
- package/docs/worker-requirements.md +107 -0
- package/examples/fleet.json +1 -0
- package/examples/workers-from-infrastructure/main.tf +132 -0
- package/examples/workers-from-infrastructure/worker-records.json +14 -0
- package/llms.txt +3 -1
- package/mise.lock +29 -0
- package/mise.toml +5 -3
- package/package.json +2 -2
- package/schema/check-ids.json +1967 -170
- package/schema/check-ids.schema.json +34 -0
- package/schema/fleet.schema.json +327 -8
- package/schema/output/check.schema.json +1 -1
- package/schema/output/fleet-state-definition.schema.json +14 -0
- package/schema/output/fleet-sync-schedule.schema.json +26 -0
- package/schema/output/fleet-sync.schema.json +18 -0
- package/schema/output/report.schema.json +3 -2
- package/schema/worker-record.schema.json +76 -0
- package/src/agents/converge.ts +140 -24
- package/src/agents/files.ts +2 -32
- package/src/agents/native-verify.ts +34 -3
- package/src/agents/secrets.ts +39 -4
- package/src/check-owners.ts +61 -2
- package/src/check-registry.ts +630 -0
- package/src/cli/agent-guide.ts +3 -3
- package/src/cli/cli.ts +4 -0
- package/src/cli/fleet-manifest.ts +9 -3
- package/src/cli/fleet-node-lifecycle.ts +313 -64
- package/src/cli/fleet-node-park.ts +238 -0
- package/src/cli/fleet-node-seed.ts +51 -35
- package/src/cli/fleet-state.ts +2 -1
- package/src/cli/fleet-sync-checkout.ts +7 -0
- package/src/cli/fleet-sync-enrollment.ts +193 -32
- package/src/cli/fleet-sync-forward.ts +4 -0
- package/src/cli/fleet-sync-manager.ts +103 -33
- package/src/cli/fleet-sync-output.ts +31 -11
- package/src/cli/fleet-sync-plan.ts +65 -9
- package/src/cli/fleet-sync-records.ts +113 -0
- package/src/cli/fleet-sync-remote.ts +151 -9
- package/src/cli/fleet-sync-schedule.ts +14 -4
- package/src/cli/fleet-sync-state.ts +28 -16
- package/src/cli/fleet-sync-vault.ts +77 -0
- package/src/cli/fleet-sync-worker.ts +214 -0
- package/src/cli/fleet-sync.ts +150 -19
- package/src/cli/fleet-validate.ts +59 -21
- package/src/cli/fleet-watch.ts +7 -1
- package/src/cli/fleet.ts +87 -5
- package/src/cli/managed-health.ts +25 -1
- package/src/cli/register-fleet.ts +115 -4
- package/src/cli/registry.ts +14 -0
- package/src/cli/tailnet.ts +43 -10
- package/src/cli/ui-data.ts +3 -1
- package/src/cli/worker-usage-report.ts +63 -0
- package/src/clock.ts +42 -0
- package/src/definition.ts +332 -9
- package/src/diagnostics.ts +5 -2
- package/src/exposure.ts +102 -0
- package/src/fleet-config.ts +3 -1
- package/src/git-identity.ts +76 -3
- package/src/ide/ide.ts +5 -0
- package/src/ide/t3.ts +12 -2
- package/src/input-hashes.ts +6 -0
- package/src/machine/machine-class.ts +59 -0
- package/src/machine/platform.ts +90 -11
- package/src/machine/state.ts +37 -15
- package/src/machine/support-matrix.ts +11 -9
- package/src/machine-health.ts +28 -5
- package/src/manager-health.ts +148 -2
- package/src/manager-sync.ts +65 -4
- package/src/monitoring/beszel.ts +8 -8
- package/src/monitoring/enroll.ts +8 -1
- package/src/monitoring/hub-access.ts +10 -4
- package/src/monitoring/systems.ts +138 -0
- package/src/network-definition.ts +13 -4
- package/src/node-converge-linux.ts +136 -27
- package/src/node-converge.ts +13 -3
- package/src/platform/ansible-receipt.ts +17 -2
- package/src/platform/ansible.ts +40 -4
- package/src/platform/manager-host.ts +25 -8
- package/src/platform/resource-verify.ts +18 -7
- package/src/platform/t3-policy.ts +24 -10
- package/src/platform/t3-service.ts +109 -34
- package/src/platform/ubuntu-release.ts +24 -0
- package/src/platform/ubuntu-verify.ts +31 -3
- package/src/platform/ubuntu-worker.ts +58 -0
- package/src/platform/worker-platform.ts +72 -0
- package/src/protocol.ts +1 -1
- package/src/public-reachability.ts +186 -0
- package/src/remote-bootstrap.ts +50 -12
- package/src/remote-operation.ts +18 -4
- package/src/repair-plan.ts +23 -12
- package/src/report.ts +2 -1
- package/src/schedule-check.ts +21 -2
- package/src/secrets.ts +4 -0
- package/src/service-auth.ts +55 -48
- package/src/ssh-connection.ts +141 -0
- package/src/ssh.ts +63 -24
- package/src/state/store.ts +4 -2
- package/src/state/sync.ts +35 -6
- package/src/state/workers.ts +140 -0
- package/src/t3/pairing.ts +59 -0
- package/src/t3/peer-link.ts +333 -0
- package/src/t3/version.ts +1 -1
- package/src/tailnet-cleanup.ts +185 -0
- package/src/tailnet-control.ts +346 -0
- package/src/tailscale.ts +1 -1
- package/src/toolchain-checks.ts +11 -1
- package/src/types.ts +4 -2
- package/src/worker-account.ts +63 -0
- package/src/worker-activity.ts +303 -0
- package/src/worker-capability-checks.ts +55 -16
- package/src/worker-capacity.ts +31 -0
- package/src/worker-exposure.ts +388 -0
- package/src/worker-features.ts +201 -0
- package/src/worker-host-checks.ts +71 -20
- package/src/worker-manifest.ts +27 -4
- package/src/worker-parking.ts +70 -0
- package/src/worker-policy.ts +17 -1
- package/src/worker-readiness.ts +15 -3
- package/src/worker-reboot.ts +129 -0
- package/src/worker-records.ts +305 -0
- package/src/worker-seed.ts +16 -11
- package/src/worker-sizing.ts +76 -0
- package/src/cli/schedule-logs.ts +0 -36
package/docs/operations.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
> This document was written for the fleet dev-fleet was extracted from. Paths and
|
|
2
2
|
> commands were updated; some statements still describe that fleet and are being
|
|
3
|
-
> revised. The README is authoritative
|
|
3
|
+
> revised. The README is authoritative, and what a machine must offer to be a
|
|
4
|
+
> worker is listed only in [worker requirements](worker-requirements.md).
|
|
4
5
|
|
|
5
6
|
# Developer fleet
|
|
6
7
|
|
|
@@ -22,7 +23,7 @@ Use `devfleet fleet-sync` to reconcile all declared workers and
|
|
|
22
23
|
`devfleet fleet-status --workers` to inspect remaining gaps. See
|
|
23
24
|
[worker-contract.md](worker-contract.md) for bootstrap, ownership and the exact
|
|
24
25
|
scope of readiness. `fleet-status` without flags diagnoses the local roster.
|
|
25
|
-
Workers are Ubuntu 26.04; the manager is macOS and never sleeps on AC power
|
|
26
|
+
Workers are Ubuntu 26.04 or 24.04; the manager is macOS and never sleeps on AC power
|
|
26
27
|
(`ansible/macos/manager.yml`). Machine policy (never sleep, no automatic
|
|
27
28
|
logout, always-on Tailscale/SSH/Docker/Beszel, memory and disk safety, per-node
|
|
28
29
|
credential renewal) is
|
|
@@ -134,6 +135,26 @@ expiring: tag them, or disable key expiry for each in the Tailscale admin
|
|
|
134
135
|
console (tagged nodes do not expire). `tailscale:key-expiry` warns two weeks
|
|
135
136
|
ahead and fails three days ahead.
|
|
136
137
|
|
|
138
|
+
With `tailnet` in `fleet.json` and the secret `tailnet-admin`, nothing needs
|
|
139
|
+
renewing: every worker joins with a key that carries the fleet's tag, and a
|
|
140
|
+
tagged device has key expiry disabled on the hosted Tailscale. For an enrolled
|
|
141
|
+
worker whose key still expires (one that joined before it was tagged),
|
|
142
|
+
`fleet-sync` disables expiry through the control plane
|
|
143
|
+
(`tailscale:key-expiry-policy`); `tailscale:key-expiry` measures the result on
|
|
144
|
+
the next run. Headscale gives a node registered with a pre-authorised key no
|
|
145
|
+
expiry.
|
|
146
|
+
|
|
147
|
+
### Maintenance reboots
|
|
148
|
+
|
|
149
|
+
Kernel and library updates apply only after a reboot (`worker:reboot-pending`).
|
|
150
|
+
`devfleet fleet-sync --maintenance-reboot` reboots each worker that has one
|
|
151
|
+
pending: one at a time, the canary first, only while it runs no agent turn
|
|
152
|
+
(`--force` ends them), through `ansible/ubuntu/reboot.yml`. Each worker is
|
|
153
|
+
converged again once it answers; a worker that does not come back ready stops
|
|
154
|
+
the ones after it. A new worker that was never ready is rebooted once after its
|
|
155
|
+
first converge in any `fleet-sync`, so it reaches ready in one run; a worker
|
|
156
|
+
that was ready before is rebooted only by `--maintenance-reboot`.
|
|
157
|
+
|
|
137
158
|
To re-join a worker whose key expired, a person signs it in again, because
|
|
138
159
|
only a person's identity can authorise a device:
|
|
139
160
|
|
|
@@ -205,7 +226,8 @@ serially; diagnostics run at most four remote probes concurrently.
|
|
|
205
226
|
shell PATH and installs frozen Bun dependencies. Review OS-specific effects
|
|
206
227
|
and existing config conflict guards in the shared bootstrap runbook.
|
|
207
228
|
- Record input hashes only after success and stable checkout inputs. Failed
|
|
208
|
-
setup can leave partial installs
|
|
229
|
+
setup can leave partial installs, which the next sync completes. Going back
|
|
230
|
+
is `fleet-sync --rollback`, as the [README](../README.md#the-version) says. Trusted checkout
|
|
209
231
|
package lifecycle scripts can execute during installation.
|
|
210
232
|
- No other application repos, credentials, provider settings or cloud resources
|
|
211
233
|
are synchronized. `devfleet fleet-sync` remains the broader convergence.
|
|
@@ -258,12 +280,45 @@ A notification carries node and check IDs only. `devfleet fleet-watch --test`
|
|
|
258
280
|
sends one test notification to every target.
|
|
259
281
|
|
|
260
282
|
Every run stores what it saw, and when, as `watch.json` beside the fleet state
|
|
261
|
-
in the bucket. That time is the manager's heartbeat.
|
|
262
|
-
|
|
263
|
-
`fleet-
|
|
264
|
-
`fleet.json` (
|
|
265
|
-
|
|
266
|
-
|
|
283
|
+
in the bucket. That time is the manager's heartbeat. Nobody has to run it:
|
|
284
|
+
`fleet-sync` installs it on the manager as the launchd job
|
|
285
|
+
`dev.devfleet.fleet-watch` (`ansible/macos/manager.yml`), which runs every
|
|
286
|
+
`manager.schedule.watchMinutes` minutes of `fleet.json` (15 by default) from
|
|
287
|
+
the checkout `fleet-sync` last applied, and `manager:watch` fails when it is
|
|
288
|
+
not loaded. It reads the bucket credentials `fleet-state --init` saved.
|
|
289
|
+
|
|
290
|
+
A watcher on the manager notices nothing when the manager itself is off, so
|
|
291
|
+
call the reusable `fleet-heartbeat.yml` workflow from the repository that
|
|
292
|
+
holds your `fleet.json` (its header shows the one `uses:` line): it fails, and
|
|
293
|
+
GitHub notifies you, when the heartbeat is older than `max-age-minutes`
|
|
294
|
+
(60 by default). With the watch job every 15 minutes and the workflow every
|
|
295
|
+
30, a manager that is turned off fails the workflow within about 90 minutes
|
|
296
|
+
(the age limit plus one interval of the workflow's schedule; GitHub may delay
|
|
297
|
+
a scheduled run). Keep `max-age-minutes` well above `watchMinutes`.
|
|
298
|
+
|
|
299
|
+
The CLI proxy is checked off the manager the same way:
|
|
300
|
+
`.github/workflows/cliproxy-health.yml` is a reusable workflow, called with one
|
|
301
|
+
`uses:` line. It needs no stored secret: it reads the Vault address and the
|
|
302
|
+
proxy endpoint from your `fleet.json`, logs in to Vault with GitHub's OIDC
|
|
303
|
+
token (the read-only role `vault-role` names, `github-actions` by default) and
|
|
304
|
+
runs the checks of the dev-fleet release your `package.json` pins. Copy
|
|
305
|
+
[`cliproxy/docs/health-workflow.example.yml`](../cliproxy/docs/health-workflow.example.yml)
|
|
306
|
+
into `.github/workflows/` and name the release tag in its `uses:` line; it
|
|
307
|
+
runs the free readiness check every 15 minutes, a light inference check hourly
|
|
308
|
+
and the full suite nightly, and keeps one tracking issue in your repository.
|
|
309
|
+
|
|
310
|
+
### The logs of the manager's scheduled jobs
|
|
311
|
+
|
|
312
|
+
`fleet-sync --schedule` and the watch job write to
|
|
313
|
+
`~/.local/share/personal/fleet/manager/logs/<label>.{out,err}.log`. newsyslog
|
|
314
|
+
rotates each once it passes `jobLogs.maxKiB` of `ansible/macos/policy.json`
|
|
315
|
+
(1024 KiB) and keeps `jobLogs.keep` compressed archives, from the drop-in
|
|
316
|
+
`/etc/newsyslog.d/dev-fleet-USER.conf` that the manager's one administrator
|
|
317
|
+
grant (`ansible/macos/manager-privilege.yml`) installs. `manager:job-logs`
|
|
318
|
+
warns when the drop-in is missing, or a log has grown past twice that size.
|
|
319
|
+
|
|
320
|
+
`bun run health` remains the separate single read-only **cloud platform**
|
|
321
|
+
report.
|
|
267
322
|
|
|
268
323
|
## Development
|
|
269
324
|
|
package/docs/releasing.md
CHANGED
|
@@ -51,7 +51,33 @@ cannot release because publishing was never set up (no tag and nothing on
|
|
|
51
51
|
npm) fails, and the run summary says why; a merge with nothing new to release
|
|
52
52
|
passes and says so. Re-running a
|
|
53
53
|
release whose version is already on npm skips the publish and finishes the tag
|
|
54
|
-
and GitHub Release
|
|
54
|
+
and GitHub Release, whether all its jobs or only the failed ones are re-run: a
|
|
55
|
+
`HEAD` that carries its tag but has no GitHub Release is released again.
|
|
56
|
+
|
|
57
|
+
Nothing is published until the release has been tried. The workflow packs
|
|
58
|
+
the package once, then, for that commit and that tarball:
|
|
59
|
+
|
|
60
|
+
- installs the tarball in an empty directory, the way a fleet project does,
|
|
61
|
+
and runs `devfleet --version` and `devfleet fleet-state --example` on Node
|
|
62
|
+
22 and 24 ([`bootstrap/package-smoke.ts`](../bootstrap/package-smoke.ts)):
|
|
63
|
+
a broken bin path or a file missing from `files` fails here;
|
|
64
|
+
- runs the [Ubuntu VM test](../.github/workflows/vm.yml) (a real Ubuntu
|
|
65
|
+
machine: baseline, a second run, drift, reboot, and the production SSH
|
|
66
|
+
bootstrap of the commit);
|
|
67
|
+
- runs the [tailnet test](../.github/workflows/tailnet.yml) (a worker joins
|
|
68
|
+
a self-hosted tailnet unattended and leaves no device behind).
|
|
69
|
+
|
|
70
|
+
Only when all three pass is that same tarball published, and the GitHub Release
|
|
71
|
+
gets notes written from the Conventional Commits since the last release
|
|
72
|
+
([`bootstrap/release-notes.ts`](../bootstrap/release-notes.ts)). A commit
|
|
73
|
+
whose change restarts T3 on the workers, and so ends the agent turns running
|
|
74
|
+
there, says so in a `Worker-Restart: <reason>` footer, and the notes list it
|
|
75
|
+
first.
|
|
76
|
+
|
|
77
|
+
There is no `next` channel yet: publishing uses npm Trusted Publishing, which
|
|
78
|
+
can publish but cannot move a dist-tag, and moving one would need an npm
|
|
79
|
+
token stored in CI. The VM test before publishing stands in for the soak a
|
|
80
|
+
`next` channel would give.
|
|
55
81
|
|
|
56
82
|
A hand-pushed tag still works and publishes exactly that version:
|
|
57
83
|
|
|
@@ -62,6 +88,28 @@ git tag vX.Y.Z && git push origin vX.Y.Z
|
|
|
62
88
|
CI never receives an npm token: it publishes through npm Trusted Publishing
|
|
63
89
|
(OIDC).
|
|
64
90
|
|
|
91
|
+
## When a release fails
|
|
92
|
+
|
|
93
|
+
A release that fails publishes nothing, and neither does any merge after it
|
|
94
|
+
until one passes: npm stays at the last version. The
|
|
95
|
+
[Failure issues workflow](../.github/workflows/failures.yml) keeps one open
|
|
96
|
+
issue, `Release is failing on main` (label `ci-failure`), with the run, the
|
|
97
|
+
commit and the jobs and steps that failed; a failed VM test keeps its
|
|
98
|
+
redacted results and the guest's logs as an artifact of the run
|
|
99
|
+
(`gh run download <run>`). The next passing release closes the issue and
|
|
100
|
+
publishes everything since the last tag. The same holds for CI on `main`,
|
|
101
|
+
which the release waits for.
|
|
102
|
+
|
|
103
|
+
A pull request is merged by a workflow, and a merge made with a workflow's
|
|
104
|
+
own token starts no CI on `main`. [`auto-merge.yml`](../.github/workflows/auto-merge.yml)
|
|
105
|
+
starts it when the newest commit on `main` has no run: when a CI run ends,
|
|
106
|
+
and every hour. A run started that way starts nothing when it ends either,
|
|
107
|
+
so the same hourly run starts the release once CI has passed on the newest
|
|
108
|
+
commit of `main` and no release has run for it, and the Failure issues
|
|
109
|
+
workflow reads the newest finished run of every workflow it follows each
|
|
110
|
+
hour as well. A merge is therefore released within about an hour of its CI
|
|
111
|
+
passing.
|
|
112
|
+
|
|
65
113
|
## One-time setup
|
|
66
114
|
|
|
67
115
|
npm can publish new versions through OIDC but cannot create a package that does
|
package/docs/runbooks.md
CHANGED
|
@@ -8,7 +8,7 @@ person (a login, an approval, a choice) is marked **needs a person**.
|
|
|
8
8
|
`features/rules/runbooks.feature` fails when a code in the source has no
|
|
9
9
|
entry here, or a `devfleet` command quoted in the documents does not exist.
|
|
10
10
|
|
|
11
|
-
There are
|
|
11
|
+
There are 301 codes; 46 need a person.
|
|
12
12
|
|
|
13
13
|
## Bootstrap of a new or returning worker
|
|
14
14
|
|
|
@@ -34,7 +34,7 @@ There are 250 codes; 31 need a person.
|
|
|
34
34
|
- **Meaning:** A package transaction could not run or the package database is inconsistent.
|
|
35
35
|
- **Confirm:** `ssh WORKER sudo dpkg --audit` and `sudo apt-get -o Debug::NoLocking=1 check`.
|
|
36
36
|
- **Fix:** Let any running package process finish (it is never killed), free disk under `/var`, then run `devfleet fleet-sync`.
|
|
37
|
-
- **Escalate:**
|
|
37
|
+
- **Escalate:** An interrupted package run is finished by the next `devfleet fleet-sync`: the bootstrap and the baseline each run `dpkg --configure -a` once, bounded. APT_STATE_BROKEN and DPKG_INTERRUPTED mean that repair did not clear `dpkg --audit`: a person repairs the packages it names.
|
|
38
38
|
|
|
39
39
|
- `APT_DISK_FULL`: apt disk full.
|
|
40
40
|
- `APT_DNS_UNAVAILABLE`: apt dns unavailable.
|
|
@@ -104,6 +104,18 @@ There are 250 codes; 31 need a person.
|
|
|
104
104
|
- `WORKER_RECOVERY_SETUP_FAILED`: worker recovery setup failed.
|
|
105
105
|
- `WORKER_RECOVERY_TIMER_INACTIVE`: worker recovery timer inactive.
|
|
106
106
|
|
|
107
|
+
## Worker size and features
|
|
108
|
+
|
|
109
|
+
- **Meaning:** The machine is smaller than the minimum worker (`sizing.minimumWorker` in `ansible/ubuntu/policy.json`), more agent sessions run than its memory policy supports, its declared `features` in `fleet.json` are not ones dev-fleet knows, or something dev-fleet installed for a feature that is now off is still there.
|
|
110
|
+
- **Confirm:** `devfleet fleet-sync --dry-run` shows the `platform:worker-size` check of each worker and, under the plan, what turning a feature off removes; `devfleet fleet-status --workers` shows `worker:features` and `worker:agent-sessions`.
|
|
111
|
+
- **Fix:** Resize a machine below the minimum, then run `devfleet fleet-sync`; nothing was installed on it. For a feature removal, run `devfleet fleet-sync` again. For too many sessions, finish some or move work to a larger worker.
|
|
112
|
+
- **Escalate:** A package another package depends on stays installed until a person removes that one.
|
|
113
|
+
|
|
114
|
+
- `AGENT_SESSION_CAPACITY_EXHAUSTED`: agent session capacity exhausted: more agent sessions run than the memory policy supports (a warning; the size of a session is an estimate).
|
|
115
|
+
- `WORKER_FEATURES_INVALID`: worker features invalid: the session is neither desktop nor headless.
|
|
116
|
+
- `WORKER_FEATURE_REMOVAL_INCOMPLETE`: worker feature removal incomplete: a Docker package or the docker group membership is still there with containers off.
|
|
117
|
+
- `WORKER_SIZE_BELOW_MINIMUM`: worker size below minimum: less memory, disk or CPUs than the minimum worker. **Needs a person.**
|
|
118
|
+
|
|
107
119
|
## Containers on a worker
|
|
108
120
|
|
|
109
121
|
- **Meaning:** `worker:docker-run` could not start the pinned container or build a one-line image as the worker user.
|
|
@@ -119,6 +131,23 @@ There are 250 codes; 31 need a person.
|
|
|
119
131
|
- `DOCKER_SMOKE_IMAGE_MISSING`: docker smoke image missing.
|
|
120
132
|
- `DOCKER_SOCKET_ACCESS_DENIED`: docker socket access denied.
|
|
121
133
|
|
|
134
|
+
## Exposure of a worker
|
|
135
|
+
|
|
136
|
+
- **Meaning:** Something on the worker is reachable from outside the tailnet, or could be: the fleet firewall (`ansible/ubuntu/tasks/firewall.yml`) is not loaded or kept SSH open, a service listens beyond loopback and the tailnet (`worker:exposure`), or the manager reached a fleet port on the worker's public address (`worker:public-reachability`). The exposure policy is `exposure` in `fleet.json` (fleet-wide under `worker`, per worker beside `network`).
|
|
137
|
+
- **Confirm:** On the worker: `sudo nft list table inet personal_fleet`, `sudo ss -H -l -n -t -u -p` and `sudo sshd -T | grep -E 'passwordauthentication|permitrootlogin'`. From the manager: `devfleet fleet-status --workers`.
|
|
138
|
+
- **Fix:** Run `devfleet fleet-sync`. It closes SSH beyond the tailnet only when the manager's session arrives over the tailnet: point the worker's SSH alias at its tailnet name first. Bind a service to `127.0.0.1` or the tailnet address, or declare its port in `publicPorts`.
|
|
139
|
+
- **Escalate:** A firewall the tailnet path could not verify (the previous rules are back) needs `tailscale status` on the worker; a public SSH port is opened on purpose only with `publicSsh`, by a person.
|
|
140
|
+
|
|
141
|
+
- `FIREWALL_RULES_REJECTED`: firewall rules rejected by `nft --check`; the previous rules stay loaded.
|
|
142
|
+
- `FIREWALL_TAILNET_UNVERIFIED`: the tailnet path did not answer a new probe after the rules loaded; the previous rules were restored.
|
|
143
|
+
- `FIREWALL_UNMANAGED_CONFLICT`: `/etc/personal-fleet/firewall.nft` exists without the fleet's marker. **Needs a person.**
|
|
144
|
+
- `SSH_HARDENING_OVERRIDDEN`: `sshd -T` still allows passwords or root login: an earlier fragment or a Match block wins. **Needs a person.**
|
|
145
|
+
- `WORKER_EXPOSURE_UNOBSERVED`: the addresses, sockets or firewall could not be read with `sudo -n`.
|
|
146
|
+
- `WORKER_FIREWALL_INACTIVE`: the fleet firewall is not loaded.
|
|
147
|
+
- `WORKER_LISTENER_UNRESTRICTED`: a socket listens beyond loopback and the tailnet outside the allow-list; the summary names the port and the owning unit. A fleet service fails; another warns or fails by `otherListeners`.
|
|
148
|
+
- `WORKER_PUBLIC_PORT_UNRESTRICTED`: the manager connected to SSH, T3 or the monitoring port on the worker's public address.
|
|
149
|
+
- `WORKER_PUBLIC_SSH_UNDECLARED`: SSH is still open beyond the tailnet without `publicSsh`, because no session over the tailnet proved the manager's path. **Needs a person.**
|
|
150
|
+
|
|
122
151
|
## Headless browser on a worker
|
|
123
152
|
|
|
124
153
|
- **Meaning:** `worker:browser` could not launch the pinned headless Chromium of the web tool profile.
|
|
@@ -133,10 +162,10 @@ There are 250 codes; 31 need a person.
|
|
|
133
162
|
|
|
134
163
|
## Git identity and pushing
|
|
135
164
|
|
|
136
|
-
- **Meaning:** Commits on the node would carry the wrong author, or git cannot authenticate to GitHub on its own.
|
|
165
|
+
- **Meaning:** Commits on the node would carry the wrong author, would not be signed although `git.signing` is declared, or git cannot authenticate to GitHub on its own.
|
|
137
166
|
- **Confirm:** On the node: `git config --global --get user.name`, and `printf "protocol=https\nhost=github.com\n\n" | GIT_TERMINAL_PROMPT=0 git credential fill >/dev/null`.
|
|
138
167
|
- **Fix:** Declare the `git` block in `fleet.json` and run `devfleet fleet-sync` (`ansible/github/git.yml`). Remove lines in `~/.gitconfig` that override it.
|
|
139
|
-
- **Escalate:** GIT_REMOTE_REFUSED means the fleet account lost access to the declared repository: a person grants it.
|
|
168
|
+
- **Escalate:** GIT_REMOTE_REFUSED means the fleet account lost access to the declared repository: a person grants it. GitHub shows signed commits as verified only once a person adds each node's `~/.ssh/dev-fleet-git-signing.pub` to the account as a signing key.
|
|
140
169
|
|
|
141
170
|
- `GIT_CONFIGURATION_INCOMPLETE`: git configuration incomplete.
|
|
142
171
|
- `GIT_CREDENTIAL_HELPER_MISSING`: git credential helper missing.
|
|
@@ -146,6 +175,9 @@ There are 250 codes; 31 need a person.
|
|
|
146
175
|
- `GIT_IDENTITY_UNDECLARED`: git identity undeclared.
|
|
147
176
|
- `GIT_REMOTE_REFUSED`: git remote refused. **Needs a person.**
|
|
148
177
|
- `GIT_REMOTE_UNREACHABLE`: git remote unreachable.
|
|
178
|
+
- `GIT_SIGNING_FAILED`: git signing failed: the node's own key is missing or cannot sign without a prompt.
|
|
179
|
+
- `GIT_SIGNING_MISSING`: git signing missing.
|
|
180
|
+
- `GIT_SIGNING_OVERRIDDEN`: git signing overridden.
|
|
149
181
|
|
|
150
182
|
## GitHub sign-in
|
|
151
183
|
|
|
@@ -180,12 +212,34 @@ There are 250 codes; 31 need a person.
|
|
|
180
212
|
- `TAILSCALE_SOURCE_OWNER_CONFLICT`: tailscale source owner conflict. **Needs a person.**
|
|
181
213
|
- `TAILSCALE_UPGRADE_PENDING`: tailscale upgrade pending. **Needs a person.**
|
|
182
214
|
|
|
215
|
+
## A new worker with no hands on it
|
|
216
|
+
|
|
217
|
+
- **Meaning:** A machine created without a person (from a pipeline or infrastructure-as-code) could not be enrolled, joined to the tailnet, given its account or brought to ready in one run: its first boot is still running or failed, the tailnet control plane refused a key, the image lets only root in and the account could not be created, or its first-converge or maintenance reboot did not happen.
|
|
218
|
+
- **Confirm:** The `bootstrap:first-boot`, `tailscale:enrolment-key`, `worker:account` and `worker:reboot` checks of the report `devfleet fleet-sync --json` prints, or the `tailscale:device` check of `fleet-node-replace` and `fleet-node-retire` and the `tailscale:leftover` check of `devfleet fleet-status`, which ask the same control plane; on the worker, `cloud-init status --long`.
|
|
219
|
+
- **Fix:** FIRST_BOOT_PENDING and WORKER_REBOOT_BUSY clear on their own: run `devfleet fleet-sync` again later. A TAILNET code names what to fix in `tailnet` of `fleet.json` or in the secret `tailnet-admin`; then run `devfleet fleet-sync`.
|
|
220
|
+
- **Escalate:** A first boot that failed means the image or its user data is wrong: recreate the machine. A root login that cannot create the declared account needs a person on the machine (`useradd --create-home --shell /bin/bash --groups sudo USER`).
|
|
221
|
+
|
|
222
|
+
- `FIRST_BOOT_FAILED`: first boot failed: cloud-init ended in an error. **Needs a person.**
|
|
223
|
+
- `FIRST_BOOT_PENDING`: first boot pending: cloud-init still runs; retryable.
|
|
224
|
+
- `TAILNET_ADMIN_REQUIRED`: tailnet admin required: fleet.json declares tailnet without the secret. **Needs a person.**
|
|
225
|
+
- `TAILNET_CONTROL_DENIED`: tailnet control denied the tailnet-admin credential. **Needs a person.**
|
|
226
|
+
- `TAILNET_CONTROL_REJECTED`: tailnet control rejected the request (tag or user). **Needs a person.**
|
|
227
|
+
- `TAILNET_CONTROL_RESPONSE_INVALID`: tailnet control response invalid.
|
|
228
|
+
- `TAILNET_CONTROL_UNAVAILABLE`: tailnet control unavailable.
|
|
229
|
+
- `TAILNET_KEY_DELIVERY_FAILED`: tailnet key delivery failed.
|
|
230
|
+
- `TAILNET_KEY_INVALID`: tailnet key invalid.
|
|
231
|
+
- `WORKER_ACCOUNT_CREATION_FAILED`: worker account creation failed. **Needs a person.**
|
|
232
|
+
- `WORKER_ACCOUNT_ROOT_REQUIRED`: worker account root required. **Needs a person.**
|
|
233
|
+
- `WORKER_ACCOUNT_UNDECLARED`: worker account undeclared in workers[].network.user. **Needs a person.**
|
|
234
|
+
- `WORKER_REBOOT_BUSY`: worker reboot busy: an agent turn runs.
|
|
235
|
+
- `WORKER_REBOOT_FAILED`: worker reboot failed.
|
|
236
|
+
|
|
183
237
|
## T3 Code
|
|
184
238
|
|
|
185
239
|
- **Meaning:** The native T3 server or desktop app, its policy, its private route or its providers did not converge or verify.
|
|
186
240
|
- **Confirm:** `devfleet t3-status` and the `t3:*` checks of `devfleet fleet-status`.
|
|
187
241
|
- **Fix:** Run `devfleet fleet-sync`, then `devfleet t3-apply` for providers. A MIGRATION or MAINTENANCE code names a one-time step in its remedy.
|
|
188
|
-
- **Escalate:**
|
|
242
|
+
- **Escalate:** A worker's first pairing, and relinking a peer that was not replaced, need a person in T3 Settings (Connections). A replaced worker is paired by `devfleet fleet-node-replace` or the next `devfleet fleet-sync` (`t3:link`).
|
|
189
243
|
|
|
190
244
|
- `MANAGER_T3_LINK_UNVERIFIED`: manager t3 link unverified.
|
|
191
245
|
- `T3_BASELINE_PREREQUISITES_REQUIRED`: t3 baseline prerequisites required.
|
|
@@ -224,6 +278,8 @@ There are 250 codes; 31 need a person.
|
|
|
224
278
|
- `T3_PROVIDER_NAMES_PENDING`: t3 provider names pending.
|
|
225
279
|
- `T3_PROVIDER_NAMES_UNVERIFIED`: t3 provider names unverified.
|
|
226
280
|
- `T3_RESTART_PENDING`: t3 restart pending.
|
|
281
|
+
- `T3_SERVER_INSTALL_FAILED`: t3 server install failed: the pinned server of a headless worker is missing from the toolchain, or `t3-headless.yml` did not apply.
|
|
282
|
+
- `T3_SERVER_STOPPED`: t3 server stopped: the lingering `t3code-headless.service` of a headless worker is not running.
|
|
227
283
|
- `T3_SETTINGS_BUSY`: t3 settings busy.
|
|
228
284
|
|
|
229
285
|
## Monitoring (Beszel)
|
|
@@ -295,10 +351,12 @@ There are 250 codes; 31 need a person.
|
|
|
295
351
|
- **Meaning:** An agent CLI, its profile or a provider behind the CLI proxy did not answer as expected.
|
|
296
352
|
- **Confirm:** The `agent:*` and `provider:*` checks of `devfleet fleet-status`, and `devfleet cliproxy` on the manager.
|
|
297
353
|
- **Fix:** Run `devfleet fleet-sync`; a rate limit or upstream outage clears on its own.
|
|
298
|
-
- **Escalate:** PROVIDER_AUTH_REJECTED needs a person to sign the account in again on the proxy.
|
|
354
|
+
- **Escalate:** PROVIDER_AUTH_REJECTED needs a person to sign the account in again on the proxy. AGENT_PROFILE_RENDER_MISMATCH is a defect of the release (a template of `ansible/agents/agents.yml` no longer renders what `src/agents/render.ts` specifies): report it to dev-fleet. No receipt vouches for what that play wrote, so later applies refuse the file until a person reviews it.
|
|
299
355
|
|
|
300
356
|
- `AGENT_MEMORY_LIMIT_MISSING`: agent memory limit missing.
|
|
301
357
|
- `AGENT_MODEL_UNSERVED`: agent model unserved.
|
|
358
|
+
- `AGENT_PROFILE_RENDER_MISMATCH`: agent profile render mismatch.
|
|
359
|
+
- `AGENT_PROFILE_REQUEST_INVALID`: agent profile request invalid.
|
|
302
360
|
- `AGENT_PROFILE_STALE`: agent profile stale.
|
|
303
361
|
- `AGENT_SERVICE_ENVIRONMENT_INCOMPLETE`: agent service environment incomplete.
|
|
304
362
|
- `AGENT_SERVICE_ENVIRONMENT_UNVERIFIED`: agent service environment unverified.
|
|
@@ -338,7 +396,7 @@ There are 250 codes; 31 need a person.
|
|
|
338
396
|
## Vault and credentials
|
|
339
397
|
|
|
340
398
|
- **Meaning:** A machine cannot read the secrets it needs with its own Vault session.
|
|
341
|
-
- **Confirm:** `vault token lookup` on the machine and the `worker:vault-renewal` check.
|
|
399
|
+
- **Confirm:** `vault token lookup` on the machine and the `worker:vault-renewal` or `manager:vault-renewal` check.
|
|
342
400
|
- **Fix:** Log the machine in once (`devfleet fleet-sync` asks on the manager); renewal is automatic afterwards.
|
|
343
401
|
- **Escalate:** A refused policy needs the Vault administrator.
|
|
344
402
|
|
|
@@ -346,6 +404,8 @@ There are 250 codes; 31 need a person.
|
|
|
346
404
|
- `VAULT_LOGIN_REQUIRED`: vault login required. **Needs a person.**
|
|
347
405
|
- `VAULT_POLICY_PERMISSION_REQUIRED`: vault policy permission required. **Needs a person.**
|
|
348
406
|
- `VAULT_RENEWAL_FAILING`: vault renewal failing.
|
|
407
|
+
- `VAULT_RENEWAL_MISSING`: vault renewal missing: the manager's renewal job is not loaded. Run `devfleet fleet-sync` on the manager.
|
|
408
|
+
- `VAULT_RENEWAL_NOT_MANAGER`: vault renewal not manager.
|
|
349
409
|
- `VAULT_WORKER_POLICY_FAILED`: vault worker policy failed.
|
|
350
410
|
|
|
351
411
|
## The fleet definition and a worker's lifecycle
|
|
@@ -361,6 +421,23 @@ There are 250 codes; 31 need a person.
|
|
|
361
421
|
- `DEFINITION_UNREADABLE`: definition unreadable.
|
|
362
422
|
- `SSH_HOST_KEY_NOT_CHANGED`: ssh host key not changed.
|
|
363
423
|
|
|
424
|
+
## Parked, idle, replaced and retired workers
|
|
425
|
+
|
|
426
|
+
- **Meaning:** a worker could not be parked or resumed (agent turns under way, a declared `parking.stop` or `parking.start` command failed, the sync after a resume did not end ready), a worker whose record disappeared could not be retired, or the manager's T3 link to a replaced or retired worker could not be removed, or the manager's T3 could not pair with a replaced worker's new environment.
|
|
427
|
+
- **Confirm:** `devfleet fleet-status --workers` lists each worker as active or parked, with its uptime, agent turns and last agent activity; the report of `devfleet fleet-node-park`, `devfleet fleet-node-resume` or `devfleet fleet-sync` names the step that failed.
|
|
428
|
+
- **Fix:** For WORKER_BUSY wait for the turns to end, or repeat `devfleet fleet-node-park` with `--force`. For a failed hook, run the command `fleet.json` names under `parking` by hand with the worker ID, then repeat. For WORKER_RESUME_FAILED follow the sync report and run `devfleet fleet-sync --node ID`. For T3_LINK_UNLINK_FAILED remove the link in the manager's T3 (Settings, Connections). For T3_LINK_PAIRING_FAILED follow the remedy, which names why the link was refused (Linked environments off in the T3 policy, the manager's T3 not running, a worker T3 that did not mint a code), then run `devfleet fleet-sync --node ID`: it pairs a replaced worker until it is paired.
|
|
429
|
+
- **Escalate:** A record retirement that keeps failing at the same step: follow that step's own runbook.
|
|
430
|
+
|
|
431
|
+
- `T3_LINK_PAIRING_FAILED`: t3 link pairing failed: the manager's T3 could not pair with a replaced worker's new T3 environment. The pairing code never appears in a report.
|
|
432
|
+
- `T3_LINK_UNLINK_FAILED`: t3 link unlink failed.
|
|
433
|
+
- `WORKER_ACTIVITY_UNREADABLE`: worker activity unreadable. **Needs a person.**
|
|
434
|
+
- `WORKER_AUTO_PARK_REFUSED`: worker auto park refused.
|
|
435
|
+
- `WORKER_BUSY`: worker busy. **Needs a person.**
|
|
436
|
+
- `WORKER_RECORD_RETIREMENT_FAILED`: worker record retirement failed.
|
|
437
|
+
- `WORKER_RESUME_FAILED`: worker resume failed.
|
|
438
|
+
- `WORKER_START_HOOK_FAILED`: worker start hook failed.
|
|
439
|
+
- `WORKER_STOP_HOOK_FAILED`: worker stop hook failed.
|
|
440
|
+
|
|
364
441
|
## Fleet operations and the fleet state
|
|
365
442
|
|
|
366
443
|
- **Meaning:** fleet-sync could not plan, lock, reach a machine, promote a revision or keep the fleet state.
|
|
@@ -380,6 +457,7 @@ There are 250 codes; 31 need a person.
|
|
|
380
457
|
- `FLEET_REMOTE_LOCKED`: fleet remote locked.
|
|
381
458
|
- `FLEET_REMOTE_OPERATION_FAILED`: fleet remote operation failed.
|
|
382
459
|
- `FLEET_REVISION_REQUIRED`: fleet revision required.
|
|
460
|
+
- `FLEET_ROLLBACK_UNAVAILABLE`: fleet rollback unavailable.
|
|
383
461
|
- `FLEET_SCHEDULE_PREVIEW`: fleet schedule preview.
|
|
384
462
|
- `FLEET_SOURCE_UNAVAILABLE`: fleet source unavailable.
|
|
385
463
|
- `FLEET_STATE_DIVERGED`: fleet state diverged. **Needs a person.**
|
|
@@ -394,6 +472,19 @@ There are 250 codes; 31 need a person.
|
|
|
394
472
|
- `REMOTE_CONFIGURATION_INCOMPLETE`: remote configuration incomplete.
|
|
395
473
|
- `REMOTE_PHASE_FAILED`: remote phase failed.
|
|
396
474
|
|
|
475
|
+
## The manager's own policy and scheduled jobs
|
|
476
|
+
|
|
477
|
+
- **Meaning:** `ansible/macos/manager.yml` did not pass its check-mode run or its apply, or something it declares (network time, the fleet-watch job, the rotation of the scheduled jobs' logs) is not in place.
|
|
478
|
+
- **Confirm:** The `manager:playbook`, `manager:clock`, `manager:watch` and `manager:job-logs` checks of `devfleet fleet-sync --dry-run`; on the manager, the check-mode run the remedy quotes.
|
|
479
|
+
- **Fix:** Run `devfleet fleet-sync`, which applies the playbook in check mode first, then for real. What needs root (power settings, reading network time, newsyslog) comes from the one grant: `ansible-playbook -e user=$(id -un) -K ansible/macos/manager-privilege.yml`, run again after an upgrade that changed it.
|
|
480
|
+
- **Escalate:** The grant needs an administrator's password, once.
|
|
481
|
+
|
|
482
|
+
- `MANAGER_CLOCK_UNOBSERVED`: manager clock unobserved: the grant that lets fleet-sync read network time is missing. **Needs a person.**
|
|
483
|
+
- `MANAGER_JOB_LOG_ROTATION_MISSING`: manager job log rotation missing: the newsyslog drop-in the grant installs is missing or outdated. **Needs a person.**
|
|
484
|
+
- `MANAGER_PLAYBOOK_CHECK_FAILED`: manager playbook check failed: nothing was applied.
|
|
485
|
+
- `MANAGER_PLAYBOOK_FAILED`: manager playbook failed.
|
|
486
|
+
- `MANAGER_WATCH_INTERVAL_INVALID`: manager watch interval invalid: `manager.schedule.watchMinutes` is outside 5 to 1440.
|
|
487
|
+
|
|
397
488
|
## Notifications (fleet-watch)
|
|
398
489
|
|
|
399
490
|
- **Meaning:** fleet-watch could not reach a notification target `notify` in `fleet.json` names, or its address is not stored as a secret.
|
package/docs/state.md
CHANGED
|
@@ -4,12 +4,13 @@ A fleet is described in two places you own. Your project in Git holds
|
|
|
4
4
|
`fleet.json` and pins the dev-fleet version. Everything else the manager
|
|
5
5
|
cannot rebuild from Git is the **fleet state**:
|
|
6
6
|
|
|
7
|
-
| Document | What it is
|
|
8
|
-
| ------------ |
|
|
9
|
-
| `roster` | The enrolled connection to each worker (SSH alias, Tailscale device)
|
|
10
|
-
| `enrollment` | The revision `fleet-sync` approved and the one it last applied
|
|
11
|
-
| `definition` | The fleet configuration `fleet-sync` last applied: your `fleet.json`
|
|
12
|
-
| `history` | The last 90 days of `fleet-sync` runs (at most 500), and the schedule
|
|
7
|
+
| Document | What it is |
|
|
8
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `roster` | The enrolled connection to each worker (SSH alias, Tailscale device) |
|
|
10
|
+
| `enrollment` | The revision `fleet-sync` approved and the one it last applied |
|
|
11
|
+
| `definition` | The fleet configuration `fleet-sync` last applied: your `fleet.json` |
|
|
12
|
+
| `history` | The last 90 days of `fleet-sync` runs (at most 500), and the schedule |
|
|
13
|
+
| `workers` | Per worker: its pinned SSH host key, whether it is parked and since when, whether it came from a worker record, and whether the manager is to pair its T3 after a replacement |
|
|
13
14
|
|
|
14
15
|
`devfleet fleet-watch` also keeps `watch.json` in the bucket, beside the
|
|
15
16
|
database: the health of each worker when it last looked, and when that was
|
|
@@ -195,6 +196,12 @@ state that is replicated differently or not at all:
|
|
|
195
196
|
| Monitoring hub (Beszel) database | `~/Library/Application Support/personal-beszel` | `fleet-sync` recreates accounts and systems; metric history, alerts and settings made by hand are lost |
|
|
196
197
|
| T3 ownership record | `~/.t3/userdata/personal-managed.json` | `t3-apply --import ID` adopts instances again |
|
|
197
198
|
|
|
199
|
+
The manager reaches a worker that has no SSH alias against
|
|
200
|
+
`~/.local/share/dev-fleet/ssh/known_hosts` (beside the state directory): a
|
|
201
|
+
cache rendered from the host keys the `workers` document pins and the worker
|
|
202
|
+
records declare, rewritten before every session. Delete it and the next
|
|
203
|
+
command writes it again; nothing is ever learned into it.
|
|
204
|
+
|
|
198
205
|
Everything else under `~/.local/share/personal/fleet` and
|
|
199
206
|
`~/.local/state/personal/fleet` (the copy of the source repository, run
|
|
200
207
|
checkouts, reports, receipts, logs) is a cache that the next `fleet-sync`
|
|
@@ -257,9 +264,14 @@ mise exec litestream -- litestream restore -no-expand-env \
|
|
|
257
264
|
```
|
|
258
265
|
|
|
259
266
|
`devfleet fleet-state --restore --timestamp 2026-11-01T00:00:00Z --yes` does
|
|
260
|
-
the same and then uploads that version as the bucket's current one
|
|
261
|
-
remedy for a `corrupt` replica: the bucket
|
|
262
|
-
cannot be read.
|
|
267
|
+
the same and then uploads that version as the bucket's current one, which
|
|
268
|
+
takes the manager lease. It is the remedy for a `corrupt` replica: the bucket
|
|
269
|
+
answers, but its latest version cannot be read. `--yes` consents to replacing
|
|
270
|
+
the bucket, not to replacing another manager: while one holds the lease in the
|
|
271
|
+
bucket's current version, the restore stops with `FLEET_MANAGER_LEASE_HELD`
|
|
272
|
+
before it changes anything, and `--take-over` overrides it. When the current
|
|
273
|
+
version cannot be read, its lease cannot either, and the lease in this
|
|
274
|
+
machine's copy (the last one it saw) decides instead.
|
|
263
275
|
|
|
264
276
|
Litestream is pinned in `mise.toml` and installed the first time it is used.
|
|
265
277
|
The manager is the only machine that runs it.
|
|
@@ -274,8 +286,8 @@ writes anything and stops with `FLEET_MANAGER_LEASE_HELD`. `fleet-state
|
|
|
274
286
|
--init` still restores the state on such a machine; it only writes nothing.
|
|
275
287
|
|
|
276
288
|
The lease runs for seven days after the last change. After that, any machine
|
|
277
|
-
may take it. Before that, `--take-over` (on `fleet-sync`, `fleet-state --init
|
|
278
|
-
or `fleet-state --
|
|
289
|
+
may take it. Before that, `--take-over` (on `fleet-sync`, `fleet-state --init`,
|
|
290
|
+
`fleet-state --import` or `fleet-state --restore --timestamp`) makes this machine the manager; use it only once
|
|
279
291
|
the other one is gone ([replacing the manager](manager-recovery.md)).
|
|
280
292
|
|
|
281
293
|
A machine is named in the lease by a digest of its machine ID
|
package/docs/support-matrix.md
CHANGED
|
@@ -13,11 +13,12 @@ rest. The default is one macOS manager and Ubuntu workers treated as cattle.
|
|
|
13
13
|
| -------------- | --------- | ------------------- | --------------- | ----------- | ----------------------------------------------------------------------------------------- |
|
|
14
14
|
| manager | macOS | 14 or newer | arm64, x86_64 | supported | ansible/macos/policy.json |
|
|
15
15
|
| worker | Ubuntu | 26.04 (resolute) | amd64, arm64 | supported | ansible/ubuntu/policy.json; a machine of its own, physical or virtual |
|
|
16
|
+
| worker | Ubuntu | 24.04 (noble) | amd64, arm64 | supported | ansible/ubuntu/policy.json; a machine of its own, physical or virtual |
|
|
16
17
|
| manager | any other | any | any | refused | The fleet configuration accepts only a macOS manager. |
|
|
17
18
|
| worker | Ubuntu | any other release | any | refused | UBUNTU_BASELINE_MISMATCH |
|
|
18
19
|
| worker | Ubuntu | an accepted release | any other | refused | UBUNTU_BASELINE_MISMATCH |
|
|
19
|
-
| worker | Debian | any | any | refused | UBUNTU_BASELINE_MISMATCH
|
|
20
|
-
| worker | macOS | any | any | refused | The fleet configuration accepts only Linux workers.
|
|
20
|
+
| worker | Debian | any | any | refused | UBUNTU_BASELINE_MISMATCH; not yet supported (see below). |
|
|
21
|
+
| worker | macOS | any | any | refused | The fleet configuration accepts only Linux workers; not yet supported (see below). |
|
|
21
22
|
| worker | Windows | any | any | refused | The fleet configuration accepts only Linux workers. |
|
|
22
23
|
| worker | WSL2 | any | any | refused | WORKER_ENVIRONMENT_UNSUPPORTED |
|
|
23
24
|
| worker | container | any | any | refused | WORKER_ENVIRONMENT_UNSUPPORTED |
|
|
@@ -32,5 +33,40 @@ refuses a worker that is not Linux, the platform probe refuses another
|
|
|
32
33
|
release, CPU or environment (`ubuntu:release`, `platform:environment`), and
|
|
33
34
|
the bootstrap and the Ubuntu baseline refuse them again on the machine.
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
## Adding a platform or a release
|
|
37
|
+
|
|
38
|
+
What differs from one worker platform to another is named by the
|
|
39
|
+
`WorkerPlatform` interface (`src/platform/worker-platform.ts`): its policy and
|
|
40
|
+
playbooks, its package manager and pins, its Tailscale key, its service
|
|
41
|
+
manager, its session model and its host checks. Ubuntu is the one
|
|
42
|
+
implementation (`src/platform/ubuntu-worker.ts`).
|
|
43
|
+
|
|
44
|
+
Another Ubuntu release is one entry in `releases` of
|
|
45
|
+
`ansible/ubuntu/policy.json`: its version, codename and architectures, its
|
|
46
|
+
package and browser library pins read from the snapshot's Packages indexes
|
|
47
|
+
for that release (`packages`, `browserPackages`), and `tailscaleKeySha256`
|
|
48
|
+
only when Tailscale signs its suite with another key than the common one.
|
|
49
|
+
`ansible/ubuntu/tasks/release.yml` selects the entry from the facts, so the
|
|
50
|
+
playbooks need no change of their own. The release is supported once the
|
|
51
|
+
fresh-box features pass on it: CI runs them for every accepted release
|
|
52
|
+
(`DEVFLEET_TEST_FRESH_BOX_RELEASE`), with its image pinned in
|
|
53
|
+
`features/fresh-box/Dockerfile`.
|
|
54
|
+
|
|
55
|
+
## Not yet supported
|
|
56
|
+
|
|
57
|
+
These are refused today. What each would need, beyond a row here:
|
|
58
|
+
|
|
59
|
+
- **Debian workers.** A `WorkerPlatform` of its own with a Debian policy:
|
|
60
|
+
pins read from snapshot.debian.org for each release, Tailscale's key digest
|
|
61
|
+
for the Debian suite, the platform probe accepting `ID=debian`, and the
|
|
62
|
+
bootstrap and `tasks/release.yml` accepting it in place of
|
|
63
|
+
`UBUNTU_BASELINE_MISMATCH`. The playbooks assume Ubuntu's archive keyring,
|
|
64
|
+
snapshot service and unattended-upgrades origins, and the GNOME desktop
|
|
65
|
+
T3 runs in; each needs a Debian equivalent. Then a fresh-box image and job.
|
|
66
|
+
- **macOS workers.** Everything a worker platform names differs: launchd in
|
|
67
|
+
place of systemd as the service manager, a logged-in user session in place
|
|
68
|
+
of a lingering systemd user manager, Homebrew or pinned downloads in place
|
|
69
|
+
of APT, power and sleep policy through `pmset`, and host checks of their
|
|
70
|
+
own. The fleet configuration would accept `macOS` for a worker, and the
|
|
71
|
+
convergence would run playbooks under `ansible/macos` for it. No container
|
|
72
|
+
can stand in for a fresh macOS machine, so the test would be a macOS runner.
|
package/docs/topology.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
> This document was written for the fleet dev-fleet was extracted from. Paths and
|
|
2
2
|
> commands were updated; some statements still describe that fleet and are being
|
|
3
|
-
> revised. The README is authoritative
|
|
3
|
+
> revised. The README is authoritative, and what a machine must offer to be a
|
|
4
|
+
> worker is listed only in [worker requirements](worker-requirements.md).
|
|
4
5
|
|
|
5
6
|
# Fleet topology: one manager, many workers
|
|
6
7
|
|
|
@@ -8,10 +9,10 @@ The fleet has exactly two roles, declared in
|
|
|
8
9
|
your project's `fleet.json` (version 3, checked by [the schema](../schema/fleet.schema.json)) and validated by
|
|
9
10
|
[`src/definition.ts`](../src/definition.ts):
|
|
10
11
|
|
|
11
|
-
| Role | How many | Machine
|
|
12
|
-
| ----------- | ----------- |
|
|
13
|
-
| **manager** | exactly one | the human's macOS machine (`manager`)
|
|
14
|
-
| **workers** | any | Linux boxes (`workers`, Ubuntu 26.04) | no | the T3 desktop app (UI plus server), agent tools, the Beszel agent; clients only |
|
|
12
|
+
| Role | How many | Machine | Holds state | Runs |
|
|
13
|
+
| ----------- | ----------- | ---------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------- |
|
|
14
|
+
| **manager** | exactly one | the human's macOS machine (`manager`) | yes | `fleet-sync`, the T3 desktop that links to every worker, and **every singleton service** |
|
|
15
|
+
| **workers** | any | Linux boxes (`workers`, Ubuntu 26.04 or 24.04) | no | the T3 desktop app (UI plus server), agent tools, the Beszel agent; clients only |
|
|
15
16
|
|
|
16
17
|
## Manager
|
|
17
18
|
|
|
@@ -51,7 +52,9 @@ your project's `fleet.json` (version 3, checked by [the schema](../schema/fleet.
|
|
|
51
52
|
|
|
52
53
|
- Stateless cattle: turn one off, re-image it or add another at will. A worker
|
|
53
54
|
is declared in `workers`, enrolled once (SSH trust, sudo, its own Vault
|
|
54
|
-
token), and then fully rebuilt by `fleet-sync` from Git and Vault.
|
|
55
|
+
token), and then fully rebuilt by `fleet-sync` from Git and Vault. What a
|
|
56
|
+
machine must offer before that is listed in
|
|
57
|
+
[worker requirements](worker-requirements.md).
|
|
55
58
|
- Workers never host a singleton service. The proxy reports any local install
|
|
56
59
|
on a worker as a duplicate owner, and the hub refuses to install anywhere but
|
|
57
60
|
the manager.
|
|
@@ -73,5 +76,63 @@ your project's `fleet.json` (version 3, checked by [the schema](../schema/fleet.
|
|
|
73
76
|
|
|
74
77
|
- **Add or replace a worker:** add it to `workers`, enroll it with
|
|
75
78
|
`fleet-sync`, done. Remove it by deleting the entry.
|
|
79
|
+
- **Workers an infrastructure tool creates:** `workerRecords` in `fleet.json`
|
|
80
|
+
names a file (relative to `fleet.json`) or a command that returns worker
|
|
81
|
+
records, the provider-neutral description of a machine in
|
|
82
|
+
[`schema/worker-record.schema.json`](../schema/worker-record.schema.json):
|
|
83
|
+
`id`, `tailnetName` or `address`, `user`, `port`, `hostKey` and optional
|
|
84
|
+
`labels` and `billing`. Their workers join the inline list; dev-fleet only
|
|
85
|
+
reads them and never calls a provider or creates a machine. A record that
|
|
86
|
+
appears is a declared worker, enrolled by the next `fleet-sync`; one that
|
|
87
|
+
disappears (a file that is gone holds none) is retired by the next
|
|
88
|
+
`fleet-sync` exactly as `fleet-node-retire` retires one. A worker listed by
|
|
89
|
+
hand is never retired by itself. An invalid record is refused naming its
|
|
90
|
+
field, such as `$.workerRecords[0].workers[1].hostKey`.
|
|
91
|
+
[`examples/workers-from-infrastructure`](../examples/workers-from-infrastructure/main.tf)
|
|
92
|
+
shows the handoff with a local virtual machine, the seed going in and the
|
|
93
|
+
record coming out. The canary stays the one `canary` names, so the order
|
|
94
|
+
of generated records means nothing.
|
|
95
|
+
- **Replace or retire a worker:** `fleet-node-replace` and `fleet-node-retire`
|
|
96
|
+
take everything of the old machine with it: its pinned host key, its
|
|
97
|
+
monitoring system (pointed at the new machine, or deleted), the manager's
|
|
98
|
+
T3 link to its environment, its Vault token and its tailnet device. The
|
|
99
|
+
manager's T3 pairs with the replacement's environment by itself
|
|
100
|
+
(`t3:link`): at once when its T3 runs, otherwise in the next `fleet-sync`
|
|
101
|
+
once it does, and the roster records the new environment. The pairing code
|
|
102
|
+
is minted on the worker over SSH and handed to the manager's own T3 server
|
|
103
|
+
in a request body, never on a command line, in a report or in a log. The device is deleted through the
|
|
104
|
+
control plane fleet.json declares under `tailnet`, asked with the secret
|
|
105
|
+
`tailnet-admin` (workers are not registered as ephemeral devices); without
|
|
106
|
+
either, `tailscale:device` is skipped and names the device to remove in the
|
|
107
|
+
admin console. `fleet-status --workers` warns about a monitoring system that
|
|
108
|
+
matches no declared worker (`beszel:leftover`), and `fleet-status` about a
|
|
109
|
+
device carrying the worker tag that is no declared worker, parked or not,
|
|
110
|
+
and no device the roster binds (`tailscale:leftover`).
|
|
111
|
+
|
|
112
|
+
## Parked and idle workers
|
|
113
|
+
|
|
114
|
+
A rented worker costs money while it runs. `devfleet fleet-node-park --id ID`
|
|
115
|
+
records in the fleet state that it is stopped or destroyed on purpose: from
|
|
116
|
+
then on `fleet-sync` and `fleet-status` list it as parked with the time since
|
|
117
|
+
(`worker:parked`, not applicable), never contact it, never make it the
|
|
118
|
+
canary, and it never changes an exit code. Parking refuses while agent turns
|
|
119
|
+
run on it (`WORKER_BUSY`) unless `--force`. `devfleet fleet-node-resume --id
|
|
120
|
+
ID` marks it active and runs `fleet-sync --node ID`, which converges and
|
|
121
|
+
verifies it. `parking.stop` and `parking.start` in `fleet.json` name commands
|
|
122
|
+
(an infrastructure tool, never a provider dev-fleet calls) run with the worker
|
|
123
|
+
ID as their last argument. A parked worker found online on the tailnet is a
|
|
124
|
+
warning (`worker:parked-online`); one that comes back as a new machine goes
|
|
125
|
+
through `fleet-node-replace`.
|
|
126
|
+
|
|
127
|
+
`fleet-status` reports, for every worker it reaches, its uptime, the agent
|
|
128
|
+
turns under way and the time of the last agent activity, read from what T3
|
|
129
|
+
records (status and times, never a session's content), in its text and as
|
|
130
|
+
`workers` in its JSON. With `parking.idleAfterHours`, a worker with no agent
|
|
131
|
+
activity for longer is `worker:idle` (a warning whose remedy is to park it);
|
|
132
|
+
one with a turn under way never is. `billing.hourlyRate` on a worker (or its
|
|
133
|
+
record) is only displayed and summed: `usage` says how many workers run, how
|
|
134
|
+
many are idle and what the idle ones cost per day. `parking.autoPark: true`
|
|
135
|
+
lets `fleet-watch` park idle workers by itself; it is off unless set.
|
|
136
|
+
|
|
76
137
|
- **Replace the manager:** follow
|
|
77
138
|
[replacing the manager](manager-recovery.md).
|