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.
This commit is contained in:
parent
40a66bae03
commit
3b4af6ef11
1 changed files with 38 additions and 12 deletions
|
|
@ -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`);
|
||||
|
|
|
|||
Loading…
Reference in a new issue