mirror of
https://github.com/Routstr/routstrd.git
synced 2026-10-05 20:38:22 +00:00
feat(update): restart daemons after update
The update command previously only downloaded and installed new
binaries for routstrd and cocod without restarting the running
daemons, so updates would not take effect until a manual restart.
- Add restartDaemonsAfterUpdate() helper that gracefully stops and
restarts both daemons after a successful update
- routstrd: uses POST /stop (drains active connections), polls for
shutdown, then calls startDaemon() with configured port/provider
- cocod: runs 'cocod stop', then spawns 'cocod daemon' detached,
polls 'cocod ping' until it comes back up
- Skips restart for daemons that weren't running
- Skips routstrd daemon restart when using a remote daemon
- Collects and reports failures without rolling back the update
- Bump @routstr/sdk to 0.3.15
- Bump routstrd version to 0.3.7
Closes nostr task: update-restart
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "routstrd",
|
"name": "routstrd",
|
||||||
"version": "0.3.6",
|
"version": "0.3.7",
|
||||||
"module": "src/index.ts",
|
"module": "src/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": false,
|
"private": false,
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cashu/cashu-ts": "^4.3.0",
|
"@cashu/cashu-ts": "^4.3.0",
|
||||||
"@routstr/sdk": "^0.3.14",
|
"@routstr/sdk": "^0.3.15",
|
||||||
"applesauce-core": "^5.1.0",
|
"applesauce-core": "^5.1.0",
|
||||||
"applesauce-relay": "^5.1.0",
|
"applesauce-relay": "^5.1.0",
|
||||||
"applesauce-wallet-connect": "^6.0.0",
|
"applesauce-wallet-connect": "^6.0.0",
|
||||||
|
|||||||
+127
@@ -103,6 +103,130 @@ async function installCocodOrExit(): Promise<void> {
|
|||||||
console.log("cocod installed successfully.");
|
console.log("cocod installed successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restart the routstrd and cocod daemons after an update so the new
|
||||||
|
* binaries take effect immediately. Failures are collected and reported
|
||||||
|
* but never roll back the update itself.
|
||||||
|
*/
|
||||||
|
async function restartDaemonsAfterUpdate(): Promise<void> {
|
||||||
|
const config = await loadConfig();
|
||||||
|
const isRemote = !!config.daemonUrl;
|
||||||
|
const failures: string[] = [];
|
||||||
|
|
||||||
|
// --- routstrd daemon ---
|
||||||
|
if (isRemote) {
|
||||||
|
console.log("\nUsing remote daemon — skipping routstrd daemon restart.");
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const wasRunning = await isDaemonRunning();
|
||||||
|
if (!wasRunning) {
|
||||||
|
console.log("\nroutstrd daemon was not running — skipping restart.");
|
||||||
|
} else {
|
||||||
|
console.log("\nRestarting routstrd daemon...");
|
||||||
|
|
||||||
|
// Graceful stop — the /stop endpoint closes the HTTP server
|
||||||
|
// (draining active connections) then exits.
|
||||||
|
await callDaemon("/stop", { method: "POST" });
|
||||||
|
|
||||||
|
for (let i = 0; i < 50; i++) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
if (!(await isDaemonRunning())) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await isDaemonRunning()) {
|
||||||
|
throw new Error("routstrd did not stop within 5 seconds");
|
||||||
|
}
|
||||||
|
console.log("routstrd daemon stopped.");
|
||||||
|
|
||||||
|
console.log("Starting routstrd daemon...");
|
||||||
|
await startDaemon({
|
||||||
|
port: String(config.port || 8008),
|
||||||
|
provider: config.provider || undefined,
|
||||||
|
});
|
||||||
|
console.log("routstrd daemon restarted.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const msg = error instanceof Error ? error.message : String(error);
|
||||||
|
failures.push(`routstrd daemon: ${msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- cocod daemon ---
|
||||||
|
try {
|
||||||
|
const cocodExecutable = resolveCocodExecutable(config.cocodPath);
|
||||||
|
|
||||||
|
// Check whether cocod is currently running.
|
||||||
|
const pingProc = Bun.spawn([cocodExecutable, "ping"], {
|
||||||
|
stdout: "ignore",
|
||||||
|
stderr: "ignore",
|
||||||
|
});
|
||||||
|
const cocodWasRunning = (await pingProc.exited) === 0;
|
||||||
|
|
||||||
|
if (!cocodWasRunning) {
|
||||||
|
console.log("\ncocod daemon was not running — skipping restart.");
|
||||||
|
} else {
|
||||||
|
console.log("\nRestarting cocod daemon...");
|
||||||
|
|
||||||
|
const stopProc = Bun.spawn([cocodExecutable, "stop"], {
|
||||||
|
stdout: "inherit",
|
||||||
|
stderr: "inherit",
|
||||||
|
});
|
||||||
|
const stopCode = await stopProc.exited;
|
||||||
|
if (stopCode !== 0) {
|
||||||
|
throw new Error(`cocod stop exited with code ${stopCode}`);
|
||||||
|
}
|
||||||
|
console.log("cocod daemon stopped.");
|
||||||
|
|
||||||
|
// Start cocod in the background (detached, like the wallet client does).
|
||||||
|
const env = { ...process.env };
|
||||||
|
const startProc = Bun.spawn([cocodExecutable, "daemon"], {
|
||||||
|
stdout: "ignore",
|
||||||
|
stderr: "ignore",
|
||||||
|
stdin: "ignore",
|
||||||
|
detached: true,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
startProc.unref?.();
|
||||||
|
|
||||||
|
// Poll until cocod responds to ping (max ~10 s).
|
||||||
|
let started = false;
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
const poll = Bun.spawn([cocodExecutable, "ping"], {
|
||||||
|
stdout: "ignore",
|
||||||
|
stderr: "ignore",
|
||||||
|
});
|
||||||
|
if ((await poll.exited) === 0) {
|
||||||
|
started = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!started) {
|
||||||
|
throw new Error("cocod did not come back up within 10 seconds");
|
||||||
|
}
|
||||||
|
console.log("cocod daemon restarted.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const msg = error instanceof Error ? error.message : String(error);
|
||||||
|
failures.push(`cocod daemon: ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- report ---
|
||||||
|
if (failures.length > 0) {
|
||||||
|
console.error("\n⚠ Some daemons failed to restart:");
|
||||||
|
for (const f of failures) {
|
||||||
|
console.error(` - ${f}`);
|
||||||
|
}
|
||||||
|
console.error(
|
||||||
|
"The update was applied but may not take effect until daemons are manually restarted.",
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("\n✓ All daemons restarted successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
async function requireLocalDaemon(): Promise<void> {
|
async function requireLocalDaemon(): Promise<void> {
|
||||||
const config = await loadConfig();
|
const config = await loadConfig();
|
||||||
if (config.daemonUrl) {
|
if (config.daemonUrl) {
|
||||||
@@ -264,6 +388,9 @@ program
|
|||||||
console.log("cocod updated successfully.\n");
|
console.log("cocod updated successfully.\n");
|
||||||
|
|
||||||
console.log("Both routstrd and cocod have been updated!");
|
console.log("Both routstrd and cocod have been updated!");
|
||||||
|
|
||||||
|
// Restart daemons so the new binaries take effect immediately.
|
||||||
|
await restartDaemonsAfterUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
|
|||||||
Reference in New Issue
Block a user