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'), + }; } /**