From 8309e1e82e9bcca087648422eba27e2d5aa00781 Mon Sep 17 00:00:00 2001 From: Khalid Abdi Date: Sat, 27 Jun 2026 21:33:08 +0300 Subject: [PATCH] fix: don't show a bogus container IP for the network address under Docker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /api/network reported the container's bridge IP (172.x) when running in Docker, which surfaced as a broken/"Error" value in Settings → About & updates. Skip container-internal interfaces (detected via /.dockerenv) so the endpoint returns no address inside a container, and guard the panel against an unexpected response shape — both paths fall back to the helpful "open via the server's IP" hint. Co-Authored-By: Claude Opus 4.8 --- backend/src/routes/network.ts | 28 +++++++++++++------ .../components/settings/settings-version.tsx | 4 ++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/backend/src/routes/network.ts b/backend/src/routes/network.ts index 4c6f031..52c88dc 100644 --- a/backend/src/routes/network.ts +++ b/backend/src/routes/network.ts @@ -1,9 +1,13 @@ // GET /api/network — best-effort discovery of LAN addresses other departments // can use to reach temetro, for the Settings "Network access" panel. // -// Caveat: inside Docker's default bridge network this sees the container's IPs, -// not the host's LAN IP, so the frontend prefers the address the browser is -// actually using (window.location) and treats this as a fallback/hint. +// Caveat: inside Docker's network this sees the container's bridge IP (e.g. +// 172.x), not the host's LAN IP. Surfacing that bogus address looked like an +// error in Settings, so when we detect we're in a container we return NO +// addresses — the frontend then prefers the address the browser is actually +// using (window.location) and otherwise shows a helpful "open via the server's +// IP" hint instead of an unreachable container IP. +import { existsSync } from "node:fs"; import { networkInterfaces } from "node:os"; import { Router } from "express"; @@ -19,16 +23,24 @@ function frontendPort(): number { } } +// True when running inside a container: the interface IPs are the container's +// bridge network, not the host's reachable LAN address. +function inContainer(): boolean { + return existsSync("/.dockerenv") || process.env.RUNNING_IN_DOCKER === "true"; +} + const router = Router(); router.get("/", (_req, res) => { const port = frontendPort(); const addresses: string[] = []; - for (const iface of Object.values(networkInterfaces())) { - for (const net of iface ?? []) { - // Node <18 reports family as "IPv4"; >=18 may report the number 4. - const isV4 = net.family === "IPv4" || (net.family as unknown) === 4; - if (isV4 && !net.internal) addresses.push(net.address); + if (!inContainer()) { + for (const iface of Object.values(networkInterfaces())) { + for (const net of iface ?? []) { + // Node <18 reports family as "IPv4"; >=18 may report the number 4. + const isV4 = net.family === "IPv4" || (net.family as unknown) === 4; + if (isV4 && !net.internal) addresses.push(net.address); + } } } res.json({ diff --git a/frontend/components/settings/settings-version.tsx b/frontend/components/settings/settings-version.tsx index 11c6117..205fd7e 100644 --- a/frontend/components/settings/settings-version.tsx +++ b/frontend/components/settings/settings-version.tsx @@ -66,7 +66,9 @@ export function VersionPanel() { useEffect(() => { if (localShareUrl) return; getNetworkInfo() - .then((n) => setNetworkUrls(n.urls)) + // Guard against an unexpected shape so a missing/garbled response shows the + // helpful "open via the server's IP" hint rather than a broken value. + .then((n) => setNetworkUrls(Array.isArray(n?.urls) ? n.urls : [])) .catch(() => setNetworkUrls([])); }, [localShareUrl]);