diff --git a/package.json b/package.json index 042aeb7..6bdf901 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "routstrd", - "version": "0.3.6", + "version": "0.3.7", "module": "src/index.ts", "type": "module", "private": false, @@ -24,7 +24,7 @@ }, "dependencies": { "@cashu/cashu-ts": "^4.3.0", - "@routstr/sdk": "^0.3.14", + "@routstr/sdk": "^0.3.15", "applesauce-core": "^5.1.0", "applesauce-relay": "^5.1.0", "applesauce-wallet-connect": "^6.0.0", diff --git a/src/cli.ts b/src/cli.ts index 586b000..06cdd90 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -103,6 +103,130 @@ async function installCocodOrExit(): Promise { 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 { + 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 { const config = await loadConfig(); if (config.daemonUrl) { @@ -264,6 +388,9 @@ program console.log("cocod updated successfully.\n"); console.log("Both routstrd and cocod have been updated!"); + + // Restart daemons so the new binaries take effect immediately. + await restartDaemonsAfterUpdate(); }); program