@crvouga/dev-fleet 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +237 -37
  2. package/agents/setup.md +42 -12
  3. package/ansible/macos/manager.yml +7 -7
  4. package/ansible/macos/policy.json +2 -10
  5. package/ansible/monitoring/README.md +86 -25
  6. package/ansible/monitoring/agent.yml +40 -2
  7. package/ansible/monitoring/hub-route-darwin.yml +51 -0
  8. package/ansible/monitoring/lock.json +5 -0
  9. package/ansible/pyproject.toml +12 -0
  10. package/ansible/ubuntu/baseline.yml +1 -1
  11. package/ansible/ubuntu/disk-gc.py +336 -0
  12. package/ansible/ubuntu/policy.json +12 -3
  13. package/ansible/ubuntu/tailscale.yml +8 -5
  14. package/ansible/ubuntu/tasks/resources.yml +61 -1
  15. package/ansible/uv.lock +190 -0
  16. package/bootstrap/bootstrap.sh +25 -18
  17. package/bootstrap/doctor.ts +46 -1
  18. package/bootstrap/fleet-manager-run.sh +42 -15
  19. package/bootstrap/fleet-remote-bootstrap.sh +85 -12
  20. package/bootstrap/global-config.ts +64 -6
  21. package/bootstrap/install-launcher.sh +3 -0
  22. package/bootstrap/npm.ts +10 -2
  23. package/bootstrap/release-version.ts +157 -0
  24. package/docs/consumer-setup.md +408 -0
  25. package/docs/declared.md +89 -0
  26. package/docs/machine-setup.md +399 -0
  27. package/docs/operations.md +276 -0
  28. package/docs/releasing.md +103 -0
  29. package/docs/state.md +254 -0
  30. package/docs/topology.md +67 -0
  31. package/docs/ubuntu-sync.md +351 -0
  32. package/docs/worker-contract.md +67 -0
  33. package/examples/fleet.json +5 -0
  34. package/llms.txt +35 -0
  35. package/mise.lock +29 -0
  36. package/mise.toml +12 -1
  37. package/package.json +11 -2
  38. package/schema/fleet.schema.json +71 -15
  39. package/src/agents/fleet-verify.ts +9 -0
  40. package/src/agents/native-verify.ts +167 -22
  41. package/src/agents/provider-errors.ts +9 -2
  42. package/src/agents/registry.ts +7 -1
  43. package/src/cli/agent-guide.ts +39 -5
  44. package/src/cli/app.ts +11 -4
  45. package/src/cli/cli.ts +7 -0
  46. package/src/cli/command-index.ts +7 -0
  47. package/src/cli/fleet-state.ts +499 -0
  48. package/src/cli/fleet-sync-checkout.ts +56 -17
  49. package/src/cli/fleet-sync-enrollment.ts +19 -23
  50. package/src/cli/fleet-sync-forward.ts +1 -1
  51. package/src/cli/fleet-sync-manager.ts +64 -8
  52. package/src/cli/fleet-sync-monitoring.ts +5 -5
  53. package/src/cli/fleet-sync-output.ts +4 -1
  54. package/src/cli/fleet-sync-remote.ts +11 -5
  55. package/src/cli/fleet-sync-state.ts +86 -0
  56. package/src/cli/fleet-sync.ts +123 -57
  57. package/src/cli/fleet.ts +4 -2
  58. package/src/cli/managed-health.ts +4 -4
  59. package/src/cli/node-sync.ts +7 -0
  60. package/src/cli/program.ts +20 -4
  61. package/src/cli/project.ts +51 -0
  62. package/src/cli/register-fleet.ts +51 -1
  63. package/src/cli/registry.ts +28 -1
  64. package/src/cli/roster.ts +46 -30
  65. package/src/cli/settings.ts +15 -12
  66. package/src/cli/source-operations.ts +27 -3
  67. package/src/cli/tailnet.ts +2 -2
  68. package/src/definition.ts +31 -1
  69. package/src/diagnostics.ts +1 -1
  70. package/src/fleet-config.ts +105 -19
  71. package/src/machine/source.ts +3 -3
  72. package/src/machine/state.ts +15 -27
  73. package/src/manager-sync.ts +8 -3
  74. package/src/monitoring/agent-evidence.ts +146 -0
  75. package/src/monitoring/apply.ts +15 -3
  76. package/src/monitoring/beszel.ts +11 -4
  77. package/src/monitoring/enroll.ts +2 -1
  78. package/src/monitoring/hub-access.ts +4 -3
  79. package/src/monitoring/hub-backup.ts +121 -0
  80. package/src/monitoring/hub-viewer.ts +8 -8
  81. package/src/monitoring/hub.ts +3 -3
  82. package/src/monitoring/manager-hub.ts +30 -14
  83. package/src/node-converge-linux.ts +2 -0
  84. package/src/package-info.ts +45 -0
  85. package/src/platform/ansible.ts +19 -15
  86. package/src/platform/resource-verify.ts +8 -4
  87. package/src/platform/ubuntu-audit.ts +5 -2
  88. package/src/remote-bootstrap.ts +17 -19
  89. package/src/repair-catalog.ts +1 -1
  90. package/src/runner.ts +1 -1
  91. package/src/state/bucket.ts +208 -0
  92. package/src/state/check.ts +74 -0
  93. package/src/state/connection.ts +92 -0
  94. package/src/state/enrollment.ts +45 -0
  95. package/src/state/legacy.ts +89 -0
  96. package/src/state/litestream.ts +143 -0
  97. package/src/state/operator.ts +20 -0
  98. package/src/state/paths.ts +40 -0
  99. package/src/state/roster.ts +62 -0
  100. package/src/state/store.ts +169 -0
  101. package/src/state/sync.ts +352 -0
  102. package/src/t3-endpoint.ts +64 -8
  103. package/src/t3-linked-check.ts +23 -1
  104. package/src/toolchain-checks.ts +25 -0
  105. package/src/types.ts +4 -0
  106. package/src/worker-access-checks.ts +56 -3
  107. package/src/worker-host-checks.ts +5 -0
  108. package/src/workstation-checks.ts +1 -1
  109. package/src/index.ts +0 -6
  110. package/src/test-fixtures.ts +0 -36
package/README.md CHANGED
@@ -10,14 +10,40 @@ Status: extracted from a working three-machine fleet and in daily use there.
10
10
  It has not yet been set up from nothing by a second person; expect rough edges
11
11
  and read [what is assumed](#what-it-assumes) first.
12
12
 
13
+ ## How you use it
14
+
15
+ You do not clone this repository. dev-fleet is a command you install, and your
16
+ fleet is a small project of your own:
17
+
18
+ ```
19
+ my-fleet/ your repository
20
+ fleet.json your machines, your services, the bucket that holds the state
21
+ package.json pins the dev-fleet version every machine runs
22
+ ```
23
+
24
+ You run `devfleet` from inside that project and pass it the credentials of a
25
+ storage bucket you own. Nothing else has to be kept:
26
+
27
+ | What | Where it lives |
28
+ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
29
+ | The fleet configuration | `fleet.json`, in your repository |
30
+ | The version of dev-fleet | The `@crvouga/dev-fleet` dependency in your `package.json` |
31
+ | The fleet state: the enrolled workers, the approved revision, the configuration applied | Your bucket (any S3-compatible service) |
32
+ | Secrets | The provider `fleet.json` names (`vault`, `sops`, `file` or `env`) |
33
+ | The source, checkouts, tools and reports dev-fleet puts on a machine | A cache: delete it and `fleet-sync` rebuilds it |
34
+
35
+ The CLI proxy and the monitoring hub on the manager still keep data of their
36
+ own; [known limits](#known-limits) says what is lost with the manager.
37
+
13
38
  ## What it assumes
14
39
 
15
40
  dev-fleet is opinionated. These are part of it, not options:
16
41
 
17
- - The **manager** is a macOS machine. It runs `fleet-sync` and hosts the
18
- shared services (the CLI proxy and the monitoring hub) in Docker Desktop.
42
+ - The **manager** is a macOS machine. You run `fleet-sync` on it, and it hosts
43
+ the shared services (the CLI proxy and the monitoring hub) in Docker Desktop.
19
44
  - **Workers** are Ubuntu 26.04 machines reached over SSH. They are stateless:
20
45
  everything on them is rebuilt from Git and your secrets.
46
+ - The fleet state is kept in an **S3-compatible bucket** you own.
21
47
  - Machines reach each other over **Tailscale**.
22
48
  - Coding agents (Claude Code, Codex, Grok, OpenCode, Antigravity) run through
23
49
  **CLIProxyAPI** on the manager, and **T3 Code** lists one provider per
@@ -25,58 +51,149 @@ dev-fleet is opinionated. These are part of it, not options:
25
51
  - Machine policy is **Ansible** (`ansible/`), tool versions are **mise** pins
26
52
  (`mise.toml`, `mise.lock`), monitoring is **Beszel**.
27
53
 
54
+ What dev-fleet puts on a machine is written in tracked files and pinned to a
55
+ version or digest. [What is declared, and where](docs/declared.md) lists it,
56
+ and what is not pinned yet.
57
+
28
58
  What it does not assume is anything about you: no account, hostname, domain,
29
59
  tailnet or repository is written in this code. They live in your `fleet.json`.
30
60
 
31
61
  ## Quickstart
32
62
 
33
- On the manager (macOS, with Git, Docker Desktop and Tailscale signed in):
63
+ Setting it up with a coding agent? Point it at
64
+ [`llms.txt`](llms.txt) or straight at
65
+ [Set up a fleet from your own project](docs/consumer-setup.md): the whole
66
+ procedure in one pass, including what the agent must ask you for and the
67
+ first sync only you can run. Both ship in the package, under
68
+ `node_modules/@crvouga/dev-fleet/`.
69
+
70
+ On the manager you need macOS with Git, Node 22 or newer, Docker Desktop and
71
+ Tailscale signed in. You also need an empty private bucket on any
72
+ S3-compatible service (AWS S3, Cloudflare R2, Backblaze B2, MinIO) and an
73
+ access key that can read and write it.
34
74
 
35
75
  ```sh
36
- git clone https://github.com/crvouga/dev-fleet.git ~/.local/share/dev-fleet/source
37
- cd ~/.local/share/dev-fleet/source
38
- bash bootstrap/bootstrap.sh # installs the pinned toolchain with mise
76
+ mkdir my-fleet && cd my-fleet && git init
77
+ npm init -y
78
+ npm install --save-dev --save-exact @crvouga/dev-fleet
39
79
 
40
- mkdir -p ~/.config/dev-fleet
41
- cp examples/fleet.json ~/.config/dev-fleet/fleet.json
42
- $EDITOR ~/.config/dev-fleet/fleet.json # your machines, endpoints and secrets
80
+ npx devfleet fleet-state --example > fleet.json
81
+ $EDITOR fleet.json # your machines, and your bucket under "state"
43
82
 
44
- export DEVFLEET_REPO=$PWD
45
- sh bootstrap/fleet-manager-run.sh "$(git rev-parse HEAD)" --dry-run # preview
46
- sh bootstrap/fleet-manager-run.sh "$(git rev-parse HEAD)" # apply
83
+ export DEVFLEET_BUCKET_ACCESS_KEY_ID=…
84
+ export DEVFLEET_BUCKET_SECRET_ACCESS_KEY=…
85
+ npx devfleet fleet-sync --dry-run # preview
86
+ npx devfleet fleet-sync # apply
47
87
  ```
48
88
 
49
- `fleet-sync` converges the manager first, then every worker over SSH: it sends
50
- the pinned revision and your configuration, installs the toolchain, applies
51
- the Ansible policy and verifies the result. It prints one table and writes a
52
- full JSON report.
89
+ Commit `fleet.json`, `package.json` and the lockfile. Keep the two credentials
90
+ out of Git: pass them from wherever you keep secrets (a password manager's
91
+ `run` command, a git-ignored `.env`, your shell). They are read from the
92
+ environment and never taken from arguments.
93
+
94
+ `fleet-sync` converges the manager first, then every worker over SSH. It
95
+ fetches the commit your dev-fleet version was published from, sends it and
96
+ your configuration to each machine, installs the pinned toolchain, applies the
97
+ Ansible policy and verifies the result. It prints one table and writes a full
98
+ JSON report. The first run on a machine installs everything, mise included, so
99
+ Git is the only tool it needs beforehand.
100
+
101
+ `--dry-run` changes no machine. On the manager it checks the release out into
102
+ dev-fleet's cache and installs the Bun that release pins beside any others, so
103
+ that it can run; it never installs the toolchain, publishes the global mise
104
+ configuration, edits shell profiles or touches a worker.
105
+
106
+ `devfleet` finds the closest `fleet.json` in the directory you run it from or
107
+ above it. `DEVFLEET_CONFIG` names a file elsewhere.
53
108
 
54
- Most people keep `fleet.json` in their own repository and point at it:
109
+ The CLI runs on [Bun](https://bun.sh), which the package brings as a
110
+ dependency. If your package manager does not run dependency install scripts,
111
+ allow Bun's or install Bun yourself; the CLI uses the one on `PATH`.
112
+
113
+ ## The version
114
+
115
+ Every release of `@crvouga/dev-fleet` is published from one commit and names
116
+ it. The version in your `package.json` is therefore the revision of every
117
+ machine in the fleet:
55
118
 
56
119
  ```sh
57
- DEVFLEET_CONFIG=/path/to/your/fleet.json sh bootstrap/fleet-manager-run.sh REVISION
120
+ npx devfleet --version # 1.4.0 (commit 3f2a…)
121
+ npm install --save-dev --save-exact @crvouga/dev-fleet@latest
122
+ npx devfleet fleet-sync # the fleet moves to that release
58
123
  ```
59
124
 
60
- Pin `REVISION` to a full commit SHA of this repository; that is your version.
125
+ Install an older version and run `fleet-sync` to go back. The first worker
126
+ proves a new revision before the others receive it.
127
+
128
+ ## Your project's own tools
129
+
130
+ On a fleet machine the global mise configuration (`~/.config/mise/config.toml`
131
+ and its `mise.lock`) belongs to dev-fleet: `fleet-sync` publishes it with the
132
+ fleet's pinned tools and the agent worktree roots mise must trust. A project
133
+ pins its own tools in its own `mise.toml` and installs them with
134
+ `mise install --locked` in its checkout. It never publishes a global
135
+ configuration. If anything replaces dev-fleet's, `fleet-status` fails the check
136
+ `tool:global-mise-config` until the next `fleet-sync`.
137
+
138
+ ## Using it from code
139
+
140
+ dev-fleet has no library API. The package is the `devfleet` command: it
141
+ declares no `main` or `exports`, and nothing under `src/`, `bootstrap/` or
142
+ `ansible/` is a supported import. Any release, a patch included, may change
143
+ them, and some modules read the fleet configuration as soon as they are
144
+ imported. If your project needs something dev-fleet does, ask for a command in
145
+ an [issue](#bugs-and-feature-requests).
146
+
147
+ ## State
148
+
149
+ Your bucket is the source of truth for everything Git does not hold: the
150
+ roster of enrolled workers, the approved revision, and the configuration
151
+ `fleet-sync` last applied. It is one SQLite database that
152
+ [Litestream](https://litestream.io) replicates to the bucket. `fleet.json`
153
+ says where the bucket is and the command is passed its credentials:
154
+
155
+ ```json
156
+ "state": {
157
+ "bucket": "my-fleet-state",
158
+ "endpoint": "https://ACCOUNT.r2.cloudflarestorage.com",
159
+ "region": "auto"
160
+ }
161
+ ```
61
162
 
62
- ## Run it from npm
163
+ ```sh
164
+ npx devfleet fleet-state # where the state is, and how it stands with the bucket
165
+ ```
166
+
167
+ The copy on the manager is disposable. **To replace the manager**, clone your
168
+ project on the new machine and run the same two commands:
169
+
170
+ ```sh
171
+ npm ci
172
+ npx devfleet fleet-sync # with the two bucket credentials in the environment
173
+ ```
174
+
175
+ The roster and the approved revision come back from the bucket, and the
176
+ machine is rebuilt from Git.
63
177
 
64
- The CLI is also published as `@crvouga/dev-fleet`, for any machine or project
65
- that has Node:
178
+ Some runs are started by a machine rather than by you: a worker that hands
179
+ `fleet-sync` to the manager, for example. They get no environment, so save the
180
+ credentials on the manager once (in `~/.config/dev-fleet/bucket.json`,
181
+ readable by you alone):
66
182
 
67
183
  ```sh
68
- npx @crvouga/dev-fleet commands --json
69
- npx @crvouga/dev-fleet fleet-status --local
184
+ npx devfleet fleet-state --init
70
185
  ```
71
186
 
72
- `fleet-sync` still runs from a clone, because it pins a git revision. See
73
- [releasing](docs/releasing.md) for how versions are published.
187
+ Without a bucket everything still works from the local copy, and `fleet-sync`
188
+ warns that the manager holds the only one. [docs/state.md](docs/state.md)
189
+ explains how replication stays correct, how to move an existing fleet to a
190
+ project, and which state is not in the bucket yet.
74
191
 
75
192
  ## Add or remove a machine
76
193
 
77
194
  1. Set up SSH from the manager to the new Ubuntu box (a non-root user with
78
195
  sudo) and join it to your tailnet.
79
- 2. Add it under `workers` in `fleet.json`:
196
+ 2. Add it under `workers` in your `fleet.json` and commit:
80
197
 
81
198
  ```json
82
199
  {
@@ -92,20 +209,25 @@ npx @crvouga/dev-fleet fleet-status --local
92
209
  }
93
210
  ```
94
211
 
95
- 3. Run `fleet-sync`. A box that has only sudo is enough: the opt-in test
96
- `bun ansible/ubuntu/tests/fresh-box.ts` proves that against a bare
97
- container.
212
+ 3. Run `fleet-sync`. A machine that has only an SSH server and an account
213
+ with sudo is enough: every pull request proves that against a bare
214
+ Ubuntu machine, over SSH
215
+ ([`fresh-box.feature`](features/fresh-box/fresh-box.feature)).
98
216
 
99
- Remove a machine by deleting its entry.
217
+ Remove a machine by deleting its entry and running `fleet-sync`.
100
218
 
101
219
  ## Configuration
102
220
 
103
- `fleet.json` is validated by [`schema/fleet.schema.json`](schema/fleet.schema.json);
104
- [`examples/fleet.json`](examples/fleet.json) is a complete example.
221
+ Your `fleet.json` is validated by
222
+ [`schema/fleet.schema.json`](schema/fleet.schema.json), which the installed
223
+ package carries: `fleet-state --example` prints a complete example that points
224
+ `$schema` at it, so an editor checks the file as you type.
225
+ [`examples/fleet.json`](examples/fleet.json) is the same example.
105
226
 
106
227
  | Block | What it declares |
107
228
  | ---------- | ------------------------------------------------------------------- |
108
229
  | `source` | The Git repository nodes accept code from (this one, or your fork) |
230
+ | `state` | The bucket that holds the fleet state; never its credentials |
109
231
  | `secrets` | Where secrets are kept: `env`, `file`, `sops` or `vault` |
110
232
  | `manager` | The one macOS manager |
111
233
  | `workers` | The Ubuntu workers |
@@ -116,16 +238,62 @@ Secrets are asked for by purpose (`proxy-client-key`,
116
238
  `monitoring-account-password`, `monitoring-tunnel-token`,
117
239
  `proxy-tunnel-token`); see [`src/secrets.ts`](src/secrets.ts).
118
240
 
241
+ `fleet-sync` records the configuration it applies in the fleet state, so a
242
+ run started outside your project uses the one last applied.
243
+ `fleet-state --export` prints it.
244
+
245
+ To run your own fork, publish it as your own package and name its repository
246
+ under `source`: the commit a package names must be reachable there.
247
+
248
+ ## Bugs and feature requests
249
+
250
+ A defect in dev-fleet, or something it cannot do yet, is raised as an
251
+ [issue](https://github.com/crvouga/dev-fleet/issues). From your project that
252
+ is the only way to change dev-fleet, for you and for the coding agents working
253
+ there. Do not patch the installed package, a checkout `fleet-sync` put on a
254
+ machine or a file it manages: the next install or `fleet-sync` replaces them.
255
+
256
+ The tool says so itself: `devfleet --help` names the issue tracker, and the
257
+ agent contract (`devfleet commands --json`, under `agent.support`) carries the
258
+ rule, what an issue should include and what it must leave out. Agents only
259
+ read it if your project sends them there, so put this in the `AGENTS.md` of
260
+ the project that holds your `fleet.json`:
261
+
262
+ ```md
263
+ The fleet is managed with dev-fleet (`npx devfleet`, pinned in package.json).
264
+ Read `npx devfleet commands --json` before using it. Never edit dev-fleet
265
+ itself: not node_modules, not a clone, not what it installs on a machine. A
266
+ bug in dev-fleet or a feature it lacks is a GitHub issue at
267
+ https://github.com/crvouga/dev-fleet/issues; tell me what is blocked and link
268
+ it. Issues are public: no credentials, hostnames, accounts, domains or
269
+ fleet.json contents.
270
+ ```
271
+
119
272
  ## Known limits
120
273
 
121
274
  - Each worker's own login and renewal, and the proxy's backup and recovery,
122
275
  are implemented for the **`vault`** secrets provider only. With `env`, `file`
123
276
  or `sops` the fleet's three shared secrets are read, but those two features
124
277
  are not available yet.
125
- - State on machines still uses the historical `personal` prefix
278
+ - The bucket holds the fleet's own state. The CLI proxy keeps its separate
279
+ encrypted backup (`vault` only), and the monitoring hub's database is not
280
+ replicated: `fleet-sync` recreates its accounts and systems, but metric
281
+ history is lost with the manager. See
282
+ [what is not in the bucket yet](docs/state.md#what-is-not-in-the-bucket-yet).
283
+ - Caches and units on machines still use the historical `personal` prefix
126
284
  (`~/.local/share/personal/fleet`, `/etc/personal-fleet`, unit names). It
127
285
  works, and renaming it needs a migration.
286
+ - The CLI proxy's installed code is a Git worktree of the manager's copy of
287
+ the source repository (`~/.local/share/personal/fleet/manager/source`).
288
+ `fleet-sync` fetches that copy again when it is deleted, but the proxy then
289
+ needs `devfleet cliproxy install` once more.
128
290
  - The manager is a Mac host. There is no container image of the manager.
291
+ - `fleet-sync --schedule enable` (unattended runs on a timer) is not available
292
+ from the published package yet, so nothing re-applies the fleet until you
293
+ run `fleet-sync`.
294
+ - Not everything on a machine is pinned. [What is declared, and
295
+ where](docs/declared.md) lists what is and what is not: most of a worker's
296
+ own packages, the manager's applications and every login are outside it.
129
297
  - The reference documents under `docs/` were written for the fleet this came
130
298
  from and are being revised.
131
299
 
@@ -134,24 +302,56 @@ Secrets are asked for by purpose (`proxy-client-key`,
134
302
  | Path | What |
135
303
  | ------------ | ----------------------------------------------------------------- |
136
304
  | `src/` | Inventory, orchestration, verification, the report; `src/cli` CLI |
305
+ | `src/state/` | The fleet state database and its replication to the bucket |
137
306
  | `ansible/` | Machine policy: `ubuntu/`, `macos/`, `monitoring/` |
138
307
  | `agents/` | Agent launchers T3 runs, and their registry |
139
308
  | `cliproxy/` | The CLIProxyAPI service: install, audit, backup, model health |
140
309
  | `bootstrap/` | Toolchain bootstrap and the manager and worker entry scripts |
141
310
  | `schema/` | JSON schemas for `fleet.json` and the private roster |
142
311
  | `examples/` | A complete example configuration |
312
+ | `features/` | What dev-fleet does, as Gherkin features: the tests |
313
+ | `bin/` | The entry point of the published package |
143
314
 
144
315
  ## Develop
145
316
 
317
+ Working on dev-fleet itself is the only reason to clone it:
318
+
146
319
  ```sh
320
+ git clone https://github.com/crvouga/dev-fleet.git && cd dev-fleet
147
321
  bash bootstrap/bootstrap.sh
148
322
  bun install --frozen-lockfile
149
323
  bun run check
324
+ ./devfleet fleet-status --local # the CLI, from this checkout
150
325
  ```
151
326
 
152
- `bun run check` runs formatting, typecheck, lint, the TypeScript tests and the
153
- proxy package's Python tests. Tests run against `examples/fleet.json` and
154
- never contact a real machine.
327
+ Run from a checkout, `fleet-sync` converges the fleet to the checkout's `HEAD`
328
+ commit (never to uncommitted files). The checkout's `origin` must be the
329
+ repository `fleet.json` names under `source`. Every merge to `main` publishes a release;
330
+ see [releasing](docs/releasing.md).
331
+
332
+ What dev-fleet does is specified in Gherkin, under
333
+ [`features/`](features/README.md): a `.feature` file states a rule in plain
334
+ words and the steps beside it check it. There are no other TypeScript tests.
335
+ `bun run check` runs formatting, typecheck, lint, every feature and the proxy
336
+ package's Python tests. Features run against `examples/fleet.json` and never
337
+ contact a real machine.
338
+
339
+ Every pull request runs these checks ([`ci.yml`](.github/workflows/ci.yml)):
340
+
341
+ | Check | What it proves |
342
+ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
343
+ | Check | `bun run check` |
344
+ | Fleet state | The pinned Litestream replicates the fleet state to a directory and to an S3 API, and a second manager restores it |
345
+ | Fresh worker over SSH | A new Ubuntu machine with only an SSH server and sudo is bootstrapped by the production code over real SSH, on amd64 and on arm64 |
346
+ | Workflows | The workflow files pass `actionlint` |
347
+
348
+ Some scenarios need something a laptop may not have, and are skipped there:
349
+
350
+ ```sh
351
+ DEVFLEET_TEST_FRESH_BOX=1 bun test features/fresh-box # Docker; tests the HEAD commit
352
+ mise install litestream
353
+ DEVFLEET_TEST_S3=http://127.0.0.1:5000 bun test features/state
354
+ ```
155
355
 
156
356
  ## License
157
357
 
package/agents/setup.md CHANGED
@@ -1,3 +1,11 @@
1
+ # Bugs and feature requests
2
+
3
+ This file ships inside the dev-fleet package. If you are an agent working in
4
+ another project and dev-fleet is wrong or lacks something, do not edit it
5
+ here, in a clone or on a machine. Raise an issue in the dev-fleet repository;
6
+ `devfleet --help` prints the address and `devfleet commands --json` carries
7
+ the rule under `agent.support`.
8
+
1
9
  # Manager-led Ubuntu fleet setup
2
10
 
3
11
  For the enrolled Ubuntu development fleet, run on the designated manager:
@@ -16,23 +24,17 @@ The legacy local commands below retain their existing behavior for compatibility
16
24
  Use **personal** for node and T3 configuration. Workstation and fleet are internal
17
25
  implementation packages; neither exports its own executable.
18
26
 
19
- After the shared [machine bootstrap](../../../bootstrap/README.md), run in the
20
- personal checkout:
27
+ `fleet-sync` installs the `devfleet` launcher on every machine it converges,
28
+ linked to the fleet-owned checkout of the applied revision. On such a machine:
21
29
 
22
30
  ```sh
23
- sh bootstrap/install-launcher.sh
24
- devfleet fleet-node-sync
25
31
  devfleet fleet-status
26
32
  ```
27
33
 
28
- The npm fallback is the same interface:
29
-
30
- ```sh
31
- devfleet fleet-node-sync
32
- ```
33
-
34
- Use `PERSONAL_WORKSPACE` or `--workspace PATH` when invoking the npm CLI outside
35
- the checkout. The installed source launcher resolves its checkout automatically.
34
+ `fleet-node-sync` reconciles one machine from a checkout of dev-fleet
35
+ (`--workspace PATH` or `PERSONAL_WORKSPACE` names it). It is for development:
36
+ run from the published package it is refused, because `fleet-sync` does the
37
+ same from a checkout it owns.
36
38
  Each machine still needs its own authorized Vault session, SSH keys and T3 pairing.
37
39
  The sync never copies them or provisions infrastructure.
38
40
 
@@ -111,6 +113,34 @@ T3's installed server source at revision
111
113
  external settings watch/reload, and optimistic CLI writes. Future T3 schema changes
112
114
  require adapter verification. A disk-level success is not a live inference test.
113
115
 
116
+ ## Supported alternatives
117
+
118
+ `PROVIDER_PROTOCOL_INCOMPATIBLE` means the gateway answered that it has no
119
+ endpoint, method or media type for the protocol a route speaks (HTTP 404, 405,
120
+ 415 or 501). The route as declared cannot work, so the check fails and the
121
+ sync exits nonzero; signing in again or retrying changes nothing. Nothing on
122
+ the machine is changed to work around it.
123
+
124
+ The protocol a tool speaks to the proxy is its `proxy` field in
125
+ `agents/registry.json` (`responses`, `messages`, `generateContent` or
126
+ `chat_completions`). The supported alternatives, in this order:
127
+
128
+ 1. A model the proxy lists at `/v1/models` for the same protocol: change the
129
+ tool's model in `agents/registry.json`.
130
+ 2. Another protocol the tool's own CLI speaks and the pinned proxy serves.
131
+ `chat_completions` is served for OpenAI-compatible clients such as
132
+ OpenCode and Grok.
133
+ 3. The tool's direct profile, signed in natively on that machine, instead of
134
+ the shared proxy.
135
+
136
+ Each is a reviewed change to a tracked file, followed by `devfleet
137
+ fleet-sync`. A newer CLIProxyAPI is a reviewed change to
138
+ `cliproxy/image.lock.json`; a sync never upgrades it to get around a failure.
139
+
140
+ A model that is merely missing from the catalog is `PROVIDER_MODEL_UNAVAILABLE`
141
+ instead: a warning, because it is usually the provider account's (billing or
142
+ access), not the route's.
143
+
114
144
  ## Other local operations
115
145
 
116
146
  All former workstation operations are available through `dev-fleet`:
@@ -1,26 +1,26 @@
1
1
  ---
2
2
  # Manager power policy. Workers depend on the manager's singleton services
3
3
  # (CLI proxy, Beszel hub), so the manager never sleeps on AC power. Sleep on
4
- # battery is left to the human. Run once with: ansible-playbook -K manager.yml
4
+ # battery is left to the human. The settings are the `pmset` object in
5
+ # policy.json; fleet-sync measures the same object as manager:power. Changing
6
+ # them needs an administrator: ansible-playbook -K manager.yml
5
7
  - name: Keep the fleet manager awake on AC power
6
8
  hosts: localhost
7
9
  connection: local
8
10
  become: true
9
11
  gather_facts: false
10
- vars:
11
- manager_pmset_ac:
12
- sleep: '0'
13
- womp: '1'
12
+ vars_files:
13
+ - policy.json
14
14
  tasks:
15
15
  - name: Inspect effective power management settings
16
16
  ansible.builtin.command:
17
17
  argv: [/usr/bin/pmset, -g, custom]
18
18
  changed_when: false
19
19
  register: pmset_custom
20
- - name: Never sleep while on AC power
20
+ - name: Apply the declared AC power settings
21
21
  ansible.builtin.command:
22
22
  argv: [/usr/bin/pmset, -c, '{{ item.key }}', '{{ item.value }}']
23
- loop: '{{ manager_pmset_ac | dict2items }}'
23
+ loop: '{{ pmset | dict2items }}'
24
24
  loop_control:
25
25
  label: '{{ item.key }}'
26
26
  vars:
@@ -1,17 +1,9 @@
1
1
  {
2
2
  "version": 1,
3
3
  "minimumMajorVersion": 14,
4
- "ansibleCore": "2.19.9",
5
4
  "pmset": {
6
5
  "sleep": "0",
7
- "disksleep": "0",
8
- "autorestart": "1",
9
- "womp": "1",
10
- "powernap": "0"
6
+ "womp": "1"
11
7
  },
12
- "autoLogOutDelay": 0,
13
- "screensaverIdleTime": 0,
14
- "tailscaleDaemon": "com.tailscale.tailscaled",
15
- "beszelDaemon": "dev.devfleet.beszel-agent",
16
- "credentialRenewalAgent": "dev.devfleet.worker-auth"
8
+ "beszelDaemon": "dev.devfleet.beszel-agent"
17
9
  }