mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:00:43 +00:00
fix(bonjour): default mdns host to system hostname
This commit is contained in:
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- Channels/setup: treat bundled channel plugins as already bundled during `channels add` and onboarding, enabling them without writing redundant `plugins.load.paths` entries or path install records. Fixes #72740. Thanks @iCodePoet.
|
||||
- WhatsApp: honor gateway `HTTPS_PROXY` / `HTTP_PROXY` env vars for QR-login WebSocket connections, while respecting `NO_PROXY`, so proxied networks no longer fall back to direct `mmg.whatsapp.net` connections that time out with 408. Fixes #72547; supersedes #72692. Thanks @mebusw and @SymbolStar.
|
||||
- Bonjour: default mDNS advertisements to the system hostname when it is DNS-safe, avoiding `openclaw.local` probing conflicts and Gateway restart loops on hosts such as `Lobster` or `ubuntu`. Fixes #72355 and #72689; supersedes #72694. Thanks @mscheuerlein-bot, @gcusms, @moyuwuhen601, @pavan987, @zml-0912, @hhq365, and @SymbolStar.
|
||||
- Agents/OpenAI-compatible: retry replay-safe empty `stop` turns once for `openai-completions` endpoints, so transient empty local backend responses no longer surface as “Agent couldn't generate a response” when a continuation succeeds. Fixes #72751. Thanks @moooV252.
|
||||
- Git hooks: skip ignored staged paths when formatting and restaging pre-commit files, so merge commits no longer abort when `.gitignore` newly ignores staged merged content. Fixes #72744. Thanks @100yenadmin.
|
||||
- Memory-core/dreaming: add a supported `dreaming.model` knob for Dream Diary narrative subagents, wired through phase config and the existing plugin subagent model-override trust gate. Refs #65963. Thanks @esqandil and @mjamiv.
|
||||
|
||||
@@ -141,6 +141,12 @@ The Gateway writes a rolling log file (printed on startup as
|
||||
- `bonjour: watchdog detected non-announced service ...`
|
||||
- `bonjour: disabling advertiser after ... failed restarts ...`
|
||||
|
||||
Bonjour uses the system hostname for the advertised `.local` host when it is a
|
||||
valid DNS label. If the system hostname contains spaces, underscores, or another
|
||||
invalid DNS-label character, OpenClaw falls back to `openclaw.local`. Set
|
||||
`OPENCLAW_MDNS_HOSTNAME=<name>` before starting the Gateway when you need an
|
||||
explicit host label.
|
||||
|
||||
## Debugging on iOS node
|
||||
|
||||
The iOS node uses `NWBrowser` to discover `_openclaw-gw._tcp`.
|
||||
|
||||
@@ -649,7 +649,7 @@ Validation and safety notes:
|
||||
|
||||
- `minimal` (default): omit `cliPath` + `sshPort` from TXT records.
|
||||
- `full`: include `cliPath` + `sshPort`.
|
||||
- Hostname defaults to `openclaw`. Override with `OPENCLAW_MDNS_HOSTNAME`.
|
||||
- Hostname defaults to the system hostname when it is a valid DNS label, falling back to `openclaw`. Override with `OPENCLAW_MDNS_HOSTNAME`.
|
||||
|
||||
### Wide-area (DNS-SD)
|
||||
|
||||
|
||||
@@ -704,11 +704,57 @@ describe("gateway bonjour advertiser", () => {
|
||||
});
|
||||
|
||||
const [gatewayCall] = createService.mock.calls as Array<[ServiceCall]>;
|
||||
expect(gatewayCall?.[0]?.name).toBe("openclaw (OpenClaw)");
|
||||
expect(gatewayCall?.[0]?.name).toBe("Mac (OpenClaw)");
|
||||
expect(gatewayCall?.[0]?.domain).toBe("local");
|
||||
expect(gatewayCall?.[0]?.hostname).toBe("Mac");
|
||||
expect((gatewayCall?.[0]?.txt as Record<string, string>)?.lanHost).toBe("Mac.local");
|
||||
|
||||
await started.stop();
|
||||
});
|
||||
|
||||
it("falls back to openclaw when system hostname is invalid for DNS", async () => {
|
||||
// Allow advertiser to run in unit tests.
|
||||
delete process.env.VITEST;
|
||||
process.env.NODE_ENV = "development";
|
||||
delete process.env.OPENCLAW_MDNS_HOSTNAME;
|
||||
vi.spyOn(os, "hostname").mockReturnValue("My_Lobster Host");
|
||||
|
||||
const destroy = vi.fn().mockResolvedValue(undefined);
|
||||
const advertise = vi.fn().mockResolvedValue(undefined);
|
||||
mockCiaoService({ advertise, destroy });
|
||||
|
||||
const started = await startAdvertiser({
|
||||
gatewayPort: 18789,
|
||||
sshPort: 2222,
|
||||
});
|
||||
|
||||
const [gatewayCall] = createService.mock.calls as Array<[ServiceCall]>;
|
||||
expect(gatewayCall?.[0]?.hostname).toBe("openclaw");
|
||||
expect((gatewayCall?.[0]?.txt as Record<string, string>)?.lanHost).toBe("openclaw.local");
|
||||
|
||||
await started.stop();
|
||||
});
|
||||
|
||||
it("uses system hostname when OPENCLAW_MDNS_HOSTNAME is unset", async () => {
|
||||
// Allow advertiser to run in unit tests.
|
||||
delete process.env.VITEST;
|
||||
process.env.NODE_ENV = "development";
|
||||
delete process.env.OPENCLAW_MDNS_HOSTNAME;
|
||||
vi.spyOn(os, "hostname").mockReturnValue("Lobster");
|
||||
|
||||
const destroy = vi.fn().mockResolvedValue(undefined);
|
||||
const advertise = vi.fn().mockResolvedValue(undefined);
|
||||
mockCiaoService({ advertise, destroy });
|
||||
|
||||
const started = await startAdvertiser({
|
||||
gatewayPort: 18789,
|
||||
sshPort: 2222,
|
||||
});
|
||||
|
||||
const [gatewayCall] = createService.mock.calls as Array<[ServiceCall]>;
|
||||
expect(gatewayCall?.[0]?.hostname).toBe("Lobster");
|
||||
expect((gatewayCall?.[0]?.txt as Record<string, string>)?.lanHost).toBe("Lobster.local");
|
||||
|
||||
await started.stop();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ChildProcess } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import os from "node:os";
|
||||
import type { PluginLogger } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { classifyCiaoProcessError, type CiaoProcessErrorClassification } from "./ciao.js";
|
||||
@@ -160,6 +161,28 @@ function isDisabledByEnv() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function resolveSystemMdnsHostname(): string | null {
|
||||
let raw: string;
|
||||
try {
|
||||
raw = os.hostname();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const firstLabel =
|
||||
trimmed
|
||||
.replace(/\.local$/i, "")
|
||||
.split(".")[0]
|
||||
?.trim() ?? "";
|
||||
if (!/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/.test(firstLabel)) {
|
||||
return null;
|
||||
}
|
||||
return firstLabel;
|
||||
}
|
||||
|
||||
function safeServiceName(name: string) {
|
||||
const trimmed = name.trim();
|
||||
return trimmed.length > 0 ? trimmed : "OpenClaw";
|
||||
@@ -328,7 +351,8 @@ export async function startGatewayBonjourAdvertiser(
|
||||
cleanupUnhandledRejection = deps.registerUnhandledRejectionHandler?.(handleCiaoProcessError);
|
||||
cleanupUncaughtException = deps.registerUncaughtExceptionHandler?.(handleCiaoProcessError);
|
||||
|
||||
const hostnameRaw = process.env.OPENCLAW_MDNS_HOSTNAME?.trim() || "openclaw";
|
||||
const hostnameRaw =
|
||||
process.env.OPENCLAW_MDNS_HOSTNAME?.trim() || resolveSystemMdnsHostname() || "openclaw";
|
||||
const hostname =
|
||||
hostnameRaw
|
||||
.replace(/\.local$/i, "")
|
||||
|
||||
Reference in New Issue
Block a user