From 3b4af6ef11377c71a85718ad8f2090b9a35d7363 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Thu, 21 May 2026 00:15:03 -0400 Subject: [PATCH] node-agent: prefer NODE_IP and skip docker bridge interfaces In bridge mode the agent was reporting the container's 172.x address because the first non-internal interface in os.networkInterfaces() was docker0. Now honours NODE_IP, skips lo/docker*/br-*/veth*/etc, and down-ranks the 172.16-31 range so real LAN IPs win. Also exposes the detected IP on /health for the onboarding script to print. --- services/node-agent/index.js | 50 +++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/services/node-agent/index.js b/services/node-agent/index.js index 425ac3e..a00ef1e 100644 --- a/services/node-agent/index.js +++ b/services/node-agent/index.js @@ -5,9 +5,37 @@ import fs from 'fs'; const MAM_API_URL = (process.env.MAM_API_URL || 'http://localhost:3000').replace(/\/$/, ''); const NODE_TOKEN = process.env.NODE_TOKEN || ''; const NODE_ROLE = process.env.NODE_ROLE || 'worker'; -const AGENT_PORT = parseInt(process.env.AGENT_PORT || '3002', 10); +const AGENT_PORT = parseInt(process.env.AGENT_PORT || '7436', 10); const HEARTBEAT_MS = parseInt(process.env.HEARTBEAT_MS || '30000', 10); -const VERSION = '1.0.0'; +const VERSION = '1.1.0'; + +// Pick the host's LAN IP. Inside a bridge-mode container, +// os.networkInterfaces() returns the container's docker-bridge IP (172.x), +// not the host's LAN address. Two strategies: +// 1. honour an explicit NODE_IP override (set by onboard-node.sh) +// 2. filter out interfaces that obviously belong to container/virtual +// bridges and down-rank the docker bridge range so real LAN IPs win +// +// Combine with `network_mode: host` in docker-compose.worker.yml for the +// most reliable result — that lets os.networkInterfaces() see the host's +// real adapters directly. +function getIp() { + if (process.env.NODE_IP) return process.env.NODE_IP; + + const SKIP_IFACE = /^(lo|docker\d*|br-|veth|cni|flannel|cali|tun|tap|virbr|kube)/i; + const isContainerBridge = (ip) => /^172\.(1[6-9]|2\d|3[01])\./.test(ip); + + const candidates = []; + for (const [name, addrs] of Object.entries(os.networkInterfaces())) { + if (SKIP_IFACE.test(name)) continue; + for (const a of (addrs || [])) { + if (a.family !== 'IPv4' || a.internal) continue; + candidates.push({ name, address: a.address, rank: isContainerBridge(a.address) ? 1 : 0 }); + } + } + candidates.sort((a, b) => a.rank - b.rank); + return candidates.length ? candidates[0].address : null; +} const server = http.createServer((req, res) => { if (req.method === 'GET' && req.url === '/health') { @@ -18,6 +46,7 @@ const server = http.createServer((req, res) => { uptime: Math.round(process.uptime()), version: VERSION, role: NODE_ROLE, + ip: getIp(), })); } else { res.writeHead(404); @@ -44,14 +73,6 @@ function sampleCpu() { }); } -function getIp() { - for (const ifaces of Object.values(os.networkInterfaces())) { - const hit = (ifaces || []).find(a => a.family === 'IPv4' && !a.internal); - if (hit) return hit.address; - } - return null; -} - // ── Hardware detection ──────────────────────────────────────────────────── // GPU_COUNT / BMD_COUNT env vars override filesystem detection when /dev isn't mapped function detectHardware() { @@ -79,10 +100,14 @@ function detectHardware() { } else { try { const bmdEntries = fs.readdirSync('/dev/blackmagic'); - capabilities.blackmagic = bmdEntries.map(d => ({ device: `/dev/blackmagic/${d}` })); + capabilities.blackmagic = bmdEntries.map((d, i) => ({ device: `/dev/blackmagic/${d}`, index: i })); } catch (_) {} } + // Best-effort model name from BMD_MODEL env (set manually) — used by the UI + // to render the correct card layout (Duo 2, Quad 2, Mini Recorder, ...). + if (process.env.BMD_MODEL) capabilities.blackmagic_model = process.env.BMD_MODEL; + return capabilities; } @@ -120,7 +145,7 @@ async function heartbeat() { const gpuStr = capabilities.gpus.length ? ` gpu=${capabilities.gpus.length}` : ''; const bmdStr = capabilities.blackmagic.length ? ` bmd=${capabilities.blackmagic.length}` : ''; process.stdout.write( - `[hb] ${payload.hostname} cpu=${cpu_usage}% ` + + `[hb] ${payload.hostname} ip=${ip_address || '?'} cpu=${cpu_usage}% ` + `mem=${payload.mem_used_mb}/${payload.mem_total_mb}MB${gpuStr}${bmdStr}\n` ); } else { @@ -138,6 +163,7 @@ setInterval(heartbeat, HEARTBEAT_MS); server.listen(AGENT_PORT, () => { console.log(`wild-dragon-node-agent v${VERSION}`); console.log(` Listening :${AGENT_PORT}`); + console.log(` Host IP ${getIp() || '(unresolved)'}`); console.log(` Primary ${MAM_API_URL}`); console.log(` Role ${NODE_ROLE}`); console.log(` Heartbeat every ${HEARTBEAT_MS / 1000}s`);