fix: auto-detect tailnet DNS hint

This commit is contained in:
Peter Steinberger
2025-12-20 14:23:53 +01:00
parent 082b4fb193
commit 3e39dd49aa
5 changed files with 65 additions and 26 deletions

View File

@@ -77,6 +77,7 @@ import {
pickPrimaryTailnetIPv4,
pickPrimaryTailnetIPv6,
} from "../infra/tailnet.js";
import { getTailnetHostname } from "../infra/tailscale.js";
import {
defaultVoiceWakeTriggers,
loadVoiceWakeConfig,
@@ -89,6 +90,7 @@ import {
import { logError, logInfo, logWarn } from "../logger.js";
import { getChildLogger, getResolvedLoggerSettings } from "../logging.js";
import { setCommandLaneConcurrency } from "../process/command-queue.js";
import { runExec } from "../process/exec.js";
import { monitorWebProvider, webAuthExists } from "../providers/web/index.js";
import { defaultRuntime } from "../runtime.js";
import { monitorTelegramProvider } from "../telegram/monitor.js";
@@ -174,6 +176,20 @@ function formatBonjourInstanceName(displayName: string) {
return `${trimmed} (Clawdis)`;
}
async function resolveTailnetDnsHint(): Promise<string | undefined> {
const envRaw = process.env.CLAWDIS_TAILNET_DNS?.trim();
const env = envRaw && envRaw.length > 0 ? envRaw.replace(/\.$/, "") : "";
if (env) return env;
const exec: typeof runExec = (command, args) =>
runExec(command, args, { timeoutMs: 1500, maxBuffer: 200_000 });
try {
return await getTailnetHostname(exec);
} catch {
return undefined;
}
}
type GatewaySessionsDefaults = {
model: string | null;
contextTokens: number | null;
@@ -2048,12 +2064,7 @@ export async function startGatewayServer(
? sshPortParsed
: undefined;
const tailnetDnsEnv = process.env.CLAWDIS_TAILNET_DNS?.trim();
const tailnetDns = wideAreaDiscoveryEnabled
? WIDE_AREA_DISCOVERY_DOMAIN
: tailnetDnsEnv && tailnetDnsEnv.length > 0
? tailnetDnsEnv
: undefined;
const tailnetDns = await resolveTailnetDnsHint();
const bonjour = await startGatewayBonjourAdvertiser({
instanceName: formatBonjourInstanceName(machineDisplayName),

View File

@@ -1,3 +1,4 @@
import { existsSync } from "node:fs";
import chalk from "chalk";
import { promptYesNo } from "../cli/prompt.js";
import { danger, info, isVerbose, logVerbose, warn } from "../globals.js";
@@ -7,20 +8,37 @@ import { ensureBinary } from "./binaries.js";
export async function getTailnetHostname(exec: typeof runExec = runExec) {
// Derive tailnet hostname (or IP fallback) from tailscale status JSON.
const { stdout } = await exec("tailscale", ["status", "--json"]);
const parsed = stdout ? (JSON.parse(stdout) as Record<string, unknown>) : {};
const self =
typeof parsed.Self === "object" && parsed.Self !== null
? (parsed.Self as Record<string, unknown>)
: undefined;
const dns =
typeof self?.DNSName === "string" ? (self.DNSName as string) : undefined;
const ips = Array.isArray(self?.TailscaleIPs)
? (self.TailscaleIPs as string[])
: [];
if (dns && dns.length > 0) return dns.replace(/\.$/, "");
if (ips.length > 0) return ips[0];
throw new Error("Could not determine Tailscale DNS or IP");
const candidates = [
"tailscale",
"/Applications/Tailscale.app/Contents/MacOS/Tailscale",
];
let lastError: unknown;
for (const candidate of candidates) {
if (candidate.startsWith("/") && !existsSync(candidate)) continue;
try {
const { stdout } = await exec(candidate, ["status", "--json"]);
const parsed = stdout
? (JSON.parse(stdout) as Record<string, unknown>)
: {};
const self =
typeof parsed.Self === "object" && parsed.Self !== null
? (parsed.Self as Record<string, unknown>)
: undefined;
const dns =
typeof self?.DNSName === "string" ? (self.DNSName as string) : undefined;
const ips = Array.isArray(self?.TailscaleIPs)
? (self.TailscaleIPs as string[])
: [];
if (dns && dns.length > 0) return dns.replace(/\.$/, "");
if (ips.length > 0) return ips[0];
throw new Error("Could not determine Tailscale DNS or IP");
} catch (err) {
lastError = err;
}
}
throw lastError ?? new Error("Could not determine Tailscale DNS or IP");
}
export async function ensureGoInstalled(