Skip to content

Commit 9a8d65a

Browse files
serve.js: hop to a free port when the requested one is busy
EADDRINUSE on the default 3240 was fatal. Resolve the port up front (probe the preferred one; if busy, take an OS-assigned free port and log the hop) BEFORE deriving PUBLIC_URL / loopback — a retry-around-listen can't fix the origin already baked into every plugin config. Set PORT= to pin a specific port.
1 parent b87d912 commit 9a8d65a

1 file changed

Lines changed: 34 additions & 1 deletion

File tree

serve.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,47 @@
99

1010
import crypto from 'node:crypto';
1111
import fs from 'node:fs';
12+
import net from 'node:net';
1213
import path from 'node:path';
1314
import { fileURLToPath } from 'node:url';
1415
import { createServer } from 'javascript-solid-server/src/server.js';
1516

1617
const __dirname = path.dirname(fileURLToPath(import.meta.url));
1718
const at = (p) => path.join(__dirname, p);
1819

19-
const PORT = Number(process.env.PORT || 3240);
20+
// Is `port` bindable on 0.0.0.0 right now? (A quick listen-then-close probe.)
21+
function portFree(port) {
22+
return new Promise((resolve) => {
23+
const probe = net.createServer();
24+
probe.once('error', () => resolve(false));
25+
probe.listen(port, '0.0.0.0', () => probe.close(() => resolve(true)));
26+
});
27+
}
28+
29+
// An OS-assigned free port (bind to 0, read it back, release).
30+
function freePort() {
31+
return new Promise((resolve, reject) => {
32+
const probe = net.createServer();
33+
probe.once('error', reject);
34+
probe.listen(0, '0.0.0.0', () => {
35+
const { port } = probe.address();
36+
probe.close(() => resolve(port));
37+
});
38+
});
39+
}
40+
41+
// Prefer the requested port; if it's busy, hop to a free one and say so.
42+
// (The plugin configs bake the origin in before listen, so the port has to
43+
// be settled up front — a retry-on-EADDRINUSE loop around listen() can't fix
44+
// the loopback/baseUrl already handed to every plugin.)
45+
async function pickPort(preferred) {
46+
if (await portFree(preferred)) return preferred;
47+
const hopped = await freePort();
48+
console.warn(`port ${preferred} is in use — hopping to ${hopped} (set PORT= to pin one)`);
49+
return hopped;
50+
}
51+
52+
const PORT = await pickPort(Number(process.env.PORT || 3240));
2053
const PUBLIC_URL = (process.env.PUBLIC_URL || `http://localhost:${PORT}`).replace(/\/+$/, '');
2154
const DATA = process.env.DATA || path.join(__dirname, 'data');
2255
const PODS = path.join(DATA, 'pods');

0 commit comments

Comments
 (0)