diff --git a/wol-gate/server.js b/wol-gate/server.js index 6528c14..114e5c7 100644 --- a/wol-gate/server.js +++ b/wol-gate/server.js @@ -5,7 +5,6 @@ // Must run on an always-on host with L2 access to the target's broadcast // domain (host networking) so the magic packet actually reaches the target. const http = require('http'); -const net = require('net'); const dgram = require('dgram'); const krCidrs = require('./kr-cidrs.json'); @@ -140,21 +139,31 @@ function sendMagicPacket(mac, broadcastAddr, port) { }); } +// HTTP-level check, not just TCP connect: a target mid-boot can have its listener socket +// open (TCP connect succeeds) well before the actual service behind it is answering requests +// (e.g. Ollama's port opens before the model/runtime is ready) — a bare TCP check reports +// "alive" during that gap, so proxyRequest() gets sent through and times out with its own +// "Bad gateway: read ETIMEDOUT" instead of the caller seeing the (retryable) waking-up page. +// Observed live 2026-08-08 on a wol-gate-fronted Ollama target. Any real HTTP response +// (regardless of status code — even a 404 proves the app layer is serving, not just the +// socket) counts as alive; only a connection error/timeout counts as still-waking. function checkTargetAlive() { return new Promise((resolve) => { - const socket = new net.Socket(); let done = false; const finish = (alive) => { if (done) return; done = true; - socket.destroy(); resolve(alive); }; - socket.setTimeout(HEALTH_TIMEOUT_MS); - socket.once('connect', () => finish(true)); - socket.once('timeout', () => finish(false)); - socket.once('error', () => finish(false)); - socket.connect(TARGET_PORT, TARGET_HOST); + const req = http.request({ + host: TARGET_HOST, port: TARGET_PORT, method: 'GET', path: '/', timeout: HEALTH_TIMEOUT_MS, + }, (res) => { + res.destroy(); + finish(true); + }); + req.on('timeout', () => req.destroy()); + req.on('error', () => finish(false)); + req.end(); }); }