@mycadet/cli 0.12.10 → 0.13.0-dev.202610171617.9ea363e
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 +13 -0
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/package.json +8 -3
- package/src/__tests__/assistant-client-cloud-auth.test.ts +2 -2
- package/src/__tests__/backup-ops-matching.test.ts +15 -13
- package/src/__tests__/backup-ops.test.ts +105 -0
- package/src/__tests__/backup.test.ts +114 -673
- package/src/__tests__/cloud-login-poll.test.ts +69 -0
- package/src/__tests__/cloud-releases.test.ts +82 -0
- package/src/__tests__/commands-mock.ts +101 -0
- package/src/__tests__/debug.test.ts +17 -91
- package/src/__tests__/devices.test.ts +64 -2
- package/src/__tests__/ensure-binary.test.ts +52 -0
- package/src/__tests__/gateway-token.test.ts +267 -0
- package/src/__tests__/hatch-next-steps.test.ts +38 -0
- package/src/__tests__/host-teleport-docker.test.ts +371 -0
- package/src/__tests__/host-teleport.test.ts +405 -0
- package/src/__tests__/multi-local.test.ts +3 -3
- package/src/__tests__/nginx-ingress.test.ts +587 -36
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/paired-lifecycle.test.ts +2 -2
- package/src/__tests__/ps-cloud-status.test.ts +8 -7
- package/src/__tests__/root-backup-docker.test.ts +294 -0
- package/src/__tests__/root-backup.test.ts +527 -0
- package/src/__tests__/root-restore-command.test.ts +147 -0
- package/src/__tests__/root-signing-key.test.ts +50 -0
- package/src/__tests__/setup.test.ts +17 -7
- package/src/__tests__/shell-mode.test.ts +83 -0
- package/src/__tests__/sleep.test.ts +11 -0
- package/src/__tests__/teleport.test.ts +68 -2996
- package/src/__tests__/tunnel-startup-summary.test.ts +110 -0
- package/src/__tests__/tunnel-stop.test.ts +226 -0
- package/src/__tests__/tunnel.test.ts +22 -4
- package/src/__tests__/upgrade-local.test.ts +21 -11
- package/src/__tests__/upgrade-replay-env.test.ts +2 -2
- package/src/__tests__/upgrade-root-recovery.test.ts +185 -0
- package/src/__tests__/wake.test.ts +4 -1
- package/src/__tests__/windows-lifecycle.test.ts +25 -0
- package/src/commands/backup.ts +44 -399
- package/src/commands/client.ts +3 -3
- package/src/commands/completions.ts +0 -2
- package/src/commands/debug.ts +75 -131
- package/src/commands/devices.ts +117 -47
- package/src/commands/gateway/token.ts +77 -23
- package/src/commands/hatch.ts +10 -52
- package/src/commands/pair.ts +9 -3
- package/src/commands/ps.ts +4 -2
- package/src/commands/restore.ts +77 -444
- package/src/commands/retire.ts +2 -3
- package/src/commands/sleep.ts +15 -2
- package/src/commands/teleport.ts +47 -1559
- package/src/commands/tunnel.ts +91 -11
- package/src/commands/upgrade.ts +84 -157
- package/src/commands/wake.ts +1 -1
- package/src/index.ts +0 -4
- package/src/lib/__tests__/http-client-version.test.ts +29 -0
- package/src/lib/__tests__/runtime-url.test.ts +3 -75
- package/src/lib/__tests__/web-dist.test.ts +27 -1
- package/src/lib/assistant-client.ts +1 -1
- package/src/lib/assistant-config.ts +43 -23
- package/src/lib/backup-ops.ts +37 -251
- package/src/lib/cloud-client.ts +10 -13
- package/src/lib/cloud-login-poll.ts +75 -0
- package/src/lib/cloud-login.ts +5 -2
- package/src/lib/cloud-releases.ts +54 -10
- package/src/lib/cloud-values.ts +1 -1
- package/src/lib/confirm-action.ts +2 -2
- package/src/lib/docker.ts +65 -34
- package/src/lib/ensure-binary.ts +65 -6
- package/src/lib/github-repository.ts +2 -0
- package/src/lib/guardian-token.ts +1 -23
- package/src/lib/hatch-local.ts +18 -52
- package/src/lib/hatch-next-steps.ts +15 -2
- package/src/lib/host-teleport.ts +506 -0
- package/src/lib/http-client.ts +25 -0
- package/src/lib/ingress-config.ts +31 -0
- package/src/lib/live-voice/tunnel.test.ts +1 -1
- package/src/lib/live-voice/tunnel.ts +3 -3
- package/src/lib/local.ts +10 -8
- package/src/lib/nginx-ingress.ts +572 -95
- package/src/lib/process.ts +13 -0
- package/src/lib/root-backup.ts +917 -0
- package/src/lib/root-signing-key.ts +31 -0
- package/src/lib/runtime-url.ts +0 -29
- package/src/lib/teleport-backup.ts +3 -16
- package/src/lib/tunnel.ts +87 -34
- package/src/lib/tunnels/cadet.ts +12 -1
- package/src/lib/tunnels/cloudflare.ts +36 -1
- package/src/lib/tunnels/ngrok.ts +16 -2
- package/src/lib/tunnels/process-lifecycle.ts +26 -0
- package/src/lib/tunnels/tailscale.ts +79 -1
- package/src/lib/upgrade-lifecycle.ts +63 -141
- package/src/lib/web-dist.ts +79 -1
- package/src/lib/xdg-log.ts +11 -0
- package/src/__tests__/bundle-staging.test.ts +0 -268
- package/src/__tests__/unpair.test.ts +0 -160
- package/src/commands/rollback.ts +0 -479
- package/src/commands/unpair.ts +0 -122
- package/src/lib/__tests__/job-polling.test.ts +0 -278
- package/src/lib/__tests__/local-runtime-client.test.ts +0 -666
- package/src/lib/bundle-staging.ts +0 -220
- package/src/lib/job-polling.ts +0 -195
- package/src/lib/local-runtime-client.ts +0 -374
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { afterAll, beforeEach, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import * as backups from "../lib/backup-ops.js";
|
|
3
|
+
import * as docker from "../lib/docker.js";
|
|
4
|
+
import * as lifecycle from "../lib/upgrade-lifecycle.js";
|
|
5
|
+
import * as config from "../lib/assistant-config.js";
|
|
6
|
+
import { mockCommands } from "./commands-mock.js";
|
|
7
|
+
import * as loopback from "../lib/loopback-fetch.js";
|
|
8
|
+
|
|
9
|
+
const capture = spyOn(backups, "createBackup");
|
|
10
|
+
const images = spyOn(docker, "captureImageRefs");
|
|
11
|
+
const pull = spyOn(docker, "pullImage");
|
|
12
|
+
const wake = spyOn(docker, "wakeContainers");
|
|
13
|
+
const save = spyOn(config, "saveAssistantEntry");
|
|
14
|
+
const restore = spyOn(backups, "restoreBackup");
|
|
15
|
+
const sleep = spyOn(docker, "sleepContainers");
|
|
16
|
+
const stop = spyOn(docker, "stopContainers");
|
|
17
|
+
const start = spyOn(docker, "startContainers");
|
|
18
|
+
const fetch = spyOn(loopback, "loopbackSafeFetch");
|
|
19
|
+
const res = docker.dockerResourceNames("recovery");
|
|
20
|
+
const containerOptions = {
|
|
21
|
+
gatewayPort: 7830,
|
|
22
|
+
assistantPort: 3001,
|
|
23
|
+
imageTags: {
|
|
24
|
+
assistant: "assistant:old",
|
|
25
|
+
gateway: "gateway:old",
|
|
26
|
+
"credential-executor": "ces:old",
|
|
27
|
+
},
|
|
28
|
+
instanceName: "recovery",
|
|
29
|
+
res,
|
|
30
|
+
};
|
|
31
|
+
const events: string[] = [];
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
events.length = 0;
|
|
34
|
+
capture.mockReset();
|
|
35
|
+
images.mockReset();
|
|
36
|
+
pull.mockReset();
|
|
37
|
+
wake.mockReset();
|
|
38
|
+
save.mockReset();
|
|
39
|
+
save.mockImplementation(() => {});
|
|
40
|
+
restore.mockReset();
|
|
41
|
+
sleep.mockReset();
|
|
42
|
+
stop.mockReset();
|
|
43
|
+
start.mockReset();
|
|
44
|
+
fetch.mockReset();
|
|
45
|
+
restore.mockImplementation(async () => {
|
|
46
|
+
events.push("restore-roots");
|
|
47
|
+
return true;
|
|
48
|
+
});
|
|
49
|
+
sleep.mockImplementation(async () => {
|
|
50
|
+
events.push("sleep");
|
|
51
|
+
});
|
|
52
|
+
stop.mockImplementation(async () => {
|
|
53
|
+
events.push("remove");
|
|
54
|
+
});
|
|
55
|
+
start.mockImplementation(async () => {
|
|
56
|
+
events.push("start-old-version");
|
|
57
|
+
});
|
|
58
|
+
fetch.mockImplementation(async () => {
|
|
59
|
+
events.push("check-ready");
|
|
60
|
+
return Response.json({ ready: true });
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
afterAll(() => {
|
|
64
|
+
for (const spy of [
|
|
65
|
+
restore,
|
|
66
|
+
sleep,
|
|
67
|
+
stop,
|
|
68
|
+
start,
|
|
69
|
+
fetch,
|
|
70
|
+
capture,
|
|
71
|
+
images,
|
|
72
|
+
pull,
|
|
73
|
+
wake,
|
|
74
|
+
save,
|
|
75
|
+
])
|
|
76
|
+
spy.mockRestore();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("failed-state recovery restores tar roots while stopped before restarting old services", async () => {
|
|
80
|
+
expect(
|
|
81
|
+
await lifecycle.attemptFailedStateRestore({
|
|
82
|
+
runtimeUrl: "http://127.0.0.1:1",
|
|
83
|
+
assistantId: "recovery",
|
|
84
|
+
backupPath: "pre-upgrade.tar.gz",
|
|
85
|
+
backupLabel: "pre-upgrade",
|
|
86
|
+
res,
|
|
87
|
+
containerOptions,
|
|
88
|
+
}),
|
|
89
|
+
).toEqual({ restored: true, ready: true });
|
|
90
|
+
expect(restore).toHaveBeenCalledWith(
|
|
91
|
+
"http://127.0.0.1:1",
|
|
92
|
+
"recovery",
|
|
93
|
+
"pre-upgrade.tar.gz",
|
|
94
|
+
{ resume: false },
|
|
95
|
+
);
|
|
96
|
+
expect(events).toEqual([
|
|
97
|
+
"restore-roots",
|
|
98
|
+
"sleep",
|
|
99
|
+
"remove",
|
|
100
|
+
"start-old-version",
|
|
101
|
+
"check-ready",
|
|
102
|
+
]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("failed root restore does not restart a partial installation", async () => {
|
|
106
|
+
restore.mockResolvedValue(false);
|
|
107
|
+
expect(
|
|
108
|
+
await lifecycle.attemptFailedStateRestore({
|
|
109
|
+
runtimeUrl: "http://127.0.0.1:1",
|
|
110
|
+
assistantId: "recovery",
|
|
111
|
+
backupPath: "pre-upgrade.tar.gz",
|
|
112
|
+
backupLabel: "pre-upgrade",
|
|
113
|
+
res,
|
|
114
|
+
containerOptions,
|
|
115
|
+
}),
|
|
116
|
+
).toEqual({ restored: false, ready: false });
|
|
117
|
+
expect(start).not.toHaveBeenCalled();
|
|
118
|
+
expect(stop).not.toHaveBeenCalled();
|
|
119
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("versioned restore installs roots into stopped target containers before first startup", async () => {
|
|
123
|
+
const commands = mockCommands();
|
|
124
|
+
commands.on(
|
|
125
|
+
"docker",
|
|
126
|
+
(args) => args[0] === "inspect",
|
|
127
|
+
() => ({
|
|
128
|
+
stdout: JSON.stringify(["ACTOR_TOKEN_SIGNING_KEY=" + "01".repeat(32)]),
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
const entry: config.AssistantEntry = {
|
|
132
|
+
assistantId: "recovery",
|
|
133
|
+
cloud: "docker",
|
|
134
|
+
runtimeUrl: "http://127.0.0.1:1",
|
|
135
|
+
};
|
|
136
|
+
capture.mockImplementation(async () => {
|
|
137
|
+
events.push("capture-safety");
|
|
138
|
+
return "/tmp/pre-rollback.tar.gz";
|
|
139
|
+
});
|
|
140
|
+
images.mockResolvedValue({
|
|
141
|
+
assistant: "assistant:current",
|
|
142
|
+
gateway: "gateway:current",
|
|
143
|
+
"credential-executor": "ces:current",
|
|
144
|
+
});
|
|
145
|
+
pull.mockResolvedValue(undefined);
|
|
146
|
+
start.mockImplementation(async (options) => {
|
|
147
|
+
expect(options.createOnly).toBe(true);
|
|
148
|
+
events.push("create-stopped-target");
|
|
149
|
+
});
|
|
150
|
+
wake.mockImplementation(async () => {
|
|
151
|
+
events.push("wake-restored-target");
|
|
152
|
+
});
|
|
153
|
+
fetch.mockImplementation(async (url) => {
|
|
154
|
+
if (String(url).includes("/healthz"))
|
|
155
|
+
return Response.json({ version: "0.12.11" });
|
|
156
|
+
if (String(url).includes("/readyz")) {
|
|
157
|
+
events.push("check-ready");
|
|
158
|
+
return Response.json({ ready: true });
|
|
159
|
+
}
|
|
160
|
+
return Response.json({});
|
|
161
|
+
});
|
|
162
|
+
try {
|
|
163
|
+
await lifecycle.performDockerRollback(entry, {
|
|
164
|
+
targetVersion: "0.12.10",
|
|
165
|
+
backupPath: "/tmp/source.tar.gz",
|
|
166
|
+
});
|
|
167
|
+
expect(restore).toHaveBeenCalledWith(
|
|
168
|
+
entry.runtimeUrl,
|
|
169
|
+
entry.assistantId,
|
|
170
|
+
"/tmp/source.tar.gz",
|
|
171
|
+
{ resume: false },
|
|
172
|
+
);
|
|
173
|
+
expect(events).toEqual([
|
|
174
|
+
"capture-safety",
|
|
175
|
+
"sleep",
|
|
176
|
+
"remove",
|
|
177
|
+
"create-stopped-target",
|
|
178
|
+
"restore-roots",
|
|
179
|
+
"wake-restored-target",
|
|
180
|
+
"check-ready",
|
|
181
|
+
]);
|
|
182
|
+
} finally {
|
|
183
|
+
commands.restore();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
@@ -173,7 +173,7 @@ mock.module("../lib/ingress-config.js", () => ({
|
|
|
173
173
|
}));
|
|
174
174
|
|
|
175
175
|
const ensureTunnelEdgeMock = mock<typeof nginxIngress.ensureTunnelEdge>(
|
|
176
|
-
async () => ({ port: 7840, started: true, includesWebApp: true }),
|
|
176
|
+
async () => ({ port: 7840, started: true, includesWebApp: true, shellMode: "web" }),
|
|
177
177
|
);
|
|
178
178
|
const isIngressRunningMock = mock<typeof nginxIngress.isIngressRunning>(
|
|
179
179
|
() => false,
|
|
@@ -305,6 +305,7 @@ beforeEach(() => {
|
|
|
305
305
|
port: 7840,
|
|
306
306
|
started: true,
|
|
307
307
|
includesWebApp: true,
|
|
308
|
+
shellMode: "web",
|
|
308
309
|
});
|
|
309
310
|
isIngressRunningMock.mockReset();
|
|
310
311
|
isIngressRunningMock.mockReturnValue(false);
|
|
@@ -1073,6 +1074,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1073
1074
|
port: 7840,
|
|
1074
1075
|
started: false,
|
|
1075
1076
|
includesWebApp: true,
|
|
1077
|
+
shellMode: "web",
|
|
1076
1078
|
});
|
|
1077
1079
|
process.argv = ["bun", "cadet", "wake", "docker-1"];
|
|
1078
1080
|
|
|
@@ -1579,6 +1581,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1579
1581
|
port: 7841,
|
|
1580
1582
|
started: false,
|
|
1581
1583
|
includesWebApp: true,
|
|
1584
|
+
shellMode: "web",
|
|
1582
1585
|
});
|
|
1583
1586
|
|
|
1584
1587
|
await wake();
|
|
@@ -227,3 +227,28 @@ test("preserves PID tracking when process termination fails", async () => {
|
|
|
227
227
|
expect(isProcessAlive(pidFile)).toEqual({ alive: true, pid: process.pid });
|
|
228
228
|
rmSync(dir, { recursive: true, force: true });
|
|
229
229
|
});
|
|
230
|
+
|
|
231
|
+
test("graceful-only stop times out without SIGKILL or losing PID identity", async () => {
|
|
232
|
+
const originalKill = process.kill;
|
|
233
|
+
const calls: Array<NodeJS.Signals | number | undefined> = [];
|
|
234
|
+
process.kill = ((_pid: number, signal?: NodeJS.Signals | number) => {
|
|
235
|
+
calls.push(signal);
|
|
236
|
+
return true;
|
|
237
|
+
}) as typeof process.kill;
|
|
238
|
+
try {
|
|
239
|
+
expect(
|
|
240
|
+
await stopProcess(
|
|
241
|
+
4812,
|
|
242
|
+
"backup service",
|
|
243
|
+
0,
|
|
244
|
+
"linux",
|
|
245
|
+
undefined,
|
|
246
|
+
() => "cadet-daemon",
|
|
247
|
+
true,
|
|
248
|
+
),
|
|
249
|
+
).toBe(false);
|
|
250
|
+
expect(calls).toEqual([0, "SIGTERM", 0]);
|
|
251
|
+
} finally {
|
|
252
|
+
process.kill = originalKill;
|
|
253
|
+
}
|
|
254
|
+
});
|