diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f46c5d0dce..8167e2ecb80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,10 @@ Docs: https://docs.openclaw.ai ### Fixes +- CLI/status: label the OpenClaw Serve/Funnel setting as `Tailscale exposure` + and show daemon state separately when available, so `gateway.tailscale.mode: +"off"` no longer reads like the Tailscale daemon is stopped. Fixes #71790. + Thanks @pesvobodak. - Plugins/Bonjour: stop ciao mDNS watchdog failures from looping forever when the advertiser stays stuck in `probing` or `announcing`; Bonjour now disables itself for the current Gateway process after repeated failed restarts while diff --git a/docs/gateway/tailscale.md b/docs/gateway/tailscale.md index 54fe66e5b04..6b11d71f753 100644 --- a/docs/gateway/tailscale.md +++ b/docs/gateway/tailscale.md @@ -16,6 +16,10 @@ Tailscale provides HTTPS, routing, and (for Serve) identity headers. - `funnel`: Public HTTPS via `tailscale funnel`. OpenClaw requires a shared password. - `off`: Default (no Tailscale automation). +Status and audit output use **Tailscale exposure** for this OpenClaw Serve/Funnel +mode. `off` means OpenClaw is not managing Serve or Funnel; it does not mean the +local Tailscale daemon is stopped or logged out. + ## Auth Set `gateway.auth.mode` to control the handshake: diff --git a/src/commands/status-all/diagnosis.test.ts b/src/commands/status-all/diagnosis.test.ts index 2c8d4a429e4..db4410497fb 100644 --- a/src/commands/status-all/diagnosis.test.ts +++ b/src/commands/status-all/diagnosis.test.ts @@ -63,6 +63,18 @@ function createBaseParams( } describe("status-all diagnosis port checks", () => { + it("labels OpenClaw Tailscale exposure separately from daemon state", async () => { + const params = createBaseParams([]); + params.tailscale.backendState = "Running"; + params.tailscale.dnsName = "box.tail.ts.net"; + + await appendStatusAllDiagnosis(params); + + const output = params.lines.join("\n"); + expect(output).toContain("✓ Tailscale exposure: off · daemon Running · box.tail.ts.net"); + expect(output).not.toContain("Tailscale: off"); + }); + it("treats same-process dual-stack loopback listeners as healthy", async () => { const params = createBaseParams([ { pid: 5001, commandLine: "openclaw-gateway", address: "127.0.0.1:18789" }, diff --git a/src/commands/status-all/diagnosis.ts b/src/commands/status-all/diagnosis.ts index 44f3b01c527..2f866844e66 100644 --- a/src/commands/status-all/diagnosis.ts +++ b/src/commands/status-all/diagnosis.ts @@ -169,8 +169,8 @@ export async function appendStatusAllDiagnosis(params: { const hasDns = Boolean(params.tailscale.dnsName); const label = params.tailscaleMode === "off" - ? `Tailscale: off · ${backend}${params.tailscale.dnsName ? ` · ${params.tailscale.dnsName}` : ""}` - : `Tailscale: ${params.tailscaleMode} · ${backend}${params.tailscale.dnsName ? ` · ${params.tailscale.dnsName}` : ""}`; + ? `Tailscale exposure: off · daemon ${backend}${params.tailscale.dnsName ? ` · ${params.tailscale.dnsName}` : ""}` + : `Tailscale exposure: ${params.tailscaleMode} · daemon ${backend}${params.tailscale.dnsName ? ` · ${params.tailscale.dnsName}` : ""}`; emitCheck(label, okBackend && (params.tailscaleMode === "off" || hasDns) ? "ok" : "warn"); if (params.tailscale.error) { lines.push(` ${muted(`error: ${params.tailscale.error}`)}`); diff --git a/src/commands/status-all/format.test.ts b/src/commands/status-all/format.test.ts index 830a11d32b6..387706494b2 100644 --- a/src/commands/status-all/format.test.ts +++ b/src/commands/status-all/format.test.ts @@ -145,7 +145,7 @@ describe("status-all format", () => { includeBackendStateWhenOff: true, includeDnsNameWhenOff: true, }), - ).toBe("off · Stopped · box.tail.ts.net"); + ).toBe("off · daemon Stopped · box.tail.ts.net"); }); it("formats service values across short and detailed runtime surfaces", () => { @@ -301,7 +301,7 @@ describe("status-all format", () => { ).toEqual([ { Item: "Version", Value: "1.0.0" }, { Item: "Dashboard", Value: "https://openclaw.local" }, - { Item: "Tailscale", Value: "serve · https://tail.example" }, + { Item: "Tailscale exposure", Value: "serve · https://tail.example" }, { Item: "Channel", Value: "stable" }, { Item: "Git", Value: "main @ v1.0.0" }, { Item: "Update", Value: "up to date" }, @@ -373,7 +373,7 @@ describe("status-all format", () => { ).toEqual([ { Item: "Version", Value: "1.0.0" }, { Item: "Dashboard", Value: "http://127.0.0.1:18789/" }, - { Item: "Tailscale", Value: "serve · box.tail.ts.net · https://box.tail.ts.net" }, + { Item: "Tailscale exposure", Value: "serve · box.tail.ts.net · https://box.tail.ts.net" }, { Item: "Channel", Value: "stable (config)" }, { Item: "Git", Value: "main · tag v1.2.3" }, { Item: "Update", Value: "available · custom update" }, diff --git a/src/commands/status-all/format.ts b/src/commands/status-all/format.ts index 400492baa09..debe4790586 100644 --- a/src/commands/status-all/format.ts +++ b/src/commands/status-all/format.ts @@ -112,7 +112,9 @@ export function formatStatusTailscaleValue(params: { const decorateWarn = params.decorateWarn ?? ((value: string) => value); if (params.tailscaleMode === "off") { const suffix = [ - params.includeBackendStateWhenOff && params.backendState ? params.backendState : null, + params.includeBackendStateWhenOff && params.backendState + ? `daemon ${params.backendState}` + : null, params.includeDnsNameWhenOff && params.dnsName ? params.dnsName : null, ] .filter(Boolean) @@ -192,7 +194,7 @@ export function buildStatusOverviewRows(params: { const rows: StatusOverviewRow[] = [...(params.prefixRows ?? [])]; rows.push( { Item: "Dashboard", Value: params.dashboardValue }, - { Item: "Tailscale", Value: params.tailscaleValue }, + { Item: "Tailscale exposure", Value: params.tailscaleValue }, { Item: "Channel", Value: params.channelLabel }, ); if (params.gitLabel) { diff --git a/src/commands/status-overview-surface.test.ts b/src/commands/status-overview-surface.test.ts index 9d91ce4efb8..eee8893459f 100644 --- a/src/commands/status-overview-surface.test.ts +++ b/src/commands/status-overview-surface.test.ts @@ -81,7 +81,7 @@ describe("status-overview-surface", () => { ).toEqual([ { Item: "OS", Value: "macOS · node 22" }, { Item: "Dashboard", Value: "http://127.0.0.1:18789/" }, - { Item: "Tailscale", Value: "muted(off · box.tail.ts.net)" }, + { Item: "Tailscale exposure", Value: "muted(off · box.tail.ts.net)" }, { Item: "Channel", Value: "stable (config)" }, { Item: "Git", Value: "main · tag v1.2.3" }, { Item: "Update", Value: "available · custom update" },