@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.
Files changed (72) hide show
  1. package/README.md +28 -0
  2. package/ansible/macos/manager.yml +3 -0
  3. package/ansible/macos/t3-policy.yml +49 -0
  4. package/ansible/ubuntu/policy.json +1 -0
  5. package/ansible/ubuntu/t3-desktop.yml +19 -0
  6. package/ansible/ubuntu/t3-health.sh +93 -22
  7. package/ansible/ubuntu/t3-idle.py +42 -0
  8. package/ansible/ubuntu/t3-policy.yml +4 -11
  9. package/ansible/ubuntu/tasks/recovery.yml +60 -1
  10. package/ansible/ubuntu/tasks/t3-restart.yml +37 -0
  11. package/ansible/ubuntu/wip-guard.py +121 -0
  12. package/bootstrap/native-t3.ts +22 -11
  13. package/cliproxy/README.md +3 -3
  14. package/cliproxy/scripts/accounts.py +216 -0
  15. package/cliproxy/scripts/checkpoint.py +29 -1
  16. package/cliproxy/scripts/cli.py +1 -1
  17. package/cliproxy/scripts/clientkeys.py +99 -0
  18. package/cliproxy/scripts/lease.py +80 -0
  19. package/cliproxy/scripts/ops +137 -25
  20. package/cliproxy/scripts/restore-verify +15 -0
  21. package/docs/accounts.md +154 -0
  22. package/docs/declared.md +3 -1
  23. package/docs/durability.md +26 -0
  24. package/docs/machine-setup.md +4 -3
  25. package/docs/topology.md +2 -0
  26. package/docs/ubuntu-sync.md +28 -22
  27. package/llms.txt +1 -0
  28. package/mise.toml +3 -1
  29. package/package.json +2 -2
  30. package/schema/fleet.schema.json +66 -0
  31. package/src/accounts/checks.ts +220 -0
  32. package/src/accounts/context.ts +76 -0
  33. package/src/accounts/propagate.ts +62 -0
  34. package/src/accounts/roster.ts +85 -0
  35. package/src/accounts/selection.ts +67 -0
  36. package/src/accounts/sessions.ts +120 -0
  37. package/src/agents/account-relay.ts +176 -26
  38. package/src/agents/launch.ts +84 -5
  39. package/src/agents/native-verify.ts +128 -2
  40. package/src/cli/fleet-accounts.ts +253 -0
  41. package/src/cli/fleet-sync-accounts.ts +58 -0
  42. package/src/cli/fleet-sync-manager.ts +20 -4
  43. package/src/cli/fleet-sync-output.ts +3 -2
  44. package/src/cli/fleet-sync-remote.ts +20 -13
  45. package/src/cli/fleet-sync.ts +36 -0
  46. package/src/cli/register-fleet.ts +35 -0
  47. package/src/cli/registry.ts +13 -0
  48. package/src/definition.ts +85 -3
  49. package/src/ide/definition.ts +86 -0
  50. package/src/ide/ide.ts +43 -0
  51. package/src/ide/index.ts +5 -0
  52. package/src/ide/orchestrate.ts +50 -0
  53. package/src/ide/t3.ts +111 -0
  54. package/src/manager-sync.ts +1 -15
  55. package/src/node-converge-linux.ts +28 -7
  56. package/src/platform/darwin.ts +0 -13
  57. package/src/platform/fallback.ts +0 -8
  58. package/src/platform/linux.ts +0 -10
  59. package/src/platform/t3-policy.ts +33 -6
  60. package/src/platform/t3-service.ts +104 -0
  61. package/src/platform/types.ts +0 -4
  62. package/src/platform/windows.ts +0 -14
  63. package/src/remote-entry.ts +15 -1
  64. package/src/service-auth.ts +15 -5
  65. package/src/t3/accounts.ts +7 -1
  66. package/src/t3/models.ts +133 -0
  67. package/src/t3/pairing.ts +31 -0
  68. package/src/t3/profile.ts +203 -0
  69. package/src/t3/version.ts +119 -0
  70. package/src/t3-linked-check.ts +1 -1
  71. package/src/transport.ts +3 -2
  72. 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
+ }
@@ -103,7 +103,7 @@ function linkedEvidence(
103
103
  };
104
104
  }
105
105
 
106
- async function observedIdentity(
106
+ export async function observedIdentity(
107
107
  node: FleetNode,
108
108
  run: Runner
109
109
  ): Promise<string | undefined> {
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' && options.expected
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(