Salvatore, Daemon
0 dependency daemon management for Node.
and
A replacement for pm2
that isn't copyleft.
DaemonPID is a Node utility module which provides straight-forward and robust PID file management; perfect for writing and reading PID files for daemonized services. It provides the ability to check or monitor the status of previously launched child processes, store additional data along with the process id, and provides process start-time verification to ensure the recorded process-id was not recycled by the OS.
Warning: this module is for POSIX systems and will not function on Windows. If you're a Windows veteran and would like to make it work, please feel free to contribute!
Features
✨ Maintained
🦹🏽 ESM
☁️ High-level API
⏱️ No callbacks - uses Promises
😎 TypeScript-friendly
🙂↔️ Compatible with MacOS and Linux
Daemon features:
- can "start" multiple times and retain the same process
- process keeps running after main program exits
- configurable logging to file
What is a PID file?
A PID file is a text file in a well-defined location in the
file-system which contains at-least the process ID of a running application.
Writing PID files is a convention used in many software systems as a simple
way to check the status of services or daemonized processes without requiring
separate supervisory or monitoring processes.
PID File Pitfalls
Process IDs are not required to be unique and can potentially be recycled. To solve this issue, salvatore
also records and checks the command
and start time of the process for future comparison and status checks.
What is a Daemon?
A daemon is a program that runs in the background, rather than under direct control of an interactive user. It typically performs routine tasks or provides services such as managing printers, handling emails, or serving web pages. Daemons are common in Unix-like operating systems and are crucial for system functionality by handling system processes independently of user input. They often start when the system boots and run continuously until the system shuts down.
more info on wikipedia.
import { PidFile } from 'salvatore'
The PidFile
provides a number of methods and getters for working with PID files and helping you build tools that could use process-based tooling.
For an example, with logging, check out the examples/pidfile
folder.
For the child process:
import { PidFile } from 'salvatore';
const pidFile = new PidFile('.some.file.pid');
pidFile.write();
process.on('exit', function() {
pidFile.delete();
});
For the parent process:
import { PidFile } from 'salvatore';
import { spawn } from 'node:child_process';
const pidFile = new PidFile('.some.file.pid');
const process = spawn(process.argv0, []'child.js'], {
detached: true,
stdio: 'ignore',
});
pidFile.data
pidFile.uptime
pidFile.startedAt
pidFile.isRunning
pidFile.pid
pidFile.fileContents
pidFile.exists
pidFile.command
pidFile.kill(signal);
import { Daemon } from 'salvatore'
In the file-to-be-daemonized:
import { PidFile } from 'salvatore';
const pidFile = new PidFile('.example.pid');
pidFile.write();
process.on('exit', () => pidFile.delete());
async function run() {
await new Promise((resolve) => {
setTimeout(() => resolve(null), 20_000);
});
}
await run();
a small CLI for managing the daemon:
#!/usr/bin/env node
import { Daemon } from 'salvatore';
const daemon = new Daemon('./waiter.js', { pidFilePath: '.example.pid' });
const [,, ...args] = process.argv;
const [command] = args;
async function main() {
switch (command) {
case 'start': return daemon.ensureStarted();
case 'stop': return daemon.stop();
case 'status': return console.log(JSON.stringify(daemon.info));
default: throw new Error(`Command not recognized`);
}
}
await main();
Storing Data
Additional information can be stored in the PID file for use later. Any data convertible to JSON can be stored. This could be useful for starting a server and then reading out what PORT that server had started on:
In the daemonized file:
import { PidFile } from 'salvatore';
import express from 'express';
const pidfile = new PidFile('.express.pid');
const app = express();
const listener = app.listen(8888, function () {
let { port, host } = listener.address();
pidFile.write({
port,
host,
})
});
and then in the bootstrap / cli / start script
#!/usr/bin/env node
import { Daemon } from 'salvatore';
const daemon = new Daemon('./server.js', { pidFilePath: '.express.pid' });
const [,, ...args] = process.argv;
const [command] = args;
async function main() {
switch (command) {
case 'start':
return daemon.ensureStarted();
case 'address': {
const { host, port } = daemon.info.data
console.log({ host, port })
}
}
}
await main();
Examples
See the examples
directory for examples.
Each example has a CLI with these commands:
./cli.js start
./cli.js stop
./cli.js status
Example:
salvatore/examples/daemon/
❯ ./cli.js status
Running: true
PID: 49443
Started: Fri Jun 28 2024 12:35:32 GMT-0400 (Eastern Daylight Time)
Uptime: 4783086
Data: "custom-data-from-the-daemon"
Contributing
- fork it
- change it
- pr it
- 🎉 collab time 🎉
Notes
Each test that operates on the examples folder runs in its own tmp directory. This is because we need a unique pid file for each test, and in order to run the tests in parallel, this is the only way to not run in to issues with one test reading another's pid file.
Why "Salvatore"
I've pronounced "Daemon" as "Damon" my whole life, and I can't stop.
I know that literature out there says that "Daemon" is supposed to be prounced like "Demon", but 1. I don't like that, and 2. I appreciate the disambiguation. Though, after having working through this project now, debugging mac vs linux issues, I understand why folks would call these things demons. oofta.
Damon Salvatore is the name of a character in The Vampire Diaries, and since I prounce "Daemon" as "Damon", it was a natural fit.
Original project: daemon-pid - published code