@crvouga/dev-fleet 0.8.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 +65 -9
- package/agents/audit.md +18 -18
- package/ansible/agents/agents.yml +134 -0
- package/ansible/agents/templates/antigravity.settings.json.j2 +3 -0
- package/ansible/agents/templates/claude.settings.json.j2 +3 -0
- package/ansible/agents/templates/codex.config.toml.j2 +11 -0
- package/ansible/agents/templates/gemini.settings.json.j2 +14 -0
- package/ansible/agents/templates/grok.config.toml.j2 +19 -0
- package/ansible/agents/templates/opencode.json.j2 +24 -0
- package/ansible/agents/templates/receipt.json.j2 +10 -0
- package/ansible/ansible.cfg +14 -0
- package/ansible/github/git.yml +82 -0
- package/ansible/macos/manager-privilege.yml +55 -9
- package/ansible/macos/manager.yml +154 -7
- package/ansible/macos/policy.json +13 -0
- package/ansible/macos/t3-policy.yml +5 -3
- package/ansible/macos/vault-renew.yml +85 -0
- package/ansible/monitoring/agent-darwin.yml +3 -0
- package/ansible/monitoring/agent.yml +2 -0
- package/ansible/monitoring/hub-darwin.yml +3 -0
- package/ansible/monitoring/hub-route-darwin.yml +1 -0
- package/ansible/net/remote-probe.sh +4 -1
- package/ansible/platforms.json +2 -2
- package/ansible/pyproject.toml +5 -0
- package/ansible/ubuntu/baseline.yml +39 -13
- package/ansible/ubuntu/disk-gc.py +6 -2
- package/ansible/ubuntu/firewall.nft.j2 +50 -0
- package/ansible/ubuntu/policy.json +125 -6
- package/ansible/ubuntu/reboot.yml +89 -0
- package/ansible/ubuntu/recovery.yml +13 -3
- package/ansible/ubuntu/t3/policy.json +3 -0
- package/ansible/ubuntu/t3-desktop.yml +23 -2
- package/ansible/ubuntu/t3-headless.yml +128 -0
- package/ansible/ubuntu/t3-health.sh +24 -10
- package/ansible/ubuntu/t3-idle.py +37 -2
- package/ansible/ubuntu/t3-policy.yml +4 -1
- package/ansible/ubuntu/t3-route.yml +1 -0
- package/ansible/ubuntu/tailscale.yml +54 -8
- package/ansible/ubuntu/tasks/browser.yml +3 -0
- package/ansible/ubuntu/tasks/containers-off.yml +43 -0
- package/ansible/ubuntu/tasks/docker.yml +53 -6
- package/ansible/ubuntu/tasks/dpkg.yml +4 -0
- package/ansible/ubuntu/tasks/features.yml +22 -0
- package/ansible/ubuntu/tasks/firewall.yml +172 -0
- package/ansible/ubuntu/tasks/install.yml +2 -1
- package/ansible/ubuntu/tasks/packages.yml +4 -2
- package/ansible/ubuntu/tasks/release.yml +6 -0
- package/ansible/ubuntu/tasks/resources.yml +6 -4
- package/ansible/ubuntu/tasks/services.yml +2 -2
- package/ansible/ubuntu/tasks/sizing.yml +49 -0
- package/ansible/ubuntu/tasks/snapshot.yml +14 -3
- package/ansible/ubuntu/tasks/ssh.yml +61 -4
- package/ansible/ubuntu/tasks/t3-restart.yml +3 -1
- package/ansible/ubuntu/tasks/user.yml +1 -0
- package/ansible/ubuntu/worker-account.yml +117 -0
- package/ansible/uv.lock +265 -0
- package/bin/devfleet.mjs +42 -4
- package/bootstrap/fleet-remote-bootstrap.sh +91 -25
- package/bootstrap/global-config.ts +29 -3
- package/bootstrap/package-smoke.ts +127 -0
- package/bootstrap/pins.ts +189 -0
- package/bootstrap/release-notes.ts +88 -0
- package/bootstrap/release-version.ts +41 -7
- package/bootstrap/worker-features.ts +114 -0
- package/bootstrap/worker-seed.yaml +7 -3
- package/bootstrap/worker-tools.ts +39 -9
- package/cliproxy/README.md +1 -1
- package/cliproxy/docs/health-workflow.example.yml +20 -143
- package/docs/authentication.md +19 -11
- package/docs/consumer-setup.md +42 -27
- package/docs/convergence.md +67 -0
- package/docs/declared.md +61 -34
- package/docs/machine-setup.md +73 -7
- package/docs/manager-setup.md +10 -3
- package/docs/operations.md +64 -9
- package/docs/releasing.md +49 -1
- package/docs/runbooks.md +98 -7
- package/docs/state.md +23 -11
- package/docs/support-matrix.md +40 -4
- package/docs/topology.md +67 -6
- package/docs/ubuntu-sync.md +78 -28
- package/docs/worker-contract.md +122 -19
- package/docs/worker-requirements.md +107 -0
- package/examples/fleet.json +1 -0
- package/examples/workers-from-infrastructure/main.tf +132 -0
- package/examples/workers-from-infrastructure/worker-records.json +14 -0
- package/llms.txt +3 -1
- package/mise.lock +29 -0
- package/mise.toml +5 -3
- package/package.json +2 -2
- package/schema/check-ids.json +1967 -170
- package/schema/check-ids.schema.json +34 -0
- package/schema/fleet.schema.json +327 -8
- package/schema/output/check.schema.json +1 -1
- package/schema/output/fleet-state-definition.schema.json +14 -0
- package/schema/output/fleet-sync-schedule.schema.json +26 -0
- package/schema/output/fleet-sync.schema.json +18 -0
- package/schema/output/report.schema.json +3 -2
- package/schema/worker-record.schema.json +76 -0
- package/src/agents/converge.ts +140 -24
- package/src/agents/files.ts +2 -32
- package/src/agents/native-verify.ts +34 -3
- package/src/agents/secrets.ts +39 -4
- package/src/check-owners.ts +61 -2
- package/src/check-registry.ts +630 -0
- package/src/cli/agent-guide.ts +3 -3
- package/src/cli/cli.ts +4 -0
- package/src/cli/fleet-manifest.ts +9 -3
- package/src/cli/fleet-node-lifecycle.ts +313 -64
- package/src/cli/fleet-node-park.ts +238 -0
- package/src/cli/fleet-node-seed.ts +51 -35
- package/src/cli/fleet-state.ts +2 -1
- package/src/cli/fleet-sync-checkout.ts +7 -0
- package/src/cli/fleet-sync-enrollment.ts +193 -32
- package/src/cli/fleet-sync-forward.ts +4 -0
- package/src/cli/fleet-sync-manager.ts +103 -33
- package/src/cli/fleet-sync-output.ts +31 -11
- package/src/cli/fleet-sync-plan.ts +65 -9
- package/src/cli/fleet-sync-records.ts +113 -0
- package/src/cli/fleet-sync-remote.ts +151 -9
- package/src/cli/fleet-sync-schedule.ts +14 -4
- package/src/cli/fleet-sync-state.ts +28 -16
- package/src/cli/fleet-sync-vault.ts +77 -0
- package/src/cli/fleet-sync-worker.ts +214 -0
- package/src/cli/fleet-sync.ts +150 -19
- package/src/cli/fleet-validate.ts +59 -21
- package/src/cli/fleet-watch.ts +7 -1
- package/src/cli/fleet.ts +87 -5
- package/src/cli/managed-health.ts +25 -1
- package/src/cli/register-fleet.ts +115 -4
- package/src/cli/registry.ts +14 -0
- package/src/cli/tailnet.ts +43 -10
- package/src/cli/ui-data.ts +3 -1
- package/src/cli/worker-usage-report.ts +63 -0
- package/src/clock.ts +42 -0
- package/src/definition.ts +332 -9
- package/src/diagnostics.ts +5 -2
- package/src/exposure.ts +102 -0
- package/src/fleet-config.ts +3 -1
- package/src/git-identity.ts +76 -3
- package/src/ide/ide.ts +5 -0
- package/src/ide/t3.ts +12 -2
- package/src/input-hashes.ts +6 -0
- package/src/machine/machine-class.ts +59 -0
- package/src/machine/platform.ts +90 -11
- package/src/machine/state.ts +37 -15
- package/src/machine/support-matrix.ts +11 -9
- package/src/machine-health.ts +28 -5
- package/src/manager-health.ts +148 -2
- package/src/manager-sync.ts +65 -4
- package/src/monitoring/beszel.ts +8 -8
- package/src/monitoring/enroll.ts +8 -1
- package/src/monitoring/hub-access.ts +10 -4
- package/src/monitoring/systems.ts +138 -0
- package/src/network-definition.ts +13 -4
- package/src/node-converge-linux.ts +136 -27
- package/src/node-converge.ts +13 -3
- package/src/platform/ansible-receipt.ts +17 -2
- package/src/platform/ansible.ts +40 -4
- package/src/platform/manager-host.ts +25 -8
- package/src/platform/resource-verify.ts +18 -7
- package/src/platform/t3-policy.ts +24 -10
- package/src/platform/t3-service.ts +109 -34
- package/src/platform/ubuntu-release.ts +24 -0
- package/src/platform/ubuntu-verify.ts +31 -3
- package/src/platform/ubuntu-worker.ts +58 -0
- package/src/platform/worker-platform.ts +72 -0
- package/src/protocol.ts +1 -1
- package/src/public-reachability.ts +186 -0
- package/src/remote-bootstrap.ts +50 -12
- package/src/remote-operation.ts +18 -4
- package/src/repair-plan.ts +23 -12
- package/src/report.ts +2 -1
- package/src/schedule-check.ts +21 -2
- package/src/secrets.ts +4 -0
- package/src/service-auth.ts +55 -48
- package/src/ssh-connection.ts +141 -0
- package/src/ssh.ts +63 -24
- package/src/state/store.ts +4 -2
- package/src/state/sync.ts +35 -6
- package/src/state/workers.ts +140 -0
- package/src/t3/pairing.ts +59 -0
- package/src/t3/peer-link.ts +333 -0
- package/src/t3/version.ts +1 -1
- package/src/tailnet-cleanup.ts +185 -0
- package/src/tailnet-control.ts +346 -0
- package/src/tailscale.ts +1 -1
- package/src/toolchain-checks.ts +11 -1
- package/src/types.ts +4 -2
- package/src/worker-account.ts +63 -0
- package/src/worker-activity.ts +303 -0
- package/src/worker-capacity.ts +31 -0
- package/src/worker-exposure.ts +388 -0
- package/src/worker-features.ts +201 -0
- package/src/worker-host-checks.ts +71 -20
- package/src/worker-manifest.ts +27 -4
- package/src/worker-parking.ts +70 -0
- package/src/worker-policy.ts +17 -1
- package/src/worker-readiness.ts +15 -3
- package/src/worker-reboot.ts +129 -0
- package/src/worker-records.ts +305 -0
- package/src/worker-seed.ts +16 -11
- package/src/worker-sizing.ts +76 -0
- package/src/cli/schedule-logs.ts +0 -36
package/docs/ubuntu-sync.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
> This document was written for the fleet dev-fleet was extracted from. Paths and
|
|
2
2
|
> commands were updated; some statements still describe that fleet and are being
|
|
3
|
-
> revised. The README is authoritative
|
|
3
|
+
> revised. The README is authoritative, and what a machine must offer to be a
|
|
4
|
+
> worker is listed only in [worker requirements](worker-requirements.md).
|
|
4
5
|
|
|
5
6
|
# Developer fleet: one manager (macOS) and stateless workers (Ubuntu)
|
|
6
7
|
|
|
@@ -10,7 +11,7 @@ What is proven, and where:
|
|
|
10
11
|
|
|
11
12
|
| Evidence | Where |
|
|
12
13
|
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
-
| A bare Ubuntu
|
|
14
|
+
| A bare Ubuntu machine bootstrapped over real SSH, then repeated | `features/fresh-box/fresh-box.feature`, on every pull request (each accepted release, amd64 and arm64) |
|
|
14
15
|
| One run over several machines, one versioned report, no partial receipt | `features/fresh-box/fleet.feature` |
|
|
15
16
|
| Interrupted checkout, package installation and toolchain setup resumed | `features/fresh-box/fleet.feature` |
|
|
16
17
|
| Overlapping runs, a dropped connection, a held APT lock, no DNS, no disk | `features/fresh-box/fleet.feature` |
|
|
@@ -86,9 +87,11 @@ existing SSH aliases, tests strict SSH access, and offers one enrollment
|
|
|
86
87
|
confirmation for the verified connections. The apply approval binds a fingerprint
|
|
87
88
|
of the selected revision, policy and connection records; changed bindings fail
|
|
88
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
|
|
89
|
-
out of band before enrolling trust. Unreachable machines remain listed;
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
out of band before enrolling trust. Unreachable machines remain listed; what a machine
|
|
91
|
+
needs to be reachable the first time (SSH with a key, a known host key, the
|
|
92
|
+
tailnet, or first-boot data that provides them) is listed in
|
|
93
|
+
[worker requirements](worker-requirements.md). No software on the manager can
|
|
94
|
+
bootstrap a disconnected box.
|
|
92
95
|
|
|
93
96
|
A previously configured manager uses saved connection bindings and its approved
|
|
94
97
|
revision. A changed Tailscale device ID is a replacement gate, even if the old
|
|
@@ -177,21 +180,21 @@ independently. Bespoke TypeScript remains only where a native CLI or API has no
|
|
|
177
180
|
declarative owner: T3 service lifecycle, Beszel enrollment tokens, provider
|
|
178
181
|
verification and the CLIProxy singleton.
|
|
179
182
|
|
|
180
|
-
| Policy | Workers (Ubuntu 26.04, stateless)
|
|
181
|
-
| --------------------------- |
|
|
182
|
-
| Never sleep | systemd sleep targets masked; logind lid/idle policy; GNOME power plugin set to `nothing`
|
|
183
|
-
| No automatic logout or lock | GNOME `lock-enabled false`, `idle-delay 0`; designated-user lingering
|
|
184
|
-
| Always-on services | drop-ins `Restart=always`, `StartLimitIntervalSec=0` for ssh, tailscaled, docker, beszel-agent
|
|
185
|
-
| 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)
|
|
186
|
-
| Memory safety | `systemd-oomd` (swap 90%, pressure 60%); `user.slice` `MemoryMax=90%`; each `t3code-*.scope` agent `MemoryMax=30%`, `OOMPolicy=continue`; swap file when none
|
|
187
|
-
| 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
|
|
188
|
-
| Tailscale | pinned APT package, `tailscaled` enabled, native login required
|
|
189
|
-
| Dashboard agent | pinned native binary, hardened `beszel-agent` systemd unit as user `beszel`
|
|
190
|
-
| Credential renewal | user timer `personal-worker-auth` renews the node's own Vault token every 10min; `personal-worker-github` signs it in to GitHub from the stored token ([GitHub](github.md))
|
|
191
|
-
| Noninteractive sudo | validated `/etc/sudoers.d/personal-fleet-USER`
|
|
192
|
-
| 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 |
|
|
193
|
-
| T3 providers | the same managed instances (`fleet/agents/t3.json`) through locked T3 administrator policy
|
|
194
|
-
| Singleton services | never; clients of the manager's services only
|
|
183
|
+
| Policy | Workers (Ubuntu 26.04 or 24.04, stateless) | Manager (macOS, singleton) |
|
|
184
|
+
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| 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 |
|
|
186
|
+
| No automatic logout or lock | GNOME `lock-enabled false`, `idle-delay 0`; designated-user lingering | not managed (the human uses it) |
|
|
187
|
+
| 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) |
|
|
188
|
+
| 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`) |
|
|
189
|
+
| 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 |
|
|
190
|
+
| 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 |
|
|
191
|
+
| Tailscale | pinned APT package, `tailscaled` enabled, native login required | Tailscale app, human login |
|
|
192
|
+
| Dashboard agent | pinned native binary, hardened `beszel-agent` systemd unit as user `beszel` | not installed |
|
|
193
|
+
| Credential renewal | user timer `personal-worker-auth` renews the node's own Vault token every 10min; `personal-worker-github` signs it in to GitHub from the stored token ([GitHub](github.md)) | the human's own Vault and GitHub sessions |
|
|
194
|
+
| Noninteractive sudo | validated `/etc/sudoers.d/personal-fleet-USER` | only `pmset -c`, granted once by `manager-privilege.yml` (`-K`) |
|
|
195
|
+
| T3 worker app | pinned desktop `.deb` is the only T3 process (UI plus embedded server) on a desktop worker, and the pinned server as the lingering user unit `t3code-headless.service` on a headless one (`features.session`; no session is waited for); 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 |
|
|
196
|
+
| 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 |
|
|
197
|
+
| Singleton services | never; clients of the manager's services only | every singleton runs here: CLI proxy (`cliproxy`), Beszel hub (`workstation/monitoring`) |
|
|
195
198
|
|
|
196
199
|
Credential sync is deliberately indirect: no secret is copied between machines.
|
|
197
200
|
Each worker has a Vault token of its own, kept in a document of its own,
|
|
@@ -236,14 +239,58 @@ GNOME lock/idle changes apply to the designated user only. Effective running
|
|
|
236
239
|
session/logind behavior still needs a maintenance/reboot acceptance check; writing
|
|
237
240
|
a file does not prove logout/reboot survival. The sudoers fragment is validated
|
|
238
241
|
with visudo before installation; this user and its agents can administer the
|
|
239
|
-
machine.
|
|
242
|
+
machine.
|
|
243
|
+
|
|
244
|
+
SSH changes validate the complete configuration before reload and restore the
|
|
245
|
+
previous fragments on validation failure. sshd takes keys only and refuses
|
|
246
|
+
root (`10-personal-fleet-exposure.conf`, which sorts before an image's own
|
|
247
|
+
fragments; `SSH_HARDENING_OVERRIDDEN` when `sshd -T` still shows otherwise).
|
|
248
|
+
Existing listeners and socket activation are preserved; unmanaged fragments
|
|
249
|
+
require review. The manager makes a **new** strict SSH connection after apply.
|
|
250
|
+
|
|
251
|
+
### Exposure
|
|
252
|
+
|
|
253
|
+
Every worker, with a public address or without one, admits inbound traffic
|
|
254
|
+
only over loopback and the tailnet (`ansible/ubuntu/tasks/firewall.yml`, the
|
|
255
|
+
`inet personal_fleet` nftables table, loaded at boot by
|
|
256
|
+
`personal-fleet-firewall.service`). Open beyond it: tailscaled's UDP port,
|
|
257
|
+
DHCP replies, ICMP, and what the exposure policy opens. Where Docker is
|
|
258
|
+
installed, a published container port is dropped from outside the tailnet
|
|
259
|
+
before Docker's own chains see it. Docker's and Tailscale's tables are left to
|
|
260
|
+
their owners.
|
|
261
|
+
|
|
262
|
+
The policy is `exposure` in `fleet.json`, fleet-wide under `worker` and per
|
|
263
|
+
worker beside `network`; a field a worker names replaces the fleet-wide one:
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
"exposure": {
|
|
267
|
+
"publicSsh": false,
|
|
268
|
+
"publicPorts": [{ "protocol": "udp", "port": 51820 }],
|
|
269
|
+
"otherListeners": "warn"
|
|
270
|
+
}
|
|
271
|
+
```
|
|
240
272
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
273
|
+
`publicSsh` is break-glass access: SSH stays open beyond the tailnet.
|
|
274
|
+
Without it the public SSH port closes only when the session running the sync
|
|
275
|
+
arrived over the tailnet, after the tailnet path answers a new probe and sshd
|
|
276
|
+
a new connection on the tailnet address; a failed verification restores the
|
|
277
|
+
previous rules (`FIREWALL_TAILNET_UNVERIFIED`). A session from a public
|
|
278
|
+
address, a tailnet that is not up, or a run without a session that finds the
|
|
279
|
+
port open keeps it open, and `worker:exposure` reports
|
|
280
|
+
`WORKER_PUBLIC_SSH_UNDECLARED` until the manager's SSH alias names the
|
|
281
|
+
worker's tailnet name.
|
|
282
|
+
|
|
283
|
+
`worker:exposure` (on the worker) lists listening sockets and fails on one
|
|
284
|
+
bound beyond loopback and the tailnet outside the allow-list (sshd, tailscaled,
|
|
285
|
+
the system's DHCP and name-resolution listeners, `publicPorts`), naming the
|
|
286
|
+
port and its unit. A fleet service there always fails; anything else, such as
|
|
287
|
+
an agent's development server, warns or fails by `otherListeners`.
|
|
288
|
+
`worker:public-reachability` (from the manager, in `fleet-status --workers`)
|
|
289
|
+
tries the SSH, T3 and monitoring ports on the public address Tailscale knows
|
|
290
|
+
for the worker; it never blocks readiness, because the manager may share the
|
|
291
|
+
worker's network. Both are not applicable without an address outside the
|
|
292
|
+
tailnet. Closing the public path on a machine with two interfaces still needs
|
|
293
|
+
the disposable VM or a real machine to prove.
|
|
247
294
|
|
|
248
295
|
The manager is the only configuration coordinator. No per-node pull timer is
|
|
249
296
|
installed. Manager schedule controls use the existing platform adapter:
|
|
@@ -327,7 +374,10 @@ A worker whose T3 has not loaded the staged provider policy, or runs an older
|
|
|
327
374
|
release than the one installed, is restarted only while it runs no agent turn;
|
|
328
375
|
otherwise sync reports the pending restart and the recovery timer applies it
|
|
329
376
|
at the first idle check, failing the check after
|
|
330
|
-
`t3RestartMaxDeferralHours` (`ansible/ubuntu/policy.json`).
|
|
377
|
+
`t3RestartMaxDeferralHours` (`ansible/ubuntu/policy.json`).
|
|
378
|
+
`fleet-sync --restart-busy` passes `t3_restart_busy` to the plays, which then
|
|
379
|
+
restart a busy T3 at once and end its turns; the `--dry-run` plan says, per
|
|
380
|
+
T3 restart, whether running turns end. A pending proxy
|
|
331
381
|
update is installed even when it recreates the gateway. The manager finishes before the workers
|
|
332
382
|
start, so they are verified against the updated proxy. `--dry-run` previews.
|
|
333
383
|
|
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