refactor: de-duplicate channel runtime and payload helpers

This commit is contained in:
Peter Steinberger
2026-02-23 21:25:20 +00:00
parent 0ae7f470a2
commit 0183610db3
44 changed files with 775 additions and 698 deletions

View File

@@ -1,6 +1,8 @@
import os from "node:os";
import type { OpenClawConfig } from "../config/types.js";
import { resolveGatewayBindUrl } from "../shared/gateway-bind-url.js";
import { isCarrierGradeNatIpv4Address, isRfc1918Ipv4Address } from "../shared/net/ip.js";
import { resolveTailnetHostWithRunner } from "../shared/tailscale-status.js";
const DEFAULT_GATEWAY_PORT = 18789;
@@ -161,58 +163,6 @@ function pickTailnetIPv4(
return pickIPv4Matching(networkInterfaces, isTailnetIPv4);
}
function parsePossiblyNoisyJsonObject(raw: string): Record<string, unknown> {
const start = raw.indexOf("{");
const end = raw.lastIndexOf("}");
if (start === -1 || end <= start) {
return {};
}
try {
return JSON.parse(raw.slice(start, end + 1)) as Record<string, unknown>;
} catch {
return {};
}
}
async function resolveTailnetHost(
runCommandWithTimeout?: PairingSetupCommandRunner,
): Promise<string | null> {
if (!runCommandWithTimeout) {
return null;
}
const candidates = ["tailscale", "/Applications/Tailscale.app/Contents/MacOS/Tailscale"];
for (const candidate of candidates) {
try {
const result = await runCommandWithTimeout([candidate, "status", "--json"], {
timeoutMs: 5000,
});
if (result.code !== 0) {
continue;
}
const raw = result.stdout.trim();
if (!raw) {
continue;
}
const parsed = parsePossiblyNoisyJsonObject(raw);
const self =
typeof parsed.Self === "object" && parsed.Self !== null
? (parsed.Self as Record<string, unknown>)
: undefined;
const dns = typeof self?.DNSName === "string" ? self.DNSName : undefined;
if (dns && dns.length > 0) {
return dns.replace(/\.$/, "");
}
const ips = Array.isArray(self?.TailscaleIPs) ? (self.TailscaleIPs as string[]) : [];
if (ips.length > 0) {
return ips[0] ?? null;
}
} catch {
continue;
}
}
return null;
}
function resolveAuth(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): ResolveAuthResult {
const mode = cfg.gateway?.auth?.mode;
const token =
@@ -278,7 +228,7 @@ async function resolveGatewayUrl(
const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
if (tailscaleMode === "serve" || tailscaleMode === "funnel") {
const host = await resolveTailnetHost(opts.runCommandWithTimeout);
const host = await resolveTailnetHostWithRunner(opts.runCommandWithTimeout);
if (!host) {
return { error: "Tailscale Serve is enabled, but MagicDNS could not be resolved." };
}
@@ -289,29 +239,16 @@ async function resolveGatewayUrl(
return { url: remoteUrl, source: "gateway.remote.url" };
}
const bind = cfg.gateway?.bind ?? "loopback";
if (bind === "custom") {
const host = cfg.gateway?.customBindHost?.trim();
if (host) {
return { url: `${scheme}://${host}:${port}`, source: "gateway.bind=custom" };
}
return { error: "gateway.bind=custom requires gateway.customBindHost." };
}
if (bind === "tailnet") {
const host = pickTailnetIPv4(opts.networkInterfaces);
if (host) {
return { url: `${scheme}://${host}:${port}`, source: "gateway.bind=tailnet" };
}
return { error: "gateway.bind=tailnet set, but no tailnet IP was found." };
}
if (bind === "lan") {
const host = pickLanIPv4(opts.networkInterfaces);
if (host) {
return { url: `${scheme}://${host}:${port}`, source: "gateway.bind=lan" };
}
return { error: "gateway.bind=lan set, but no private LAN IP was found." };
const bindResult = resolveGatewayBindUrl({
bind: cfg.gateway?.bind,
customBindHost: cfg.gateway?.customBindHost,
scheme,
port,
pickTailnetHost: () => pickTailnetIPv4(opts.networkInterfaces),
pickLanHost: () => pickLanIPv4(opts.networkInterfaces),
});
if (bindResult) {
return bindResult;
}
return {