@crvouga/dev-fleet 0.2.0 → 0.3.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/README.md +28 -0
- package/ansible/macos/manager.yml +3 -0
- package/ansible/macos/t3-policy.yml +49 -0
- package/ansible/ubuntu/policy.json +1 -0
- package/ansible/ubuntu/t3-desktop.yml +19 -0
- package/ansible/ubuntu/t3-health.sh +93 -22
- package/ansible/ubuntu/t3-idle.py +42 -0
- package/ansible/ubuntu/t3-policy.yml +4 -11
- package/ansible/ubuntu/tasks/recovery.yml +60 -1
- package/ansible/ubuntu/tasks/t3-restart.yml +37 -0
- package/ansible/ubuntu/wip-guard.py +121 -0
- package/bootstrap/native-t3.ts +22 -11
- package/cliproxy/README.md +3 -3
- package/cliproxy/scripts/accounts.py +216 -0
- package/cliproxy/scripts/checkpoint.py +29 -1
- package/cliproxy/scripts/cli.py +1 -1
- package/cliproxy/scripts/clientkeys.py +99 -0
- package/cliproxy/scripts/lease.py +80 -0
- package/cliproxy/scripts/ops +137 -25
- package/cliproxy/scripts/restore-verify +15 -0
- package/docs/accounts.md +154 -0
- package/docs/declared.md +3 -1
- package/docs/durability.md +26 -0
- package/docs/machine-setup.md +4 -3
- package/docs/topology.md +2 -0
- package/docs/ubuntu-sync.md +28 -22
- package/llms.txt +1 -0
- package/mise.toml +3 -1
- package/package.json +2 -2
- package/schema/fleet.schema.json +66 -0
- package/src/accounts/checks.ts +220 -0
- package/src/accounts/context.ts +76 -0
- package/src/accounts/propagate.ts +62 -0
- package/src/accounts/roster.ts +85 -0
- package/src/accounts/selection.ts +67 -0
- package/src/accounts/sessions.ts +120 -0
- package/src/agents/account-relay.ts +176 -26
- package/src/agents/launch.ts +84 -5
- package/src/agents/native-verify.ts +128 -2
- package/src/cli/fleet-accounts.ts +253 -0
- package/src/cli/fleet-sync-accounts.ts +58 -0
- package/src/cli/fleet-sync-manager.ts +20 -4
- package/src/cli/fleet-sync-output.ts +3 -2
- package/src/cli/fleet-sync-remote.ts +20 -13
- package/src/cli/fleet-sync.ts +36 -0
- package/src/cli/register-fleet.ts +35 -0
- package/src/cli/registry.ts +13 -0
- package/src/definition.ts +85 -3
- package/src/ide/definition.ts +86 -0
- package/src/ide/ide.ts +43 -0
- package/src/ide/index.ts +5 -0
- package/src/ide/orchestrate.ts +50 -0
- package/src/ide/t3.ts +111 -0
- package/src/manager-sync.ts +1 -15
- package/src/node-converge-linux.ts +28 -7
- package/src/platform/darwin.ts +0 -13
- package/src/platform/fallback.ts +0 -8
- package/src/platform/linux.ts +0 -10
- package/src/platform/t3-policy.ts +33 -6
- package/src/platform/t3-service.ts +104 -0
- package/src/platform/types.ts +0 -4
- package/src/platform/windows.ts +0 -14
- package/src/remote-entry.ts +15 -1
- package/src/service-auth.ts +15 -5
- package/src/t3/accounts.ts +7 -1
- package/src/t3/models.ts +133 -0
- package/src/t3/pairing.ts +31 -0
- package/src/t3/profile.ts +203 -0
- package/src/t3/version.ts +119 -0
- package/src/t3-linked-check.ts +1 -1
- package/src/transport.ts +3 -2
- package/src/worker-host-checks.ts +36 -13
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { sshArgs } from '../ssh';
|
|
4
|
+
import type { Check, FleetNode, Runner } from '../types';
|
|
5
|
+
|
|
6
|
+
/** The T3 release every machine runs: `t3_version` in mise.toml. */
|
|
7
|
+
export async function pinnedT3Version(workspace: string): Promise<string> {
|
|
8
|
+
const toml = await readFile(join(workspace, 'mise.toml'), 'utf8');
|
|
9
|
+
const version = /^t3_version = "([^"]+)"$/m.exec(toml)?.[1];
|
|
10
|
+
if (!version) throw new Error('mise.toml pins no T3 version.');
|
|
11
|
+
return version;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Where T3 notes the version it last started as, under its home. */
|
|
15
|
+
export const STARTED_VERSION = 'userdata/desktop-last-started-version';
|
|
16
|
+
|
|
17
|
+
const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?$/;
|
|
18
|
+
|
|
19
|
+
/** `t3:version`: the T3 a machine runs is the one the fleet pins, so the
|
|
20
|
+
* manager and the workers it drives never run different releases.
|
|
21
|
+
*/
|
|
22
|
+
export function versionCheck(
|
|
23
|
+
running: string | null,
|
|
24
|
+
pinned: string,
|
|
25
|
+
where: string,
|
|
26
|
+
install: string
|
|
27
|
+
): Check {
|
|
28
|
+
const observed = running?.trim() ?? '';
|
|
29
|
+
if (!VERSION.test(observed))
|
|
30
|
+
return {
|
|
31
|
+
id: 't3:version',
|
|
32
|
+
status: 'warn',
|
|
33
|
+
summary: 'T3_VERSION_UNKNOWN',
|
|
34
|
+
remedy: {
|
|
35
|
+
action: `T3 on ${where} has not recorded the version it runs. Start T3 there once, then run fleet-sync.`,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
if (observed !== pinned)
|
|
39
|
+
return {
|
|
40
|
+
id: 't3:version',
|
|
41
|
+
status: 'warn',
|
|
42
|
+
summary: 'T3_VERSION_SKEW',
|
|
43
|
+
remedy: {
|
|
44
|
+
action: `T3 on ${where} runs ${observed}; the fleet pins ${pinned}. ${install}`,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
id: 't3:version',
|
|
49
|
+
status: 'pass',
|
|
50
|
+
summary: `T3 on ${where} runs ${pinned}, the release every machine runs.`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function managerVersion(
|
|
55
|
+
t3Home: string,
|
|
56
|
+
workspace: string
|
|
57
|
+
): Promise<Check> {
|
|
58
|
+
const running = await readFile(join(t3Home, STARTED_VERSION), 'utf8').catch(
|
|
59
|
+
() => null
|
|
60
|
+
);
|
|
61
|
+
return versionCheck(
|
|
62
|
+
running,
|
|
63
|
+
await pinnedT3Version(workspace),
|
|
64
|
+
'the manager',
|
|
65
|
+
'Install that release of T3 Code on the manager. With the fleet policy staged (ansible-playbook -K ansible/macos/manager.yml) it no longer updates itself.'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function workerVersion(
|
|
70
|
+
node: FleetNode,
|
|
71
|
+
run: Runner,
|
|
72
|
+
workspace: string
|
|
73
|
+
): Promise<Check> {
|
|
74
|
+
const result = await run(
|
|
75
|
+
[...sshArgs(node), `cat "$HOME/.t3/${STARTED_VERSION}"`],
|
|
76
|
+
{ timeoutMs: 15_000 }
|
|
77
|
+
);
|
|
78
|
+
return versionCheck(
|
|
79
|
+
result.ok ? result.stdout : null,
|
|
80
|
+
await pinnedT3Version(workspace),
|
|
81
|
+
`worker ${node.id}`,
|
|
82
|
+
'Run fleet-sync for that worker: it installs the pinned desktop app and restarts T3 when its policy changed.'
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Where T3 on macOS reads its administrator policy. */
|
|
87
|
+
export const MACOS_POLICY = '/Library/Application Support/T3Code/policy.json';
|
|
88
|
+
|
|
89
|
+
/** `t3:updates`: T3's own updater is off by the fleet's policy, so it changes
|
|
90
|
+
* only when the pin does.
|
|
91
|
+
*/
|
|
92
|
+
export async function updatesCheck(
|
|
93
|
+
policyPath: string,
|
|
94
|
+
stage: string
|
|
95
|
+
): Promise<Check> {
|
|
96
|
+
let mode: unknown;
|
|
97
|
+
try {
|
|
98
|
+
const policy = JSON.parse(await readFile(policyPath, 'utf8')) as {
|
|
99
|
+
limits?: { updates?: { mode?: unknown } };
|
|
100
|
+
};
|
|
101
|
+
mode = policy.limits?.updates?.mode;
|
|
102
|
+
} catch {
|
|
103
|
+
mode = undefined;
|
|
104
|
+
}
|
|
105
|
+
return mode === 'off'
|
|
106
|
+
? {
|
|
107
|
+
id: 't3:updates',
|
|
108
|
+
status: 'pass',
|
|
109
|
+
summary: 'T3’s own updater is off by the fleet’s policy.',
|
|
110
|
+
}
|
|
111
|
+
: {
|
|
112
|
+
id: 't3:updates',
|
|
113
|
+
status: 'warn',
|
|
114
|
+
summary: 'T3_UPDATES_UNMANAGED',
|
|
115
|
+
remedy: {
|
|
116
|
+
action: `T3 on this machine may update itself away from the pinned release. Stage the fleet's policy once, as an administrator: ${stage}`,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
package/src/t3-linked-check.ts
CHANGED
package/src/transport.ts
CHANGED
|
@@ -21,7 +21,7 @@ export async function bundleProbe(): Promise<string> {
|
|
|
21
21
|
export interface RemoteOptions {
|
|
22
22
|
source: string;
|
|
23
23
|
timeoutMs: number;
|
|
24
|
-
action: 'probe' | 'sync' | 'repair' | 'configure';
|
|
24
|
+
action: 'probe' | 'sync' | 'repair' | 'configure' | 'accounts';
|
|
25
25
|
configuration?: ConfigurationRequest;
|
|
26
26
|
repairs?: AutomatedRepair[];
|
|
27
27
|
expected?: Fingerprint;
|
|
@@ -41,7 +41,8 @@ export async function inspectRemote(
|
|
|
41
41
|
return { node: node.id, checks: [sshFailure(preflight)] };
|
|
42
42
|
}
|
|
43
43
|
const target =
|
|
44
|
-
options.action === 'probe'
|
|
44
|
+
(options.action === 'probe' || options.action === 'accounts') &&
|
|
45
|
+
options.expected
|
|
45
46
|
? await managedProbeTarget(node, options.expected.head, run)
|
|
46
47
|
: { node, checks: [] };
|
|
47
48
|
const command = remoteCommand(target.node, {
|
|
@@ -4,6 +4,7 @@ import { verifyUbuntuBoot } from './platform/ubuntu-verify';
|
|
|
4
4
|
import { verifyResourceSafety } from './platform/resource-verify';
|
|
5
5
|
import policy from '../ansible/ubuntu/policy.json';
|
|
6
6
|
import { userInfo } from 'node:os';
|
|
7
|
+
import { T3_HEALTH_SCRIPT, runningVersionCheck } from './platform/t3-service';
|
|
7
8
|
import type { Check, Runner } from './types';
|
|
8
9
|
|
|
9
10
|
function check(id: string, ok: boolean, summary: string): Check {
|
|
@@ -120,18 +121,13 @@ async function recoveryChecks(run: Runner): Promise<Check[]> {
|
|
|
120
121
|
)
|
|
121
122
|
);
|
|
122
123
|
checks.push(await t3HealthCheck(run));
|
|
124
|
+
checks.push(await runningVersionCheck(run, userInfo().uid));
|
|
123
125
|
const renewal = await run([
|
|
124
126
|
'systemctl',
|
|
125
127
|
'--user',
|
|
126
128
|
'is-active',
|
|
127
129
|
'personal-worker-auth.timer',
|
|
128
130
|
]);
|
|
129
|
-
const recovery = await run([
|
|
130
|
-
'systemctl',
|
|
131
|
-
'--user',
|
|
132
|
-
'is-active',
|
|
133
|
-
'personal-worker-recovery.timer',
|
|
134
|
-
]);
|
|
135
131
|
checks.push(
|
|
136
132
|
check(
|
|
137
133
|
'credential-renewal',
|
|
@@ -139,17 +135,44 @@ async function recoveryChecks(run: Runner): Promise<Check[]> {
|
|
|
139
135
|
'Worker credential renewal timer is active.'
|
|
140
136
|
)
|
|
141
137
|
);
|
|
142
|
-
checks.push(
|
|
143
|
-
check(
|
|
144
|
-
'service-recovery',
|
|
145
|
-
recovery.ok,
|
|
146
|
-
'Worker service recovery timer is active.'
|
|
147
|
-
)
|
|
148
|
-
);
|
|
138
|
+
checks.push(await serviceRecoveryCheck(run));
|
|
149
139
|
checks.push(await powerRecoveryCheck(run));
|
|
150
140
|
return checks;
|
|
151
141
|
}
|
|
152
142
|
|
|
143
|
+
/** The recovery timer is active, the script its unit runs exists, and the
|
|
144
|
+
* last run of the unit succeeded: a watchdog that fails on every tick is not
|
|
145
|
+
* recovery. */
|
|
146
|
+
export async function serviceRecoveryCheck(run: Runner): Promise<Check> {
|
|
147
|
+
const timer = await run([
|
|
148
|
+
'systemctl',
|
|
149
|
+
'--user',
|
|
150
|
+
'is-active',
|
|
151
|
+
'personal-worker-recovery.timer',
|
|
152
|
+
]);
|
|
153
|
+
const script = await run(['test', '-r', T3_HEALTH_SCRIPT]);
|
|
154
|
+
const unit = await run([
|
|
155
|
+
'systemctl',
|
|
156
|
+
'--user',
|
|
157
|
+
'show',
|
|
158
|
+
'personal-worker-recovery.service',
|
|
159
|
+
'--property=Result',
|
|
160
|
+
]);
|
|
161
|
+
const result = /^Result=(.*)$/m.exec(unit.stdout)?.[1] ?? 'unknown';
|
|
162
|
+
const summary = !timer.ok
|
|
163
|
+
? 'WORKER_RECOVERY_TIMER_INACTIVE'
|
|
164
|
+
: !script.ok
|
|
165
|
+
? 'WORKER_RECOVERY_SCRIPT_MISSING'
|
|
166
|
+
: !unit.ok || result !== 'success'
|
|
167
|
+
? `WORKER_RECOVERY_LAST_RUN_FAILED (${/^[\w-]+$/.test(result) ? result : 'unknown'})`
|
|
168
|
+
: 'Worker service recovery timer is active and its last run succeeded.';
|
|
169
|
+
return check(
|
|
170
|
+
'service-recovery',
|
|
171
|
+
timer.ok && script.ok && unit.ok && result === 'success',
|
|
172
|
+
summary
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
153
176
|
/** The server answers its local API now, not merely that a process exists. */
|
|
154
177
|
async function t3HealthCheck(run: Runner): Promise<Check> {
|
|
155
178
|
const probe = await run(
|