@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
package/src/commands/tunnel.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
formatAssistantReference,
|
|
12
12
|
loadAllAssistants,
|
|
13
|
+
readRootProcesses,
|
|
13
14
|
resolveTargetAssistant,
|
|
14
15
|
type AssistantEntry,
|
|
15
16
|
} from "../lib/assistant-config";
|
|
@@ -19,9 +20,11 @@ import {
|
|
|
19
20
|
getDefaultWorkspaceDir,
|
|
20
21
|
isLocalContainerEntry,
|
|
21
22
|
parseGatewayPortFromEntryUrls,
|
|
23
|
+
saveShellMode,
|
|
22
24
|
} from "../lib/ingress-config.js";
|
|
23
25
|
import {
|
|
24
26
|
ensureTunnelEdge,
|
|
27
|
+
formatEdgeMode,
|
|
25
28
|
getNginxVersion,
|
|
26
29
|
type TunnelEdge,
|
|
27
30
|
} from "../lib/nginx-ingress.js";
|
|
@@ -37,6 +40,8 @@ import {
|
|
|
37
40
|
hasWebhookIntegrationsConfigured,
|
|
38
41
|
providerSupportsReservedDomain,
|
|
39
42
|
runProviderTunnel,
|
|
43
|
+
stopTunnelProcess,
|
|
44
|
+
summarizeStartupFailure,
|
|
40
45
|
} from "../lib/tunnel.js";
|
|
41
46
|
|
|
42
47
|
// The accepted `--provider` values ARE the shared registry, so validation
|
|
@@ -48,9 +53,10 @@ interface TunnelArgs {
|
|
|
48
53
|
domain: string | null;
|
|
49
54
|
clearDomain: boolean;
|
|
50
55
|
foreground: boolean;
|
|
56
|
+
shell: "web" | "app" | null;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
const FLAGS_WITH_VALUES = ["--provider", "--domain"] as const;
|
|
59
|
+
const FLAGS_WITH_VALUES = ["--provider", "--domain", "--shell"] as const;
|
|
54
60
|
|
|
55
61
|
function parseArgs(): TunnelArgs {
|
|
56
62
|
const args = process.argv.slice(3);
|
|
@@ -58,18 +64,17 @@ function parseArgs(): TunnelArgs {
|
|
|
58
64
|
let domain: string | null = null;
|
|
59
65
|
let clearDomain = false;
|
|
60
66
|
let foreground = false;
|
|
67
|
+
let shell: "web" | "app" | null = null;
|
|
61
68
|
|
|
62
69
|
for (let i = 0; i < args.length; i++) {
|
|
63
70
|
const arg = args[i];
|
|
64
71
|
if (arg === "--help" || arg === "-h") {
|
|
65
72
|
console.log(
|
|
66
|
-
"Usage: cadet tunnel [<name>] --provider <provider> [options]",
|
|
73
|
+
"Usage: cadet tunnel [<name>] --provider <provider> [options]\n cadet tunnel stop [<name>]",
|
|
67
74
|
);
|
|
68
75
|
console.log("");
|
|
69
76
|
console.log("Front a locally running assistant with an HTTPS tunnel.");
|
|
70
|
-
console.log(
|
|
71
|
-
"The tunnel URL is saved to the workspace config as the ingress base URL.",
|
|
72
|
-
);
|
|
77
|
+
console.log("The tunnel URL is saved to the workspace config as the ingress base URL.");
|
|
73
78
|
console.log("");
|
|
74
79
|
console.log(
|
|
75
80
|
"How far that URL reaches depends on the provider; omit --provider for",
|
|
@@ -111,6 +116,19 @@ function parseArgs(): TunnelArgs {
|
|
|
111
116
|
console.log(
|
|
112
117
|
" tunnel without one. Cannot be combined with --domain.",
|
|
113
118
|
);
|
|
119
|
+
console.log(
|
|
120
|
+
" --shell <web|app> Which client bundle the edge serves as the outer shell:",
|
|
121
|
+
);
|
|
122
|
+
console.log(
|
|
123
|
+
" web (default) serves the web SPA directly; app serves the",
|
|
124
|
+
);
|
|
125
|
+
console.log(
|
|
126
|
+
" app bundle as the outer shell and iframes the web UI from",
|
|
127
|
+
);
|
|
128
|
+
console.log(
|
|
129
|
+
" /web/assistant/. Saved to the workspace config so `cadet wake`",
|
|
130
|
+
);
|
|
131
|
+
console.log(" restores it; changing it restarts the nginx edge.");
|
|
114
132
|
console.log(
|
|
115
133
|
` --foreground Stream the tunnel's logs to this terminal. By default the tunnel`,
|
|
116
134
|
);
|
|
@@ -121,7 +139,7 @@ function parseArgs(): TunnelArgs {
|
|
|
121
139
|
` URL, and logs stream to tunnel.log in the CLI log directory. A background`,
|
|
122
140
|
);
|
|
123
141
|
console.log(
|
|
124
|
-
" tunnel stops with `
|
|
142
|
+
" tunnel stops with `cadet tunnel stop`, except when it adopted an already-running",
|
|
125
143
|
);
|
|
126
144
|
console.log(
|
|
127
145
|
" tunnel (started outside this command): killing the supervisor then",
|
|
@@ -188,6 +206,20 @@ function parseArgs(): TunnelArgs {
|
|
|
188
206
|
i++;
|
|
189
207
|
} else if (arg === "--clear-domain") {
|
|
190
208
|
clearDomain = true;
|
|
209
|
+
} else if (arg === "--shell") {
|
|
210
|
+
const next = args[i + 1];
|
|
211
|
+
if (!next || next.startsWith("-")) {
|
|
212
|
+
console.error("Error: --shell requires a value: web or app.");
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
if (next !== "web" && next !== "app") {
|
|
216
|
+
console.error(
|
|
217
|
+
`Error: unknown shell '${next}'. Valid shells: web, app.`,
|
|
218
|
+
);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
shell = next;
|
|
222
|
+
i++;
|
|
191
223
|
} else if (arg === "--foreground") {
|
|
192
224
|
foreground = true;
|
|
193
225
|
} else if (arg.startsWith("-")) {
|
|
@@ -227,7 +259,7 @@ function parseArgs(): TunnelArgs {
|
|
|
227
259
|
process.exit(1);
|
|
228
260
|
}
|
|
229
261
|
|
|
230
|
-
return { assistantName, provider, domain, clearDomain, foreground };
|
|
262
|
+
return { assistantName, provider, domain, clearDomain, foreground, shell };
|
|
231
263
|
}
|
|
232
264
|
|
|
233
265
|
/** A tunnelable assistant plus the gateway port and workspace the edge fronts. */
|
|
@@ -394,9 +426,17 @@ async function spawnDetachedTunnel(): Promise<void> {
|
|
|
394
426
|
});
|
|
395
427
|
|
|
396
428
|
if (exitCode !== undefined) {
|
|
429
|
+
let logText = "";
|
|
430
|
+
try {
|
|
431
|
+
logText = readFileSync(logPath, "utf-8");
|
|
432
|
+
} catch {
|
|
433
|
+
// Log file not readable ā the summary falls back to none.
|
|
434
|
+
}
|
|
435
|
+
const reason = summarizeStartupFailure(logText);
|
|
397
436
|
console.error(
|
|
398
|
-
`Error: tunnel process exited during startup${
|
|
437
|
+
`Error (${exitCode ?? "unknown"}): tunnel process exited during startup${reason ? `: ${reason}` : "."}`,
|
|
399
438
|
);
|
|
439
|
+
console.error(`Logs: ${logPath}`);
|
|
400
440
|
process.exit(1);
|
|
401
441
|
}
|
|
402
442
|
|
|
@@ -411,13 +451,41 @@ async function spawnDetachedTunnel(): Promise<void> {
|
|
|
411
451
|
if (adopted) {
|
|
412
452
|
console.log(adoptedTunnelStopNotice(child.pid));
|
|
413
453
|
} else {
|
|
414
|
-
console.log(
|
|
454
|
+
console.log("Stop with: cadet tunnel stop");
|
|
415
455
|
}
|
|
416
456
|
}
|
|
417
457
|
|
|
418
458
|
export const tunnel = Object.assign(
|
|
419
459
|
async function tunnel(): Promise<void> {
|
|
420
|
-
|
|
460
|
+
if (process.argv[3] === "stop") {
|
|
461
|
+
const args = process.argv.slice(4);
|
|
462
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
463
|
+
console.log("Usage: cadet tunnel stop [<name>]");
|
|
464
|
+
console.log(
|
|
465
|
+
"Stop the recorded Cadet-managed tunnel, keeping nginx and the saved domain.",
|
|
466
|
+
);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (args.some((arg) => arg.startsWith("-")))
|
|
470
|
+
throw new Error("Usage: cadet tunnel stop [<name>]");
|
|
471
|
+
const { entry, workspaceDir } = resolveLocalTunnelTarget(
|
|
472
|
+
args.join(" ") || null,
|
|
473
|
+
);
|
|
474
|
+
if (!readRootProcesses().tunnel) {
|
|
475
|
+
console.log(
|
|
476
|
+
"No Cadet-managed tunnel is recorded. Externally started tunnels must be stopped directly.",
|
|
477
|
+
);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const stopped = await stopTunnelProcess(entry.assistantId, workspaceDir);
|
|
481
|
+
console.log(
|
|
482
|
+
stopped
|
|
483
|
+
? "Tunnel stopped. nginx and the saved domain are unchanged."
|
|
484
|
+
: "No running tunnel found; cleared stale tunnel state.",
|
|
485
|
+
);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const { assistantName, provider, domain, clearDomain, foreground, shell } =
|
|
421
489
|
parseArgs();
|
|
422
490
|
|
|
423
491
|
if (!foreground) {
|
|
@@ -435,6 +503,15 @@ export const tunnel = Object.assign(
|
|
|
435
503
|
const preserveIngressUrl =
|
|
436
504
|
provider === "tailscale" && guardTailnetOnlyWebhookIngress(workspaceDir);
|
|
437
505
|
|
|
506
|
+
// Persist the shell mode like the reserved domain: parsed once, saved to
|
|
507
|
+
// the CLI lockfile beside the nginx edge record it configures, and read
|
|
508
|
+
// back by every later edge restore (wake/upgrade), so the choice outlives
|
|
509
|
+
// the command that made it.
|
|
510
|
+
if (shell) {
|
|
511
|
+
saveShellMode(shell);
|
|
512
|
+
console.log(`Shell mode saved: ${shell}.`);
|
|
513
|
+
}
|
|
514
|
+
|
|
438
515
|
if (clearDomain) {
|
|
439
516
|
clearReservedDomain();
|
|
440
517
|
console.log("Cleared the saved reserved domain.");
|
|
@@ -475,7 +552,10 @@ export const tunnel = Object.assign(
|
|
|
475
552
|
}
|
|
476
553
|
|
|
477
554
|
console.log(
|
|
478
|
-
`${edge.started ? "Started" : "Reusing"} the nginx edge on 127.0.0.1:${edge.port}
|
|
555
|
+
`${edge.started ? "Started" : "Reusing"} the nginx edge on 127.0.0.1:${edge.port} (${formatEdgeMode(
|
|
556
|
+
edge.includesWebApp,
|
|
557
|
+
edge.shellMode,
|
|
558
|
+
)}).`,
|
|
479
559
|
);
|
|
480
560
|
|
|
481
561
|
const baseTunnelOpts = {
|
package/src/commands/upgrade.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
pullImage,
|
|
23
23
|
startContainers,
|
|
24
24
|
stopContainers,
|
|
25
|
+
sleepContainers,
|
|
25
26
|
} from "../lib/docker";
|
|
26
27
|
import {
|
|
27
28
|
fetchLatestStableVersion,
|
|
@@ -67,6 +68,7 @@ import {
|
|
|
67
68
|
stripVersionPrefix,
|
|
68
69
|
versionsEqual,
|
|
69
70
|
} from "../lib/version-compat.js";
|
|
71
|
+
import { fetchCurrentVersion } from "../lib/http-client.js";
|
|
70
72
|
import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
|
|
71
73
|
import {
|
|
72
74
|
generateLocalSigningKey,
|
|
@@ -76,7 +78,10 @@ import {
|
|
|
76
78
|
startLocalDaemon,
|
|
77
79
|
stopLocalProcesses,
|
|
78
80
|
} from "../lib/local.js";
|
|
79
|
-
import {
|
|
81
|
+
import {
|
|
82
|
+
restoreContainerTunnelEdge,
|
|
83
|
+
restoreTunnelEdgeAndAutoTunnel,
|
|
84
|
+
} from "../lib/tunnel.js";
|
|
80
85
|
import {
|
|
81
86
|
leaseGuardianToken,
|
|
82
87
|
resetGuardianBootstrap,
|
|
@@ -114,7 +119,7 @@ function parseArgs(): UpgradeArgs {
|
|
|
114
119
|
console.log("Usage: cadet upgrade [<name>] [options]");
|
|
115
120
|
console.log("");
|
|
116
121
|
console.log("Upgrade an assistant to a newer version.");
|
|
117
|
-
console.log("To roll back to a previous version, use `cadet
|
|
122
|
+
console.log("To roll back to a previous version, use `cadet restore` with a backup.");
|
|
118
123
|
console.log("");
|
|
119
124
|
console.log("Arguments:");
|
|
120
125
|
console.log(
|
|
@@ -291,7 +296,7 @@ async function upgradeDocker(
|
|
|
291
296
|
}
|
|
292
297
|
|
|
293
298
|
// Reject downgrades ā `cadet upgrade` only handles forward version changes.
|
|
294
|
-
//
|
|
299
|
+
// Downgrades require a versioned backup restore.
|
|
295
300
|
if (!currentVersion && versionTag) {
|
|
296
301
|
console.warn(
|
|
297
302
|
"ā ļø Could not determine current version from health endpoint ā skipping version-direction check.\n",
|
|
@@ -300,7 +305,7 @@ async function upgradeDocker(
|
|
|
300
305
|
if (currentVersion && versionTag) {
|
|
301
306
|
const cmp = compareVersions(versionTag, currentVersion);
|
|
302
307
|
if (cmp !== null && cmp < 0) {
|
|
303
|
-
const msg = `Cannot upgrade to an older version (${versionTag} < ${currentVersion}). Use \`cadet
|
|
308
|
+
const msg = `Cannot upgrade to an older version (${versionTag} < ${currentVersion}). Use \`cadet restore --from <backup-path> --version ${versionTag}\` instead.`;
|
|
304
309
|
console.error(msg);
|
|
305
310
|
emitCliError("VERSION_DIRECTION", msg);
|
|
306
311
|
process.exit(1);
|
|
@@ -342,22 +347,6 @@ async function upgradeDocker(
|
|
|
342
347
|
);
|
|
343
348
|
}
|
|
344
349
|
|
|
345
|
-
// Persist rollback state to lockfile BEFORE any destructive changes.
|
|
346
|
-
// This enables the `cadet rollback` command to restore the previous version.
|
|
347
|
-
if (entry.containerInfo) {
|
|
348
|
-
const rollbackEntry: AssistantEntry = {
|
|
349
|
-
...entry,
|
|
350
|
-
previousContainerInfo: { ...entry.containerInfo },
|
|
351
|
-
previousVersion: currentVersion,
|
|
352
|
-
previousDbMigrationVersion: preMigrationState.dbVersion,
|
|
353
|
-
previousWorkspaceMigrationId: preMigrationState.lastWorkspaceMigrationId,
|
|
354
|
-
};
|
|
355
|
-
saveAssistantEntry(rollbackEntry);
|
|
356
|
-
if (currentVersion) {
|
|
357
|
-
console.log(` Saved rollback state: ${currentVersion}\n`);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
350
|
// Record version transition start in workspace git history
|
|
362
351
|
await commitWorkspaceViaGateway(
|
|
363
352
|
entry.runtimeUrl,
|
|
@@ -437,7 +426,7 @@ async function upgradeDocker(
|
|
|
437
426
|
const assistantPort =
|
|
438
427
|
entry.containerInfo?.assistantPort ?? ASSISTANT_INTERNAL_PORT;
|
|
439
428
|
|
|
440
|
-
//
|
|
429
|
+
// Capture complete persistent roots before replacing any services.
|
|
441
430
|
await broadcastUpgradeEvent(
|
|
442
431
|
entry.runtimeUrl,
|
|
443
432
|
entry.assistantId,
|
|
@@ -453,20 +442,7 @@ async function upgradeDocker(
|
|
|
453
442
|
// Clean up old pre-upgrade backups, keep last 3
|
|
454
443
|
pruneOldBackups(entry.assistantId, 3);
|
|
455
444
|
} else {
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Persist the backup path so `cadet rollback` can restore the exact backup
|
|
460
|
-
// created for this upgrade attempt ā never a stale backup from a prior cycle.
|
|
461
|
-
// Re-read the entry to pick up the rollback state saved earlier.
|
|
462
|
-
{
|
|
463
|
-
const current = findAssistantByName(entry.assistantId);
|
|
464
|
-
if (current) {
|
|
465
|
-
saveAssistantEntry({
|
|
466
|
-
...current,
|
|
467
|
-
preUpgradeBackupPath: backupPath ?? undefined,
|
|
468
|
-
});
|
|
469
|
-
}
|
|
445
|
+
throw new Error("Pre-upgrade root backup failed; upgrade aborted before replacing services.");
|
|
470
446
|
}
|
|
471
447
|
|
|
472
448
|
await broadcastUpgradeEvent(
|
|
@@ -476,6 +452,7 @@ async function upgradeDocker(
|
|
|
476
452
|
);
|
|
477
453
|
|
|
478
454
|
console.log("š Stopping existing containers...");
|
|
455
|
+
await sleepContainers(res);
|
|
479
456
|
await stopContainers(res);
|
|
480
457
|
console.log("ā
Containers stopped\n");
|
|
481
458
|
|
|
@@ -509,13 +486,10 @@ async function upgradeDocker(
|
|
|
509
486
|
cesImage: imageTags["credential-executor"],
|
|
510
487
|
assistantPort,
|
|
511
488
|
},
|
|
512
|
-
previousContainerInfo: entry.containerInfo,
|
|
513
|
-
previousDbMigrationVersion: preMigrationState.dbVersion,
|
|
514
|
-
previousWorkspaceMigrationId: preMigrationState.lastWorkspaceMigrationId,
|
|
515
|
-
// Preserve the backup path so `cadet rollback` can restore it later
|
|
516
|
-
preUpgradeBackupPath: backupPath ?? undefined,
|
|
517
489
|
};
|
|
518
490
|
saveAssistantEntry(updatedEntry);
|
|
491
|
+
// Refresh the host-served web bundle before clients reload on completion.
|
|
492
|
+
await restoreContainerTunnelEdge(updatedEntry);
|
|
519
493
|
|
|
520
494
|
// Notify clients on the new service group that the upgrade succeeded.
|
|
521
495
|
await broadcastUpgradeEvent(
|
|
@@ -581,6 +555,7 @@ async function upgradeDocker(
|
|
|
581
555
|
);
|
|
582
556
|
}
|
|
583
557
|
|
|
558
|
+
await sleepContainers(res);
|
|
584
559
|
await stopContainers(res);
|
|
585
560
|
|
|
586
561
|
await startContainers(
|
|
@@ -647,14 +622,12 @@ async function upgradeDocker(
|
|
|
647
622
|
entry.runtimeUrl,
|
|
648
623
|
entry.assistantId,
|
|
649
624
|
backupPath,
|
|
650
|
-
{
|
|
625
|
+
{},
|
|
651
626
|
);
|
|
652
627
|
if (restored) {
|
|
653
628
|
console.log(" ā
Data restored successfully\n");
|
|
654
629
|
} else {
|
|
655
|
-
|
|
656
|
-
" ā ļø Data restore failed (rollback continues without data restoration)\n",
|
|
657
|
-
);
|
|
630
|
+
throw new Error(`Root restore failed; rollback recovery is incomplete. Backup: ${backupPath}`);
|
|
658
631
|
}
|
|
659
632
|
} else {
|
|
660
633
|
console.log(
|
|
@@ -678,11 +651,6 @@ async function upgradeDocker(
|
|
|
678
651
|
previousImageRefs["credential-executor"],
|
|
679
652
|
assistantPort,
|
|
680
653
|
},
|
|
681
|
-
previousContainerInfo: undefined,
|
|
682
|
-
previousDbMigrationVersion: undefined,
|
|
683
|
-
previousWorkspaceMigrationId: undefined,
|
|
684
|
-
// Clear the backup path ā the upgrade that created it just failed
|
|
685
|
-
preUpgradeBackupPath: undefined,
|
|
686
654
|
};
|
|
687
655
|
saveAssistantEntry(rolledBackEntry);
|
|
688
656
|
|
|
@@ -810,40 +778,6 @@ async function ensureGuardianTokenAfterLocalUpgrade(
|
|
|
810
778
|
}
|
|
811
779
|
}
|
|
812
780
|
|
|
813
|
-
async function fetchLocalUpgradeState(
|
|
814
|
-
entry: AssistantEntry,
|
|
815
|
-
adminUrl: string,
|
|
816
|
-
): Promise<{
|
|
817
|
-
currentVersion?: string;
|
|
818
|
-
preMigrationState: {
|
|
819
|
-
dbVersion?: number;
|
|
820
|
-
lastWorkspaceMigrationId?: string;
|
|
821
|
-
};
|
|
822
|
-
}> {
|
|
823
|
-
try {
|
|
824
|
-
const healthResp = await loopbackSafeFetch(
|
|
825
|
-
`${adminUrl}/healthz?include=migrations`,
|
|
826
|
-
{
|
|
827
|
-
signal: AbortSignal.timeout(5000),
|
|
828
|
-
},
|
|
829
|
-
);
|
|
830
|
-
if (healthResp.ok) {
|
|
831
|
-
const health = (await healthResp.json()) as {
|
|
832
|
-
version?: string;
|
|
833
|
-
migrations?: { dbVersion?: number; lastWorkspaceMigrationId?: string };
|
|
834
|
-
};
|
|
835
|
-
return {
|
|
836
|
-
currentVersion: health.version,
|
|
837
|
-
preMigrationState: health.migrations ?? {},
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
} catch {
|
|
841
|
-
// Best-effort ā sleeping assistants and unhealthy gateways can still be restarted.
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
return { preMigrationState: {} };
|
|
845
|
-
}
|
|
846
|
-
|
|
847
781
|
async function upgradeLocal(
|
|
848
782
|
entry: AssistantEntry,
|
|
849
783
|
version: string | null,
|
|
@@ -856,6 +790,7 @@ async function upgradeLocal(
|
|
|
856
790
|
process.exit(1);
|
|
857
791
|
}
|
|
858
792
|
|
|
793
|
+
const originalResources = { ...entry.resources };
|
|
859
794
|
const signingKey = resolveExistingLocalSigningKey(entry.resources);
|
|
860
795
|
if (entry.resources.signingKey !== signingKey) {
|
|
861
796
|
entry.resources.signingKey = signingKey;
|
|
@@ -864,10 +799,7 @@ async function upgradeLocal(
|
|
|
864
799
|
|
|
865
800
|
const targetVersion = await targetVersionFromCli(version);
|
|
866
801
|
const adminUrl = localAdminUrl(entry);
|
|
867
|
-
const
|
|
868
|
-
entry,
|
|
869
|
-
adminUrl,
|
|
870
|
-
);
|
|
802
|
+
const currentVersion = await fetchCurrentVersion(adminUrl);
|
|
871
803
|
|
|
872
804
|
if (!currentVersion && targetVersion) {
|
|
873
805
|
console.warn(
|
|
@@ -877,7 +809,7 @@ async function upgradeLocal(
|
|
|
877
809
|
if (currentVersion && targetVersion) {
|
|
878
810
|
const cmp = compareVersions(targetVersion, currentVersion);
|
|
879
811
|
if (cmp !== null && cmp < 0) {
|
|
880
|
-
const msg = `Cannot upgrade to an older version (${targetVersion} < ${currentVersion}). Use \`cadet
|
|
812
|
+
const msg = `Cannot upgrade to an older version (${targetVersion} < ${currentVersion}). Use \`cadet restore --from <backup-path> --version ${targetVersion}\` instead.`;
|
|
881
813
|
console.error(msg);
|
|
882
814
|
emitCliError("VERSION_DIRECTION", msg);
|
|
883
815
|
process.exit(1);
|
|
@@ -933,7 +865,7 @@ async function upgradeLocal(
|
|
|
933
865
|
console.log(` Backup saved: ${backupPath}\n`);
|
|
934
866
|
pruneOldBackups(entry.assistantId, 3);
|
|
935
867
|
} else {
|
|
936
|
-
|
|
868
|
+
throw new Error("Pre-upgrade root backup failed; upgrade aborted before replacing services.");
|
|
937
869
|
}
|
|
938
870
|
|
|
939
871
|
let bootstrapSecret = entry.guardianBootstrapSecret;
|
|
@@ -944,20 +876,8 @@ async function upgradeLocal(
|
|
|
944
876
|
entryChanged = true;
|
|
945
877
|
}
|
|
946
878
|
|
|
947
|
-
if (
|
|
948
|
-
|
|
949
|
-
backupPath ||
|
|
950
|
-
currentVersion ||
|
|
951
|
-
preMigrationState.dbVersion !== undefined ||
|
|
952
|
-
preMigrationState.lastWorkspaceMigrationId !== undefined
|
|
953
|
-
) {
|
|
954
|
-
saveAssistantEntry({
|
|
955
|
-
...entry,
|
|
956
|
-
previousVersion: currentVersion,
|
|
957
|
-
previousDbMigrationVersion: preMigrationState.dbVersion,
|
|
958
|
-
previousWorkspaceMigrationId: preMigrationState.lastWorkspaceMigrationId,
|
|
959
|
-
preUpgradeBackupPath: backupPath ?? undefined,
|
|
960
|
-
});
|
|
879
|
+
if (entryChanged) {
|
|
880
|
+
saveAssistantEntry(entry);
|
|
961
881
|
}
|
|
962
882
|
|
|
963
883
|
await broadcastUpgradeEvent(
|
|
@@ -977,64 +897,69 @@ async function upgradeLocal(
|
|
|
977
897
|
runtimeVersion: runtimeInstall.version,
|
|
978
898
|
runtimeInstallDir: runtimeInstall.installDir,
|
|
979
899
|
};
|
|
980
|
-
saveAssistantEntry(
|
|
981
|
-
...entry,
|
|
982
|
-
previousVersion: currentVersion,
|
|
983
|
-
previousDbMigrationVersion: preMigrationState.dbVersion,
|
|
984
|
-
previousWorkspaceMigrationId: preMigrationState.lastWorkspaceMigrationId,
|
|
985
|
-
preUpgradeBackupPath: backupPath ?? undefined,
|
|
986
|
-
});
|
|
900
|
+
saveAssistantEntry(entry);
|
|
987
901
|
console.log(` Runtime installed: ${runtimeInstall.installDir}\n`);
|
|
988
902
|
|
|
989
|
-
console.log("š Stopping local assistant processes...");
|
|
990
|
-
await stopLocalProcesses(entry.resources);
|
|
991
|
-
console.log("ā
Local assistant processes stopped\n");
|
|
992
|
-
|
|
993
|
-
console.log("š Starting upgraded local assistant...");
|
|
994
|
-
const previousAppVersion = process.env.APP_VERSION;
|
|
995
|
-
process.env.APP_VERSION = stripVersionPrefix(targetVersion);
|
|
996
903
|
try {
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
await Promise.all([
|
|
1001
|
-
startCes(false, entry.resources),
|
|
1002
|
-
startLocalDaemon(false, entry.resources, { signingKey }),
|
|
1003
|
-
startGateway(false, entry.resources, { signingKey, bootstrapSecret }),
|
|
1004
|
-
]);
|
|
1005
|
-
} finally {
|
|
1006
|
-
if (previousAppVersion === undefined) {
|
|
1007
|
-
delete process.env.APP_VERSION;
|
|
1008
|
-
} else {
|
|
1009
|
-
process.env.APP_VERSION = previousAppVersion;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
await ensureGuardianTokenAfterLocalUpgrade(
|
|
1013
|
-
entry,
|
|
1014
|
-
entry.resources.gatewayPort,
|
|
1015
|
-
bootstrapSecret,
|
|
1016
|
-
);
|
|
1017
|
-
console.log("ā
Local assistant processes started\n");
|
|
904
|
+
console.log("š Stopping local assistant processes...");
|
|
905
|
+
await stopLocalProcesses(entry.resources);
|
|
906
|
+
console.log("ā
Local assistant processes stopped\n");
|
|
1018
907
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
908
|
+
console.log("š Starting upgraded local assistant...");
|
|
909
|
+
const previousAppVersion = process.env.APP_VERSION;
|
|
910
|
+
process.env.APP_VERSION = stripVersionPrefix(targetVersion);
|
|
911
|
+
try {
|
|
912
|
+
// Bring CES, daemon, and gateway up in parallel, the way the Docker
|
|
913
|
+
// topology starts its sibling processes together. startCes always
|
|
914
|
+
// launches the CES sibling.
|
|
915
|
+
await Promise.all([
|
|
916
|
+
startCes(false, entry.resources),
|
|
917
|
+
startLocalDaemon(false, entry.resources, { signingKey }),
|
|
918
|
+
startGateway(false, entry.resources, { signingKey, bootstrapSecret }),
|
|
919
|
+
]);
|
|
920
|
+
} finally {
|
|
921
|
+
if (previousAppVersion === undefined) {
|
|
922
|
+
delete process.env.APP_VERSION;
|
|
923
|
+
} else {
|
|
924
|
+
process.env.APP_VERSION = previousAppVersion;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
await ensureGuardianTokenAfterLocalUpgrade(
|
|
928
|
+
entry,
|
|
929
|
+
entry.resources.gatewayPort,
|
|
930
|
+
bootstrapSecret,
|
|
931
|
+
);
|
|
932
|
+
console.log("ā
Local assistant processes started\n");
|
|
1025
933
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
if (!ready) {
|
|
1029
|
-
await broadcastUpgradeEvent(
|
|
1030
|
-
adminUrl,
|
|
934
|
+
const workspaceDir = join(entry.resources.instanceDir, "workspace");
|
|
935
|
+
await restoreTunnelEdgeAndAutoTunnel(
|
|
1031
936
|
entry.assistantId,
|
|
1032
|
-
|
|
937
|
+
entry.resources.gatewayPort,
|
|
938
|
+
workspaceDir,
|
|
1033
939
|
);
|
|
1034
|
-
|
|
1035
|
-
console.
|
|
1036
|
-
|
|
1037
|
-
|
|
940
|
+
|
|
941
|
+
console.log("Waiting for assistant to become ready...");
|
|
942
|
+
const ready = await waitForReady(adminUrl);
|
|
943
|
+
if (!ready) {
|
|
944
|
+
await broadcastUpgradeEvent(
|
|
945
|
+
adminUrl,
|
|
946
|
+
entry.assistantId,
|
|
947
|
+
buildCompleteEvent(currentVersion ?? "unknown", false),
|
|
948
|
+
);
|
|
949
|
+
const msg = `Upgrade to ${targetVersion} failed: local assistant did not become ready.`;
|
|
950
|
+
console.error(`\nā ${msg}`);
|
|
951
|
+
throw new Error(msg);
|
|
952
|
+
}
|
|
953
|
+
} catch (error) {
|
|
954
|
+
console.error("Local upgrade failed; restoring the previous runtime and service roots...");
|
|
955
|
+
await stopLocalProcesses(entry.resources);
|
|
956
|
+
entry.resources = originalResources;
|
|
957
|
+
saveAssistantEntry(entry);
|
|
958
|
+
const restored = await restoreBackup(adminUrl, entry.assistantId, backupPath, { resume: false });
|
|
959
|
+
if (!restored) throw new Error(`Local recovery failed; services remain stopped. Backup: ${backupPath}`, { cause: error });
|
|
960
|
+
await Promise.all([startCes(false, entry.resources), startLocalDaemon(false, entry.resources, { signingKey }), startGateway(false, entry.resources, { signingKey, bootstrapSecret })]);
|
|
961
|
+
if (!await waitForReady(adminUrl)) throw new Error(`Previous local runtime did not recover. Backup: ${backupPath}`, { cause: error });
|
|
962
|
+
throw error;
|
|
1038
963
|
}
|
|
1039
964
|
|
|
1040
965
|
await broadcastUpgradeEvent(
|
|
@@ -1223,7 +1148,7 @@ async function upgradePlatform(
|
|
|
1223
1148
|
}
|
|
1224
1149
|
|
|
1225
1150
|
if (resolution.isDowngrade && resolution.target && currentVersion) {
|
|
1226
|
-
const msg = `Cannot upgrade to an older version (${resolution.target} < ${currentVersion}). Use \`cadet
|
|
1151
|
+
const msg = `Cannot upgrade to an older version (${resolution.target} < ${currentVersion}). Use \`cadet restore --from <backup-path> --version ${resolution.target}\` instead.`;
|
|
1227
1152
|
console.error(msg);
|
|
1228
1153
|
emitCliError("VERSION_DIRECTION", msg);
|
|
1229
1154
|
process.exit(1);
|
|
@@ -1384,6 +1309,8 @@ async function upgradePrepare(
|
|
|
1384
1309
|
description: `Pre-upgrade snapshot before ${currentVersion} ā ${targetVersion}`,
|
|
1385
1310
|
});
|
|
1386
1311
|
|
|
1312
|
+
if (!backupPath) throw new Error("Pre-upgrade root backup failed; update preparation aborted.");
|
|
1313
|
+
|
|
1387
1314
|
// 5. Prune old backups (keep 3)
|
|
1388
1315
|
if (backupPath) {
|
|
1389
1316
|
pruneOldBackups(entry.assistantId, 3);
|
|
@@ -1455,11 +1382,11 @@ async function resolveLatestStableTag(): Promise<string> {
|
|
|
1455
1382
|
const latestVersion = await fetchLatestStableVersion();
|
|
1456
1383
|
if (!latestVersion) {
|
|
1457
1384
|
console.error(
|
|
1458
|
-
"Error: Could not determine the latest stable release from
|
|
1385
|
+
"Error: Could not determine the latest stable release from Cadet Cloud or GitHub Releases.",
|
|
1459
1386
|
);
|
|
1460
1387
|
emitCliError(
|
|
1461
1388
|
"UNKNOWN",
|
|
1462
|
-
"Could not determine the latest stable release from
|
|
1389
|
+
"Could not determine the latest stable release from Cadet Cloud or GitHub Releases",
|
|
1463
1390
|
);
|
|
1464
1391
|
process.exit(1);
|
|
1465
1392
|
}
|
package/src/commands/wake.ts
CHANGED
|
@@ -201,7 +201,7 @@ class WakeExit extends Error {}
|
|
|
201
201
|
* refusals so the caller decides between exiting (single path) and
|
|
202
202
|
* continuing to the next entry (`--all`).
|
|
203
203
|
*/
|
|
204
|
-
async function wakeAssistant(
|
|
204
|
+
export async function wakeAssistant(
|
|
205
205
|
entry: AssistantEntry,
|
|
206
206
|
opts: WakeAssistantOptions,
|
|
207
207
|
): Promise<void> {
|
package/src/index.ts
CHANGED
|
@@ -31,7 +31,6 @@ import { ps } from "./commands/ps";
|
|
|
31
31
|
import { restore } from "./commands/restore";
|
|
32
32
|
import { retire } from "./commands/retire";
|
|
33
33
|
import { restart } from "./commands/restart";
|
|
34
|
-
import { rollback } from "./commands/rollback";
|
|
35
34
|
import { setup } from "./commands/setup";
|
|
36
35
|
import { sleep } from "./commands/sleep";
|
|
37
36
|
import { debug } from "./commands/debug";
|
|
@@ -39,7 +38,6 @@ import { teleport } from "./commands/teleport";
|
|
|
39
38
|
import { terminal } from "./commands/terminal";
|
|
40
39
|
import { tunnel } from "./commands/tunnel";
|
|
41
40
|
import { uninstall } from "./commands/uninstall";
|
|
42
|
-
import { unpair } from "./commands/unpair";
|
|
43
41
|
import { upgrade } from "./commands/upgrade";
|
|
44
42
|
import { use } from "./commands/use";
|
|
45
43
|
import { voice } from "./commands/voice";
|
|
@@ -73,14 +71,12 @@ const commands = {
|
|
|
73
71
|
restore,
|
|
74
72
|
retire,
|
|
75
73
|
restart,
|
|
76
|
-
rollback,
|
|
77
74
|
setup,
|
|
78
75
|
sleep,
|
|
79
76
|
teleport,
|
|
80
77
|
terminal,
|
|
81
78
|
tunnel,
|
|
82
79
|
uninstall,
|
|
83
|
-
unpair,
|
|
84
80
|
upgrade,
|
|
85
81
|
use,
|
|
86
82
|
voice,
|