Phase 1: lock cron, reload chaud, argon2, providers, IMDb lookup, cache LRU, /health, /metrics, rate limit, UI dark, biome

This commit is contained in:
unfr
2026-04-24 07:35:10 +02:00
parent f9745a2390
commit a184a21f57
36 changed files with 2060 additions and 364 deletions

60
lib/lockFile.js Normal file
View File

@@ -0,0 +1,60 @@
// Simple PID-based lock file. Atomic via O_EXCL.
// If a stale lock is found (PID no longer alive), it is removed.
import { closeSync, openSync, readFileSync, unlinkSync, writeSync } from 'node:fs';
function isAlive(pid) {
try {
process.kill(pid, 0);
return true;
} catch (e) {
return e.code === 'EPERM';
}
}
export function acquireLock(lockPath) {
for (let attempt = 0; attempt < 2; attempt++) {
try {
const fd = openSync(lockPath, 'wx');
writeSync(fd, String(process.pid));
closeSync(fd);
const release = () => {
try {
unlinkSync(lockPath);
} catch {
/* ignore */
}
};
process.on('exit', release);
process.on('SIGINT', () => {
release();
process.exit(130);
});
process.on('SIGTERM', () => {
release();
process.exit(143);
});
return release;
} catch (err) {
if (err.code !== 'EEXIST') throw err;
// Lock exists — check if owner is still alive
let pid = 0;
try {
pid = parseInt(readFileSync(lockPath, 'utf8').trim(), 10);
} catch {
/* unreadable */
}
if (pid && isAlive(pid)) {
throw new Error(`Cron already running (PID ${pid}, lock ${lockPath})`);
}
// Stale lock — remove and retry
console.log(`Removing stale lock (PID ${pid || '?'} no longer alive): ${lockPath}`);
try {
unlinkSync(lockPath);
} catch {
/* ignore */
}
}
}
throw new Error(`Could not acquire lock after retries: ${lockPath}`);
}