chore: log gateway reload and signals
This commit is contained in:
@@ -22,13 +22,17 @@ import {
|
||||
setGatewayWsLogStyle,
|
||||
} from "../gateway/ws-logging.js";
|
||||
import { setVerbose } from "../globals.js";
|
||||
import type { GatewayBonjourBeacon } from "../infra/bonjour-discovery.js";
|
||||
import { discoverGatewayBeacons } from "../infra/bonjour-discovery.js";
|
||||
import { GatewayLockError } from "../infra/gateway-lock.js";
|
||||
import { formatPortDiagnostics, inspectPortUsage } from "../infra/ports.js";
|
||||
import { WIDE_AREA_DISCOVERY_DOMAIN } from "../infra/widearea-dns.js";
|
||||
import {
|
||||
createSubsystemLogger,
|
||||
setConsoleSubsystemFilter,
|
||||
} from "../logging.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { colorize, isRich, theme } from "../terminal/theme.js";
|
||||
import { forceFreePortAndWait } from "./ports.js";
|
||||
import { withProgress } from "./progress.js";
|
||||
|
||||
@@ -87,6 +91,103 @@ const toOptionString = (value: unknown): string | undefined => {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
type GatewayDiscoverOpts = {
|
||||
timeout?: string;
|
||||
json?: boolean;
|
||||
};
|
||||
|
||||
function parseDiscoverTimeoutMs(raw: unknown, fallbackMs: number): number {
|
||||
if (raw === undefined || raw === null) return fallbackMs;
|
||||
const value = typeof raw === "string" ? raw.trim() : String(raw);
|
||||
if (!value) return fallbackMs;
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
throw new Error(`invalid --timeout: ${value}`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function pickBeaconHost(beacon: GatewayBonjourBeacon): string | null {
|
||||
const host = beacon.tailnetDns || beacon.lanHost || beacon.host;
|
||||
return host?.trim() ? host.trim() : null;
|
||||
}
|
||||
|
||||
function pickGatewayPort(beacon: GatewayBonjourBeacon): number {
|
||||
const port = beacon.gatewayPort ?? 18789;
|
||||
return port > 0 ? port : 18789;
|
||||
}
|
||||
|
||||
function dedupeBeacons(
|
||||
beacons: GatewayBonjourBeacon[],
|
||||
): GatewayBonjourBeacon[] {
|
||||
const out: GatewayBonjourBeacon[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const b of beacons) {
|
||||
const host = pickBeaconHost(b) ?? "";
|
||||
const key = [
|
||||
b.domain ?? "",
|
||||
b.instanceName ?? "",
|
||||
b.displayName ?? "",
|
||||
host,
|
||||
String(b.port ?? ""),
|
||||
String(b.bridgePort ?? ""),
|
||||
String(b.gatewayPort ?? ""),
|
||||
].join("|");
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
out.push(b);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function renderBeaconLines(
|
||||
beacon: GatewayBonjourBeacon,
|
||||
rich: boolean,
|
||||
): string[] {
|
||||
const nameRaw = (
|
||||
beacon.displayName ||
|
||||
beacon.instanceName ||
|
||||
"Gateway"
|
||||
).trim();
|
||||
const domainRaw = (beacon.domain || "local.").trim();
|
||||
|
||||
const title = colorize(rich, theme.accentBright, nameRaw);
|
||||
const domain = colorize(rich, theme.muted, domainRaw);
|
||||
|
||||
const parts: string[] = [];
|
||||
if (beacon.tailnetDns)
|
||||
parts.push(
|
||||
`${colorize(rich, theme.info, "tailnet")}: ${beacon.tailnetDns}`,
|
||||
);
|
||||
if (beacon.lanHost)
|
||||
parts.push(`${colorize(rich, theme.info, "lan")}: ${beacon.lanHost}`);
|
||||
if (beacon.host)
|
||||
parts.push(`${colorize(rich, theme.info, "host")}: ${beacon.host}`);
|
||||
|
||||
const host = pickBeaconHost(beacon);
|
||||
const gatewayPort = pickGatewayPort(beacon);
|
||||
const wsUrl = host ? `ws://${host}:${gatewayPort}` : null;
|
||||
|
||||
const firstLine =
|
||||
parts.length > 0
|
||||
? `${title} ${domain} · ${parts.join(" · ")}`
|
||||
: `${title} ${domain}`;
|
||||
|
||||
const lines = [`- ${firstLine}`];
|
||||
if (wsUrl) {
|
||||
lines.push(
|
||||
` ${colorize(rich, theme.muted, "ws")}: ${colorize(rich, theme.command, wsUrl)}`,
|
||||
);
|
||||
}
|
||||
if (typeof beacon.sshPort === "number" && beacon.sshPort > 0 && host) {
|
||||
const ssh = `ssh -N -L 18789:127.0.0.1:18789 <user>@${host} -p ${beacon.sshPort}`;
|
||||
lines.push(
|
||||
` ${colorize(rich, theme.muted, "ssh")}: ${colorize(rich, theme.command, ssh)}`,
|
||||
);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function describeUnknownError(err: unknown): string {
|
||||
if (err instanceof Error) return err.message;
|
||||
if (typeof err === "string") return err;
|
||||
@@ -219,9 +320,18 @@ async function runGatewayLoop(params: {
|
||||
})();
|
||||
};
|
||||
|
||||
const onSigterm = () => request("stop", "SIGTERM");
|
||||
const onSigint = () => request("stop", "SIGINT");
|
||||
const onSigusr1 = () => request("restart", "SIGUSR1");
|
||||
const onSigterm = () => {
|
||||
gatewayLog.info("signal SIGTERM received");
|
||||
request("stop", "SIGTERM");
|
||||
};
|
||||
const onSigint = () => {
|
||||
gatewayLog.info("signal SIGINT received");
|
||||
request("stop", "SIGINT");
|
||||
};
|
||||
const onSigusr1 = () => {
|
||||
gatewayLog.info("signal SIGUSR1 received");
|
||||
request("restart", "SIGUSR1");
|
||||
};
|
||||
|
||||
process.on("SIGTERM", onSigterm);
|
||||
process.on("SIGINT", onSigint);
|
||||
@@ -658,4 +768,75 @@ export function registerGatewayCli(program: Command) {
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
gateway
|
||||
.command("discover")
|
||||
.description(
|
||||
`Discover gateways via Bonjour (multicast local. + unicast ${WIDE_AREA_DISCOVERY_DOMAIN})`,
|
||||
)
|
||||
.option("--timeout <ms>", "Per-command timeout in ms", "2000")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts: GatewayDiscoverOpts) => {
|
||||
try {
|
||||
const timeoutMs = parseDiscoverTimeoutMs(opts.timeout, 2000);
|
||||
const beacons = await withProgress(
|
||||
{
|
||||
label: "Scanning for gateways…",
|
||||
indeterminate: true,
|
||||
enabled: opts.json !== true,
|
||||
},
|
||||
async () => await discoverGatewayBeacons({ timeoutMs }),
|
||||
);
|
||||
|
||||
const deduped = dedupeBeacons(beacons).sort((a, b) =>
|
||||
String(a.displayName || a.instanceName).localeCompare(
|
||||
String(b.displayName || b.instanceName),
|
||||
),
|
||||
);
|
||||
|
||||
if (opts.json) {
|
||||
const enriched = deduped.map((b) => {
|
||||
const host = pickBeaconHost(b);
|
||||
const port = pickGatewayPort(b);
|
||||
return {
|
||||
...b,
|
||||
wsUrl: host ? `ws://${host}:${port}` : null,
|
||||
};
|
||||
});
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
timeoutMs,
|
||||
domains: ["local.", WIDE_AREA_DISCOVERY_DOMAIN],
|
||||
count: enriched.length,
|
||||
beacons: enriched,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const rich = isRich();
|
||||
defaultRuntime.log(colorize(rich, theme.heading, "Gateway Discovery"));
|
||||
defaultRuntime.log(
|
||||
colorize(
|
||||
rich,
|
||||
theme.muted,
|
||||
`Found ${deduped.length} gateway(s) · domains: local., ${WIDE_AREA_DISCOVERY_DOMAIN}`,
|
||||
),
|
||||
);
|
||||
if (deduped.length === 0) return;
|
||||
|
||||
for (const beacon of deduped) {
|
||||
for (const line of renderBeaconLines(beacon, rich)) {
|
||||
defaultRuntime.log(line);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
defaultRuntime.error(`gateway discover failed: ${String(err)}`);
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user