From 752c06d24a0743a350effabb09c1252ae4b1200c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sat, 29 Aug 2026 14:16:46 +0100 Subject: [PATCH] save the pm2 list after starting or stopping a server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without it, pm2 resurrects the list as it was the last time somebody happened to run `pm2 save` — so a machine that reboots comes back without the site, and with nothing to indicate anything was meant to be there. The systemd unit is enabled on this host, so the resurrect definitely happens; the only question was what it would restore. After stopping too, not only after starting. The dump has to match reality in both directions: a server stopped here and left in the dump comes back on its own at the next reboot, which is the same bug pointing the other way. A failed save does not fail the action — the process is already started or stopped by then, and reporting "could not start" about something that did start is worse than a line saying the list was not persisted. It is in the console either way. Co-Authored-By: Claude Opus 5 --- api/servers.ts | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/api/servers.ts b/api/servers.ts index ee3120a..f8df6b1 100644 --- a/api/servers.ts +++ b/api/servers.ts @@ -128,6 +128,27 @@ const run = async ( } }; +/** + * Persist pm2's process list, so a reboot brings back what is actually running. + * + * Without this, pm2 resurrects the list as it was the last time somebody saved — which, after starting + * a server here, means the machine comes back up without the site and with no indication that anything + * was meant to be there. The systemd unit is enabled on this host, so the resurrect definitely happens; + * the only question is what it restores. + * + * Run after every start AND every stop, because the dump has to match reality in both directions: a + * server stopped here and left in the dump comes back on its own at the next reboot, which is the same + * bug pointing the other way. + * + * A failure to save does not fail the action. The process is already started or stopped by then, and + * reporting "could not start" about something that did start is worse than a line saying the list was + * not persisted. + */ +async function persist(): Promise { + const saved = await run([PM2, 'save'], 30_000); + return saved.ok ? 'pm2 list saved' : `pm2 save failed: ${saved.output}`; +} + export function checkPort(value: unknown, field: string): number { const port = Number(value); if (!Number.isInteger(port) || port < 1024 || port > 65535) { @@ -270,14 +291,19 @@ export async function startServer(which: ServerName): Promise<{ ok: boolean; out return { ok: started.ok, - output: `$ pm2 start ${PROCESS[which]} (${which}, port ${port})\n${started.output}`, + output: [`$ pm2 start ${PROCESS[which]} (${which}, port ${port})`, started.output, await persist()] + .filter(Boolean) + .join('\n'), }; } /** Stop one. `delete`, not `stop`, so a stopped entry does not linger in the list looking like a fault. */ export async function stopServer(which: ServerName): Promise<{ ok: boolean; output: string }> { const result = await run([PM2, 'delete', PROCESS[which]], 30_000); - return { ok: result.ok, output: `$ pm2 delete ${PROCESS[which]}\n${result.output}` }; + return { + ok: result.ok, + output: [`$ pm2 delete ${PROCESS[which]}`, result.output, await persist()].filter(Boolean).join('\n'), + }; } /**