openvibers/openvibe.tools Commits

Tools apps: systemd holds each app's listener too (audio, docs, food, img, maps, text, yt), so a restart queues the gateway's requests instead of refusing them; the process listens on fd 3 only when handed it (#79)

OP OpenVibers committed 27 days ago · 448f45ae97 · 22 files, +228 −7

Changes

ddiff --git a/apps/audio/deploy/systemd/openvibe-tools-audio.service.d/socket.conf b/apps/audio/deploy/systemd/openvibe-tools-audio.service.d/socket.conf
nnew file mode 100644
iindex 0000000..db28a4e
---- /dev/null
++++ b/apps/audio/deploy/systemd/openvibe-tools-audio.service.d/socket.conf
@@@ -0,0 +1,9 @@
++# Bind the service to the socket unit that owns its listener. A drop-in, so the main unit stays usable on a box that
++# has not been switched over to socket activation.
++[Unit]
++Requires=openvibe-tools-audio.socket
++After=openvibe-tools-audio.socket
++
++[Service]
++# Node's libuv expects a non-blocking descriptor. (A [Service] option: in [Socket] systemd ignores it.)
++NonBlocking=true
ddiff --git a/apps/audio/deploy/systemd/openvibe-tools-audio.socket b/apps/audio/deploy/systemd/openvibe-tools-audio.socket
nnew file mode 100644
iindex 0000000..789b2cc
---- /dev/null
++++ b/apps/audio/deploy/systemd/openvibe-tools-audio.socket
@@@ -0,0 +1,17 @@
++# OpenVibe.Tools audio app — listening socket, owned by systemd rather than by the process (as the gateway's).
++#
++# The gateway (openvibe-tools.service) proxies this app's pages and API to 127.0.0.1:4014. Without this, each restart
++# closed the listener for the whole boot and the gateway got ECONNREFUSED; with it, requests that arrive mid-restart
++# wait in the kernel's accept queue and are served as soon as the new process calls accept().
++[Unit]
++Description=OpenVibe.Tools audio HTTP socket
++# No PartOf=openvibe-tools-audio.service: the socket must outlive every restart.
++
++[Socket]
++# Loopback only: the gateway is the only client (the host firewall is off, so 0.0.0.0 would be public).
++ListenStream=127.0.0.1:4014
++NoDelay=true
++Backlog=511
++
++[Install]
++WantedBy=sockets.target
ddiff --git a/apps/audio/server/index.js b/apps/audio/server/index.js
iindex 3f72b56..058dabe 100644
---- a/apps/audio/server/index.js
++++ b/apps/audio/server/index.js
@@@ -315,7 +315,10 @@ app.get('*', (req, res) => {
});
// ── Start ────────────────────────────────────────────────────
--const server = app.listen(config.port, config.host, () => {
++// systemd hands this process the listener when deploy/systemd/openvibe-tools-audio.socket is installed: a restart then
++// queues the gateway's requests in the kernel instead of refusing them. Otherwise it listens on the port itself.
++const socketActivated = process.env.LISTEN_FDS === '1' && Number(process.env.LISTEN_PID) === process.pid;
++const server = app.listen(...(socketActivated ? [{ fd: 3 }] : [config.port, config.host]), () => {
retention.startCleanup();
console.log(`\n╔═══════════════════════════════════════╗`);
console.log(`║ 🎵 Audio.OpenVibe — Audio Processing Hub ║`);
ddiff --git a/apps/docs/deploy/systemd/openvibe-tools-docs.service.d/socket.conf b/apps/docs/deploy/systemd/openvibe-tools-docs.service.d/socket.conf
nnew file mode 100644
iindex 0000000..c363c06
---- /dev/null
++++ b/apps/docs/deploy/systemd/openvibe-tools-docs.service.d/socket.conf
@@@ -0,0 +1,9 @@
++# Bind the service to the socket unit that owns its listener. A drop-in, so the main unit stays usable on a box that
++# has not been switched over to socket activation.
++[Unit]
++Requires=openvibe-tools-docs.socket
++After=openvibe-tools-docs.socket
++
++[Service]
++# Node's libuv expects a non-blocking descriptor. (A [Service] option: in [Socket] systemd ignores it.)
++NonBlocking=true
ddiff --git a/apps/docs/deploy/systemd/openvibe-tools-docs.socket b/apps/docs/deploy/systemd/openvibe-tools-docs.socket
nnew file mode 100644
iindex 0000000..f45821d
---- /dev/null
++++ b/apps/docs/deploy/systemd/openvibe-tools-docs.socket
@@@ -0,0 +1,17 @@
++# OpenVibe.Tools docs app — listening socket, owned by systemd rather than by the process (as the gateway's).
++#
++# The gateway (openvibe-tools.service) proxies this app's pages and API to 127.0.0.1:4016. Without this, each restart
++# closed the listener for the whole boot and the gateway got ECONNREFUSED; with it, requests that arrive mid-restart
++# wait in the kernel's accept queue and are served as soon as the new process calls accept().
++[Unit]
++Description=OpenVibe.Tools docs HTTP socket
++# No PartOf=openvibe-tools-docs.service: the socket must outlive every restart.
++
++[Socket]
++# Loopback only: the gateway is the only client (the host firewall is off, so 0.0.0.0 would be public).
++ListenStream=127.0.0.1:4016
++NoDelay=true
++Backlog=511
++
++[Install]
++WantedBy=sockets.target
ddiff --git a/apps/docs/server/index.js b/apps/docs/server/index.js
iindex 8c801b2..023536c 100644
---- a/apps/docs/server/index.js
++++ b/apps/docs/server/index.js
@@@ -311,7 +311,10 @@ app.get('*', (req, res) => {
});
// ── Start ────────────────────────────────────────────────────
--const server = app.listen(config.port, config.host, () => {
++// systemd hands this process the listener when deploy/systemd/openvibe-tools-docs.socket is installed: a restart then
++// queues the gateway's requests in the kernel instead of refusing them. Otherwise it listens on the port itself.
++const socketActivated = process.env.LISTEN_FDS === '1' && Number(process.env.LISTEN_PID) === process.pid;
++const server = app.listen(...(socketActivated ? [{ fd: 3 }] : [config.port, config.host]), () => {
retention.startCleanup();
console.log(`\n╔═══════════════════════════════════════╗`);
console.log(`║ 📄 Docs.OpenVibe — Document Tools Hub ║`);
ddiff --git a/apps/food/deploy/systemd/openvibe-tools-food.service.d/socket.conf b/apps/food/deploy/systemd/openvibe-tools-food.service.d/socket.conf
nnew file mode 100644
iindex 0000000..3d7b59f
---- /dev/null
++++ b/apps/food/deploy/systemd/openvibe-tools-food.service.d/socket.conf
@@@ -0,0 +1,9 @@
++# Bind the service to the socket unit that owns its listener. A drop-in, so the main unit stays usable on a box that
++# has not been switched over to socket activation.
++[Unit]
++Requires=openvibe-tools-food.socket
++After=openvibe-tools-food.socket
++
++[Service]
++# Node's libuv expects a non-blocking descriptor. (A [Service] option: in [Socket] systemd ignores it.)
++NonBlocking=true
ddiff --git a/apps/food/deploy/systemd/openvibe-tools-food.socket b/apps/food/deploy/systemd/openvibe-tools-food.socket
nnew file mode 100644
iindex 0000000..c8c6f73
---- /dev/null
++++ b/apps/food/deploy/systemd/openvibe-tools-food.socket
@@@ -0,0 +1,17 @@
++# OpenVibe.Tools food app — listening socket, owned by systemd rather than by the process (as the gateway's).
++#
++# The gateway (openvibe-tools.service) proxies this app's pages and API to 127.0.0.1:4011. Without this, each restart
++# closed the listener for the whole boot and the gateway got ECONNREFUSED; with it, requests that arrive mid-restart
++# wait in the kernel's accept queue and are served as soon as the new process calls accept().
++[Unit]
++Description=OpenVibe.Tools food HTTP socket
++# No PartOf=openvibe-tools-food.service: the socket must outlive every restart.
++
++[Socket]
++# Loopback only: the gateway is the only client (the host firewall is off, so 0.0.0.0 would be public).
++ListenStream=127.0.0.1:4011
++NoDelay=true
++Backlog=511
++
++[Install]
++WantedBy=sockets.target
ddiff --git a/apps/food/server/index.js b/apps/food/server/index.js
iindex 1df5565..6ff51e2 100644
---- a/apps/food/server/index.js
++++ b/apps/food/server/index.js
@@@ -171,7 +171,10 @@ app.get('/api/internal/analytics/bots', internalAccess, async (req, res) => {
// What used to be the page's own paths go to OpenVibe.Food as well (the API answers JSON 404s).
app.get('*', (req, res) => (req.path.startsWith('/api/') ? res.status(404).json({ error: 'Not found' }) : toFood(req, res)));
--const server = app.listen(PORT, '127.0.0.1', () => {
++// systemd hands this process the listener when deploy/systemd/openvibe-tools-food.socket is installed: a restart then
++// queues the gateway's requests in the kernel instead of refusing them. Otherwise it listens on the port itself.
++const socketActivated = process.env.LISTEN_FDS === '1' && Number(process.env.LISTEN_PID) === process.pid;
++const server = app.listen(...(socketActivated ? [{ fd: 3 }] : [PORT, '127.0.0.1']), () => {
console.log(`[Food.OpenVibe] 🍽️ food.openvibe.tools listening on 127.0.0.1:${PORT}`);
});
ddiff --git a/apps/gateway/test/systemd-units.test.js b/apps/gateway/test/systemd-units.test.js
iindex 9c992b4..67be2ad 100644
---- a/apps/gateway/test/systemd-units.test.js
++++ b/apps/gateway/test/systemd-units.test.js
@@@ -31,4 +31,22 @@ const index = fs.readFileSync(path.join(__dirname, '..', 'server', 'index.js'),
assert.match(index, /process\.env\.LISTEN_FDS === '1' && Number\(process\.env\.LISTEN_PID\) === process\.pid/, 'fd 3 only when handed to this process');
assert.ok(index.includes('const server = app.listen(...(socketActivated ? [{ fd: 3 }] : [config.port, config.host]), () => {'.trim()), 'otherwise the configured port');
++// The apps behind the gateway: the same rules, each on its own port.
++const APPS = { audio: 4014, docs: 4016, food: 4011, img: 4012, maps: 4010, text: 4015, yt: 4013 };
++for (const [app, port] of Object.entries(APPS)) {
++ const dir = path.join(__dirname, '..', '..', app, 'deploy', 'systemd');
++ const rd = (...p) => fs.readFileSync(path.join(dir, ...p), 'utf8');
++ const sock = rd(`openvibe-tools-${app}.socket`);
++ assert.deepStrictEqual([...section(sock, 'Socket').matchAll(/^ListenStream=(.+)$/gm)].map((m) => m[1].trim()), [`127.0.0.1:${port}`], `${app}: loopback, its port`);
++ assert.ok(!/^NonBlocking=/m.test(section(sock, 'Socket')) && !/^PartOf=/m.test(section(sock, 'Unit')), `${app}: no NonBlocking/PartOf in the socket`);
++ const drop = rd(`openvibe-tools-${app}.service.d`, 'socket.conf');
++ assert.match(section(drop, 'Unit'), new RegExp(`^Requires=openvibe-tools-${app}\\.socket$`, 'm'), app);
++ assert.match(section(drop, 'Unit'), new RegExp(`^After=openvibe-tools-${app}\\.socket$`, 'm'), app);
++ assert.match(section(drop, 'Service'), /^NonBlocking=true$/m, app);
++ assert.match(rd(`openvibe-tools-${app}.service`), new RegExp(`^Environment=PORT=${port}$`, 'm'), `${app}: the unit's PORT is the socket's`);
++ const src = fs.readFileSync(path.join(__dirname, '..', '..', app, 'server', 'index.js'), 'utf8');
++ assert.match(src, /process\.env\.LISTEN_FDS === '1' && Number\(process\.env\.LISTEN_PID\) === process\.pid/, `${app}: fd 3 only when handed to this process`);
++ assert.match(src, /app\.listen\(\.\.\.\(socketActivated \? \[\{ fd: 3 \}\] : \[/, `${app}: otherwise its port`);
++}
++
console.log('systemd units: all checks passed');
ddiff --git a/apps/img/deploy/systemd/openvibe-tools-img.service.d/socket.conf b/apps/img/deploy/systemd/openvibe-tools-img.service.d/socket.conf
nnew file mode 100644
iindex 0000000..9ab88d2
---- /dev/null
++++ b/apps/img/deploy/systemd/openvibe-tools-img.service.d/socket.conf
@@@ -0,0 +1,9 @@
++# Bind the service to the socket unit that owns its listener. A drop-in, so the main unit stays usable on a box that
++# has not been switched over to socket activation.
++[Unit]
++Requires=openvibe-tools-img.socket
++After=openvibe-tools-img.socket
++
++[Service]
++# Node's libuv expects a non-blocking descriptor. (A [Service] option: in [Socket] systemd ignores it.)
++NonBlocking=true
ddiff --git a/apps/img/deploy/systemd/openvibe-tools-img.socket b/apps/img/deploy/systemd/openvibe-tools-img.socket
nnew file mode 100644
iindex 0000000..8178016
---- /dev/null
++++ b/apps/img/deploy/systemd/openvibe-tools-img.socket
@@@ -0,0 +1,17 @@
++# OpenVibe.Tools img app — listening socket, owned by systemd rather than by the process (as the gateway's).
++#
++# The gateway (openvibe-tools.service) proxies this app's pages and API to 127.0.0.1:4012. Without this, each restart
++# closed the listener for the whole boot and the gateway got ECONNREFUSED; with it, requests that arrive mid-restart
++# wait in the kernel's accept queue and are served as soon as the new process calls accept().
++[Unit]
++Description=OpenVibe.Tools img HTTP socket
++# No PartOf=openvibe-tools-img.service: the socket must outlive every restart.
++
++[Socket]
++# Loopback only: the gateway is the only client (the host firewall is off, so 0.0.0.0 would be public).
++ListenStream=127.0.0.1:4012
++NoDelay=true
++Backlog=511
++
++[Install]
++WantedBy=sockets.target
ddiff --git a/apps/img/server/index.js b/apps/img/server/index.js
iindex 91b98cf..6c68cc0 100644
---- a/apps/img/server/index.js
++++ b/apps/img/server/index.js
@@@ -278,7 +278,10 @@ app.get('*', (req, res) => {
});
// ── Start ────────────────────────────────────────────────────
--const server = app.listen(config.port, config.host, () => {
++// systemd hands this process the listener when deploy/systemd/openvibe-tools-img.socket is installed: a restart then
++// queues the gateway's requests in the kernel instead of refusing them. Otherwise it listens on the port itself.
++const socketActivated = process.env.LISTEN_FDS === '1' && Number(process.env.LISTEN_PID) === process.pid;
++const server = app.listen(...(socketActivated ? [{ fd: 3 }] : [config.port, config.host]), () => {
retention.startCleanup();
console.log(`\n╔═══════════════════════════════════════╗`);
console.log(`║ 🖼️ Img.OpenVibe — Image Conversion Hub ║`);
ddiff --git a/apps/maps/deploy/systemd/openvibe-tools-maps.service.d/socket.conf b/apps/maps/deploy/systemd/openvibe-tools-maps.service.d/socket.conf
nnew file mode 100644
iindex 0000000..5c7be5e
---- /dev/null
++++ b/apps/maps/deploy/systemd/openvibe-tools-maps.service.d/socket.conf
@@@ -0,0 +1,9 @@
++# Bind the service to the socket unit that owns its listener. A drop-in, so the main unit stays usable on a box that
++# has not been switched over to socket activation.
++[Unit]
++Requires=openvibe-tools-maps.socket
++After=openvibe-tools-maps.socket
++
++[Service]
++# Node's libuv expects a non-blocking descriptor. (A [Service] option: in [Socket] systemd ignores it.)
++NonBlocking=true
ddiff --git a/apps/maps/deploy/systemd/openvibe-tools-maps.socket b/apps/maps/deploy/systemd/openvibe-tools-maps.socket
nnew file mode 100644
iindex 0000000..4ec1384
---- /dev/null
++++ b/apps/maps/deploy/systemd/openvibe-tools-maps.socket
@@@ -0,0 +1,17 @@
++# OpenVibe.Tools maps app — listening socket, owned by systemd rather than by the process (as the gateway's).
++#
++# The gateway (openvibe-tools.service) proxies this app's pages and API to 127.0.0.1:4010. Without this, each restart
++# closed the listener for the whole boot and the gateway got ECONNREFUSED; with it, requests that arrive mid-restart
++# wait in the kernel's accept queue and are served as soon as the new process calls accept().
++[Unit]
++Description=OpenVibe.Tools maps HTTP socket
++# No PartOf=openvibe-tools-maps.service: the socket must outlive every restart.
++
++[Socket]
++# Loopback only: the gateway is the only client (the host firewall is off, so 0.0.0.0 would be public).
++ListenStream=127.0.0.1:4010
++NoDelay=true
++Backlog=511
++
++[Install]
++WantedBy=sockets.target
ddiff --git a/apps/maps/server/index.js b/apps/maps/server/index.js
iindex af41841..d23079b 100644
---- a/apps/maps/server/index.js
++++ b/apps/maps/server/index.js
@@@ -494,7 +494,10 @@ app.get('*', (req, res) => {
});
// ── Start ──────────────────────────────────────────────────
--const server = app.listen(config.port, config.host, () => {
++// systemd hands this process the listener when deploy/systemd/openvibe-tools-maps.socket is installed: a restart then
++// queues the gateway's requests in the kernel instead of refusing them. Otherwise it listens on the port itself.
++const socketActivated = process.env.LISTEN_FDS === '1' && Number(process.env.LISTEN_PID) === process.pid;
++const server = app.listen(...(socketActivated ? [{ fd: 3 }] : [config.port, config.host]), () => {
console.log(`[Maps.OpenVibe] 🗺️ maps.openvibe.tools listening on ${config.host}:${config.port}`);
});
ddiff --git a/apps/text/deploy/systemd/openvibe-tools-text.service.d/socket.conf b/apps/text/deploy/systemd/openvibe-tools-text.service.d/socket.conf
nnew file mode 100644
iindex 0000000..c9fc1c1
---- /dev/null
++++ b/apps/text/deploy/systemd/openvibe-tools-text.service.d/socket.conf
@@@ -0,0 +1,9 @@
++# Bind the service to the socket unit that owns its listener. A drop-in, so the main unit stays usable on a box that
++# has not been switched over to socket activation.
++[Unit]
++Requires=openvibe-tools-text.socket
++After=openvibe-tools-text.socket
++
++[Service]
++# Node's libuv expects a non-blocking descriptor. (A [Service] option: in [Socket] systemd ignores it.)
++NonBlocking=true
ddiff --git a/apps/text/deploy/systemd/openvibe-tools-text.socket b/apps/text/deploy/systemd/openvibe-tools-text.socket
nnew file mode 100644
iindex 0000000..5f7067f
---- /dev/null
++++ b/apps/text/deploy/systemd/openvibe-tools-text.socket
@@@ -0,0 +1,17 @@
++# OpenVibe.Tools text app — listening socket, owned by systemd rather than by the process (as the gateway's).
++#
++# The gateway (openvibe-tools.service) proxies this app's pages and API to 127.0.0.1:4015. Without this, each restart
++# closed the listener for the whole boot and the gateway got ECONNREFUSED; with it, requests that arrive mid-restart
++# wait in the kernel's accept queue and are served as soon as the new process calls accept().
++[Unit]
++Description=OpenVibe.Tools text HTTP socket
++# No PartOf=openvibe-tools-text.service: the socket must outlive every restart.
++
++[Socket]
++# Loopback only: the gateway is the only client (the host firewall is off, so 0.0.0.0 would be public).
++ListenStream=127.0.0.1:4015
++NoDelay=true
++Backlog=511
++
++[Install]
++WantedBy=sockets.target
ddiff --git a/apps/text/server/index.js b/apps/text/server/index.js
iindex 27f2a87..dc37d7a 100644
---- a/apps/text/server/index.js
++++ b/apps/text/server/index.js
@@@ -168,7 +168,10 @@ app.get('*', (req, res) => {
});
// ── Start ────────────────────────────────────────────────────
--const server = app.listen(config.port, config.host, () => {
++// systemd hands this process the listener when deploy/systemd/openvibe-tools-text.socket is installed: a restart then
++// queues the gateway's requests in the kernel instead of refusing them. Otherwise it listens on the port itself.
++const socketActivated = process.env.LISTEN_FDS === '1' && Number(process.env.LISTEN_PID) === process.pid;
++const server = app.listen(...(socketActivated ? [{ fd: 3 }] : [config.port, config.host]), () => {
const toolCount = new Set(Object.values(HOSTNAME_MAP)).size;
const domainCount = Object.keys(HOSTNAME_MAP).length;
console.log(`\n╔═══════════════════════════════════════╗`);
ddiff --git a/apps/yt/deploy/systemd/openvibe-tools-yt.service.d/socket.conf b/apps/yt/deploy/systemd/openvibe-tools-yt.service.d/socket.conf
nnew file mode 100644
iindex 0000000..6786aeb
---- /dev/null
++++ b/apps/yt/deploy/systemd/openvibe-tools-yt.service.d/socket.conf
@@@ -0,0 +1,9 @@
++# Bind the service to the socket unit that owns its listener. A drop-in, so the main unit stays usable on a box that
++# has not been switched over to socket activation.
++[Unit]
++Requires=openvibe-tools-yt.socket
++After=openvibe-tools-yt.socket
++
++[Service]
++# Node's libuv expects a non-blocking descriptor. (A [Service] option: in [Socket] systemd ignores it.)
++NonBlocking=true
ddiff --git a/apps/yt/deploy/systemd/openvibe-tools-yt.socket b/apps/yt/deploy/systemd/openvibe-tools-yt.socket
nnew file mode 100644
iindex 0000000..0f9ab8a
---- /dev/null
++++ b/apps/yt/deploy/systemd/openvibe-tools-yt.socket
@@@ -0,0 +1,17 @@
++# OpenVibe.Tools yt app — listening socket, owned by systemd rather than by the process (as the gateway's).
++#
++# The gateway (openvibe-tools.service) proxies this app's pages and API to 127.0.0.1:4013. Without this, each restart
++# closed the listener for the whole boot and the gateway got ECONNREFUSED; with it, requests that arrive mid-restart
++# wait in the kernel's accept queue and are served as soon as the new process calls accept().
++[Unit]
++Description=OpenVibe.Tools yt HTTP socket
++# No PartOf=openvibe-tools-yt.service: the socket must outlive every restart.
++
++[Socket]
++# Loopback only: the gateway is the only client (the host firewall is off, so 0.0.0.0 would be public).
++ListenStream=127.0.0.1:4013
++NoDelay=true
++Backlog=511
++
++[Install]
++WantedBy=sockets.target
ddiff --git a/apps/yt/server/index.js b/apps/yt/server/index.js
iindex 887fc01..238439a 100644
---- a/apps/yt/server/index.js
++++ b/apps/yt/server/index.js
@@@ -317,7 +317,10 @@ app.use((req, res) => {
});
// ── Start ────────────────────────────────────────────────────
--const server = app.listen(config.port, config.host, () => {
++// systemd hands this process the listener when deploy/systemd/openvibe-tools-yt.socket is installed: a restart then
++// queues the gateway's requests in the kernel instead of refusing them. Otherwise it listens on the port itself.
++const socketActivated = process.env.LISTEN_FDS === '1' && Number(process.env.LISTEN_PID) === process.pid;
++const server = app.listen(...(socketActivated ? [{ fd: 3 }] : [config.port, config.host]), () => {
downloader.startCleanup();
downloader.startUpstreamProbe();
console.log(`\n╔═══════════════════════════════════════╗`);