From 989cfd1e33dbca9907d5b940445a7e61cadc4545 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 26 Apr 2026 23:59:38 +0100 Subject: [PATCH] fix(bonjour): auto-disable advertising in containers --- extensions/bonjour/src/advertiser.test.ts | 33 +++++++++++++++ extensions/bonjour/src/advertiser.ts | 50 ++++++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/extensions/bonjour/src/advertiser.test.ts b/extensions/bonjour/src/advertiser.test.ts index 7b1de8be9ed..25a205e6771 100644 --- a/extensions/bonjour/src/advertiser.test.ts +++ b/extensions/bonjour/src/advertiser.test.ts @@ -1,3 +1,4 @@ +import fs from "node:fs"; import os from "node:os"; import { afterEach, describe, expect, it, vi } from "vitest"; @@ -207,6 +208,38 @@ describe("gateway bonjour advertiser", () => { await expect(started.stop()).resolves.toBeUndefined(); }); + it("auto-disables Bonjour in detected containers", async () => { + enableAdvertiserUnitMode(); + vi.spyOn(fs, "existsSync").mockImplementation((filePath) => String(filePath) === "/.dockerenv"); + + const started = await startAdvertiser({ + gatewayPort: 18789, + sshPort: 2222, + }); + + expect(createService).not.toHaveBeenCalled(); + await expect(started.stop()).resolves.toBeUndefined(); + }); + + it("honors explicit Bonjour opt-in inside detected containers", async () => { + enableAdvertiserUnitMode(); + process.env.OPENCLAW_DISABLE_BONJOUR = "0"; + vi.spyOn(fs, "existsSync").mockImplementation((filePath) => String(filePath) === "/.dockerenv"); + + const destroy = vi.fn().mockResolvedValue(undefined); + const advertise = vi.fn().mockResolvedValue(undefined); + mockCiaoService({ advertise, destroy }); + + const started = await startAdvertiser({ + gatewayPort: 18789, + sshPort: 2222, + }); + + expect(createService).toHaveBeenCalledTimes(1); + + await started.stop(); + }); + it("attaches conflict listeners for services", async () => { enableAdvertiserUnitMode(); diff --git a/extensions/bonjour/src/advertiser.ts b/extensions/bonjour/src/advertiser.ts index 0db1fcbed7f..1908d69aa1b 100644 --- a/extensions/bonjour/src/advertiser.ts +++ b/extensions/bonjour/src/advertiser.ts @@ -1,3 +1,4 @@ +import fs from "node:fs"; import type { PluginLogger } from "openclaw/plugin-sdk/plugin-entry"; import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env"; import { classifyCiaoProcessError, type CiaoProcessErrorClassification } from "./ciao.js"; @@ -89,16 +90,61 @@ async function loadCiaoModule(): Promise { return ciaoModulePromise; } -function isDisabledByEnv() { - if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_BONJOUR)) { +function readBonjourDisableOverride(): boolean | null { + const raw = process.env.OPENCLAW_DISABLE_BONJOUR; + const normalized = raw?.trim().toLowerCase(); + if (!normalized) { + return null; + } + if (isTruthyEnvValue(raw)) { return true; } + switch (normalized) { + case "0": + case "false": + case "no": + case "off": + return false; + default: + return null; + } +} + +function isContainerEnvironment() { + for (const sentinelPath of ["/.dockerenv", "/run/.containerenv", "/var/run/.containerenv"]) { + try { + if (fs.existsSync(sentinelPath)) { + return true; + } + } catch { + // ignore + } + } + + try { + const cgroup = fs.readFileSync("/proc/1/cgroup", "utf8"); + return /\/docker\/|cri-containerd-[0-9a-f]|containerd\/[0-9a-f]{64}|\/kubepods[/.]|\blxc\b/u.test( + cgroup, + ); + } catch { + return false; + } +} + +function isDisabledByEnv() { if (process.env.NODE_ENV === "test") { return true; } if (process.env.VITEST) { return true; } + const envOverride = readBonjourDisableOverride(); + if (envOverride !== null) { + return envOverride; + } + if (isContainerEnvironment()) { + return true; + } return false; }