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
@@ -1247,6 +1247,23 @@ describe("legacy config detection", () => {
|
||||
expect((res.config?.gateway as { token?: string })?.token).toBeUndefined();
|
||||
});
|
||||
|
||||
it("migrates gateway.bind and bridge.bind from 'tailnet' to 'auto'", async () => {
|
||||
vi.resetModules();
|
||||
const { migrateLegacyConfig } = await import("./config.js");
|
||||
const res = migrateLegacyConfig({
|
||||
gateway: { bind: "tailnet" as const },
|
||||
bridge: { bind: "tailnet" as const },
|
||||
});
|
||||
expect(res.changes).toContain(
|
||||
"Migrated gateway.bind from 'tailnet' to 'auto'.",
|
||||
);
|
||||
expect(res.changes).toContain(
|
||||
"Migrated bridge.bind from 'tailnet' to 'auto'.",
|
||||
);
|
||||
expect(res.config?.gateway?.bind).toBe("auto");
|
||||
expect(res.config?.bridge?.bind).toBe("auto");
|
||||
});
|
||||
|
||||
it('rejects telegram.dmPolicy="open" without allowFrom "*"', async () => {
|
||||
vi.resetModules();
|
||||
const { validateConfigObject } = await import("./config.js");
|
||||
|
||||
@@ -891,6 +891,29 @@ const LEGACY_CONFIG_MIGRATIONS: LegacyConfigMigration[] = [
|
||||
delete raw.identity;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bind-tailnet->auto",
|
||||
describe: "Remap gateway/bridge bind 'tailnet' to 'auto'",
|
||||
apply: (raw, changes) => {
|
||||
const migrateBind = (
|
||||
obj: Record<string, unknown> | null | undefined,
|
||||
key: string,
|
||||
) => {
|
||||
if (!obj) return;
|
||||
const bind = obj.bind;
|
||||
if (bind === "tailnet") {
|
||||
obj.bind = "auto";
|
||||
changes.push(`Migrated ${key}.bind from 'tailnet' to 'auto'.`);
|
||||
}
|
||||
};
|
||||
|
||||
const gateway = getRecord(raw.gateway);
|
||||
migrateBind(gateway, "gateway");
|
||||
|
||||
const bridge = getRecord(raw.bridge);
|
||||
migrateBind(bridge, "bridge");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function findLegacyConfigIssues(raw: unknown): LegacyConfigIssue[] {
|
||||
|
||||
@@ -1244,17 +1244,17 @@ export type ProviderCommandsConfig = {
|
||||
native?: NativeCommandsSetting;
|
||||
};
|
||||
|
||||
export type BridgeBindMode = "auto" | "lan" | "tailnet" | "loopback";
|
||||
export type BridgeBindMode = "auto" | "lan" | "loopback" | "custom";
|
||||
|
||||
export type BridgeConfig = {
|
||||
enabled?: boolean;
|
||||
port?: number;
|
||||
/**
|
||||
* Bind address policy for the node bridge server.
|
||||
* - auto: prefer tailnet IP when present, else LAN (0.0.0.0)
|
||||
* - lan: 0.0.0.0 (reachable on local network + any forwarded interfaces)
|
||||
* - tailnet: bind to the Tailscale interface IP (100.64.0.0/10) plus loopback
|
||||
* - loopback: 127.0.0.1
|
||||
* - auto: Tailnet IPv4 if available, else 0.0.0.0 (fallback to all interfaces)
|
||||
* - lan: 0.0.0.0 (all interfaces, no fallback)
|
||||
* - loopback: 127.0.0.1 (local-only)
|
||||
* - custom: User-specified IP, fallback to 0.0.0.0 if unavailable (requires customBindHost on gateway)
|
||||
*/
|
||||
bind?: BridgeBindMode;
|
||||
};
|
||||
@@ -1369,9 +1369,15 @@ export type GatewayConfig = {
|
||||
mode?: "local" | "remote";
|
||||
/**
|
||||
* Bind address policy for the Gateway WebSocket + Control UI HTTP server.
|
||||
* - auto: Tailnet IPv4 if available, else 0.0.0.0 (fallback to all interfaces)
|
||||
* - lan: 0.0.0.0 (all interfaces, no fallback)
|
||||
* - loopback: 127.0.0.1 (local-only)
|
||||
* - custom: User-specified IP, fallback to 0.0.0.0 if unavailable (requires customBindHost)
|
||||
* Default: loopback (127.0.0.1).
|
||||
*/
|
||||
bind?: BridgeBindMode;
|
||||
/** Custom IP address for bind="custom" mode. Fallback: 0.0.0.0. */
|
||||
customBindHost?: string;
|
||||
controlUi?: GatewayControlUiConfig;
|
||||
auth?: GatewayAuthConfig;
|
||||
tailscale?: GatewayTailscaleConfig;
|
||||
|
||||
Reference in New Issue
Block a user