@crvouga/dev-fleet 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mise/locks/aws-cdk/2.1146.1/aube-lock.yaml +24 -0
- package/.mise/locks/aws-cdk/2.1146.1/package.json +7 -0
- package/.mise/locks/azure-cli/2.91.0~a837e014/pyproject.toml +5 -0
- package/.mise/locks/azure-cli/2.91.0~a837e014/uv.lock +2794 -0
- package/.mise/locks/gemini/0.66.0~84aac8e6/aube-lock.yaml +123 -0
- package/.mise/locks/gemini/0.66.0~84aac8e6/package.json +12 -0
- package/.mise/locks/npm-devcontainers-cli/0.90.0/aube-lock.yaml +24 -0
- package/.mise/locks/npm-devcontainers-cli/0.90.0/package.json +7 -0
- package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/aube-lock.yaml +1432 -0
- package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/package.json +7 -0
- package/.mise/locks/npm-vercel/65.8.0/aube-lock.yaml +3595 -0
- package/.mise/locks/npm-vercel/65.8.0/package.json +7 -0
- package/.mise/locks/npm-wrangler/4.156.0/aube-lock.yaml +895 -0
- package/.mise/locks/npm-wrangler/4.156.0/package.json +7 -0
- package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/aube-lock.yaml +24 -0
- package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/package.json +7 -0
- package/LICENSE +21 -0
- package/README.md +357 -2
- package/agents/antigravity-proxy.ts +3 -0
- package/agents/audit.md +89 -0
- package/agents/claude-proxy +5 -0
- package/agents/claude-proxy.ts +3 -0
- package/agents/codex-proxy.ts +3 -0
- package/agents/grok-proxy.ts +3 -0
- package/agents/managed-proxy.ts +55 -0
- package/agents/opencode-proxy.ts +3 -0
- package/agents/registry.json +209 -0
- package/agents/registry.schema.json +149 -0
- package/agents/setup.md +158 -0
- package/agents/t3.json +40 -0
- package/ansible/macos/manager.yml +30 -0
- package/ansible/macos/policy.json +9 -0
- package/ansible/monitoring/README.md +215 -0
- package/ansible/monitoring/agent-darwin.yml +198 -0
- package/ansible/monitoring/agent.yml +172 -0
- package/ansible/monitoring/beszel-agent.service +42 -0
- package/ansible/monitoring/compose.yml +74 -0
- package/ansible/monitoring/hub-darwin.yml +102 -0
- package/ansible/monitoring/hub-route-darwin.yml +51 -0
- package/ansible/monitoring/lock.json +16 -0
- package/ansible/net/remote-probe.sh +13 -0
- package/ansible/pyproject.toml +12 -0
- package/ansible/ubuntu/authorize-sudo.sh +51 -0
- package/ansible/ubuntu/baseline.yml +89 -0
- package/ansible/ubuntu/disk-gc.py +336 -0
- package/ansible/ubuntu/disk-guard.py +104 -0
- package/ansible/ubuntu/policy.json +52 -0
- package/ansible/ubuntu/recovery.yml +15 -0
- package/ansible/ubuntu/t3/policy.json +7 -0
- package/ansible/ubuntu/t3-desktop.yml +220 -0
- package/ansible/ubuntu/t3-health.sh +33 -0
- package/ansible/ubuntu/t3-policy.yml +76 -0
- package/ansible/ubuntu/t3-route.yml +28 -0
- package/ansible/ubuntu/tailscale.yml +78 -0
- package/ansible/ubuntu/tasks/credentials.yml +47 -0
- package/ansible/ubuntu/tasks/docker.yml +31 -0
- package/ansible/ubuntu/tasks/packages.yml +56 -0
- package/ansible/ubuntu/tasks/power.yml +51 -0
- package/ansible/ubuntu/tasks/recovery.yml +39 -0
- package/ansible/ubuntu/tasks/resources.yml +312 -0
- package/ansible/ubuntu/tasks/services.yml +31 -0
- package/ansible/ubuntu/tasks/ssh.yml +71 -0
- package/ansible/ubuntu/tasks/user.yml +120 -0
- package/ansible/uv.lock +190 -0
- package/bin/devfleet.mjs +36 -0
- package/bootstrap/bootstrap.ps1 +50 -0
- package/bootstrap/bootstrap.sh +80 -0
- package/bootstrap/devfleet +7 -0
- package/bootstrap/docker-desktop.sh +33 -0
- package/bootstrap/doctor.ts +234 -0
- package/bootstrap/fleet-manager-run.sh +103 -0
- package/bootstrap/fleet-remote-bootstrap.sh +258 -0
- package/bootstrap/global-config.ts +171 -0
- package/bootstrap/install-launcher.sh +10 -0
- package/bootstrap/native-aws.ts +86 -0
- package/bootstrap/native-t3.ts +154 -0
- package/bootstrap/npm.ts +358 -0
- package/bootstrap/release-version.ts +157 -0
- package/bootstrap/setup-status.ts +51 -0
- package/bootstrap/setup.ts +88 -0
- package/bootstrap/shell-path.ts +105 -0
- package/bootstrap/verify-t3-archive.py +49 -0
- package/bootstrap/worker-auth.ts +65 -0
- package/bootstrap/worker-github.ts +47 -0
- package/bootstrap/worker-tools.ts +24 -0
- package/cliproxy/AGENTS.md +11 -0
- package/cliproxy/README.md +269 -0
- package/cliproxy/clients/bootstrap.py +143 -0
- package/cliproxy/clients/launcher.py +57 -0
- package/cliproxy/compose.yaml +90 -0
- package/cliproxy/config.template.yaml +70 -0
- package/cliproxy/docs/cloud-adoption.md +16 -0
- package/cliproxy/docs/deployment.png +0 -0
- package/cliproxy/docs/deployment.svg +109 -0
- package/cliproxy/docs/health-workflow.example.yml +185 -0
- package/cliproxy/docs/migration.md +131 -0
- package/cliproxy/docs/model-health-report.schema.json +364 -0
- package/cliproxy/docs/model-health.md +393 -0
- package/cliproxy/image.lock.json +5 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-backup-check.plist +38 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-backup.plist +48 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-fleet.plist +32 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-gc.plist +38 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-monitor.plist +29 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-vault-renew.plist +31 -0
- package/cliproxy/models.json +1618 -0
- package/cliproxy/requirements.lock +78 -0
- package/cliproxy/scripts/accounts.py +38 -0
- package/cliproxy/scripts/audit.py +123 -0
- package/cliproxy/scripts/backup +234 -0
- package/cliproxy/scripts/check.py +34 -0
- package/cliproxy/scripts/checkpoint.py +226 -0
- package/cliproxy/scripts/cli.py +64 -0
- package/cliproxy/scripts/cliproxy_host.py +257 -0
- package/cliproxy/scripts/cloud_state.py +150 -0
- package/cliproxy/scripts/container-health +6 -0
- package/cliproxy/scripts/docker +24 -0
- package/cliproxy/scripts/external-health.py +79 -0
- package/cliproxy/scripts/fleet_config.py +43 -0
- package/cliproxy/scripts/fleet_sync.py +274 -0
- package/cliproxy/scripts/initialize +84 -0
- package/cliproxy/scripts/install-dependencies +48 -0
- package/cliproxy/scripts/install.py +406 -0
- package/cliproxy/scripts/model_health.py +16 -0
- package/cliproxy/scripts/modelhealth/__init__.py +5 -0
- package/cliproxy/scripts/modelhealth/checks.py +429 -0
- package/cliproxy/scripts/modelhealth/cli.py +117 -0
- package/cliproxy/scripts/modelhealth/config.py +186 -0
- package/cliproxy/scripts/modelhealth/emulator.py +536 -0
- package/cliproxy/scripts/modelhealth/expect.py +88 -0
- package/cliproxy/scripts/modelhealth/findings.py +183 -0
- package/cliproxy/scripts/modelhealth/protocol.py +330 -0
- package/cliproxy/scripts/modelhealth/redact.py +28 -0
- package/cliproxy/scripts/modelhealth/report.py +150 -0
- package/cliproxy/scripts/modelhealth/runner.py +390 -0
- package/cliproxy/scripts/modelhealth/scenario.py +131 -0
- package/cliproxy/scripts/modelhealth/schema.py +63 -0
- package/cliproxy/scripts/modelhealth/simulate.py +111 -0
- package/cliproxy/scripts/modelhealth/transport.py +213 -0
- package/cliproxy/scripts/monitor +68 -0
- package/cliproxy/scripts/ops +527 -0
- package/cliproxy/scripts/public-endpoint +127 -0
- package/cliproxy/scripts/recover_node.py +177 -0
- package/cliproxy/scripts/restore-verify +60 -0
- package/cliproxy/scripts/retention.py +144 -0
- package/cliproxy/scripts/service-start +7 -0
- package/cliproxy/scripts/validate-config +56 -0
- package/cliproxy/scripts/vault-access.py +57 -0
- package/cliproxy/scripts/vault-backup-login +55 -0
- package/cliproxy/scripts/vault-recovery +56 -0
- package/cliproxy/scripts/vault-renew +30 -0
- package/cliproxy/systemd/cliproxy-backup-check.service +10 -0
- package/cliproxy/systemd/cliproxy-backup-check.timer +8 -0
- package/cliproxy/systemd/cliproxy-backup.service +13 -0
- package/cliproxy/systemd/cliproxy-backup.timer +8 -0
- package/cliproxy/systemd/cliproxy-fleet.service +17 -0
- package/cliproxy/systemd/cliproxy-gc.service +13 -0
- package/cliproxy/systemd/cliproxy-gc.timer +8 -0
- package/cliproxy/systemd/cliproxy-monitor.service +8 -0
- package/cliproxy/systemd/cliproxy-monitor.timer +7 -0
- package/cliproxy/systemd/cliproxy-vault-renew.service +8 -0
- package/cliproxy/systemd/cliproxy-vault-renew.timer +7 -0
- package/cliproxy/vendor/LICENSE +22 -0
- package/cliproxy/vendor/config.upstream.yaml +1349 -0
- package/docs/consumer-setup.md +408 -0
- package/docs/declared.md +89 -0
- package/docs/machine-setup.md +399 -0
- package/docs/operations.md +276 -0
- package/docs/releasing.md +103 -0
- package/docs/state.md +254 -0
- package/docs/topology.md +67 -0
- package/docs/ubuntu-sync.md +351 -0
- package/docs/worker-contract.md +67 -0
- package/examples/fleet.json +100 -0
- package/examples/roster.json +13 -0
- package/llms.txt +35 -0
- package/mise.lock +1521 -0
- package/mise.toml +110 -0
- package/package.json +74 -4
- package/schema/fleet.schema.json +420 -0
- package/schema/roster.schema.json +77 -0
- package/src/agents/account-relay.ts +111 -0
- package/src/agents/accounts.ts +85 -0
- package/src/agents/audit.ts +77 -0
- package/src/agents/cli.ts +13 -0
- package/src/agents/command.ts +204 -0
- package/src/agents/converge.ts +123 -0
- package/src/agents/doctor.ts +107 -0
- package/src/agents/errors.ts +2 -0
- package/src/agents/files.ts +73 -0
- package/src/agents/fleet-verify.ts +165 -0
- package/src/agents/launch.ts +290 -0
- package/src/agents/native-verify.ts +371 -0
- package/src/agents/provider-errors.ts +57 -0
- package/src/agents/registry.ts +61 -0
- package/src/agents/render.ts +110 -0
- package/src/agents/round-trip.ts +192 -0
- package/src/agents/secrets.ts +13 -0
- package/src/agents/toolchain.ts +34 -0
- package/src/agents/verify-cache.ts +103 -0
- package/src/agents/verify.ts +127 -0
- package/src/auth-checks.ts +163 -0
- package/src/cli/agent-guide.ts +129 -0
- package/src/cli/app.ts +122 -0
- package/src/cli/cli.ts +28 -0
- package/src/cli/command-index.ts +57 -0
- package/src/cli/contracts.ts +25 -0
- package/src/cli/fleet-reference.ts +17 -0
- package/src/cli/fleet-state.ts +499 -0
- package/src/cli/fleet-sync-checkout.ts +130 -0
- package/src/cli/fleet-sync-enrollment.ts +140 -0
- package/src/cli/fleet-sync-forward.ts +134 -0
- package/src/cli/fleet-sync-manager.ts +249 -0
- package/src/cli/fleet-sync-monitoring.ts +185 -0
- package/src/cli/fleet-sync-output.ts +317 -0
- package/src/cli/fleet-sync-privilege.ts +27 -0
- package/src/cli/fleet-sync-progress.ts +109 -0
- package/src/cli/fleet-sync-remote.ts +242 -0
- package/src/cli/fleet-sync-report.ts +26 -0
- package/src/cli/fleet-sync-schedule.ts +138 -0
- package/src/cli/fleet-sync-state.ts +86 -0
- package/src/cli/fleet-sync-vault.ts +166 -0
- package/src/cli/fleet-sync.ts +375 -0
- package/src/cli/fleet.ts +165 -0
- package/src/cli/managed-health.ts +219 -0
- package/src/cli/node-deployment.ts +34 -0
- package/src/cli/node-sync.ts +121 -0
- package/src/cli/node-t3.ts +31 -0
- package/src/cli/options.ts +141 -0
- package/src/cli/probe-source.ts +7 -0
- package/src/cli/program.ts +185 -0
- package/src/cli/project.ts +51 -0
- package/src/cli/register-fleet.ts +296 -0
- package/src/cli/registry.ts +185 -0
- package/src/cli/repair-apply.ts +67 -0
- package/src/cli/repair-guard.ts +11 -0
- package/src/cli/repair-inspect.ts +150 -0
- package/src/cli/repair-script.ts +76 -0
- package/src/cli/repair.ts +209 -0
- package/src/cli/roster.ts +105 -0
- package/src/cli/search.ts +35 -0
- package/src/cli/settings.ts +67 -0
- package/src/cli/source-operations.ts +102 -0
- package/src/cli/t3/command.ts +90 -0
- package/src/cli/t3/register.ts +55 -0
- package/src/cli/tailnet.ts +127 -0
- package/src/cli/ui-actions.ts +133 -0
- package/src/cli/ui-controller.ts +113 -0
- package/src/cli/ui-data.ts +106 -0
- package/src/cli/ui-execute.ts +163 -0
- package/src/cli/ui-repairs.ts +34 -0
- package/src/cli/ui-reports.ts +211 -0
- package/src/cli/ui-targets.ts +100 -0
- package/src/cli/ui-terminal.ts +109 -0
- package/src/cli/ui-types.ts +49 -0
- package/src/cli/ui-wizard.ts +101 -0
- package/src/cli/ui.ts +9 -0
- package/src/cli/worker-health-output.ts +37 -0
- package/src/config.ts +186 -0
- package/src/configure.ts +255 -0
- package/src/definition.ts +290 -0
- package/src/diagnostics.ts +92 -0
- package/src/doctor-check.ts +10 -0
- package/src/errors.ts +1 -0
- package/src/fingerprint.ts +120 -0
- package/src/fleet-config.ts +174 -0
- package/src/input-hashes.ts +73 -0
- package/src/machine/local-source.ts +52 -0
- package/src/machine/lock-error.ts +2 -0
- package/src/machine/platform.ts +44 -0
- package/src/machine/source.ts +68 -0
- package/src/machine/state.ts +173 -0
- package/src/machine-health.ts +95 -0
- package/src/managed-source.ts +47 -0
- package/src/manager-failure.ts +48 -0
- package/src/manager-sync.ts +405 -0
- package/src/manager.ts +62 -0
- package/src/monitoring/agent-evidence.ts +146 -0
- package/src/monitoring/apply.ts +183 -0
- package/src/monitoring/beszel.ts +198 -0
- package/src/monitoring/enroll.ts +111 -0
- package/src/monitoring/hub-access.ts +114 -0
- package/src/monitoring/hub-backup.ts +121 -0
- package/src/monitoring/hub-viewer.ts +142 -0
- package/src/monitoring/hub.ts +93 -0
- package/src/monitoring/manager-agent.ts +73 -0
- package/src/monitoring/manager-hub.ts +248 -0
- package/src/monitoring/remote.ts +65 -0
- package/src/network-definition.ts +83 -0
- package/src/node-converge-linux.ts +374 -0
- package/src/node-converge-shared.ts +83 -0
- package/src/node-converge.ts +40 -0
- package/src/onboard.ts +35 -0
- package/src/package-info.ts +45 -0
- package/src/platform/ansible-receipt.ts +128 -0
- package/src/platform/ansible.ts +66 -0
- package/src/platform/darwin.ts +203 -0
- package/src/platform/detect.ts +11 -0
- package/src/platform/fallback.ts +110 -0
- package/src/platform/index.ts +38 -0
- package/src/platform/launchd.ts +98 -0
- package/src/platform/linux.ts +139 -0
- package/src/platform/ps.ts +45 -0
- package/src/platform/resource-verify.ts +132 -0
- package/src/platform/systemd-user.ts +95 -0
- package/src/platform/t3-policy.ts +137 -0
- package/src/platform/t3-route.ts +68 -0
- package/src/platform/t3-service.ts +75 -0
- package/src/platform/types.ts +123 -0
- package/src/platform/ubuntu-audit.ts +66 -0
- package/src/platform/ubuntu-verify.ts +105 -0
- package/src/platform/windows.ts +148 -0
- package/src/power-recovery.ts +55 -0
- package/src/probe.ts +51 -0
- package/src/protocol.ts +119 -0
- package/src/proxy-converge.ts +85 -0
- package/src/remote-bootstrap.ts +210 -0
- package/src/remote-entry.ts +89 -0
- package/src/remote-lock.ts +12 -0
- package/src/repair-catalog.ts +206 -0
- package/src/repair-plan.ts +78 -0
- package/src/repair-runner.ts +84 -0
- package/src/report.ts +71 -0
- package/src/runner.ts +104 -0
- package/src/secrets.ts +169 -0
- package/src/service-auth.ts +115 -0
- package/src/ssh.ts +135 -0
- package/src/state/bucket.ts +208 -0
- package/src/state/check.ts +74 -0
- package/src/state/connection.ts +92 -0
- package/src/state/enrollment.ts +45 -0
- package/src/state/legacy.ts +89 -0
- package/src/state/litestream.ts +143 -0
- package/src/state/operator.ts +20 -0
- package/src/state/paths.ts +40 -0
- package/src/state/roster.ts +62 -0
- package/src/state/store.ts +169 -0
- package/src/state/sync.ts +352 -0
- package/src/sync.ts +104 -0
- package/src/t3/accounts.ts +81 -0
- package/src/t3/desired.ts +165 -0
- package/src/t3/files.ts +81 -0
- package/src/t3/names-check.ts +53 -0
- package/src/t3/names.ts +155 -0
- package/src/t3/plan.ts +82 -0
- package/src/t3/providers.ts +74 -0
- package/src/t3/reconcile.ts +123 -0
- package/src/t3-endpoint.ts +104 -0
- package/src/t3-linked-check.ts +123 -0
- package/src/tailnet-health.ts +231 -0
- package/src/tailnet-matching.ts +37 -0
- package/src/tailscale.ts +141 -0
- package/src/toolchain-checks.ts +108 -0
- package/src/topology.ts +24 -0
- package/src/transport.ts +122 -0
- package/src/types.ts +99 -0
- package/src/vault-config.ts +38 -0
- package/src/worker-access-checks.ts +168 -0
- package/src/worker-capacity.ts +34 -0
- package/src/worker-host-checks.ts +181 -0
- package/src/worker-policy.ts +64 -0
- package/src/worker-readiness.ts +64 -0
- package/src/workstation-checks.ts +29 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { verifyT3Linked } from '../t3-linked-check';
|
|
2
|
+
import { verifyT3Endpoint } from '../t3-endpoint';
|
|
3
|
+
import type { Check, NodeReport, Runner } from '../types';
|
|
4
|
+
import { fleet as definition } from '../fleet-config';
|
|
5
|
+
import { enrolledRoster } from '../definition';
|
|
6
|
+
import type { Tailnet } from '../tailscale';
|
|
7
|
+
import type { FleetNode } from '../types';
|
|
8
|
+
import {
|
|
9
|
+
publicHubUrl,
|
|
10
|
+
verifyBeszel,
|
|
11
|
+
type BeszelRequest,
|
|
12
|
+
} from '../monitoring/beszel';
|
|
13
|
+
import { hubAccess } from '../monitoring/hub-access';
|
|
14
|
+
import { hubUrl } from '../topology';
|
|
15
|
+
|
|
16
|
+
export interface DashboardAccess {
|
|
17
|
+
(hub: string): Promise<{ request: BeszelRequest }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The hub always runs on the manager, reached by its MagicDNS name. */
|
|
21
|
+
export function dashboardUrl(tailnet: Tailnet): string | null {
|
|
22
|
+
const canonical = definition;
|
|
23
|
+
const visible = tailnet.computers.some(
|
|
24
|
+
(computer) =>
|
|
25
|
+
computer.dns.replace(/\.$/, '') ===
|
|
26
|
+
canonical.manager.network.tailscaleName
|
|
27
|
+
);
|
|
28
|
+
return visible ? hubUrl(canonical) : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Centralized dashboard evidence for every canonical worker: the hub must answer
|
|
32
|
+
* privately, and each node must be registered, connected and reporting fresh
|
|
33
|
+
* samples. A missing manager Vault session is reported, never silently passed.
|
|
34
|
+
*/
|
|
35
|
+
export async function appendDashboardEvidence(
|
|
36
|
+
reports: NodeReport[],
|
|
37
|
+
tailnet: Tailnet,
|
|
38
|
+
options: { access?: DashboardAccess; fetcher?: typeof fetch } = {}
|
|
39
|
+
): Promise<string | null> {
|
|
40
|
+
const hub = dashboardUrl(tailnet);
|
|
41
|
+
const dashboard: NodeReport = { node: 'dashboard', checks: [] };
|
|
42
|
+
reports.push(dashboard);
|
|
43
|
+
if (!hub) {
|
|
44
|
+
dashboard.checks.push(
|
|
45
|
+
fail(
|
|
46
|
+
'beszel:hub',
|
|
47
|
+
'BESZEL_HUB_SELECTION_REQUIRED',
|
|
48
|
+
'The hub runs on the manager, which is not visible in Tailscale. Wake the manager and reconnect it to the tailnet.'
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const fetcher = options.fetcher ?? fetch;
|
|
54
|
+
dashboard.checks.push(await hubReachable(hub, fetcher));
|
|
55
|
+
dashboard.checks.push(await publicEndpoint(fetcher));
|
|
56
|
+
await nodeEvidence(reports, dashboard, hub, options.access ?? hubAccess);
|
|
57
|
+
return hub;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function nodeEvidence(
|
|
61
|
+
reports: NodeReport[],
|
|
62
|
+
dashboard: NodeReport,
|
|
63
|
+
hub: string,
|
|
64
|
+
access: DashboardAccess
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
const canonical = definition;
|
|
67
|
+
try {
|
|
68
|
+
const { request } = await access(hub);
|
|
69
|
+
for (const machine of canonical.workers) {
|
|
70
|
+
const evidence = await verifyBeszel(hub, machine.id, request);
|
|
71
|
+
const report =
|
|
72
|
+
reports.find(
|
|
73
|
+
(entry) =>
|
|
74
|
+
entry.node === machine.network?.tailscaleName ||
|
|
75
|
+
entry.node === machine.id
|
|
76
|
+
) ?? dashboard;
|
|
77
|
+
report.checks.push(...evidence.checks);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
dashboard.checks.push({
|
|
81
|
+
id: 'beszel:access',
|
|
82
|
+
status: 'skip',
|
|
83
|
+
summary:
|
|
84
|
+
error instanceof Error && error.message.startsWith('BESZEL_')
|
|
85
|
+
? error.message
|
|
86
|
+
: 'BESZEL_ACCOUNT_REQUIRED',
|
|
87
|
+
remedy: {
|
|
88
|
+
action:
|
|
89
|
+
'Authenticate this manager to Vault so BESZEL_FLEET_AUTH can be read, then rerun fleet-status --workers. Node registration and metric freshness are unverified until then.',
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function hubReachable(
|
|
96
|
+
hub: string,
|
|
97
|
+
fetcher: typeof fetch
|
|
98
|
+
): Promise<Check> {
|
|
99
|
+
try {
|
|
100
|
+
const response = await fetcher(`${hub}/api/health`, {
|
|
101
|
+
redirect: 'error',
|
|
102
|
+
signal: AbortSignal.timeout(10_000),
|
|
103
|
+
});
|
|
104
|
+
if (response.ok)
|
|
105
|
+
return {
|
|
106
|
+
id: 'beszel:hub',
|
|
107
|
+
status: 'pass',
|
|
108
|
+
summary: `Dashboard answers privately at ${hub}.`,
|
|
109
|
+
};
|
|
110
|
+
} catch {
|
|
111
|
+
/* Connection details are not evidence; fall through. */
|
|
112
|
+
}
|
|
113
|
+
return fail(
|
|
114
|
+
'beszel:hub',
|
|
115
|
+
'BESZEL_HUB_UNREACHABLE',
|
|
116
|
+
`The private dashboard did not answer at ${hub}/api/health. The hub runs on the manager: keep it awake with Docker Desktop running, then run devfleet fleet-sync there.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The Cloudflare hostname is convenience access; the private hub stays canonical,
|
|
121
|
+
* so an offline connector warns instead of failing the audit.
|
|
122
|
+
*/
|
|
123
|
+
async function publicEndpoint(fetcher: typeof fetch): Promise<Check> {
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetcher(`${publicHubUrl()}/api/health`, {
|
|
126
|
+
redirect: 'error',
|
|
127
|
+
signal: AbortSignal.timeout(10_000),
|
|
128
|
+
});
|
|
129
|
+
if (response.ok)
|
|
130
|
+
return {
|
|
131
|
+
id: 'beszel:public-endpoint',
|
|
132
|
+
status: 'pass',
|
|
133
|
+
summary: `Dashboard answers publicly at ${publicHubUrl()}.`,
|
|
134
|
+
};
|
|
135
|
+
} catch {
|
|
136
|
+
/* Connection details are not evidence; fall through. */
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
id: 'beszel:public-endpoint',
|
|
140
|
+
status: 'warn',
|
|
141
|
+
summary: 'BESZEL_PUBLIC_ENDPOINT_OFFLINE',
|
|
142
|
+
remedy: {
|
|
143
|
+
action: `${publicHubUrl()} did not answer. Create the Cloudflare tunnel and store its token in the fleet's secrets, then run fleet-sync on the manager so the hub starts its Cloudflare connector. The private dashboard is unaffected.`,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function fail(id: string, summary: string, action: string): Check {
|
|
149
|
+
return { id, status: 'fail', summary, remedy: { action } };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Explicit developer scope; the ordinary health command still audits all peers. */
|
|
153
|
+
export function managedHealthScope(
|
|
154
|
+
tailnet: Tailnet,
|
|
155
|
+
roster: FleetNode[],
|
|
156
|
+
includeManager = true
|
|
157
|
+
) {
|
|
158
|
+
const canonical = definition;
|
|
159
|
+
const declared = roster.filter((node) =>
|
|
160
|
+
canonical.workers.some((entry) => entry.id === node.id)
|
|
161
|
+
);
|
|
162
|
+
const nodes = declared.length
|
|
163
|
+
? enrolledRoster(canonical, { version: 1, nodes: declared }).nodes
|
|
164
|
+
: [];
|
|
165
|
+
const computers = tailnet.computers.filter(
|
|
166
|
+
(peer) =>
|
|
167
|
+
(includeManager && peer.self) ||
|
|
168
|
+
canonical.workers.some(
|
|
169
|
+
(node) =>
|
|
170
|
+
peer.dns === node.network?.tailscaleName ||
|
|
171
|
+
peer.dns.split('.')[0] === node.ssh ||
|
|
172
|
+
nodes.some(
|
|
173
|
+
(entry) => entry.id === node.id && entry.tailscaleId === peer.id
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
const missing = canonical.workers.filter(
|
|
178
|
+
(node) => !nodes.some((entry) => entry.id === node.id)
|
|
179
|
+
);
|
|
180
|
+
return {
|
|
181
|
+
nodes,
|
|
182
|
+
tailnet: {
|
|
183
|
+
...tailnet,
|
|
184
|
+
computers,
|
|
185
|
+
checks: [
|
|
186
|
+
...tailnet.checks,
|
|
187
|
+
...missing.map((node) => ({
|
|
188
|
+
id: `fleet:enrollment:${node.id}`,
|
|
189
|
+
status: 'fail' as const,
|
|
190
|
+
summary:
|
|
191
|
+
'Canonical developer node lacks a verified SSH/check-out registration.',
|
|
192
|
+
remedy: {
|
|
193
|
+
action: `Enroll ${node.id} with devfleet fleet-node-add, or use interactive fleet-sync enrollment.`,
|
|
194
|
+
},
|
|
195
|
+
})),
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
excluded: tailnet.computers
|
|
199
|
+
.filter((peer) => !computers.includes(peer))
|
|
200
|
+
.map((peer) => peer.dns),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function appendManagerLinks(
|
|
205
|
+
reports: NodeReport[],
|
|
206
|
+
registered: FleetNode[],
|
|
207
|
+
run: Runner
|
|
208
|
+
): Promise<void> {
|
|
209
|
+
for (const node of registered) {
|
|
210
|
+
const machine = definition.workers.find((entry) => entry.id === node.id);
|
|
211
|
+
const report = reports.find(
|
|
212
|
+
(entry) =>
|
|
213
|
+
entry.node === machine?.network?.tailscaleName || entry.node === node.id
|
|
214
|
+
);
|
|
215
|
+
if (!report) continue;
|
|
216
|
+
report.checks.push(...(await verifyT3Endpoint(node, run)));
|
|
217
|
+
report.checks.push(await verifyT3Linked(node, run));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { read, object, write } from '../t3/files';
|
|
3
|
+
import { FleetUsageError } from '../errors';
|
|
4
|
+
|
|
5
|
+
export async function deployment(root: string) {
|
|
6
|
+
const raw = await read(join(root, 'node_modules/.personal-node.json'));
|
|
7
|
+
if (raw === null) return null;
|
|
8
|
+
const data = object(raw);
|
|
9
|
+
if (
|
|
10
|
+
data['version'] !== 1 ||
|
|
11
|
+
typeof data['revision'] !== 'string' ||
|
|
12
|
+
!/^[a-f0-9]{40}$/.test(data['revision']) ||
|
|
13
|
+
typeof data['node'] !== 'string' ||
|
|
14
|
+
!/^[a-z0-9][a-z0-9-]{0,62}$/.test(data['node'])
|
|
15
|
+
)
|
|
16
|
+
throw new FleetUsageError(
|
|
17
|
+
'Invalid local node receipt. Restore it before reconciling.'
|
|
18
|
+
);
|
|
19
|
+
return {
|
|
20
|
+
workspace: join(root, 'node_modules/.fleet-revisions', data['revision']),
|
|
21
|
+
node: data['node'],
|
|
22
|
+
revision: data['revision'],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function recordDeployment(
|
|
27
|
+
root: string,
|
|
28
|
+
node: string,
|
|
29
|
+
revision: string
|
|
30
|
+
) {
|
|
31
|
+
const path = join(root, 'node_modules/.personal-node.json');
|
|
32
|
+
const content = JSON.stringify({ version: 1, node, revision }) + '\n';
|
|
33
|
+
if ((await read(path)) !== content) await write(path, content);
|
|
34
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { configureWorkspace } from '../configure';
|
|
3
|
+
import { runCommand } from '../runner';
|
|
4
|
+
import { FleetUsageError } from '../errors';
|
|
5
|
+
import { packageInfo } from '../package-info';
|
|
6
|
+
import type { Runner } from '../types';
|
|
7
|
+
import type { Settings } from './settings';
|
|
8
|
+
import type { CommandOutcome } from './contracts';
|
|
9
|
+
import { syncT3 } from './node-t3';
|
|
10
|
+
import { nodeId, type T3Options } from '../t3/desired';
|
|
11
|
+
import { recordDeployment } from './node-deployment';
|
|
12
|
+
|
|
13
|
+
export interface NodeSyncOptions extends T3Options {
|
|
14
|
+
revision?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** One local convergence: locked tools, public launcher, profiles, then T3. */
|
|
18
|
+
export async function syncNode(
|
|
19
|
+
options: NodeSyncOptions,
|
|
20
|
+
settings: Settings,
|
|
21
|
+
run: Runner = runCommand
|
|
22
|
+
): Promise<CommandOutcome> {
|
|
23
|
+
if (process.platform === 'win32' && !options.dryRun)
|
|
24
|
+
throw new FleetUsageError(
|
|
25
|
+
'Codex proxy node sync requires macOS/Linux/WSL. Use dev-fleet commands for native Windows configuration.'
|
|
26
|
+
);
|
|
27
|
+
const node = nodeId(options.id);
|
|
28
|
+
// An installed package is not a checkout, and the Git repository around it
|
|
29
|
+
// is the operator's own project: its HEAD is not a dev-fleet revision.
|
|
30
|
+
if (packageInfo().revision)
|
|
31
|
+
throw new FleetUsageError(
|
|
32
|
+
'fleet-node-sync works on a checkout of dev-fleet. Run fleet-sync instead: it converges this machine from a checkout it owns.'
|
|
33
|
+
);
|
|
34
|
+
const head = await run(['git', 'rev-parse', '--verify', 'HEAD'], {
|
|
35
|
+
cwd: settings.workspace,
|
|
36
|
+
});
|
|
37
|
+
const revision = options.revision ?? head.stdout.trim();
|
|
38
|
+
if (!head.ok || !/^[a-f0-9]{40}$/.test(revision))
|
|
39
|
+
throw new FleetUsageError(
|
|
40
|
+
'An existing personal checkout is required; set --workspace PATH.'
|
|
41
|
+
);
|
|
42
|
+
const request = { node, revision, tool: 'codex', profile: 'proxy' };
|
|
43
|
+
if (options.dryRun)
|
|
44
|
+
return {
|
|
45
|
+
exitCode: 0,
|
|
46
|
+
data: {
|
|
47
|
+
version: 1,
|
|
48
|
+
command: 'fleet-node-sync',
|
|
49
|
+
ok: true,
|
|
50
|
+
applied: false,
|
|
51
|
+
...request,
|
|
52
|
+
steps: [
|
|
53
|
+
'Reconcile locked tools',
|
|
54
|
+
'Install personal and retire owned ws launcher',
|
|
55
|
+
'Upsert Codex proxy profile',
|
|
56
|
+
'Reconcile declared T3 instances and remove stale owned entries',
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
const checks = await configureWorkspace(settings.workspace, request, run);
|
|
61
|
+
if (checks.some((check) => check.status !== 'pass'))
|
|
62
|
+
return {
|
|
63
|
+
exitCode: 1,
|
|
64
|
+
data: { version: 1, command: 'fleet-node-sync', ok: false, checks },
|
|
65
|
+
};
|
|
66
|
+
return finishSync(options, settings, { request, checks }, run);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function finishSync(
|
|
70
|
+
options: NodeSyncOptions,
|
|
71
|
+
settings: Settings,
|
|
72
|
+
input: {
|
|
73
|
+
request: { node: string; revision: string; tool: string; profile: string };
|
|
74
|
+
checks: Awaited<ReturnType<typeof configureWorkspace>>;
|
|
75
|
+
},
|
|
76
|
+
run: Runner
|
|
77
|
+
): Promise<CommandOutcome> {
|
|
78
|
+
const { request, checks } = input;
|
|
79
|
+
const { node, revision } = request;
|
|
80
|
+
const workspace = join(
|
|
81
|
+
settings.workspace,
|
|
82
|
+
'node_modules/.fleet-revisions',
|
|
83
|
+
revision
|
|
84
|
+
);
|
|
85
|
+
const installed = await run(['sh', 'bootstrap/install-launcher.sh'], {
|
|
86
|
+
cwd: workspace,
|
|
87
|
+
timeoutMs: 30_000,
|
|
88
|
+
});
|
|
89
|
+
if (!installed.ok)
|
|
90
|
+
return {
|
|
91
|
+
exitCode: 1,
|
|
92
|
+
data: {
|
|
93
|
+
version: 1,
|
|
94
|
+
ok: false,
|
|
95
|
+
checks,
|
|
96
|
+
error:
|
|
97
|
+
'Launcher installation failed; profile changes may already have applied.',
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
const t3 = await syncT3({ ...options, id: node }, { ...settings, workspace });
|
|
101
|
+
if (t3.exitCode !== 0) return t3;
|
|
102
|
+
await recordDeployment(settings.workspace, node, revision);
|
|
103
|
+
return {
|
|
104
|
+
exitCode: 0,
|
|
105
|
+
data: {
|
|
106
|
+
version: 1,
|
|
107
|
+
command: 'fleet-node-sync',
|
|
108
|
+
ok: true,
|
|
109
|
+
applied: true,
|
|
110
|
+
...request,
|
|
111
|
+
checks,
|
|
112
|
+
t3: t3.data,
|
|
113
|
+
readiness: {
|
|
114
|
+
authentication: 'unverified',
|
|
115
|
+
inference: 'unverified',
|
|
116
|
+
pairing: 'unverified',
|
|
117
|
+
},
|
|
118
|
+
next: 'Use devfleet t3-status for configuration drift and devfleet fleet-status for machine health.',
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { FleetUsageError } from '../errors';
|
|
2
|
+
import type { Settings } from './settings';
|
|
3
|
+
import type { CommandOutcome } from './contracts';
|
|
4
|
+
import { t3Command } from './t3/command';
|
|
5
|
+
import type { T3Options } from '../t3/desired';
|
|
6
|
+
|
|
7
|
+
export async function syncT3(
|
|
8
|
+
options: T3Options,
|
|
9
|
+
settings: Settings
|
|
10
|
+
): Promise<CommandOutcome> {
|
|
11
|
+
try {
|
|
12
|
+
return await t3Command('t3-apply', options, settings);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
return {
|
|
15
|
+
exitCode: 1,
|
|
16
|
+
data: {
|
|
17
|
+
version: 1,
|
|
18
|
+
command: 'fleet-node-sync',
|
|
19
|
+
ok: false,
|
|
20
|
+
applied: true,
|
|
21
|
+
stage: 't3',
|
|
22
|
+
error:
|
|
23
|
+
error instanceof FleetUsageError
|
|
24
|
+
? error.message
|
|
25
|
+
: 'T3 reconciliation failed; inspect local configuration and ownership state.',
|
|
26
|
+
progress:
|
|
27
|
+
'Tools, launcher and profile steps completed. Node receipt was not advanced. Fix the T3 conflict and repeat fleet-node-sync.',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { CAPABILITIES } from '../config';
|
|
2
|
+
import { FleetUsageError } from '../errors';
|
|
3
|
+
import type { Capability } from '../types';
|
|
4
|
+
import type { Settings } from './settings';
|
|
5
|
+
|
|
6
|
+
export interface CliOptions extends Partial<Settings> {
|
|
7
|
+
managed?: boolean;
|
|
8
|
+
workers?: boolean;
|
|
9
|
+
tailnet?: boolean;
|
|
10
|
+
list?: boolean;
|
|
11
|
+
onboard?: boolean;
|
|
12
|
+
schema?: boolean;
|
|
13
|
+
json?: boolean;
|
|
14
|
+
nonInteractive?: boolean;
|
|
15
|
+
interactive?: boolean;
|
|
16
|
+
local?: boolean;
|
|
17
|
+
all?: boolean;
|
|
18
|
+
node?: string[];
|
|
19
|
+
require?: string;
|
|
20
|
+
timeout?: string;
|
|
21
|
+
strict?: boolean;
|
|
22
|
+
repair?: string;
|
|
23
|
+
script?: boolean;
|
|
24
|
+
planHash?: string;
|
|
25
|
+
apply?: boolean;
|
|
26
|
+
yes?: boolean;
|
|
27
|
+
id?: string;
|
|
28
|
+
ssh?: string;
|
|
29
|
+
path?: string;
|
|
30
|
+
runtime?: string;
|
|
31
|
+
search?: string;
|
|
32
|
+
group?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function capabilities(value?: string): Capability[] {
|
|
36
|
+
const values =
|
|
37
|
+
value === 'none'
|
|
38
|
+
? []
|
|
39
|
+
: (value?.split(',') ??
|
|
40
|
+
CAPABILITIES.filter((entry) => entry !== 'worker'));
|
|
41
|
+
if (values.some((entry) => !CAPABILITIES.includes(entry as Capability)))
|
|
42
|
+
throw new FleetUsageError(
|
|
43
|
+
`--require expects ${CAPABILITIES.join(',')} or none.`
|
|
44
|
+
);
|
|
45
|
+
return [...new Set(values)] as Capability[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function timeout(options: CliOptions): number {
|
|
49
|
+
const value = Number(
|
|
50
|
+
options.timeout ?? (options.apply ? 1_500_000 : 120_000)
|
|
51
|
+
);
|
|
52
|
+
if (!Number.isInteger(value) || value < 1000 || value > 1_800_000)
|
|
53
|
+
throw new FleetUsageError('--timeout must be 1000–1800000 milliseconds.');
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Validation runs on the internal action selected by fleet-* flags. */
|
|
58
|
+
export function validateOptions(command: string, options: CliOptions): void {
|
|
59
|
+
if (options.tailnet && options.workers)
|
|
60
|
+
throw new FleetUsageError('Choose --tailnet or --workers, not both.');
|
|
61
|
+
if (options.onboard && options.schema)
|
|
62
|
+
throw new FleetUsageError('Choose --onboard or --schema, not both.');
|
|
63
|
+
validateMutation(command, options);
|
|
64
|
+
validateRepair(command, options);
|
|
65
|
+
validateScope(command, options);
|
|
66
|
+
if (
|
|
67
|
+
(['discover', 'health'].includes(command) ||
|
|
68
|
+
(command === 'repair' && !options.local)) &&
|
|
69
|
+
options.require !== undefined
|
|
70
|
+
)
|
|
71
|
+
throw new FleetUsageError(
|
|
72
|
+
'--require is supported for roster status and local repair only; tailnet checks use declared roster requirements.'
|
|
73
|
+
);
|
|
74
|
+
if (options.timeout !== undefined) timeout(options);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function validateMutation(command: string, options: CliOptions): void {
|
|
78
|
+
if (options.apply && !['add', 'repair'].includes(command))
|
|
79
|
+
throw new FleetUsageError(
|
|
80
|
+
'--apply is only valid for fleet-node-add and fleet-repair.'
|
|
81
|
+
);
|
|
82
|
+
if (options.apply && !options.yes)
|
|
83
|
+
throw new FleetUsageError('Mutation requires both --apply and --yes.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validateScope(command: string, options: CliOptions): void {
|
|
87
|
+
const selected = [options.local, options.all, options.node?.length].filter(
|
|
88
|
+
Boolean
|
|
89
|
+
);
|
|
90
|
+
if (['discover', 'health'].includes(command) && selected.length)
|
|
91
|
+
throw new FleetUsageError(
|
|
92
|
+
'Tailscale discovery, --tailnet and --workers always cover all visible computers; --local, --all and --node are not supported.'
|
|
93
|
+
);
|
|
94
|
+
if (selected.length > 1)
|
|
95
|
+
throw new FleetUsageError(
|
|
96
|
+
'Choose --local, --all, or --node, not a combination.'
|
|
97
|
+
);
|
|
98
|
+
validateRosterScope(command, options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function validateRosterScope(command: string, options: CliOptions): void {
|
|
102
|
+
if ((options.local || options.all) && !['doctor', 'repair'].includes(command))
|
|
103
|
+
throw new FleetUsageError(
|
|
104
|
+
'--local and --all are only valid for fleet-status and fleet-repair.'
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function validateRepair(command: string, options: CliOptions): void {
|
|
109
|
+
if (
|
|
110
|
+
options.planHash !== undefined &&
|
|
111
|
+
!/^[a-f0-9]{64}$/.test(options.planHash)
|
|
112
|
+
)
|
|
113
|
+
throw new FleetUsageError(
|
|
114
|
+
'--plan-hash expects the SHA-256 value from a repair preview.'
|
|
115
|
+
);
|
|
116
|
+
const repairFlags =
|
|
117
|
+
options.repair !== undefined ||
|
|
118
|
+
options.script === true ||
|
|
119
|
+
options.planHash !== undefined;
|
|
120
|
+
if (repairFlags && command !== 'repair')
|
|
121
|
+
throw new FleetUsageError(
|
|
122
|
+
'--repair, --script and --plan-hash are only valid for fleet-repair without --list.'
|
|
123
|
+
);
|
|
124
|
+
if (options.script && options.apply)
|
|
125
|
+
throw new FleetUsageError(
|
|
126
|
+
'--script only generates a preview; run a reviewed repair separately with --apply --yes.'
|
|
127
|
+
);
|
|
128
|
+
validateCatalog(command, options);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function validateCatalog(command: string, options: CliOptions): void {
|
|
132
|
+
const filtered =
|
|
133
|
+
options.local ||
|
|
134
|
+
options.all ||
|
|
135
|
+
Boolean(options.node?.length) ||
|
|
136
|
+
options.require !== undefined;
|
|
137
|
+
if (command === 'repairs' && filtered)
|
|
138
|
+
throw new FleetUsageError(
|
|
139
|
+
'fleet-repair --list is a static catalog and does not accept target or capability filters.'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { bundleProbe } from '../transport';
|
|
3
|
+
|
|
4
|
+
export async function probeSource(): Promise<string> {
|
|
5
|
+
if (typeof Bun !== 'undefined') return bundleProbe();
|
|
6
|
+
return readFile(new URL('./probe.mjs', import.meta.url), 'utf8');
|
|
7
|
+
}
|