@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,246 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { CliOptions } from './options';
|
|
3
|
+
import type { CommandOutcome } from './contracts';
|
|
4
|
+
import type { Settings } from './settings';
|
|
5
|
+
import { resolveSettings } from './settings';
|
|
6
|
+
import { executeFleet } from './fleet';
|
|
7
|
+
import { fleetSync, type FleetSyncOptions } from './fleet-sync';
|
|
8
|
+
import { syncNode, type NodeSyncOptions } from './node-sync';
|
|
9
|
+
|
|
10
|
+
type Complete = (outcome: CommandOutcome) => void;
|
|
11
|
+
|
|
12
|
+
interface Registration {
|
|
13
|
+
program: Command;
|
|
14
|
+
defaults: Settings;
|
|
15
|
+
complete: Complete;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function collect(value: string, previous: string[]): string[] {
|
|
19
|
+
return [...previous, value];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function targetOptions(command: Command): Command {
|
|
23
|
+
return command
|
|
24
|
+
.option('--node <id>', 'Select a registered node; repeatable', collect, [])
|
|
25
|
+
.option('--local', 'This manager only; no roster or SSH required')
|
|
26
|
+
.option('--timeout <ms>', 'Per-node deadline, 1000–1800000 milliseconds');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function withSettings(defaults: Settings, options: CliOptions) {
|
|
30
|
+
return resolveSettings({
|
|
31
|
+
...defaults,
|
|
32
|
+
...(options.workspace ? { workspace: options.workspace } : {}),
|
|
33
|
+
...(options.config ? { config: options.config } : {}),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Internal fleet actions are selected by flags; the public surface stays flat. */
|
|
38
|
+
function fleetAction(name: string, options: CliOptions): string {
|
|
39
|
+
if (name === 'fleet-status')
|
|
40
|
+
return options.tailnet || options.workers ? 'health' : 'doctor';
|
|
41
|
+
if (name === 'fleet-repair') return options.list ? 'repairs' : 'repair';
|
|
42
|
+
if (name === 'fleet-nodes')
|
|
43
|
+
return options.onboard ? 'onboard' : options.schema ? 'schema' : 'nodes';
|
|
44
|
+
if (name === 'fleet-node-add') return 'add';
|
|
45
|
+
return 'discover';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function register(
|
|
49
|
+
{ program, defaults, complete }: Registration,
|
|
50
|
+
name: string,
|
|
51
|
+
description: string
|
|
52
|
+
): Command {
|
|
53
|
+
return program
|
|
54
|
+
.command(name)
|
|
55
|
+
.description(description)
|
|
56
|
+
.action(async (_options, command: Command) => {
|
|
57
|
+
const options = command.optsWithGlobals<CliOptions>();
|
|
58
|
+
const settings = await withSettings(defaults, options);
|
|
59
|
+
complete(
|
|
60
|
+
await executeFleet(
|
|
61
|
+
fleetAction(name, options),
|
|
62
|
+
{ ...options, ...(options.workers ? { managed: true } : {}) },
|
|
63
|
+
settings
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function registerStatus(registration: Registration) {
|
|
70
|
+
targetOptions(
|
|
71
|
+
register(
|
|
72
|
+
registration,
|
|
73
|
+
'fleet-status',
|
|
74
|
+
'Check this computer and registered nodes without changing them'
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
.option('--all', 'Explicit spelling of the default roster scope')
|
|
78
|
+
.option(
|
|
79
|
+
'--tailnet',
|
|
80
|
+
'Check every visible Tailscale computer instead of the roster'
|
|
81
|
+
)
|
|
82
|
+
.option(
|
|
83
|
+
'--workers',
|
|
84
|
+
'Audit the canonical worker fleet against the published reference'
|
|
85
|
+
)
|
|
86
|
+
.option(
|
|
87
|
+
'--require <capabilities>',
|
|
88
|
+
'Manager capabilities, comma separated, or none'
|
|
89
|
+
)
|
|
90
|
+
.option('--strict', 'Warnings and skipped checks also produce exit 1')
|
|
91
|
+
.addHelpText(
|
|
92
|
+
'after',
|
|
93
|
+
'\nRead-only. --local, --node and --all select roster scope; --tailnet and --workers check Tailscale computers and never take a roster scope.'
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function registerRepair(registration: Registration) {
|
|
98
|
+
targetOptions(
|
|
99
|
+
register(
|
|
100
|
+
registration,
|
|
101
|
+
'fleet-repair',
|
|
102
|
+
'Preview owner-managed repairs; --apply --yes executes them'
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
.option('--list', 'Print the static repair and login catalog instead')
|
|
106
|
+
.option('--all', 'Every visible Tailscale computer (default)')
|
|
107
|
+
.option(
|
|
108
|
+
'--require <capabilities>',
|
|
109
|
+
'Local only: manager capabilities, comma separated, or none'
|
|
110
|
+
)
|
|
111
|
+
.option(
|
|
112
|
+
'--repair <ids>',
|
|
113
|
+
'workspace,workstation,providers; comma separated'
|
|
114
|
+
)
|
|
115
|
+
.option(
|
|
116
|
+
'--plan-hash <sha256>',
|
|
117
|
+
'Require the reviewed plan to match current diagnostics'
|
|
118
|
+
)
|
|
119
|
+
.option(
|
|
120
|
+
'--script',
|
|
121
|
+
'Generate a reviewable POSIX script without executing it'
|
|
122
|
+
)
|
|
123
|
+
.option('--strict', 'Warnings and skipped checks also produce exit 1')
|
|
124
|
+
.option('--apply', 'Authorize the displayed repair plan; requires --yes')
|
|
125
|
+
.option('--yes', 'Confirm an explicitly requested --apply operation')
|
|
126
|
+
.addHelpText(
|
|
127
|
+
'after',
|
|
128
|
+
'\nExample: devfleet fleet-repair --local --repair workspace --plan-hash REVIEWED_SHA256 --apply --yes --json'
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function registerNodes(registration: Registration) {
|
|
133
|
+
register(
|
|
134
|
+
registration,
|
|
135
|
+
'fleet-nodes',
|
|
136
|
+
'List registered developer computers; --onboard and --schema add guidance'
|
|
137
|
+
)
|
|
138
|
+
.option('--node <id>', 'Select a registered node; repeatable', collect, [])
|
|
139
|
+
.option(
|
|
140
|
+
'--onboard',
|
|
141
|
+
'Per-node SSH, VS Code and authentication instructions'
|
|
142
|
+
)
|
|
143
|
+
.option('--schema', 'Print the versioned roster JSON schema');
|
|
144
|
+
register(
|
|
145
|
+
registration,
|
|
146
|
+
'fleet-node-add',
|
|
147
|
+
'Register an existing computer in the local roster; writes need --apply --yes'
|
|
148
|
+
)
|
|
149
|
+
.requiredOption('--id <name>', 'Unique lowercase node ID')
|
|
150
|
+
.requiredOption(
|
|
151
|
+
'--ssh <alias>',
|
|
152
|
+
'Existing OpenSSH alias; never writes SSH configuration'
|
|
153
|
+
)
|
|
154
|
+
.requiredOption('--path <path>', 'Absolute POSIX checkout path on the node')
|
|
155
|
+
.option(
|
|
156
|
+
'--runtime <path>',
|
|
157
|
+
'Optional absolute Node/Bun runtime; defaults to node on SSH PATH'
|
|
158
|
+
)
|
|
159
|
+
.option(
|
|
160
|
+
'--require <capabilities>',
|
|
161
|
+
'Required capabilities, comma separated, or none'
|
|
162
|
+
)
|
|
163
|
+
.option('--apply', 'Write the roster; requires --yes')
|
|
164
|
+
.option('--yes', 'Confirm an explicitly requested --apply operation');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function registerSync({ program, defaults, complete }: Registration) {
|
|
168
|
+
program
|
|
169
|
+
.command('fleet-sync')
|
|
170
|
+
.description(
|
|
171
|
+
'Synchronize the enrolled Ubuntu fleet from this manager over verified SSH'
|
|
172
|
+
)
|
|
173
|
+
.option(
|
|
174
|
+
'--revision <sha>',
|
|
175
|
+
'Approve an exact source commit; defaults to published release or current canonical checkout'
|
|
176
|
+
)
|
|
177
|
+
.option(
|
|
178
|
+
'--node <id>',
|
|
179
|
+
'Explicit enrolled remote target; repeatable',
|
|
180
|
+
collect,
|
|
181
|
+
[]
|
|
182
|
+
)
|
|
183
|
+
.option(
|
|
184
|
+
'--all',
|
|
185
|
+
'Explicit spelling of the default enrolled fleet scope; never discovered peers'
|
|
186
|
+
)
|
|
187
|
+
.option(
|
|
188
|
+
'--dry-run',
|
|
189
|
+
'Read-only plan; no authentication, enrollment, installation or scheduling'
|
|
190
|
+
)
|
|
191
|
+
.option(
|
|
192
|
+
'--apply',
|
|
193
|
+
'Accepted for compatibility; fleet-sync applies by default (schedule writes still need --apply --yes)'
|
|
194
|
+
)
|
|
195
|
+
.option('--yes', 'Accepted for compatibility with --apply')
|
|
196
|
+
.option(
|
|
197
|
+
'--schedule <action>',
|
|
198
|
+
'Manager-only schedule: enable, pause, resume, disable, status'
|
|
199
|
+
)
|
|
200
|
+
.action(async (_options, command: Command) => {
|
|
201
|
+
const options = command.optsWithGlobals<
|
|
202
|
+
FleetSyncOptions & Partial<Settings>
|
|
203
|
+
>();
|
|
204
|
+
complete(
|
|
205
|
+
await fleetSync(options, {
|
|
206
|
+
...defaults,
|
|
207
|
+
...(options.config ? { config: options.config } : {}),
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
program
|
|
212
|
+
.command('fleet-node-sync')
|
|
213
|
+
.description(
|
|
214
|
+
'Reconcile this node’s locked tools, launcher, Codex proxy profile and T3 instances; applies by default'
|
|
215
|
+
)
|
|
216
|
+
.option(
|
|
217
|
+
'--id <name>',
|
|
218
|
+
'Stable node name; defaults to this machine hostname'
|
|
219
|
+
)
|
|
220
|
+
.option('--revision <sha>', 'Reviewed commit; defaults to checkout HEAD')
|
|
221
|
+
.option('--dry-run', 'Preview without changing this node')
|
|
222
|
+
.option('--t3-home <path>', 'T3 base directory')
|
|
223
|
+
.action(async (_options, command: Command) => {
|
|
224
|
+
const options = command.optsWithGlobals<
|
|
225
|
+
NodeSyncOptions & Partial<Settings>
|
|
226
|
+
>();
|
|
227
|
+
complete(await syncNode(options, await withSettings(defaults, options)));
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function registerFleet(
|
|
232
|
+
program: Command,
|
|
233
|
+
defaults: Settings,
|
|
234
|
+
complete: Complete
|
|
235
|
+
): void {
|
|
236
|
+
const registration = { program, defaults, complete };
|
|
237
|
+
registerStatus(registration);
|
|
238
|
+
register(
|
|
239
|
+
registration,
|
|
240
|
+
'fleet-discover',
|
|
241
|
+
'List computers visible through Tailscale, including offline devices'
|
|
242
|
+
);
|
|
243
|
+
registerSync(registration);
|
|
244
|
+
registerRepair(registration);
|
|
245
|
+
registerNodes(registration);
|
|
246
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { SOURCE_OPERATIONS } from './source-operations';
|
|
2
|
+
import type { CommandSpec } from './contracts';
|
|
3
|
+
import { searchCommands } from './search';
|
|
4
|
+
|
|
5
|
+
type Entry = [
|
|
6
|
+
id: string,
|
|
7
|
+
title: string,
|
|
8
|
+
description: string,
|
|
9
|
+
keywords: string[],
|
|
10
|
+
mutation?: boolean,
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function group(name: string, entries: Entry[]): CommandSpec[] {
|
|
14
|
+
return entries.map(([id, title, description, keywords, mutation]) => ({
|
|
15
|
+
id,
|
|
16
|
+
title,
|
|
17
|
+
description,
|
|
18
|
+
group: name,
|
|
19
|
+
keywords,
|
|
20
|
+
args: id.split(' '),
|
|
21
|
+
examples: [`devfleet ${id} --json`],
|
|
22
|
+
mutation: mutation === true,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** One flat, prefixed command surface shared by help, the palette and agents. */
|
|
27
|
+
export const COMMANDS: CommandSpec[] = [
|
|
28
|
+
...group('Fleet', [
|
|
29
|
+
[
|
|
30
|
+
'fleet-status',
|
|
31
|
+
'Check this computer and registered nodes',
|
|
32
|
+
'Read-only SSH, authentication, T3, VS Code and dependency checks for this manager and every roster node. --tailnet covers every visible Tailscale computer; --workers audits the canonical worker fleet.',
|
|
33
|
+
['health', 'doctor', 'ssh', 'auth', 'github', 'vscode', 't3', 'diagnose'],
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
'fleet-status --local',
|
|
37
|
+
'Check this computer only',
|
|
38
|
+
'Diagnose this process context without a roster or remote connections.',
|
|
39
|
+
['local', 'manager', 'health'],
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
'fleet-status --tailnet',
|
|
43
|
+
'Check all Tailscale computers',
|
|
44
|
+
'Discover every visible non-mobile Tailscale computer, then test reachability, pinned tools, configuration and service logins. Offline, unregistered and unverified computers fail coverage.',
|
|
45
|
+
['tailscale', 'tailnet', 'all', 'versions', 'codex', 'claude', 'login'],
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
'fleet-status --workers',
|
|
49
|
+
'Check the canonical worker fleet',
|
|
50
|
+
'Read-only canonical worker readiness, dependencies, services and authentication; lists every remaining gap.',
|
|
51
|
+
['workers', 'readiness', 'health', 'canonical'],
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
'fleet-discover',
|
|
55
|
+
'Discover Tailscale computers',
|
|
56
|
+
'List all computers visible to this Tailscale account, including offline devices. Excludes iOS, Android and tvOS; never writes a roster.',
|
|
57
|
+
['tailscale', 'tailnet', 'computers', 'machines'],
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
'fleet-sync',
|
|
61
|
+
'Synchronize the enrolled Ubuntu fleet',
|
|
62
|
+
'Manager push to all enrolled Ubuntu nodes at one approved revision over verified SSH; interactive confirmation or --apply --yes. Readiness is verified separately.',
|
|
63
|
+
['ubuntu', 'enrollment', 'converge', 'sync'],
|
|
64
|
+
true,
|
|
65
|
+
],
|
|
66
|
+
[
|
|
67
|
+
'fleet-node-sync',
|
|
68
|
+
'Sync this node',
|
|
69
|
+
'Reconcile locked tools, the personal launcher, Codex proxy profile and owned T3 instances on this machine. Applies by default; --dry-run previews.',
|
|
70
|
+
['node', 'sync', 'proxy', 'tools'],
|
|
71
|
+
true,
|
|
72
|
+
],
|
|
73
|
+
[
|
|
74
|
+
'fleet-repair',
|
|
75
|
+
'Repair tools and configuration',
|
|
76
|
+
'Inspect visible Tailscale computers and preview owner-managed repairs. --apply --yes executes eligible tools/config repairs and reruns diagnostics. Logins and host trust remain explicit operator steps.',
|
|
77
|
+
['fix', 'repair', 'sync', 'setup', 'versions', 'config', 'login'],
|
|
78
|
+
true,
|
|
79
|
+
],
|
|
80
|
+
[
|
|
81
|
+
'fleet-repair --list',
|
|
82
|
+
'Explore repairs and login commands',
|
|
83
|
+
'List existing tool owners, automated repairs, native per-machine login commands and shared Vault access guidance. No roster or credentials required.',
|
|
84
|
+
['fix', 'repair', 'login', 'credentials', 'vault', 'catalog'],
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
'fleet-nodes',
|
|
88
|
+
'Explore fleet nodes',
|
|
89
|
+
'List and validate registered developer computers without contacting them. --onboard adds per-node setup instructions; --schema prints the roster JSON schema.',
|
|
90
|
+
['roster', 'list', 'machines', 'validate', 'schema'],
|
|
91
|
+
],
|
|
92
|
+
[
|
|
93
|
+
'fleet-nodes --onboard',
|
|
94
|
+
'Explore onboarding instructions',
|
|
95
|
+
'Get per-node SSH templates, VS Code guidance, authentication steps and safe verification commands.',
|
|
96
|
+
['setup', 'guide', 'instructions', 'vscode', 'connect'],
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
'fleet-nodes --schema',
|
|
100
|
+
'Explore the fleet JSON schema',
|
|
101
|
+
'Read the versioned roster schema for editors and agents.',
|
|
102
|
+
['agent', 'config', 'json', 'schema'],
|
|
103
|
+
],
|
|
104
|
+
[
|
|
105
|
+
'fleet-node-add',
|
|
106
|
+
'Register a developer computer',
|
|
107
|
+
'Preview or explicitly save an existing SSH alias, checkout path and per-machine capabilities. No SSH or cloud setup.',
|
|
108
|
+
['onboarding', 'register', 'new', 'setup', 'add'],
|
|
109
|
+
true,
|
|
110
|
+
],
|
|
111
|
+
]),
|
|
112
|
+
...group('T3', [
|
|
113
|
+
[
|
|
114
|
+
't3-status',
|
|
115
|
+
'Check T3 configuration drift',
|
|
116
|
+
'Compare declared T3 provider instances with the configuration on this machine; exit 1 on drift.',
|
|
117
|
+
['t3', 'drift', 'status'],
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
't3-apply',
|
|
121
|
+
'Reconcile T3 provider instances',
|
|
122
|
+
'Create and update declared instances and remove previously owned instances absent from the definition. --dry-run plans; --plan-hash rejects stale plans; --import ID adopts an existing instance.',
|
|
123
|
+
['t3', 'apply', 'plan', 'import', 'reconcile'],
|
|
124
|
+
true,
|
|
125
|
+
],
|
|
126
|
+
[
|
|
127
|
+
't3-cleanup',
|
|
128
|
+
'Remove owned T3 instances',
|
|
129
|
+
'Remove only provider instances recorded in personal ownership state. --dry-run previews.',
|
|
130
|
+
['t3', 'cleanup', 'remove'],
|
|
131
|
+
true,
|
|
132
|
+
],
|
|
133
|
+
]),
|
|
134
|
+
...group(
|
|
135
|
+
'Services',
|
|
136
|
+
Object.entries(SOURCE_OPERATIONS).map(([id, operation]) => [
|
|
137
|
+
id,
|
|
138
|
+
operation.title,
|
|
139
|
+
`${operation.description} Inspect devfleet ${id} help.`,
|
|
140
|
+
operation.keywords,
|
|
141
|
+
operation.mutation,
|
|
142
|
+
])
|
|
143
|
+
),
|
|
144
|
+
...group('Reference', [
|
|
145
|
+
[
|
|
146
|
+
'commands',
|
|
147
|
+
'Explore the command catalog and agent contract',
|
|
148
|
+
'Search the command index; the JSON document also carries the agent usage/safety contract, release metadata and exit codes.',
|
|
149
|
+
['search', 'help', 'llm', 'agent', 'api', 'index'],
|
|
150
|
+
],
|
|
151
|
+
]),
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
export function commandCatalog(search = '', group?: string): CommandSpec[] {
|
|
155
|
+
return searchCommands(COMMANDS, search).filter(
|
|
156
|
+
(command) => !group || command.group.toLowerCase() === group.toLowerCase()
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { automaticRepairs } from '../repair-plan';
|
|
2
|
+
import { mapBounded } from '../report';
|
|
3
|
+
import { repairWorkspace } from '../repair-runner';
|
|
4
|
+
import { inspectRemote } from '../transport';
|
|
5
|
+
import { timeout } from './options';
|
|
6
|
+
import type { AutomatedRepair } from '../repair-catalog';
|
|
7
|
+
import type { RepairTarget, RepairStep } from '../repair-plan';
|
|
8
|
+
import type { NodeReport } from '../types';
|
|
9
|
+
import type { CliOptions } from './options';
|
|
10
|
+
import type { FleetServices } from './fleet';
|
|
11
|
+
|
|
12
|
+
export async function applyRepairs(
|
|
13
|
+
targets: RepairTarget[],
|
|
14
|
+
steps: RepairStep[],
|
|
15
|
+
options: CliOptions,
|
|
16
|
+
services: FleetServices
|
|
17
|
+
): Promise<NodeReport[]> {
|
|
18
|
+
let source: Promise<string> | undefined;
|
|
19
|
+
return mapBounded(
|
|
20
|
+
targets,
|
|
21
|
+
async (target) => {
|
|
22
|
+
const actions = automaticRepairs(steps, target.name);
|
|
23
|
+
const checks: NodeReport['checks'] = [];
|
|
24
|
+
for (const step of actions) {
|
|
25
|
+
try {
|
|
26
|
+
if (!target.fingerprint)
|
|
27
|
+
throw new Error('No verified checkout fingerprint');
|
|
28
|
+
const repairs = [step.id as AutomatedRepair];
|
|
29
|
+
const result = target.node
|
|
30
|
+
? (
|
|
31
|
+
await inspectRemote(
|
|
32
|
+
target.node,
|
|
33
|
+
{
|
|
34
|
+
action: 'repair',
|
|
35
|
+
repairs,
|
|
36
|
+
expected: target.fingerprint,
|
|
37
|
+
source: await (source ??= Promise.resolve().then(
|
|
38
|
+
services.source
|
|
39
|
+
)),
|
|
40
|
+
timeoutMs: timeout(options),
|
|
41
|
+
},
|
|
42
|
+
services.run
|
|
43
|
+
)
|
|
44
|
+
).checks
|
|
45
|
+
: await repairWorkspace(
|
|
46
|
+
target.root,
|
|
47
|
+
repairs,
|
|
48
|
+
services.run,
|
|
49
|
+
target.fingerprint
|
|
50
|
+
);
|
|
51
|
+
checks.push(...result);
|
|
52
|
+
if (result.some((check) => check.status !== 'pass')) break;
|
|
53
|
+
} catch {
|
|
54
|
+
checks.push({
|
|
55
|
+
id: 'repair:operation',
|
|
56
|
+
status: 'fail',
|
|
57
|
+
summary:
|
|
58
|
+
'Repair could not be prepared or completed. Later actions on this computer were stopped; inspect partial changes locally. Raw errors are withheld.',
|
|
59
|
+
});
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { node: target.name, checks };
|
|
64
|
+
},
|
|
65
|
+
1
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import type { RepairStep, RepairTarget } from '../repair-plan';
|
|
3
|
+
|
|
4
|
+
export function repairPlanHash(
|
|
5
|
+
steps: RepairStep[],
|
|
6
|
+
targets: RepairTarget[]
|
|
7
|
+
): string {
|
|
8
|
+
return createHash('sha256')
|
|
9
|
+
.update(JSON.stringify({ steps, targets }))
|
|
10
|
+
.digest('hex');
|
|
11
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { selectNodes } from '../config';
|
|
2
|
+
import { probe } from '../probe';
|
|
3
|
+
import { mapBounded } from '../report';
|
|
4
|
+
import { discoverTailnet } from '../tailscale';
|
|
5
|
+
import {
|
|
6
|
+
HEALTH_CAPABILITIES,
|
|
7
|
+
tailnetHealth,
|
|
8
|
+
uniqueMapping,
|
|
9
|
+
} from '../tailnet-health';
|
|
10
|
+
import { matchComputers } from '../tailnet-matching';
|
|
11
|
+
import { inspectRemote } from '../transport';
|
|
12
|
+
import { FleetUsageError } from '../errors';
|
|
13
|
+
import { readRoster } from './roster';
|
|
14
|
+
import { capabilities, timeout } from './options';
|
|
15
|
+
import type { NodeReport, FleetConfig } from '../types';
|
|
16
|
+
import type { RepairTarget } from '../repair-plan';
|
|
17
|
+
import type { FleetServices } from './fleet';
|
|
18
|
+
import type { CliOptions } from './options';
|
|
19
|
+
import type { Settings } from './settings';
|
|
20
|
+
|
|
21
|
+
export interface RepairInspection {
|
|
22
|
+
reports: NodeReport[];
|
|
23
|
+
targets: RepairTarget[];
|
|
24
|
+
coverage: { scope: string; limitation: string };
|
|
25
|
+
}
|
|
26
|
+
export async function inspectRepair(
|
|
27
|
+
options: CliOptions,
|
|
28
|
+
settings: Settings,
|
|
29
|
+
services: FleetServices
|
|
30
|
+
): Promise<RepairInspection> {
|
|
31
|
+
if (options.local) return inspectLocal(settings, services, options);
|
|
32
|
+
const roster = await readRoster(settings.config);
|
|
33
|
+
if (options.node?.length) return inspectSelected(options, roster, services);
|
|
34
|
+
return inspectTailnet(options, settings, roster, services);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function inspectLocal(
|
|
38
|
+
settings: Settings,
|
|
39
|
+
services: FleetServices,
|
|
40
|
+
options: CliOptions
|
|
41
|
+
): Promise<RepairInspection> {
|
|
42
|
+
const snapshot = await probe(
|
|
43
|
+
settings.workspace,
|
|
44
|
+
options.require === undefined
|
|
45
|
+
? HEALTH_CAPABILITIES
|
|
46
|
+
: capabilities(options.require),
|
|
47
|
+
services.run
|
|
48
|
+
);
|
|
49
|
+
return {
|
|
50
|
+
reports: [{ node: 'local', snapshot, checks: snapshot.checks }],
|
|
51
|
+
targets: [
|
|
52
|
+
{
|
|
53
|
+
name: 'local',
|
|
54
|
+
root: settings.workspace,
|
|
55
|
+
fingerprint: snapshot.fingerprint,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
coverage: {
|
|
59
|
+
scope: 'local',
|
|
60
|
+
limitation: 'Only this process context was tested.',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function inspectSelected(
|
|
66
|
+
options: CliOptions,
|
|
67
|
+
roster: FleetConfig | null,
|
|
68
|
+
services: FleetServices
|
|
69
|
+
): Promise<RepairInspection> {
|
|
70
|
+
if (!roster)
|
|
71
|
+
throw new FleetUsageError(
|
|
72
|
+
'Register real SSH aliases and checkout paths before selecting remote repairs.'
|
|
73
|
+
);
|
|
74
|
+
const nodes = selectNodes(roster, options.node ?? []);
|
|
75
|
+
const source = await services.source();
|
|
76
|
+
const reports = await mapBounded(nodes, (node) =>
|
|
77
|
+
inspectRemote(
|
|
78
|
+
node,
|
|
79
|
+
{ action: 'probe', source, timeoutMs: timeout(options) },
|
|
80
|
+
services.run
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
return {
|
|
84
|
+
reports,
|
|
85
|
+
targets: nodes.map((node) =>
|
|
86
|
+
attachFingerprint({ name: node.id, root: node.workspace, node }, reports)
|
|
87
|
+
),
|
|
88
|
+
coverage: {
|
|
89
|
+
scope: 'selected',
|
|
90
|
+
limitation:
|
|
91
|
+
'Only selected roster computers were tested; other devices are unverified.',
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function inspectTailnet(
|
|
97
|
+
options: CliOptions,
|
|
98
|
+
settings: Settings,
|
|
99
|
+
roster: FleetConfig | null,
|
|
100
|
+
services: FleetServices
|
|
101
|
+
): Promise<RepairInspection> {
|
|
102
|
+
const tailnet = await discoverTailnet(services.run);
|
|
103
|
+
const reports = await tailnetHealth(
|
|
104
|
+
tailnet,
|
|
105
|
+
roster?.nodes ?? [],
|
|
106
|
+
{
|
|
107
|
+
root: settings.workspace,
|
|
108
|
+
timeoutMs: timeout(options),
|
|
109
|
+
source: services.source,
|
|
110
|
+
},
|
|
111
|
+
services.run
|
|
112
|
+
);
|
|
113
|
+
const matches = await matchComputers(
|
|
114
|
+
roster?.nodes ?? [],
|
|
115
|
+
tailnet.computers,
|
|
116
|
+
services.run
|
|
117
|
+
);
|
|
118
|
+
const targets: RepairTarget[] = [];
|
|
119
|
+
for (const peer of tailnet.computers) {
|
|
120
|
+
if (peer.self) targets.push({ name: peer.dns, root: settings.workspace });
|
|
121
|
+
else if (peer.online && uniqueMapping(matches.get(peer.id)!, matches)) {
|
|
122
|
+
const node = matches.get(peer.id)![0]!;
|
|
123
|
+
targets.push({ name: peer.dns, root: node.workspace, node });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!tailnet.computers.some((peer) => peer.self))
|
|
127
|
+
targets.push({ name: 'manager', root: settings.workspace });
|
|
128
|
+
return {
|
|
129
|
+
reports,
|
|
130
|
+
targets: targets.map((target) => attachFingerprint(target, reports)),
|
|
131
|
+
coverage: {
|
|
132
|
+
scope: 'tailscale-visible-computers',
|
|
133
|
+
limitation:
|
|
134
|
+
'Only devices visible to this Tailscale account/ACLs. Offline, unregistered or ambiguous computers cannot be repaired. No credentials, host keys or resources are cloned.',
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function attachFingerprint(
|
|
140
|
+
target: RepairTarget,
|
|
141
|
+
reports: NodeReport[]
|
|
142
|
+
): RepairTarget {
|
|
143
|
+
const snapshot = reports.find(
|
|
144
|
+
(report) => report.node === target.name
|
|
145
|
+
)?.snapshot;
|
|
146
|
+
return {
|
|
147
|
+
...target,
|
|
148
|
+
...(snapshot ? { fingerprint: snapshot.fingerprint } : {}),
|
|
149
|
+
};
|
|
150
|
+
}
|