Skip to content

Instantly share code, notes, and snippets.

@jeremyben
Last active February 20, 2024 06:07
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremyben/46ad0422dd0e925efd879324efa3f02f to your computer and use it in GitHub Desktop.
Save jeremyben/46ad0422dd0e925efd879324efa3f02f to your computer and use it in GitHub Desktop.
systemd socket activation
# https://www.freedesktop.org/software/systemd/man/systemd.unit.html
[Unit]
Description=My App
After=network.target
# https://www.freedesktop.org/software/systemd/man/systemd.exec.html
[Service]
Type=simple
# https://www.freedesktop.org/software/systemd/man/systemd.exec.html#WorkingDirectory=
WorkingDirectory=-/srv/app/
ExecStart=/usr/bin/node server.js
DynamicUser=yes
ProtectHome=yes
Restart=always
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
# https://www.freedesktop.org/software/systemd/man/systemd.socket.html
[Socket]
# https://www.freedesktop.org/software/systemd/man/systemd.socket.html#ListenStream=
ListenStream=3000
BindIPv6Only = both
# SO_REUSEPORT not supported by node: https://github.com/nodejs/node/issues/12228
# ReusePort=true
[Install]
WantedBy=sockets.target
import http from 'http'
import { getSystemdSocketHandle } from './socket-handle'
const server = http.createServer(async (req, res) => {
console.log(req.url, req.headers)
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ data: true }))
})
// Still compatible if not socket activated.
const handle = getSystemdSocketHandle() || process.env.PORT || 3000
server.listen(handle, () => {
console.log('Server started', server.address())
})
/**
* Returns file descriptor of systemd socket.
* Returns `undefined` if the process has not been activated by systemd socket.
*/
export function getSystemdSocketHandle() {
// https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html
const SD_LISTEN_FDS_START = 3
const LISTEN_PID = !!process.env.LISTEN_PID && Number.parseInt(process.env.LISTEN_PID)
const LISTEN_FDS = !!process.env.LISTEN_FDS && Number.parseInt(process.env.LISTEN_FDS)
const LISTEN_FDNAMES = !!process.env.LISTEN_FDNAMES && process.env.LISTEN_FDNAMES.split(':')
// console.log('PID:', process.pid, 'PPID:', process.ppid)
// console.log('LISTEN_PID:', LISTEN_PID, 'LISTEN_FDS:', LISTEN_FDS, 'LISTEN_FDNAMES:', LISTEN_FDNAMES)
const isSocketActivated = !!LISTEN_PID && !!LISTEN_FDS
if (!isSocketActivated) {
return undefined
}
if (LISTEN_PID !== process.pid) {
throw Error(`Cannot use file descriptors meant for pid ${LISTEN_PID} in pid ${process.pid}`)
}
if (LISTEN_FDS > 1) {
throw Error(`One file descriptor expected. ${LISTEN_FDS} received (${LISTEN_FDNAMES})`)
}
return { fd: SD_LISTEN_FDS_START }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment