mirror of
https://github.com/Routstr/routstrd.git
synced 2026-08-10 20:17:32 +00:00
fix: surface real daemon error on startup failure and hint kill PID
The CLI spawned the daemon detached with stdout/stderr redirected to debug.log, so when the daemon exited early the user only saw a useless 'Check logs in ~/.routstrd/logs' message while the actual error sat silently in the log file. - daemon/index.ts: print fatal startup error to stderr (captured by the spawn redirect) in addition to the dated logger. - start-daemon.ts: record debug.log offset before spawning; on early exit read the newly appended output and include it in the thrown error. - coco-client.ts: add 'kill <PID>' alternative to the legacy-cocod guard error message alongside 'cocod stop'.
This commit is contained in:
@@ -335,6 +335,12 @@ async function main(): Promise<void> {
|
||||
if (import.meta.main) {
|
||||
main().catch((error) => {
|
||||
logger.error("Failed to start Routstr daemon:", error);
|
||||
// Also write to stderr so the spawning CLI can surface the real error
|
||||
// (stdout/stderr are redirected to debug.log by start-daemon.ts).
|
||||
console.error(
|
||||
"Failed to start Routstr daemon:",
|
||||
error instanceof Error ? error.message : error,
|
||||
);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ export async function assertLegacyCocodNotRunning(
|
||||
throw new Error(
|
||||
`Legacy cocod daemon is still running with PID ${runningPid}. ` +
|
||||
"Refusing to open the wallet database because cocod and coco-core cannot safely use it at the same time. " +
|
||||
"Run 'cocod stop' and try again.",
|
||||
`Run 'cocod stop' or 'kill ${runningPid}' and try again.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ export async function assertLegacyCocodNotRunning(
|
||||
throw new Error(
|
||||
`Legacy cocod daemon is still running with PID ${newlyRunningPid}. ` +
|
||||
"Refusing to open the wallet database because cocod and coco-core cannot safely use it at the same time. " +
|
||||
"Run 'cocod stop' and try again.",
|
||||
`Run 'cocod stop' or 'kill ${newlyRunningPid}' and try again.`,
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
|
||||
+30
-2
@@ -1,4 +1,4 @@
|
||||
import { openSync } from "fs";
|
||||
import { openSync, existsSync, readFileSync } from "fs";
|
||||
import { logger } from "./utils/logger";
|
||||
import { CONFIG_DIR, LOGS_DIR } from "./utils/config";
|
||||
import { withCrossProcessLock } from "./utils/process-lock";
|
||||
@@ -6,6 +6,27 @@ import { withCrossProcessLock } from "./utils/process-lock";
|
||||
const DAEMON_STARTUP_LOCK_PATH = `${CONFIG_DIR}/routstrd-startup.lock`;
|
||||
const DEBUG_LOG_PATH = `${CONFIG_DIR}/debug.log`;
|
||||
|
||||
/**
|
||||
* The spawned daemon's stdout/stderr is redirected to DEBUG_LOG_PATH, so when
|
||||
* it exits early we can surface its actual error from there instead of just
|
||||
* telling the user to go read logs.
|
||||
*/
|
||||
function readDaemonOutput(offset: number): string {
|
||||
try {
|
||||
if (!existsSync(DEBUG_LOG_PATH)) return "";
|
||||
const content = readFileSync(DEBUG_LOG_PATH, "utf-8");
|
||||
return content
|
||||
.slice(offset)
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
.slice(-30)
|
||||
.join("\n");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
async function isDaemonHealthy(port: string): Promise<boolean> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
||||
@@ -44,6 +65,9 @@ async function startDaemonUnlocked(
|
||||
const daemonScript = new URL("./daemon/index.js", import.meta.url).pathname;
|
||||
const shellCmd = `bun run "${daemonScript}" ${args.map((a) => `'${a}'`).join(" ")}`;
|
||||
|
||||
const debugLogOffset = existsSync(DEBUG_LOG_PATH)
|
||||
? readFileSync(DEBUG_LOG_PATH, "utf-8").length
|
||||
: 0;
|
||||
const debugLogFd = openSync(DEBUG_LOG_PATH, "a");
|
||||
|
||||
const proc = Bun.spawn(["sh", "-c", shellCmd], {
|
||||
@@ -65,8 +89,12 @@ async function startDaemonUnlocked(
|
||||
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
||||
|
||||
if (exitCode !== null) {
|
||||
const daemonOutput = readDaemonOutput(debugLogOffset);
|
||||
throw new Error(
|
||||
`Daemon process exited early with code ${exitCode}. Check logs in ${LOGS_DIR}`,
|
||||
`Daemon process exited early with code ${exitCode}.` +
|
||||
(daemonOutput
|
||||
? `\n\nDaemon output:\n${daemonOutput}`
|
||||
: ` Check logs in ${LOGS_DIR}`),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user