@openparachute/hub 0.6.3 → 0.6.4-rc.10
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/package.json +1 -2
- package/src/__tests__/account-home-ui.test.ts +344 -110
- package/src/__tests__/account-mirror.test.ts +156 -0
- package/src/__tests__/account-setup.test.ts +880 -0
- package/src/__tests__/account-usage.test.ts +137 -0
- package/src/__tests__/account-vault-admin-token.test.ts +301 -0
- package/src/__tests__/account-vault-token.test.ts +53 -1
- package/src/__tests__/admin-vault-admin-token.test.ts +17 -0
- package/src/__tests__/admin-vaults.test.ts +20 -0
- package/src/__tests__/api-account.test.ts +236 -4
- package/src/__tests__/api-invites.test.ts +217 -0
- package/src/__tests__/api-mint-token.test.ts +259 -10
- package/src/__tests__/api-modules-ops.test.ts +195 -3
- package/src/__tests__/api-modules.test.ts +40 -4
- package/src/__tests__/api-settings-hub-origin.test.ts +13 -8
- package/src/__tests__/auto-wire.test.ts +101 -1
- package/src/__tests__/cli.test.ts +188 -2
- package/src/__tests__/cloudflare-state.test.ts +104 -0
- package/src/__tests__/expose-2fa-warning.test.ts +11 -8
- package/src/__tests__/expose-cloudflare.test.ts +135 -9
- package/src/__tests__/expose-interactive.test.ts +234 -7
- package/src/__tests__/expose-supervisor-version.test.ts +104 -0
- package/src/__tests__/expose.test.ts +10 -5
- package/src/__tests__/grants.test.ts +197 -8
- package/src/__tests__/hub-origin-resolution.test.ts +179 -25
- package/src/__tests__/hub-server.test.ts +761 -13
- package/src/__tests__/hub-unit.test.ts +185 -0
- package/src/__tests__/init.test.ts +579 -3
- package/src/__tests__/install.test.ts +448 -2
- package/src/__tests__/invites.test.ts +220 -0
- package/src/__tests__/launchctl-guard.test.ts +185 -0
- package/src/__tests__/migrate-cutover.test.ts +33 -0
- package/src/__tests__/module-ops-client.test.ts +68 -0
- package/src/__tests__/scope-explanations.test.ts +16 -0
- package/src/__tests__/serve-boot.test.ts +74 -1
- package/src/__tests__/serve.test.ts +121 -7
- package/src/__tests__/setup-wizard.test.ts +110 -0
- package/src/__tests__/spawn-path.test.ts +191 -0
- package/src/__tests__/status.test.ts +64 -0
- package/src/__tests__/supervisor.test.ts +374 -0
- package/src/__tests__/users.test.ts +66 -0
- package/src/__tests__/well-known.test.ts +25 -0
- package/src/__tests__/wizard.test.ts +72 -1
- package/src/account-home-ui.ts +481 -235
- package/src/account-mirror.ts +126 -0
- package/src/account-setup.ts +381 -0
- package/src/account-usage.ts +118 -0
- package/src/account-vault-admin-token.ts +242 -0
- package/src/account-vault-token.ts +36 -2
- package/src/admin-login-ui.ts +121 -0
- package/src/admin-vault-admin-token.ts +8 -2
- package/src/admin-vaults.ts +137 -29
- package/src/api-account.ts +118 -1
- package/src/api-invites.ts +345 -0
- package/src/api-mint-token.ts +81 -0
- package/src/api-modules-ops.ts +168 -53
- package/src/api-modules.ts +36 -0
- package/src/auto-wire.ts +87 -0
- package/src/cli.ts +128 -34
- package/src/cloudflare/detect.ts +1 -1
- package/src/cloudflare/state.ts +104 -8
- package/src/commands/expose-2fa-warning.ts +17 -13
- package/src/commands/expose-cloudflare.ts +103 -36
- package/src/commands/expose-interactive.ts +163 -17
- package/src/commands/expose-supervisor.ts +45 -0
- package/src/commands/init.ts +183 -4
- package/src/commands/install.ts +321 -3
- package/src/commands/migrate-cutover.ts +12 -5
- package/src/commands/serve-boot.ts +33 -3
- package/src/commands/serve.ts +158 -37
- package/src/commands/status.ts +9 -1
- package/src/commands/wizard.ts +36 -2
- package/src/grants.ts +113 -0
- package/src/help.ts +18 -5
- package/src/hub-db.ts +70 -2
- package/src/hub-server.ts +438 -41
- package/src/hub-settings.ts +3 -3
- package/src/hub-unit.ts +259 -9
- package/src/invites.ts +291 -0
- package/src/launchctl-guard.ts +131 -0
- package/src/managed-unit.ts +13 -3
- package/src/migrate-offer.ts +15 -6
- package/src/module-ops-client.ts +47 -22
- package/src/scope-attenuation.ts +19 -0
- package/src/scope-explanations.ts +9 -1
- package/src/service-spec.ts +17 -4
- package/src/setup-wizard.ts +34 -2
- package/src/spawn-path.ts +148 -0
- package/src/supervisor.ts +232 -7
- package/src/users.ts +54 -8
- package/src/vault-hub-origin-env.ts +28 -0
- package/src/vault-name.ts +13 -1
- package/src/well-known.ts +13 -0
- package/web/ui/dist/assets/{index-mz8XcVPP.css → index-BYYUeLGA.css} +1 -1
- package/web/ui/dist/assets/index-D3cDUOOj.js +61 -0
- package/web/ui/dist/index.html +2 -2
- package/web/ui/dist/assets/index-D_0TRjeo.js +0 -61
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
NO_MANAGER_MESSAGE,
|
|
5
5
|
NO_UNIT_MESSAGE,
|
|
6
6
|
ensureHubUnit,
|
|
7
|
+
ensureHubVersionMatches,
|
|
7
8
|
installAndStartHubUnit,
|
|
8
9
|
queryHubUnitState,
|
|
9
10
|
restartHubUnit,
|
|
@@ -38,6 +39,12 @@ function fakeDeps(
|
|
|
38
39
|
over: Partial<HubUnitDeps> & {
|
|
39
40
|
runResults?: ServiceCommandResult[];
|
|
40
41
|
healthSeq?: boolean[];
|
|
42
|
+
/**
|
|
43
|
+
* #590: scripted version-aware /health probe results. Each element is
|
|
44
|
+
* `null` (hub not answering) or `{ ok, version }`. Drives
|
|
45
|
+
* `probeHealthVersion` across the version-check + post-restart re-probe.
|
|
46
|
+
*/
|
|
47
|
+
healthVersionSeq?: ({ ok: boolean; version?: string } | null)[];
|
|
41
48
|
listeningSeq?: boolean[];
|
|
42
49
|
installedUnit?: boolean;
|
|
43
50
|
} = {},
|
|
@@ -46,6 +53,7 @@ function fakeDeps(
|
|
|
46
53
|
const files = new Map<string, string>();
|
|
47
54
|
let runIdx = 0;
|
|
48
55
|
let healthIdx = 0;
|
|
56
|
+
let healthVersionIdx = 0;
|
|
49
57
|
let listeningIdx = 0;
|
|
50
58
|
const ok: ServiceCommandResult = { code: 0, stdout: "", stderr: "" };
|
|
51
59
|
|
|
@@ -96,6 +104,13 @@ function fakeDeps(
|
|
|
96
104
|
if (!seq) return false;
|
|
97
105
|
return seq[Math.min(healthIdx++, seq.length - 1)] ?? false;
|
|
98
106
|
}),
|
|
107
|
+
probeHealthVersion:
|
|
108
|
+
over.probeHealthVersion ??
|
|
109
|
+
(async () => {
|
|
110
|
+
const seq = over.healthVersionSeq;
|
|
111
|
+
if (!seq) return null;
|
|
112
|
+
return seq[Math.min(healthVersionIdx++, seq.length - 1)] ?? null;
|
|
113
|
+
}),
|
|
99
114
|
portListening:
|
|
100
115
|
over.portListening ??
|
|
101
116
|
(async () => {
|
|
@@ -260,6 +275,10 @@ describe("installAndStartHubUnit — init bringup (§3.3 / §4.2)", () => {
|
|
|
260
275
|
expect(written).toBeDefined();
|
|
261
276
|
expect(written).toContain("Environment=PARACHUTE_HOME=/home/op/.parachute");
|
|
262
277
|
expect(written).toContain("src/cli.ts serve");
|
|
278
|
+
// The default unit PATH is enriched with operator-tool dirs so the managed
|
|
279
|
+
// hub + its supervised children can find scribe's parakeet-mlx / ffmpeg
|
|
280
|
+
// (hub launchd-PATH regression). $HOME/.local/bin is the Linux operator dir.
|
|
281
|
+
expect(written).toContain("/home/op/.bun/bin:/usr/local/bin:/usr/bin:/bin:/home/op/.local/bin");
|
|
263
282
|
// enable --now drove the start.
|
|
264
283
|
expect(f.calls).toContainEqual([
|
|
265
284
|
"systemctl",
|
|
@@ -572,3 +591,169 @@ describe("queryHubUnitState — §6.4 hub-row manager query", () => {
|
|
|
572
591
|
expect(r.detail).toContain("spawn EPERM");
|
|
573
592
|
});
|
|
574
593
|
});
|
|
594
|
+
|
|
595
|
+
describe("ensureHubVersionMatches — version-check-and-restart at adoption (#590)", () => {
|
|
596
|
+
const INSTALLED = "0.6.4-rc.9";
|
|
597
|
+
|
|
598
|
+
test("versions match → no-op, NO restart, NO manager call", async () => {
|
|
599
|
+
const f = fakeDeps({
|
|
600
|
+
platform: "darwin",
|
|
601
|
+
getuid: () => 501,
|
|
602
|
+
installedUnit: true,
|
|
603
|
+
healthVersionSeq: [{ ok: true, version: INSTALLED }],
|
|
604
|
+
});
|
|
605
|
+
const res = await ensureHubVersionMatches({
|
|
606
|
+
installedVersion: INSTALLED,
|
|
607
|
+
port: 1939,
|
|
608
|
+
deps: f.deps,
|
|
609
|
+
readyPollMs: 0,
|
|
610
|
+
});
|
|
611
|
+
expect(res.outcome).toBe("match");
|
|
612
|
+
expect(res.runningVersion).toBe(INSTALLED);
|
|
613
|
+
// No launchctl/systemctl call at all — the version agreed.
|
|
614
|
+
expect(f.calls).toEqual([]);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
test("mismatch + unit-managed → restarts ONCE + re-probe shows new version → restarted", async () => {
|
|
618
|
+
const f = fakeDeps({
|
|
619
|
+
platform: "darwin",
|
|
620
|
+
getuid: () => 501,
|
|
621
|
+
installedUnit: true,
|
|
622
|
+
// first probe: stale zombie; after the restart the re-probe sees the new code.
|
|
623
|
+
healthVersionSeq: [
|
|
624
|
+
{ ok: true, version: "0.5.14-rc.4" },
|
|
625
|
+
{ ok: true, version: INSTALLED },
|
|
626
|
+
],
|
|
627
|
+
});
|
|
628
|
+
const res = await ensureHubVersionMatches({
|
|
629
|
+
installedVersion: INSTALLED,
|
|
630
|
+
port: 1939,
|
|
631
|
+
deps: f.deps,
|
|
632
|
+
readyPollMs: 0,
|
|
633
|
+
});
|
|
634
|
+
expect(res.outcome).toBe("restarted");
|
|
635
|
+
// Exactly ONE restart (launchctl kickstart -k) was issued.
|
|
636
|
+
const restarts = f.calls.filter((c) => c.includes("kickstart"));
|
|
637
|
+
expect(restarts).toHaveLength(1);
|
|
638
|
+
expect(restarts[0]).toEqual(["launchctl", "kickstart", "-k", "gui/501/computer.parachute.hub"]);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
test("/health has NO version field (very old hub) → treated as mismatch → restart", async () => {
|
|
642
|
+
const f = fakeDeps({
|
|
643
|
+
platform: "linux",
|
|
644
|
+
getuid: () => 1000,
|
|
645
|
+
installedUnit: true,
|
|
646
|
+
healthVersionSeq: [{ ok: true /* no version */ }, { ok: true, version: INSTALLED }],
|
|
647
|
+
});
|
|
648
|
+
const res = await ensureHubVersionMatches({
|
|
649
|
+
installedVersion: INSTALLED,
|
|
650
|
+
port: 1939,
|
|
651
|
+
deps: f.deps,
|
|
652
|
+
readyPollMs: 0,
|
|
653
|
+
});
|
|
654
|
+
expect(res.outcome).toBe("restarted");
|
|
655
|
+
expect(f.calls).toContainEqual(["systemctl", "--user", "restart", "parachute-hub.service"]);
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
test("mismatch but NOT unit-managed (no unit installed) → not-unit-managed, NO kill, actionable message", async () => {
|
|
659
|
+
const f = fakeDeps({
|
|
660
|
+
platform: "darwin",
|
|
661
|
+
getuid: () => 501,
|
|
662
|
+
installedUnit: false, // a detached legacy pid / dev `bun run serve`
|
|
663
|
+
healthVersionSeq: [{ ok: true, version: "0.5.14-rc.4" }],
|
|
664
|
+
});
|
|
665
|
+
const res = await ensureHubVersionMatches({
|
|
666
|
+
installedVersion: INSTALLED,
|
|
667
|
+
port: 1939,
|
|
668
|
+
deps: f.deps,
|
|
669
|
+
readyPollMs: 0,
|
|
670
|
+
});
|
|
671
|
+
expect(res.outcome).toBe("not-unit-managed");
|
|
672
|
+
// Did NOT issue any manager command — we never blindly kill a hub we don't own.
|
|
673
|
+
expect(f.calls).toEqual([]);
|
|
674
|
+
const joined = res.messages.join("\n");
|
|
675
|
+
expect(joined).toContain("NOT managed by a Parachute service unit");
|
|
676
|
+
expect(joined).toContain("0.5.14-rc.4");
|
|
677
|
+
expect(joined).toContain(INSTALLED);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
test("mismatch but no service manager at all → not-unit-managed (don't kill)", async () => {
|
|
681
|
+
const f = fakeDeps({
|
|
682
|
+
platform: "linux",
|
|
683
|
+
installedUnit: true,
|
|
684
|
+
which: () => null, // no systemctl → no manager
|
|
685
|
+
healthVersionSeq: [{ ok: true, version: "0.5.14-rc.4" }],
|
|
686
|
+
});
|
|
687
|
+
const res = await ensureHubVersionMatches({
|
|
688
|
+
installedVersion: INSTALLED,
|
|
689
|
+
port: 1939,
|
|
690
|
+
deps: f.deps,
|
|
691
|
+
readyPollMs: 0,
|
|
692
|
+
});
|
|
693
|
+
expect(res.outcome).toBe("not-unit-managed");
|
|
694
|
+
expect(f.calls).toEqual([]);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
test("restart-loop guard: still mismatched after the single restart → still-mismatched, restarts ONCE", async () => {
|
|
698
|
+
const f = fakeDeps({
|
|
699
|
+
platform: "darwin",
|
|
700
|
+
getuid: () => 501,
|
|
701
|
+
installedUnit: true,
|
|
702
|
+
// bun-linked branch checkout: the restart comes back STILL on the old
|
|
703
|
+
// version (package.json trails). Every probe returns the stale version.
|
|
704
|
+
healthVersionSeq: [{ ok: true, version: "0.5.14-rc.4" }],
|
|
705
|
+
});
|
|
706
|
+
const res = await ensureHubVersionMatches({
|
|
707
|
+
installedVersion: INSTALLED,
|
|
708
|
+
port: 1939,
|
|
709
|
+
deps: f.deps,
|
|
710
|
+
readyTimeoutMs: 0, // immediate deadline → one re-probe then settle
|
|
711
|
+
readyPollMs: 0,
|
|
712
|
+
});
|
|
713
|
+
expect(res.outcome).toBe("still-mismatched");
|
|
714
|
+
// Restarted AT MOST once — no loop.
|
|
715
|
+
const restarts = f.calls.filter((c) => c.includes("kickstart"));
|
|
716
|
+
expect(restarts).toHaveLength(1);
|
|
717
|
+
expect(res.messages.join("\n")).toContain("still not reporting");
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
test("hub not answering /health at all → not-running (no-op, no restart)", async () => {
|
|
721
|
+
const f = fakeDeps({
|
|
722
|
+
platform: "darwin",
|
|
723
|
+
getuid: () => 501,
|
|
724
|
+
installedUnit: true,
|
|
725
|
+
healthVersionSeq: [null], // connection refused / timeout
|
|
726
|
+
});
|
|
727
|
+
const res = await ensureHubVersionMatches({
|
|
728
|
+
installedVersion: INSTALLED,
|
|
729
|
+
port: 1939,
|
|
730
|
+
deps: f.deps,
|
|
731
|
+
readyPollMs: 0,
|
|
732
|
+
});
|
|
733
|
+
expect(res.outcome).toBe("not-running");
|
|
734
|
+
expect(f.calls).toEqual([]);
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
test("mismatch + unit-managed but the restart command fails → restart-failed", async () => {
|
|
738
|
+
const f = fakeDeps({
|
|
739
|
+
platform: "linux",
|
|
740
|
+
getuid: () => 1000,
|
|
741
|
+
installedUnit: true,
|
|
742
|
+
healthVersionSeq: [{ ok: true, version: "0.5.14-rc.4" }],
|
|
743
|
+
run: (cmd) => {
|
|
744
|
+
if (cmd.includes("restart")) {
|
|
745
|
+
return { code: 1, stdout: "", stderr: "Unit parachute-hub.service not found." };
|
|
746
|
+
}
|
|
747
|
+
return { code: 0, stdout: "", stderr: "" };
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
const res = await ensureHubVersionMatches({
|
|
751
|
+
installedVersion: INSTALLED,
|
|
752
|
+
port: 1939,
|
|
753
|
+
deps: f.deps,
|
|
754
|
+
readyPollMs: 0,
|
|
755
|
+
});
|
|
756
|
+
expect(res.outcome).toBe("restart-failed");
|
|
757
|
+
expect(res.messages.join("\n")).toContain("Unit parachute-hub.service not found.");
|
|
758
|
+
});
|
|
759
|
+
});
|