@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,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
- name: Apply reviewed Ubuntu development-server policy
|
|
3
|
+
hosts: localhost
|
|
4
|
+
connection: local
|
|
5
|
+
become: true
|
|
6
|
+
gather_facts: true
|
|
7
|
+
vars_files:
|
|
8
|
+
- policy.json
|
|
9
|
+
pre_tasks:
|
|
10
|
+
- name: Require the supported common release and an explicit non-root user
|
|
11
|
+
ansible.builtin.assert:
|
|
12
|
+
that:
|
|
13
|
+
- ansible_facts.distribution == 'Ubuntu'
|
|
14
|
+
- ansible_facts.distribution_version == release
|
|
15
|
+
- ansible_facts.architecture in ['x86_64', 'aarch64']
|
|
16
|
+
- development_user is match('^[a-z_][a-z0-9_-]{0,31}$')
|
|
17
|
+
- development_user != 'root'
|
|
18
|
+
fail_msg: Unsupported platform or user; no distribution upgrade is permitted.
|
|
19
|
+
- name: Read the designated existing account
|
|
20
|
+
ansible.builtin.getent:
|
|
21
|
+
database: passwd
|
|
22
|
+
key: '{{ development_user }}'
|
|
23
|
+
- name: Refuse a different user identity or home
|
|
24
|
+
ansible.builtin.assert:
|
|
25
|
+
that:
|
|
26
|
+
- ansible_facts.getent_passwd[development_user][1] | int == development_uid | int
|
|
27
|
+
- ansible_facts.getent_passwd[development_user][4] == development_home
|
|
28
|
+
# Memory and disk safety must land even when the disk is nearly full.
|
|
29
|
+
- ansible.builtin.import_tasks: tasks/resources.yml
|
|
30
|
+
- name: Inspect capacity before package and service changes
|
|
31
|
+
ansible.builtin.command:
|
|
32
|
+
argv:
|
|
33
|
+
- /usr/bin/python3
|
|
34
|
+
- '-c'
|
|
35
|
+
- |
|
|
36
|
+
import json, os
|
|
37
|
+
print(json.dumps([{"path": p, "freeMiB": (v.f_bavail * v.f_frsize) // (1024 * 1024), "freeInodes": v.f_favail} for p in ['/', '/var', os.environ['FLEET_HOME']] for v in [os.statvfs(p)]]))
|
|
38
|
+
environment:
|
|
39
|
+
FLEET_HOME: '{{ development_home }}'
|
|
40
|
+
changed_when: false
|
|
41
|
+
register: capacity
|
|
42
|
+
- name: Preserve management capacity instead of installing into an exhausted disk
|
|
43
|
+
ansible.builtin.assert:
|
|
44
|
+
that:
|
|
45
|
+
- item.freeMiB >= minimumFreeDiskMiB
|
|
46
|
+
- item.freeInodes >= minimumFreeInodes
|
|
47
|
+
fail_msg: RESOURCE_CAPACITY_REQUIRED; inspect disk space and inodes on the reported filesystem. Preserve developer work; no automatic cleanup or process killing.
|
|
48
|
+
loop: '{{ capacity.stdout | from_json }}'
|
|
49
|
+
- name: Inspect installed packages
|
|
50
|
+
ansible.builtin.package_facts:
|
|
51
|
+
manager: apt
|
|
52
|
+
- name: Preserve Docker installations owned by a different distribution
|
|
53
|
+
ansible.builtin.assert:
|
|
54
|
+
that: "'docker-ce' not in ansible_facts.packages and 'containerd.io' not in ansible_facts.packages"
|
|
55
|
+
fail_msg: DOCKER_OWNER_CONFLICT; migrate the existing Docker owner explicitly before applying the Ubuntu-native baseline.
|
|
56
|
+
- name: Inspect existing service ownership
|
|
57
|
+
ansible.builtin.service_facts:
|
|
58
|
+
- name: Wait for legitimate package transactions before inspecting dpkg
|
|
59
|
+
ansible.builtin.command:
|
|
60
|
+
argv:
|
|
61
|
+
- /usr/bin/python3
|
|
62
|
+
- '-c'
|
|
63
|
+
- |
|
|
64
|
+
import fcntl
|
|
65
|
+
files = [open(path, 'a') for path in ['/var/lib/dpkg/lock-frontend', '/var/lib/dpkg/lock']]
|
|
66
|
+
for handle in files:
|
|
67
|
+
fcntl.lockf(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
68
|
+
register: apt_idle
|
|
69
|
+
changed_when: false
|
|
70
|
+
retries: 24
|
|
71
|
+
delay: 5
|
|
72
|
+
until: apt_idle.rc == 0
|
|
73
|
+
- name: Inspect package database consistency
|
|
74
|
+
ansible.builtin.command: dpkg --audit
|
|
75
|
+
changed_when: false
|
|
76
|
+
register: dpkg_audit
|
|
77
|
+
- name: Require package database recovery before modifying packages
|
|
78
|
+
ansible.builtin.assert:
|
|
79
|
+
that: dpkg_audit.stdout | trim == ''
|
|
80
|
+
fail_msg: APT_STATE_BROKEN; inspect dpkg --audit and use reviewed package recovery. Never remove package lock files.
|
|
81
|
+
tasks:
|
|
82
|
+
- ansible.builtin.import_tasks: tasks/packages.yml
|
|
83
|
+
- ansible.builtin.import_tasks: tasks/docker.yml
|
|
84
|
+
- ansible.builtin.import_tasks: tasks/services.yml
|
|
85
|
+
- ansible.builtin.import_tasks: tasks/power.yml
|
|
86
|
+
- ansible.builtin.import_tasks: tasks/user.yml
|
|
87
|
+
- ansible.builtin.import_tasks: tasks/ssh.yml
|
|
88
|
+
- ansible.builtin.import_tasks: tasks/credentials.yml
|
|
89
|
+
when: development_source is defined
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""Stop the user process group that is filling the disk before it runs out.
|
|
3
|
+
|
|
4
|
+
Managed by personal fleet-sync (tasks/resources.yml) as
|
|
5
|
+
personal-fleet-disk-guard.service. Every interval it measures free space on the
|
|
6
|
+
watched filesystems and the bytes each leaf cgroup under user.slice wrote. When
|
|
7
|
+
any watched filesystem is below the floor, it kills only the single cgroup that
|
|
8
|
+
wrote the most during the last interval, and only if that is at least the write
|
|
9
|
+
threshold. It never deletes files, never touches system services and never
|
|
10
|
+
kills idle work. Free space must be recovered by a person.
|
|
11
|
+
|
|
12
|
+
Usage: disk-guard.py --path / --path /home/me [--floor-mib N] [--write-mib N]
|
|
13
|
+
[--interval S] [--cgroup-root DIR] [--dry-run]
|
|
14
|
+
"""
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import time
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
MIB = 1024 * 1024
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def leaf_writes(root):
|
|
26
|
+
"""Bytes written so far by every leaf cgroup below root, from io.stat."""
|
|
27
|
+
writes = {}
|
|
28
|
+
for current, dirs, files in os.walk(root):
|
|
29
|
+
if dirs or 'io.stat' not in files:
|
|
30
|
+
continue
|
|
31
|
+
try:
|
|
32
|
+
text = Path(current, 'io.stat').read_text()
|
|
33
|
+
except OSError:
|
|
34
|
+
continue
|
|
35
|
+
total = 0
|
|
36
|
+
for line in text.splitlines():
|
|
37
|
+
for field in line.split()[1:]:
|
|
38
|
+
key, _, value = field.partition('=')
|
|
39
|
+
if key == 'wbytes' and value.isdigit():
|
|
40
|
+
total += int(value)
|
|
41
|
+
writes[current] = total
|
|
42
|
+
return writes
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def choose_victim(previous, current, low, threshold):
|
|
46
|
+
"""The leaf that wrote the most since the previous sample, if it is a runaway."""
|
|
47
|
+
if not low:
|
|
48
|
+
return None
|
|
49
|
+
best, best_delta = None, 0
|
|
50
|
+
for path, total in current.items():
|
|
51
|
+
delta = total - previous.get(path, total)
|
|
52
|
+
if delta > best_delta:
|
|
53
|
+
best, best_delta = path, delta
|
|
54
|
+
if best is None or best_delta < threshold:
|
|
55
|
+
return None
|
|
56
|
+
return best, best_delta
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def free_bytes(path):
|
|
60
|
+
st = os.statvfs(path)
|
|
61
|
+
return st.f_bavail * st.f_frsize
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def log(**event):
|
|
65
|
+
print(json.dumps(event, sort_keys=True), flush=True)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def main(argv):
|
|
69
|
+
parser = argparse.ArgumentParser()
|
|
70
|
+
parser.add_argument('--path', action='append', required=True)
|
|
71
|
+
parser.add_argument('--floor-mib', type=int, default=3072)
|
|
72
|
+
parser.add_argument('--write-mib', type=int, default=32)
|
|
73
|
+
parser.add_argument('--interval', type=float, default=5.0)
|
|
74
|
+
parser.add_argument('--cgroup-root', default='/sys/fs/cgroup/user.slice')
|
|
75
|
+
parser.add_argument('--dry-run', action='store_true')
|
|
76
|
+
args = parser.parse_args(argv)
|
|
77
|
+
floor, threshold = args.floor_mib * MIB, args.write_mib * MIB
|
|
78
|
+
log(event='started', paths=args.path, floor_mib=args.floor_mib,
|
|
79
|
+
write_mib=args.write_mib, interval=args.interval, dry_run=args.dry_run)
|
|
80
|
+
previous = leaf_writes(args.cgroup_root)
|
|
81
|
+
while True:
|
|
82
|
+
time.sleep(args.interval)
|
|
83
|
+
try:
|
|
84
|
+
current = leaf_writes(args.cgroup_root)
|
|
85
|
+
free = {path: free_bytes(path) for path in args.path}
|
|
86
|
+
low = [path for path, value in free.items() if value < floor]
|
|
87
|
+
victim = choose_victim(previous, current, bool(low), threshold)
|
|
88
|
+
previous = current
|
|
89
|
+
if victim is None:
|
|
90
|
+
continue
|
|
91
|
+
path, delta = victim
|
|
92
|
+
event = dict(event='runaway_writer', cgroup=path,
|
|
93
|
+
wrote_mib=delta // MIB, low=low,
|
|
94
|
+
free_mib={p: v // MIB for p, v in free.items()})
|
|
95
|
+
if not args.dry_run:
|
|
96
|
+
Path(path, 'cgroup.kill').write_text('1\n')
|
|
97
|
+
event['killed'] = True
|
|
98
|
+
log(**event)
|
|
99
|
+
except OSError as exc:
|
|
100
|
+
log(event='check_failed', error=type(exc).__name__)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == '__main__':
|
|
104
|
+
main(sys.argv[1:])
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"release": "26.04",
|
|
4
|
+
"architectures": ["amd64", "arm64"],
|
|
5
|
+
"snapshot": "20261101T000000Z",
|
|
6
|
+
"packages": {
|
|
7
|
+
"sudo": "1.9.17p2-1ubuntu3.2",
|
|
8
|
+
"openssh-server": "1:10.2p1-2ubuntu3.8",
|
|
9
|
+
"dconf-cli": "0.49.0-4",
|
|
10
|
+
"docker.io": "29.1.3-0ubuntu4.1",
|
|
11
|
+
"docker-compose-v2": "2.40.3+ds1-0ubuntu1",
|
|
12
|
+
"docker-buildx": "0.30.1-0ubuntu1",
|
|
13
|
+
"build-essential": "12.12ubuntu2.26.04.2",
|
|
14
|
+
"ca-certificates": "20260816ubuntu0.26.04.2",
|
|
15
|
+
"curl": "8.18.0-1ubuntu2.8",
|
|
16
|
+
"git": "1:2.53.0-1ubuntu1.1",
|
|
17
|
+
"python3": "3.14.3-0ubuntu2",
|
|
18
|
+
"unzip": "6.0-29ubuntu1",
|
|
19
|
+
"xz-utils": "5.8.3-1ubuntu0.1"
|
|
20
|
+
},
|
|
21
|
+
"aptLockTimeoutSeconds": 120,
|
|
22
|
+
"minimumFreeDiskMiB": 4096,
|
|
23
|
+
"minimumFreeInodes": 10000,
|
|
24
|
+
"serviceStartLimitBurst": 5,
|
|
25
|
+
"serviceStartLimitIntervalSeconds": 300,
|
|
26
|
+
"alwaysOnServices": ["ssh", "tailscaled", "docker", "beszel-agent"],
|
|
27
|
+
"resourceSafety": {
|
|
28
|
+
"swapFileMiB": 8192,
|
|
29
|
+
"userMemoryMax": "90%",
|
|
30
|
+
"agentMemoryMax": "30%",
|
|
31
|
+
"agentSwapMax": "2G",
|
|
32
|
+
"journalMaxUse": "1G",
|
|
33
|
+
"journalKeepFree": "4G",
|
|
34
|
+
"tmpMaxSize": "25%",
|
|
35
|
+
"tmpAge": "2d",
|
|
36
|
+
"varTmpAge": "14d",
|
|
37
|
+
"diskGuardFloorMiB": 3072,
|
|
38
|
+
"diskGuardWriteMiB": 32,
|
|
39
|
+
"diskGuardIntervalSeconds": 5,
|
|
40
|
+
"dockerLogMaxSize": "50m",
|
|
41
|
+
"dockerLogMaxFiles": "3"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
- name: Enable recovery for an already verified T3 desktop app
|
|
3
|
+
hosts: localhost
|
|
4
|
+
connection: local
|
|
5
|
+
become: true
|
|
6
|
+
gather_facts: false
|
|
7
|
+
tasks:
|
|
8
|
+
- name: Verify the desktop unit runs before adding any recovery trigger
|
|
9
|
+
ansible.builtin.command:
|
|
10
|
+
argv: [systemctl, --user, is-active, t3code-desktop.service]
|
|
11
|
+
become_user: '{{ development_user }}'
|
|
12
|
+
environment:
|
|
13
|
+
XDG_RUNTIME_DIR: '/run/user/{{ development_uid }}'
|
|
14
|
+
changed_when: false
|
|
15
|
+
- ansible.builtin.import_tasks: tasks/recovery.yml
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The worker's only T3 process: the pinned desktop app, which embeds the server
|
|
3
|
+
# on 127.0.0.1:3773. GDM logs the designated user in automatically and the user
|
|
4
|
+
# unit starts the app with the graphical session, so the UI is on screen and the
|
|
5
|
+
# server is up after every boot. Starting or migrating is left to fleet-sync.
|
|
6
|
+
#
|
|
7
|
+
# The upstream .deb declares libxss1, which Ubuntu 26.04 no longer ships and the
|
|
8
|
+
# binary does not link. Instead of patching the package or leaving apt with an
|
|
9
|
+
# unmet dependency, the verified package payload is unpacked to its own path and
|
|
10
|
+
# its real runtime libraries and bundled AppArmor profile are installed.
|
|
11
|
+
- name: Install the pinned T3 Code desktop app and start it with the session
|
|
12
|
+
hosts: localhost
|
|
13
|
+
connection: local
|
|
14
|
+
become: true
|
|
15
|
+
gather_facts: false
|
|
16
|
+
vars:
|
|
17
|
+
t3_desktop_dir: /opt/T3 Code (Nightly)
|
|
18
|
+
# The package's Depends without libxss1 (see above).
|
|
19
|
+
t3_desktop_libraries:
|
|
20
|
+
- libasound2t64
|
|
21
|
+
- libatspi2.0-0t64
|
|
22
|
+
- libgbm1
|
|
23
|
+
- libgtk-3-0t64
|
|
24
|
+
- libnotify4
|
|
25
|
+
- libnss3
|
|
26
|
+
- libsecret-1-0
|
|
27
|
+
- libuuid1
|
|
28
|
+
- libxtst6
|
|
29
|
+
- xdg-utils
|
|
30
|
+
tasks:
|
|
31
|
+
- name: Create the package cache
|
|
32
|
+
ansible.builtin.file:
|
|
33
|
+
path: /var/cache/personal-fleet
|
|
34
|
+
state: directory
|
|
35
|
+
owner: root
|
|
36
|
+
group: root
|
|
37
|
+
mode: '0755'
|
|
38
|
+
- name: Download the pinned desktop package
|
|
39
|
+
ansible.builtin.get_url:
|
|
40
|
+
url: '{{ t3_desktop_url }}'
|
|
41
|
+
dest: '/var/cache/personal-fleet/{{ t3_desktop_name }}'
|
|
42
|
+
checksum: 'sha256:{{ t3_desktop_sha256 }}'
|
|
43
|
+
mode: '0644'
|
|
44
|
+
timeout: 300
|
|
45
|
+
- name: Install the desktop runtime libraries
|
|
46
|
+
ansible.builtin.apt:
|
|
47
|
+
name: '{{ t3_desktop_libraries }}'
|
|
48
|
+
state: present
|
|
49
|
+
lock_timeout: 120
|
|
50
|
+
- name: Read the unpacked app version
|
|
51
|
+
ansible.builtin.command:
|
|
52
|
+
argv: [cat, '{{ t3_desktop_dir }}/.personal-fleet-version']
|
|
53
|
+
register: t3_desktop_installed
|
|
54
|
+
changed_when: false
|
|
55
|
+
failed_when: false
|
|
56
|
+
- name: Unpack the pinned app when its version differs
|
|
57
|
+
when: t3_desktop_installed.stdout | default('') != t3_desktop_name
|
|
58
|
+
block:
|
|
59
|
+
- name: Clear the staging directory
|
|
60
|
+
ansible.builtin.file:
|
|
61
|
+
path: /opt/.t3code-staging
|
|
62
|
+
state: absent
|
|
63
|
+
- name: Unpack the verified package payload
|
|
64
|
+
ansible.builtin.command:
|
|
65
|
+
argv:
|
|
66
|
+
- dpkg-deb
|
|
67
|
+
- -x
|
|
68
|
+
- '/var/cache/personal-fleet/{{ t3_desktop_name }}'
|
|
69
|
+
- /opt/.t3code-staging
|
|
70
|
+
changed_when: true
|
|
71
|
+
- name: Record the unpacked package
|
|
72
|
+
ansible.builtin.copy:
|
|
73
|
+
dest: '/opt/.t3code-staging{{ t3_desktop_dir }}/.personal-fleet-version'
|
|
74
|
+
content: '{{ t3_desktop_name }}'
|
|
75
|
+
mode: '0644'
|
|
76
|
+
- name: Remove the copy kept from the previous update
|
|
77
|
+
ansible.builtin.file:
|
|
78
|
+
path: '{{ t3_desktop_dir }}.previous'
|
|
79
|
+
state: absent
|
|
80
|
+
- name: Inspect the current app directory
|
|
81
|
+
ansible.builtin.stat:
|
|
82
|
+
path: '{{ t3_desktop_dir }}'
|
|
83
|
+
register: t3_desktop_current
|
|
84
|
+
- name: Keep the current app until the next update (a running app keeps its files)
|
|
85
|
+
ansible.builtin.command:
|
|
86
|
+
argv:
|
|
87
|
+
[mv, -T, '{{ t3_desktop_dir }}', '{{ t3_desktop_dir }}.previous']
|
|
88
|
+
when: t3_desktop_current.stat.exists
|
|
89
|
+
changed_when: true
|
|
90
|
+
- name: Move the pinned app into place
|
|
91
|
+
ansible.builtin.command:
|
|
92
|
+
argv:
|
|
93
|
+
- mv
|
|
94
|
+
- -T
|
|
95
|
+
- '/opt/.t3code-staging{{ t3_desktop_dir }}'
|
|
96
|
+
- '{{ t3_desktop_dir }}'
|
|
97
|
+
changed_when: true
|
|
98
|
+
- name: Add the launcher entry and icon
|
|
99
|
+
ansible.builtin.copy:
|
|
100
|
+
src: '/opt/.t3code-staging/usr/share/{{ item }}'
|
|
101
|
+
dest: '/usr/share/{{ item }}'
|
|
102
|
+
remote_src: true
|
|
103
|
+
mode: '0644'
|
|
104
|
+
loop:
|
|
105
|
+
- applications/t3code.desktop
|
|
106
|
+
- icons/hicolor/512x512/apps/t3code.png
|
|
107
|
+
- name: Remove the staging directory
|
|
108
|
+
ansible.builtin.file:
|
|
109
|
+
path: /opt/.t3code-staging
|
|
110
|
+
state: absent
|
|
111
|
+
- name: Install the AppArmor profile the package ships
|
|
112
|
+
ansible.builtin.copy:
|
|
113
|
+
src: '{{ t3_desktop_dir }}/resources/apparmor-profile'
|
|
114
|
+
dest: /etc/apparmor.d/t3code
|
|
115
|
+
remote_src: true
|
|
116
|
+
mode: '0644'
|
|
117
|
+
register: t3_desktop_apparmor
|
|
118
|
+
- name: Load the AppArmor profile so the app sandbox can start
|
|
119
|
+
ansible.builtin.command:
|
|
120
|
+
argv:
|
|
121
|
+
[
|
|
122
|
+
apparmor_parser,
|
|
123
|
+
--replace,
|
|
124
|
+
--write-cache,
|
|
125
|
+
--skip-read-cache,
|
|
126
|
+
/etc/apparmor.d/t3code,
|
|
127
|
+
]
|
|
128
|
+
when: t3_desktop_apparmor.changed
|
|
129
|
+
changed_when: true
|
|
130
|
+
- name: Inspect the GDM configuration
|
|
131
|
+
ansible.builtin.stat:
|
|
132
|
+
path: /etc/gdm3/custom.conf
|
|
133
|
+
register: gdm_config
|
|
134
|
+
- name: Log the designated user in automatically
|
|
135
|
+
ansible.builtin.lineinfile:
|
|
136
|
+
path: /etc/gdm3/custom.conf
|
|
137
|
+
regexp: '^#?\s*{{ item.key }}\s*='
|
|
138
|
+
line: '{{ item.key }} = {{ item.value }}'
|
|
139
|
+
insertafter: '^\[daemon\]'
|
|
140
|
+
loop:
|
|
141
|
+
- { key: AutomaticLoginEnable, value: 'true' }
|
|
142
|
+
- { key: AutomaticLogin, value: '{{ development_user }}' }
|
|
143
|
+
when: gdm_config.stat.exists
|
|
144
|
+
- name: Create the user unit directory
|
|
145
|
+
ansible.builtin.file:
|
|
146
|
+
path: '{{ development_home }}/.config/systemd/user'
|
|
147
|
+
state: directory
|
|
148
|
+
owner: '{{ development_user }}'
|
|
149
|
+
mode: '0700'
|
|
150
|
+
- name: Declare the desktop app as a session service
|
|
151
|
+
ansible.builtin.copy:
|
|
152
|
+
dest: '{{ development_home }}/.config/systemd/user/t3code-desktop.service'
|
|
153
|
+
owner: '{{ development_user }}'
|
|
154
|
+
mode: '0600'
|
|
155
|
+
content: |
|
|
156
|
+
# Managed by personal fleet-sync: ansible/ubuntu/t3-desktop.yml
|
|
157
|
+
[Unit]
|
|
158
|
+
Description=T3 Code desktop (UI and server)
|
|
159
|
+
PartOf=graphical-session.target
|
|
160
|
+
After=graphical-session.target
|
|
161
|
+
StartLimitIntervalSec=300
|
|
162
|
+
StartLimitBurst=5
|
|
163
|
+
[Service]
|
|
164
|
+
Type=simple
|
|
165
|
+
WorkingDirectory=%h
|
|
166
|
+
Environment=T3CODE_HOME=%h/.t3
|
|
167
|
+
# /tmp is RAM with a per-user quota that agent and build caches
|
|
168
|
+
# exhaust; agents use disk-backed /var/tmp (aged by tasks/resources.yml).
|
|
169
|
+
Environment=TMPDIR=/var/tmp
|
|
170
|
+
# Native Wayland hangs before the first window on Asahi (16K pages);
|
|
171
|
+
# XWayland works on every GNOME worker, so use it everywhere.
|
|
172
|
+
ExecStart="{{ t3_desktop_dir }}/t3code" --ozone-platform=x11
|
|
173
|
+
KillMode=mixed
|
|
174
|
+
Restart=always
|
|
175
|
+
RestartSec=5
|
|
176
|
+
CPUWeight=1000
|
|
177
|
+
IOWeight=1000
|
|
178
|
+
MemoryLow=1536M
|
|
179
|
+
OOMScoreAdjust=100
|
|
180
|
+
ManagedOOMPreference=avoid
|
|
181
|
+
[Install]
|
|
182
|
+
WantedBy=graphical-session.target
|
|
183
|
+
# Repoint recovery now: a timer still running an older revision's script
|
|
184
|
+
# would restart the headless server right after its approved migration.
|
|
185
|
+
- ansible.builtin.import_tasks: tasks/recovery.yml
|
|
186
|
+
- name: Enable the desktop app for every graphical login
|
|
187
|
+
become: true
|
|
188
|
+
become_user: '{{ development_user }}'
|
|
189
|
+
environment:
|
|
190
|
+
XDG_RUNTIME_DIR: '/run/user/{{ development_uid }}'
|
|
191
|
+
ansible.builtin.systemd_service:
|
|
192
|
+
scope: user
|
|
193
|
+
name: t3code-desktop.service
|
|
194
|
+
daemon_reload: true
|
|
195
|
+
enabled: true
|
|
196
|
+
# One T3 process per worker: the desktop app. The former headless server
|
|
197
|
+
# (`t3 service install`) is retired; absent on machines that never had it.
|
|
198
|
+
- name: Retire the former headless T3 server
|
|
199
|
+
become: true
|
|
200
|
+
become_user: '{{ development_user }}'
|
|
201
|
+
environment:
|
|
202
|
+
XDG_RUNTIME_DIR: '/run/user/{{ development_uid }}'
|
|
203
|
+
ansible.builtin.systemd_service:
|
|
204
|
+
scope: user
|
|
205
|
+
name: t3code.service
|
|
206
|
+
state: stopped
|
|
207
|
+
enabled: false
|
|
208
|
+
failed_when: false
|
|
209
|
+
# Starting needs the auto-login graphical session, which a machine has only
|
|
210
|
+
# after its first reboot; the running state is measured after this play.
|
|
211
|
+
- name: Start the desktop app without restarting a running one
|
|
212
|
+
become: true
|
|
213
|
+
become_user: '{{ development_user }}'
|
|
214
|
+
environment:
|
|
215
|
+
XDG_RUNTIME_DIR: '/run/user/{{ development_uid }}'
|
|
216
|
+
ansible.builtin.systemd_service:
|
|
217
|
+
scope: user
|
|
218
|
+
name: t3code-desktop.service
|
|
219
|
+
state: started
|
|
220
|
+
failed_when: false
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Keep the worker T3 desktop app (UI and server) up and responsive. Run every minute by
|
|
3
|
+
# personal-worker-recovery.timer (managed by fleet-sync, tasks/recovery.yml).
|
|
4
|
+
# Stopped or failed: start it. Running but its local API has not answered for
|
|
5
|
+
# three checks in a row: restart it, since a hung server serves no turns.
|
|
6
|
+
set -u
|
|
7
|
+
url=http://127.0.0.1:3773/.well-known/t3/environment
|
|
8
|
+
state="${XDG_RUNTIME_DIR:?}/personal-t3-health-failures"
|
|
9
|
+
limit=3
|
|
10
|
+
|
|
11
|
+
# The former headless server still runs until its approved migration; never
|
|
12
|
+
# start a second T3 next to it.
|
|
13
|
+
if systemctl --user is-active --quiet t3code.service; then
|
|
14
|
+
echo "Headless T3 still runs; desktop migration pending."
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
if ! systemctl --user is-active --quiet t3code-desktop.service; then
|
|
18
|
+
rm -f "$state"
|
|
19
|
+
echo "T3 is not running; starting it."
|
|
20
|
+
exec systemctl --user start t3code-desktop.service
|
|
21
|
+
fi
|
|
22
|
+
if curl --fail --silent --max-time 10 --output /dev/null "$url"; then
|
|
23
|
+
rm -f "$state"
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
failures=$(($(cat "$state" 2>/dev/null || echo 0) + 1))
|
|
27
|
+
echo "$failures" > "$state"
|
|
28
|
+
echo "T3 local API did not answer ($failures/$limit)."
|
|
29
|
+
if [ "$failures" -ge "$limit" ]; then
|
|
30
|
+
rm -f "$state"
|
|
31
|
+
echo "T3 is unresponsive; restarting it."
|
|
32
|
+
exec systemctl --user restart t3code-desktop.service
|
|
33
|
+
fi
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
- name: Stage the native T3 policy and make the running app load it
|
|
3
|
+
hosts: localhost
|
|
4
|
+
connection: local
|
|
5
|
+
become: true
|
|
6
|
+
gather_facts: false
|
|
7
|
+
tasks:
|
|
8
|
+
- name: Inspect existing administrator policy
|
|
9
|
+
ansible.builtin.stat:
|
|
10
|
+
path: /etc/t3code/policy.json
|
|
11
|
+
register: policy_file
|
|
12
|
+
- name: Inspect Fleet ownership marker
|
|
13
|
+
ansible.builtin.stat:
|
|
14
|
+
path: /etc/t3code/.personal-fleet-policy
|
|
15
|
+
register: policy_owner
|
|
16
|
+
- name: Refuse to overwrite an unrelated administrator policy
|
|
17
|
+
ansible.builtin.assert:
|
|
18
|
+
that: not policy_file.stat.exists or policy_owner.stat.exists
|
|
19
|
+
fail_msg: T3_POLICY_OWNER_CONFLICT; review and merge the existing administrator policy in Git before adoption.
|
|
20
|
+
- name: Create administrator-owned policy directory
|
|
21
|
+
ansible.builtin.file:
|
|
22
|
+
path: /etc/t3code
|
|
23
|
+
state: directory
|
|
24
|
+
owner: root
|
|
25
|
+
group: root
|
|
26
|
+
mode: '0755'
|
|
27
|
+
- name: Record policy ownership before resumable installation
|
|
28
|
+
ansible.builtin.copy:
|
|
29
|
+
dest: /etc/t3code/.personal-fleet-policy
|
|
30
|
+
content: "ansible/ubuntu/t3/policy.json\n"
|
|
31
|
+
owner: root
|
|
32
|
+
group: root
|
|
33
|
+
mode: '0644'
|
|
34
|
+
- name: Stage validated policy with backup
|
|
35
|
+
ansible.builtin.copy:
|
|
36
|
+
content: "{{ t3_policy_document | default(lookup('ansible.builtin.file', 't3/policy.json') | from_json) | to_nice_json }}"
|
|
37
|
+
validate: '{{ t3_executable | quote }} policy check %s'
|
|
38
|
+
dest: /etc/t3code/policy.json
|
|
39
|
+
owner: root
|
|
40
|
+
group: root
|
|
41
|
+
mode: '0644'
|
|
42
|
+
backup: true
|
|
43
|
+
# T3 reads its policy once, at startup. A running app that has not loaded
|
|
44
|
+
# the staged policy is restarted (this ends its running turns) so every
|
|
45
|
+
# node offers the same providers after every sync.
|
|
46
|
+
- name: Check whether the desktop app is running
|
|
47
|
+
become: true
|
|
48
|
+
become_user: '{{ development_user }}'
|
|
49
|
+
environment:
|
|
50
|
+
XDG_RUNTIME_DIR: '/run/user/{{ development_uid }}'
|
|
51
|
+
ansible.builtin.command:
|
|
52
|
+
argv: [systemctl, --user, is-active, --quiet, t3code-desktop.service]
|
|
53
|
+
register: t3_desktop_active
|
|
54
|
+
changed_when: false
|
|
55
|
+
failed_when: false
|
|
56
|
+
- name: Ask the running T3 which policy it loaded
|
|
57
|
+
become: true
|
|
58
|
+
become_user: '{{ development_user }}'
|
|
59
|
+
ansible.builtin.command:
|
|
60
|
+
argv: ['{{ t3_executable }}', policy, show]
|
|
61
|
+
register: t3_policy_loaded
|
|
62
|
+
changed_when: false
|
|
63
|
+
failed_when: false
|
|
64
|
+
when: t3_desktop_active.rc == 0
|
|
65
|
+
- name: Restart T3 so it loads the staged policy
|
|
66
|
+
become: true
|
|
67
|
+
become_user: '{{ development_user }}'
|
|
68
|
+
environment:
|
|
69
|
+
XDG_RUNTIME_DIR: '/run/user/{{ development_uid }}'
|
|
70
|
+
ansible.builtin.systemd_service:
|
|
71
|
+
scope: user
|
|
72
|
+
name: t3code-desktop.service
|
|
73
|
+
state: restarted
|
|
74
|
+
when:
|
|
75
|
+
- t3_desktop_active.rc == 0
|
|
76
|
+
- t3_policy_loaded.stdout is not search('loaded this policy\.\s*$')
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The worker's T3 is reached over the tailnet only: Tailscale Serve terminates
|
|
3
|
+
# HTTPS on port 443 and proxies to the loopback T3 server. Never Funnel. A
|
|
4
|
+
# port 443 that already has any owner is left alone (reported, never reset).
|
|
5
|
+
- name: Serve the worker T3 privately over the tailnet
|
|
6
|
+
hosts: localhost
|
|
7
|
+
connection: local
|
|
8
|
+
become: true
|
|
9
|
+
gather_facts: false
|
|
10
|
+
tasks:
|
|
11
|
+
- name: Require the declared loopback target
|
|
12
|
+
ansible.builtin.assert:
|
|
13
|
+
that: "t3_route_target is match('^http://127\\.0\\.0\\.1:[0-9]+$')"
|
|
14
|
+
|
|
15
|
+
- name: Read this node's Serve configuration
|
|
16
|
+
ansible.builtin.command:
|
|
17
|
+
argv: [tailscale, serve, status, --json]
|
|
18
|
+
register: t3_serve_status
|
|
19
|
+
changed_when: false
|
|
20
|
+
- name: Publish T3 on tailnet HTTPS port 443
|
|
21
|
+
ansible.builtin.command:
|
|
22
|
+
argv: [tailscale, serve, --bg, --https=443, '{{ t3_route_target }}']
|
|
23
|
+
vars:
|
|
24
|
+
serve: "{{ t3_serve_status.stdout | default('{}', true) | from_json }}"
|
|
25
|
+
when:
|
|
26
|
+
- "'443' not in (serve.TCP | default({}))"
|
|
27
|
+
- "(serve.Web | default({})) | select('search', ':443$') | list | length == 0"
|
|
28
|
+
changed_when: true
|