@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/worker-contract.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
|
# Declarative remote workers
|
|
6
7
|
|
|
@@ -12,11 +13,16 @@ devfleet fleet-status --workers
|
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
The manager needs Node/npm, Git, SSH, Tailscale, and its own authorized T3 link.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
designated non-root account, trusted SSH access and administrator authorization. Standard Ubuntu cloud-image users
|
|
16
|
+
What each worker must offer beforehand is listed once, in
|
|
17
|
+
[worker requirements](worker-requirements.md). Standard Ubuntu cloud-image users
|
|
18
18
|
already commonly have noninteractive sudo; otherwise establish the repository's
|
|
19
|
-
`authorize-sudo.sh` policy once during administrator enrollment.
|
|
19
|
+
`authorize-sudo.sh` policy once during administrator enrollment. An image that
|
|
20
|
+
lets only root in (many rented servers) needs no hands either: when the
|
|
21
|
+
account `workers[].network.user` names cannot log in yet, or the SSH alias
|
|
22
|
+
logs in as root, `fleet-sync` creates that account once over the root login
|
|
23
|
+
(`ansible/ubuntu/worker-account.yml`: the key root was reached with and the
|
|
24
|
+
fleet's sudo rule) and continues as it. The developer toolchain never runs as
|
|
25
|
+
root, and the SSH policy turns root login off afterwards. Routine commands
|
|
20
26
|
never ask for or store an SSH/sudo password. Unknown host keys, a different device
|
|
21
27
|
identity, or missing privilege stop with a named action rather than bypassing trust.
|
|
22
28
|
|
|
@@ -27,18 +33,110 @@ Ansible policy. Developer checkouts and dirty work stay intact. A published CLI
|
|
|
27
33
|
uses its source provenance; a source project action explicitly selects checkout
|
|
28
34
|
HEAD. Private rosters contain connection paths and identities, not weaker policy.
|
|
29
35
|
|
|
30
|
-
| Requirement | Declaration and enforcement
|
|
31
|
-
| ---------------------------------- |
|
|
32
|
-
| One consistent dependency set | `mise.toml` worker profile, `mise.lock`, Ubuntu snapshot/package pins; exact versions and deployed input hashes checked live
|
|
33
|
-
| Common service access | Canonical required capabilities; declared GitHub account, worker's own renewable Vault session, authenticated singleton gateway catalog
|
|
34
|
-
| 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
|
|
35
|
-
| T3 connected and persistent |
|
|
36
|
-
| 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
|
|
37
|
-
| Recovery after power loss | Required boot services and recovery timers; recognized firmware restore-after-AC settings inspected when exposed; macOS `autorestart` where supported
|
|
38
|
-
| 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
|
|
39
|
-
| Central dashboard | Pinned Beszel hub on the manager, every worker enrolled with its own token; health verifies hub reachability, registration, connection and fresh samples
|
|
40
|
-
| No routine password prompts | Verified BatchMode SSH, `sudo -n`, source-owned NOPASSWD policy; missing initial authorization is a failure
|
|
41
|
-
| 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
|
|
36
|
+
| Requirement | Declaration and enforcement |
|
|
37
|
+
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
38
|
+
| One consistent dependency set | `mise.toml` worker profile, `mise.lock`, Ubuntu snapshot/package pins; exact versions and deployed input hashes checked live |
|
|
39
|
+
| Common service access | Canonical required capabilities; declared GitHub account, worker's own renewable Vault session, authenticated singleton gateway catalog |
|
|
40
|
+
| 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 |
|
|
41
|
+
| T3 connected and persistent | One pinned T3 process: the desktop app in the auto-login session, or on a headless worker the pinned server as a lingering user unit; enabled/active with restart policy, bounded recovery timer, private endpoint, manager link matched to SSH-observed environment identity |
|
|
42
|
+
| 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 |
|
|
43
|
+
| Recovery after power loss | Required boot services and recovery timers; recognized firmware restore-after-AC settings inspected when exposed (not applicable on a virtual machine); macOS `autorestart` where supported |
|
|
44
|
+
| Always-on services | `Restart=always` drop-ins for ssh, tailscaled, docker (with `containers`) and beszel-agent (launchd `KeepAlive` on macOS); health requires the running unit to report that policy |
|
|
45
|
+
| Central dashboard | Pinned Beszel hub on the manager, every worker enrolled with its own token; health verifies hub reachability, registration, connection and fresh samples |
|
|
46
|
+
| No routine password prompts | Verified BatchMode SSH, `sudo -n`, source-owned NOPASSWD policy; missing initial authorization is a failure |
|
|
47
|
+
| 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 |
|
|
48
|
+
|
|
49
|
+
## Worker features
|
|
50
|
+
|
|
51
|
+
What a worker is built with beyond the base is declared in `fleet.json`:
|
|
52
|
+
fleet-wide under `worker.features`, or for one worker under its own
|
|
53
|
+
`features`. Each block may name a `preset` first; its own fields override
|
|
54
|
+
it. A fleet that names no feature gets what every worker had before features
|
|
55
|
+
existed.
|
|
56
|
+
|
|
57
|
+
| Feature | Values | Default | What it decides |
|
|
58
|
+
| ------------ | --------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `containers` | `true`, `false` | `true` | Docker: the Ubuntu packages, `docker.service`, the developer's `docker` group membership, the `docker-cli` tool and the Docker step of the disk GC |
|
|
60
|
+
| `session` | `desktop`, `headless` | `desktop` | How T3 runs: the desktop app in an auto-login graphical session, or the pinned server as a lingering user service with no display manager |
|
|
61
|
+
| `preset` | `editing` | none | `editing`: no containers, a headless session and only the base tools (it adds no tool profile) |
|
|
62
|
+
|
|
63
|
+
What each feature installs is listed once, under `features` in
|
|
64
|
+
`ansible/ubuntu/policy.json`. A worker without it installs none of it, and its
|
|
65
|
+
checks (`worker:package:*`, `worker:service:docker`, `worker:docker-run`,
|
|
66
|
+
`tool:docker-cli`) report **not applicable** with the reason. Turning
|
|
67
|
+
`containers` off on an existing worker removes what dev-fleet installed for it:
|
|
68
|
+
the packages (not purged: images and volumes under `/var/lib/docker` stay
|
|
69
|
+
until a person deletes them), the restart drop-in and the developer's group
|
|
70
|
+
membership. `fleet-sync --dry-run` lists what each worker will lose (`plan`,
|
|
71
|
+
under `removals`), and `worker:features` reports what the run removed and
|
|
72
|
+
fails with `WORKER_FEATURE_REMOVAL_INCOMPLETE` while any of it remains. A
|
|
73
|
+
headless worker installs no desktop-only package but removes none: a desktop
|
|
74
|
+
the machine came with may depend on them.
|
|
75
|
+
|
|
76
|
+
## The class of a machine
|
|
77
|
+
|
|
78
|
+
The probe records each worker's class before anything is installed
|
|
79
|
+
(`platform:machine-class`): **physical**, **virtual** (systemd-detect-virt
|
|
80
|
+
names a hypervisor) or unsupported (a container or WSL2, refused by
|
|
81
|
+
`platform:environment`). A virtual machine has no lid, no battery and no
|
|
82
|
+
firmware its guest can read, and its provider restarts it after a power loss,
|
|
83
|
+
so `worker:power-loss-recovery` and `ubuntu:lid-policy` report **not
|
|
84
|
+
applicable**, with the reason and the worker's optional `powerRecoveryNote`
|
|
85
|
+
(for example "provider restarts instances"). The sleep masks are still
|
|
86
|
+
checked: a guest can be suspended by its own idle policy. On a physical
|
|
87
|
+
machine nothing changes. A not-applicable check is counted apart, shown with
|
|
88
|
+
its reason in text and in `--json`, and never fails a run, not even with
|
|
89
|
+
`--strict`.
|
|
90
|
+
|
|
91
|
+
## Size
|
|
92
|
+
|
|
93
|
+
The minimum size of a worker for the editing workload is stated in
|
|
94
|
+
[worker requirements](worker-requirements.md), from `sizing.minimumWorker` in
|
|
95
|
+
`ansible/ubuntu/policy.json`. The probe measures it before anything is
|
|
96
|
+
installed and refuses a smaller machine with `WORKER_SIZE_BELOW_MINIMUM`
|
|
97
|
+
(`platform:worker-size`); the baseline asserts the same before it installs a
|
|
98
|
+
package.
|
|
99
|
+
|
|
100
|
+
The disk dev-fleet itself installs is measured on every pull request: the
|
|
101
|
+
fresh-box CI job converges a stock Ubuntu image of each accepted release on
|
|
102
|
+
amd64 and arm64, measures what each part takes, and fails when a part
|
|
103
|
+
outgrows its budget in `sizing.footprintMiB`
|
|
104
|
+
(`features/fresh-box/fresh-box.feature`). Measured on Ubuntu 26.04:
|
|
105
|
+
|
|
106
|
+
| Part | amd64 | arm64 | Budget |
|
|
107
|
+
| -------------------------------------------- | ------------ | ------------ | -------- |
|
|
108
|
+
| Base tools with the agent CLIs, and `go` | 5184 MiB | 4830 MiB | 6144 MiB |
|
|
109
|
+
| The fleet's checkouts (up to four revisions) | 189 MiB each | 189 MiB each | 1024 MiB |
|
|
110
|
+
| Tool profile `web` (its tools) | | 135 MiB | 256 MiB |
|
|
111
|
+
| Tool profile `rust` | | 1562 MiB | 2048 MiB |
|
|
112
|
+
| Tool profile `jvm` | 437 MiB | 303 MiB | 640 MiB |
|
|
113
|
+
|
|
114
|
+
What is not measured there, and stays an estimate: Ubuntu Server itself
|
|
115
|
+
(about 3 to 5 GiB), the system packages of the baseline, the headless
|
|
116
|
+
Chromium the `web` profile installs as packages, the swap file (half of
|
|
117
|
+
memory), and the engine and images of `containers`. The minimum adds those
|
|
118
|
+
to the measured parts with room for the disk floors below.
|
|
119
|
+
|
|
120
|
+
The disk and memory lines of the worker policy scale with the machine: each
|
|
121
|
+
is a share of the root filesystem or of memory between a lower and an upper
|
|
122
|
+
bound (`sizing.thresholds`), and the upper bounds are the fixed numbers every
|
|
123
|
+
worker had before.
|
|
124
|
+
|
|
125
|
+
| Line | Share | Lower | Upper |
|
|
126
|
+
| ---------------------------------- | ------------- | -------- | --------- |
|
|
127
|
+
| Swap file (created only when none) | 50% of memory | 1024 MiB | 8192 MiB |
|
|
128
|
+
| Free disk the baseline keeps | 5% of disk | 2048 MiB | 4096 MiB |
|
|
129
|
+
| Disk guard floor | 3% of disk | 1024 MiB | 3072 MiB |
|
|
130
|
+
| Disk GC pressure line | 15% of disk | 4096 MiB | 15360 MiB |
|
|
131
|
+
| `worker:capacity:disk` warns under | 10% of disk | 3072 MiB | 10240 MiB |
|
|
132
|
+
| `worker:capacity:disk` fails under | 2% of disk | 1024 MiB | 2048 MiB |
|
|
133
|
+
|
|
134
|
+
`worker:agent-sessions` reports how many agent sessions run and how many the
|
|
135
|
+
memory policy supports at once: the memory user workloads may use (90%), less
|
|
136
|
+
what T3 keeps for itself (1536 MiB), in sessions of an estimated 1024 MiB each
|
|
137
|
+
(`sizing.agentSessionMiB`, also an estimate). More sessions than that warn
|
|
138
|
+
with `AGENT_SESSION_CAPACITY_EXHAUSTED`; each agent is still capped at 30% of
|
|
139
|
+
memory, so the largest is stopped under pressure rather than the machine.
|
|
42
140
|
|
|
43
141
|
## Package pins and security updates
|
|
44
142
|
|
|
@@ -64,12 +162,17 @@ one:
|
|
|
64
162
|
A package run cut off by a power loss is finished by the next sync: the
|
|
65
163
|
baseline waits for the APT lock, runs `dpkg --configure -a` once (bounded to
|
|
66
164
|
ten minutes) and audits again; only a database still inconsistent stops with
|
|
67
|
-
`APT_STATE_BROKEN`.
|
|
165
|
+
`APT_STATE_BROKEN`. When the cut-off run left one of the bootstrap's own
|
|
166
|
+
packages unconfigured (an upgrade of `python3` or `git`, say), the bootstrap
|
|
167
|
+
makes the same repair before it installs what it lacks, whoever started the
|
|
168
|
+
run, waiting up to two minutes for a package process that holds the lock;
|
|
169
|
+
only a database still inconsistent stops it with `DPKG_INTERRUPTED`.
|
|
68
170
|
|
|
69
171
|
Health does not mutate configuration, renew tokens, log in, install software, or
|
|
70
172
|
make billable inference requests. It audits the immutable desired worker deployment,
|
|
71
173
|
not an unrelated developer branch. Every declared worker must be enrolled and
|
|
72
|
-
reachable
|
|
174
|
+
reachable, unless it is parked (`devfleet fleet-node-park`): a parked worker
|
|
175
|
+
is listed as parked and never contacted. Extra service hosts are listed as outside scope. Required failures,
|
|
73
176
|
warnings and unverified checks prevent a ready result; explicitly non-required
|
|
74
177
|
checks remain visible without inventing a pass.
|
|
75
178
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# What a machine must offer to be a worker
|
|
2
|
+
|
|
3
|
+
The one list of what a machine needs before it can be a worker, wherever it
|
|
4
|
+
runs: a computer at home, a virtual machine on your own host or an instance
|
|
5
|
+
rented from any provider. Everything else, `fleet-sync` installs. No vendor is
|
|
6
|
+
required; any provider that boots a stock Ubuntu image with SSH and cloud-init
|
|
7
|
+
user data works the same way.
|
|
8
|
+
|
|
9
|
+
Each requirement that a check enforces names the check (from
|
|
10
|
+
[schema/check-ids.json](../schema/check-ids.json)) and the failure code it
|
|
11
|
+
reports (each has an entry in the [runbooks](runbooks.md)). A dash means the
|
|
12
|
+
check reports no code of its own; its summary and remedy say what failed.
|
|
13
|
+
`features/rules/worker-requirements.feature` fails when a check or code here
|
|
14
|
+
does not exist, when a check marked as a worker requirement in the registry
|
|
15
|
+
has no row here, or when the image, architecture and size rows disagree with
|
|
16
|
+
`ansible/ubuntu/policy.json`.
|
|
17
|
+
|
|
18
|
+
## The machine
|
|
19
|
+
|
|
20
|
+
| Requirement | How to check it | Check | Failure code |
|
|
21
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------- | -------------------------------- |
|
|
22
|
+
| A stock Ubuntu image of an accepted release: **26.04** (resolute) or **24.04** (noble). No distribution upgrade is ever made. | `. /etc/os-release; echo $VERSION_ID` | `ubuntu:release` | `UBUNTU_BASELINE_MISMATCH` |
|
|
23
|
+
| An accepted architecture: **amd64** (x86_64) or **arm64** (aarch64). | `uname -m` | `ubuntu:release` | `UBUNTU_BASELINE_MISMATCH` |
|
|
24
|
+
| A machine of its own: physical, or a full virtual machine (KVM, Xen, VMware, Hyper-V, or any hypervisor `systemd-detect-virt --vm` names). A container (Docker, LXC, systemd-nspawn, OpenVZ) or WSL2 is refused. | `systemd-detect-virt` | `platform:environment` | `WORKER_ENVIRONMENT_UNSUPPORTED` |
|
|
25
|
+
| At least **2 CPUs**, **3584 MiB** of memory (a 4 GiB instance) and a **20480 MiB** root filesystem. What dev-fleet installs is measured in CI; the operating system is estimated. | `nproc; free -m; df -m /` | `platform:worker-size` | `WORKER_SIZE_BELOW_MINIMUM` |
|
|
26
|
+
| Survives one reboot: the first converge ends with a reboot while no agent runs, and the worker must answer SSH again within 15 minutes. | `devfleet fleet-status --workers` after the first run | `worker:reboot` | `WORKER_REBOOT_FAILED` |
|
|
27
|
+
|
|
28
|
+
The minimum size is `sizing.minimumWorker` in `ansible/ubuntu/policy.json`;
|
|
29
|
+
[the worker contract](worker-contract.md#size) explains the estimate and what
|
|
30
|
+
grows with the machine. The accepted releases and architectures are `releases`
|
|
31
|
+
in the same file; [the support matrix](support-matrix.md) lists every
|
|
32
|
+
platform and why the others are refused.
|
|
33
|
+
|
|
34
|
+
## Access
|
|
35
|
+
|
|
36
|
+
| Requirement | How to check it | Check | Failure code |
|
|
37
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------- | ----------------------------------- |
|
|
38
|
+
| SSH with a key, from the manager, without a password. | `ssh -o BatchMode=yes WORKER true` | `ssh:auth` | - |
|
|
39
|
+
| A host key the manager knows before the first connection: generated by `devfleet fleet-node-seed`, given as `hostKey` in a worker record, or pinned with `devfleet fleet-node-replace --host-key`. | `devfleet fleet-status --workers` | `ssh:host-key` | - |
|
|
40
|
+
| A non-root account that may use `sudo` without a password, as `workers[].network.user` names it. An image that lets only root in is fine: `fleet-sync` creates the account over the root login once. | `ssh WORKER 'id -u; sudo -n true'` | `bootstrap:privilege` | `SUDO_AUTHORIZATION_REQUIRED` |
|
|
41
|
+
| With only a root login, `workers[].network.user` names the account to create. | `fleet.json` | `worker:account` | `WORKER_ACCOUNT_UNDECLARED` |
|
|
42
|
+
| Joined to the fleet's tailnet: by the seed at first boot, or by `fleet-sync` with a single-use key the control plane `fleet.json` declares (`tailnet`) mints for it. | `tailscale status --json` on the manager | `tailscale:configuration` | `TAILSCALE_SETUP_OR_LOGIN_REQUIRED` |
|
|
43
|
+
|
|
44
|
+
## First-boot data
|
|
45
|
+
|
|
46
|
+
A rented machine is set up from cloud-init user data and nothing else.
|
|
47
|
+
`devfleet fleet-node-seed --id ID --output PATH` writes it: the account with
|
|
48
|
+
the manager's key, the sudo rule, a host key generated for the machine, the
|
|
49
|
+
pinned Tailscale and a short-lived single-use key. Any image that runs
|
|
50
|
+
cloud-init accepts it (`--autoinstall` writes an Ubuntu autoinstall file for a
|
|
51
|
+
machine installed from the ISO). The seed holds a secret: pass it to the
|
|
52
|
+
provider as user data and delete it afterwards.
|
|
53
|
+
|
|
54
|
+
| Requirement | How to check it | Check | Failure code |
|
|
55
|
+
| --------------------------------------------------------------------------------------------------------------- | -------------------------- | ---------------------- | ----------------------------------------- |
|
|
56
|
+
| The image runs cloud-init, or nothing at first boot. `fleet-sync` waits for a first boot that is still running. | `cloud-init status --wait` | `bootstrap:first-boot` | `FIRST_BOOT_FAILED`, `FIRST_BOOT_PENDING` |
|
|
57
|
+
|
|
58
|
+
## Network
|
|
59
|
+
|
|
60
|
+
No inbound connection from the internet is needed: the manager reaches a
|
|
61
|
+
worker over the tailnet. A fixed or public address is not needed either.
|
|
62
|
+
Outbound, the worker opens HTTPS (TCP 443) to:
|
|
63
|
+
|
|
64
|
+
- the Ubuntu archive its image is configured with, and `snapshot.ubuntu.com`
|
|
65
|
+
(every pinned package);
|
|
66
|
+
- `pkgs.tailscale.com` (Tailscale) and the fleet's tailnet control plane:
|
|
67
|
+
`tailnet.url` in `fleet.json`, or the hosted control plane when it names
|
|
68
|
+
none; Tailscale also uses UDP 41641 and its relays (UDP 3478, TCP 443);
|
|
69
|
+
- `github.com`, `api.github.com` and GitHub's release download hosts, and the
|
|
70
|
+
other download hosts `mise.lock` names
|
|
71
|
+
(`grep -o 'https://[^/"]*' mise.lock | sort -u` lists them);
|
|
72
|
+
- the CLI proxy at `services.proxy.endpoint`, and the secret store
|
|
73
|
+
`fleet.json` names (Vault's address with `vault`).
|
|
74
|
+
|
|
75
|
+
| Requirement | How to check it | Check | Failure code |
|
|
76
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ---------------------------- | ---------------------------------------------------------- |
|
|
77
|
+
| Reaches the Ubuntu archive and the snapshot. | `ssh WORKER 'sudo apt-get update'` | `bootstrap:prerequisites` | `APT_REPOSITORY_UNAVAILABLE`, `APT_DNS_UNAVAILABLE` |
|
|
78
|
+
| Reaches GitHub. | `ssh WORKER 'curl -sI https://api.github.com'` | `worker:github-sync` | `GITHUB_UNREACHABLE` |
|
|
79
|
+
| Reaches the CLI proxy. | `devfleet fleet-status --workers` | `proxy:reachable-from` | - |
|
|
80
|
+
| Accepts no inbound connection beyond loopback and the tailnet, but what `exposure` in `fleet.json` declares. The fleet firewall closes the rest by itself. | `sudo nft list table inet personal_fleet` | `worker:exposure` | `WORKER_LISTENER_UNRESTRICTED`, `WORKER_FIREWALL_INACTIVE` |
|
|
81
|
+
| Its public address, when it has one, answers on no fleet port. | `devfleet fleet-status --workers` | `worker:public-reachability` | `WORKER_PUBLIC_PORT_UNRESTRICTED` |
|
|
82
|
+
|
|
83
|
+
A provider's own firewall (a security group) may stay closed to all inbound
|
|
84
|
+
traffic: Tailscale needs none, and SSH from the internet is never needed once
|
|
85
|
+
the seed has joined the machine to the tailnet.
|
|
86
|
+
|
|
87
|
+
## What is not needed
|
|
88
|
+
|
|
89
|
+
- A display, a desktop or a GPU: `"session": "headless"` (the `editing`
|
|
90
|
+
preset) runs T3 as a service with no graphical session.
|
|
91
|
+
- A public inbound port, a fixed address or a domain name.
|
|
92
|
+
- Git, curl, Python, Node, Bun or mise beforehand: `fleet-sync` installs them.
|
|
93
|
+
- Docker beforehand: the `containers` feature (on unless a worker turns it
|
|
94
|
+
off) installs it.
|
|
95
|
+
- A password for SSH or `sudo`, or anyone at the console.
|
|
96
|
+
- An account with any particular provider.
|
|
97
|
+
|
|
98
|
+
## From infrastructure as code
|
|
99
|
+
|
|
100
|
+
[examples/workers-from-infrastructure](../examples/workers-from-infrastructure/main.tf)
|
|
101
|
+
shows the handoff with a local virtual machine: the seed from
|
|
102
|
+
`devfleet fleet-node-seed` goes in as user data, and a worker record
|
|
103
|
+
(`schema/worker-record.schema.json`) comes out for `fleet.json` to read. For a
|
|
104
|
+
hosted provider only these change: the provider block and its credentials, the
|
|
105
|
+
image (the provider's stock Ubuntu image of an accepted release), the instance
|
|
106
|
+
type (at least the size above) and the network (a private network, or an
|
|
107
|
+
inbound rule set with nothing open). The seed and the record stay the same.
|
package/examples/fleet.json
CHANGED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Workers from infrastructure as code: an example, with a local virtual
|
|
2
|
+
# machine (libvirt) standing in for the provider. Any tool or provider that
|
|
3
|
+
# can boot a stock Ubuntu image with cloud-init user data and write a file
|
|
4
|
+
# works the same way; dev-fleet never calls a provider and never creates a
|
|
5
|
+
# machine. It reads the worker records this writes (worker-records.json, see
|
|
6
|
+
# schema/worker-record.schema.json) because fleet.json names the file:
|
|
7
|
+
#
|
|
8
|
+
# "workerRecords": [{ "file": "infra/worker-records.json" }]
|
|
9
|
+
#
|
|
10
|
+
# The handoff, from the fleet project (OpenTofu installed, libvirt on this
|
|
11
|
+
# machine):
|
|
12
|
+
#
|
|
13
|
+
# 1. Declare the worker. With only `tailnet_name` and `user` set, this
|
|
14
|
+
# writes its record and creates nothing else:
|
|
15
|
+
# tofu -chdir=infra apply -var-file=workers.tfvars
|
|
16
|
+
# 2. Seed it. dev-fleet generates its host key, pins it in the fleet state
|
|
17
|
+
# and writes the cloud-init user data:
|
|
18
|
+
# npx devfleet fleet-node-seed --id rented-one \
|
|
19
|
+
# --output infra/seed/rented-one.yaml --apply --yes --json
|
|
20
|
+
# Put the path in `seed` (and, if you like, the printed hostKey in
|
|
21
|
+
# `host_key`, so that the record carries it too).
|
|
22
|
+
# 3. Create the machine, then converge it:
|
|
23
|
+
# tofu -chdir=infra apply -var-file=workers.tfvars
|
|
24
|
+
# npx devfleet fleet-sync --non-interactive
|
|
25
|
+
# 4. Remove it: drop it from `workers` and apply, or `tofu destroy`. Its
|
|
26
|
+
# record is gone, so the next `npx devfleet fleet-sync` retires it: its
|
|
27
|
+
# token, pinned host key, monitoring system and T3 link are removed.
|
|
28
|
+
#
|
|
29
|
+
# What the machine must offer (image, architecture, size, access, network) is
|
|
30
|
+
# docs/worker-requirements.md. For a hosted provider only these change: the
|
|
31
|
+
# provider block and its credentials, the image (its stock Ubuntu image of an
|
|
32
|
+
# accepted release, in place of `ubuntu_image`), the instance type (at least
|
|
33
|
+
# the minimum size, in place of `memory_mib` and `vcpus`) and the network (a
|
|
34
|
+
# private network, or inbound rules with nothing open, in place of
|
|
35
|
+
# `network_interface`). The seed goes in as the instance's user data and the
|
|
36
|
+
# records come out the same way.
|
|
37
|
+
#
|
|
38
|
+
# This example cannot be applied in dev-fleet's CI (it needs a libvirt host
|
|
39
|
+
# and a tailnet), so it is not proven there; the record format and what
|
|
40
|
+
# fleet-sync does with records are (features/cli/fleet-validate.feature,
|
|
41
|
+
# features/fleet-sync/node-lifecycle.feature). Pin the provider in your own
|
|
42
|
+
# project: `tofu init` writes .terraform.lock.hcl.
|
|
43
|
+
|
|
44
|
+
terraform {
|
|
45
|
+
required_providers {
|
|
46
|
+
libvirt = { source = "dmacvicar/libvirt" }
|
|
47
|
+
local = { source = "hashicorp/local" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
provider "libvirt" {
|
|
52
|
+
uri = "qemu:///system"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
variable "ubuntu_image" {
|
|
56
|
+
description = "A stock Ubuntu cloud image (a URL or a local path) of the release fleet.json names."
|
|
57
|
+
type = string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
variable "workers" {
|
|
61
|
+
description = "The workers, by fleet ID. A worker with a seed is created; one without is only declared."
|
|
62
|
+
type = map(object({
|
|
63
|
+
tailnet_name = string
|
|
64
|
+
user = string
|
|
65
|
+
seed = optional(string)
|
|
66
|
+
host_key = optional(string)
|
|
67
|
+
memory_mib = optional(number, 8192)
|
|
68
|
+
vcpus = optional(number, 4)
|
|
69
|
+
hourly_rate = optional(number)
|
|
70
|
+
}))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
locals {
|
|
74
|
+
seeded = { for id, worker in var.workers : id => worker if worker.seed != null }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
resource "libvirt_volume" "image" {
|
|
78
|
+
name = "devfleet-ubuntu.qcow2"
|
|
79
|
+
source = var.ubuntu_image
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
resource "libvirt_volume" "disk" {
|
|
83
|
+
for_each = local.seeded
|
|
84
|
+
name = "${each.key}.qcow2"
|
|
85
|
+
base_volume_id = libvirt_volume.image.id
|
|
86
|
+
size = 64 * 1024 * 1024 * 1024
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
resource "libvirt_cloudinit_disk" "seed" {
|
|
90
|
+
for_each = local.seeded
|
|
91
|
+
name = "${each.key}-seed.iso"
|
|
92
|
+
user_data = file(each.value.seed)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
resource "libvirt_domain" "worker" {
|
|
96
|
+
for_each = local.seeded
|
|
97
|
+
name = each.key
|
|
98
|
+
memory = each.value.memory_mib
|
|
99
|
+
vcpu = each.value.vcpus
|
|
100
|
+
cloudinit = libvirt_cloudinit_disk.seed[each.key].id
|
|
101
|
+
|
|
102
|
+
disk {
|
|
103
|
+
volume_id = libvirt_volume.disk[each.key].id
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
network_interface {
|
|
107
|
+
network_name = "default"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# The handoff: one record per declared worker. Only what dev-fleet needs to
|
|
112
|
+
# reach the machine, and nothing that names this tool or the provider.
|
|
113
|
+
resource "local_file" "worker_records" {
|
|
114
|
+
filename = "${path.module}/worker-records.json"
|
|
115
|
+
file_permission = "0644"
|
|
116
|
+
content = jsonencode({
|
|
117
|
+
version = 1
|
|
118
|
+
workers = [
|
|
119
|
+
for id, worker in var.workers : merge(
|
|
120
|
+
{ id = id, tailnetName = worker.tailnet_name, user = worker.user },
|
|
121
|
+
worker.host_key == null ? {} : { hostKey = worker.host_key },
|
|
122
|
+
worker.hourly_rate == null ? {} : { billing = { hourlyRate = worker.hourly_rate } },
|
|
123
|
+
{ labels = { source = "local-vm" } },
|
|
124
|
+
)
|
|
125
|
+
]
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
output "fleet_workers" {
|
|
130
|
+
description = "The same records, for a fleet.json that runs a command instead: { \"command\": [\"tofu\", \"-chdir=infra\", \"output\", \"-json\", \"fleet_workers\"] }."
|
|
131
|
+
value = jsondecode(local_file.worker_records.content)
|
|
132
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../schema/worker-record.schema.json",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"workers": [
|
|
5
|
+
{
|
|
6
|
+
"id": "rented-one",
|
|
7
|
+
"tailnetName": "rented-one.tail0a1b2c.ts.net",
|
|
8
|
+
"user": "dev",
|
|
9
|
+
"hostKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleHostKeyOfARentedWorker",
|
|
10
|
+
"billing": { "hourlyRate": 0.5 },
|
|
11
|
+
"labels": { "source": "local-vm" }
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dev-fleet
|
|
2
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.
|
|
3
|
+
> dev-fleet keeps a set of development machines in sync: one macOS **manager** and any number of Ubuntu (26.04 or 24.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
4
|
|
|
5
5
|
Rules for an agent working in a project that uses dev-fleet:
|
|
6
6
|
|
|
@@ -16,10 +16,12 @@ Rules for an agent working in a project that uses dev-fleet:
|
|
|
16
16
|
- [README](https://github.com/crvouga/dev-fleet/blob/main/README.md): what dev-fleet assumes, quickstart, versions, adding a machine, known limits
|
|
17
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
18
|
- [fleet.json schema](https://github.com/crvouga/dev-fleet/blob/main/schema/fleet.schema.json): JSON Schema for the fleet configuration
|
|
19
|
+
- [Check-ID registry](https://github.com/crvouga/dev-fleet/blob/main/schema/check-ids.json): every check ID a report can carry, with what it means, whether it can block readiness (severity) and what repairs it (a fleet-repair recipe, or why only a person can); generated from the code
|
|
19
20
|
|
|
20
21
|
## Operate it
|
|
21
22
|
|
|
22
23
|
- [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
|
|
24
|
+
- [Worker requirements](https://github.com/crvouga/dev-fleet/blob/main/docs/worker-requirements.md): what a machine must offer to be a worker (image, architecture, size, access, first-boot data, network), each with its check and failure code, and what changes for a hosted provider
|
|
23
25
|
- [Topology](https://github.com/crvouga/dev-fleet/blob/main/docs/topology.md): the manager and worker roles and the canary
|
|
24
26
|
- [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
27
|
- [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
|
package/mise.lock
CHANGED
|
@@ -1355,6 +1355,35 @@ url = "https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.am
|
|
|
1355
1355
|
url_api = "https://api.github.com/repos/getsops/sops/releases/assets/486808200"
|
|
1356
1356
|
provenance = "cosign"
|
|
1357
1357
|
|
|
1358
|
+
[[tools.tmux]]
|
|
1359
|
+
version = "3.8"
|
|
1360
|
+
backend = "aqua:tmux/tmux-builds"
|
|
1361
|
+
specifiers = ["3.8"]
|
|
1362
|
+
|
|
1363
|
+
[tools.tmux."platforms.linux-arm64"]
|
|
1364
|
+
checksum = "sha256:f399311b1159705e69839eaf4f6f68c8b8540dc3a8538b87c30dedb9c3c7fc23"
|
|
1365
|
+
checksum_source = "mise-versions"
|
|
1366
|
+
url = "https://github.com/tmux/tmux-builds/releases/download/v3.8/tmux-3.8-linux-arm64.tar.gz"
|
|
1367
|
+
url_api = "https://api.github.com/repos/tmux/tmux-builds/releases/assets/721688993"
|
|
1368
|
+
|
|
1369
|
+
[tools.tmux."platforms.linux-x64"]
|
|
1370
|
+
checksum = "sha256:147849ff18b62a8aa3d5d8f75dd07c3f9fdfad529f932236afdec838fc1d15e1"
|
|
1371
|
+
checksum_source = "mise-versions"
|
|
1372
|
+
url = "https://github.com/tmux/tmux-builds/releases/download/v3.8/tmux-3.8-linux-x86_64.tar.gz"
|
|
1373
|
+
url_api = "https://api.github.com/repos/tmux/tmux-builds/releases/assets/721688991"
|
|
1374
|
+
|
|
1375
|
+
[tools.tmux."platforms.macos-arm64"]
|
|
1376
|
+
checksum = "sha256:ec926bbf28ddf55d0dc7c9791d43e03e251ddab392d763f7fbb38d4b8408d051"
|
|
1377
|
+
checksum_source = "mise-versions"
|
|
1378
|
+
url = "https://github.com/tmux/tmux-builds/releases/download/v3.8/tmux-3.8-macos-arm64.tar.gz"
|
|
1379
|
+
url_api = "https://api.github.com/repos/tmux/tmux-builds/releases/assets/721688988"
|
|
1380
|
+
|
|
1381
|
+
[tools.tmux."platforms.macos-x64"]
|
|
1382
|
+
checksum = "sha256:e7b92e51d9e9610ce80d1c477da1e6ff6a6398ea6b6b4d4ff7b19cf60f594555"
|
|
1383
|
+
checksum_source = "mise-versions"
|
|
1384
|
+
url = "https://github.com/tmux/tmux-builds/releases/download/v3.8/tmux-3.8-macos-x86_64.tar.gz"
|
|
1385
|
+
url_api = "https://api.github.com/repos/tmux/tmux-builds/releases/assets/721688994"
|
|
1386
|
+
|
|
1358
1387
|
[[tools.usql]]
|
|
1359
1388
|
version = "0.21.6"
|
|
1360
1389
|
backend = "aqua:xo/usql"
|
package/mise.toml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Shared workstation toolchain. Bootstrap installs this globally; projects may override it.
|
|
2
2
|
# Commit mise.lock whenever changing these pins.
|
|
3
|
-
# min_version is also the exact mise bootstrap/bootstrap.sh installs;
|
|
4
|
-
# mise_*_sha256 digests under [vars] with it.
|
|
3
|
+
# min_version is also the exact mise bootstrap/bootstrap.sh installs; the
|
|
4
|
+
# mise_*_sha256 digests under [vars] move with it (bun bootstrap/pins.ts mise).
|
|
5
5
|
min_version = "2026.10.24"
|
|
6
6
|
|
|
7
7
|
[settings]
|
|
@@ -30,6 +30,8 @@ go = "1.27.3"
|
|
|
30
30
|
jq = "1.8.2"
|
|
31
31
|
yq = "4.54.1"
|
|
32
32
|
ripgrep = "15.2.0"
|
|
33
|
+
# Terminal multiplexer for long agent and build sessions on a worker.
|
|
34
|
+
tmux = { version = "3.8", os = ["linux", "macos"] }
|
|
33
35
|
fd = "10.5.0"
|
|
34
36
|
fzf = "0.74.5"
|
|
35
37
|
bat = "0.26.1"
|
|
@@ -109,7 +111,7 @@ docker_desktop_arm64_sha256 = "9c6f13ce4edda2abf20d801552b6acb96acd3d64b3e91b50c
|
|
|
109
111
|
docker_desktop_amd64_sha256 = "96b53f9a4a807b6be6207d18f360b9778ff481e1d94b5027bdf1ae2a22d837cb"
|
|
110
112
|
|
|
111
113
|
# Minimal remote development baseline; versions are resolved exclusively by tools + mise.lock.
|
|
112
|
-
worker_tools = "bun,node,gh,python,uv,docker-cli,vault,jq,codex,claude,aqua:anomalyco/opencode,gemini,copilot,http:grok,http:antigravity"
|
|
114
|
+
worker_tools = "bun,node,gh,python,uv,docker-cli,vault,jq,tmux,codex,claude,aqua:anomalyco/opencode,gemini,copilot,http:grok,http:antigravity"
|
|
113
115
|
# Tool profiles a fleet adds to its workers (`worker.toolProfiles` in
|
|
114
116
|
# fleet.json, fleet-wide or per worker): one `worker_profile_<name>` each,
|
|
115
117
|
# naming tools pinned above. bootstrap/worker-tools.ts lists the names.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvouga/dev-fleet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Keeps a set of development machines in sync: one manager and many workers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "github:crvouga/dev-fleet",
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=22"
|
|
74
74
|
},
|
|
75
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "92f24582645e8a05f47124be1076a28f637ec347"
|
|
76
76
|
}
|