mirror of
https://github.com/Routstr/routstrd.git
synced 2026-08-09 03:44:38 +00:00
fix: call stopLegacyCocod in restart/update/service paths and wait for pidfile release
The 'restart' command failed when the old daemon still held the cocod pidfile during wallet disposal — assertLegacyCocodNotRunning detected the PID as 'cocod running' and refused to start. - Call stopLegacyCocod() in restart, update restart, and service install (previously only in 'start'). - After /stop, poll for both health-down AND pidfile-released before spawning the new daemon. The pidfile is released during wallet disposal, which happens after server.close(), so health check alone was insufficient.
This commit is contained in:
+118
-8
@@ -146,16 +146,25 @@ async function restartDaemonsAfterUpdate(): Promise<void> {
|
||||
// (draining active connections) then exits.
|
||||
await callDaemon("/stop", { method: "POST" });
|
||||
|
||||
for (let i = 0; i < 50; i++) {
|
||||
// Wait for daemon to fully stop — both the HTTP health check to fail
|
||||
// AND the legacy cocod pidfile to be released.
|
||||
const pidFilePath = `${process.env.HOME || process.env.USERPROFILE || ""}/.cocod/cocod.pid`;
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
if (!(await isDaemonRunning())) break;
|
||||
const healthDown = !(await isDaemonRunning());
|
||||
const pidFileReleased = !existsSync(pidFilePath);
|
||||
if (healthDown && pidFileReleased) break;
|
||||
}
|
||||
|
||||
if (await isDaemonRunning()) {
|
||||
throw new Error("routstrd did not stop within 5 seconds");
|
||||
throw new Error("routstrd did not stop within 10 seconds");
|
||||
}
|
||||
console.log("routstrd daemon stopped.");
|
||||
|
||||
// Stop a legacy cocod daemon (from older routstrd versions) before
|
||||
// starting the new in-process coco wallet.
|
||||
await stopLegacyCocod();
|
||||
|
||||
console.log("Starting routstrd daemon...");
|
||||
await startDaemon({
|
||||
port: String(config.port || 8008),
|
||||
@@ -536,9 +545,97 @@ program
|
||||
program
|
||||
.command("balance")
|
||||
.description("Get wallet and API key balances")
|
||||
.action(async () => {
|
||||
.option("--api-keys", "List all stored API keys (baseUrl + key + balance)", false)
|
||||
.option(
|
||||
"--delete-api-keys <baseUrl>",
|
||||
"Delete the API key stored for the given provider base URL (refunds balance first)",
|
||||
)
|
||||
.option(
|
||||
"--mint-url <url>",
|
||||
"Mint URL to refund the deleted API key balance to (defaults to first mint in wallet)",
|
||||
)
|
||||
.action(async (options: { apiKeys: boolean; deleteApiKeys?: string; mintUrl?: string }) => {
|
||||
await ensureDaemonRunning();
|
||||
|
||||
// --delete-api-keys <baseUrl>: refund then remove the API key for a
|
||||
// single provider.
|
||||
if (options.deleteApiKeys) {
|
||||
const baseUrl = options.deleteApiKeys;
|
||||
const queryParts = [`baseUrl=${encodeURIComponent(baseUrl)}`];
|
||||
if (options.mintUrl) {
|
||||
queryParts.push(`mintUrl=${encodeURIComponent(options.mintUrl)}`);
|
||||
}
|
||||
const result = await callDaemon(
|
||||
`/keys/api/delete?${queryParts.join("&")}`,
|
||||
{ method: "DELETE" },
|
||||
);
|
||||
if (result.error) {
|
||||
console.log(result.error);
|
||||
process.exit(1);
|
||||
}
|
||||
const out = result.output as
|
||||
| {
|
||||
baseUrl?: string;
|
||||
removed?: boolean;
|
||||
refunded?: boolean;
|
||||
refundedAmount?: number;
|
||||
refundMessage?: string;
|
||||
message?: string;
|
||||
}
|
||||
| undefined;
|
||||
if (out) {
|
||||
console.log(out.message ?? `Removed API key for ${baseUrl}`);
|
||||
if (out.refunded && out.refundedAmount !== undefined) {
|
||||
console.log(` Refunded: ${out.refundedAmount} sats`);
|
||||
} else if (out.refundMessage) {
|
||||
console.log(` Refund: ${out.refundMessage}`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// --api-keys: list every stored API key with full details.
|
||||
if (options.apiKeys) {
|
||||
const result = await callDaemon("/keys/api");
|
||||
if (result.error) {
|
||||
console.log(result.error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const data = result.output as
|
||||
| {
|
||||
apiKeys: Array<{
|
||||
baseUrl: string;
|
||||
key: string;
|
||||
balance: number;
|
||||
lastUsed: number | null;
|
||||
}>;
|
||||
count: number;
|
||||
total: number;
|
||||
unit: string;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
console.log("=== API Keys ===\n");
|
||||
if (!data || data.apiKeys.length === 0) {
|
||||
console.log(" No API keys stored.");
|
||||
return;
|
||||
}
|
||||
for (const k of data.apiKeys) {
|
||||
const lastUsed = k.lastUsed
|
||||
? new Date(k.lastUsed).toISOString()
|
||||
: "never";
|
||||
console.log(` ${k.baseUrl}`);
|
||||
console.log(` key: ${k.key}`);
|
||||
console.log(` balance: ${k.balance} ${data.unit}`);
|
||||
console.log(` lastUsed: ${lastUsed}`);
|
||||
console.log("");
|
||||
}
|
||||
console.log(` Total: ${data.apiKeys.length} key(s), ${data.total} ${data.unit}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Default: show the full wallet + API key balance summary.
|
||||
const [walletResult, keysResult] = await Promise.all([
|
||||
callDaemon("/balance"),
|
||||
callDaemon("/keys/balance"),
|
||||
@@ -1645,6 +1742,10 @@ serviceCmd
|
||||
|
||||
console.log("Starting routstrd via PM2...");
|
||||
try {
|
||||
// Stop a legacy cocod daemon (from older routstrd versions) before
|
||||
// starting the new in-process coco wallet.
|
||||
await stopLegacyCocod();
|
||||
|
||||
// Use --interpreter bun to ensure it runs with bun
|
||||
execSync(`pm2 start "${daemonPath}" --name routstrd --interpreter bun`, {
|
||||
stdio: "inherit",
|
||||
@@ -1702,16 +1803,21 @@ program
|
||||
console.log("Stopping daemon...");
|
||||
await callDaemon("/stop", { method: "POST" });
|
||||
|
||||
// Wait for daemon to fully stop
|
||||
for (let i = 0; i < 50; i++) {
|
||||
// Wait for daemon to fully stop — both the HTTP health check to fail
|
||||
// AND the legacy cocod pidfile to be released (the daemon releases
|
||||
// it during wallet disposal, which happens after server.close()).
|
||||
const pidFilePath = `${process.env.HOME || process.env.USERPROFILE || ""}/.cocod/cocod.pid`;
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
if (!(await isDaemonRunning())) {
|
||||
const healthDown = !(await isDaemonRunning());
|
||||
const pidFileReleased = !existsSync(pidFilePath);
|
||||
if (healthDown && pidFileReleased) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (await isDaemonRunning()) {
|
||||
logger.error("Daemon failed to stop within 5 seconds");
|
||||
logger.error("Daemon failed to stop within 10 seconds");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("Daemon stopped.");
|
||||
@@ -1719,6 +1825,10 @@ program
|
||||
console.log("Daemon was not running.");
|
||||
}
|
||||
|
||||
// Stop a legacy cocod daemon (from older routstrd versions) before
|
||||
// starting the new in-process coco wallet — both cannot share coco.db.
|
||||
await stopLegacyCocod();
|
||||
|
||||
console.log("Starting daemon...");
|
||||
await startDaemon({
|
||||
port: options.port || String(config.port || 8008),
|
||||
|
||||
Reference in New Issue
Block a user