@crvouga/dev-fleet 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mise/locks/aws-cdk/2.1146.1/aube-lock.yaml +24 -0
- package/.mise/locks/aws-cdk/2.1146.1/package.json +7 -0
- package/.mise/locks/azure-cli/2.91.0~a837e014/pyproject.toml +5 -0
- package/.mise/locks/azure-cli/2.91.0~a837e014/uv.lock +2794 -0
- package/.mise/locks/gemini/0.66.0~84aac8e6/aube-lock.yaml +123 -0
- package/.mise/locks/gemini/0.66.0~84aac8e6/package.json +12 -0
- package/.mise/locks/npm-devcontainers-cli/0.90.0/aube-lock.yaml +24 -0
- package/.mise/locks/npm-devcontainers-cli/0.90.0/package.json +7 -0
- package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/aube-lock.yaml +1432 -0
- package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/package.json +7 -0
- package/.mise/locks/npm-vercel/65.8.0/aube-lock.yaml +3595 -0
- package/.mise/locks/npm-vercel/65.8.0/package.json +7 -0
- package/.mise/locks/npm-wrangler/4.156.0/aube-lock.yaml +895 -0
- package/.mise/locks/npm-wrangler/4.156.0/package.json +7 -0
- package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/aube-lock.yaml +24 -0
- package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/package.json +7 -0
- package/LICENSE +21 -0
- package/README.md +357 -2
- package/agents/antigravity-proxy.ts +3 -0
- package/agents/audit.md +89 -0
- package/agents/claude-proxy +5 -0
- package/agents/claude-proxy.ts +3 -0
- package/agents/codex-proxy.ts +3 -0
- package/agents/grok-proxy.ts +3 -0
- package/agents/managed-proxy.ts +55 -0
- package/agents/opencode-proxy.ts +3 -0
- package/agents/registry.json +209 -0
- package/agents/registry.schema.json +149 -0
- package/agents/setup.md +158 -0
- package/agents/t3.json +40 -0
- package/ansible/macos/manager.yml +30 -0
- package/ansible/macos/policy.json +9 -0
- package/ansible/monitoring/README.md +215 -0
- package/ansible/monitoring/agent-darwin.yml +198 -0
- package/ansible/monitoring/agent.yml +172 -0
- package/ansible/monitoring/beszel-agent.service +42 -0
- package/ansible/monitoring/compose.yml +74 -0
- package/ansible/monitoring/hub-darwin.yml +102 -0
- package/ansible/monitoring/hub-route-darwin.yml +51 -0
- package/ansible/monitoring/lock.json +16 -0
- package/ansible/net/remote-probe.sh +13 -0
- package/ansible/pyproject.toml +12 -0
- package/ansible/ubuntu/authorize-sudo.sh +51 -0
- package/ansible/ubuntu/baseline.yml +89 -0
- package/ansible/ubuntu/disk-gc.py +336 -0
- package/ansible/ubuntu/disk-guard.py +104 -0
- package/ansible/ubuntu/policy.json +52 -0
- package/ansible/ubuntu/recovery.yml +15 -0
- package/ansible/ubuntu/t3/policy.json +7 -0
- package/ansible/ubuntu/t3-desktop.yml +220 -0
- package/ansible/ubuntu/t3-health.sh +33 -0
- package/ansible/ubuntu/t3-policy.yml +76 -0
- package/ansible/ubuntu/t3-route.yml +28 -0
- package/ansible/ubuntu/tailscale.yml +78 -0
- package/ansible/ubuntu/tasks/credentials.yml +47 -0
- package/ansible/ubuntu/tasks/docker.yml +31 -0
- package/ansible/ubuntu/tasks/packages.yml +56 -0
- package/ansible/ubuntu/tasks/power.yml +51 -0
- package/ansible/ubuntu/tasks/recovery.yml +39 -0
- package/ansible/ubuntu/tasks/resources.yml +312 -0
- package/ansible/ubuntu/tasks/services.yml +31 -0
- package/ansible/ubuntu/tasks/ssh.yml +71 -0
- package/ansible/ubuntu/tasks/user.yml +120 -0
- package/ansible/uv.lock +190 -0
- package/bin/devfleet.mjs +36 -0
- package/bootstrap/bootstrap.ps1 +50 -0
- package/bootstrap/bootstrap.sh +80 -0
- package/bootstrap/devfleet +7 -0
- package/bootstrap/docker-desktop.sh +33 -0
- package/bootstrap/doctor.ts +234 -0
- package/bootstrap/fleet-manager-run.sh +103 -0
- package/bootstrap/fleet-remote-bootstrap.sh +258 -0
- package/bootstrap/global-config.ts +171 -0
- package/bootstrap/install-launcher.sh +10 -0
- package/bootstrap/native-aws.ts +86 -0
- package/bootstrap/native-t3.ts +154 -0
- package/bootstrap/npm.ts +358 -0
- package/bootstrap/release-version.ts +157 -0
- package/bootstrap/setup-status.ts +51 -0
- package/bootstrap/setup.ts +88 -0
- package/bootstrap/shell-path.ts +105 -0
- package/bootstrap/verify-t3-archive.py +49 -0
- package/bootstrap/worker-auth.ts +65 -0
- package/bootstrap/worker-github.ts +47 -0
- package/bootstrap/worker-tools.ts +24 -0
- package/cliproxy/AGENTS.md +11 -0
- package/cliproxy/README.md +269 -0
- package/cliproxy/clients/bootstrap.py +143 -0
- package/cliproxy/clients/launcher.py +57 -0
- package/cliproxy/compose.yaml +90 -0
- package/cliproxy/config.template.yaml +70 -0
- package/cliproxy/docs/cloud-adoption.md +16 -0
- package/cliproxy/docs/deployment.png +0 -0
- package/cliproxy/docs/deployment.svg +109 -0
- package/cliproxy/docs/health-workflow.example.yml +185 -0
- package/cliproxy/docs/migration.md +131 -0
- package/cliproxy/docs/model-health-report.schema.json +364 -0
- package/cliproxy/docs/model-health.md +393 -0
- package/cliproxy/image.lock.json +5 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-backup-check.plist +38 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-backup.plist +48 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-fleet.plist +32 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-gc.plist +38 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-monitor.plist +29 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-vault-renew.plist +31 -0
- package/cliproxy/models.json +1618 -0
- package/cliproxy/requirements.lock +78 -0
- package/cliproxy/scripts/accounts.py +38 -0
- package/cliproxy/scripts/audit.py +123 -0
- package/cliproxy/scripts/backup +234 -0
- package/cliproxy/scripts/check.py +34 -0
- package/cliproxy/scripts/checkpoint.py +226 -0
- package/cliproxy/scripts/cli.py +64 -0
- package/cliproxy/scripts/cliproxy_host.py +257 -0
- package/cliproxy/scripts/cloud_state.py +150 -0
- package/cliproxy/scripts/container-health +6 -0
- package/cliproxy/scripts/docker +24 -0
- package/cliproxy/scripts/external-health.py +79 -0
- package/cliproxy/scripts/fleet_config.py +43 -0
- package/cliproxy/scripts/fleet_sync.py +274 -0
- package/cliproxy/scripts/initialize +84 -0
- package/cliproxy/scripts/install-dependencies +48 -0
- package/cliproxy/scripts/install.py +406 -0
- package/cliproxy/scripts/model_health.py +16 -0
- package/cliproxy/scripts/modelhealth/__init__.py +5 -0
- package/cliproxy/scripts/modelhealth/checks.py +429 -0
- package/cliproxy/scripts/modelhealth/cli.py +117 -0
- package/cliproxy/scripts/modelhealth/config.py +186 -0
- package/cliproxy/scripts/modelhealth/emulator.py +536 -0
- package/cliproxy/scripts/modelhealth/expect.py +88 -0
- package/cliproxy/scripts/modelhealth/findings.py +183 -0
- package/cliproxy/scripts/modelhealth/protocol.py +330 -0
- package/cliproxy/scripts/modelhealth/redact.py +28 -0
- package/cliproxy/scripts/modelhealth/report.py +150 -0
- package/cliproxy/scripts/modelhealth/runner.py +390 -0
- package/cliproxy/scripts/modelhealth/scenario.py +131 -0
- package/cliproxy/scripts/modelhealth/schema.py +63 -0
- package/cliproxy/scripts/modelhealth/simulate.py +111 -0
- package/cliproxy/scripts/modelhealth/transport.py +213 -0
- package/cliproxy/scripts/monitor +68 -0
- package/cliproxy/scripts/ops +527 -0
- package/cliproxy/scripts/public-endpoint +127 -0
- package/cliproxy/scripts/recover_node.py +177 -0
- package/cliproxy/scripts/restore-verify +60 -0
- package/cliproxy/scripts/retention.py +144 -0
- package/cliproxy/scripts/service-start +7 -0
- package/cliproxy/scripts/validate-config +56 -0
- package/cliproxy/scripts/vault-access.py +57 -0
- package/cliproxy/scripts/vault-backup-login +55 -0
- package/cliproxy/scripts/vault-recovery +56 -0
- package/cliproxy/scripts/vault-renew +30 -0
- package/cliproxy/systemd/cliproxy-backup-check.service +10 -0
- package/cliproxy/systemd/cliproxy-backup-check.timer +8 -0
- package/cliproxy/systemd/cliproxy-backup.service +13 -0
- package/cliproxy/systemd/cliproxy-backup.timer +8 -0
- package/cliproxy/systemd/cliproxy-fleet.service +17 -0
- package/cliproxy/systemd/cliproxy-gc.service +13 -0
- package/cliproxy/systemd/cliproxy-gc.timer +8 -0
- package/cliproxy/systemd/cliproxy-monitor.service +8 -0
- package/cliproxy/systemd/cliproxy-monitor.timer +7 -0
- package/cliproxy/systemd/cliproxy-vault-renew.service +8 -0
- package/cliproxy/systemd/cliproxy-vault-renew.timer +7 -0
- package/cliproxy/vendor/LICENSE +22 -0
- package/cliproxy/vendor/config.upstream.yaml +1349 -0
- package/docs/consumer-setup.md +408 -0
- package/docs/declared.md +89 -0
- package/docs/machine-setup.md +399 -0
- package/docs/operations.md +276 -0
- package/docs/releasing.md +103 -0
- package/docs/state.md +254 -0
- package/docs/topology.md +67 -0
- package/docs/ubuntu-sync.md +351 -0
- package/docs/worker-contract.md +67 -0
- package/examples/fleet.json +100 -0
- package/examples/roster.json +13 -0
- package/llms.txt +35 -0
- package/mise.lock +1521 -0
- package/mise.toml +110 -0
- package/package.json +74 -4
- package/schema/fleet.schema.json +420 -0
- package/schema/roster.schema.json +77 -0
- package/src/agents/account-relay.ts +111 -0
- package/src/agents/accounts.ts +85 -0
- package/src/agents/audit.ts +77 -0
- package/src/agents/cli.ts +13 -0
- package/src/agents/command.ts +204 -0
- package/src/agents/converge.ts +123 -0
- package/src/agents/doctor.ts +107 -0
- package/src/agents/errors.ts +2 -0
- package/src/agents/files.ts +73 -0
- package/src/agents/fleet-verify.ts +165 -0
- package/src/agents/launch.ts +290 -0
- package/src/agents/native-verify.ts +371 -0
- package/src/agents/provider-errors.ts +57 -0
- package/src/agents/registry.ts +61 -0
- package/src/agents/render.ts +110 -0
- package/src/agents/round-trip.ts +192 -0
- package/src/agents/secrets.ts +13 -0
- package/src/agents/toolchain.ts +34 -0
- package/src/agents/verify-cache.ts +103 -0
- package/src/agents/verify.ts +127 -0
- package/src/auth-checks.ts +163 -0
- package/src/cli/agent-guide.ts +129 -0
- package/src/cli/app.ts +122 -0
- package/src/cli/cli.ts +28 -0
- package/src/cli/command-index.ts +57 -0
- package/src/cli/contracts.ts +25 -0
- package/src/cli/fleet-reference.ts +17 -0
- package/src/cli/fleet-state.ts +499 -0
- package/src/cli/fleet-sync-checkout.ts +130 -0
- package/src/cli/fleet-sync-enrollment.ts +140 -0
- package/src/cli/fleet-sync-forward.ts +134 -0
- package/src/cli/fleet-sync-manager.ts +249 -0
- package/src/cli/fleet-sync-monitoring.ts +185 -0
- package/src/cli/fleet-sync-output.ts +317 -0
- package/src/cli/fleet-sync-privilege.ts +27 -0
- package/src/cli/fleet-sync-progress.ts +109 -0
- package/src/cli/fleet-sync-remote.ts +242 -0
- package/src/cli/fleet-sync-report.ts +26 -0
- package/src/cli/fleet-sync-schedule.ts +138 -0
- package/src/cli/fleet-sync-state.ts +86 -0
- package/src/cli/fleet-sync-vault.ts +166 -0
- package/src/cli/fleet-sync.ts +375 -0
- package/src/cli/fleet.ts +165 -0
- package/src/cli/managed-health.ts +219 -0
- package/src/cli/node-deployment.ts +34 -0
- package/src/cli/node-sync.ts +121 -0
- package/src/cli/node-t3.ts +31 -0
- package/src/cli/options.ts +141 -0
- package/src/cli/probe-source.ts +7 -0
- package/src/cli/program.ts +185 -0
- package/src/cli/project.ts +51 -0
- package/src/cli/register-fleet.ts +296 -0
- package/src/cli/registry.ts +185 -0
- package/src/cli/repair-apply.ts +67 -0
- package/src/cli/repair-guard.ts +11 -0
- package/src/cli/repair-inspect.ts +150 -0
- package/src/cli/repair-script.ts +76 -0
- package/src/cli/repair.ts +209 -0
- package/src/cli/roster.ts +105 -0
- package/src/cli/search.ts +35 -0
- package/src/cli/settings.ts +67 -0
- package/src/cli/source-operations.ts +102 -0
- package/src/cli/t3/command.ts +90 -0
- package/src/cli/t3/register.ts +55 -0
- package/src/cli/tailnet.ts +127 -0
- package/src/cli/ui-actions.ts +133 -0
- package/src/cli/ui-controller.ts +113 -0
- package/src/cli/ui-data.ts +106 -0
- package/src/cli/ui-execute.ts +163 -0
- package/src/cli/ui-repairs.ts +34 -0
- package/src/cli/ui-reports.ts +211 -0
- package/src/cli/ui-targets.ts +100 -0
- package/src/cli/ui-terminal.ts +109 -0
- package/src/cli/ui-types.ts +49 -0
- package/src/cli/ui-wizard.ts +101 -0
- package/src/cli/ui.ts +9 -0
- package/src/cli/worker-health-output.ts +37 -0
- package/src/config.ts +186 -0
- package/src/configure.ts +255 -0
- package/src/definition.ts +290 -0
- package/src/diagnostics.ts +92 -0
- package/src/doctor-check.ts +10 -0
- package/src/errors.ts +1 -0
- package/src/fingerprint.ts +120 -0
- package/src/fleet-config.ts +174 -0
- package/src/input-hashes.ts +73 -0
- package/src/machine/local-source.ts +52 -0
- package/src/machine/lock-error.ts +2 -0
- package/src/machine/platform.ts +44 -0
- package/src/machine/source.ts +68 -0
- package/src/machine/state.ts +173 -0
- package/src/machine-health.ts +95 -0
- package/src/managed-source.ts +47 -0
- package/src/manager-failure.ts +48 -0
- package/src/manager-sync.ts +405 -0
- package/src/manager.ts +62 -0
- package/src/monitoring/agent-evidence.ts +146 -0
- package/src/monitoring/apply.ts +183 -0
- package/src/monitoring/beszel.ts +198 -0
- package/src/monitoring/enroll.ts +111 -0
- package/src/monitoring/hub-access.ts +114 -0
- package/src/monitoring/hub-backup.ts +121 -0
- package/src/monitoring/hub-viewer.ts +142 -0
- package/src/monitoring/hub.ts +93 -0
- package/src/monitoring/manager-agent.ts +73 -0
- package/src/monitoring/manager-hub.ts +248 -0
- package/src/monitoring/remote.ts +65 -0
- package/src/network-definition.ts +83 -0
- package/src/node-converge-linux.ts +374 -0
- package/src/node-converge-shared.ts +83 -0
- package/src/node-converge.ts +40 -0
- package/src/onboard.ts +35 -0
- package/src/package-info.ts +45 -0
- package/src/platform/ansible-receipt.ts +128 -0
- package/src/platform/ansible.ts +66 -0
- package/src/platform/darwin.ts +203 -0
- package/src/platform/detect.ts +11 -0
- package/src/platform/fallback.ts +110 -0
- package/src/platform/index.ts +38 -0
- package/src/platform/launchd.ts +98 -0
- package/src/platform/linux.ts +139 -0
- package/src/platform/ps.ts +45 -0
- package/src/platform/resource-verify.ts +132 -0
- package/src/platform/systemd-user.ts +95 -0
- package/src/platform/t3-policy.ts +137 -0
- package/src/platform/t3-route.ts +68 -0
- package/src/platform/t3-service.ts +75 -0
- package/src/platform/types.ts +123 -0
- package/src/platform/ubuntu-audit.ts +66 -0
- package/src/platform/ubuntu-verify.ts +105 -0
- package/src/platform/windows.ts +148 -0
- package/src/power-recovery.ts +55 -0
- package/src/probe.ts +51 -0
- package/src/protocol.ts +119 -0
- package/src/proxy-converge.ts +85 -0
- package/src/remote-bootstrap.ts +210 -0
- package/src/remote-entry.ts +89 -0
- package/src/remote-lock.ts +12 -0
- package/src/repair-catalog.ts +206 -0
- package/src/repair-plan.ts +78 -0
- package/src/repair-runner.ts +84 -0
- package/src/report.ts +71 -0
- package/src/runner.ts +104 -0
- package/src/secrets.ts +169 -0
- package/src/service-auth.ts +115 -0
- package/src/ssh.ts +135 -0
- package/src/state/bucket.ts +208 -0
- package/src/state/check.ts +74 -0
- package/src/state/connection.ts +92 -0
- package/src/state/enrollment.ts +45 -0
- package/src/state/legacy.ts +89 -0
- package/src/state/litestream.ts +143 -0
- package/src/state/operator.ts +20 -0
- package/src/state/paths.ts +40 -0
- package/src/state/roster.ts +62 -0
- package/src/state/store.ts +169 -0
- package/src/state/sync.ts +352 -0
- package/src/sync.ts +104 -0
- package/src/t3/accounts.ts +81 -0
- package/src/t3/desired.ts +165 -0
- package/src/t3/files.ts +81 -0
- package/src/t3/names-check.ts +53 -0
- package/src/t3/names.ts +155 -0
- package/src/t3/plan.ts +82 -0
- package/src/t3/providers.ts +74 -0
- package/src/t3/reconcile.ts +123 -0
- package/src/t3-endpoint.ts +104 -0
- package/src/t3-linked-check.ts +123 -0
- package/src/tailnet-health.ts +231 -0
- package/src/tailnet-matching.ts +37 -0
- package/src/tailscale.ts +141 -0
- package/src/toolchain-checks.ts +108 -0
- package/src/topology.ts +24 -0
- package/src/transport.ts +122 -0
- package/src/types.ts +99 -0
- package/src/vault-config.ts +38 -0
- package/src/worker-access-checks.ts +168 -0
- package/src/worker-capacity.ts +34 -0
- package/src/worker-host-checks.ts +181 -0
- package/src/worker-policy.ts +64 -0
- package/src/worker-readiness.ts +64 -0
- package/src/workstation-checks.ts +29 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
> This document was written for the fleet dev-fleet was extracted from. Paths and
|
|
2
|
+
> commands were updated; some statements still describe that fleet and are being
|
|
3
|
+
> revised. The README is authoritative.
|
|
4
|
+
|
|
5
|
+
# Developer fleet
|
|
6
|
+
|
|
7
|
+
`@pkgs/fleet` diagnoses development computers: SSH, VS Code Remote-SSH,
|
|
8
|
+
per-machine authentication, T3 Connect and shared toolchain/dependency drift.
|
|
9
|
+
It is a local CLI/library, not a daemon or cloud manager. It reuses the
|
|
10
|
+
existing mise/workstation owners rather than introducing another installer.
|
|
11
|
+
|
|
12
|
+
The fleet is one singleton **manager** (the human's macOS machine, which runs
|
|
13
|
+
`fleet-sync` and every singleton service such as the CLI proxy and Beszel hub)
|
|
14
|
+
and stateless Linux **workers**. See [topology](topology.md).
|
|
15
|
+
|
|
16
|
+
See [Ubuntu fleet-sync implementation and operator status](ubuntu-sync.md) for
|
|
17
|
+
the manager-driven public command and its outstanding full-machine gates.
|
|
18
|
+
|
|
19
|
+
## Worker contract
|
|
20
|
+
|
|
21
|
+
Use `devfleet fleet-sync` to reconcile all declared workers and
|
|
22
|
+
`devfleet fleet-status --workers` to inspect remaining gaps. See
|
|
23
|
+
[worker-contract.md](worker-contract.md) for bootstrap, ownership and the exact
|
|
24
|
+
scope of readiness. `fleet-status` without flags diagnoses the local roster.
|
|
25
|
+
Workers are Ubuntu 26.04; the manager is macOS and never sleeps on AC power
|
|
26
|
+
(`ansible/macos/manager.yml`). Machine policy (never sleep, no automatic
|
|
27
|
+
logout, always-on Tailscale/SSH/Docker/Beszel, memory and disk safety, per-node
|
|
28
|
+
credential renewal) is
|
|
29
|
+
declared in Ansible and measured by `fleet-status --workers`, which also verifies
|
|
30
|
+
the centralized Beszel dashboard. See the
|
|
31
|
+
[platform matrix](ubuntu-sync.md#machine-policy-by-platform).
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
Run from the repository root after `bun install --frozen-lockfile`:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
devfleet fleet-node-add --id dev-linux --ssh dev-linux --path /home/dev/workspace --json
|
|
39
|
+
devfleet fleet-node-add --id dev-linux --ssh dev-linux --path /home/dev/workspace --apply --yes --json
|
|
40
|
+
devfleet fleet-nodes --json # validate the roster; no network
|
|
41
|
+
devfleet fleet-nodes --onboard --node dev-linux --json
|
|
42
|
+
devfleet fleet-status --json # manager + all roster nodes
|
|
43
|
+
devfleet fleet-status --node dev-linux --json
|
|
44
|
+
devfleet fleet-status --local --json # no roster/SSH needed
|
|
45
|
+
devfleet fleet-repair --node dev-linux --repair workspace --json # plan only
|
|
46
|
+
devfleet fleet-repair --node dev-linux --repair workspace --apply --yes --json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This private package exposes a TypeScript API from `@pkgs/fleet`; the public
|
|
50
|
+
executable is `personal` (`bun run personal` from the checkout). Schema, roster
|
|
51
|
+
listing, onboarding and repair previews do not contact nodes or auth services.
|
|
52
|
+
Status never opens a login flow.
|
|
53
|
+
|
|
54
|
+
## Roster
|
|
55
|
+
|
|
56
|
+
The roster is kept in the [fleet state](state.md) and replicated to its bucket.
|
|
57
|
+
A `.fleet.local.json` found from the current working directory (gitignored), or
|
|
58
|
+
`--config PATH`, names a roster file instead (keep it out of Git yourself). This describes
|
|
59
|
+
**existing developer computers**, not desired cloud infrastructure. OpenTofu
|
|
60
|
+
remains the sole cloud resource owner; fleet creates no networks, hosts or secrets.
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"$schema": "./packages/fleet/schema.json",
|
|
65
|
+
"version": 1,
|
|
66
|
+
"nodes": [
|
|
67
|
+
{
|
|
68
|
+
"id": "dev-linux",
|
|
69
|
+
"ssh": "dev-linux",
|
|
70
|
+
"workspace": "/home/developer/workspace",
|
|
71
|
+
"runtime": "/home/developer/.local/share/mise/shims/node",
|
|
72
|
+
"required": ["github", "vault", "t3", "toolchain"]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- `id`: unique lowercase name, up to 63 characters; 1–64 nodes per roster.
|
|
79
|
+
- `ssh`: existing alias/hostname. Put User, Port, IdentityFile, ProxyJump and
|
|
80
|
+
keychain configuration in normal SSH config. user@host/IPv6 should use aliases.
|
|
81
|
+
- `workspace`: absolute POSIX checkout path. Spaces/apostrophes work; relative
|
|
82
|
+
paths, tilde expansion and control characters are rejected.
|
|
83
|
+
- `runtime`: optional absolute Node/Bun executable or mise shim. Defaults to `node` on
|
|
84
|
+
the **non-login SSH PATH**. Configure explicitly when GUI terminals work but SSH
|
|
85
|
+
cannot find Node. A shim also needs mise's environment/PATH working.
|
|
86
|
+
- `required`: `github`, `vault`, `t3`, `vscode`, `toolchain`. Defaults to all except
|
|
87
|
+
vscode. Set vscode on editor/manager machines, not just on headless targets.
|
|
88
|
+
`[]` still checks checkout/dependencies/SSH. Unknown fields fail closed.
|
|
89
|
+
|
|
90
|
+
The manager reference defaults to where the CLI runs from: the published
|
|
91
|
+
package, or the dev-fleet checkout a source run is started in. `--workspace PATH`
|
|
92
|
+
changes that reference/local sync target. Nodes are compared to **that checkout**,
|
|
93
|
+
including uncommitted lockfile edits, not an implicitly fetched origin/main.
|
|
94
|
+
Manager capabilities default to all five; headless managers can pass
|
|
95
|
+
`--require github,vault,t3,toolchain` or `--require none`. This does not override
|
|
96
|
+
remote nodes' required lists. Run `fleet-nodes --schema` for editor/agent validation.
|
|
97
|
+
|
|
98
|
+
## Evidence and diagnosis
|
|
99
|
+
|
|
100
|
+
| Check | Evidence / next action |
|
|
101
|
+
| ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
102
|
+
| `workspace`, `git:clean` | HEAD, input availability and dirty-tree status; no paths/diffs/file contents logged. |
|
|
103
|
+
| `revision`, `sync:*` | Compare exact HEAD and SHA-256 of package.json, bun.lock, mise.toml, mise.lock against manager. Missing inputs never pass. |
|
|
104
|
+
| `dependencies` | Last successful fleet frozen-sync receipt matches input hashes. Missing receipt is unverified, not in-sync. |
|
|
105
|
+
| `auth:toolchain` | Existing bootstrap/doctor.ts runs in the node checkout with its Bun, bounded to 90s. Use personal workstation tools check --json for individual pins. |
|
|
106
|
+
| `ssh:agent` | Manager agent exposes an identity; missing agent warns because IdentityFile keys may still work. |
|
|
107
|
+
| `ssh:session` | Fresh batch SSH session actually runs the Node/Bun probe. No reused multiplexed connection or agent forwarding. |
|
|
108
|
+
| `ssh:host-key`, `ssh:dns`, `ssh:auth`, `ssh:network`, `ssh:runtime`, `ssh:timeout` | Classified failure and targeted remedy, without raw logs. |
|
|
109
|
+
| `auth:github` | gh auth status --active succeeds on this machine; not proof of Git SSH or every repository permission. |
|
|
110
|
+
| `auth:vault` | Server-side vault token lookup succeeds in this context; not proof of access to every secret path. |
|
|
111
|
+
| `auth:vscode` | code CLI finds the local Remote-SSH extension. Other profiles/Cursor/GUI PATH/agents can differ. |
|
|
112
|
+
| `t3:settings` | Connect explicitly enabled and not revoked in t3 status --json; settings are not connectivity. |
|
|
113
|
+
| `t3:api` | Read-only t3 environment list --json succeeds against the local API; no pairing/token minting. |
|
|
114
|
+
| `t3:end-to-end`, `vscode:forwarding` | SKIP: actual relay/peer authorization and VS Code tunnel/server installation require operator testing. |
|
|
115
|
+
|
|
116
|
+
An SSH shell is not proof that VS Code or T3 Connect works. Diagnostics check
|
|
117
|
+
**directed manager → node** connectivity, not all pairs. Run from each intended
|
|
118
|
+
manager to check reverse directions. No host-key acceptance, known_hosts edits,
|
|
119
|
+
sshd changes, agent forwarding, password retries or credential copying happen.
|
|
120
|
+
SSH config/executables/hosts are trusted operator inputs; configured ProxyCommand
|
|
121
|
+
or Match commands can execute the operator's own code.
|
|
122
|
+
|
|
123
|
+
Fingerprints also cover all flat workspace package manifests and the files under
|
|
124
|
+
`.mise/locks`, so uncommitted dependency/sidecar edits cannot hide behind an
|
|
125
|
+
unchanged root lockfile. Missing/unreadable inputs cannot prove synchronization.
|
|
126
|
+
|
|
127
|
+
The receipt (`node_modules/.fleet-sync.json`) is historical evidence of completed
|
|
128
|
+
frozen setup, **not a full integrity audit**. Manual node_modules edits/deletions
|
|
129
|
+
can invalidate that assumption; resync when in doubt. Native package builds still
|
|
130
|
+
need normal repository validation. A green install is not a healthy fleet.
|
|
131
|
+
|
|
132
|
+
## Add a computer
|
|
133
|
+
|
|
134
|
+
1. Review and run the shared [machine bootstrap](../bootstrap/bootstrap.sh) on
|
|
135
|
+
the new machine. Reopen terminal/editor. Do not invent a second installer.
|
|
136
|
+
2. As operator, enable OS SSH access on a trusted private network. Generate a
|
|
137
|
+
distinct machine key, authorize only its public key and verify the server
|
|
138
|
+
fingerprint out of band. Never expose public SSH or copy private keys/auth
|
|
139
|
+
folders. Cloud provisioning remains an OpenTofu task.
|
|
140
|
+
3. Configure the manager's SSH alias, append the node to the local roster,
|
|
141
|
+
then run `fleet-nodes` and `fleet-nodes --onboard --node ID --json`. Onboarding
|
|
142
|
+
emits a ready-to-edit SSH stanza, VS Code settings and ordered instructions;
|
|
143
|
+
it does not overwrite user config. Test `ssh ALIAS` manually first.
|
|
144
|
+
4. Ensure noninteractive shell startup is quiet and Node (or explicit Bun runtime) is reachable. Fleet
|
|
145
|
+
bundles/streams its own dependency-free probe over stdin: the remote computer
|
|
146
|
+
needs Node or Bun, **not a matching installation of this package** or its dependencies.
|
|
147
|
+
5. Authenticate separately: GitHub, Git SSH, Vault, coding providers and T3 pairing
|
|
148
|
+
are distinct sessions. Use gh auth login/setup-git, approved vault login and
|
|
149
|
+
operator-driven t3 up --connect as needed. Never put secrets in reports.
|
|
150
|
+
Workstation/provider configuration remains an explicit `personal workstation sync` operation.
|
|
151
|
+
6. In VS Code's local profile install Remote-SSH, select the same SSH binary,
|
|
152
|
+
alias/config, enable its login terminal temporarily and inspect Output →
|
|
153
|
+
Remote - SSH. Restart after PATH/agent changes. Check disk space, remote
|
|
154
|
+
server prerequisites and TCP forwarding policy. Onboarding includes a
|
|
155
|
+
loopback-only forwarding test against a known service, not a public listener.
|
|
156
|
+
7. Run doctor, review remedies and explicitly sync if needed. Actually open the
|
|
157
|
+
remote workspace in VS Code and test the linked peer in T3 Settings →
|
|
158
|
+
Connections. These are the end-to-end acceptance gates.
|
|
159
|
+
|
|
160
|
+
Remote nodes require macOS/Linux/WSL and a **POSIX SSH shell**. Native Windows
|
|
161
|
+
supports local commands/diagnostics; use a WSL SSH endpoint for remote checks.
|
|
162
|
+
Fleet does not guess cmd.exe/PowerShell quoting. Use the existing native Windows
|
|
163
|
+
bootstrap and run local reports when a WSL endpoint is unavailable.
|
|
164
|
+
|
|
165
|
+
## Explicit sync
|
|
166
|
+
|
|
167
|
+
`sync` is a plan unless **both --apply --yes** are present. Selected nodes apply
|
|
168
|
+
serially; diagnostics run at most four remote probes concurrently.
|
|
169
|
+
|
|
170
|
+
- `--pull` requires a clean attached branch, then runs git pull --ff-only against
|
|
171
|
+
its upstream. No stash/reset/force/rebase/discard. Without it, the branch stays.
|
|
172
|
+
- Invalidate the old receipt, then delegate to
|
|
173
|
+
`mise exec --locked bun -- bun bootstrap/setup.ts --non-interactive`.
|
|
174
|
+
This existing owner installs locked tools, publishes user mise config, sets
|
|
175
|
+
shell PATH and installs frozen Bun dependencies. Review OS-specific effects
|
|
176
|
+
and existing config conflict guards in the shared bootstrap runbook.
|
|
177
|
+
- Record input hashes only after success and stable checkout inputs. Failed
|
|
178
|
+
setup can leave partial installs; no rollback is claimed. Trusted checkout
|
|
179
|
+
package lifecycle scripts can execute during installation.
|
|
180
|
+
- No other application repos, credentials, provider settings or cloud resources
|
|
181
|
+
are synchronized. `personal workstation update`/`personal workstation sync` remain explicit broader convergence.
|
|
182
|
+
- Run doctor again afterward; successful sync does not prove connectivity/auth.
|
|
183
|
+
|
|
184
|
+
Subprocesses have deadlines and a combined 1 MiB output cap. SSH probes default
|
|
185
|
+
to 120s per node; --timeout MS allows 1s–30m. Applied sync defaults to a 25m SSH
|
|
186
|
+
envelope and 20m setup deadline. A killed/disconnected SSH session does **not**
|
|
187
|
+
guarantee remote installers stop: inspect the node before retrying. POSIX local
|
|
188
|
+
subprocess groups are killed at their deadline.
|
|
189
|
+
|
|
190
|
+
## Agent contract
|
|
191
|
+
|
|
192
|
+
`--json` emits one versioned document on stdout, with no progress/auth/child
|
|
193
|
+
output. Bun's script runner may print its command to stderr. Reports have
|
|
194
|
+
`version: 1`, command, ok, generatedAt, nodes and pass/warn/fail/skip counts.
|
|
195
|
+
Node checks have stable id, status, safe summary and optional remedy.action;
|
|
196
|
+
snapshots include platform and revision/hash fingerprints. Preserve skips and
|
|
197
|
+
warnings: neither is a pass. A plan is not completed work.
|
|
198
|
+
|
|
199
|
+
Exit 0: no failing checks (warnings/skips remain visible). Exit 1: health or
|
|
200
|
+
operation failure. Exit 2: usage/config error. `doctor --strict` also fails on
|
|
201
|
+
warnings/skips, including intentional end-to-end skips: it is a **coverage gate**,
|
|
202
|
+
not a way to certify the whole fleet from one shell. Usage errors use
|
|
203
|
+
`code: invalid_usage`; unexpected errors use `operation_failed` without exposing
|
|
204
|
+
exception strings. Fleet never prompts; --non-interactive is accepted.
|
|
205
|
+
|
|
206
|
+
No telemetry or report file is written automatically. Reports still contain
|
|
207
|
+
aliases/hashes: review before sharing publicly. For monitoring, schedule read-only
|
|
208
|
+
`devfleet fleet-status --json` in your existing task runner and alert on exit 1/2;
|
|
209
|
+
do not schedule automatic sync/repairs. `bun run health` remains the separate
|
|
210
|
+
single read-only **cloud platform** report.
|
|
211
|
+
|
|
212
|
+
## Development
|
|
213
|
+
|
|
214
|
+
```sh
|
|
215
|
+
bun run --cwd packages/fleet tc
|
|
216
|
+
bun run --cwd packages/fleet lint
|
|
217
|
+
bun run --cwd packages/fleet test
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Tests inject results/use temporary fixtures; they do not contact nodes, login or
|
|
221
|
+
change global config. The transport smoke test runs the bundled probe locally
|
|
222
|
+
against a temporary checkout, not an SSH server.
|
|
223
|
+
|
|
224
|
+
## Tailscale fleet-status --tailnet in the public CLI
|
|
225
|
+
|
|
226
|
+
`devfleet fleet-discover --json` lists computers visible to the local signed-in
|
|
227
|
+
Tailscale daemon, including offline devices, while explicitly excluding iOS,
|
|
228
|
+
Android and tvOS. `devfleet fleet-status --tailnet --json` checks them all and reports
|
|
229
|
+
unregistered/ambiguous computers and registered nodes missing from the inventory.
|
|
230
|
+
Register actual SSH aliases and checkout paths; the checker does not guess them.
|
|
231
|
+
Existing verified-host-key SSH policy and bounded probes apply. Tailscale daemon
|
|
232
|
+
health errors invalidate complete discovery. ACL-hidden devices cannot be certified.
|
|
233
|
+
|
|
234
|
+
Health probes each registered remote node's declared `required` capabilities; it
|
|
235
|
+
no longer adds cloud-service logins or direct provider authentication implicitly.
|
|
236
|
+
The manager default covers T3, toolchain and workstation configuration.
|
|
237
|
+
Explicit `services` requirements retain Railway, Vercel, Cloudflare, AWS and npm
|
|
238
|
+
checks. No raw account/auth output is retained. Version checks keep
|
|
239
|
+
per-tool results, including the expected pinned version when available, and require
|
|
240
|
+
Docker Engine/Compose/Buildx readiness. `workstation` delegates to read-only
|
|
241
|
+
`personal workstation doctor --json --non-interactive`; provider configuration presence is not live
|
|
242
|
+
authorization. Warnings/skips fail the strict whole-tailnet health coverage gate.
|
|
243
|
+
|
|
244
|
+
## Repair planning and shared access
|
|
245
|
+
|
|
246
|
+
The public CLI exposes `devfleet fleet-repair --list` (static catalog) and
|
|
247
|
+
`devfleet fleet-repair` (read-only inspection/plan by default). The repair library
|
|
248
|
+
in this package only executes allowlisted existing owner commands. Native
|
|
249
|
+
account logins and unknown/manual checks always remain operator steps.
|
|
250
|
+
|
|
251
|
+
Use `--apply --yes` only after review; `--plan-hash` binds application to the
|
|
252
|
+
previewed steps, targets and checkout/shared-input fingerprints. The remote
|
|
253
|
+
protocol independently requires explicit apply, a supported repair action and
|
|
254
|
+
expected input fingerprints. It preserves verified SSH and disables forwarding.
|
|
255
|
+
After owner commands, the manager obtains fresh health checks. Successful
|
|
256
|
+
installation never substitutes for authentication or resource authorization.
|
|
257
|
+
|
|
258
|
+
The workstation owner reads permitted shared provider keys from each machine's
|
|
259
|
+
existing Vault session. For process-scoped shared secret access, use the existing
|
|
260
|
+
`personal infra-vault run --config dev -- COMMAND` workflow on that machine with its intended
|
|
261
|
+
project/config. Infrastructure, access policy and secret seeding remain owned by
|
|
262
|
+
OpenTofu. No OAuth/session/keychain/SSH credential transfer is implemented.
|
|
263
|
+
|
|
264
|
+
All commands are in the `devfleet` entry point.
|
|
265
|
+
|
|
266
|
+
## Reviewed workstation configuration rollout
|
|
267
|
+
|
|
268
|
+
`devfleet fleet-node-sync --revision FULL_SHA --dry-run` previews this node's
|
|
269
|
+
reviewed configuration; without `--dry-run` it applies. `devfleet fleet-sync
|
|
270
|
+
--revision FULL_SHA` rolls the same reviewed revision out to the enrolled fleet
|
|
271
|
+
after approval. The [setup runbook](../agents/setup.md)
|
|
272
|
+
covers canaries, offline/partial failures, per-node receipts and rollback. Both
|
|
273
|
+
delegate rendering to personal workstation and installation to existing mise/bootstrap.
|
|
274
|
+
They never reset the developer checkout or transfer credentials. Status delegates
|
|
275
|
+
managed-profile drift evidence to the workstation owner; historical rollout receipts
|
|
276
|
+
alone are not a live readiness assertion.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
dev-fleet is published to npm as `@crvouga/dev-fleet`. The package is how
|
|
4
|
+
dev-fleet is used: a fleet is a project that depends on it and runs `devfleet`
|
|
5
|
+
from there (see the [README](../README.md#how-you-use-it)). Nobody clones this
|
|
6
|
+
repository to run a fleet.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npm install --save-dev --save-exact @crvouga/dev-fleet
|
|
10
|
+
npx devfleet fleet-sync
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The package ships the TypeScript sources, the Ansible policy, the bootstrap
|
|
14
|
+
scripts and the proxy scripts, because the CLI reads them at run time. Node
|
|
15
|
+
will not run TypeScript from `node_modules`, so the `devfleet` entry is a small
|
|
16
|
+
JavaScript launcher (`bin/devfleet.mjs`) that runs the CLI on the Bun this
|
|
17
|
+
release pins (a dependency of the package), falling back to a Bun on `PATH`.
|
|
18
|
+
|
|
19
|
+
Machines do not run the package. `fleet-sync` rebuilds every machine from one
|
|
20
|
+
git commit of this repository, and the package says which: the Release
|
|
21
|
+
workflow writes the commit it publishes from into the package's `package.json`
|
|
22
|
+
as `gitHead` ([`src/package-info.ts`](../src/package-info.ts) reads it). Run
|
|
23
|
+
from the package, `fleet-sync` fetches that commit from the repository
|
|
24
|
+
`fleet.json` names under `source` into a cache the manager owns, and converges
|
|
25
|
+
the manager and every worker from checkouts of it. So a version of the package
|
|
26
|
+
is a revision of the fleet, and the commit a release names must stay reachable
|
|
27
|
+
in the source repository.
|
|
28
|
+
|
|
29
|
+
## What a release is
|
|
30
|
+
|
|
31
|
+
Every merge to `main` is a release. When CI passes on `main`, the
|
|
32
|
+
[Release workflow](../.github/workflows/release.yml) picks the next version from
|
|
33
|
+
the commits since the newest `vX.Y.Z` tag (or, before there is one, the newest
|
|
34
|
+
version on npm)
|
|
35
|
+
([`bootstrap/release-version.ts`](../bootstrap/release-version.ts)), publishes it
|
|
36
|
+
to `@crvouga/dev-fleet` with provenance, pushes the tag and creates the GitHub
|
|
37
|
+
Release. The commit messages choose the bump:
|
|
38
|
+
|
|
39
|
+
| Commits since the last tag | Bump |
|
|
40
|
+
| ---------------------------------------------- | ------------------------------------ |
|
|
41
|
+
| any `feat!:`, `fix(x)!:` or `BREAKING CHANGE:` | major (minor while the major is `0`) |
|
|
42
|
+
| otherwise any `feat:` | minor |
|
|
43
|
+
| otherwise | patch |
|
|
44
|
+
|
|
45
|
+
`package.json` stays at `0.0.0-development` and carries no `gitHead`; the
|
|
46
|
+
workflow stamps the version and the commit into the package it publishes, so
|
|
47
|
+
nobody edits a version or runs `npm publish` by hand after the one-time setup
|
|
48
|
+
below. Merges that land while a release runs
|
|
49
|
+
wait, and the newest one releases everything since the last tag. A run that
|
|
50
|
+
cannot release because publishing was never set up (no tag and nothing on
|
|
51
|
+
npm) fails, and the run summary says why; a merge with nothing new to release
|
|
52
|
+
passes and says so. Re-running a
|
|
53
|
+
release whose version is already on npm skips the publish and finishes the tag
|
|
54
|
+
and GitHub Release.
|
|
55
|
+
|
|
56
|
+
A hand-pushed tag still works and publishes exactly that version:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
git tag vX.Y.Z && git push origin vX.Y.Z
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
CI never receives an npm token: it publishes through npm Trusted Publishing
|
|
63
|
+
(OIDC).
|
|
64
|
+
|
|
65
|
+
## One-time setup
|
|
66
|
+
|
|
67
|
+
npm can publish new versions through OIDC but cannot create a package that does
|
|
68
|
+
not exist yet. One command does the one-time work with your own npm login, from
|
|
69
|
+
`main` with a clean tree:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
bun run bootstrap
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
It signs in to npm (browser approval), runs `bun run check`, publishes `0.1.0`
|
|
76
|
+
from a throwaway checkout of `HEAD` (so your working tree is never edited),
|
|
77
|
+
makes `release.yml` the package's trusted publisher, and tags `v0.1.0` on the
|
|
78
|
+
commit npm recorded. When npm recorded none (as for the `0.1.0` that is on
|
|
79
|
+
npm), there is nothing to tag: until the first tag exists, the Release
|
|
80
|
+
workflow counts from the newest version on npm and every commit up to `HEAD`
|
|
81
|
+
picks the bump. Every step measures first and skips what is already done,
|
|
82
|
+
so run it again after any interruption. It needs npm 11.10 or newer (for
|
|
83
|
+
`npm trust`) and an npm account that owns the `@crvouga` scope. Flags: `--yes`
|
|
84
|
+
skips the confirmation, `--skip-check` skips `bun run check`.
|
|
85
|
+
|
|
86
|
+
Verify from a machine that has never seen the package:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
npx --yes @crvouga/dev-fleet --version
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
If `npm trust` is unavailable, set the publisher in the browser instead:
|
|
93
|
+
<https://www.npmjs.com/package/@crvouga/dev-fleet/access> → Trusted Publisher →
|
|
94
|
+
GitHub Actions → organization `crvouga`, repository `dev-fleet`, workflow
|
|
95
|
+
filename `release.yml`, environment empty.
|
|
96
|
+
|
|
97
|
+
## Why the package looks like this
|
|
98
|
+
|
|
99
|
+
- `files` is an allow-list; tests and Python caches are excluded. `features/rules/package.feature`
|
|
100
|
+
fails if a file the CLI reads is missing from the tarball.
|
|
101
|
+
- `bun` is a dependency pinned to the same version as `packageManager` and the
|
|
102
|
+
`mise.toml` pin; bump the three together.
|
|
103
|
+
- The provenance statement links each version to the workflow run that built it.
|
package/docs/state.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Fleet state
|
|
2
|
+
|
|
3
|
+
A fleet is described in two places you own. Your project in Git holds
|
|
4
|
+
`fleet.json` and pins the dev-fleet version. Everything else the manager
|
|
5
|
+
cannot rebuild from Git is the **fleet state**:
|
|
6
|
+
|
|
7
|
+
| Document | What it is |
|
|
8
|
+
| ------------ | -------------------------------------------------------------------- |
|
|
9
|
+
| `roster` | The enrolled connection to each worker (SSH alias, Tailscale device) |
|
|
10
|
+
| `enrollment` | The revision `fleet-sync` approved and the one it last applied |
|
|
11
|
+
| `definition` | The fleet configuration `fleet-sync` last applied: your `fleet.json` |
|
|
12
|
+
|
|
13
|
+
They live in one SQLite database, and [Litestream](https://litestream.io)
|
|
14
|
+
replicates that database to a storage bucket you own. The bucket is the source
|
|
15
|
+
of truth. The copy on the manager is disposable: delete it, or replace the
|
|
16
|
+
whole machine, and the next command brings the fleet back.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
your project (Git)
|
|
20
|
+
fleet.json the configuration; says where the bucket is
|
|
21
|
+
package.json the dev-fleet version
|
|
22
|
+
~/.local/share/dev-fleet/state/ the local copy of the state; safe to delete
|
|
23
|
+
fleet.db the database Litestream replicates
|
|
24
|
+
definition.json the stored configuration as a file, rewritten from fleet.db
|
|
25
|
+
replicated.json, litestream.yml bookkeeping
|
|
26
|
+
~/.config/dev-fleet/bucket.json optional: credentials saved for unattended runs
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Set it up
|
|
30
|
+
|
|
31
|
+
Any S3-compatible bucket works: AWS S3, Cloudflare R2, Backblaze B2, MinIO.
|
|
32
|
+
Create an empty private bucket and an access key that can read and write it.
|
|
33
|
+
Name the bucket in `fleet.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
"state": {
|
|
37
|
+
"bucket": "my-fleet-state",
|
|
38
|
+
"endpoint": "https://ACCOUNT.r2.cloudflarestorage.com",
|
|
39
|
+
"region": "auto",
|
|
40
|
+
"prefix": "dev-fleet"
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Omit `endpoint` for AWS S3. `prefix` defaults to `dev-fleet`; one bucket can
|
|
45
|
+
hold several fleets under different prefixes. The block never holds
|
|
46
|
+
credentials, and a `fleet.json` that does is refused.
|
|
47
|
+
|
|
48
|
+
Every devfleet command reads the credentials from its environment:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
export DEVFLEET_BUCKET_ACCESS_KEY_ID=…
|
|
52
|
+
export DEVFLEET_BUCKET_SECRET_ACCESS_KEY=…
|
|
53
|
+
npx devfleet fleet-sync
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Set both or neither. With neither, and nothing saved on the machine, the
|
|
57
|
+
standard AWS environment or profile is used. Credentials are never taken from
|
|
58
|
+
arguments and never written to a report or a log.
|
|
59
|
+
|
|
60
|
+
**A new fleet.** Nothing more: the first `fleet-sync` seeds the bucket.
|
|
61
|
+
|
|
62
|
+
**A replacement manager, or a second machine.** Clone your project, `npm ci`,
|
|
63
|
+
and run `fleet-sync` with the credentials. The bucket already holds the fleet,
|
|
64
|
+
so its state is downloaded before anything is changed.
|
|
65
|
+
|
|
66
|
+
**Runs the manager starts by itself.** A worker that hands `fleet-sync` to the
|
|
67
|
+
manager starts a command there that gets no environment and no project. Save
|
|
68
|
+
the credentials on the manager once:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
npx devfleet fleet-state --init
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
It takes the bucket from `fleet.json` and the credentials from the environment,
|
|
75
|
+
or asks for them on the terminal, and writes both to
|
|
76
|
+
`~/.config/dev-fleet/bucket.json` (readable by you alone). Such a run then
|
|
77
|
+
uses the configuration last applied and the saved credentials. Without them it
|
|
78
|
+
still converges the fleet from the local copy and reports that the bucket was
|
|
79
|
+
not reached; the next run that has credentials uploads what is pending.
|
|
80
|
+
|
|
81
|
+
**A fleet that kept its configuration in the bucket.** Before projects, the
|
|
82
|
+
configuration was stored with `fleet-state --import` and the bucket was named
|
|
83
|
+
with `fleet-state --init --bucket …`. To move such a fleet into a project, on
|
|
84
|
+
the manager:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
mkdir my-fleet && cd my-fleet && git init && npm init -y
|
|
88
|
+
npm install --save-dev --save-exact @crvouga/dev-fleet
|
|
89
|
+
npx devfleet fleet-state --export > fleet.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Add the `state` block to `fleet.json` with the same bucket, endpoint, region
|
|
93
|
+
and prefix as `~/.config/dev-fleet/bucket.json`, then run
|
|
94
|
+
`npx devfleet fleet-sync`. The credentials already saved for that bucket keep
|
|
95
|
+
working.
|
|
96
|
+
|
|
97
|
+
The clone of dev-fleet the fleet used to run from is then no longer read by
|
|
98
|
+
`fleet-sync`, with one exception: the CLI proxy's installed code is a Git
|
|
99
|
+
worktree of the repository it was installed from. Install the proxy again
|
|
100
|
+
from the project (`npx devfleet cliproxy install`, which takes the revision
|
|
101
|
+
`fleet-sync` applied from the manager's own copy of the source) before you
|
|
102
|
+
delete the clone.
|
|
103
|
+
|
|
104
|
+
Without a `fleet.json`, `fleet-state --init --bucket NAME --endpoint https://HOST`
|
|
105
|
+
still connects a bucket and restores the configuration stored in it.
|
|
106
|
+
|
|
107
|
+
On a manager older than the fleet state itself, `--init` (or the first
|
|
108
|
+
`fleet-sync`) also moves the configuration, the roster and the approved
|
|
109
|
+
revision from the files that held them into the state. Those files are left in
|
|
110
|
+
place and no longer read.
|
|
111
|
+
|
|
112
|
+
`"state": { "directory": "/Volumes/backup/fleet" }` replicates to a directory
|
|
113
|
+
instead of a bucket (an attached disk, a mounted share). It behaves the same
|
|
114
|
+
way, needs no credentials and is the quickest way to try this out.
|
|
115
|
+
|
|
116
|
+
## Day to day
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
devfleet fleet-state # read-only: where the state is, and how it stands with the bucket
|
|
120
|
+
devfleet fleet-state --example # print a fleet.json to start a project from
|
|
121
|
+
devfleet fleet-state --export # print the configuration fleet-sync last applied
|
|
122
|
+
devfleet fleet-state --push # upload changes made while the bucket was unreachable
|
|
123
|
+
devfleet fleet-state --restore # replace the local copy with the bucket's
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
To change the fleet, for example to add a worker, edit `fleet.json`, commit
|
|
127
|
+
and run `devfleet fleet-sync` from the project. `fleet-sync` validates the
|
|
128
|
+
file, installs it on every machine and records it in the state.
|
|
129
|
+
|
|
130
|
+
Which configuration a command uses, in this order:
|
|
131
|
+
|
|
132
|
+
1. The file `DEVFLEET_CONFIG` names.
|
|
133
|
+
2. The closest `fleet.json` in the working directory or above it.
|
|
134
|
+
3. The configuration `fleet-sync` last applied, from the fleet state (the
|
|
135
|
+
devfleet CLI on the manager).
|
|
136
|
+
4. The copy `fleet-sync` installed at `~/.config/dev-fleet/fleet.json`
|
|
137
|
+
(launchers and scheduled jobs on every machine read only this one).
|
|
138
|
+
|
|
139
|
+
`fleet-state --import PATH` stores a configuration in the state without
|
|
140
|
+
running `fleet-sync`.
|
|
141
|
+
|
|
142
|
+
## How it stays correct
|
|
143
|
+
|
|
144
|
+
Litestream does not run as a daemon. A devfleet command that changes the state
|
|
145
|
+
runs it once, so the command has not finished until the bucket has the change
|
|
146
|
+
and nothing has to be kept running on the manager. Every change follows the
|
|
147
|
+
same three steps:
|
|
148
|
+
|
|
149
|
+
1. **Download** the bucket's current version and compare it with the local copy.
|
|
150
|
+
2. **Change** the local copy, on top of the bucket's version.
|
|
151
|
+
3. **Upload**, then download again. The change counts only once a restore from
|
|
152
|
+
the bucket returns it.
|
|
153
|
+
|
|
154
|
+
`fleet-sync` does step 1 before it reads the configuration and step 3 after
|
|
155
|
+
it records what it applied. Its report carries the result as the
|
|
156
|
+
`state:replica` check.
|
|
157
|
+
|
|
158
|
+
`fleet-state` reports one of these:
|
|
159
|
+
|
|
160
|
+
| Standing | Meaning | What happens |
|
|
161
|
+
| -------------- | ------------------------------------------------------ | ---------------------------------------------------------- |
|
|
162
|
+
| `in-sync` | The bucket and this machine hold the same version | Nothing to do |
|
|
163
|
+
| `local-only` | No bucket is configured | A warning on every `fleet-sync`; name one in `fleet.json` |
|
|
164
|
+
| `empty` | Neither side holds any state | The first `fleet-sync` records it |
|
|
165
|
+
| `unpublished` | The bucket holds nothing yet | The next change, or `--push`, seeds it |
|
|
166
|
+
| `local-ahead` | This machine has changes the bucket has not received | The next change, or `--push`, uploads them |
|
|
167
|
+
| `bucket-ahead` | Another manager changed the bucket; this copy is stale | The next change or `fleet-sync` takes the bucket's version |
|
|
168
|
+
| `unreachable` | The bucket did not answer, or did not take an upload | Changes still apply locally and are uploaded later |
|
|
169
|
+
| `diverged` | Both sides changed | Refused. `--restore --yes` takes the bucket's version |
|
|
170
|
+
|
|
171
|
+
A stale copy never overwrites the bucket. A machine that was offline while
|
|
172
|
+
another one changed the fleet is brought up to date before it can change
|
|
173
|
+
anything, and if it also changed something itself it stops and says so. The
|
|
174
|
+
bucket wins: export what you still need, then `--restore --yes`.
|
|
175
|
+
|
|
176
|
+
An unreachable bucket does not block `fleet-sync`. The fleet converges, the
|
|
177
|
+
report warns, and the next command uploads what is pending.
|
|
178
|
+
|
|
179
|
+
## What is not in the bucket yet
|
|
180
|
+
|
|
181
|
+
The fleet state covers what `fleet-sync` itself needs. The manager also holds
|
|
182
|
+
state that is replicated differently or not at all:
|
|
183
|
+
|
|
184
|
+
| State | Where | If the manager is lost |
|
|
185
|
+
| ---------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
186
|
+
| Secrets | Your `secrets` provider (`vault`, `sops`, `file`, `env`) | Unaffected for `vault`; `file` and `env` secrets must be supplied again |
|
|
187
|
+
| CLI proxy: provider logins, keys, Tailscale identity | `~/Library/Application Support/cliproxy-fleet/state` | Restored from its own encrypted restic snapshots ([cliproxy](../cliproxy/README.md)); needs `vault` |
|
|
188
|
+
| Monitoring hub (Beszel) database | `~/Library/Application Support/personal-beszel` | `fleet-sync` recreates accounts and systems; metric history, alerts and settings made by hand are lost |
|
|
189
|
+
| T3 ownership record | `~/.t3/userdata/personal-managed.json` | `t3-apply --import ID` adopts instances again |
|
|
190
|
+
|
|
191
|
+
Everything else under `~/.local/share/personal/fleet` and
|
|
192
|
+
`~/.local/state/personal/fleet` (the copy of the source repository, run
|
|
193
|
+
checkouts, reports, receipts, logs) is a cache that the next `fleet-sync`
|
|
194
|
+
rebuilds. The CLI proxy's installed code is a Git worktree of that copy of the
|
|
195
|
+
source repository: after deleting it, run `devfleet cliproxy install` again
|
|
196
|
+
once `fleet-sync` has fetched it back.
|
|
197
|
+
|
|
198
|
+
## Reference
|
|
199
|
+
|
|
200
|
+
| Setting | Meaning |
|
|
201
|
+
| -------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
202
|
+
| `state` in `fleet.json` | Where the bucket is |
|
|
203
|
+
| `DEVFLEET_BUCKET_ACCESS_KEY_ID`, `DEVFLEET_BUCKET_SECRET_ACCESS_KEY` | The bucket's credentials, for the command they are passed to |
|
|
204
|
+
| `DEVFLEET_CONFIG` | A configuration file, instead of the project's `fleet.json` |
|
|
205
|
+
| `DEVFLEET_STATE` | Another state directory. Earlier files are not read for it |
|
|
206
|
+
| `DEVFLEET_BUCKET` | Another `bucket.json` |
|
|
207
|
+
|
|
208
|
+
How a command reaches the bucket:
|
|
209
|
+
|
|
210
|
+
- **Where it is**: the `state` block of the configuration; without one, the
|
|
211
|
+
bucket saved in `bucket.json`.
|
|
212
|
+
- **Credentials**: the two variables; otherwise the ones saved in
|
|
213
|
+
`bucket.json`, only when they were saved for that same bucket; otherwise the
|
|
214
|
+
standard AWS environment or profile.
|
|
215
|
+
|
|
216
|
+
`fleet-state --json` reports both as `bucket.location` (`fleet.json` or
|
|
217
|
+
`bucket.json`) and `bucket.credentials` (`environment`, `bucket.json` or
|
|
218
|
+
`aws`), never a credential.
|
|
219
|
+
|
|
220
|
+
`bucket.json`, written by `fleet-state --init`:
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"version": 1,
|
|
225
|
+
"bucket": "my-fleet-state",
|
|
226
|
+
"endpoint": "https://ACCOUNT.r2.cloudflarestorage.com",
|
|
227
|
+
"region": "auto",
|
|
228
|
+
"prefix": "dev-fleet",
|
|
229
|
+
"accessKeyId": "…",
|
|
230
|
+
"secretAccessKey": "…"
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
It must be readable by you alone (`chmod 600`); devfleet refuses to use it
|
|
235
|
+
otherwise. The keys reach Litestream through its environment and are never
|
|
236
|
+
written to its configuration, a report or a log. To rotate them, pass the new
|
|
237
|
+
ones to `fleet-state --init`.
|
|
238
|
+
|
|
239
|
+
The database is stored under `PREFIX/fleet.db/`. Each change adds a few
|
|
240
|
+
kilobytes and Litestream keeps thirty days of snapshots, so a restore to an
|
|
241
|
+
earlier point is possible with Litestream directly (with the bucket's keys in
|
|
242
|
+
`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`):
|
|
243
|
+
|
|
244
|
+
```sh
|
|
245
|
+
mise exec litestream -- litestream restore -no-expand-env \
|
|
246
|
+
-config ~/.local/share/dev-fleet/state/litestream.yml \
|
|
247
|
+
-timestamp 2026-11-01T00:00:00Z -o /tmp/fleet-then.db \
|
|
248
|
+
~/.local/share/dev-fleet/state/fleet.db
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Litestream is pinned in `mise.toml` and installed the first time it is used.
|
|
252
|
+
The manager is the only machine that runs it. There is one manager, so there is
|
|
253
|
+
one writer; two machines changing the state at the same moment are not
|
|
254
|
+
supported, only detected afterwards.
|