@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
|
@@ -20,18 +20,16 @@ import {
|
|
|
20
20
|
pullImage,
|
|
21
21
|
startContainers,
|
|
22
22
|
stopContainers,
|
|
23
|
+
sleepContainers,
|
|
24
|
+
wakeContainers,
|
|
23
25
|
} from "./docker.js";
|
|
24
26
|
import { getStateDir } from "./environments/paths.js";
|
|
25
27
|
import { getCurrentEnvironment } from "./environments/resolve.js";
|
|
26
28
|
import { loadGuardianToken } from "./guardian-token.js";
|
|
27
29
|
import { classifyReadyzResponse } from "./http-client.js";
|
|
28
|
-
import {
|
|
30
|
+
import { resolveImageRefs } from "./cloud-releases.js";
|
|
29
31
|
import { z } from "zod";
|
|
30
32
|
|
|
31
|
-
const GatewayHealthSchema = z.object({
|
|
32
|
-
version: z.string().optional(),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
33
|
const MigrationGateBodySchema = z.object({
|
|
36
34
|
status: z.string().optional(),
|
|
37
35
|
reason: z.string().optional(),
|
|
@@ -42,7 +40,7 @@ import {
|
|
|
42
40
|
type ServiceName,
|
|
43
41
|
} from "./statefulset.js";
|
|
44
42
|
import { execOutput } from "./step-runner.js";
|
|
45
|
-
import { compareVersions
|
|
43
|
+
import { compareVersions } from "./version-compat.js";
|
|
46
44
|
import { loopbackSafeFetch } from "./loopback-fetch.js";
|
|
47
45
|
|
|
48
46
|
// ---------------------------------------------------------------------------
|
|
@@ -231,7 +229,7 @@ export function buildReplayEnv(
|
|
|
231
229
|
interface ReplayState {
|
|
232
230
|
bootstrapSecret: string | undefined;
|
|
233
231
|
cesServiceToken: string;
|
|
234
|
-
signingKey
|
|
232
|
+
signingKey?: string;
|
|
235
233
|
extraAssistantEnv: Record<string, string>;
|
|
236
234
|
extraGatewayEnv: Record<string, string>;
|
|
237
235
|
}
|
|
@@ -239,8 +237,9 @@ interface ReplayState {
|
|
|
239
237
|
/**
|
|
240
238
|
* Derive the secrets and replay env for replacement containers from
|
|
241
239
|
* already-captured assistant/gateway envs. GUARDIAN_BOOTSTRAP_SECRET is only
|
|
242
|
-
* set on the gateway; CES_SERVICE_TOKEN
|
|
243
|
-
*
|
|
240
|
+
* set on the gateway; CES_SERVICE_TOKEN can be generated for older instances.
|
|
241
|
+
* An absent signing-key override stays absent so replacements use the persisted
|
|
242
|
+
* gateway root rather than rotate its identity. CADET_DEVICE_ID is
|
|
244
243
|
* backfilled on the gateway from the host, and the assistant inherits the
|
|
245
244
|
* gateway's value (captured values win) so the pair always matches.
|
|
246
245
|
*/
|
|
@@ -259,7 +258,8 @@ export function buildReplayState(
|
|
|
259
258
|
cesServiceToken:
|
|
260
259
|
capturedEnv["CES_SERVICE_TOKEN"] || randomBytes(32).toString("hex"),
|
|
261
260
|
signingKey:
|
|
262
|
-
|
|
261
|
+
gatewayEnv["ACTOR_TOKEN_SIGNING_KEY"] ||
|
|
262
|
+
capturedEnv["ACTOR_TOKEN_SIGNING_KEY"],
|
|
263
263
|
extraAssistantEnv,
|
|
264
264
|
extraGatewayEnv,
|
|
265
265
|
};
|
|
@@ -287,28 +287,6 @@ export async function captureReplayState(
|
|
|
287
287
|
return buildReplayState(capturedEnv, gatewayEnv);
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
/**
|
|
291
|
-
* Best-effort fetch of the running service group version from the gateway
|
|
292
|
-
* `/healthz` endpoint. Returns `undefined` when the endpoint is
|
|
293
|
-
* unreachable or does not include a version field.
|
|
294
|
-
*/
|
|
295
|
-
export async function fetchCurrentVersion(
|
|
296
|
-
runtimeUrl: string,
|
|
297
|
-
): Promise<string | undefined> {
|
|
298
|
-
try {
|
|
299
|
-
const resp = await loopbackSafeFetch(`${runtimeUrl}/healthz`, {
|
|
300
|
-
signal: AbortSignal.timeout(5000),
|
|
301
|
-
});
|
|
302
|
-
if (resp.ok) {
|
|
303
|
-
const parsed = GatewayHealthSchema.safeParse(await resp.json());
|
|
304
|
-
return parsed.success ? parsed.data.version : undefined;
|
|
305
|
-
}
|
|
306
|
-
} catch {
|
|
307
|
-
// Best-effort
|
|
308
|
-
}
|
|
309
|
-
return undefined;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
290
|
/**
|
|
313
291
|
* Best-effort fetch of the assistant's configured public ingress URL from the
|
|
314
292
|
* gateway `integrations/ingress/config` endpoint. Returns `undefined` when
|
|
@@ -344,41 +322,6 @@ export async function fetchAssistantIngressUrl(
|
|
|
344
322
|
return undefined;
|
|
345
323
|
}
|
|
346
324
|
|
|
347
|
-
/**
|
|
348
|
-
* Determine the version that was running before the current one.
|
|
349
|
-
*
|
|
350
|
-
* Checks (in order):
|
|
351
|
-
* 1. `entry.previousVersion` (saved by the upgrade flow from health).
|
|
352
|
-
* 2. The releases list from the cloud API — finds the version
|
|
353
|
-
* immediately before `currentVersion`.
|
|
354
|
-
*
|
|
355
|
-
* Returns `undefined` when neither source yields a result.
|
|
356
|
-
*/
|
|
357
|
-
export async function fetchPreviousVersion(
|
|
358
|
-
currentVersion: string | undefined,
|
|
359
|
-
previousVersionFromLockfile: string | undefined,
|
|
360
|
-
): Promise<string | undefined> {
|
|
361
|
-
// 1. Lockfile-cached value (written during upgrade from health endpoint)
|
|
362
|
-
if (previousVersionFromLockfile) return previousVersionFromLockfile;
|
|
363
|
-
|
|
364
|
-
// 2. Derive from releases list
|
|
365
|
-
if (!currentVersion) return undefined;
|
|
366
|
-
const releases = await fetchReleases();
|
|
367
|
-
if (!releases) return undefined;
|
|
368
|
-
|
|
369
|
-
const normalizedCurrent = stripVersionPrefix(currentVersion);
|
|
370
|
-
|
|
371
|
-
// Releases are ordered newest-first; find the entry right after the
|
|
372
|
-
// current version (i.e. the one that was running before the upgrade).
|
|
373
|
-
const idx = releases.findIndex(
|
|
374
|
-
(r) => stripVersionPrefix(r.version ?? "") === normalizedCurrent,
|
|
375
|
-
);
|
|
376
|
-
if (idx >= 0 && idx + 1 < releases.length) {
|
|
377
|
-
return releases[idx + 1].version;
|
|
378
|
-
}
|
|
379
|
-
return undefined;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
325
|
/**
|
|
383
326
|
* Poll the gateway `/readyz` endpoint until the assistant reports migrations
|
|
384
327
|
* ready or the timeout elapses. Returns whether the assistant became ready.
|
|
@@ -577,12 +520,8 @@ export async function rollbackMigrations(
|
|
|
577
520
|
}
|
|
578
521
|
|
|
579
522
|
/**
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
* exemption admits it, and the import replaces the DB wholesale), then
|
|
583
|
-
* restart the containers — the failed readiness latch only clears on
|
|
584
|
-
* restart — and re-check readiness. Shared by the upgrade failure branch
|
|
585
|
-
* and performDockerRollback's auto-rollback.
|
|
523
|
+
* Restore the host root archive for a reverted stack with failed migrations,
|
|
524
|
+
* restart its containers, and check readiness before resuming the installation.
|
|
586
525
|
*/
|
|
587
526
|
export async function attemptFailedStateRestore(opts: {
|
|
588
527
|
runtimeUrl: string;
|
|
@@ -601,7 +540,7 @@ export async function attemptFailedStateRestore(opts: {
|
|
|
601
540
|
opts.runtimeUrl,
|
|
602
541
|
opts.assistantId,
|
|
603
542
|
opts.backupPath,
|
|
604
|
-
{
|
|
543
|
+
{ resume: false },
|
|
605
544
|
);
|
|
606
545
|
if (!restored) {
|
|
607
546
|
return { restored: false, ready: false };
|
|
@@ -609,6 +548,7 @@ export async function attemptFailedStateRestore(opts: {
|
|
|
609
548
|
console.log(
|
|
610
549
|
" Backup imported — restarting containers to complete recovery...",
|
|
611
550
|
);
|
|
551
|
+
await sleepContainers(opts.res);
|
|
612
552
|
await stopContainers(opts.res);
|
|
613
553
|
await startContainers(opts.containerOptions, (msg) => console.log(msg));
|
|
614
554
|
return { restored: true, ready: await waitForReady(opts.runtimeUrl) };
|
|
@@ -619,28 +559,29 @@ export async function attemptFailedStateRestore(opts: {
|
|
|
619
559
|
// ---------------------------------------------------------------------------
|
|
620
560
|
|
|
621
561
|
export interface PerformDockerRollbackOptions {
|
|
562
|
+
/** Root archive must be installed before the target runtime starts. */
|
|
563
|
+
backupPath: string;
|
|
622
564
|
/** Specific version to roll back to. */
|
|
623
565
|
targetVersion?: string;
|
|
624
566
|
}
|
|
625
567
|
|
|
626
568
|
/**
|
|
627
|
-
*
|
|
628
|
-
* (targeted version rollback) and `restore.ts` (version + data restore).
|
|
569
|
+
* Switch Docker runtime versions as part of a versioned backup restore.
|
|
629
570
|
*
|
|
630
571
|
* This function handles the full lifecycle:
|
|
631
572
|
* - Version validation (target must be older than current)
|
|
632
573
|
* - Image resolution and pulling
|
|
633
|
-
* -
|
|
574
|
+
* - Post-swap migration rollback to the target runtime's registry ceiling
|
|
634
575
|
* - Container stop/start with target images
|
|
635
576
|
* - Readiness check
|
|
636
|
-
* - Lockfile update with
|
|
577
|
+
* - Lockfile update with the running images
|
|
637
578
|
* - Auto-rollback on failure
|
|
638
579
|
*/
|
|
639
580
|
export async function performDockerRollback(
|
|
640
581
|
entry: AssistantEntry,
|
|
641
582
|
options: PerformDockerRollbackOptions,
|
|
642
583
|
): Promise<void> {
|
|
643
|
-
const { targetVersion } = options;
|
|
584
|
+
const { targetVersion, backupPath } = options;
|
|
644
585
|
|
|
645
586
|
if (!targetVersion) {
|
|
646
587
|
throw new Error("targetVersion is required for performDockerRollback");
|
|
@@ -708,21 +649,6 @@ export async function performDockerRollback(
|
|
|
708
649
|
}
|
|
709
650
|
}
|
|
710
651
|
|
|
711
|
-
// Persist rollback state to lockfile BEFORE any destructive changes
|
|
712
|
-
if (entry.containerInfo) {
|
|
713
|
-
const rollbackEntry: AssistantEntry = {
|
|
714
|
-
...entry,
|
|
715
|
-
previousContainerInfo: { ...entry.containerInfo },
|
|
716
|
-
previousVersion: currentVersion,
|
|
717
|
-
previousDbMigrationVersion: preMigrationState.dbVersion,
|
|
718
|
-
previousWorkspaceMigrationId: preMigrationState.lastWorkspaceMigrationId,
|
|
719
|
-
};
|
|
720
|
-
saveAssistantEntry(rollbackEntry);
|
|
721
|
-
if (currentVersion) {
|
|
722
|
-
console.log(` Saved rollback state: ${currentVersion}\n`);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
652
|
// Record rollback start in workspace git history
|
|
727
653
|
await commitWorkspaceViaGateway(
|
|
728
654
|
entry.runtimeUrl,
|
|
@@ -747,15 +673,17 @@ export async function performDockerRollback(
|
|
|
747
673
|
entry.runtimeUrl,
|
|
748
674
|
entry.assistantId,
|
|
749
675
|
{
|
|
750
|
-
prefix: `${entry.assistantId}-pre-
|
|
676
|
+
prefix: `${entry.assistantId}-pre-rollback`,
|
|
751
677
|
description: `Pre-rollback snapshot before ${currentVersion ?? "unknown"} → ${targetVersion}`,
|
|
752
678
|
},
|
|
753
679
|
);
|
|
754
680
|
if (preRollbackBackupPath) {
|
|
755
681
|
console.log(` Backup saved: ${preRollbackBackupPath}\n`);
|
|
756
|
-
pruneOldBackups(entry.assistantId, 3);
|
|
682
|
+
pruneOldBackups(entry.assistantId, 3, "pre-rollback");
|
|
757
683
|
} else {
|
|
758
|
-
|
|
684
|
+
throw new Error(
|
|
685
|
+
"Pre-rollback root backup failed; rollback aborted before replacing services.",
|
|
686
|
+
);
|
|
759
687
|
}
|
|
760
688
|
|
|
761
689
|
const {
|
|
@@ -831,46 +759,47 @@ export async function performDockerRollback(
|
|
|
831
759
|
|
|
832
760
|
// Stop containers, migrate security files, start with target images
|
|
833
761
|
console.log("🛑 Stopping existing containers...");
|
|
762
|
+
await sleepContainers(res);
|
|
834
763
|
await stopContainers(res);
|
|
835
764
|
console.log("✅ Containers stopped\n");
|
|
836
765
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
766
|
+
let ready = false;
|
|
767
|
+
try {
|
|
768
|
+
console.log("Creating stopped containers with the target version...");
|
|
769
|
+
await startContainers(
|
|
770
|
+
{
|
|
771
|
+
signingKey,
|
|
772
|
+
bootstrapSecret,
|
|
773
|
+
cesServiceToken,
|
|
774
|
+
extraAssistantEnv,
|
|
775
|
+
extraGatewayEnv,
|
|
776
|
+
gatewayPort,
|
|
777
|
+
assistantPort,
|
|
778
|
+
imageTags: targetImageTags,
|
|
779
|
+
instanceName,
|
|
780
|
+
res,
|
|
781
|
+
createOnly: true,
|
|
782
|
+
},
|
|
783
|
+
(msg) => console.log(msg),
|
|
784
|
+
);
|
|
785
|
+
if (
|
|
786
|
+
!(await restoreBackup(entry.runtimeUrl, entry.assistantId, backupPath, {
|
|
787
|
+
resume: false,
|
|
788
|
+
}))
|
|
789
|
+
)
|
|
790
|
+
throw new Error("Target backup roots could not be restored.");
|
|
791
|
+
await wakeContainers(res);
|
|
792
|
+
console.log("Waiting for restored assistant to become ready...");
|
|
793
|
+
ready = await waitForReady(entry.runtimeUrl);
|
|
794
|
+
} catch (error) {
|
|
795
|
+
console.error(
|
|
796
|
+
`Versioned restore failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
797
|
+
);
|
|
798
|
+
}
|
|
858
799
|
|
|
859
800
|
if (ready) {
|
|
860
801
|
// Success path
|
|
861
802
|
|
|
862
|
-
// Post-swap migration rollback: ask the now-running old daemon to roll
|
|
863
|
-
// back any migrations above its own registry ceiling.
|
|
864
|
-
await rollbackMigrations(
|
|
865
|
-
entry.runtimeUrl,
|
|
866
|
-
entry.assistantId,
|
|
867
|
-
undefined,
|
|
868
|
-
undefined,
|
|
869
|
-
true,
|
|
870
|
-
);
|
|
871
|
-
|
|
872
|
-
// Capture new digests from the rolled-back containers
|
|
873
|
-
// Swap current/previous state to enable "rollback the rollback"
|
|
874
803
|
const updatedEntry: AssistantEntry = {
|
|
875
804
|
...entry,
|
|
876
805
|
containerInfo: {
|
|
@@ -879,10 +808,6 @@ export async function performDockerRollback(
|
|
|
879
808
|
cesImage: targetImageTags["credential-executor"],
|
|
880
809
|
assistantPort,
|
|
881
810
|
},
|
|
882
|
-
previousContainerInfo: entry.containerInfo,
|
|
883
|
-
previousDbMigrationVersion: preMigrationState.dbVersion,
|
|
884
|
-
previousWorkspaceMigrationId: preMigrationState.lastWorkspaceMigrationId,
|
|
885
|
-
preUpgradeBackupPath: undefined,
|
|
886
811
|
};
|
|
887
812
|
saveAssistantEntry(updatedEntry);
|
|
888
813
|
|
|
@@ -950,6 +875,7 @@ export async function performDockerRollback(
|
|
|
950
875
|
);
|
|
951
876
|
}
|
|
952
877
|
|
|
878
|
+
await sleepContainers(res);
|
|
953
879
|
await stopContainers(res);
|
|
954
880
|
|
|
955
881
|
await startContainers(
|
|
@@ -1013,13 +939,13 @@ export async function performDockerRollback(
|
|
|
1013
939
|
entry.runtimeUrl,
|
|
1014
940
|
entry.assistantId,
|
|
1015
941
|
preRollbackBackupPath,
|
|
1016
|
-
{
|
|
942
|
+
{},
|
|
1017
943
|
);
|
|
1018
944
|
if (restored) {
|
|
1019
945
|
console.log(" ✅ Data restored successfully\n");
|
|
1020
946
|
} else {
|
|
1021
|
-
|
|
1022
|
-
|
|
947
|
+
throw new Error(
|
|
948
|
+
`Root restore failed; rollback recovery is incomplete. Backup: ${preRollbackBackupPath}`,
|
|
1023
949
|
);
|
|
1024
950
|
}
|
|
1025
951
|
}
|
|
@@ -1038,10 +964,6 @@ export async function performDockerRollback(
|
|
|
1038
964
|
currentImageRefs["credential-executor"],
|
|
1039
965
|
assistantPort,
|
|
1040
966
|
},
|
|
1041
|
-
previousContainerInfo: undefined,
|
|
1042
|
-
previousDbMigrationVersion: undefined,
|
|
1043
|
-
previousWorkspaceMigrationId: undefined,
|
|
1044
|
-
preUpgradeBackupPath: undefined,
|
|
1045
967
|
};
|
|
1046
968
|
saveAssistantEntry(revertedEntry);
|
|
1047
969
|
|
package/src/lib/web-dist.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stripVersionPrefix } from "./version-compat.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { isValidReleaseVersion } from "@mycadet/local-mode";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
5
|
import { createRequire } from "node:module";
|
|
3
6
|
import { dirname, join } from "node:path";
|
|
4
7
|
|
|
@@ -46,3 +49,78 @@ export function findWebDistDir(options: WebDistOptions = {}): string | null {
|
|
|
46
49
|
}
|
|
47
50
|
return null;
|
|
48
51
|
}
|
|
52
|
+
|
|
53
|
+
interface AppDistOptions {
|
|
54
|
+
execPath?: string;
|
|
55
|
+
platform?: NodeJS.Platform;
|
|
56
|
+
resolvePackage?: () => string;
|
|
57
|
+
startDir?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Locate the pre-built standalone app shell bundle for installed, packaged,
|
|
62
|
+
* or source CLIs. Resolution mirrors {@link findWebDistDir}: the published
|
|
63
|
+
* `@mycadet/app` package carries `dist/standalone`, packaged Windows CLIs
|
|
64
|
+
* ship it beside the binary, and source checkouts fall back to the build
|
|
65
|
+
* output in the repo tree.
|
|
66
|
+
*/
|
|
67
|
+
export function findAppDistDir(options: AppDistOptions = {}): string | null {
|
|
68
|
+
try {
|
|
69
|
+
const packageJson = (
|
|
70
|
+
options.resolvePackage ??
|
|
71
|
+
(() => loadModule.resolve("@mycadet/app/package.json"))
|
|
72
|
+
)();
|
|
73
|
+
const distDir = join(dirname(packageJson), "dist", "standalone");
|
|
74
|
+
if (existsSync(join(distDir, "index.html"))) {
|
|
75
|
+
return distDir;
|
|
76
|
+
}
|
|
77
|
+
} catch {}
|
|
78
|
+
|
|
79
|
+
if ((options.platform ?? process.platform) === "win32") {
|
|
80
|
+
const runtimeDir = dirname(options.execPath ?? process.execPath);
|
|
81
|
+
const packagedDist = join(runtimeDir, "app-dist");
|
|
82
|
+
if (existsSync(join(packagedDist, "index.html"))) {
|
|
83
|
+
return packagedDist;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let dir = options.startDir ?? import.meta.dir;
|
|
88
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
89
|
+
const candidate = join(
|
|
90
|
+
dir,
|
|
91
|
+
"clients",
|
|
92
|
+
"app",
|
|
93
|
+
"dist",
|
|
94
|
+
"standalone",
|
|
95
|
+
"index.html",
|
|
96
|
+
);
|
|
97
|
+
if (existsSync(candidate)) {
|
|
98
|
+
return dirname(candidate);
|
|
99
|
+
}
|
|
100
|
+
const parent = dirname(dir);
|
|
101
|
+
if (parent === dir) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
dir = parent;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const WebReleaseSchema = z.object({
|
|
110
|
+
version: z
|
|
111
|
+
.string()
|
|
112
|
+
.min(1)
|
|
113
|
+
.refine((value) => value !== "latest" && isValidReleaseVersion(value)),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export function readWebReleaseVersion(distDir: string): string | undefined {
|
|
117
|
+
try {
|
|
118
|
+
const payload: unknown = JSON.parse(
|
|
119
|
+
readFileSync(join(distDir, "release.json"), "utf8"),
|
|
120
|
+
);
|
|
121
|
+
const parsed = WebReleaseSchema.safeParse(payload);
|
|
122
|
+
return parsed.success ? stripVersionPrefix(parsed.data.version) : undefined;
|
|
123
|
+
} catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/lib/xdg-log.ts
CHANGED
|
@@ -142,3 +142,14 @@ export function pipeToLogFile(
|
|
|
142
142
|
stream.on("error", onDone);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
+
|
|
146
|
+
/** Runtime output belongs to the instance, alongside its service state. */
|
|
147
|
+
export function openRuntimeLogFile(instanceDir: string, service: "assistant" | "gateway"): number | "ignore" {
|
|
148
|
+
try {
|
|
149
|
+
const dir = join(instanceDir, "logs");
|
|
150
|
+
mkdirSync(dir, { recursive: true });
|
|
151
|
+
return openSync(join(dir, `${service}-stdout.log`), "a", 0o600);
|
|
152
|
+
} catch {
|
|
153
|
+
return "ignore";
|
|
154
|
+
}
|
|
155
|
+
}
|