@crvouga/dev-fleet 0.1.0 → 0.3.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 +265 -37
- package/agents/setup.md +42 -12
- package/ansible/macos/manager.yml +10 -7
- package/ansible/macos/policy.json +2 -10
- package/ansible/macos/t3-policy.yml +49 -0
- package/ansible/monitoring/README.md +86 -25
- package/ansible/monitoring/agent.yml +40 -2
- package/ansible/monitoring/hub-route-darwin.yml +51 -0
- package/ansible/monitoring/lock.json +5 -0
- package/ansible/pyproject.toml +12 -0
- package/ansible/ubuntu/baseline.yml +1 -1
- package/ansible/ubuntu/disk-gc.py +336 -0
- package/ansible/ubuntu/policy.json +13 -3
- package/ansible/ubuntu/t3-desktop.yml +19 -0
- package/ansible/ubuntu/t3-health.sh +93 -22
- package/ansible/ubuntu/t3-idle.py +42 -0
- package/ansible/ubuntu/t3-policy.yml +4 -11
- package/ansible/ubuntu/tailscale.yml +8 -5
- package/ansible/ubuntu/tasks/recovery.yml +60 -1
- package/ansible/ubuntu/tasks/resources.yml +61 -1
- package/ansible/ubuntu/tasks/t3-restart.yml +37 -0
- package/ansible/ubuntu/wip-guard.py +121 -0
- package/ansible/uv.lock +190 -0
- package/bootstrap/bootstrap.sh +25 -18
- package/bootstrap/doctor.ts +46 -1
- package/bootstrap/fleet-manager-run.sh +42 -15
- package/bootstrap/fleet-remote-bootstrap.sh +85 -12
- package/bootstrap/global-config.ts +64 -6
- package/bootstrap/install-launcher.sh +3 -0
- package/bootstrap/native-t3.ts +22 -11
- package/bootstrap/npm.ts +10 -2
- package/bootstrap/release-version.ts +157 -0
- package/cliproxy/README.md +3 -3
- package/cliproxy/scripts/accounts.py +216 -0
- package/cliproxy/scripts/checkpoint.py +29 -1
- package/cliproxy/scripts/cli.py +1 -1
- package/cliproxy/scripts/clientkeys.py +99 -0
- package/cliproxy/scripts/lease.py +80 -0
- package/cliproxy/scripts/ops +137 -25
- package/cliproxy/scripts/restore-verify +15 -0
- package/docs/accounts.md +154 -0
- package/docs/consumer-setup.md +408 -0
- package/docs/declared.md +91 -0
- package/docs/durability.md +26 -0
- package/docs/machine-setup.md +400 -0
- package/docs/operations.md +276 -0
- package/docs/releasing.md +103 -0
- package/docs/state.md +254 -0
- package/docs/topology.md +69 -0
- package/docs/ubuntu-sync.md +357 -0
- package/docs/worker-contract.md +67 -0
- package/examples/fleet.json +5 -0
- package/llms.txt +36 -0
- package/mise.lock +29 -0
- package/mise.toml +15 -2
- package/package.json +11 -2
- package/schema/fleet.schema.json +137 -15
- package/src/accounts/checks.ts +220 -0
- package/src/accounts/context.ts +76 -0
- package/src/accounts/propagate.ts +62 -0
- package/src/accounts/roster.ts +85 -0
- package/src/accounts/selection.ts +67 -0
- package/src/accounts/sessions.ts +120 -0
- package/src/agents/account-relay.ts +176 -26
- package/src/agents/fleet-verify.ts +9 -0
- package/src/agents/launch.ts +84 -5
- package/src/agents/native-verify.ts +293 -22
- package/src/agents/provider-errors.ts +9 -2
- package/src/agents/registry.ts +7 -1
- package/src/cli/agent-guide.ts +39 -5
- package/src/cli/app.ts +11 -4
- package/src/cli/cli.ts +7 -0
- package/src/cli/command-index.ts +7 -0
- package/src/cli/fleet-accounts.ts +253 -0
- package/src/cli/fleet-state.ts +499 -0
- package/src/cli/fleet-sync-accounts.ts +58 -0
- package/src/cli/fleet-sync-checkout.ts +56 -17
- package/src/cli/fleet-sync-enrollment.ts +19 -23
- package/src/cli/fleet-sync-forward.ts +1 -1
- package/src/cli/fleet-sync-manager.ts +84 -12
- package/src/cli/fleet-sync-monitoring.ts +5 -5
- package/src/cli/fleet-sync-output.ts +7 -3
- package/src/cli/fleet-sync-remote.ts +31 -18
- package/src/cli/fleet-sync-state.ts +86 -0
- package/src/cli/fleet-sync.ts +159 -57
- package/src/cli/fleet.ts +4 -2
- package/src/cli/managed-health.ts +4 -4
- package/src/cli/node-sync.ts +7 -0
- package/src/cli/program.ts +20 -4
- package/src/cli/project.ts +51 -0
- package/src/cli/register-fleet.ts +86 -1
- package/src/cli/registry.ts +41 -1
- package/src/cli/roster.ts +46 -30
- package/src/cli/settings.ts +15 -12
- package/src/cli/source-operations.ts +27 -3
- package/src/cli/tailnet.ts +2 -2
- package/src/definition.ts +116 -4
- package/src/diagnostics.ts +1 -1
- package/src/fleet-config.ts +105 -19
- package/src/ide/definition.ts +86 -0
- package/src/ide/ide.ts +43 -0
- package/src/ide/index.ts +5 -0
- package/src/ide/orchestrate.ts +50 -0
- package/src/ide/t3.ts +111 -0
- package/src/machine/source.ts +3 -3
- package/src/machine/state.ts +15 -27
- package/src/manager-sync.ts +9 -18
- package/src/monitoring/agent-evidence.ts +146 -0
- package/src/monitoring/apply.ts +15 -3
- package/src/monitoring/beszel.ts +11 -4
- package/src/monitoring/enroll.ts +2 -1
- package/src/monitoring/hub-access.ts +4 -3
- package/src/monitoring/hub-backup.ts +121 -0
- package/src/monitoring/hub-viewer.ts +8 -8
- package/src/monitoring/hub.ts +3 -3
- package/src/monitoring/manager-hub.ts +30 -14
- package/src/node-converge-linux.ts +30 -7
- package/src/package-info.ts +45 -0
- package/src/platform/ansible.ts +19 -15
- package/src/platform/darwin.ts +0 -13
- package/src/platform/fallback.ts +0 -8
- package/src/platform/linux.ts +0 -10
- package/src/platform/resource-verify.ts +8 -4
- package/src/platform/t3-policy.ts +33 -6
- package/src/platform/t3-service.ts +104 -0
- package/src/platform/types.ts +0 -4
- package/src/platform/ubuntu-audit.ts +5 -2
- package/src/platform/windows.ts +0 -14
- package/src/remote-bootstrap.ts +17 -19
- package/src/remote-entry.ts +15 -1
- package/src/repair-catalog.ts +1 -1
- package/src/runner.ts +1 -1
- package/src/service-auth.ts +15 -5
- package/src/state/bucket.ts +208 -0
- package/src/state/check.ts +74 -0
- package/src/state/connection.ts +92 -0
- package/src/state/enrollment.ts +45 -0
- package/src/state/legacy.ts +89 -0
- package/src/state/litestream.ts +143 -0
- package/src/state/operator.ts +20 -0
- package/src/state/paths.ts +40 -0
- package/src/state/roster.ts +62 -0
- package/src/state/store.ts +169 -0
- package/src/state/sync.ts +352 -0
- package/src/t3/accounts.ts +7 -1
- package/src/t3/models.ts +133 -0
- package/src/t3/pairing.ts +31 -0
- package/src/t3/profile.ts +203 -0
- package/src/t3/version.ts +119 -0
- package/src/t3-endpoint.ts +64 -8
- package/src/t3-linked-check.ts +24 -2
- package/src/toolchain-checks.ts +25 -0
- package/src/transport.ts +3 -2
- package/src/types.ts +4 -0
- package/src/worker-access-checks.ts +56 -3
- package/src/worker-host-checks.ts +41 -13
- package/src/workstation-checks.ts +1 -1
- package/src/index.ts +0 -6
- package/src/test-fixtures.ts +0 -36
package/docs/topology.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
> This document was written for the fleet dev-fleet was extracted from. Paths and
|
|
2
|
+
> commands were updated; some statements still describe that fleet and are being
|
|
3
|
+
> revised. The README is authoritative.
|
|
4
|
+
|
|
5
|
+
# Fleet topology: one manager, many workers
|
|
6
|
+
|
|
7
|
+
The fleet has exactly two roles, declared in
|
|
8
|
+
your project's `fleet.json` (version 3, checked by [the schema](../schema/fleet.schema.json)) and validated by
|
|
9
|
+
[`src/definition.ts`](../src/definition.ts):
|
|
10
|
+
|
|
11
|
+
| Role | How many | Machine | Holds state | Runs |
|
|
12
|
+
| ----------- | ----------- | ------------------------------------- | ----------- | ---------------------------------------------------------------------------------------- |
|
|
13
|
+
| **manager** | exactly one | the human's macOS machine (`manager`) | yes | `fleet-sync`, the T3 desktop that links to every worker, and **every singleton service** |
|
|
14
|
+
| **workers** | any | Linux boxes (`workers`, Ubuntu 26.04) | no | the T3 desktop app (UI plus server), agent tools, the Beszel agent; clients only |
|
|
15
|
+
|
|
16
|
+
## Manager
|
|
17
|
+
|
|
18
|
+
- A singleton. All control happens from it, by the human: `devfleet fleet-sync`
|
|
19
|
+
converges the manager **locally** first, then each worker over SSH.
|
|
20
|
+
Started on a worker, `fleet-sync` hands the same request to the manager over
|
|
21
|
+
host-key-verified SSH (no agent forwarding, no credentials copied) and prints
|
|
22
|
+
the manager's result; with no SSH route it changes nothing and reports
|
|
23
|
+
`FLEET_NOT_MANAGER` or `FLEET_MANAGER_UNREACHABLE`. The manager runs that
|
|
24
|
+
request from a clean, fleet-owned checkout of the exact revision
|
|
25
|
+
(`bootstrap/fleet-manager-run.sh`, under
|
|
26
|
+
`~/.local/share/personal/fleet/manager-runs`, three most recent kept), so
|
|
27
|
+
fixes in the revision reach the manager without touching the human's
|
|
28
|
+
checkout. The revision must be on the manager or on `origin`.
|
|
29
|
+
- It hosts every singleton service. Today that is the CLI proxy
|
|
30
|
+
(`services.proxy`, [`cliproxy`](../cliproxy/README.md))
|
|
31
|
+
and the Beszel hub (`services.monitoring`,
|
|
32
|
+
[`ansible/monitoring`](../ansible/monitoring/README.md)).
|
|
33
|
+
The manager also runs its own Beszel agent, so it is always a system on that
|
|
34
|
+
hub.
|
|
35
|
+
A new singleton service is declared under `services` and placed on the
|
|
36
|
+
manager; it never names a host.
|
|
37
|
+
- Workers depend on it, so it never sleeps on AC power
|
|
38
|
+
(`pmset` in `ansible/macos/policy.json`, checked as `manager:power`, applied
|
|
39
|
+
once by an administrator with `ansible/macos/manager.yml`; the failed check
|
|
40
|
+
prints the command). While the
|
|
41
|
+
manager is asleep, closed or offline, workers have no CLI proxy and no
|
|
42
|
+
dashboard; nothing fails over, because a singleton never runs twice.
|
|
43
|
+
- Its state (proxy OAuth/runtime state, the hub database) is backed up off the
|
|
44
|
+
machine as each service documents, so a replacement manager restores it.
|
|
45
|
+
|
|
46
|
+
## Workers
|
|
47
|
+
|
|
48
|
+
- Stateless cattle: turn one off, re-image it or add another at will. A worker
|
|
49
|
+
is declared in `workers`, enrolled once (SSH trust, sudo, its own Vault
|
|
50
|
+
token), and then fully rebuilt by `fleet-sync` from Git and Vault.
|
|
51
|
+
- Workers never host a singleton service. The proxy reports any local install
|
|
52
|
+
on a worker as a duplicate owner, and the hub refuses to install anywhere but
|
|
53
|
+
the manager.
|
|
54
|
+
- Anything on a worker is disposable: T3 sessions and worktrees, caches,
|
|
55
|
+
checkouts. Durable work lives in Git remotes; credentials come from Vault.
|
|
56
|
+
Work in progress in agent worktrees is pushed to a recovery ref every 15
|
|
57
|
+
minutes; [durability](durability.md) says what a lost machine takes with it.
|
|
58
|
+
- The first declared worker is the rollout canary for a revision new to the
|
|
59
|
+
fleet: a failed canary stops promotion to the other workers. Repeating a
|
|
60
|
+
revision that already converged everywhere runs all workers at once.
|
|
61
|
+
|
|
62
|
+
## Changing the topology
|
|
63
|
+
|
|
64
|
+
- **Add or replace a worker:** add it to `workers`, enroll it with
|
|
65
|
+
`fleet-sync`, done. Remove it by deleting the entry.
|
|
66
|
+
- **Replace the manager:** edit `manager`, then move each singleton service with
|
|
67
|
+
its documented fence-and-restore runbook (never two copies at once):
|
|
68
|
+
[CLI proxy](../cliproxy/docs/migration.md),
|
|
69
|
+
[Beszel hub](../ansible/monitoring/README.md).
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
> This document was written for the fleet dev-fleet was extracted from. Paths and
|
|
2
|
+
> commands were updated; some statements still describe that fleet and are being
|
|
3
|
+
> revised. The README is authoritative.
|
|
4
|
+
|
|
5
|
+
# Developer fleet: one manager (macOS) and stateless workers (Ubuntu)
|
|
6
|
+
|
|
7
|
+
## Release status
|
|
8
|
+
|
|
9
|
+
What is proven, and where:
|
|
10
|
+
|
|
11
|
+
| Evidence | Where |
|
|
12
|
+
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| A bare Ubuntu 26.04 machine bootstrapped over real SSH, then repeated | `features/fresh-box/fresh-box.feature`, on every pull request (amd64 and arm64) |
|
|
14
|
+
| One run over several machines, one versioned report, no partial receipt | `features/fresh-box/fleet.feature` |
|
|
15
|
+
| Interrupted checkout, package installation and toolchain setup resumed | `features/fresh-box/fleet.feature` |
|
|
16
|
+
| Overlapping runs, a dropped connection, a held APT lock, no DNS, no disk | `features/fresh-box/fleet.feature` |
|
|
17
|
+
| A failed canary stops promotion; a changed host key is never touched | `features/fresh-box/fleet.feature`, `features/orchestration/fleet-sync.feature` |
|
|
18
|
+
| Offline, unapproved and replaced devices | `features/orchestration/fleet-sync.feature` (the tailnet is injected) |
|
|
19
|
+
| T3 route, origin policy, pairing redaction, service environment | `features/core/t3-access.feature`, `features/agents/native-agents.feature` |
|
|
20
|
+
| Vault renewal and policy outcomes, provider incompatibilities | `features/core/worker-checks.feature`, `features/agents/provider-failures.feature` |
|
|
21
|
+
| Beszel agent privileges, evidence, backups and the stock API | `features/monitoring/`, and `ansible/monitoring/tests/upstream.py` against the pinned binaries (backup restored into a separate instance) |
|
|
22
|
+
|
|
23
|
+
A container has no systemd, so the fresh-box runs prove bootstrap,
|
|
24
|
+
interruption and fault handling, not a booted desktop session. What only a
|
|
25
|
+
live fleet can show is checked by `fleet-sync` itself on every run and stays a
|
|
26
|
+
failed or skipped required check until it is true; nothing advances a success
|
|
27
|
+
receipt before then:
|
|
28
|
+
|
|
29
|
+
- the private T3 route reached from a separate tailnet client, and a paired
|
|
30
|
+
conversation with a tool action (`t3:manager-endpoint`, `t3:paired-access`,
|
|
31
|
+
`t3:end-to-end`);
|
|
32
|
+
- each provider route signed in on its real account, under the T3 service's
|
|
33
|
+
environment (`provider:*`, `agent:service-environment`);
|
|
34
|
+
- unattended Vault renewal under the fleet's real policy
|
|
35
|
+
(`worker:vault-renewal`, `worker:proxy-authentication`);
|
|
36
|
+
- private Beszel metrics from every worker and a scheduled off-host hub backup
|
|
37
|
+
(`beszel:*`, `beszel:backup`);
|
|
38
|
+
- reboot, logout and crash recovery of a real GNOME session
|
|
39
|
+
(`worker:t3-recovery`, `ubuntu:boot-policy`).
|
|
40
|
+
|
|
41
|
+
Reproduce the disposable runs with
|
|
42
|
+
`DEVFLEET_TEST_FRESH_BOX=1 bun test features/fresh-box`; the VM procedure for
|
|
43
|
+
a full systemd guest is in [`ansible/ubuntu/tests/README.md`](../ansible/ubuntu/tests/README.md).
|
|
44
|
+
|
|
45
|
+
## One command, from the manager
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
devfleet fleet-sync
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`devfleet fleet-sync` executes the manager-push implementation:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
resolve enrolled fleet
|
|
55
|
+
→ inspect Tailscale and verified SSH
|
|
56
|
+
→ bootstrap missing remote prerequisites
|
|
57
|
+
→ apply one approved exact revision
|
|
58
|
+
→ establish fresh SSH connections and check readiness
|
|
59
|
+
→ report every target, including offline and blocked nodes
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Bare invocation selects every canonical Linux and macOS node. It does not mean
|
|
63
|
+
“this computer,” and the manager is not implicitly a deployment target. Its own
|
|
64
|
+
node is targeted only if explicitly declared in Fleet membership. Ubuntu 26.04 is
|
|
65
|
+
the accepted worker platform; a node declared `"os": "macOS"` runs the same phases
|
|
66
|
+
best effort through `ansible/macos` (see the platform matrix below).
|
|
67
|
+
Windows machines stay declared for diagnostics and are never converged.
|
|
68
|
+
`--node ID` filters troubleshooting/rollout. `--all` is an explicit spelling of the
|
|
69
|
+
default. Neither flag authorizes managing arbitrary Tailscale peers.
|
|
70
|
+
|
|
71
|
+
Roster-level workspace convergence lives in `devfleet fleet-repair --repair workspace`, which previews until `--apply --yes`.
|
|
72
|
+
The new manager report is version **2**, deliberately replacing the unshipped
|
|
73
|
+
local-first fleet-sync version 1 contract.
|
|
74
|
+
|
|
75
|
+
## Prerequisites and first enrollment
|
|
76
|
+
|
|
77
|
+
The manager needs Node/npm (Node `^22.13.0 || >=23.5.0`), an OpenSSH client,
|
|
78
|
+
a working Tailscale daemon, and Git for approved source preparation. npx cannot
|
|
79
|
+
execute without Node/npm. Existing shared setup remains
|
|
80
|
+
[`bootstrap/bootstrap.sh`](../bootstrap/bootstrap.sh); run that tracked
|
|
81
|
+
bootstrap on a manager without developer tools, then invoke the same command.
|
|
82
|
+
Do not install an unrelated fleet installer. The npm command’s help, catalog and
|
|
83
|
+
structured missing-enrollment result require neither Bun nor a checkout.
|
|
84
|
+
|
|
85
|
+
On first interactive use the command resolves only canonical machines through
|
|
86
|
+
existing SSH aliases, tests strict SSH access, and offers one enrollment
|
|
87
|
+
confirmation for the verified connections. The apply approval binds a fingerprint
|
|
88
|
+
of the selected revision, policy and connection records; changed bindings fail
|
|
89
|
+
with `FLEET_PLAN_STALE` before source preparation or remote mutation. It stores private overrides mode 0600. Unknown host keys are never accepted automatically. Compare fingerprints
|
|
90
|
+
out of band before enrolling trust. Unreachable machines remain listed; first
|
|
91
|
+
reachability requires an existing trusted LAN SSH route or physical setup of
|
|
92
|
+
SSH/Tailscale. No software on the manager can bootstrap a disconnected box.
|
|
93
|
+
|
|
94
|
+
A previously configured manager uses saved connection bindings and its approved
|
|
95
|
+
revision. A changed Tailscale device ID is a replacement gate, even if the old
|
|
96
|
+
DNS name and SSH alias still resolve. Enrollment never copies host keys,
|
|
97
|
+
Tailscale state, provider OAuth, or another machine’s T3 environment.
|
|
98
|
+
|
|
99
|
+
Remote developer-tool prerequisites are not required: the initial SSH probe uses
|
|
100
|
+
POSIX facilities. Approved application sends a Git bundle of that one commit
|
|
101
|
+
(its files, no history; the worker checkout is shallow) and the bootstrap
|
|
102
|
+
script extracted from that exact commit over verified SSH. A worker that
|
|
103
|
+
already has the revision receives nothing, and the toolchain setup is skipped
|
|
104
|
+
while its inputs (`mise.toml`, the locks, `scripts/dev`) are unchanged since a
|
|
105
|
+
successful setup within the last day. It installs missing
|
|
106
|
+
Ubuntu bootstrap packages through signed APT, then uses the existing mise and
|
|
107
|
+
frozen dependency installer. Bootstrap APT packages use the installed Ubuntu
|
|
108
|
+
archive configuration, because a new machine cannot reach the HTTPS snapshot
|
|
109
|
+
before it has CA certificates; the baseline then pins each of them, Ansible
|
|
110
|
+
included, to the exact version in `ansible/ubuntu/policy.json`. The interactive manager flow can request native sudo authorization over a verified SSH terminal after the consolidated apply confirmation. It validates and installs the designated-user sudo policy; passwords are never captured or stored. Missing administrator permission, an unmanaged conflicting sudoers fragment, or unavailable terminal remains action-required. JSON and unattended runs never prompt and require existing noninteractive sudo.
|
|
111
|
+
|
|
112
|
+
## Policy, ownership and private state
|
|
113
|
+
|
|
114
|
+
- `fleet.json` and `definition.schema.json`: portable machine
|
|
115
|
+
IDs, aliases, OS, singleton roles and Ubuntu release policy.
|
|
116
|
+
- Private manager overrides: `--config PATH`, `PERSONAL_FLEET_CONFIG`, or the
|
|
117
|
+
roster in the [fleet state](state.md), using the existing roster schema.
|
|
118
|
+
Preserve workspace/runtime paths, capabilities and explicit `tailscaleId`
|
|
119
|
+
bindings.
|
|
120
|
+
- Workstation network consumers derive their inventory from Fleet. The old file
|
|
121
|
+
is retained only as `packages/fleet/migrations/workstation-network-v1.json` for
|
|
122
|
+
lossless identity-path migration; conflicts fail rather than silently winning.
|
|
123
|
+
- Manager approval: the `enrollment` document in the [fleet state](state.md).
|
|
124
|
+
Manager deployment lock: `~/.local/share/personal/fleet/manager/sync.lock`.
|
|
125
|
+
- Remote source: `~/.local/share/personal/fleet/revisions/FULL_SHA`.
|
|
126
|
+
Remote lock file: `~/.local/share/personal/fleet/remote-operation.lock`, held
|
|
127
|
+
with `flock` on Linux and the same descriptor through Python `fcntl` on macOS.
|
|
128
|
+
Never repurpose a development checkout or reset dirty worktrees.
|
|
129
|
+
- Fleet owns `ubuntu/`, `macos/` and `monitoring/`; mise manifests, locks and
|
|
130
|
+
`scripts/dev` own developer tools. CLIProxy deployment/auth/recovery stays with
|
|
131
|
+
`cliproxy`. Cloud resources, DNS, Vault policy and tailnet access
|
|
132
|
+
policy stay with OpenTofu and are not modified by this command.
|
|
133
|
+
|
|
134
|
+
The repository is public. Existing portable host metadata was retained during
|
|
135
|
+
migration; do not publish device IDs, private keys, manager-specific secrets,
|
|
136
|
+
credentials, backups or runtime databases. Intentional persistent policy changes
|
|
137
|
+
must be reviewed in Git. Emergency service restarts do not authorize permanent
|
|
138
|
+
untracked configuration edits.
|
|
139
|
+
|
|
140
|
+
## Preview, approval and results
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
devfleet fleet-sync --dry-run --json
|
|
144
|
+
devfleet fleet-sync --revision FULL_40_CHARACTER_SHA --dry-run --json
|
|
145
|
+
devfleet fleet-sync --revision FULL_40_CHARACTER_SHA --non-interactive --json
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Preview can query Tailscale and establish read-only SSH sessions. It never enrolls,
|
|
149
|
+
authenticates to a provider, installs, creates pairing material, schedules work or
|
|
150
|
+
writes configuration. JSON/noninteractive modes never prompt or open a browser.
|
|
151
|
+
`fleet-sync` applies on invocation with no confirmation prompt; use `--dry-run`
|
|
152
|
+
to preview. `--apply --yes` is still accepted for existing scripts. Initial
|
|
153
|
+
trust/enrollment of new connections still asks separately.
|
|
154
|
+
|
|
155
|
+
Select one full lowercase commit SHA. Approval is saved on the manager and
|
|
156
|
+
retained throughout the run. New package versions do not silently replace saved
|
|
157
|
+
approval. Promote a reviewed release with the explicit `--revision SHA` invocation.
|
|
158
|
+
Use `devfleet@VERSION fleet-sync ...` for automation; unversioned npx
|
|
159
|
+
is convenience, not a reproducibility guarantee. Dirty review artifacts have no
|
|
160
|
+
automatically approved source SHA.
|
|
161
|
+
|
|
162
|
+
Version 2 JSON contains `execution: manager-push`, desired revision, per-node
|
|
163
|
+
reachability, applied revision (null when not verified), checks, durations,
|
|
164
|
+
candidate count and the private dashboard URL when the enrolled hub resolves.
|
|
165
|
+
An absent applied revision is not “unchanged.” Every required skip/failure keeps
|
|
166
|
+
readiness incomplete. Exit 0 means the requested read-only inspection or required
|
|
167
|
+
checks passed; 1 means incomplete/action-required/failed; 2 means invalid usage or
|
|
168
|
+
refused configuration. JSON is one document on stdout, with no child process logs.
|
|
169
|
+
|
|
170
|
+
These are distinct facts: configuration applied; local services healthy; fresh
|
|
171
|
+
manager connection verified; authenticated IDE/T3 tool execution verified.
|
|
172
|
+
Neither an HTTP 200 nor a successful installer establishes the last fact.
|
|
173
|
+
|
|
174
|
+
## Machine policy by platform
|
|
175
|
+
|
|
176
|
+
Every machine-level setting is declared in Ansible using only `ansible.builtin`
|
|
177
|
+
modules, applied by `node-converge` after source bootstrap, and then measured
|
|
178
|
+
independently. Bespoke TypeScript remains only where a native CLI or API has no
|
|
179
|
+
declarative owner: T3 service lifecycle, Beszel enrollment tokens, provider
|
|
180
|
+
verification and the CLIProxy singleton.
|
|
181
|
+
|
|
182
|
+
| Policy | Workers (Ubuntu 26.04, stateless) | Manager (macOS, singleton) |
|
|
183
|
+
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
184
|
+
| Never sleep | systemd sleep targets masked; logind lid/idle policy; GNOME power plugin set to `nothing` | never sleeps on AC power (`pmset -c sleep 0`, `macos/manager.yml`, verified as `manager:power`); battery sleep is left to the human |
|
|
185
|
+
| No automatic logout or lock | GNOME `lock-enabled false`, `idle-delay 0`; designated-user lingering | not managed (the human uses it) |
|
|
186
|
+
| Always-on services | drop-ins `Restart=always`, `StartLimitIntervalSec=0` for ssh, tailscaled, docker, beszel-agent | singleton services under launchd and Docker Desktop (see the last row) |
|
|
187
|
+
| T3 health and connection | `fleet-sync` and `fleet-status --workers` require `worker:t3-health` (local API answers), `t3:manager-endpoint` (manager HTTPS over Tailscale) and `t3:paired-access` (manager T3 link reachable) | the human's T3 desktop holds the links to every worker (`t3:paired-access`) |
|
|
188
|
+
| Memory safety | `systemd-oomd` (swap 90%, pressure 60%); `user.slice` `MemoryMax=90%`; each `t3code-*.scope` agent `MemoryMax=30%`, `OOMPolicy=continue`; swap file when none | not managed |
|
|
189
|
+
| Disk safety | `personal-fleet-disk-guard` kills only the top user-cgroup writer below 3 GiB free, never deletes files; hourly `personal-fleet-disk-gc.timer` deletes git-ignored build output (`node_modules`, `.cache`, `dist`, ...) in checkouts idle 7d (12h under 15 GiB free) with no process inside, plus unused Docker images and build cache, never source, worktrees or volumes; journal 1G; `/tmp` 25% of RAM, aged 2d hourly, a measured write probe (`TMP_QUOTA_EXHAUSTED`), and T3 agents use disk-backed `/var/tmp` (`TMPDIR`, aged 14d); Docker log rotation | not managed |
|
|
190
|
+
| Tailscale | pinned APT package, `tailscaled` enabled, native login required | Tailscale app, human login |
|
|
191
|
+
| Dashboard agent | pinned native binary, hardened `beszel-agent` systemd unit as user `beszel` | not installed |
|
|
192
|
+
| Credential renewal | user timer `personal-worker-auth` renews the node's own Vault token and GitHub login every 10min | the human's own Vault and GitHub sessions |
|
|
193
|
+
| Noninteractive sudo | validated `/etc/sudoers.d/personal-fleet-USER` | never; the human runs `manager.yml` once with `-K` |
|
|
194
|
+
| T3 worker app | pinned desktop `.deb` is the only T3 process (UI plus embedded server); GDM auto-login, user unit `t3code-desktop.service` bound to the graphical session with `Restart=always`; `personal-worker-recovery` runs a copy of `t3-health.sh` installed in `/usr/local/lib/personal-fleet` every minute: it stops and disables a still-running headless `t3code.service`, reports `T3_NO_GRAPHICAL_SESSION` and restarts the display manager (three times at most) when there is no session, starts a stopped app after `reset-failed`, restarts one whose local API missed three checks, and restarts one on another release than the installed one or with a deferred policy restart once it runs no agent turn; administrator policy | not a worker |
|
|
195
|
+
| T3 providers | the same managed instances (`fleet/agents/t3.json`) through locked T3 administrator policy | reconciled into the human's T3 settings by `fleet-sync` (`t3:providers`) with the ownership-safe `t3-apply` engine, so every T3 offers the identical set; unrelated and unmanaged instances are preserved. `fleet-sync` also applies the same proxy agent profiles (`configuration:apply`) and verifies the shared Vault gateway credential end to end with `provider:*:inference`, exactly as on workers |
|
|
196
|
+
| Singleton services | never; clients of the manager's services only | every singleton runs here: CLI proxy (`cliproxy`), Beszel hub (`workstation/monitoring`) |
|
|
197
|
+
|
|
198
|
+
Credential sync is deliberately indirect: no secret is copied between machines.
|
|
199
|
+
OpenTofu issues each enrolled worker its own renewable periodic token
|
|
200
|
+
(`vault.tokens` entries `fleet-worker-<node>`, policy `personal-read`, sunk into
|
|
201
|
+
`secret/personal/prd` as `FLEET_WORKER_TOKEN_<NODE>`). `devfleet fleet-sync`
|
|
202
|
+
delivers each node's token automatically over SSH stdin (never as an argument)
|
|
203
|
+
whenever that node has no working Vault session. Interactive runs first log the
|
|
204
|
+
manager in (`vault login -method=userpass`, password typed on your terminal)
|
|
205
|
+
and fall back to the same password login on a node whose token is not issued.
|
|
206
|
+
Without either, the node reports `VAULT_LOGIN_REQUIRED` and its T3 proxy
|
|
207
|
+
providers stay unavailable. The manual equivalent of the automatic delivery:
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
vault kv get -field=FLEET_WORKER_TOKEN_WORKER_ONE secret/personal/prd \
|
|
211
|
+
| ssh worker-one 'vault login -address=https://vault.example.com -no-print -'
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
From then on the node's `personal-worker-auth` timer renews that token and its
|
|
215
|
+
GitHub login, and renders provider configuration from Vault through Workstation.
|
|
216
|
+
|
|
217
|
+
## Ubuntu and remote-work safety
|
|
218
|
+
|
|
219
|
+
The declared common baseline is Ubuntu **26.04 LTS**, amd64/arm64. Other releases
|
|
220
|
+
receive `UBUNTU_BASELINE_MISMATCH`; there is no automatic release upgrade or
|
|
221
|
+
package downgrade. Fleet audits release, packages, service state and dpkg
|
|
222
|
+
consistency, declares snapshot sources for managed transactions, and pins its
|
|
223
|
+
selected APT packages. Unrelated applications and package sources are preserved.
|
|
224
|
+
|
|
225
|
+
Power policy masks sleep/hibernate targets and declares logind sleep/lid defaults.
|
|
226
|
+
GNOME lock/idle changes apply to the designated user only. Effective running
|
|
227
|
+
session/logind behavior still needs a maintenance/reboot acceptance check; writing
|
|
228
|
+
a file does not prove logout/reboot survival. The sudoers fragment is validated
|
|
229
|
+
with visudo before installation; this user and its agents can administer the
|
|
230
|
+
machine. SSH authentication remains enabled.
|
|
231
|
+
|
|
232
|
+
SSH forwarding changes validate the complete configuration before reload and
|
|
233
|
+
restore the previous fragment on validation failure. Existing listeners,
|
|
234
|
+
authentication and firewall rules are preserved. Existing socket activation is preserved for forwarding-only changes; listener
|
|
235
|
+
migrations and unmanaged fragments require review. The manager makes a **new**
|
|
236
|
+
strict SSH connection after apply. Private-listener/firewall acceptance and
|
|
237
|
+
failure-after-reload recovery still require disposable integration testing.
|
|
238
|
+
|
|
239
|
+
The manager is the only configuration coordinator. No per-node pull timer is
|
|
240
|
+
installed. Manager schedule controls use the existing platform adapter:
|
|
241
|
+
|
|
242
|
+
```sh
|
|
243
|
+
devfleet fleet-sync --schedule status --json
|
|
244
|
+
devfleet fleet-sync --schedule enable --revision FULL_40_CHARACTER_SHA --apply --yes
|
|
245
|
+
devfleet fleet-sync --schedule pause --apply --yes
|
|
246
|
+
devfleet fleet-sync --schedule resume --revision FULL_40_CHARACTER_SHA --apply --yes
|
|
247
|
+
devfleet fleet-sync --schedule disable --apply --yes
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
These are manager setup/maintenance operations, not per-node commands. Enable
|
|
251
|
+
and resume install the exact published CLI version in the managed manager
|
|
252
|
+
runtime and schedule the same whole-fleet command every 30 minutes at the explicit
|
|
253
|
+
approved SHA. They refuse dirty artifacts. Pause/disable remove only the manager
|
|
254
|
+
schedule. Re-enable with the new approved SHA when promoting a release; the timer
|
|
255
|
+
does not resolve `latest`. Linux user scheduling requires lingering; macOS uses
|
|
256
|
+
the existing LaunchAgent and requires a user session. Unattended manager boot
|
|
257
|
+
acceptance and log-retention configuration remain gates. No node pull scheduler
|
|
258
|
+
is installed or necessary.
|
|
259
|
+
|
|
260
|
+
Sync never reboots a box and never ends a running agent turn. A worker's T3
|
|
261
|
+
that must restart for a new provider policy or release is restarted only while
|
|
262
|
+
it runs no agent turn; otherwise the restart is pending
|
|
263
|
+
(`T3_POLICY_RESTART_PENDING`, `T3_RESTART_PENDING`) and the recovery timer
|
|
264
|
+
applies it at the first idle check. Explicit
|
|
265
|
+
maintenance reboot/reconnect orchestration remains an acceptance gate. Kernel,
|
|
266
|
+
driver, firmware, hardware and power failures are outside any “never freezes”
|
|
267
|
+
promise. Inspect disk/inodes, swap/memory pressure, `systemctl --failed`, and prior
|
|
268
|
+
boot journal evidence locally; sanitize it before sharing. Do not enable automatic
|
|
269
|
+
reboot-on-pressure or kill active work to obtain a green report.
|
|
270
|
+
|
|
271
|
+
T3 0.1.7's supported administrator policy at `/etc/t3code/policy.json`
|
|
272
|
+
disables independent updates and declares the existing managed Codex instance
|
|
273
|
+
plus an OpenCode instance for all five gateway models. Fleet reuses the
|
|
274
|
+
existing `agents/t3.json` renderer; it does not edit the live settings database.
|
|
275
|
+
Native `t3 policy check` validates before installation, and `t3 policy show`
|
|
276
|
+
compares staged policy with the running server. A staged policy the running
|
|
277
|
+
server has not loaded restarts the T3 desktop app in the same sync;
|
|
278
|
+
`T3_POLICY_RESTART_FAILED` means it still did not load afterwards.
|
|
279
|
+
Unrelated administrator policy is a conflict requiring a reviewed merge.
|
|
280
|
+
|
|
281
|
+
## Central provider access
|
|
282
|
+
|
|
283
|
+
The `https://cli-proxy.example.com` gateway is a singleton service, so it
|
|
284
|
+
runs on the manager (`definition.json.services.proxy`). Workers consume that
|
|
285
|
+
gateway; no worker gets a separate OAuth-owning proxy. Keep the existing
|
|
286
|
+
key/endpoint contract, encrypted recovery and runtime authentication. Moving it
|
|
287
|
+
requires fencing the old host before restoring state
|
|
288
|
+
([migration](../cliproxy/docs/migration.md)). Fleet never copies identities.
|
|
289
|
+
|
|
290
|
+
### Agents on every node
|
|
291
|
+
|
|
292
|
+
Every node (manager and workers) offers the same agents in T3, at the same
|
|
293
|
+
versions (pinned in `mise.toml`/`mise.lock`, including the vendor CLIs
|
|
294
|
+
`http:grok` and `http:antigravity`), signed in to the same multiplexed proxy
|
|
295
|
+
accounts. T3 launches most through a tracked launcher in
|
|
296
|
+
`agents/` that injects the shared proxy key from Vault at
|
|
297
|
+
launch; nothing is copied between nodes.
|
|
298
|
+
|
|
299
|
+
| T3 provider | T3 driver | Route |
|
|
300
|
+
| ----------------------------- | ------------- | --------------------------------------------------------------- |
|
|
301
|
+
| `Claude <account email>` | `claudeAgent` | One per Claude account on the proxy (`claude-proxy`) |
|
|
302
|
+
| `Codex <account email>` | `codex` | One per Codex account on the proxy (`codex-proxy.ts`) |
|
|
303
|
+
| `AntiGravity <account email>` | `opencode` | One per Antigravity account, via OpenCode (`opencode-proxy.ts`) |
|
|
304
|
+
| `Grok CLI Proxy` | `grok` | Pooled: Grok Build custom model, `env_key` (`grok-proxy.ts`) |
|
|
305
|
+
| `OpenCode CLI Proxy` | `opencode` | Pooled accounts and Muse via OpenCode (`opencode-proxy.ts`) |
|
|
306
|
+
|
|
307
|
+
**Ansible applies, TypeScript measures.** On a worker the T3 lifecycle is
|
|
308
|
+
declared in playbooks: `t3-policy.yml` stages the provider policy and restarts
|
|
309
|
+
a running T3 that has not loaded it once it runs no agent turn, `t3-desktop.yml` installs the app, retires
|
|
310
|
+
the former headless server and starts it, and `t3-route.yml` publishes the
|
|
311
|
+
private tailnet route when port 443 has no owner. The TypeScript around them
|
|
312
|
+
only refuses conflicts and reports what it observes afterwards. Each sync also
|
|
313
|
+
prunes old revision checkouts on the worker, keeping the new one, the one T3
|
|
314
|
+
launches from and the two most recent others.
|
|
315
|
+
|
|
316
|
+
**Every sync applies everything, except a T3 restart that would end a turn.**
|
|
317
|
+
A worker whose T3 has not loaded the staged provider policy, or runs an older
|
|
318
|
+
release than the one installed, is restarted only while it runs no agent turn;
|
|
319
|
+
otherwise sync reports the pending restart and the recovery timer applies it
|
|
320
|
+
at the first idle check, failing the check after
|
|
321
|
+
`t3RestartMaxDeferralHours` (`ansible/ubuntu/policy.json`). A pending proxy
|
|
322
|
+
update is installed even when it recreates the gateway. The manager finishes before the workers
|
|
323
|
+
start, so they are verified against the updated proxy. `--dry-run` previews.
|
|
324
|
+
|
|
325
|
+
**Accounts are never listed in this repository.** The proxy's credential files
|
|
326
|
+
are the only source of truth: every sync gives each Claude, Codex and
|
|
327
|
+
Antigravity credential the model prefix `<type>-<email>`
|
|
328
|
+
(`cliproxy/scripts/accounts.py`, applied through the proxy's own
|
|
329
|
+
API), so the proxy's model list names every signed-in account. Each node reads
|
|
330
|
+
that list with its own proxy key and offers one provider per account, so all
|
|
331
|
+
machines list the same providers. Signing an account in or out on the proxy
|
|
332
|
+
(`devfleet cliproxy add-account PROVIDER`, or the dashboard) changes T3 on the
|
|
333
|
+
next `fleet-sync`; an unreachable proxy fails the check and changes nothing.
|
|
334
|
+
|
|
335
|
+
A provider is pinned to its account: T3 sets `WS_AGENT_ACCOUNT` and the
|
|
336
|
+
launcher routes every request to `<type>-<email>/<model>`. Claude and Codex
|
|
337
|
+
send whichever model is picked, so their launcher runs a loopback relay
|
|
338
|
+
(`account-relay.ts`, alive only while that agent runs) that adds the prefix;
|
|
339
|
+
OpenCode is configured with exactly the account's models. Requests without a
|
|
340
|
+
prefix (Grok, the pooled OpenCode provider, health checks) still use the whole
|
|
341
|
+
pool, fill-first. T3's own Antigravity runtime cannot carry an account, so
|
|
342
|
+
Antigravity accounts run on the OpenCode harness. Cursor is not part of the
|
|
343
|
+
fleet (its backend cannot be proxied). Native Muse Code is not used: it only
|
|
344
|
+
talks to Meta's own catalog endpoint, so Muse models run through OpenCode.
|
|
345
|
+
|
|
346
|
+
### Checkout project actions
|
|
347
|
+
|
|
348
|
+
`bash bootstrap/project-action.sh fleet-sync` warms the shared toolchain
|
|
349
|
+
and passes the current checkout HEAD as the exact revision to the existing approval
|
|
350
|
+
flow. Extra arguments such as `--dry-run` are forwarded. The manager can bundle
|
|
351
|
+
committed objects from this canonical local repository, including unpublished
|
|
352
|
+
commits; dirty working files are never transferred. Review and commit desired
|
|
353
|
+
changes before approving deployment. The Ubuntu baseline now owns the native
|
|
354
|
+
Docker engine, Compose and Buildx on every enrolled Linux workstation, preserving
|
|
355
|
+
third-party engine ownership conflicts and existing running workloads. Workstation
|
|
356
|
+
launcher/link convergence is part of the node apply, with credentials still checked
|
|
357
|
+
separately. A dependency receipt proves frozen installation inputs only.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
> This document was written for the fleet dev-fleet was extracted from. Paths and
|
|
2
|
+
> commands were updated; some statements still describe that fleet and are being
|
|
3
|
+
> revised. The README is authoritative.
|
|
4
|
+
|
|
5
|
+
# Declarative remote workers
|
|
6
|
+
|
|
7
|
+
The public `devfleet` CLI owns two primary operations:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
devfleet fleet-sync
|
|
11
|
+
devfleet fleet-status --workers
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The manager needs Node/npm, Git, SSH, Tailscale, and its own authorized T3 link.
|
|
15
|
+
Initially each Ubuntu 26.04 worker (or best-effort macOS 14+ node) needs a
|
|
16
|
+
designated non-root account, trusted SSH access and administrator authorization. Standard Ubuntu cloud-image users
|
|
17
|
+
already commonly have noninteractive sudo; otherwise establish the repository's
|
|
18
|
+
`authorize-sudo.sh` policy once during administrator enrollment. Routine commands
|
|
19
|
+
never ask for or store an SSH/sudo password. Unknown host keys, a different device
|
|
20
|
+
identity, or missing privilege stop with a named action rather than bypassing trust.
|
|
21
|
+
|
|
22
|
+
Convergence binds one reviewed revision and worker membership. It transfers only
|
|
23
|
+
committed Git objects over SSH, bootstraps missing prerequisites, installs the
|
|
24
|
+
locked minimal tool profile and frozen dependencies, and applies Fleet's
|
|
25
|
+
Ansible policy. Developer checkouts and dirty work stay intact. A published CLI
|
|
26
|
+
uses its source provenance; a source project action explicitly selects checkout
|
|
27
|
+
HEAD. Private rosters contain connection paths and identities, not weaker policy.
|
|
28
|
+
|
|
29
|
+
| Requirement | Declaration and enforcement |
|
|
30
|
+
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
31
|
+
| One consistent dependency set | `mise.toml` worker profile, `mise.lock`, Ubuntu snapshot/package pins; exact versions and deployed input hashes checked live |
|
|
32
|
+
| Common service access | Canonical required capabilities; declared GitHub account, worker's own renewable Vault session, authenticated singleton gateway catalog |
|
|
33
|
+
| One gateway host | The CLI proxy runs only on the manager; ownership/audit/recovery code rejects any install on a worker and missing singleton state |
|
|
34
|
+
| T3 connected and persistent | Pinned desktop app as the only T3 process, auto-login session unit enabled/active with restart policy, bounded recovery timer, private endpoint, manager link matched to SSH-observed environment identity |
|
|
35
|
+
| No idle sleep and session survival | systemd sleep masks, logind policy, designated-user lingering; running policy and boot timing checked, not inferred from files alone |
|
|
36
|
+
| Recovery after power loss | Required boot services and recovery timers; recognized firmware restore-after-AC settings inspected when exposed; macOS `autorestart` where supported |
|
|
37
|
+
| Always-on services | `Restart=always` drop-ins for ssh, tailscaled, docker and beszel-agent (launchd `KeepAlive` on macOS); health requires the running unit to report that policy |
|
|
38
|
+
| Central dashboard | Pinned Beszel hub on the manager, every worker enrolled with its own token; health verifies hub reachability, registration, connection and fresh samples |
|
|
39
|
+
| No routine password prompts | Verified BatchMode SSH, `sudo -n`, source-owned NOPASSWD policy; missing initial authorization is a failure |
|
|
40
|
+
| Keep service authorization alive | Native service credentials stay per-node; Vault self-renewal timer and GitHub recovery from authorized Vault credentials; expiring T3 links are reported before expiry |
|
|
41
|
+
|
|
42
|
+
Health does not mutate configuration, renew tokens, log in, install software, or
|
|
43
|
+
make billable inference requests. It audits the immutable desired worker deployment,
|
|
44
|
+
not an unrelated developer branch. Every declared worker must be enrolled and
|
|
45
|
+
reachable. Extra service hosts are listed as outside scope. Required failures,
|
|
46
|
+
warnings and unverified checks prevent a ready result; explicitly non-required
|
|
47
|
+
checks remain visible without inventing a pass.
|
|
48
|
+
|
|
49
|
+
The desired fleet contract is stronger than today's live acceptance. A real
|
|
50
|
+
power-loss test and firmware/VM-host automatic restart cannot be proved merely by
|
|
51
|
+
starting an OS service. Unsupported firmware inspection remains unverified. A
|
|
52
|
+
still-running headless `t3code.service` needs a deliberate migration to the
|
|
53
|
+
desktop app; convergence preserves active work instead of killing it. External providers can
|
|
54
|
+
revoke authorization; renewal is not a substitute for an initially granted,
|
|
55
|
+
renewable node identity. Required expired/missing credentials remain failures,
|
|
56
|
+
never interactive password prompts or successful receipts.
|
|
57
|
+
|
|
58
|
+
Cloud resources, Vault roles/policies and secret values remain exclusively owned
|
|
59
|
+
by the OpenTofu roots. Worker configuration belongs to Fleet's Ansible policy and
|
|
60
|
+
mise. Runtime logins/renewal do not provision cloud resources. Existing SSH keys,
|
|
61
|
+
T3 pairing data, provider identities and the gateway's encrypted recovery state
|
|
62
|
+
are never cloned from another node.
|
|
63
|
+
|
|
64
|
+
The normal manager apply flow reports configuration application separately
|
|
65
|
+
from readiness and advances the applied-revision receipt only after required
|
|
66
|
+
acceptance succeeds. Read the remaining release gates in [ubuntu-sync.md](ubuntu-sync.md)
|
|
67
|
+
before treating any release as complete fleet certification.
|
package/examples/fleet.json
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
"source": {
|
|
5
5
|
"repository": "https://github.com/crvouga/dev-fleet.git"
|
|
6
6
|
},
|
|
7
|
+
"state": {
|
|
8
|
+
"bucket": "my-fleet-state",
|
|
9
|
+
"endpoint": "https://s3.example.com",
|
|
10
|
+
"region": "auto"
|
|
11
|
+
},
|
|
7
12
|
"secrets": {
|
|
8
13
|
"provider": "vault",
|
|
9
14
|
"address": "https://vault.example.com",
|
package/llms.txt
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# dev-fleet
|
|
2
|
+
|
|
3
|
+
> dev-fleet keeps a set of development machines in sync: one macOS **manager** and any number of Ubuntu 26.04 **workers** reached over SSH and Tailscale. It is used as the npm package `@crvouga/dev-fleet` (CLI `devfleet`) from a project of your own that holds `fleet.json` and pins the package version; one command, `devfleet fleet-sync`, converges every machine to the commit that version was published from. The fleet state lives in an S3-compatible bucket you own.
|
|
4
|
+
|
|
5
|
+
Rules for an agent working in a project that uses dev-fleet:
|
|
6
|
+
|
|
7
|
+
- Never clone, vendor or patch dev-fleet, and never edit what it installs on a machine. A defect or missing feature is a public issue at https://github.com/crvouga/dev-fleet/issues (no credentials, hostnames, accounts, domains or `fleet.json` contents).
|
|
8
|
+
- Never put a secret in an argument, `fleet.json`, a commit or a log. Bucket credentials come from `DEVFLEET_BUCKET_ACCESS_KEY_ID` and `DEVFLEET_BUCKET_SECRET_ACCESS_KEY`; other secrets from the provider `fleet.json` names (`vault`, `sops`, `file` or `env`).
|
|
9
|
+
- `npx devfleet commands --json` prints the machine-readable contract: commands, exit codes, safety rules, how to report a bug. Read it first; pass `--json --non-interactive` to every diagnostic.
|
|
10
|
+
- `--dry-run`, `fleet-status` and `fleet-state` (without flags) are read-only. `fleet-sync` changes every machine, and its first run must be done by a human at a terminal (host enrollment, sudo, Vault).
|
|
11
|
+
- The installed package carries these documents under `node_modules/@crvouga/dev-fleet/`.
|
|
12
|
+
|
|
13
|
+
## Set up a fleet
|
|
14
|
+
|
|
15
|
+
- [Set up a fleet from your own project](https://github.com/crvouga/dev-fleet/blob/main/docs/consumer-setup.md): the one-pass procedure for an agent: prerequisites and how to verify them, what to ask the human, every `fleet.json` field, secrets, bucket credentials, the first sync, verification and failure codes
|
|
16
|
+
- [README](https://github.com/crvouga/dev-fleet/blob/main/README.md): what dev-fleet assumes, quickstart, versions, adding a machine, known limits
|
|
17
|
+
- [Example fleet.json](https://github.com/crvouga/dev-fleet/blob/main/examples/fleet.json): a complete configuration with placeholders (`npx devfleet fleet-state --example` prints it)
|
|
18
|
+
- [fleet.json schema](https://github.com/crvouga/dev-fleet/blob/main/schema/fleet.schema.json): JSON Schema for the fleet configuration
|
|
19
|
+
|
|
20
|
+
## Operate it
|
|
21
|
+
|
|
22
|
+
- [Fleet state](https://github.com/crvouga/dev-fleet/blob/main/docs/state.md): the bucket, replication, replacing the manager, what the bucket does not hold yet
|
|
23
|
+
- [Topology](https://github.com/crvouga/dev-fleet/blob/main/docs/topology.md): the manager and worker roles and the canary
|
|
24
|
+
- [What is declared, and where](https://github.com/crvouga/dev-fleet/blob/main/docs/declared.md): what is pinned on a machine and what is not
|
|
25
|
+
- [Provider accounts](https://github.com/crvouga/dev-fleet/blob/main/docs/accounts.md): the account roster, its states and checks, adding or removing an account, propagation to every node, account selection
|
|
26
|
+
- [CLI proxy](https://github.com/crvouga/dev-fleet/blob/main/cliproxy/README.md): the CLIProxyAPI service on the manager that every coding agent uses
|
|
27
|
+
- [Monitoring](https://github.com/crvouga/dev-fleet/blob/main/ansible/monitoring/README.md): the Beszel hub on the manager and the agent on every machine
|
|
28
|
+
|
|
29
|
+
## Optional
|
|
30
|
+
|
|
31
|
+
- [Ubuntu fleet-sync reference](https://github.com/crvouga/dev-fleet/blob/main/docs/ubuntu-sync.md): enrollment, approval, machine policy by platform, remote-work safety
|
|
32
|
+
- [Worker contract](https://github.com/crvouga/dev-fleet/blob/main/docs/worker-contract.md): what a worker guarantees and how readiness is scoped
|
|
33
|
+
- [Operations](https://github.com/crvouga/dev-fleet/blob/main/docs/operations.md): roster, diagnostics and repairs
|
|
34
|
+
- [Coding agents on every node](https://github.com/crvouga/dev-fleet/blob/main/agents/setup.md): agent launchers and the T3 providers
|
|
35
|
+
- [Releasing](https://github.com/crvouga/dev-fleet/blob/main/docs/releasing.md): how a version is published from one commit (for dev-fleet developers)
|
|
36
|
+
- [Features](https://github.com/crvouga/dev-fleet/blob/main/features/README.md): the behaviour specified in Gherkin (for dev-fleet developers)
|
package/mise.lock
CHANGED
|
@@ -924,6 +924,35 @@ url = "https://dl.k8s.io/v1.37.2/bin/darwin/amd64/kubectl"
|
|
|
924
924
|
checksum = "sha256:1691938f7de0e931969f9968c3601cf32d6b0d573573afb4ce471f3d28f51984"
|
|
925
925
|
url = "https://dl.k8s.io/v1.37.2/bin/windows/amd64/kubectl.exe"
|
|
926
926
|
|
|
927
|
+
[[tools.litestream]]
|
|
928
|
+
version = "0.5.18"
|
|
929
|
+
backend = "aqua:benbjohnson/litestream"
|
|
930
|
+
specifiers = ["0.5.18"]
|
|
931
|
+
|
|
932
|
+
[tools.litestream."platforms.linux-arm64"]
|
|
933
|
+
checksum = "sha256:922a09e6a7ac67f2095571894dc6382e94e1dd4cdd9674883ddf1a8e74ab982d"
|
|
934
|
+
checksum_source = "mise-versions"
|
|
935
|
+
url = "https://github.com/benbjohnson/litestream/releases/download/v0.5.18/litestream-0.5.18-linux-arm64.tar.gz"
|
|
936
|
+
url_api = "https://api.github.com/repos/benbjohnson/litestream/releases/assets/706493661"
|
|
937
|
+
|
|
938
|
+
[tools.litestream."platforms.linux-x64"]
|
|
939
|
+
checksum = "sha256:d3687b9fd0f2e9426bbd3da828429ff3f1dfc5c9b74d54fdf028e4ee510e7dea"
|
|
940
|
+
checksum_source = "mise-versions"
|
|
941
|
+
url = "https://github.com/benbjohnson/litestream/releases/download/v0.5.18/litestream-0.5.18-linux-x86_64.tar.gz"
|
|
942
|
+
url_api = "https://api.github.com/repos/benbjohnson/litestream/releases/assets/706493670"
|
|
943
|
+
|
|
944
|
+
[tools.litestream."platforms.macos-arm64"]
|
|
945
|
+
checksum = "sha256:6d3e0875e5416628ef3d274e8f7bb0ecc2a19cbbe76b9ba43efe9901d82a5c84"
|
|
946
|
+
checksum_source = "mise-versions"
|
|
947
|
+
url = "https://github.com/benbjohnson/litestream/releases/download/v0.5.18/litestream-0.5.18-darwin-arm64.tar.gz"
|
|
948
|
+
url_api = "https://api.github.com/repos/benbjohnson/litestream/releases/assets/706493582"
|
|
949
|
+
|
|
950
|
+
[tools.litestream."platforms.macos-x64"]
|
|
951
|
+
checksum = "sha256:25846be4fc4a4e0b4fd9ac5d0e019c10ec70a4dc12fe84255f5607b8b9a520e0"
|
|
952
|
+
checksum_source = "mise-versions"
|
|
953
|
+
url = "https://github.com/benbjohnson/litestream/releases/download/v0.5.18/litestream-0.5.18-darwin-x86_64.tar.gz"
|
|
954
|
+
url_api = "https://api.github.com/repos/benbjohnson/litestream/releases/assets/706493692"
|
|
955
|
+
|
|
927
956
|
[[tools.ninja]]
|
|
928
957
|
version = "1.13.2"
|
|
929
958
|
backend = "aqua:ninja-build/ninja"
|