feat: add Tailscale binary detection, IP binding modes, and health probe password fix
This PR includes three main improvements:
1. Tailscale Binary Detection with Fallback Strategies
- Added findTailscaleBinary() with multi-strategy detection:
* PATH lookup via 'which' command
* Known macOS app path (/Applications/Tailscale.app/Contents/MacOS/Tailscale)
* find /Applications for Tailscale.app
* locate database lookup
- Added getTailscaleBinary() with caching
- Updated all Tailscale operations to use detected binary
- Added TUI warning when Tailscale binary not found for serve/funnel modes
2. Custom Gateway IP Binding with Fallback
- New bind mode "custom" allowing user-specified IP with fallback to 0.0.0.0
- Removed "tailnet" mode (folded into "auto")
- All modes now support graceful fallback: custom (if fail → 0.0.0.0), loopback (127.0.0.1 → 0.0.0.0), auto (tailnet → 0.0.0.0), lan (0.0.0.0)
- Added customBindHost config option for custom bind mode
- Added canBindTo() helper to test IP availability before binding
- Updated configure and onboarding wizards with new bind mode options
3. Health Probe Password Auth Fix
- Gateway probe now tries both new and old passwords
- Fixes issue where password change fails health check if gateway hasn't restarted yet
- Uses nextConfig password first, falls back to baseConfig password if needed
Files changed:
- src/infra/tailscale.ts: Binary detection + caching
- src/gateway/net.ts: IP binding with fallback logic
- src/config/types.ts: BridgeBindMode type + customBindHost field
- src/commands/configure.ts: Health probe dual-password try + Tailscale detection warning + bind mode UI
- src/wizard/onboarding.ts: Tailscale detection warning + bind mode UI
- src/gateway/server.ts: Use new resolveGatewayBindHost
- src/gateway/call.ts: Updated preferTailnet logic (removed "tailnet" mode)
- src/commands/onboard-types.ts: Updated GatewayBind type
- src/commands/onboard-helpers.ts: resolveControlUiLinks updated
- src/cli/*.ts: Updated bind mode casts
- src/gateway/call.test.ts: Removed "tailnet" mode test
This commit is contained in:
committed by
Peter Steinberger
parent
f94ad21f1e
commit
c851bdd47a
@@ -71,7 +71,8 @@ type ConfigSummary = {
|
||||
|
||||
type GatewayStatusSummary = {
|
||||
bindMode: BridgeBindMode;
|
||||
bindHost: string | null;
|
||||
bindHost: string;
|
||||
customBindHost?: string;
|
||||
port: number;
|
||||
portSource: "service args" | "env/config";
|
||||
probeUrl: string;
|
||||
@@ -190,8 +191,11 @@ function parsePortFromArgs(
|
||||
function pickProbeHostForBind(
|
||||
bindMode: string,
|
||||
tailnetIPv4: string | undefined,
|
||||
customBindHost?: string,
|
||||
) {
|
||||
if (bindMode === "tailnet") return tailnetIPv4 ?? "127.0.0.1";
|
||||
if (bindMode === "custom" && customBindHost?.trim()) {
|
||||
return customBindHost.trim();
|
||||
}
|
||||
if (bindMode === "auto") return tailnetIPv4 ?? "127.0.0.1";
|
||||
return "127.0.0.1";
|
||||
}
|
||||
@@ -429,11 +433,15 @@ async function gatherDaemonStatus(opts: {
|
||||
const bindMode = (daemonCfg.gateway?.bind ?? "loopback") as
|
||||
| "auto"
|
||||
| "lan"
|
||||
| "tailnet"
|
||||
| "loopback";
|
||||
const bindHost = resolveGatewayBindHost(bindMode);
|
||||
| "loopback"
|
||||
| "custom";
|
||||
const customBindHost = daemonCfg.gateway?.customBindHost;
|
||||
const bindHost = await resolveGatewayBindHost(
|
||||
bindMode,
|
||||
customBindHost,
|
||||
);
|
||||
const tailnetIPv4 = pickPrimaryTailnetIPv4();
|
||||
const probeHost = pickProbeHostForBind(bindMode, tailnetIPv4);
|
||||
const probeHost = pickProbeHostForBind(bindMode, tailnetIPv4, customBindHost);
|
||||
const probeUrlOverride =
|
||||
typeof opts.rpc.url === "string" && opts.rpc.url.trim().length > 0
|
||||
? opts.rpc.url.trim()
|
||||
@@ -523,6 +531,7 @@ async function gatherDaemonStatus(opts: {
|
||||
gateway: {
|
||||
bindMode,
|
||||
bindHost,
|
||||
customBindHost,
|
||||
port: daemonPort,
|
||||
portSource,
|
||||
probeUrl,
|
||||
@@ -651,6 +660,7 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
|
||||
const links = resolveControlUiLinks({
|
||||
port: status.gateway.port,
|
||||
bind: status.gateway.bindMode,
|
||||
customBindHost: status.gateway.customBindHost,
|
||||
basePath: status.config?.daemon?.controlUi?.basePath,
|
||||
});
|
||||
defaultRuntime.log(`${label("Dashboard:")} ${infoText(links.httpUrl)}`);
|
||||
@@ -660,13 +670,6 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
|
||||
`${label("Probe note:")} ${infoText(status.gateway.probeNote)}`,
|
||||
);
|
||||
}
|
||||
if (status.gateway.bindMode === "tailnet" && !status.gateway.bindHost) {
|
||||
defaultRuntime.error(
|
||||
errorText(
|
||||
"Root cause: gateway bind=tailnet but no tailnet interface was found.",
|
||||
),
|
||||
);
|
||||
}
|
||||
spacer();
|
||||
}
|
||||
const runtimeLine = formatRuntimeStatus(service.runtime);
|
||||
|
||||
Reference in New Issue
Block a user