@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,90 @@
|
|
|
1
|
+
name: cliproxy-fleet
|
|
2
|
+
services:
|
|
3
|
+
proxy:
|
|
4
|
+
image: ${CLI_PROXY_IMAGE:?image must be pinned}
|
|
5
|
+
restart: unless-stopped
|
|
6
|
+
user: '${CLIPROXY_UID:-1000}:${CLIPROXY_GID:-1000}'
|
|
7
|
+
working_dir: /state
|
|
8
|
+
command: ['/CLIProxyAPI/CLIProxyAPI', '-config', '/state/config.yaml']
|
|
9
|
+
environment:
|
|
10
|
+
HOME: /state
|
|
11
|
+
TZ: UTC
|
|
12
|
+
MANAGEMENT_STATIC_PATH: /state/static
|
|
13
|
+
ports:
|
|
14
|
+
- '127.0.0.1:8317:8317'
|
|
15
|
+
volumes:
|
|
16
|
+
- ./state/runtime:/state
|
|
17
|
+
- ./scripts/container-health:/health:ro
|
|
18
|
+
read_only: true
|
|
19
|
+
tmpfs:
|
|
20
|
+
- /tmp:rw,noexec,nosuid,size=64m,mode=1777
|
|
21
|
+
cap_drop: [ALL]
|
|
22
|
+
security_opt: [no-new-privileges:true]
|
|
23
|
+
mem_limit: 2g
|
|
24
|
+
cpus: 2
|
|
25
|
+
pids_limit: 256
|
|
26
|
+
stop_grace_period: 45s
|
|
27
|
+
healthcheck:
|
|
28
|
+
test: ['CMD', '/health']
|
|
29
|
+
interval: 30s
|
|
30
|
+
timeout: 5s
|
|
31
|
+
retries: 3
|
|
32
|
+
start_period: 30s
|
|
33
|
+
logging:
|
|
34
|
+
driver: local
|
|
35
|
+
options:
|
|
36
|
+
max-size: '10m'
|
|
37
|
+
max-file: '3'
|
|
38
|
+
tailnet:
|
|
39
|
+
profiles: [portable-endpoint]
|
|
40
|
+
user: '${CLIPROXY_UID:-1000}:${CLIPROXY_GID:-1000}'
|
|
41
|
+
image: ${TAILSCALE_IMAGE:?image must be pinned}
|
|
42
|
+
restart: unless-stopped
|
|
43
|
+
entrypoint: ['/usr/local/bin/tailscaled']
|
|
44
|
+
command:
|
|
45
|
+
[
|
|
46
|
+
'--tun=userspace-networking',
|
|
47
|
+
'--state=/var/lib/tailscale/tailscaled.state',
|
|
48
|
+
'--socket=/tmp/tailscaled.sock',
|
|
49
|
+
]
|
|
50
|
+
volumes:
|
|
51
|
+
- ./state/tailscale:/var/lib/tailscale
|
|
52
|
+
cap_drop: [ALL]
|
|
53
|
+
security_opt: [no-new-privileges:true]
|
|
54
|
+
mem_limit: 256m
|
|
55
|
+
cpus: 0.5
|
|
56
|
+
pids_limit: 128
|
|
57
|
+
logging:
|
|
58
|
+
driver: local
|
|
59
|
+
options:
|
|
60
|
+
max-size: '5m'
|
|
61
|
+
max-file: '2'
|
|
62
|
+
cloudflared:
|
|
63
|
+
profiles: [public-endpoint]
|
|
64
|
+
image: ${CLOUDFLARED_IMAGE:?image must be pinned}
|
|
65
|
+
user: '${CLIPROXY_UID:-1000}:${CLIPROXY_GID:-1000}'
|
|
66
|
+
restart: unless-stopped
|
|
67
|
+
command:
|
|
68
|
+
[
|
|
69
|
+
'tunnel',
|
|
70
|
+
'--no-autoupdate',
|
|
71
|
+
'run',
|
|
72
|
+
'--token-file',
|
|
73
|
+
'/run/secrets/tunnel-token',
|
|
74
|
+
]
|
|
75
|
+
volumes:
|
|
76
|
+
- ./state/secrets/tunnel-token:/run/secrets/tunnel-token:ro
|
|
77
|
+
depends_on:
|
|
78
|
+
proxy:
|
|
79
|
+
condition: service_healthy
|
|
80
|
+
read_only: true
|
|
81
|
+
cap_drop: [ALL]
|
|
82
|
+
security_opt: [no-new-privileges:true]
|
|
83
|
+
mem_limit: 256m
|
|
84
|
+
cpus: 0.5
|
|
85
|
+
pids_limit: 128
|
|
86
|
+
logging:
|
|
87
|
+
driver: local
|
|
88
|
+
options:
|
|
89
|
+
max-size: '5m'
|
|
90
|
+
max-file: '2'
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
config-version: 8
|
|
2
|
+
server:
|
|
3
|
+
host: '0.0.0.0'
|
|
4
|
+
port: 8317
|
|
5
|
+
max-request-body-bytes: 33554432
|
|
6
|
+
trusted-proxies: []
|
|
7
|
+
discovery:
|
|
8
|
+
enabled: false
|
|
9
|
+
management:
|
|
10
|
+
allow-remote: true
|
|
11
|
+
secret-key: REPLACE_MANAGEMENT_KEY
|
|
12
|
+
disable-control-panel: false
|
|
13
|
+
disable-auto-update-panel: true
|
|
14
|
+
panel-github-repository: https://github.com/router-for-me/Cli-Proxy-API-Management-Center
|
|
15
|
+
access:
|
|
16
|
+
api-keys:
|
|
17
|
+
- REPLACE_CLIENT_KEY
|
|
18
|
+
oauth:
|
|
19
|
+
auth-dir: /state/auths
|
|
20
|
+
# Fleet-owned (applied by `ops declared`): the Antigravity CLI sends its
|
|
21
|
+
# catalog ids; serve them from the Antigravity account's matching models.
|
|
22
|
+
model-alias:
|
|
23
|
+
antigravity:
|
|
24
|
+
- name: gemini-3.8-flash-high
|
|
25
|
+
alias: gemini-3.8-flash
|
|
26
|
+
fork: true
|
|
27
|
+
- name: gemini-3.1-flash-lite
|
|
28
|
+
alias: gemini-3.1-flash-lite-preview
|
|
29
|
+
fork: true
|
|
30
|
+
routing:
|
|
31
|
+
# Fill first: use one account until its quota is exhausted (it then cools down), then the next.
|
|
32
|
+
# Sessions stay bound to their account while it is available; new sessions start on the first
|
|
33
|
+
# available account, so usage is not spread across accounts.
|
|
34
|
+
strategy: fill-first
|
|
35
|
+
session-affinity: true
|
|
36
|
+
retry:
|
|
37
|
+
request-retry: 2
|
|
38
|
+
# 0 tries every eligible credential, so exhausting several accounts still reaches the next one.
|
|
39
|
+
max-retry-credentials: 0
|
|
40
|
+
max-retry-interval: 30
|
|
41
|
+
cooldown:
|
|
42
|
+
disable-cooling: false
|
|
43
|
+
save-cooldown-status: true
|
|
44
|
+
upstream:
|
|
45
|
+
claude:
|
|
46
|
+
# Fleet-owned (applied by `ops declared`): the proxy presents itself to
|
|
47
|
+
# Anthropic as this Claude Code version, and Anthropic refuses newer models
|
|
48
|
+
# to an older one. fleet-sync replaces the version with the `claude` pin in
|
|
49
|
+
# mise.toml on every run, so it follows the harness every node runs.
|
|
50
|
+
header-defaults:
|
|
51
|
+
user-agent: 'claude-cli/2.1.314 (external, cli)'
|
|
52
|
+
# T3 runs Claude Code under its own entrypoint label.
|
|
53
|
+
native-entrypoints: ['t3-code']
|
|
54
|
+
# Legacy compatibility field intentionally retained: never switch model capability/cost.
|
|
55
|
+
quota-exceeded:
|
|
56
|
+
switch-project: true
|
|
57
|
+
switch-preview-model: false
|
|
58
|
+
observability:
|
|
59
|
+
logs:
|
|
60
|
+
debug: false
|
|
61
|
+
logging-to-file: false
|
|
62
|
+
request-log: false
|
|
63
|
+
logs-max-total-size-mb: 50
|
|
64
|
+
error-logs-max-files: 2
|
|
65
|
+
usage:
|
|
66
|
+
usage-statistics-enabled: true
|
|
67
|
+
pprof:
|
|
68
|
+
enable: false
|
|
69
|
+
plugins:
|
|
70
|
+
enabled: false
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Cloud ownership and initial adoption
|
|
2
|
+
|
|
3
|
+
The initial workstation setup created runtime/cloud state before this package existed. Its operational code is now maintained here; installed copies are generated from committed blobs. The one-off Tailscale API provisioning and Vault secret-writing helpers are retired and removed during first package installation. They must not return as parallel infrastructure controllers.
|
|
4
|
+
|
|
5
|
+
Existing external resources are retained:
|
|
6
|
+
|
|
7
|
+
- Tailscale Service `svc:ai-proxy`, endpoint `tcp:443`, stable MagicDNS origin `https://ai-proxy.tail0a1b2c.ts.net`.
|
|
8
|
+
- Dedicated host tag `tag:ai-proxy` and optional client tag `tag:ai-client`, with owner/service approval and private access rules already in the tailnet. The original workstation identity/SSH/service route was preserved.
|
|
9
|
+
- Existing production R2 bucket and prefix `cliproxy-fleet/restic`; bucket and credentials already belong to the OpenTofu platform.
|
|
10
|
+
- Existing Vault `secret/personal/prd` fields `CLIPROXY_FLEET_RESTIC_PASSWORD`, `CLIPROXY_FLEET_CLIENT_API_KEY`, `CLIPROXY_FLEET_MANAGEMENT_KEY`, and operator-added `TAILSCALE_API_TOKEN`.
|
|
11
|
+
|
|
12
|
+
The new Vault field names still require reconciliation into `packages/infra/tofu/modules/inventory/inventory.tf.json` and the **complete** OpenTofu `vault_inputs` map before a later Vault apply. Preserve their existing values and every unrelated field. Use the established `bun run vault:fix`/`vault:edit` adoption workflow and a reviewed saved plan; never seed secrets directly from this package. The Tailscale admin token is no longer consumed by the runtime package and is not mounted into containers.
|
|
13
|
+
|
|
14
|
+
Before changing existing Tailscale cloud resources, adopt their identities and full policy through the platform's OpenTofu workflow. Do not replace the tailnet policy wholesale, recreate the Service/TailVIP, retag the workstation, or delete existing grants. Local `tailscale up` authorization and `serve` advertisement of an already defined resource are runtime operations; policy, tag ownership and Service definitions are infrastructure.
|
|
15
|
+
|
|
16
|
+
These are outstanding cloud-IaC reconciliation items, not hidden local scripts. Provider/authentication state, source-manifest metadata and the dedicated node identity are encrypted in R2 backups; recovery credentials are independently in Vault. Do not put live state, secrets, provider tokens or infrastructure plans into Git.
|
|
Binary file
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1120" viewBox="0 0 1440 1120" role="img" aria-labelledby="title description">
|
|
2
|
+
<title id="title">CLIProxyAPI deployment and integrations</title>
|
|
3
|
+
<desc id="description">Clients reach Cloudflare HTTPS with API keys. An outbound tunnel connects to cloudflared and CLIProxyAPI on the workstation. CLIProxyAPI calls the upstream AI provider. GitHub supplies tracked runtime code and OpenTofu cloud configuration. Vault supplies credentials; Restic sends encrypted backups to R2. A private Tailscale endpoint also reaches the proxy.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="#667897"/></marker>
|
|
6
|
+
<marker id="blue-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/></marker>
|
|
7
|
+
<style>
|
|
8
|
+
text { font-family: Inter, Arial, sans-serif; fill: #15243b; }
|
|
9
|
+
.heading { font-size: 32px; font-weight: 700; }
|
|
10
|
+
.subtitle { font-size: 16px; fill: #53637b; }
|
|
11
|
+
.label { font-size: 18px; font-weight: 700; }
|
|
12
|
+
.detail { font-size: 15px; fill: #53637b; }
|
|
13
|
+
.edge-label { font-size: 14px; fill: #53637b; }
|
|
14
|
+
.box { fill: white; stroke: #ccd7e6; stroke-width: 1.5; }
|
|
15
|
+
.traffic { fill: none; stroke: #2563eb; stroke-width: 3; marker-end: url(#blue-arrow); }
|
|
16
|
+
.control { fill: none; stroke: #667897; stroke-width: 2; stroke-dasharray: 7 6; marker-end: url(#arrow); }
|
|
17
|
+
.storage { fill: none; stroke: #667897; stroke-width: 2; marker-end: url(#arrow); }
|
|
18
|
+
</style>
|
|
19
|
+
</defs>
|
|
20
|
+
<rect width="1440" height="1120" rx="24" fill="#f5f8fc"/>
|
|
21
|
+
<text x="54" y="65" class="heading">CLIProxyAPI · deployment & integrations</text>
|
|
22
|
+
<text x="54" y="96" class="subtitle">Stable public HTTPS · API-key authentication · GitHub-owned configuration</text>
|
|
23
|
+
|
|
24
|
+
<rect x="54" y="130" width="1332" height="104" rx="16" class="box"/>
|
|
25
|
+
<text x="80" y="164" class="label">YOUR CLIENTS</text>
|
|
26
|
+
<text x="80" y="196" class="detail">Codex CLI · Claude Code · OpenCode · other API clients</text>
|
|
27
|
+
<text x="900" y="164" class="label">MANAGEMENT DASHBOARD</text>
|
|
28
|
+
<text x="900" y="196" class="detail">Browser + separate management key</text>
|
|
29
|
+
|
|
30
|
+
<path d="M 720 234 V 290" class="traffic"/>
|
|
31
|
+
<text x="742" y="269" class="edge-label">HTTPS + client API key / management key</text>
|
|
32
|
+
<rect x="395" y="290" width="650" height="110" rx="16" fill="#eaf1ff" stroke="#9dbaf5" stroke-width="1.5"/>
|
|
33
|
+
<text x="720" y="328" text-anchor="middle" class="label">cli-proxy.example.com</text>
|
|
34
|
+
<text x="720" y="359" text-anchor="middle" class="detail">Cloudflare DNS + HTTPS + managed Tunnel</text>
|
|
35
|
+
<text x="720" y="383" text-anchor="middle" class="detail">API: /v1 · Dashboard: /management.html</text>
|
|
36
|
+
|
|
37
|
+
<rect x="330" y="462" width="770" height="392" rx="20" fill="#edf5ff" stroke="#9dbaf5" stroke-width="1.5"/>
|
|
38
|
+
<text x="356" y="499" class="label">THIS WORKSTATION</text>
|
|
39
|
+
<text x="356" y="523" class="detail">systemd + Docker Compose · pinned Git code</text>
|
|
40
|
+
|
|
41
|
+
<path d="M 720 400 V 560" class="traffic"/>
|
|
42
|
+
<rect x="550" y="419" width="340" height="27" rx="6" fill="#f5f8fc"/>
|
|
43
|
+
<text x="720" y="438" text-anchor="middle" class="edge-label">Encrypted tunnel · initiated outbound by host</text>
|
|
44
|
+
<rect x="550" y="560" width="330" height="82" rx="13" class="box"/>
|
|
45
|
+
<text x="715" y="592" text-anchor="middle" class="label">cloudflared container</text>
|
|
46
|
+
<text x="715" y="619" text-anchor="middle" class="detail">Private token file · no added host port</text>
|
|
47
|
+
<path d="M 715 642 V 686" class="traffic"/>
|
|
48
|
+
<text x="735" y="669" class="edge-label">Docker network</text>
|
|
49
|
+
<rect x="550" y="686" width="330" height="85" rx="13" fill="#2563eb"/>
|
|
50
|
+
<text x="715" y="720" text-anchor="middle" style="fill:white;font-size:21px;font-weight:700">CLIProxyAPI</text>
|
|
51
|
+
<text x="715" y="748" text-anchor="middle" style="fill:#dbeafe;font-size:15px">Port 8317 · authentication + API routing</text>
|
|
52
|
+
<rect x="355" y="686" width="161" height="85" rx="13" class="box"/>
|
|
53
|
+
<text x="435" y="720" text-anchor="middle" class="label">Working cache</text>
|
|
54
|
+
<text x="435" y="746" text-anchor="middle" class="detail">OAuth + config</text>
|
|
55
|
+
<path d="M 550 728 H 516" class="storage" marker-start="url(#arrow)"/>
|
|
56
|
+
<text x="356" y="818" class="detail">Health monitoring · Vault renewal · cloud checkpoint every 15 minutes</text>
|
|
57
|
+
|
|
58
|
+
<rect x="1140" y="686" width="246" height="104" rx="15" class="box"/>
|
|
59
|
+
<text x="1263" y="719" text-anchor="middle" class="label">AI provider</text>
|
|
60
|
+
<text x="1263" y="746" text-anchor="middle" class="detail">Currently: Codex OAuth</text>
|
|
61
|
+
<text x="1263" y="771" text-anchor="middle" class="detail">Streaming + tool calls</text>
|
|
62
|
+
<path d="M 880 729 H 1140" class="traffic" marker-start="url(#blue-arrow)"/>
|
|
63
|
+
<text x="1008" y="714" text-anchor="middle" class="edge-label">Provider authentication</text>
|
|
64
|
+
|
|
65
|
+
<rect x="54" y="540" width="235" height="110" rx="15" class="box"/>
|
|
66
|
+
<text x="171" y="574" text-anchor="middle" class="label">Vault</text>
|
|
67
|
+
<text x="171" y="601" text-anchor="middle" class="detail">Tunnel, API + backup keys</text>
|
|
68
|
+
<text x="171" y="626" text-anchor="middle" class="detail">Current S3 credentials</text>
|
|
69
|
+
<path d="M 289 580 H 550" class="control"/>
|
|
70
|
+
<rect x="345" y="557" width="170" height="24" rx="5" fill="#edf5ff"/>
|
|
71
|
+
<text x="430" y="574" text-anchor="middle" class="edge-label">Token on startup</text>
|
|
72
|
+
|
|
73
|
+
<rect x="1140" y="540" width="246" height="110" rx="15" class="box"/>
|
|
74
|
+
<text x="1263" y="574" text-anchor="middle" class="label">Tailscale endpoint</text>
|
|
75
|
+
<text x="1263" y="601" text-anchor="middle" class="detail">Existing private alternative</text>
|
|
76
|
+
<text x="1263" y="626" text-anchor="middle" class="detail">ai-proxy.tail0a1b2c.ts.net</text>
|
|
77
|
+
<path d="M 1140 600 H 1048 V 704 H 880" class="storage"/>
|
|
78
|
+
|
|
79
|
+
<rect x="54" y="903" width="330" height="112" rx="15" class="box"/>
|
|
80
|
+
<text x="219" y="938" text-anchor="middle" class="label">GitHub · crvouga/dev-fleet</text>
|
|
81
|
+
<text x="219" y="967" text-anchor="middle" class="detail">Tracked runtime code + OpenTofu</text>
|
|
82
|
+
<text x="219" y="992" text-anchor="middle" class="detail">Reviewed main-branch CI deployment</text>
|
|
83
|
+
<path d="M 384 935 H 470 V 854" class="control"/>
|
|
84
|
+
<text x="488" y="900" class="edge-label">Pinned code install</text>
|
|
85
|
+
<path d="M 105 903 H 35 V 345 H 395" class="control"/>
|
|
86
|
+
<text x="122" y="330" class="edge-label">OpenTofu owns DNS + tunnel</text>
|
|
87
|
+
<path d="M 219 903 V 650" class="control"/>
|
|
88
|
+
<rect x="129" y="828" width="179" height="43" rx="6" fill="#f5f8fc"/>
|
|
89
|
+
<text x="219" y="847" text-anchor="middle" class="edge-label">OpenTofu owns</text>
|
|
90
|
+
<text x="219" y="865" text-anchor="middle" class="edge-label">credential delivery</text>
|
|
91
|
+
|
|
92
|
+
<rect x="680" y="903" width="220" height="112" rx="15" class="box"/>
|
|
93
|
+
<text x="790" y="938" text-anchor="middle" class="label">Restic</text>
|
|
94
|
+
<text x="790" y="967" text-anchor="middle" class="detail">15-minute checkpoints</text>
|
|
95
|
+
<text x="790" y="992" text-anchor="middle" class="detail">Runtime state + revision</text>
|
|
96
|
+
<path d="M 790 854 V 903" class="storage"/>
|
|
97
|
+
<rect x="1140" y="903" width="246" height="112" rx="15" class="box"/>
|
|
98
|
+
<text x="1263" y="938" text-anchor="middle" class="label">Cloudflare R2</text>
|
|
99
|
+
<text x="1263" y="967" text-anchor="middle" class="detail">Durable recovery source</text>
|
|
100
|
+
<text x="1263" y="992" text-anchor="middle" class="detail">cliproxy-fleet/restic</text>
|
|
101
|
+
<path d="M 900 959 H 1140" class="storage"/>
|
|
102
|
+
<text x="1020" y="941" text-anchor="middle" class="edge-label">S3-compatible API</text>
|
|
103
|
+
|
|
104
|
+
<path d="M 55 1065 H 107" class="traffic"/>
|
|
105
|
+
<text x="120" y="1070" class="detail">Inference traffic · responses return along the same path</text>
|
|
106
|
+
<path d="M 710 1065 H 762" class="control"/>
|
|
107
|
+
<text x="775" y="1070" class="detail">Configuration / credential delivery</text>
|
|
108
|
+
<text x="54" y="1100" class="detail">The public URL stays stable when the primary host moves. This workstation must remain online to serve requests.</text>
|
|
109
|
+
</svg>
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
name: CLI proxy health
|
|
2
|
+
# One workflow, five entry points, deliberately cheap by default:
|
|
3
|
+
# every 15 min readiness: free (no inference) + cloud checkpoint freshness
|
|
4
|
+
# hourly light: 2 tiny requests per model (about 200 requests a day)
|
|
5
|
+
# 03:41 nightly comprehensive suite (bounded by models.json budgets)
|
|
6
|
+
# pull request contract: offline, no credentials, no inference
|
|
7
|
+
# manual any profile, one model or one check, optional force-probe
|
|
8
|
+
on:
|
|
9
|
+
schedule:
|
|
10
|
+
- cron: '7,22,37,52 * * * *'
|
|
11
|
+
- cron: '17 * * * *'
|
|
12
|
+
- cron: '41 3 * * *'
|
|
13
|
+
pull_request:
|
|
14
|
+
paths:
|
|
15
|
+
- cliproxy/**
|
|
16
|
+
- .github/workflows/cliproxy-health.yml
|
|
17
|
+
workflow_dispatch:
|
|
18
|
+
inputs:
|
|
19
|
+
profile:
|
|
20
|
+
description: readiness is free; light is the hourly check; nightly is comprehensive; manual adds wrong-key probes and fault simulations.
|
|
21
|
+
type: choice
|
|
22
|
+
default: light
|
|
23
|
+
options: [liveness, readiness, light, nightly, manual]
|
|
24
|
+
suite:
|
|
25
|
+
description: Which declared checks to run.
|
|
26
|
+
type: choice
|
|
27
|
+
default: all
|
|
28
|
+
options: [all, core, cursor]
|
|
29
|
+
model:
|
|
30
|
+
description: Optional single model id from cliproxy/models.json (partial runs never open or close the tracking issue).
|
|
31
|
+
type: string
|
|
32
|
+
default: ''
|
|
33
|
+
check:
|
|
34
|
+
description: Optional capability glob such as chat.tools or responses.* (cheap reproduction of one finding).
|
|
35
|
+
type: string
|
|
36
|
+
default: ''
|
|
37
|
+
max_requests:
|
|
38
|
+
description: Optional lower request budget for this run.
|
|
39
|
+
type: string
|
|
40
|
+
default: ''
|
|
41
|
+
force_probe:
|
|
42
|
+
description: Probe models even if they were rate limited recently (normally paused to protect quota).
|
|
43
|
+
type: boolean
|
|
44
|
+
default: false
|
|
45
|
+
permissions:
|
|
46
|
+
contents: read
|
|
47
|
+
concurrency:
|
|
48
|
+
group: cliproxy-model-health-${{ github.event_name == 'pull_request' && format('pr-{0}', github.ref) || github.event_name == 'workflow_dispatch' && inputs.profile || github.event.schedule }}
|
|
49
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
50
|
+
env:
|
|
51
|
+
PROFILE: ${{ github.event_name == 'workflow_dispatch' && inputs.profile || github.event.schedule == '41 3 * * *' && 'nightly' || github.event.schedule == '17 * * * *' && 'light' || 'readiness' }}
|
|
52
|
+
jobs:
|
|
53
|
+
contract:
|
|
54
|
+
# Pull requests: deterministic protocol, routing and report contracts. No credentials, no inference.
|
|
55
|
+
if: github.event_name == 'pull_request'
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
timeout-minutes: 10
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
60
|
+
- name: Install PyYAML for the validate-config contract
|
|
61
|
+
run: |
|
|
62
|
+
sudo apt-get update
|
|
63
|
+
sudo apt-get install -y --no-install-recommends python3-yaml
|
|
64
|
+
- name: Lint models.json and run the offline contract tests
|
|
65
|
+
run: |
|
|
66
|
+
python3 -B cliproxy/scripts/check.py
|
|
67
|
+
python3 -B -m unittest discover -s cliproxy/tests -p "test_model_*.py"
|
|
68
|
+
- name: Run the full declarative suite against the local emulator
|
|
69
|
+
run: |
|
|
70
|
+
set +e
|
|
71
|
+
python3 -B cliproxy/scripts/model_health.py model-suite --target mock --profile manual \
|
|
72
|
+
--fail-on degraded --output model-health-report --seed 1 > /dev/null
|
|
73
|
+
code=$?
|
|
74
|
+
if [ -f model-health-report/model-health.md ]; then cat model-health-report/model-health.md >> "$GITHUB_STEP_SUMMARY"; fi
|
|
75
|
+
exit $code
|
|
76
|
+
- name: Keep structured report
|
|
77
|
+
if: ${{ always() && hashFiles('model-health-report/model-health.json') != '' }}
|
|
78
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
79
|
+
with:
|
|
80
|
+
name: model-health-contract-${{ github.run_id }}
|
|
81
|
+
path: model-health-report
|
|
82
|
+
retention-days: 14
|
|
83
|
+
health:
|
|
84
|
+
if: github.event_name != 'pull_request' && github.repository == 'crvouga/dev-fleet' && github.ref == 'refs/heads/main'
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
timeout-minutes: 60
|
|
87
|
+
permissions:
|
|
88
|
+
contents: read
|
|
89
|
+
id-token: write
|
|
90
|
+
issues: write
|
|
91
|
+
steps:
|
|
92
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
93
|
+
- name: Install read-only backup client
|
|
94
|
+
if: env.PROFILE == 'readiness'
|
|
95
|
+
run: |
|
|
96
|
+
sudo apt-get update
|
|
97
|
+
sudo apt-get install -y --no-install-recommends restic
|
|
98
|
+
- name: Read-only Vault login using GitHub OIDC
|
|
99
|
+
uses: hashicorp/vault-action@892a26828f195e65540a40b4768ae4571f51ebfc # v4.0.0
|
|
100
|
+
with:
|
|
101
|
+
url: https://vault.example.com
|
|
102
|
+
method: jwt
|
|
103
|
+
path: jwt
|
|
104
|
+
role: github-actions
|
|
105
|
+
jwtGithubAudience: https://vault.example.com
|
|
106
|
+
exportToken: true
|
|
107
|
+
- name: Verify endpoint and cloud recovery freshness
|
|
108
|
+
if: env.PROFILE == 'readiness'
|
|
109
|
+
run: python3 -B cliproxy/scripts/external-health.py
|
|
110
|
+
- name: Restore last-success and rate-limit state
|
|
111
|
+
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
112
|
+
with:
|
|
113
|
+
path: model-health-state.json
|
|
114
|
+
key: cliproxy-model-health-state-${{ github.run_id }}
|
|
115
|
+
restore-keys: cliproxy-model-health-state-
|
|
116
|
+
- name: Validate every required model
|
|
117
|
+
# Bounded spend: the profile's request/token budget, concurrency and timeouts come from models.json;
|
|
118
|
+
# inference is never retried; a model rate limited recently is not probed again until its pause ends.
|
|
119
|
+
# The client key is read in-process from Vault and never printed.
|
|
120
|
+
if: ${{ !cancelled() }}
|
|
121
|
+
env:
|
|
122
|
+
MODEL: ${{ inputs.model }}
|
|
123
|
+
CHECK: ${{ inputs.check }}
|
|
124
|
+
MAX_REQUESTS: ${{ inputs.max_requests }}
|
|
125
|
+
SUITE: ${{ inputs.suite || 'all' }}
|
|
126
|
+
FORCE_PROBE: ${{ inputs.force_probe }}
|
|
127
|
+
run: |
|
|
128
|
+
command=model-health
|
|
129
|
+
fail_on=unhealthy
|
|
130
|
+
case "$PROFILE" in nightly | manual) command=model-suite; fail_on=degraded ;; esac
|
|
131
|
+
args=(--profile "$PROFILE" --suite "$SUITE" --fail-on "$fail_on"
|
|
132
|
+
--output model-health-report --state model-health-state.json)
|
|
133
|
+
if [ -n "$MODEL" ]; then args+=(--model "$MODEL"); fi
|
|
134
|
+
if [ -n "$CHECK" ]; then args+=(--check "$CHECK"); fi
|
|
135
|
+
if [ -n "$MAX_REQUESTS" ]; then args+=(--max-requests "$MAX_REQUESTS"); fi
|
|
136
|
+
if [ "$FORCE_PROBE" = "true" ]; then args+=(--force-probe); fi
|
|
137
|
+
set +e
|
|
138
|
+
python3 -B cliproxy/scripts/model_health.py "$command" "${args[@]}" > /dev/null
|
|
139
|
+
code=$?
|
|
140
|
+
if [ -f model-health-report/agent-brief.md ]; then cat model-health-report/agent-brief.md >> "$GITHUB_STEP_SUMMARY"; fi
|
|
141
|
+
if [ -f model-health-report/model-health.md ]; then cat model-health-report/model-health.md >> "$GITHUB_STEP_SUMMARY"; fi
|
|
142
|
+
exit $code
|
|
143
|
+
- name: Save last-success and rate-limit state
|
|
144
|
+
if: ${{ always() && hashFiles('model-health-state.json') != '' }}
|
|
145
|
+
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
146
|
+
with:
|
|
147
|
+
path: model-health-state.json
|
|
148
|
+
key: cliproxy-model-health-state-${{ github.run_id }}
|
|
149
|
+
- name: Keep structured report
|
|
150
|
+
if: ${{ always() && hashFiles('model-health-report/model-health.json') != '' }}
|
|
151
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
152
|
+
with:
|
|
153
|
+
name: model-health-${{ env.PROFILE }}-${{ github.run_id }}
|
|
154
|
+
path: model-health-report
|
|
155
|
+
retention-days: 30
|
|
156
|
+
- name: Track findings in one issue per scope
|
|
157
|
+
# Opens or updates a single tracking issue when the findings change, and closes it when a full
|
|
158
|
+
# run is healthy. The body is the redacted agent brief: structure and numbers, no prompts or keys.
|
|
159
|
+
if: ${{ always() && hashFiles('model-health-report/model-health.json') != '' && inputs.model == '' && inputs.check == '' }}
|
|
160
|
+
env:
|
|
161
|
+
GH_TOKEN: ${{ github.token }}
|
|
162
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
163
|
+
run: |
|
|
164
|
+
scope=live
|
|
165
|
+
case "$PROFILE" in nightly | manual) scope=nightly ;; esac
|
|
166
|
+
title="[ops] CLI proxy model health ($scope)"
|
|
167
|
+
report=model-health-report/model-health.json
|
|
168
|
+
status=$(jq -r .summary.status "$report")
|
|
169
|
+
fingerprint=$(jq -r .summary.findings.fingerprint "$report")
|
|
170
|
+
number=$(gh issue list --state open --search "\"$title\" in:title" --json number,title \
|
|
171
|
+
--jq "map(select(.title==\"$title\"))[0].number // empty")
|
|
172
|
+
if [ "$status" = unhealthy ] || [ "$status" = degraded ]; then
|
|
173
|
+
{
|
|
174
|
+
head -c 58000 model-health-report/agent-brief.md
|
|
175
|
+
printf '\n\nRun: %s (artifact model-health-%s-%s)\n<!-- fingerprint: %s -->\n' "$RUN_URL" "$PROFILE" "$GITHUB_RUN_ID" "$fingerprint"
|
|
176
|
+
} > model-health-report/issue.md
|
|
177
|
+
if [ -z "$number" ]; then
|
|
178
|
+
gh issue create --title "$title" --body-file model-health-report/issue.md
|
|
179
|
+
elif ! gh issue view "$number" --json body --jq .body | grep -q "fingerprint: $fingerprint"; then
|
|
180
|
+
gh issue edit "$number" --body-file model-health-report/issue.md
|
|
181
|
+
gh issue comment "$number" --body "Findings changed: $RUN_URL"
|
|
182
|
+
fi
|
|
183
|
+
elif [ "$status" = healthy ] && [ -n "$number" ]; then
|
|
184
|
+
gh issue close "$number" --comment "Healthy again: $RUN_URL"
|
|
185
|
+
fi
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Restore and migration
|
|
2
|
+
|
|
3
|
+
Exactly one proxy instance runs, on the **manager** (`manager.id` in `fleet.json`). Workers are clients and never host it. Never activate duplicated OAuth or Tailscale identities concurrently. Source code comes from this package at a reviewed Git revision; secrets and state come from Vault and encrypted R2 snapshots.
|
|
4
|
+
|
|
5
|
+
`INSTALL_ROOT` below is `~/Library/Application Support/cliproxy-fleet` on the manager and `/opt/cliproxy-fleet` on the legacy Linux host. Run every `devfleet cliproxy …` command from a repository checkout on the host named in the step.
|
|
6
|
+
|
|
7
|
+
## Move the proxy from a Linux worker to the manager
|
|
8
|
+
|
|
9
|
+
Today the proxy still runs on the worker `worker-one` (legacy systemd, `/opt/cliproxy-fleet`). This one-time, operator-approved move fences that host and recovers its latest checkpoint on the manager `manager-mac`. The acknowledgement flags below are operator fencing, not a distributed lock. Plan a short outage: workers have no proxy between the source stop and the manager health check.
|
|
10
|
+
|
|
11
|
+
### 1. Prepare the manager (no outage)
|
|
12
|
+
|
|
13
|
+
On the manager, logged in, on AC power, with Docker Desktop running (enable **Start Docker Desktop when you sign in**):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cliproxy/scripts/install-dependencies
|
|
17
|
+
devfleet cliproxy install --prepare-only --revision HEAD
|
|
18
|
+
devfleet cliproxy vault-login
|
|
19
|
+
devfleet cliproxy recover
|
|
20
|
+
devfleet cliproxy snapshots
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`install-dependencies` builds the pinned tools directory (mise Python + hash-locked PyYAML, mise `restic`) that the LaunchAgents run with; it never uses sudo or Homebrew. `--prepare-only` creates the worktree and private directories without starting anything. `recover` reads the existing Restic password from Vault and confirms it; it never generates or writes cloud secrets. Do not run `initialize` before recovery.
|
|
24
|
+
|
|
25
|
+
### 2. Fence the source (outage starts)
|
|
26
|
+
|
|
27
|
+
On `worker-one`, stop scheduling, drain the dedicated Tailscale Service advertisement, stop and disable the stack, then take the final consistent checkpoint:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
sudo systemctl disable --now cliproxy-backup.timer cliproxy-backup-check.timer cliproxy-gc.timer
|
|
31
|
+
devfleet cliproxy drain
|
|
32
|
+
devfleet cliproxy stop
|
|
33
|
+
sudo systemctl disable cliproxy-fleet.service
|
|
34
|
+
devfleet cliproxy backup
|
|
35
|
+
devfleet cliproxy snapshots
|
|
36
|
+
sudo systemctl disable --now cliproxy-monitor.timer cliproxy-vault-renew.timer
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
An already stopped stack remains stopped after backup. Record the **latest successful snapshot ID** from `/opt/cliproxy-fleet/state/status/backup-success.json`; do not choose an incomplete snapshot from a failed backup. Confirm the fence before continuing:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
systemctl is-active cliproxy-fleet.service # inactive
|
|
43
|
+
systemctl is-enabled cliproxy-fleet.service # disabled
|
|
44
|
+
python3 -B cliproxy/scripts/fleet_sync.py --definition fleet.json \
|
|
45
|
+
--node worker-one --revision "$(git rev-parse HEAD)" # PROXY_DECOMMISSION_PENDING, not PROXY_DUPLICATE_OWNER
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Keep the source powered off or fenced until the move is verified or rolled back.
|
|
49
|
+
|
|
50
|
+
### 3. Recover and start on the manager
|
|
51
|
+
|
|
52
|
+
On the manager:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
devfleet cliproxy restore-cache --previous-node-stopped
|
|
56
|
+
devfleet cliproxy install --revision HEAD
|
|
57
|
+
devfleet cliproxy audit
|
|
58
|
+
devfleet cliproxy health
|
|
59
|
+
devfleet cliproxy activate
|
|
60
|
+
devfleet cliproxy public-endpoint verify
|
|
61
|
+
python3 -B cliproxy/tests/live.py --endpoint https://cli-proxy.example.com --codex
|
|
62
|
+
devfleet cliproxy backup
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`restore-cache` downloads the latest modern checkpoint, validates it and populates the empty cache without starting services. `install` (never sudo on macOS) regenerates secret caches from Vault, records the user's UID/GID for Compose, links and loads the `dev.devfleet.cliproxy-*` LaunchAgents in `gui/$UID`, and starts the recovered stack; it refuses missing runtime config and stops with `DOCKER_DESKTOP_NOT_RUNNING` if Docker Desktop is down. `audit` must show clean provenance and loaded agents. `activate` resumes the existing `svc:ai-proxy` advertisement from the restored dedicated identity. A restored `state/public-endpoint-enabled` marker starts the public connector automatically; otherwise run `devfleet cliproxy public-endpoint enable` first.
|
|
66
|
+
|
|
67
|
+
Then confirm the fleet view and clients:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python3 -B cliproxy/scripts/fleet_sync.py --definition fleet.json \
|
|
71
|
+
--node manager-mac --revision "$(git rev-parse HEAD)" # ok: true, role manager, backend launchd
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
From each worker, run the client bootstrap's `--validate` and an actual client/tool request through `https://cli-proxy.example.com` and `https://ai-proxy.tail0a1b2c.ts.net`. Remove temporary plaintext restore copies after verification.
|
|
75
|
+
|
|
76
|
+
### 4. Decommission the source
|
|
77
|
+
|
|
78
|
+
After the manager has run cleanly through several scheduled backups, retire the old host's installation. It contains private provider/Tailscale state, so remove it rather than leaving it enabled:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
sudo docker compose --project-directory /opt/cliproxy-fleet \
|
|
82
|
+
--profile portable-endpoint --profile public-endpoint down
|
|
83
|
+
sudo rm /etc/systemd/system/cliproxy-*.service /etc/systemd/system/cliproxy-*.timer
|
|
84
|
+
sudo systemctl daemon-reload
|
|
85
|
+
sudo rm -rf /opt/cliproxy-fleet
|
|
86
|
+
git worktree prune
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`fleet_sync.py --node worker-one` then reports `PROXY_CLIENT_ROLE`. Fleet-sync never performs these steps itself.
|
|
90
|
+
|
|
91
|
+
### Rollback a failed move
|
|
92
|
+
|
|
93
|
+
1. On the manager, stop and unload it, keeping any newer state:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
devfleet cliproxy drain
|
|
97
|
+
devfleet cliproxy stop
|
|
98
|
+
devfleet cliproxy backup # only if the manager refreshed credentials or changed config
|
|
99
|
+
for label in fleet monitor vault-renew backup backup-check gc; do
|
|
100
|
+
launchctl bootout "gui/$(id -u)/dev.devfleet.cliproxy-$label"
|
|
101
|
+
launchctl disable "gui/$(id -u)/dev.devfleet.cliproxy-$label"
|
|
102
|
+
done
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Move `INSTALL_ROOT/state/runtime`, `state/tailscale` and `state/endpoint.json` into a private directory under `INSTALL_ROOT/recovery/`. With the runtime cache gone, `fleet-sync` reports `PROXY_RECOVERY_REQUIRED` and can never restart the manager copy.
|
|
106
|
+
|
|
107
|
+
2. On the source, if the manager took a newer backup, move the source's `state/runtime`, `state/tailscale` and `state/endpoint.json` aside the same way and run `devfleet cliproxy restore-cache --previous-node-stopped`; otherwise keep the original source state (old OAuth refresh tokens may be invalid after the manager refreshed them).
|
|
108
|
+
3. Restart the source with `sudo systemctl enable cliproxy-fleet.service`, `devfleet cliproxy install --revision REV` (the revision it ran before), `devfleet cliproxy activate`, then re-enable its timers with `sudo systemctl enable --now cliproxy-monitor.timer cliproxy-vault-renew.timer cliproxy-backup.timer cliproxy-backup-check.timer cliproxy-gc.timer`.
|
|
109
|
+
4. Verify the stable endpoints from fleet clients. Until the move is retried, fleet-sync reports `PROXY_DUPLICATE_OWNER` on the source; that is expected and is never auto-remediated.
|
|
110
|
+
|
|
111
|
+
## One-command offline recovery
|
|
112
|
+
|
|
113
|
+
On a host with the tracked package installed and a valid private Vault token, `devfleet cliproxy recover-node NEW_DIRECTORY` builds a complete new cache using fresh Vault secrets and a verified R2 checkpoint. Supply `--vault-token-file FILE` to use an explicit existing login. It refuses existing destinations and never changes or stops the running stack. This is also a safe recovery rehearsal while the manager remains online: the new identity files are never activated. Remove rehearsal caches after inspection, since they contain private credentials.
|
|
114
|
+
|
|
115
|
+
On a replacement manager without installed tooling, invoke `python3 -B cliproxy/scripts/recover_node.py "$HOME/Library/Application Support/cliproxy-fleet" --vault-token-file FILE` from a full Git clone after `install-dependencies` passes. The destination must not yet exist. After explicitly fencing the old host, `devfleet cliproxy install --revision REV` installs tracked code and activates the recovered cache. This route replaces the separate prepare/login/password/restore steps above when an existing Vault token is already available. It does not obtain a new machine identity or bypass old-node fencing.
|
|
116
|
+
|
|
117
|
+
## Recovery details
|
|
118
|
+
|
|
119
|
+
Recovery uses the current trusted Git checkout, validates decrypted data, and preserves the new Vault login and independently recovered Restic password. It never executes backed-up code. To reproduce the original software version, read the recovered checkpoint's `INSTALLATION.json` in an offline restore first and choose that reviewed Git revision instead of HEAD.
|
|
120
|
+
|
|
121
|
+
`restore-cache` intentionally refuses an existing `runtime`, `tailscale` or endpoint cache. An existing repository pin must match the restored destination. For recovery on the same host, stop the service/agents and move these cache entries to a private rollback directory first. Never move `state/status/operation.lock` while an operation is active. An interrupted recovery leaves `state/recovery-incomplete`; inspect/remove the partial cache before retrying. Keep `state/secrets` (new Vault login/password) and the lock directory intact.
|
|
122
|
+
|
|
123
|
+
For offline inspection or historical recovery, use `devfleet cliproxy restore SNAPSHOT_ID EMPTY_DIRECTORY`, then `python3 -B INSTALL_ROOT/scripts/restore-verify EMPTY_DIRECTORY`. Modern checkpoints are located automatically despite different source paths. Old snapshots without `CHECKPOINT.json` require the original explicit restored deployment path and manual state recovery. No automatic fallback to an older snapshot occurs when validation fails.
|
|
124
|
+
|
|
125
|
+
The existing Tailscale Service remains in the cloud. Reuse the restored dedicated identity only while the original is stopped. If a drained identity requires re-advertising, use the pinned Tailscale CLI's `serve advertise` command for `svc:ai-proxy`, then inspect `serve status`. Do not change the manager's ordinary Tailscale identity or assume hostnames transfer. A new tagged identity/service definition requires the platform's OpenTofu-managed enrollment/approval workflow; no cloud provisioning helper exists in this package.
|
|
126
|
+
|
|
127
|
+
A normal code/image rollback uses `devfleet cliproxy install --rollback`. That restores a recorded Git revision, not application data. For incompatible state migrations, restore a matching pre-upgrade snapshot while stopped.
|
|
128
|
+
|
|
129
|
+
## Public hostname cutover
|
|
130
|
+
|
|
131
|
+
The public DNS/tunnel resources remain owned by OpenTofu. A restored `state/public-endpoint-enabled` marker makes service startup fetch the current isolated tunnel credential from Vault and start the connector automatically. The tunnel token itself is excluded from snapshots. If the marker is absent, run `devfleet cliproxy public-endpoint enable` after restoring and stopping the old host. Verify with `devfleet cliproxy public-endpoint verify` and `tests/live.py --endpoint https://cli-proxy.example.com --codex`. Client API and management keys do not change; keep the old host stopped during recovery.
|