@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,215 @@
|
|
|
1
|
+
# Beszel owner and recovery
|
|
2
|
+
|
|
3
|
+
The [README](../../README.md) and the [manager guide](../../docs/ubuntu-sync.md)
|
|
4
|
+
are the normal entry points: `devfleet fleet-sync`, run on the manager. This
|
|
5
|
+
directory holds the pinned Compose hub, the native agent unit and their
|
|
6
|
+
playbooks. `src/monitoring/` enrolls machines and measures registration,
|
|
7
|
+
connection, freshness and backups. `lock.json` pins the Beszel release, the
|
|
8
|
+
agent archives and the agent executables by digest. Never run an unpinned
|
|
9
|
+
upstream installer alongside these services.
|
|
10
|
+
|
|
11
|
+
## Manager and workers
|
|
12
|
+
|
|
13
|
+
The fleet has exactly two roles (`fleet.json`):
|
|
14
|
+
|
|
15
|
+
- **Manager** — the human's macOS machine. It runs fleet-sync locally and hosts the
|
|
16
|
+
singleton hub (`src/monitoring/manager-hub.ts`, `convergeManagerHub`).
|
|
17
|
+
dev-fleet keeps it awake on AC power. Nothing is installed on it over SSH.
|
|
18
|
+
- **Workers** — the stateless Linux boxes. They only run the native agent
|
|
19
|
+
(`agent.yml`, a hardened systemd unit) and connect to the manager's private hub
|
|
20
|
+
URL. A worker refuses hub mode (`BESZEL_NOT_HUB`: the hub runs only on the
|
|
21
|
+
manager) and an agent refuses any other hub URL (`BESZEL_HUB_MISMATCH`).
|
|
22
|
+
|
|
23
|
+
The manager is always a monitored system on its own hub. After the hub converges,
|
|
24
|
+
fleet-sync enrolls the manager like a worker and installs its agent locally with
|
|
25
|
+
`agent-darwin.yml`: the same pinned binary (`lock.json` carries the darwin
|
|
26
|
+
checksums) as a per-user launchd agent with `KeepAlive`
|
|
27
|
+
(`~/Library/LaunchAgents/dev.devfleet.beszel-agent.plist`, state under
|
|
28
|
+
`~/Library/Application Support/personal-beszel-agent`). It needs no sudo, so
|
|
29
|
+
fleet-sync stays unattended; it is less sandboxed than the Linux unit. It refuses
|
|
30
|
+
to run beside a leftover root LaunchDaemon with the same label
|
|
31
|
+
(`BESZEL_LEGACY_DAEMON`).
|
|
32
|
+
|
|
33
|
+
## Hub on the manager
|
|
34
|
+
|
|
35
|
+
`hub-darwin.yml` runs as the signed-in user (no sudo) under Docker Desktop, which
|
|
36
|
+
must be running (`BESZEL_DOCKER_DESKTOP_REQUIRED`); enable _Start Docker Desktop
|
|
37
|
+
when you sign in_. It refuses to replace a different existing image
|
|
38
|
+
(`BESZEL_HUB_UPGRADE_PENDING`). Paths are per-user and `0700`:
|
|
39
|
+
|
|
40
|
+
- data: `~/Library/Application Support/personal-beszel` (bind-mounted as
|
|
41
|
+
`/beszel_data`; it is in the human's normal home backups, but use the stock
|
|
42
|
+
backups below for consistent copies);
|
|
43
|
+
- connector token: `~/Library/Application Support/personal-beszel-tunnel/token`
|
|
44
|
+
(`0600`).
|
|
45
|
+
|
|
46
|
+
The dashboard is `https://<manager-MagicDNS-name>:8443`
|
|
47
|
+
(`hubUrl(definition)`), used by agents and checks. CLI output only ever prints
|
|
48
|
+
the stable public dashboard, `https://beszel.example.com`; tailnet addresses
|
|
49
|
+
are never printed. The Compose port is
|
|
50
|
+
loopback-only; Tailscale Serve (`tailscale serve --bg --https=8443
|
|
51
|
+
http://127.0.0.1:8090`, using `tailscale` on PATH or the app's
|
|
52
|
+
`/Applications/Tailscale.app/Contents/MacOS/Tailscale`) provides private HTTPS.
|
|
53
|
+
No Funnel or inbound listener is configured. Convergence passes `beszel:hub` only
|
|
54
|
+
after `/api/health` answers both locally and at the private URL. Before enrolling
|
|
55
|
+
agents, fleet-sync requires that URL to answer; otherwise workers report
|
|
56
|
+
`BESZEL_HUB_UNREACHABLE` (keep the manager awake and Docker Desktop running).
|
|
57
|
+
|
|
58
|
+
The same hub is also served at `https://beszel.example.com` through an
|
|
59
|
+
outbound-only Cloudflare Tunnel: the inventory's
|
|
60
|
+
`cloudflare.additional_tunnels.beszel` entry makes OpenTofu own the tunnel, its
|
|
61
|
+
proxied DNS record and the isolated `secret/beszel/tunnel` Vault document. The
|
|
62
|
+
hub converge reads that document with the manager's own Vault session (the
|
|
63
|
+
human's session; never printed or placed in argv), writes it to the per-user
|
|
64
|
+
token file and starts the pinned `cloudflared` Compose profile. Until the
|
|
65
|
+
credential is readable the step warns (`BESZEL_TUNNEL_CREDENTIAL_UNAVAILABLE`)
|
|
66
|
+
and the private dashboard is unaffected. Agents and the manager keep using the
|
|
67
|
+
private URL; `fleet-status --workers` reports the public hostname as a
|
|
68
|
+
warning-level `beszel:public-endpoint` check.
|
|
69
|
+
|
|
70
|
+
The dashboard needs no login: the hub runs with `AUTO_LOGIN` set to the
|
|
71
|
+
read-only user `public-viewer@example.com`, so every visitor (public or
|
|
72
|
+
tailnet) is signed in as that account and cannot change systems, alerts or
|
|
73
|
+
settings. Each hub converge (`fleet-sync`) creates that user through the hub's
|
|
74
|
+
loopback API using the fleet admin login from Vault (random unused password,
|
|
75
|
+
never printed), re-reads it to prove the role, and demotes it to read-only if it
|
|
76
|
+
ever had another role (`beszel:public-viewer`). If the admin login is
|
|
77
|
+
unavailable the step warns and the hub shows its normal login page; create the
|
|
78
|
+
user by hand (Settings > Users, role **Read-only**) as a fallback. Never point
|
|
79
|
+
`AUTO_LOGIN` at an admin account. The fleet admin login is still needed to change anything.
|
|
80
|
+
|
|
81
|
+
## The agent on a worker
|
|
82
|
+
|
|
83
|
+
Native agents use outbound authenticated WebSockets, per-node tokens and
|
|
84
|
+
persistent identity. They do not need inbound SSH monitoring ports, Docker socket
|
|
85
|
+
access or journals.
|
|
86
|
+
|
|
87
|
+
- **Unprivileged.** The unit runs as the `beszel` system account with no other
|
|
88
|
+
group, no capability, no journal and no Docker socket
|
|
89
|
+
(`features/monitoring/agent-privileges.feature`).
|
|
90
|
+
`SKIP_SYSTEMD_LOGS=true` deliberately excludes potentially sensitive service
|
|
91
|
+
logs. System services may be visible; user T3 service information is not
|
|
92
|
+
certified by that fact. Sensors, SMART, Docker stats and user-service metrics
|
|
93
|
+
stay unavailable until their specific permissions are reviewed: nothing grants
|
|
94
|
+
them by default.
|
|
95
|
+
- **Repeatable.** `agent.yml` compares the installed executable with the digest
|
|
96
|
+
of the pinned build (`agentBinaries` in `lock.json`). An unchanged agent is left
|
|
97
|
+
running; one an interrupted upgrade or an edit left behind is removed,
|
|
98
|
+
extracted again from the checksummed archive and restarted once. Edited unit
|
|
99
|
+
or connection settings are restored the same way.
|
|
100
|
+
- **Boots without a login.** It is a system unit (`WantedBy=multi-user.target`),
|
|
101
|
+
so it starts at boot with nobody signed in.
|
|
102
|
+
- **Measured from the worker's side** (`src/monitoring/agent-evidence.ts`):
|
|
103
|
+
|
|
104
|
+
| Check | Summary | Meaning |
|
|
105
|
+
| ----------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
106
|
+
| `beszel:disk` | `BESZEL_DISK_FULL`, `BESZEL_DISK_UNVERIFIED` | Less than 256 MiB free where the agent installs; nothing was changed |
|
|
107
|
+
| `beszel:service` | `BESZEL_SERVICE_STOPPED` | The unit is not enabled and active |
|
|
108
|
+
| `beszel:hub-connection` | `BESZEL_AGENT_TOKEN_REJECTED` | The hub answered the agent's token with 401 |
|
|
109
|
+
| | `BESZEL_AGENT_KEY_REJECTED` | The hub did not prove the key the agent was given |
|
|
110
|
+
| | `BESZEL_HUB_UNAVAILABLE_TO_AGENT` (warn) | The private route answered 502–504: the hub behind it is down |
|
|
111
|
+
| | `BESZEL_AGENT_CONNECTION_PENDING`, `…_LOG_UNAVAILABLE` (skip) | No outcome yet, or the log could not be read |
|
|
112
|
+
|
|
113
|
+
The outcome is the newest of a few fixed texts the pinned agent logs, taken
|
|
114
|
+
from its source; nothing of the log is reported. The hub's own view
|
|
115
|
+
(registration, `BESZEL_AGENT_DISCONNECTED`, `BESZEL_METRICS_STALE`) is
|
|
116
|
+
measured separately by `fleet-status --workers`.
|
|
117
|
+
|
|
118
|
+
Accounts need no manual step. On a hub's first start (empty data directory),
|
|
119
|
+
`hub-darwin.yml` passes Beszel's `USER_EMAIL`/`USER_PASSWORD` once, so the hub
|
|
120
|
+
creates `fleet-manager@example.com` as a verified user and as its superuser,
|
|
121
|
+
with the password OpenTofu generates as `BESZEL_FLEET_AUTH` in
|
|
122
|
+
`secret/personal/prd`. Later runs never pass it, and existing data is never
|
|
123
|
+
changed. Sign in to the dashboard with that account.
|
|
124
|
+
The hub runs with `SHARE_ALL_SYSTEMS=true`, so systems that account registers are
|
|
125
|
+
visible to every hub login.
|
|
126
|
+
A legacy JSON `identity`/`password` value is still accepted. The manager's existing
|
|
127
|
+
Vault credential must renew unattended. Superuser credentials are not distributed
|
|
128
|
+
to nodes or needed for enrollment. Revocation produces action-required results.
|
|
129
|
+
|
|
130
|
+
## Settings kept only in the hub's UI
|
|
131
|
+
|
|
132
|
+
Beszel keeps network monitors, alert rules and backup settings in its own data,
|
|
133
|
+
set in its UI. dev-fleet does not write them: there is no reconciler and no
|
|
134
|
+
endpoint beyond the stock API (`features/monitoring/stock-api.feature`). They
|
|
135
|
+
are preserved by the hub's backups. Set them once, as the fleet account:
|
|
136
|
+
|
|
137
|
+
- **Network monitors.** Add them per system from that system's page. Probe from
|
|
138
|
+
a few chosen origins (for example the manager to each worker) rather than an
|
|
139
|
+
all-to-all mesh, so a failed check names a path you can reason about.
|
|
140
|
+
- **Alerts.** Prefer sustained thresholds over instant ones: CPU, memory and
|
|
141
|
+
disk above a level for several minutes, disk capacity, a system going offline,
|
|
142
|
+
and temperatures where the sensor is actually reported. Do not add a
|
|
143
|
+
notification destination until one you are allowed to use exists.
|
|
144
|
+
- **Service alerts are not promised.** Alerting on a failed systemd service has
|
|
145
|
+
not been verified against the pinned release; the agent watches only the
|
|
146
|
+
services in `SERVICE_PATTERNS`, and none of them is relied on for alerting.
|
|
147
|
+
|
|
148
|
+
## Stock backup and restore
|
|
149
|
+
|
|
150
|
+
1. Open the hub's private PocketBase administration route `/_/` and authenticate
|
|
151
|
+
as its superuser. Keep that credential separate from fleet enrollment access.
|
|
152
|
+
2. Open **Settings → Backups**. Create and download a backup before maintenance.
|
|
153
|
+
Set a schedule, how many to keep, and S3-compatible storage there, using
|
|
154
|
+
storage you already approved. Do not provision a bucket or reuse unrelated
|
|
155
|
+
credentials to bypass access gates.
|
|
156
|
+
3. Protect downloaded archives as secrets: they contain registrations, tokens,
|
|
157
|
+
account data, alert configuration and history. Use the existing approved
|
|
158
|
+
encrypted backup destination; a backup on the same disk is not disaster recovery.
|
|
159
|
+
4. Restore the archive into an isolated instance of the same pinned release with
|
|
160
|
+
outbound agent connections blocked. Use the stock upload/restore action, never
|
|
161
|
+
database edits. Restoring restarts the hub. Verify original node IDs, fingerprints,
|
|
162
|
+
history and settings before selecting a replacement host. Fence the old hub
|
|
163
|
+
before production recovery; never operate two copies of its identity.
|
|
164
|
+
5. Run the manager command and require new metric timestamps after recovery.
|
|
165
|
+
Old historical samples do not prove agent connectivity.
|
|
166
|
+
|
|
167
|
+
Every hub converge in `fleet-sync` reads these settings back as the hub's
|
|
168
|
+
superuser (`GET /api/settings`, `GET /api/backups`; `src/monitoring/hub-backup.ts`)
|
|
169
|
+
and reports `beszel:backup`. It only warns, and never names the storage:
|
|
170
|
+
|
|
171
|
+
| Summary | Meaning |
|
|
172
|
+
| -------------------------------- | -------------------------------------------------------- |
|
|
173
|
+
| `BESZEL_BACKUP_UNSCHEDULED` | No backup schedule is set |
|
|
174
|
+
| `BESZEL_BACKUP_OFFHOST_REQUIRED` | Backups stay on the manager's disk |
|
|
175
|
+
| `BESZEL_BACKUP_STALE` | A schedule is set but no backup is newer than 48 hours |
|
|
176
|
+
| `BESZEL_BACKUP_UNVERIFIED` | The settings could not be read (no fleet login, refused) |
|
|
177
|
+
|
|
178
|
+
## Moving the hub to the manager
|
|
179
|
+
|
|
180
|
+
The hub previously ran on worker `worker-one` (`/var/lib/personal-beszel`,
|
|
181
|
+
Compose project `personal-beszel`). Move it once, as an operator, in this order;
|
|
182
|
+
never run two copies of its identity or two tunnel connectors:
|
|
183
|
+
|
|
184
|
+
1. On the old hub host, create and download a stock backup (**Settings → Backups**) and keep
|
|
185
|
+
it as a secret.
|
|
186
|
+
2. Fence the old hub on that host: `sudo docker compose -p personal-beszel down`
|
|
187
|
+
(stops the hub and its `cloudflared` connector; keep `/var/lib/personal-beszel`
|
|
188
|
+
untouched until the move is verified), then
|
|
189
|
+
`sudo tailscale serve --https=8443 off`.
|
|
190
|
+
3. On the manager, with Docker Desktop running, run fleet-sync. It starts a fresh
|
|
191
|
+
hub, which creates the fleet account itself, and enrolls every worker. To keep
|
|
192
|
+
history instead, restore the backup afterwards through the stock **Backups**
|
|
193
|
+
upload/restore action as the superuser.
|
|
194
|
+
4. Confirm fresh samples from every worker in the dashboard.
|
|
195
|
+
5. Only after verification, remove the old host's hub data and its `beszel-hub-read`
|
|
196
|
+
Vault grant (OpenTofu).
|
|
197
|
+
|
|
198
|
+
`tests/upstream.py` exercises real pinned binaries with disposable loopback data:
|
|
199
|
+
authenticated registration, fresh metrics, restart identity preservation, no
|
|
200
|
+
duplicate record, the supported PocketBase backup/restore API, and the recovery
|
|
201
|
+
drill above: a downloaded backup restored into a second, isolated hub after the
|
|
202
|
+
first is fenced, keeping node IDs, fingerprints, history and settings. It does
|
|
203
|
+
not contact live nodes, prove Tailscale routing, verify systemd reboot, or use an
|
|
204
|
+
off-host storage service. Those remain operational checks:
|
|
205
|
+
|
|
206
|
+
- From a second tailnet machine, `devfleet fleet-status --workers` reports
|
|
207
|
+
`beszel:registration`, `beszel:connection` and `beszel:freshness` passing for
|
|
208
|
+
every worker.
|
|
209
|
+
- `beszel:backup` passes on the manager once off-host storage is configured.
|
|
210
|
+
- After a worker reboots, the same checks pass again without anyone signing in.
|
|
211
|
+
|
|
212
|
+
References: [agent](https://beszel.dev/guide/agent-installation),
|
|
213
|
+
[systemd](https://beszel.dev/guide/systemd),
|
|
214
|
+
[network monitors](https://beszel.dev/guide/network-monitors),
|
|
215
|
+
[backup API](https://pocketbase.io/docs/api-backups/).
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The manager's own Beszel agent, so the manager is always a monitored system
|
|
3
|
+
# on its own hub. It runs as the signed-in user (no become, like hub-darwin.yml)
|
|
4
|
+
# as an always-on per-user launchd agent: fleet-sync stays unattended and never
|
|
5
|
+
# needs the manager's sudo password. Credentials arrive through private
|
|
6
|
+
# variables, never argv.
|
|
7
|
+
- name: Converge the manager's native Beszel agent as a per-user launchd agent
|
|
8
|
+
hosts: localhost
|
|
9
|
+
connection: local
|
|
10
|
+
become: false
|
|
11
|
+
gather_facts: true
|
|
12
|
+
vars_files:
|
|
13
|
+
- ../macos/policy.json
|
|
14
|
+
vars:
|
|
15
|
+
beszel_lock: "{{ lookup('ansible.builtin.file', 'lock.json') | from_json }}"
|
|
16
|
+
beszel_arch: "{{ {'arm64': 'darwin-arm64', 'x86_64': 'darwin-amd64'}.get(ansible_facts.architecture, 'unsupported') }}"
|
|
17
|
+
beszel_home: '{{ ansible_facts.env.HOME }}'
|
|
18
|
+
beszel_base: '{{ beszel_home }}/Library/Application Support/personal-beszel-agent'
|
|
19
|
+
beszel_root: '{{ beszel_base }}/{{ beszel_lock.version }}'
|
|
20
|
+
beszel_secrets: '{{ beszel_base }}/credentials'
|
|
21
|
+
beszel_data: '{{ beszel_base }}/data'
|
|
22
|
+
beszel_plist: '{{ beszel_home }}/Library/LaunchAgents/{{ beszelDaemon }}.plist'
|
|
23
|
+
beszel_domain: 'gui/{{ ansible_facts.user_uid }}'
|
|
24
|
+
tasks:
|
|
25
|
+
- name: Require supported platform and private hub policy
|
|
26
|
+
ansible.builtin.assert:
|
|
27
|
+
that:
|
|
28
|
+
- ansible_facts.os_family == 'Darwin'
|
|
29
|
+
- beszel_arch in beszel_lock.agents
|
|
30
|
+
- beszel_node is match('^[a-z][a-z0-9-]{0,62}$')
|
|
31
|
+
- beszel_hub_url is match('^https://[a-z0-9.-]+\.ts\.net:8443$')
|
|
32
|
+
- name: Inspect a legacy system-wide daemon with the same label
|
|
33
|
+
ansible.builtin.stat:
|
|
34
|
+
path: '/Library/LaunchDaemons/{{ beszelDaemon }}.plist'
|
|
35
|
+
register: legacy_daemon
|
|
36
|
+
- name: Refuse to run two agents for one system
|
|
37
|
+
ansible.builtin.assert:
|
|
38
|
+
that: not legacy_daemon.stat.exists
|
|
39
|
+
fail_msg: BESZEL_LEGACY_DAEMON; remove the root LaunchDaemon once (sudo launchctl bootout system/{{ beszelDaemon }}) before the per-user agent replaces it.
|
|
40
|
+
- name: Inspect existing agent ownership
|
|
41
|
+
ansible.builtin.stat:
|
|
42
|
+
path: '{{ beszel_plist }}'
|
|
43
|
+
register: existing_plist
|
|
44
|
+
- name: Read existing agent ownership marker
|
|
45
|
+
ansible.builtin.slurp:
|
|
46
|
+
src: '{{ beszel_plist }}'
|
|
47
|
+
register: existing_agent
|
|
48
|
+
when: existing_plist.stat.exists
|
|
49
|
+
# The marker's package path is informational: ownership must survive the
|
|
50
|
+
# owning directory being moved.
|
|
51
|
+
- name: Require explicit adoption of any previously unmanaged agent
|
|
52
|
+
ansible.builtin.assert:
|
|
53
|
+
that:
|
|
54
|
+
- "not existing_plist.stat.exists or 'Managed by personal Fleet:' in (existing_agent.content | default('') | b64decode)"
|
|
55
|
+
fail_msg: Unmanaged Beszel launch agent exists. Review a source-controlled migration before replacing it.
|
|
56
|
+
- name: Create private configuration, version and state directories
|
|
57
|
+
ansible.builtin.file:
|
|
58
|
+
path: '{{ item }}'
|
|
59
|
+
state: directory
|
|
60
|
+
mode: '0700'
|
|
61
|
+
loop:
|
|
62
|
+
- '{{ beszel_base }}'
|
|
63
|
+
- '{{ beszel_root }}'
|
|
64
|
+
- '{{ beszel_secrets }}'
|
|
65
|
+
- '{{ beszel_data }}'
|
|
66
|
+
- '{{ beszel_home }}/Library/LaunchAgents'
|
|
67
|
+
- name: Deliver node-specific credentials through private variables, never command arguments
|
|
68
|
+
ansible.builtin.copy:
|
|
69
|
+
dest: '{{ beszel_secrets }}/{{ item.name }}'
|
|
70
|
+
content: '{{ item.value }}'
|
|
71
|
+
mode: '0600'
|
|
72
|
+
loop:
|
|
73
|
+
- { name: hub-key, value: '{{ beszel_public_key }}' }
|
|
74
|
+
- { name: agent-token, value: '{{ beszel_agent_token }}' }
|
|
75
|
+
no_log: true
|
|
76
|
+
notify: Restart agent
|
|
77
|
+
- name: Download pinned native artifact with checksum verification
|
|
78
|
+
ansible.builtin.get_url:
|
|
79
|
+
url: 'https://github.com/henrygd/beszel/releases/download/v{{ beszel_lock.version }}/beszel-agent_{{ beszel_arch | replace("-", "_") }}.tar.gz'
|
|
80
|
+
dest: '{{ beszel_root }}/agent.tar.gz'
|
|
81
|
+
checksum: 'sha256:{{ beszel_lock.agents[beszel_arch] }}'
|
|
82
|
+
mode: '0644'
|
|
83
|
+
timeout: 60
|
|
84
|
+
register: agent_archive
|
|
85
|
+
- name: Inspect the extracted executable
|
|
86
|
+
ansible.builtin.stat:
|
|
87
|
+
path: '{{ beszel_root }}/beszel-agent'
|
|
88
|
+
register: agent_binary
|
|
89
|
+
# ansible.builtin.unarchive requires GNU tar; macOS ships BSD tar, which
|
|
90
|
+
# extracts this checksum-verified archive natively.
|
|
91
|
+
- name: Extract the pinned executable when new or missing
|
|
92
|
+
ansible.builtin.command:
|
|
93
|
+
argv:
|
|
94
|
+
[
|
|
95
|
+
/usr/bin/tar,
|
|
96
|
+
-xzf,
|
|
97
|
+
'{{ beszel_root }}/agent.tar.gz',
|
|
98
|
+
-C,
|
|
99
|
+
'{{ beszel_root }}',
|
|
100
|
+
beszel-agent,
|
|
101
|
+
]
|
|
102
|
+
when: agent_archive.changed or not agent_binary.stat.exists or not agent_binary.stat.executable
|
|
103
|
+
changed_when: true
|
|
104
|
+
notify: Restart agent
|
|
105
|
+
- name: Install the source-owned always-on launch agent
|
|
106
|
+
ansible.builtin.copy:
|
|
107
|
+
dest: '{{ beszel_plist }}'
|
|
108
|
+
mode: '0644'
|
|
109
|
+
backup: true
|
|
110
|
+
content: |
|
|
111
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
112
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
113
|
+
<!-- Managed by personal Fleet: ansible/monitoring -->
|
|
114
|
+
<plist version="1.0"><dict>
|
|
115
|
+
<key>Label</key><string>{{ beszelDaemon }}</string>
|
|
116
|
+
<key>ProgramArguments</key><array>
|
|
117
|
+
<string>{{ beszel_root }}/beszel-agent</string>
|
|
118
|
+
</array>
|
|
119
|
+
<key>EnvironmentVariables</key><dict>
|
|
120
|
+
<key>HUB_URL</key><string>{{ beszel_hub_url }}</string>
|
|
121
|
+
<key>SYSTEM_NAME</key><string>{{ beszel_node }}</string>
|
|
122
|
+
<key>DATA_DIR</key><string>{{ beszel_data }}</string>
|
|
123
|
+
<key>KEY_FILE</key><string>{{ beszel_secrets }}/hub-key</string>
|
|
124
|
+
<key>TOKEN_FILE</key><string>{{ beszel_secrets }}/agent-token</string>
|
|
125
|
+
<key>DISABLE_SSH</key><string>true</string>
|
|
126
|
+
</dict>
|
|
127
|
+
<key>RunAtLoad</key><true/>
|
|
128
|
+
<key>KeepAlive</key><true/>
|
|
129
|
+
<key>ProcessType</key><string>Background</string>
|
|
130
|
+
<key>ThrottleInterval</key><integer>20</integer>
|
|
131
|
+
<key>Umask</key><integer>63</integer>
|
|
132
|
+
<key>StandardOutPath</key><string>{{ beszel_data }}/agent.log</string>
|
|
133
|
+
<key>StandardErrorPath</key><string>{{ beszel_data }}/agent.log</string>
|
|
134
|
+
</dict></plist>
|
|
135
|
+
notify: Reload agent definition
|
|
136
|
+
- name: Inspect whether the agent is loaded
|
|
137
|
+
ansible.builtin.command:
|
|
138
|
+
argv: [/bin/launchctl, print, '{{ beszel_domain }}/{{ beszelDaemon }}']
|
|
139
|
+
changed_when: false
|
|
140
|
+
failed_when: false
|
|
141
|
+
register: agent_loaded
|
|
142
|
+
- name: Load the agent at login and now
|
|
143
|
+
ansible.builtin.command:
|
|
144
|
+
argv:
|
|
145
|
+
[
|
|
146
|
+
/bin/launchctl,
|
|
147
|
+
bootstrap,
|
|
148
|
+
'{{ beszel_domain }}',
|
|
149
|
+
'{{ beszel_plist }}',
|
|
150
|
+
]
|
|
151
|
+
when: agent_loaded.rc != 0
|
|
152
|
+
changed_when: true
|
|
153
|
+
- name: Complete any required agent restart before verification
|
|
154
|
+
ansible.builtin.meta: flush_handlers
|
|
155
|
+
- name: Independently check the agent postcondition
|
|
156
|
+
ansible.builtin.command:
|
|
157
|
+
argv: [/bin/launchctl, print, '{{ beszel_domain }}/{{ beszelDaemon }}']
|
|
158
|
+
changed_when: false
|
|
159
|
+
register: agent_state
|
|
160
|
+
retries: 5
|
|
161
|
+
delay: 2
|
|
162
|
+
until: "'state = running' in agent_state.stdout"
|
|
163
|
+
handlers:
|
|
164
|
+
# launchd caches a loaded definition: a changed plist needs a reload.
|
|
165
|
+
- name: Unload the stale agent definition
|
|
166
|
+
ansible.builtin.command:
|
|
167
|
+
argv:
|
|
168
|
+
[/bin/launchctl, bootout, '{{ beszel_domain }}/{{ beszelDaemon }}']
|
|
169
|
+
listen: Reload agent definition
|
|
170
|
+
failed_when: false
|
|
171
|
+
changed_when: true
|
|
172
|
+
- name: Load the current agent definition
|
|
173
|
+
ansible.builtin.command:
|
|
174
|
+
argv:
|
|
175
|
+
[
|
|
176
|
+
/bin/launchctl,
|
|
177
|
+
bootstrap,
|
|
178
|
+
'{{ beszel_domain }}',
|
|
179
|
+
'{{ beszel_plist }}',
|
|
180
|
+
]
|
|
181
|
+
listen: Reload agent definition
|
|
182
|
+
register: agent_reload
|
|
183
|
+
# bootout completes asynchronously; retry until the label is free.
|
|
184
|
+
until: agent_reload.rc == 0
|
|
185
|
+
retries: 5
|
|
186
|
+
delay: 2
|
|
187
|
+
changed_when: true
|
|
188
|
+
- name: Restart agent
|
|
189
|
+
ansible.builtin.command:
|
|
190
|
+
argv:
|
|
191
|
+
[
|
|
192
|
+
/bin/launchctl,
|
|
193
|
+
kickstart,
|
|
194
|
+
-k,
|
|
195
|
+
'{{ beszel_domain }}/{{ beszelDaemon }}',
|
|
196
|
+
]
|
|
197
|
+
failed_when: false
|
|
198
|
+
changed_when: true
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
- name: Converge the native Beszel agent without replacing runtime identity
|
|
3
|
+
hosts: localhost
|
|
4
|
+
connection: local
|
|
5
|
+
become: true
|
|
6
|
+
gather_facts: true
|
|
7
|
+
vars:
|
|
8
|
+
beszel_lock: "{{ lookup('ansible.builtin.file', 'lock.json') | from_json }}"
|
|
9
|
+
beszel_arch: "{{ {'x86_64': 'amd64', 'aarch64': 'arm64'}.get(ansible_facts.architecture, 'unsupported') }}"
|
|
10
|
+
tasks:
|
|
11
|
+
- name: Require supported platform and private hub policy
|
|
12
|
+
ansible.builtin.assert:
|
|
13
|
+
that:
|
|
14
|
+
- ansible_facts.distribution == 'Ubuntu'
|
|
15
|
+
- beszel_arch in beszel_lock.agents
|
|
16
|
+
- beszel_arch in beszel_lock.agentBinaries
|
|
17
|
+
- beszel_node is match('^[a-z][a-z0-9-]{0,62}$')
|
|
18
|
+
- beszel_hub_url is match('^https://[a-z0-9.-]+\.ts\.net:8443$')
|
|
19
|
+
- name: Inspect existing native service ownership
|
|
20
|
+
ansible.builtin.stat:
|
|
21
|
+
path: /etc/systemd/system/beszel-agent.service
|
|
22
|
+
checksum_algorithm: sha256
|
|
23
|
+
register: existing_service
|
|
24
|
+
- name: Read existing service ownership marker
|
|
25
|
+
ansible.builtin.slurp:
|
|
26
|
+
src: /etc/systemd/system/beszel-agent.service
|
|
27
|
+
register: existing_unit
|
|
28
|
+
when: existing_service.stat.exists
|
|
29
|
+
- name: Require explicit adoption of any previously unmanaged service
|
|
30
|
+
ansible.builtin.assert:
|
|
31
|
+
that:
|
|
32
|
+
- "not existing_service.stat.exists or (existing_unit.content | default('') | b64decode).startswith('# Managed by personal Fleet:')"
|
|
33
|
+
fail_msg: Unmanaged Beszel unit exists. Review a source-controlled migration before replacing it.
|
|
34
|
+
- name: Create unprivileged service account
|
|
35
|
+
ansible.builtin.user:
|
|
36
|
+
name: beszel
|
|
37
|
+
system: true
|
|
38
|
+
create_home: false
|
|
39
|
+
shell: /usr/sbin/nologin
|
|
40
|
+
- name: Create restricted configuration and version directories
|
|
41
|
+
ansible.builtin.file:
|
|
42
|
+
path: '{{ item.path }}'
|
|
43
|
+
state: directory
|
|
44
|
+
owner: root
|
|
45
|
+
group: '{{ item.group }}'
|
|
46
|
+
mode: '{{ item.mode }}'
|
|
47
|
+
loop:
|
|
48
|
+
- { path: /etc/personal-beszel, group: beszel, mode: '0750' }
|
|
49
|
+
- {
|
|
50
|
+
path: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}',
|
|
51
|
+
group: root,
|
|
52
|
+
mode: '0755',
|
|
53
|
+
}
|
|
54
|
+
- name: Deliver node-specific token through private stdin, never command arguments
|
|
55
|
+
ansible.builtin.copy:
|
|
56
|
+
dest: '/etc/personal-beszel/{{ item.name }}'
|
|
57
|
+
content: '{{ item.value }}'
|
|
58
|
+
owner: root
|
|
59
|
+
group: beszel
|
|
60
|
+
mode: '0640'
|
|
61
|
+
loop:
|
|
62
|
+
- { name: hub-key, value: '{{ beszel_public_key }}' }
|
|
63
|
+
- { name: agent-token, value: '{{ beszel_agent_token }}' }
|
|
64
|
+
no_log: true
|
|
65
|
+
notify: Restart agent
|
|
66
|
+
- name: Require credentials delivered through the approved secret owner
|
|
67
|
+
ansible.builtin.stat:
|
|
68
|
+
path: '/etc/personal-beszel/{{ item }}'
|
|
69
|
+
loop: [hub-key, agent-token]
|
|
70
|
+
register: credentials
|
|
71
|
+
no_log: true
|
|
72
|
+
- name: Fail closed without node-specific credentials
|
|
73
|
+
ansible.builtin.assert:
|
|
74
|
+
that: item.stat.exists and item.stat.isreg and item.stat.size > 0
|
|
75
|
+
fail_msg: Beszel credentials missing; resume enrollment through Fleet.
|
|
76
|
+
loop: '{{ credentials.results }}'
|
|
77
|
+
no_log: true
|
|
78
|
+
- name: Restrict credential permissions
|
|
79
|
+
ansible.builtin.file:
|
|
80
|
+
path: '/etc/personal-beszel/{{ item }}'
|
|
81
|
+
owner: root
|
|
82
|
+
group: beszel
|
|
83
|
+
mode: '0640'
|
|
84
|
+
loop: [hub-key, agent-token]
|
|
85
|
+
- name: Download pinned native artifact with signature-preserving HTTPS and checksum
|
|
86
|
+
ansible.builtin.get_url:
|
|
87
|
+
url: 'https://github.com/henrygd/beszel/releases/download/v{{ beszel_lock.version }}/beszel-agent_linux_{{ beszel_arch }}.tar.gz'
|
|
88
|
+
dest: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}/agent.tar.gz'
|
|
89
|
+
checksum: 'sha256:{{ beszel_lock.agents[beszel_arch] }}'
|
|
90
|
+
mode: '0644'
|
|
91
|
+
timeout: 60
|
|
92
|
+
# The executable is compared with the digest of the pinned build, so an
|
|
93
|
+
# unchanged agent is left alone (no restart) and one that an interrupted
|
|
94
|
+
# upgrade or an edit left behind is replaced.
|
|
95
|
+
- name: Inspect the installed executable
|
|
96
|
+
ansible.builtin.stat:
|
|
97
|
+
path: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}/beszel-agent'
|
|
98
|
+
checksum_algorithm: sha256
|
|
99
|
+
follow: false
|
|
100
|
+
register: agent_binary
|
|
101
|
+
- name: Remove an executable that is not the pinned build
|
|
102
|
+
ansible.builtin.file:
|
|
103
|
+
path: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}/beszel-agent'
|
|
104
|
+
state: absent
|
|
105
|
+
when: >-
|
|
106
|
+
agent_binary.stat.exists and (not agent_binary.stat.isreg
|
|
107
|
+
or agent_binary.stat.checksum != beszel_lock.agentBinaries[beszel_arch])
|
|
108
|
+
- name: Extract the pinned executable when it is missing
|
|
109
|
+
ansible.builtin.unarchive:
|
|
110
|
+
src: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}/agent.tar.gz'
|
|
111
|
+
dest: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}'
|
|
112
|
+
remote_src: true
|
|
113
|
+
include: [beszel-agent]
|
|
114
|
+
creates: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}/beszel-agent'
|
|
115
|
+
notify: Restart agent
|
|
116
|
+
- name: Inspect the extracted executable
|
|
117
|
+
ansible.builtin.stat:
|
|
118
|
+
path: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}/beszel-agent'
|
|
119
|
+
checksum_algorithm: sha256
|
|
120
|
+
follow: false
|
|
121
|
+
register: agent_extracted
|
|
122
|
+
- name: Require the pinned build before the service may start it
|
|
123
|
+
ansible.builtin.assert:
|
|
124
|
+
that:
|
|
125
|
+
- agent_extracted.stat.exists
|
|
126
|
+
- agent_extracted.stat.isreg
|
|
127
|
+
- agent_extracted.stat.checksum == beszel_lock.agentBinaries[beszel_arch]
|
|
128
|
+
fail_msg: The extracted Beszel agent does not match the pinned build.
|
|
129
|
+
- name: Keep the executable owned by root and not writable by the agent
|
|
130
|
+
ansible.builtin.file:
|
|
131
|
+
path: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}/beszel-agent'
|
|
132
|
+
owner: root
|
|
133
|
+
group: root
|
|
134
|
+
mode: '0755'
|
|
135
|
+
- name: Install nonsecret node connection settings
|
|
136
|
+
ansible.builtin.copy:
|
|
137
|
+
dest: /etc/personal-beszel/agent.env
|
|
138
|
+
content: |
|
|
139
|
+
HUB_URL={{ beszel_hub_url }}
|
|
140
|
+
SYSTEM_NAME={{ beszel_node }}
|
|
141
|
+
owner: root
|
|
142
|
+
group: beszel
|
|
143
|
+
mode: '0640'
|
|
144
|
+
backup: true
|
|
145
|
+
notify: Restart agent
|
|
146
|
+
- name: Install source-owned systemd service
|
|
147
|
+
ansible.builtin.copy:
|
|
148
|
+
src: beszel-agent.service
|
|
149
|
+
backup: true
|
|
150
|
+
dest: /etc/systemd/system/beszel-agent.service
|
|
151
|
+
owner: root
|
|
152
|
+
group: root
|
|
153
|
+
mode: '0644'
|
|
154
|
+
notify: Restart agent
|
|
155
|
+
- name: Enable agent at boot
|
|
156
|
+
ansible.builtin.systemd_service:
|
|
157
|
+
name: beszel-agent.service
|
|
158
|
+
enabled: true
|
|
159
|
+
state: started
|
|
160
|
+
daemon_reload: true
|
|
161
|
+
- name: Complete any required agent restart before verification
|
|
162
|
+
ansible.builtin.meta: flush_handlers
|
|
163
|
+
- name: Independently check agent service postcondition
|
|
164
|
+
ansible.builtin.command:
|
|
165
|
+
argv: [systemctl, is-active, beszel-agent.service]
|
|
166
|
+
changed_when: false
|
|
167
|
+
handlers:
|
|
168
|
+
- name: Restart agent
|
|
169
|
+
ansible.builtin.systemd_service:
|
|
170
|
+
name: beszel-agent.service
|
|
171
|
+
state: restarted
|
|
172
|
+
daemon_reload: true
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Managed by personal Fleet: ansible/monitoring
|
|
2
|
+
[Unit]
|
|
3
|
+
Description=Personal Fleet Beszel agent
|
|
4
|
+
Wants=network-online.target tailscaled.service
|
|
5
|
+
After=network-online.target tailscaled.service
|
|
6
|
+
StartLimitIntervalSec=0
|
|
7
|
+
|
|
8
|
+
[Service]
|
|
9
|
+
Type=simple
|
|
10
|
+
User=beszel
|
|
11
|
+
Group=beszel
|
|
12
|
+
EnvironmentFile=/etc/personal-beszel/agent.env
|
|
13
|
+
Environment=DATA_DIR=/var/lib/beszel-agent
|
|
14
|
+
Environment=KEY_FILE=/etc/personal-beszel/hub-key
|
|
15
|
+
Environment=TOKEN_FILE=/etc/personal-beszel/agent-token
|
|
16
|
+
Environment=DISABLE_SSH=true
|
|
17
|
+
Environment=SKIP_SYSTEMD_LOGS=true
|
|
18
|
+
Environment=SERVICE_PATTERNS=ssh.service,tailscaled.service,t3code-desktop.service,personal-fleet-sync.service
|
|
19
|
+
ExecStart=/usr/local/lib/personal-beszel/0.21.1/beszel-agent
|
|
20
|
+
StateDirectory=beszel-agent
|
|
21
|
+
StateDirectoryMode=0700
|
|
22
|
+
Restart=always
|
|
23
|
+
RestartSec=20
|
|
24
|
+
TimeoutStopSec=20
|
|
25
|
+
NoNewPrivileges=true
|
|
26
|
+
ProtectSystem=strict
|
|
27
|
+
ProtectHome=true
|
|
28
|
+
PrivateTmp=true
|
|
29
|
+
ProtectKernelTunables=true
|
|
30
|
+
ProtectKernelModules=true
|
|
31
|
+
ProtectControlGroups=true
|
|
32
|
+
RestrictSUIDSGID=true
|
|
33
|
+
LockPersonality=true
|
|
34
|
+
MemoryMax=256M
|
|
35
|
+
CPUQuota=25%
|
|
36
|
+
TasksMax=128
|
|
37
|
+
UMask=0077
|
|
38
|
+
LogRateLimitIntervalSec=60
|
|
39
|
+
LogRateLimitBurst=100
|
|
40
|
+
|
|
41
|
+
[Install]
|
|
42
|
+
WantedBy=multi-user.target
|