Files
Moltbot/src/commands/dashboard.ts
Jefferson Warrior c851bdd47a 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
2026-01-13 05:20:02 +00:00

73 lines
1.9 KiB
TypeScript

import {
readConfigFileSnapshot,
resolveGatewayPort,
} from "../config/config.js";
import type { RuntimeEnv } from "../runtime.js";
import { defaultRuntime } from "../runtime.js";
import {
copyToClipboard,
detectBrowserOpenSupport,
formatControlUiSshHint,
openUrl,
resolveControlUiLinks,
} from "./onboard-helpers.js";
type DashboardOptions = {
noOpen?: boolean;
};
export async function dashboardCommand(
runtime: RuntimeEnv = defaultRuntime,
options: DashboardOptions = {},
) {
const snapshot = await readConfigFileSnapshot();
const cfg = snapshot.valid ? snapshot.config : {};
const port = resolveGatewayPort(cfg);
const bind = cfg.gateway?.bind ?? "loopback";
const basePath = cfg.gateway?.controlUi?.basePath;
const customBindHost = cfg.gateway?.customBindHost;
const token =
cfg.gateway?.auth?.token ?? process.env.CLAWDBOT_GATEWAY_TOKEN ?? "";
const links = resolveControlUiLinks({
port,
bind,
customBindHost,
basePath,
});
const authedUrl = token
? `${links.httpUrl}?token=${encodeURIComponent(token)}`
: links.httpUrl;
runtime.log(`Dashboard URL: ${authedUrl}`);
const copied = await copyToClipboard(authedUrl).catch(() => false);
runtime.log(
copied ? "Copied to clipboard." : "Copy to clipboard unavailable.",
);
let opened = false;
let hint: string | undefined;
if (!options.noOpen) {
const browserSupport = await detectBrowserOpenSupport();
if (browserSupport.ok) {
opened = await openUrl(authedUrl);
}
if (!opened) {
hint = formatControlUiSshHint({
port,
basePath,
token: token || undefined,
});
}
} else {
hint = "Browser launch disabled (--no-open). Use the URL above.";
}
if (opened) {
runtime.log("Opened in your browser. Keep that tab to control Clawdbot.");
} else if (hint) {
runtime.log(hint);
}
}