supbuddy 3.1.17 → 3.1.22
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/dist/bin.js +16 -9
- package/dist/daemon/worker.cjs +122 -19
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -19207,6 +19207,12 @@ function startRelayServer(child, opts) {
|
|
|
19207
19207
|
let hostBridgeOwner = null;
|
|
19208
19208
|
let seq2 = 0;
|
|
19209
19209
|
const pending = /* @__PURE__ */ new Map();
|
|
19210
|
+
const sendOwnerReady = () => {
|
|
19211
|
+
try {
|
|
19212
|
+
child.send?.({ type: "host-bridge:owner-ready" });
|
|
19213
|
+
} catch {
|
|
19214
|
+
}
|
|
19215
|
+
};
|
|
19210
19216
|
const failAllPending = (reason) => {
|
|
19211
19217
|
for (const [, p] of pending) {
|
|
19212
19218
|
clearTimeout(p.timer);
|
|
@@ -19225,17 +19231,11 @@ function startRelayServer(child, opts) {
|
|
|
19225
19231
|
}
|
|
19226
19232
|
});
|
|
19227
19233
|
socket2.on("host-bridge:claim", () => {
|
|
19228
|
-
const hadOwner = hostBridgeOwner !== null;
|
|
19229
19234
|
if (hostBridgeOwner && hostBridgeOwner.id !== socket2.id) {
|
|
19230
19235
|
hostBridgeOwner.emit("host-bridge:revoked");
|
|
19231
19236
|
}
|
|
19232
19237
|
hostBridgeOwner = socket2;
|
|
19233
|
-
|
|
19234
|
-
try {
|
|
19235
|
-
child.send?.({ type: "host-bridge:owner-ready" });
|
|
19236
|
-
} catch {
|
|
19237
|
-
}
|
|
19238
|
-
}
|
|
19238
|
+
sendOwnerReady();
|
|
19239
19239
|
});
|
|
19240
19240
|
socket2.on("host-bridge:reply", ({ correlationId, reply }) => {
|
|
19241
19241
|
const p = pending.get(correlationId);
|
|
@@ -19265,6 +19265,10 @@ function startRelayServer(child, opts) {
|
|
|
19265
19265
|
});
|
|
19266
19266
|
};
|
|
19267
19267
|
const onChildMessage = (m) => {
|
|
19268
|
+
if (m?.type === "host-bridge:owner?") {
|
|
19269
|
+
if (hostBridgeOwner) sendOwnerReady();
|
|
19270
|
+
return;
|
|
19271
|
+
}
|
|
19268
19272
|
if (isHostBridgeMessage(m)) return;
|
|
19269
19273
|
io2.emit("worker:message", m);
|
|
19270
19274
|
};
|
|
@@ -19803,7 +19807,7 @@ function cliBuildKind(env3 = process.env) {
|
|
|
19803
19807
|
return raw === "host" || raw === "npm" ? raw : "dev";
|
|
19804
19808
|
}
|
|
19805
19809
|
function cliVersion(env3 = process.env) {
|
|
19806
|
-
return "3.1.
|
|
19810
|
+
return "3.1.22".trim() || "0.0.0-dev";
|
|
19807
19811
|
}
|
|
19808
19812
|
function isDevBuild(env3) {
|
|
19809
19813
|
return cliBuildKind(env3) === "dev";
|
|
@@ -39154,7 +39158,10 @@ async function probe443(timeoutMs = 1e3) {
|
|
|
39154
39158
|
}
|
|
39155
39159
|
async function probePort(port, timeoutMs = 1e3) {
|
|
39156
39160
|
const results = await Promise.all(["127.0.0.1", "::1"].map((host) => probeHost(host, port, timeoutMs)));
|
|
39157
|
-
|
|
39161
|
+
if (results.some(Boolean)) return true;
|
|
39162
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
39163
|
+
const confirm = await Promise.all(["127.0.0.1", "::1"].map((host) => probeHost(host, port, timeoutMs)));
|
|
39164
|
+
return confirm.some(Boolean);
|
|
39158
39165
|
}
|
|
39159
39166
|
async function probeHost(host, port, timeoutMs) {
|
|
39160
39167
|
const net2 = await import("net");
|
package/dist/daemon/worker.cjs
CHANGED
|
@@ -30248,7 +30248,10 @@ async function probe443(timeoutMs = 1e3) {
|
|
|
30248
30248
|
}
|
|
30249
30249
|
async function probePort(port, timeoutMs = 1e3) {
|
|
30250
30250
|
const results = await Promise.all(["127.0.0.1", "::1"].map((host) => probeHost(host, port, timeoutMs)));
|
|
30251
|
-
|
|
30251
|
+
if (results.some(Boolean)) return true;
|
|
30252
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
30253
|
+
const confirm2 = await Promise.all(["127.0.0.1", "::1"].map((host) => probeHost(host, port, timeoutMs)));
|
|
30254
|
+
return confirm2.some(Boolean);
|
|
30252
30255
|
}
|
|
30253
30256
|
async function probeHost(host, port, timeoutMs) {
|
|
30254
30257
|
const net2 = await __vitePreload(() => import("net"), false ? __VITE_PRELOAD__ : void 0);
|
|
@@ -45400,6 +45403,8 @@ const executors = {
|
|
|
45400
45403
|
resolversReapplied: false,
|
|
45401
45404
|
resolverError: `${resolvers.error ?? resolvers.outcome}. Missing: [${(resolvers.missing ?? []).join(", ")}]. Those domains will not resolve until the files exist.`
|
|
45402
45405
|
} : {};
|
|
45406
|
+
const anchor = await globalThis.reapplyPfAnchorIfDrifted?.("restart_proxy");
|
|
45407
|
+
const anchorReport = anchor && anchor.ok === false ? { pfAnchorUpdated: false, pfAnchorError: `${anchor.error ?? anchor.outcome}` } : anchor?.outcome === "applied" ? { pfAnchorUpdated: true } : {};
|
|
45403
45408
|
const { getPortForwardingStatus: getPortForwardingStatus2, probe443: probe4432, waitForPort: waitForPort2 } = await __vitePreload(async () => {
|
|
45404
45409
|
const { getPortForwardingStatus: getPortForwardingStatus3, probe443: probe4433, waitForPort: waitForPort3 } = await Promise.resolve().then(() => portForwarding);
|
|
45405
45410
|
return { getPortForwardingStatus: getPortForwardingStatus3, probe443: probe4433, waitForPort: waitForPort3 };
|
|
@@ -45408,14 +45413,14 @@ const executors = {
|
|
|
45408
45413
|
const caddyUp = await waitForPort2(httpsPort).catch(() => false);
|
|
45409
45414
|
const pf = caddyUp ? await getPortForwardingStatus2(httpPort, httpsPort, { probe: probe4432 }).catch(() => null) : null;
|
|
45410
45415
|
if (!caddyUp) {
|
|
45411
|
-
return { ok: true, ...resolverReport, portForwardingChecked: false, note: `Caddy is not accepting on ${httpsPort}, so port forwarding was not verified.` };
|
|
45416
|
+
return { ok: true, ...resolverReport, ...anchorReport, portForwardingChecked: false, note: `Caddy is not accepting on ${httpsPort}, so port forwarding was not verified.` };
|
|
45412
45417
|
}
|
|
45413
45418
|
if (pf?.enabled && pf.enforced === false) {
|
|
45414
45419
|
const repair = globalThis.repairPortForwarding;
|
|
45415
45420
|
const r = repair ? await repair({ prompt: "Supbuddy needs to re-apply the port-forwarding rules." }).catch((e) => ({ ok: false, error: e instanceof Error ? e.message : String(e) })) : { ok: false, error: "pf repair is unavailable in this process" };
|
|
45416
|
-
return { ok: true, ...resolverReport, portForwardingRepaired: r.ok, ...r.ok ? {} : { portForwardingError: r.error } };
|
|
45421
|
+
return { ok: true, ...resolverReport, ...anchorReport, portForwardingRepaired: r.ok, ...r.ok ? {} : { portForwardingError: r.error } };
|
|
45417
45422
|
}
|
|
45418
|
-
return { ok: true, ...resolverReport };
|
|
45423
|
+
return { ok: true, ...resolverReport, ...anchorReport };
|
|
45419
45424
|
} finally {
|
|
45420
45425
|
globalThis.scheduleDoctorAutoScan?.("proxy-restart", PROXY_RESCAN_SETTLE_MS);
|
|
45421
45426
|
}
|
|
@@ -50321,6 +50326,10 @@ function buildPfAnchor(input) {
|
|
|
50321
50326
|
}
|
|
50322
50327
|
return lines.join("\n") + "\n";
|
|
50323
50328
|
}
|
|
50329
|
+
function pfAnchorDrifted(desired, actual) {
|
|
50330
|
+
if (actual === null) return true;
|
|
50331
|
+
return actual !== desired;
|
|
50332
|
+
}
|
|
50324
50333
|
const execAsync$5 = util$1.promisify(child_process.exec);
|
|
50325
50334
|
const execFileAsync$2 = util$1.promisify(child_process.execFile);
|
|
50326
50335
|
const MIGRATE_DIR = path.join(os$1.tmpdir(), "supbuddy-migrate");
|
|
@@ -51351,6 +51360,7 @@ async function applyProjectActivation(projectId, opts) {
|
|
|
51351
51360
|
}
|
|
51352
51361
|
globalThis.applyProjectActivation = applyProjectActivation;
|
|
51353
51362
|
globalThis.reapplyDnsResolversIfDrifted = reapplyDnsResolversIfDrifted;
|
|
51363
|
+
globalThis.reapplyPfAnchorIfDrifted = reapplyPfAnchorIfDrifted;
|
|
51354
51364
|
globalThis.repairPortForwarding = repairPortForwarding;
|
|
51355
51365
|
async function testPortForwarding(_httpsPort) {
|
|
51356
51366
|
return probe443();
|
|
@@ -51574,9 +51584,15 @@ async function requestPrivilegedBatch(command, prompt, tmpFiles = []) {
|
|
|
51574
51584
|
process.on("message", handler);
|
|
51575
51585
|
});
|
|
51576
51586
|
}
|
|
51587
|
+
let privilegedRepairAttempted = false;
|
|
51577
51588
|
let guiProxyAutoStartDone = false;
|
|
51578
51589
|
let lastPrivilegedError = null;
|
|
51590
|
+
let privilegedWorkPending = null;
|
|
51579
51591
|
const TRANSIENT_PRIVILEGED_FAILURE = /TTY|ASKPASS|detached/i;
|
|
51592
|
+
function shouldFinishPrivilegedSetup(proxyState, privilegedError, workPending) {
|
|
51593
|
+
if (shouldRetryPrivilegedSetup(proxyState, privilegedError)) return true;
|
|
51594
|
+
return TRANSIENT_PRIVILEGED_FAILURE.test(workPending || "");
|
|
51595
|
+
}
|
|
51580
51596
|
function shouldRetryPrivilegedSetup(proxyState, privilegedError) {
|
|
51581
51597
|
if (!proxyState.needsPrivilegedRepair) return false;
|
|
51582
51598
|
return TRANSIENT_PRIVILEGED_FAILURE.test(privilegedError || "");
|
|
@@ -51587,14 +51603,24 @@ function isPfRulesetRejection(err, stderr) {
|
|
|
51587
51603
|
return stderr.trim().length > 0;
|
|
51588
51604
|
}
|
|
51589
51605
|
async function maybeAutoStartProxyForGui() {
|
|
51590
|
-
if (guiProxyAutoStartDone) return;
|
|
51591
51606
|
const store2 = useStore.getState();
|
|
51592
|
-
if (!store2.settings.autoStart) return;
|
|
51593
51607
|
const status = store2.proxyState.status;
|
|
51608
|
+
if (privilegedWorkPending && !privilegedRepairAttempted) {
|
|
51609
|
+
console.log(
|
|
51610
|
+
`[Proxy] owner-ready: status=${status} repairAttempted=${privilegedRepairAttempted} needsRepair=${!!store2.proxyState.needsPrivilegedRepair} pending=${JSON.stringify(privilegedWorkPending)}`
|
|
51611
|
+
);
|
|
51612
|
+
}
|
|
51613
|
+
if (status === "running" && !privilegedRepairAttempted && shouldFinishPrivilegedSetup(store2.proxyState, lastPrivilegedError, privilegedWorkPending)) {
|
|
51614
|
+
privilegedRepairAttempted = true;
|
|
51615
|
+
await retryPrivilegedSetupOnce("proxy is running but its privileged setup never ran (no GUI owner at boot)");
|
|
51616
|
+
return;
|
|
51617
|
+
}
|
|
51618
|
+
if (guiProxyAutoStartDone) return;
|
|
51594
51619
|
if (status === "running" || status === "starting") {
|
|
51595
51620
|
guiProxyAutoStartDone = true;
|
|
51596
51621
|
return;
|
|
51597
51622
|
}
|
|
51623
|
+
if (!store2.settings.autoStart) return;
|
|
51598
51624
|
guiProxyAutoStartDone = true;
|
|
51599
51625
|
console.log("[Proxy] GUI host-bridge owner ready — auto-starting proxy (autoStart on)");
|
|
51600
51626
|
try {
|
|
@@ -51604,24 +51630,25 @@ async function maybeAutoStartProxyForGui() {
|
|
|
51604
51630
|
}
|
|
51605
51631
|
const afterFirst = useStore.getState().proxyState;
|
|
51606
51632
|
if (shouldRetryPrivilegedSetup(afterFirst, lastPrivilegedError)) {
|
|
51607
|
-
|
|
51608
|
-
|
|
51633
|
+
await retryPrivilegedSetupOnce("auto-start hit the host-bridge owner race (TTY/ASKPASS)");
|
|
51634
|
+
}
|
|
51635
|
+
}
|
|
51636
|
+
async function retryPrivilegedSetupOnce(reason) {
|
|
51637
|
+
console.warn(`[Proxy] ${reason} — retrying privileged setup once in 2s`);
|
|
51638
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
51639
|
+
try {
|
|
51640
|
+
await handleStartProxy();
|
|
51641
|
+
const after = useStore.getState().proxyState;
|
|
51642
|
+
console.log(
|
|
51643
|
+
after.needsPrivilegedRepair ? "[Proxy] privileged retry still degraded — setup failed again (manual Retry remains available)" : "[Proxy] privileged retry recovered — setup applied"
|
|
51609
51644
|
);
|
|
51610
|
-
|
|
51611
|
-
|
|
51612
|
-
await handleStartProxy();
|
|
51613
|
-
const afterRetry = useStore.getState().proxyState;
|
|
51614
|
-
console.log(
|
|
51615
|
-
afterRetry.needsPrivilegedRepair ? "[Proxy] auto-start retry still degraded — privileged setup failed again (manual Retry remains available)" : "[Proxy] auto-start retry recovered — privileged setup applied"
|
|
51616
|
-
);
|
|
51617
|
-
} catch (e) {
|
|
51618
|
-
console.error("[Proxy] auto-start retry failed:", e);
|
|
51619
|
-
}
|
|
51645
|
+
} catch (e) {
|
|
51646
|
+
console.error("[Proxy] privileged retry failed:", e);
|
|
51620
51647
|
}
|
|
51621
51648
|
}
|
|
51622
51649
|
async function handleStartProxy() {
|
|
51623
51650
|
const alreadyRunning = isCaddyRunning();
|
|
51624
|
-
const repairPending = !!useStore.getState().proxyState.needsPrivilegedRepair;
|
|
51651
|
+
const repairPending = !!useStore.getState().proxyState.needsPrivilegedRepair || !!privilegedWorkPending;
|
|
51625
51652
|
if (alreadyRunning && !repairPending) {
|
|
51626
51653
|
const { probeProxyReachability: probeProxyReachability2 } = await __vitePreload(async () => {
|
|
51627
51654
|
const { probeProxyReachability: probeProxyReachability3 } = await Promise.resolve().then(() => dnsPlatform);
|
|
@@ -51751,11 +51778,13 @@ async function handleStartProxy() {
|
|
|
51751
51778
|
});
|
|
51752
51779
|
if (!sudoResult.success) {
|
|
51753
51780
|
privilegedFailure = sudoResult.error || "unknown error";
|
|
51781
|
+
privilegedWorkPending = privilegedFailure;
|
|
51754
51782
|
console.error(
|
|
51755
51783
|
`[Proxy] privileged setup failed (continuing degraded — Caddy still starts): ${privilegedFailure}`
|
|
51756
51784
|
);
|
|
51757
51785
|
} else {
|
|
51758
51786
|
console.log("[Proxy] sudo setup result: success");
|
|
51787
|
+
privilegedWorkPending = null;
|
|
51759
51788
|
const resolverAudit = await auditResolverState().catch(() => null);
|
|
51760
51789
|
if (resolverAudit && resolverAudit.in_sync === false && resolverAudit.missing.length > 0) {
|
|
51761
51790
|
privilegedFailure = `DNS resolver files missing after privileged setup: ${resolverAudit.missing.join(", ")}`;
|
|
@@ -51764,6 +51793,7 @@ async function handleStartProxy() {
|
|
|
51764
51793
|
}
|
|
51765
51794
|
} catch (privErr) {
|
|
51766
51795
|
privilegedFailure = privErr?.message || String(privErr);
|
|
51796
|
+
privilegedWorkPending = privilegedFailure;
|
|
51767
51797
|
console.error(
|
|
51768
51798
|
"[Proxy] privileged setup threw (continuing degraded — Caddy still starts):",
|
|
51769
51799
|
privilegedFailure
|
|
@@ -51921,6 +51951,75 @@ async function repairPortForwarding(opts) {
|
|
|
51921
51951
|
await clearProxyFaultIfHealthy("a confirmed pf repair");
|
|
51922
51952
|
return { ok: true, enforced: true };
|
|
51923
51953
|
}
|
|
51954
|
+
async function reapplyPfAnchorIfDrifted(reason) {
|
|
51955
|
+
const store2 = useStore.getState();
|
|
51956
|
+
const { httpPort, httpsPort, lanSharing } = store2.settings;
|
|
51957
|
+
const desired = buildPfAnchor({
|
|
51958
|
+
httpPort,
|
|
51959
|
+
httpsPort,
|
|
51960
|
+
lanSharing,
|
|
51961
|
+
lanInterfaces: lanSharing ? getLanInterfaces() : []
|
|
51962
|
+
});
|
|
51963
|
+
let actual = null;
|
|
51964
|
+
try {
|
|
51965
|
+
actual = fsSync.readFileSync("/etc/pf.anchors/virtual.localhost", "utf-8");
|
|
51966
|
+
} catch {
|
|
51967
|
+
actual = null;
|
|
51968
|
+
}
|
|
51969
|
+
let pfConf = "";
|
|
51970
|
+
try {
|
|
51971
|
+
pfConf = fsSync.readFileSync("/etc/pf.conf", "utf-8");
|
|
51972
|
+
} catch {
|
|
51973
|
+
pfConf = "";
|
|
51974
|
+
}
|
|
51975
|
+
if (!pfConf.includes("virtual.localhost")) return { ok: true, outcome: "not-configured" };
|
|
51976
|
+
if (!pfAnchorDrifted(desired, actual)) return { ok: true, outcome: "no-drift" };
|
|
51977
|
+
console.log(`[Proxy] pf anchor is out of date (${reason}) — rewriting`);
|
|
51978
|
+
const tmpSuffix = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
51979
|
+
const tmpAnchor = path.join(os$1.tmpdir(), `supbuddy-pf-anchor-${tmpSuffix}`);
|
|
51980
|
+
const tmpValidation = path.join(os$1.tmpdir(), `supbuddy-pf-validate-${tmpSuffix}`);
|
|
51981
|
+
fsSync.writeFileSync(tmpAnchor, desired);
|
|
51982
|
+
try {
|
|
51983
|
+
const { content: correctedPfConf } = buildCorrectedPfConf(pfConf);
|
|
51984
|
+
fsSync.writeFileSync(tmpValidation, buildValidationPfConf(correctedPfConf, tmpAnchor));
|
|
51985
|
+
try {
|
|
51986
|
+
child_process.execSync(`pfctl -nf "${tmpValidation}"`, { stdio: "pipe" });
|
|
51987
|
+
} catch (validationErr) {
|
|
51988
|
+
const stderr = (validationErr?.stderr?.toString() ?? "").trim();
|
|
51989
|
+
if (isPfRulesetRejection(validationErr, stderr)) {
|
|
51990
|
+
console.error(`[Proxy] refusing to install pf anchor — ruleset rejected: ${stderr}`);
|
|
51991
|
+
return { ok: false, outcome: "invalid", error: stderr || "pf rejected the generated ruleset" };
|
|
51992
|
+
}
|
|
51993
|
+
}
|
|
51994
|
+
if (!process.send) return { ok: false, outcome: "no-bridge", error: "no host bridge in this process" };
|
|
51995
|
+
const res = await defaultElevate$1({
|
|
51996
|
+
command: `cp ${JSON.stringify(tmpAnchor)} /etc/pf.anchors/virtual.localhost && ${buildPfLoadCommand({ restoreBackupOnFailure: false })}`,
|
|
51997
|
+
prompt: "Supbuddy needs to update its port-forwarding rules."
|
|
51998
|
+
}).catch((e) => ({ success: false, error: e instanceof Error ? e.message : String(e) }));
|
|
51999
|
+
if (!res.success) {
|
|
52000
|
+
console.warn(`[Proxy] pf anchor rewrite failed: ${res.error ?? "unknown error"}`);
|
|
52001
|
+
return { ok: false, outcome: "failed", error: res.error };
|
|
52002
|
+
}
|
|
52003
|
+
let after = null;
|
|
52004
|
+
try {
|
|
52005
|
+
after = fsSync.readFileSync("/etc/pf.anchors/virtual.localhost", "utf-8");
|
|
52006
|
+
} catch {
|
|
52007
|
+
after = null;
|
|
52008
|
+
}
|
|
52009
|
+
if (pfAnchorDrifted(desired, after)) {
|
|
52010
|
+
return { ok: false, outcome: "failed", error: "the elevated write reported success but the anchor on disk is unchanged" };
|
|
52011
|
+
}
|
|
52012
|
+
console.log("[Proxy] pf anchor rewritten and reloaded");
|
|
52013
|
+
return { ok: true, outcome: "applied" };
|
|
52014
|
+
} finally {
|
|
52015
|
+
for (const f2 of [tmpAnchor, tmpValidation]) {
|
|
52016
|
+
try {
|
|
52017
|
+
fsSync.unlinkSync(f2);
|
|
52018
|
+
} catch {
|
|
52019
|
+
}
|
|
52020
|
+
}
|
|
52021
|
+
}
|
|
52022
|
+
}
|
|
51924
52023
|
async function clearProxyFaultIfHealthy(reason) {
|
|
51925
52024
|
const store2 = useStore.getState();
|
|
51926
52025
|
if (store2.proxyState.status !== "error") return;
|
|
@@ -54588,6 +54687,10 @@ async function init() {
|
|
|
54588
54687
|
process.exit(0);
|
|
54589
54688
|
}
|
|
54590
54689
|
};
|
|
54690
|
+
try {
|
|
54691
|
+
process.send?.({ type: "host-bridge:owner?" });
|
|
54692
|
+
} catch {
|
|
54693
|
+
}
|
|
54591
54694
|
process.on("SIGINT", () => cleanup());
|
|
54592
54695
|
process.on("SIGTERM", () => cleanup());
|
|
54593
54696
|
shutdownHandler = async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supbuddy",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.22",
|
|
4
4
|
"description": "Run multiple Supabase projects at once on custom local domains with HTTPS. A headless CLI and daemon (proxy, DNS, Supabase/Compose lifecycle, MCP) for macOS and Linux.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"supabase",
|