@crvouga/dev-fleet 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -7
- 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 +83 -0
- package/ansible/macos/manager.yml +164 -9
- 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/macos/worker-host-key.yml +33 -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 +65 -0
- 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 +21 -11
- package/docs/consumer-setup.md +74 -58
- package/docs/convergence.md +67 -0
- package/docs/declared.md +61 -34
- package/docs/machine-setup.md +73 -7
- package/docs/manager-setup.md +34 -0
- package/docs/operations.md +64 -9
- package/docs/releasing.md +49 -1
- package/docs/runbooks.md +112 -7
- package/docs/state.md +23 -11
- package/docs/support-matrix.md +40 -4
- package/docs/topology.md +82 -12
- package/docs/ubuntu-sync.md +98 -42
- 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 +1991 -162
- package/schema/check-ids.schema.json +34 -0
- package/schema/fleet.schema.json +349 -10
- 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 +62 -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 +108 -0
- package/src/cli/fleet-node-lifecycle.ts +704 -0
- package/src/cli/fleet-node-park.ts +238 -0
- package/src/cli/fleet-node-seed.ts +167 -0
- 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 +6 -0
- package/src/cli/fleet-sync-manager.ts +123 -8
- 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 +172 -15
- package/src/cli/fleet-sync-schedule.ts +14 -4
- package/src/cli/fleet-sync-state.ts +31 -10
- package/src/cli/fleet-sync-vault.ts +152 -26
- package/src/cli/fleet-sync-worker.ts +214 -0
- package/src/cli/fleet-sync.ts +172 -21
- package/src/cli/fleet-validate.ts +277 -0
- 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 +251 -4
- package/src/cli/registry.ts +47 -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 +762 -232
- 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 +70 -16
- 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 +89 -8
- 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 +7 -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-capacity.ts +31 -0
- package/src/worker-credentials.ts +201 -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 +224 -0
- package/src/worker-parking.ts +70 -0
- package/src/worker-policy.ts +38 -36
- 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 +143 -0
- package/src/worker-sizing.ts +76 -0
- package/src/cli/schedule-logs.ts +0 -36
package/docs/machine-setup.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
|
## Machine setup: macOS, Windows and Linux
|
|
6
7
|
|
|
@@ -184,7 +185,13 @@ railway login
|
|
|
184
185
|
Only sign into providers the task actually needs. On fleet machines the Git
|
|
185
186
|
identity and the GitHub credential helper come from the `git` block of
|
|
186
187
|
`fleet.json`: `fleet-sync` writes them in `~/.config/git/config` and checks
|
|
187
|
-
them as `worker:git-identity` and `worker:git-push-auth`.
|
|
188
|
+
them as `worker:git-identity` and `worker:git-push-auth`. With
|
|
189
|
+
`"signing": "ssh"` in that block every node also makes an SSH key of its own
|
|
190
|
+
(`~/.ssh/dev-fleet-git-signing`, never copied off the node) and signs every
|
|
191
|
+
commit and tag with it, checked as `worker:git-signing`; GitHub shows those
|
|
192
|
+
commits as verified once you add each node's `.pub` to the account as a
|
|
193
|
+
signing key (`gh ssh-key add ~/.ssh/dev-fleet-git-signing.pub --type signing`
|
|
194
|
+
on the node). Elsewhere set `git
|
|
188
195
|
config --global user.name` and `user.email` after confirming them; do not guess
|
|
189
196
|
an email or overwrite an existing identity. Enable Git LFS hooks when needed with `git lfs install`.
|
|
190
197
|
|
|
@@ -357,9 +364,44 @@ headless service, so environment identity and pairing are kept. A conflicting
|
|
|
357
364
|
launcher version requires reviewed maintenance; the recovery timer stops a
|
|
358
365
|
still-running headless service. Routine fleet-sync never ends a running turn
|
|
359
366
|
to load a new pin or policy: the app restarts into them at the first idle
|
|
360
|
-
check (`t3:running-version`, `t3:policy-loaded`).
|
|
361
|
-
|
|
362
|
-
|
|
367
|
+
check (`t3:running-version`, `t3:policy-loaded`). Private routing and paired
|
|
368
|
+
execution require separate evidence.
|
|
369
|
+
|
|
370
|
+
#### Headless workers
|
|
371
|
+
|
|
372
|
+
A worker whose session is `headless` (`features.session` in `fleet.json`, or
|
|
373
|
+
the `editing` preset) has no display manager, no graphical session and no
|
|
374
|
+
GPU. It runs the pinned T3 **server**, not the desktop app under a virtual
|
|
375
|
+
display: the pinned release ships the server as its own archive
|
|
376
|
+
(`t3-<version>-linux-<arch>.tar.gz`, the `t3_version` and
|
|
377
|
+
`t3_linux_*_sha256` pins, from the release's `SHA256SUMS`), which the
|
|
378
|
+
toolchain already installs on every worker for policy commands. Its
|
|
379
|
+
`t3 serve` runs the server the desktop app embeds, with no browser; the
|
|
380
|
+
subcommand and its flags were read from the pinned release itself
|
|
381
|
+
(`t3 serve --help` of `0.1.8-nightly.20261106.4262`, whose `--mode web`
|
|
382
|
+
server answered `/.well-known/t3/environment` with that `serverVersion`).
|
|
383
|
+
A virtual display would install and pin a display server and a desktop
|
|
384
|
+
toolkit for one process, which costs the memory a small worker needs.
|
|
385
|
+
|
|
386
|
+
`ansible/ubuntu/t3-headless.yml` declares `t3code-headless.service`, a user
|
|
387
|
+
unit started by `default.target`: the designated user lingers, so it starts at
|
|
388
|
+
boot with nobody logged in. It listens on `127.0.0.1:3773` like the desktop
|
|
389
|
+
app, with `T3CODE_HOME=%h/.t3`, so the private route, the policy, the provider
|
|
390
|
+
instances, `personal-worker-recovery` (which reads the release from the
|
|
391
|
+
unit's versioned server path) and the checks are the same for both session
|
|
392
|
+
types. Switching a worker between types stops and removes the other type's
|
|
393
|
+
unit and keeps `~/.t3`; GDM auto-login, if the machine had it, is left as it
|
|
394
|
+
was. A headless worker installs none of the desktop-only packages
|
|
395
|
+
(`features.desktop` in `ansible/ubuntu/policy.json`) and reports their checks
|
|
396
|
+
as not applicable. The server prints a one-time pairing token to its own
|
|
397
|
+
journal at start, as `t3 serve` does anywhere; dev-fleet never reads it.
|
|
398
|
+
Pairing is done from the manager: by a person the first time, and by
|
|
399
|
+
`fleet-node-replace` or the next `fleet-sync` for a machine that replaces a
|
|
400
|
+
worker.
|
|
401
|
+
|
|
402
|
+
The disposable Ubuntu VM test (`ansible/ubuntu/tests/vm.py`) converges each
|
|
403
|
+
session type, reboots with nobody logged in and kills the T3 process to
|
|
404
|
+
prove the recovery timer brings it back.
|
|
363
405
|
|
|
364
406
|
Normal Ubuntu fleet setup is controller-led:
|
|
365
407
|
`devfleet fleet-sync`. See the
|
|
@@ -396,7 +438,10 @@ Controller-driven remote bootstrap passes `--worker` to the shared setup owner.
|
|
|
396
438
|
It installs the `vars.worker_tools` subset from `mise.toml`, with versions and
|
|
397
439
|
artifacts taken from the same `mise.lock`; native T3 and Ubuntu Docker/service
|
|
398
440
|
policy remain source-pinned. `bun bootstrap/doctor.ts --worker --docker --json`
|
|
399
|
-
checks that profile. It does not require unrelated cloud CLIs on workers.
|
|
441
|
+
checks that profile. It does not require unrelated cloud CLIs on workers. A
|
|
442
|
+
worker whose `containers` feature is off (`features.containers: false`, or the
|
|
443
|
+
`editing` preset) leaves `docker-cli` out of its base tools, and its doctor
|
|
444
|
+
runs with `--no-containers` instead of `--docker`.
|
|
400
445
|
|
|
401
446
|
Tool profiles add locked tool sets on top of it: `worker.toolProfiles` in
|
|
402
447
|
`fleet.json` (fleet-wide) or `toolProfiles` on one worker selects `web` (pnpm,
|
|
@@ -404,7 +449,28 @@ Deno, Yarn and headless Chromium for browser tests), `go`, `rust` (with CMake
|
|
|
404
449
|
and Ninja) or `jvm` (Temurin), each a `worker_profile_<name>` under `[vars]` in
|
|
405
450
|
`mise.toml`. `worker.tools` adds single tools dev-fleet already pins. The setup
|
|
406
451
|
installs them (`--node` reads the installed configuration) and the doctor
|
|
407
|
-
checks each as `tool:<name>` (`--profiles`, `--tools`).
|
|
452
|
+
checks each as `tool:<name>` (`--profiles`, `--tools`). The base tools include
|
|
453
|
+
`tmux`, for sessions that outlive an SSH connection.
|
|
454
|
+
|
|
455
|
+
A tool dev-fleet does not ship belongs to the project that needs it: pin it in
|
|
456
|
+
the project's own `mise.toml` and lock it with `mise lock`. On every fleet
|
|
457
|
+
machine dev-fleet's global configuration sets `locked = true` for the
|
|
458
|
+
`project` scope, so mise installs a project's tools in a checkout only from
|
|
459
|
+
that project's `mise.lock` and refuses a tool the lock does not name;
|
|
460
|
+
`mise install --locked` in the checkout installs and verifies them.
|
|
461
|
+
|
|
462
|
+
There is no `mobile` profile. No mobile toolchain can be pinned with
|
|
463
|
+
checksums for both worker architectures through mise: Flutter publishes no
|
|
464
|
+
Linux arm64 release, the Android SDK's platforms and build tools are fetched
|
|
465
|
+
by `sdkmanager` at run time (outside any lock, behind a licence a person
|
|
466
|
+
accepts), and iOS builds need macOS and Xcode, which workers are not. A
|
|
467
|
+
project that builds for Android on x86_64 workers pins `flutter`, `java` and
|
|
468
|
+
`android-sdk` in its own `mise.toml` as above.
|
|
469
|
+
|
|
470
|
+
`worker.exposure` (fleet-wide) or `exposure` on one worker says what a worker
|
|
471
|
+
opens beyond loopback and the tailnet; without it, nothing. See
|
|
472
|
+
[Exposure](ubuntu-sync.md#exposure).
|
|
473
|
+
|
|
408
474
|
The source-owned `worker-auth.ts` timer renews the worker's existing Vault token
|
|
409
475
|
and can restore the declared GitHub account from its authorized Vault service
|
|
410
476
|
credential, without copying another machine's login directory.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# What only a person does on the manager
|
|
2
|
+
|
|
3
|
+
Everything on the manager is declared and converged by `fleet-sync`: the
|
|
4
|
+
toolchain, the power policy (applied by `ansible/macos/manager.yml` on every
|
|
5
|
+
run), the CLI proxy, the monitoring hub and their jobs. What remains are the
|
|
6
|
+
steps that need a person: an identity, an approval or an administrator
|
|
7
|
+
password. Each is done once, and `fleet-sync` measures it afterwards with the
|
|
8
|
+
check named beside it.
|
|
9
|
+
|
|
10
|
+
The list is tested not to grow (`features/rules/manager-setup.feature`): a
|
|
11
|
+
step added here must be added there too, with the check that verifies it.
|
|
12
|
+
|
|
13
|
+
<!-- human-steps -->
|
|
14
|
+
|
|
15
|
+
| Step | Why a person | Verified by |
|
|
16
|
+
| ---------------------------------------------------------------------------------------------------------- | ------------------------------------- | --------------------- |
|
|
17
|
+
| Install Docker Desktop at the release `mise.toml` pins and sign in to it | A licence and an account | `manager:docker` |
|
|
18
|
+
| Sign this Mac in to Tailscale and approve it | A tailnet account and device approval | `tailscale:discovery` |
|
|
19
|
+
| Pass the bucket credentials (`DEVFLEET_BUCKET_*`) or save them with `fleet-state --init` | Credentials of an account you own | `state:bucket` |
|
|
20
|
+
| Log the manager in to Vault | A password or an identity provider | `auth:vault` |
|
|
21
|
+
| Grant the power settings once: `ansible-playbook -e user=$(id -un) -K ansible/macos/manager-privilege.yml` | An administrator password | `manager:power` |
|
|
22
|
+
|
|
23
|
+
<!-- /human-steps -->
|
|
24
|
+
|
|
25
|
+
The grant lets the manager's user run exactly `pmset -c` and
|
|
26
|
+
`systemsetup -getusingnetworktime` without a password, so later syncs never
|
|
27
|
+
prompt. The same playbook, run as an administrator, also installs what only
|
|
28
|
+
root may write: the newsyslog drop-in that keeps the logs of the scheduled
|
|
29
|
+
jobs under a fixed size (`manager:job-logs`) and T3's administrator policy
|
|
30
|
+
(`t3:updates`). Every `fleet-sync` then applies `ansible/macos/manager.yml`
|
|
31
|
+
unattended: in check mode first, then for real, with a receipt like the
|
|
32
|
+
workers' and a full re-apply at least once a day (`manager:playbook`). Run the
|
|
33
|
+
grant again when `manager:clock` or `manager:job-logs` asks for it after an
|
|
34
|
+
upgrade, or with `-e grant=absent` to remove it.
|
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 244 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 244 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 244 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 244 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 244 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 244 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 244 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 244 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 244 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,39 @@ There are 244 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.
|
|
409
|
+
- `VAULT_WORKER_POLICY_FAILED`: vault worker policy failed.
|
|
410
|
+
|
|
411
|
+
## The fleet definition and a worker's lifecycle
|
|
412
|
+
|
|
413
|
+
- **Meaning:** `fleet.json` breaks a rule of the fleet definition, the pinned Ansible rejects the inventory it describes, or a seed, replacement or retirement could not change the host keys the manager trusts.
|
|
414
|
+
- **Confirm:** `devfleet fleet-validate` names the field that breaks a rule as `{code, path, expected, actual}`; `devfleet fleet-validate --ansible` also renders the inventory and syntax-checks every playbook.
|
|
415
|
+
- **Fix:** Correct the field the check names in `fleet.json`, then run the command again. For SSH_HOST_KEY_NOT_CHANGED run `devfleet fleet-validate --ansible`, then repeat `devfleet fleet-node-seed`, `devfleet fleet-node-replace` or `devfleet fleet-node-retire`.
|
|
416
|
+
- **Escalate:** An inventory the pinned Ansible rejects for a valid definition is an issue.
|
|
417
|
+
|
|
418
|
+
- `DEFINITION_INVALID`: definition invalid.
|
|
419
|
+
- `DEFINITION_INVENTORY_REJECTED`: definition inventory rejected.
|
|
420
|
+
- `DEFINITION_MISSING`: definition missing.
|
|
421
|
+
- `DEFINITION_UNREADABLE`: definition unreadable.
|
|
422
|
+
- `SSH_HOST_KEY_NOT_CHANGED`: ssh host key not changed.
|
|
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.
|
|
349
440
|
|
|
350
441
|
## Fleet operations and the fleet state
|
|
351
442
|
|
|
@@ -366,6 +457,7 @@ There are 244 codes; 31 need a person.
|
|
|
366
457
|
- `FLEET_REMOTE_LOCKED`: fleet remote locked.
|
|
367
458
|
- `FLEET_REMOTE_OPERATION_FAILED`: fleet remote operation failed.
|
|
368
459
|
- `FLEET_REVISION_REQUIRED`: fleet revision required.
|
|
460
|
+
- `FLEET_ROLLBACK_UNAVAILABLE`: fleet rollback unavailable.
|
|
369
461
|
- `FLEET_SCHEDULE_PREVIEW`: fleet schedule preview.
|
|
370
462
|
- `FLEET_SOURCE_UNAVAILABLE`: fleet source unavailable.
|
|
371
463
|
- `FLEET_STATE_DIVERGED`: fleet state diverged. **Needs a person.**
|
|
@@ -380,6 +472,19 @@ There are 244 codes; 31 need a person.
|
|
|
380
472
|
- `REMOTE_CONFIGURATION_INCOMPLETE`: remote configuration incomplete.
|
|
381
473
|
- `REMOTE_PHASE_FAILED`: remote phase failed.
|
|
382
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
|
+
|
|
383
488
|
## Notifications (fleet-watch)
|
|
384
489
|
|
|
385
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
|