@crvouga/dev-fleet 0.0.0-stage → 0.1.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 +157 -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 +128 -0
- package/agents/t3.json +40 -0
- package/ansible/macos/manager.yml +30 -0
- package/ansible/macos/policy.json +17 -0
- package/ansible/monitoring/README.md +154 -0
- package/ansible/monitoring/agent-darwin.yml +198 -0
- package/ansible/monitoring/agent.yml +134 -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/lock.json +11 -0
- package/ansible/net/remote-probe.sh +13 -0
- package/ansible/ubuntu/authorize-sudo.sh +51 -0
- package/ansible/ubuntu/baseline.yml +89 -0
- package/ansible/ubuntu/disk-guard.py +104 -0
- package/ansible/ubuntu/policy.json +43 -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 +75 -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 +252 -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/bin/devfleet.mjs +36 -0
- package/bootstrap/bootstrap.ps1 +50 -0
- package/bootstrap/bootstrap.sh +73 -0
- package/bootstrap/devfleet +7 -0
- package/bootstrap/docker-desktop.sh +33 -0
- package/bootstrap/doctor.ts +189 -0
- package/bootstrap/fleet-manager-run.sh +76 -0
- package/bootstrap/fleet-remote-bootstrap.sh +185 -0
- package/bootstrap/global-config.ts +113 -0
- package/bootstrap/install-launcher.sh +7 -0
- package/bootstrap/native-aws.ts +86 -0
- package/bootstrap/native-t3.ts +154 -0
- package/bootstrap/npm.ts +350 -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/examples/fleet.json +95 -0
- package/examples/roster.json +13 -0
- package/mise.lock +1492 -0
- package/mise.toml +99 -0
- package/package.json +65 -4
- package/schema/fleet.schema.json +364 -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 +156 -0
- package/src/agents/launch.ts +290 -0
- package/src/agents/native-verify.ts +226 -0
- package/src/agents/provider-errors.ts +50 -0
- package/src/agents/registry.ts +55 -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 +95 -0
- package/src/cli/app.ts +115 -0
- package/src/cli/cli.ts +21 -0
- package/src/cli/command-index.ts +50 -0
- package/src/cli/contracts.ts +25 -0
- package/src/cli/fleet-reference.ts +17 -0
- package/src/cli/fleet-sync-checkout.ts +91 -0
- package/src/cli/fleet-sync-enrollment.ts +144 -0
- package/src/cli/fleet-sync-forward.ts +134 -0
- package/src/cli/fleet-sync-manager.ts +193 -0
- package/src/cli/fleet-sync-monitoring.ts +185 -0
- package/src/cli/fleet-sync-output.ts +314 -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 +236 -0
- package/src/cli/fleet-sync-report.ts +26 -0
- package/src/cli/fleet-sync-schedule.ts +138 -0
- package/src/cli/fleet-sync-vault.ts +166 -0
- package/src/cli/fleet-sync.ts +309 -0
- package/src/cli/fleet.ts +163 -0
- package/src/cli/managed-health.ts +219 -0
- package/src/cli/node-deployment.ts +34 -0
- package/src/cli/node-sync.ts +114 -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 +169 -0
- package/src/cli/register-fleet.ts +246 -0
- package/src/cli/registry.ts +158 -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 +89 -0
- package/src/cli/search.ts +35 -0
- package/src/cli/settings.ts +64 -0
- package/src/cli/source-operations.ts +78 -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 +260 -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 +88 -0
- package/src/index.ts +6 -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 +185 -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 +400 -0
- package/src/manager.ts +62 -0
- package/src/monitoring/apply.ts +171 -0
- package/src/monitoring/beszel.ts +191 -0
- package/src/monitoring/enroll.ts +110 -0
- package/src/monitoring/hub-access.ts +113 -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 +232 -0
- package/src/monitoring/remote.ts +65 -0
- package/src/network-definition.ts +83 -0
- package/src/node-converge-linux.ts +372 -0
- package/src/node-converge-shared.ts +83 -0
- package/src/node-converge.ts +40 -0
- package/src/onboard.ts +35 -0
- package/src/platform/ansible-receipt.ts +128 -0
- package/src/platform/ansible.ts +62 -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 +128 -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 +63 -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 +212 -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/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 +48 -0
- package/src/t3-linked-check.ts +101 -0
- package/src/tailnet-health.ts +231 -0
- package/src/tailnet-matching.ts +37 -0
- package/src/tailscale.ts +141 -0
- package/src/test-fixtures.ts +36 -0
- package/src/toolchain-checks.ts +83 -0
- package/src/topology.ts +24 -0
- package/src/transport.ts +122 -0
- package/src/types.ts +95 -0
- package/src/vault-config.ts +38 -0
- package/src/worker-access-checks.ts +115 -0
- package/src/worker-capacity.ts +34 -0
- package/src/worker-host-checks.ts +176 -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,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,134 @@
|
|
|
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_node is match('^[a-z][a-z0-9-]{0,62}$')
|
|
17
|
+
- beszel_hub_url is match('^https://[a-z0-9.-]+\.ts\.net:8443$')
|
|
18
|
+
- name: Inspect existing native service ownership
|
|
19
|
+
ansible.builtin.stat:
|
|
20
|
+
path: /etc/systemd/system/beszel-agent.service
|
|
21
|
+
checksum_algorithm: sha256
|
|
22
|
+
register: existing_service
|
|
23
|
+
- name: Read existing service ownership marker
|
|
24
|
+
ansible.builtin.slurp:
|
|
25
|
+
src: /etc/systemd/system/beszel-agent.service
|
|
26
|
+
register: existing_unit
|
|
27
|
+
when: existing_service.stat.exists
|
|
28
|
+
- name: Require explicit adoption of any previously unmanaged service
|
|
29
|
+
ansible.builtin.assert:
|
|
30
|
+
that:
|
|
31
|
+
- "not existing_service.stat.exists or (existing_unit.content | default('') | b64decode).startswith('# Managed by personal Fleet:')"
|
|
32
|
+
fail_msg: Unmanaged Beszel unit exists. Review a source-controlled migration before replacing it.
|
|
33
|
+
- name: Create unprivileged service account
|
|
34
|
+
ansible.builtin.user:
|
|
35
|
+
name: beszel
|
|
36
|
+
system: true
|
|
37
|
+
create_home: false
|
|
38
|
+
shell: /usr/sbin/nologin
|
|
39
|
+
- name: Create restricted configuration and version directories
|
|
40
|
+
ansible.builtin.file:
|
|
41
|
+
path: '{{ item.path }}'
|
|
42
|
+
state: directory
|
|
43
|
+
owner: root
|
|
44
|
+
group: '{{ item.group }}'
|
|
45
|
+
mode: '{{ item.mode }}'
|
|
46
|
+
loop:
|
|
47
|
+
- { path: /etc/personal-beszel, group: beszel, mode: '0750' }
|
|
48
|
+
- {
|
|
49
|
+
path: '/usr/local/lib/personal-beszel/0.21.1',
|
|
50
|
+
group: root,
|
|
51
|
+
mode: '0755',
|
|
52
|
+
}
|
|
53
|
+
- name: Deliver node-specific token through private stdin, never command arguments
|
|
54
|
+
ansible.builtin.copy:
|
|
55
|
+
dest: '/etc/personal-beszel/{{ item.name }}'
|
|
56
|
+
content: '{{ item.value }}'
|
|
57
|
+
owner: root
|
|
58
|
+
group: beszel
|
|
59
|
+
mode: '0640'
|
|
60
|
+
loop:
|
|
61
|
+
- { name: hub-key, value: '{{ beszel_public_key }}' }
|
|
62
|
+
- { name: agent-token, value: '{{ beszel_agent_token }}' }
|
|
63
|
+
no_log: true
|
|
64
|
+
notify: Restart agent
|
|
65
|
+
- name: Require credentials delivered through the approved secret owner
|
|
66
|
+
ansible.builtin.stat:
|
|
67
|
+
path: '/etc/personal-beszel/{{ item }}'
|
|
68
|
+
loop: [hub-key, agent-token]
|
|
69
|
+
register: credentials
|
|
70
|
+
no_log: true
|
|
71
|
+
- name: Fail closed without node-specific credentials
|
|
72
|
+
ansible.builtin.assert:
|
|
73
|
+
that: item.stat.exists and item.stat.isreg and item.stat.size > 0
|
|
74
|
+
fail_msg: Beszel credentials missing; resume enrollment through Fleet.
|
|
75
|
+
loop: '{{ credentials.results }}'
|
|
76
|
+
no_log: true
|
|
77
|
+
- name: Restrict credential permissions
|
|
78
|
+
ansible.builtin.file:
|
|
79
|
+
path: '/etc/personal-beszel/{{ item }}'
|
|
80
|
+
owner: root
|
|
81
|
+
group: beszel
|
|
82
|
+
mode: '0640'
|
|
83
|
+
loop: [hub-key, agent-token]
|
|
84
|
+
- name: Download pinned native artifact with signature-preserving HTTPS and checksum
|
|
85
|
+
ansible.builtin.get_url:
|
|
86
|
+
url: 'https://github.com/henrygd/beszel/releases/download/v{{ beszel_lock.version }}/beszel-agent_linux_{{ beszel_arch }}.tar.gz'
|
|
87
|
+
dest: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}/agent.tar.gz'
|
|
88
|
+
checksum: 'sha256:{{ beszel_lock.agents[beszel_arch] }}'
|
|
89
|
+
mode: '0644'
|
|
90
|
+
timeout: 60
|
|
91
|
+
- name: Extract the pinned executable and repair file drift
|
|
92
|
+
ansible.builtin.unarchive:
|
|
93
|
+
src: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}/agent.tar.gz'
|
|
94
|
+
dest: '/usr/local/lib/personal-beszel/{{ beszel_lock.version }}'
|
|
95
|
+
remote_src: true
|
|
96
|
+
notify: Restart agent
|
|
97
|
+
- name: Install nonsecret node connection settings
|
|
98
|
+
ansible.builtin.copy:
|
|
99
|
+
dest: /etc/personal-beszel/agent.env
|
|
100
|
+
content: |
|
|
101
|
+
HUB_URL={{ beszel_hub_url }}
|
|
102
|
+
SYSTEM_NAME={{ beszel_node }}
|
|
103
|
+
owner: root
|
|
104
|
+
group: beszel
|
|
105
|
+
mode: '0640'
|
|
106
|
+
backup: true
|
|
107
|
+
notify: Restart agent
|
|
108
|
+
- name: Install source-owned systemd service
|
|
109
|
+
ansible.builtin.copy:
|
|
110
|
+
src: beszel-agent.service
|
|
111
|
+
backup: true
|
|
112
|
+
dest: /etc/systemd/system/beszel-agent.service
|
|
113
|
+
owner: root
|
|
114
|
+
group: root
|
|
115
|
+
mode: '0644'
|
|
116
|
+
notify: Restart agent
|
|
117
|
+
- name: Enable agent at boot
|
|
118
|
+
ansible.builtin.systemd_service:
|
|
119
|
+
name: beszel-agent.service
|
|
120
|
+
enabled: true
|
|
121
|
+
state: started
|
|
122
|
+
daemon_reload: true
|
|
123
|
+
- name: Complete any required agent restart before verification
|
|
124
|
+
ansible.builtin.meta: flush_handlers
|
|
125
|
+
- name: Independently check agent service postcondition
|
|
126
|
+
ansible.builtin.command:
|
|
127
|
+
argv: [systemctl, is-active, beszel-agent.service]
|
|
128
|
+
changed_when: false
|
|
129
|
+
handlers:
|
|
130
|
+
- name: Restart agent
|
|
131
|
+
ansible.builtin.systemd_service:
|
|
132
|
+
name: beszel-agent.service
|
|
133
|
+
state: restarted
|
|
134
|
+
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
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Run on the manager only, through hub-darwin.yml from the verified checkout.
|
|
2
|
+
# Never copy the hub data to a worker or run a second copy of its identity.
|
|
3
|
+
name: personal-beszel
|
|
4
|
+
services:
|
|
5
|
+
hub:
|
|
6
|
+
image: henrygd/beszel:0.21.1@sha256:8514a69d9bbbcb74f371a3205561ee231fc0289739cea64e8131f341d695b770
|
|
7
|
+
restart: unless-stopped
|
|
8
|
+
ports:
|
|
9
|
+
- '127.0.0.1:8090:8090'
|
|
10
|
+
environment:
|
|
11
|
+
APP_URL: '${BESZEL_APP_URL:-${BESZEL_HUB_URL:?private HTTPS hub URL required}}'
|
|
12
|
+
DISABLE_SSH: 'true'
|
|
13
|
+
CHECK_UPDATES: 'false'
|
|
14
|
+
# Single-operator private hub: fleet-registered systems are visible to every login.
|
|
15
|
+
SHARE_ALL_SYSTEMS: 'true'
|
|
16
|
+
# Visitors are signed in automatically as this read-only viewer account
|
|
17
|
+
# (never the fleet admin), so the dashboard needs no login. Empty keeps
|
|
18
|
+
# the normal login page.
|
|
19
|
+
AUTO_LOGIN: '${BESZEL_AUTO_LOGIN:-}'
|
|
20
|
+
# First start only (empty data directory): Beszel creates the fleet login
|
|
21
|
+
# as a verified user and superuser. hub-darwin.yml sets these just once.
|
|
22
|
+
USER_EMAIL: '${BESZEL_BOOTSTRAP_EMAIL:-}'
|
|
23
|
+
USER_PASSWORD: '${BESZEL_BOOTSTRAP_PASSWORD:-}'
|
|
24
|
+
volumes:
|
|
25
|
+
- type: bind
|
|
26
|
+
source: '${BESZEL_DATA_DIR:?per-user hub data directory required}'
|
|
27
|
+
target: /beszel_data
|
|
28
|
+
bind:
|
|
29
|
+
create_host_path: false
|
|
30
|
+
logging:
|
|
31
|
+
driver: local
|
|
32
|
+
options:
|
|
33
|
+
max-size: '10m'
|
|
34
|
+
max-file: '3'
|
|
35
|
+
mem_limit: 512m
|
|
36
|
+
cpus: 1.0
|
|
37
|
+
pids_limit: 128
|
|
38
|
+
stop_grace_period: 30s
|
|
39
|
+
# Outbound-only Cloudflare Tunnel for the public dashboard hostname. The
|
|
40
|
+
# connector token is delivered from the manager's Vault session to a 0600
|
|
41
|
+
# per-user file by hub-darwin.yml.
|
|
42
|
+
cloudflared:
|
|
43
|
+
profiles: [public-endpoint]
|
|
44
|
+
image: cloudflare/cloudflared:2026.9.3@sha256:072c067d25ccbe61d46e18f0d0723255f2bb5304f7317caa95b27031520ff92c
|
|
45
|
+
user: '1000:1000'
|
|
46
|
+
restart: unless-stopped
|
|
47
|
+
command:
|
|
48
|
+
[
|
|
49
|
+
'tunnel',
|
|
50
|
+
'--no-autoupdate',
|
|
51
|
+
'run',
|
|
52
|
+
'--token-file',
|
|
53
|
+
'/run/secrets/tunnel-token',
|
|
54
|
+
]
|
|
55
|
+
volumes:
|
|
56
|
+
- type: bind
|
|
57
|
+
source: '${BESZEL_TUNNEL_TOKEN_FILE:?per-user connector token file required}'
|
|
58
|
+
target: /run/secrets/tunnel-token
|
|
59
|
+
read_only: true
|
|
60
|
+
bind:
|
|
61
|
+
create_host_path: false
|
|
62
|
+
depends_on:
|
|
63
|
+
- hub
|
|
64
|
+
read_only: true
|
|
65
|
+
cap_drop: [ALL]
|
|
66
|
+
security_opt: ['no-new-privileges:true']
|
|
67
|
+
mem_limit: 256m
|
|
68
|
+
cpus: 0.5
|
|
69
|
+
pids_limit: 128
|
|
70
|
+
logging:
|
|
71
|
+
driver: local
|
|
72
|
+
options:
|
|
73
|
+
max-size: '5m'
|
|
74
|
+
max-file: '2'
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The Beszel hub runs only on the manager (the human's macOS machine) under
|
|
3
|
+
# Docker Desktop, as that user: no become, per-user data and credential paths.
|
|
4
|
+
# Runtime variables arrive through a private 0600 variables file, never argv.
|
|
5
|
+
- name: Converge the Beszel hub on the manager
|
|
6
|
+
hosts: localhost
|
|
7
|
+
connection: local
|
|
8
|
+
become: false
|
|
9
|
+
gather_facts: true
|
|
10
|
+
vars:
|
|
11
|
+
beszel_lock: "{{ lookup('ansible.builtin.file', 'lock.json') | from_json }}"
|
|
12
|
+
beszel_compose:
|
|
13
|
+
- docker
|
|
14
|
+
- compose
|
|
15
|
+
- '-f'
|
|
16
|
+
- '{{ playbook_dir }}/compose.yml'
|
|
17
|
+
beszel_environment:
|
|
18
|
+
BESZEL_HUB_URL: '{{ beszel_hub_url }}'
|
|
19
|
+
BESZEL_APP_URL: '{{ beszel_public_url | default(beszel_hub_url) }}'
|
|
20
|
+
BESZEL_DATA_DIR: '{{ beszel_data_dir }}'
|
|
21
|
+
BESZEL_AUTO_LOGIN: '{{ beszel_auto_login | default("") }}'
|
|
22
|
+
BESZEL_TUNNEL_TOKEN_FILE: '{{ beszel_tunnel_dir }}/token'
|
|
23
|
+
tasks:
|
|
24
|
+
- name: Require the macOS manager and private dashboard policy
|
|
25
|
+
ansible.builtin.assert:
|
|
26
|
+
that:
|
|
27
|
+
- ansible_facts.os_family == 'Darwin'
|
|
28
|
+
- beszel_hub_url is match('^https://[a-z0-9.-]+\.ts\.net:8443$')
|
|
29
|
+
- beszel_data_dir is match('^/Users/[^/]+/Library/Application Support/personal-beszel$')
|
|
30
|
+
- beszel_tunnel_dir is match('^/Users/[^/]+/Library/Application Support/personal-beszel-tunnel$')
|
|
31
|
+
fail_msg: BESZEL_NOT_MANAGER; the hub runs only on the macOS manager with per-user data.
|
|
32
|
+
- name: Require a running Docker Desktop engine with Compose
|
|
33
|
+
ansible.builtin.command:
|
|
34
|
+
argv: [docker, compose, version]
|
|
35
|
+
changed_when: false
|
|
36
|
+
- name: Inspect an existing hub image before any upgrade
|
|
37
|
+
ansible.builtin.command:
|
|
38
|
+
argv:
|
|
39
|
+
[
|
|
40
|
+
docker,
|
|
41
|
+
inspect,
|
|
42
|
+
'--format',
|
|
43
|
+
'{{ "{{.Config.Image}}" }}',
|
|
44
|
+
personal-beszel-hub-1,
|
|
45
|
+
]
|
|
46
|
+
changed_when: false
|
|
47
|
+
failed_when: false
|
|
48
|
+
register: existing_hub
|
|
49
|
+
- name: Require explicit maintenance for a different existing image
|
|
50
|
+
ansible.builtin.assert:
|
|
51
|
+
that: existing_hub.rc != 0 or existing_hub.stdout == beszel_lock.image
|
|
52
|
+
fail_msg: BESZEL_HUB_UPGRADE_PENDING; take and verify a backup before an explicit maintenance upgrade.
|
|
53
|
+
- name: Create per-user hub data and connector directories without replacing data
|
|
54
|
+
ansible.builtin.file:
|
|
55
|
+
path: '{{ item }}'
|
|
56
|
+
state: directory
|
|
57
|
+
mode: '0700'
|
|
58
|
+
loop:
|
|
59
|
+
- '{{ beszel_data_dir }}'
|
|
60
|
+
- '{{ beszel_tunnel_dir }}'
|
|
61
|
+
- name: Write the public dashboard connector token for the current user only
|
|
62
|
+
ansible.builtin.copy:
|
|
63
|
+
dest: '{{ beszel_tunnel_dir }}/token'
|
|
64
|
+
content: '{{ beszel_tunnel_token }}'
|
|
65
|
+
mode: '0600'
|
|
66
|
+
when: beszel_tunnel_token | default('') | length > 0
|
|
67
|
+
no_log: true
|
|
68
|
+
- name: Detect a hub that has never started (no database yet)
|
|
69
|
+
ansible.builtin.stat:
|
|
70
|
+
path: '{{ beszel_data_dir }}/data.db'
|
|
71
|
+
register: hub_database
|
|
72
|
+
- name: Start pinned stock Compose service
|
|
73
|
+
ansible.builtin.command:
|
|
74
|
+
argv: "{{ beszel_compose + ['up', '-d', '--pull', 'missing', 'hub'] }}"
|
|
75
|
+
# A fresh hub creates the fleet login itself on first start, so no one
|
|
76
|
+
# has to create the account by hand. Existing data is never changed.
|
|
77
|
+
environment: >-
|
|
78
|
+
{{ beszel_environment | combine({
|
|
79
|
+
'BESZEL_BOOTSTRAP_EMAIL': beszel_fleet_identity,
|
|
80
|
+
'BESZEL_BOOTSTRAP_PASSWORD': beszel_fleet_password
|
|
81
|
+
} if (not hub_database.stat.exists
|
|
82
|
+
and beszel_fleet_password | default('') | length > 0)
|
|
83
|
+
else {}) }}
|
|
84
|
+
no_log: true
|
|
85
|
+
register: hub_apply
|
|
86
|
+
changed_when: "'Started' in hub_apply.stderr or 'Created' in hub_apply.stderr or 'Recreated' in hub_apply.stderr"
|
|
87
|
+
- name: Start the public connector when its credential is present
|
|
88
|
+
ansible.builtin.command:
|
|
89
|
+
argv: "{{ beszel_compose + ['--profile', 'public-endpoint', 'up', '-d', '--pull', 'missing'] }}"
|
|
90
|
+
environment: '{{ beszel_environment }}'
|
|
91
|
+
when: beszel_tunnel_token | default('') | length > 0
|
|
92
|
+
register: tunnel_apply
|
|
93
|
+
changed_when: "'Started' in tunnel_apply.stderr or 'Created' in tunnel_apply.stderr or 'Recreated' in tunnel_apply.stderr"
|
|
94
|
+
- name: Independently verify the local hub health endpoint
|
|
95
|
+
ansible.builtin.uri:
|
|
96
|
+
url: http://127.0.0.1:8090/api/health
|
|
97
|
+
status_code: 200
|
|
98
|
+
return_content: false
|
|
99
|
+
retries: 10
|
|
100
|
+
delay: 2
|
|
101
|
+
register: hub_health
|
|
102
|
+
until: hub_health.status == 200
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.21.1",
|
|
3
|
+
"image": "henrygd/beszel:0.21.1@sha256:8514a69d9bbbcb74f371a3205561ee231fc0289739cea64e8131f341d695b770",
|
|
4
|
+
"agents": {
|
|
5
|
+
"amd64": "3b6ee592632ec03b9a43312e99a40203a7b3d83fe40dd0c25acda1eb915ea7c0",
|
|
6
|
+
"arm64": "a6ea544976b9a74398ac88d5da5bb96813d9a2e353a37bbfdb6a903fa8ba1838",
|
|
7
|
+
"darwin-arm64": "9246065f680cdb0af2885b7bf5876dc45b754019f2c90eb28550cf45db978eb8",
|
|
8
|
+
"darwin-amd64": "21f4a03fd90ae46869cfcbb69e21e8312f0f05deff49669d4602a3dee4866981"
|
|
9
|
+
},
|
|
10
|
+
"source": "https://github.com/henrygd/beszel/releases/tag/v0.21.1"
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Read-only health probe that `dev-fleet net check` pipes to each host over SSH (`sh -s`).
|
|
2
|
+
# Prints key=value lines; works on macOS and Linux (including WSL).
|
|
3
|
+
if [ "$(uname)" = Darwin ]; then
|
|
4
|
+
echo "ncpu=$(sysctl -n hw.ncpu)"
|
|
5
|
+
echo "load=$(sysctl -n vm.loadavg | awk '{print $2}')"
|
|
6
|
+
echo "memfree=$(memory_pressure -Q 2>/dev/null | awk -F': ' '/percentage/{gsub("%","",$2); print $2}')"
|
|
7
|
+
sysctl -n vm.swapusage | awk '{t=$3; u=$6; gsub("M","",t); gsub("M","",u); if (t>0) print "swap=" int(u*100/t); else print "swap=0"}'
|
|
8
|
+
else
|
|
9
|
+
echo "ncpu=$(nproc)"
|
|
10
|
+
echo "load=$(cut -d' ' -f1 /proc/loadavg)"
|
|
11
|
+
awk '/MemTotal/{t=$2} /MemAvailable/{a=$2} /SwapTotal/{st=$2} /SwapFree/{sf=$2} END{print "memfree=" int(a*100/t); if (st>0) print "swap=" int((st-sf)*100/st); else print "swap=0"}' /proc/meminfo
|
|
12
|
+
fi
|
|
13
|
+
echo "diskmb=$(df -Pk "$HOME" | awk 'NR==2{print int($4/1024)}')"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Manager-led, explicitly approved initial privilege step. Run through a real
|
|
3
|
+
# SSH terminal so sudo reads the operator's password itself; never capture it.
|
|
4
|
+
set -eu
|
|
5
|
+
umask 077
|
|
6
|
+
case "$(uname -s)" in
|
|
7
|
+
Linux)
|
|
8
|
+
. /etc/os-release
|
|
9
|
+
[ "$ID" = ubuntu ] && [ "$VERSION_ID" = 26.04 ] || exit 1 ;;
|
|
10
|
+
Darwin)
|
|
11
|
+
[ "$(sw_vers -productVersion | cut -d. -f1)" -ge 14 ] || exit 1 ;;
|
|
12
|
+
*) exit 1 ;;
|
|
13
|
+
esac
|
|
14
|
+
user=$(id -un)
|
|
15
|
+
case "$user" in root|''|*[!a-z0-9_-]*) exit 1;; esac
|
|
16
|
+
command -v sudo >/dev/null 2>&1 || exit 1
|
|
17
|
+
# The same operation lock used by remote bootstrap excludes another installer.
|
|
18
|
+
root="$HOME/.local/share/personal/fleet"
|
|
19
|
+
for path in "$HOME/.local" "$HOME/.local/share" "$HOME/.local/share/personal" "$root"; do
|
|
20
|
+
[ ! -L "$path" ] || exit 1
|
|
21
|
+
done
|
|
22
|
+
mkdir -p "$root"
|
|
23
|
+
chmod 700 "$root"
|
|
24
|
+
[ ! -L "$root/remote-operation.lock" ] || exit 1
|
|
25
|
+
exec 9>"$root/remote-operation.lock"
|
|
26
|
+
if command -v flock >/dev/null 2>&1; then
|
|
27
|
+
flock -n 9 || exit 1
|
|
28
|
+
else
|
|
29
|
+
python3 -c 'import fcntl; fcntl.flock(9, fcntl.LOCK_EX | fcntl.LOCK_NB)' 9>&9 || exit 1
|
|
30
|
+
fi
|
|
31
|
+
sudo -v
|
|
32
|
+
file=$(mktemp)
|
|
33
|
+
trap 'rm -f "$file"' EXIT
|
|
34
|
+
trap 'exit 1' HUP INT TERM
|
|
35
|
+
printf '# Managed by personal Fleet: designated development user\n%s ALL=(ALL:ALL) NOPASSWD: ALL\n' "$user" >"$file"
|
|
36
|
+
sudo -n visudo -cf "$file" >/dev/null
|
|
37
|
+
# This dedicated path is owned by the same Workstation baseline. Preserve any
|
|
38
|
+
# existing fragment for recovery before installing the validated declaration.
|
|
39
|
+
target="/etc/sudoers.d/personal-fleet-$user"
|
|
40
|
+
if sudo -n test -e "$target"; then
|
|
41
|
+
sudo -n test ! -L "$target" || exit 1
|
|
42
|
+
sudo -n grep -qxF '# Managed by personal Fleet: designated development user' "$target" || {
|
|
43
|
+
printf '%s\n' 'SUDOERS_OWNERSHIP_CONFLICT: preserve the existing fragment and review its source before migration.' >&2
|
|
44
|
+
exit 1
|
|
45
|
+
}
|
|
46
|
+
sudo -n cp -p -- "$target" "$target.before-fleet-enrollment"
|
|
47
|
+
fi
|
|
48
|
+
group=root
|
|
49
|
+
[ "$(uname -s)" != Darwin ] || group=wheel
|
|
50
|
+
sudo -n install -o root -g "$group" -m 0440 -- "$file" "$target"
|
|
51
|
+
sudo -n true
|