The daemon was binding to all network interfaces (0.0.0.0) by default
because server.listen(port) without a host argument listens on all
interfaces in Node.js. This exposed unauthenticated endpoints
(/balance, /status, /providers, /wallet/balance, etc.) to anyone
who could reach the port.
Changes:
- Add 'host' field to RoutstrdConfig (default: 127.0.0.1)
- Add --host CLI flag to 'start' and 'restart' commands
- Add ROUTSTRD_HOST env var support in parseArgs
- Pass host to server.listen(port, host, callback)
- Add startup warning when bound to 0.0.0.0
- Update getDaemonBaseUrl to use config.host (with 0.0.0.0 -> localhost
fallback for client connections since 0.0.0.0 is not connectable)
- Update start-daemon.ts health checks to use the correct host
- Pass host through all startDaemon() call sites in cli.ts
- Document --host flag, config field, and security implications in README
Closes ROUTSTRD-BIND-ADDRESS
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.
When upgrading from an older routstrd that ran cocod as a separate
process, the new in-process coco wallet refused to start because cocod
still held coco.db. Users had to manually 'cocod stop' (which ironically
could respawn the daemon) or kill the PID.
- coco-client.ts: add stopLegacyCocod() — reads cocod's PID file, sends
SIGTERM, polls until the process exits and the PID file is removed.
Refuses (no SIGKILL) on timeout to avoid corrupting coco.db mid-recovery.
- cli.ts: call stopLegacyCocod() in the 'start' action before spawning
the daemon, so the upgrade path is automatic.
assertLegacyCocodNotRunning stays as a safety net for any caller that
bypasses the CLI.
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'.
- Extract version-checking logic into src/utils/update-checker.ts (shared
between CLI and TUI)
- Updating routstrd...
bun add v1.2.22 (6bafe260)
installed routstrd@0.3.10 with binaries:
- routstrd
[649.00ms] done
routstrd updated successfully.
Updating cocod...
bun add v1.2.22 (6bafe260)
installed @routstr/cocod@0.0.24 with binaries:
- cocod
[692.00ms] done
cocod updated successfully.
Both routstrd and cocod have been updated!
Using remote daemon — skipping routstrd daemon restart.
cocod daemon was not running — skipping restart.
✓ All daemons restarted successfully. now checks npm for the latest version of each package
and only reinstalls when a newer version is available; skips daemon
restart when nothing was updated
- TUI shows a bold yellow 'UPDATE AVAILABLE' banner below the header on
all tabs when a new version is detected
- TUI checks for updates at most every 210 minutes to avoid spamming the
npm registry; first check fires 3s after startup (non-blocking)
- Bump version to 0.3.11
Check the legacy cocod PID file and Unix socket before opening coco.db. Refuse startup when cocod is active or its state cannot be verified, while permitting confirmed stale socket files. Add focused coverage for PID, live socket, stale socket, and fail-closed behavior.
After rebasing onto main, the update command and restartDaemonsAfterUpdate
still referenced resolveCocodExecutable and @routstr/cocod. Since cocod is
no longer a separate process, remove all cocod daemon management from the
update flow.
- Redirect detached daemon stdout/stderr to ~/.routstrd/debug.log
instead of ignoring them, so uncaught exception stack traces are
no longer lost
- Add process-level uncaughtException/unhandledRejection handlers
that log to the file logger before the process dies
- Wrap setInterval async callbacks (model refresh + refund jobs) in
IIFE catch chains so rejected promises can't escape and kill the
process silently
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
renderToday declared todayStats/recentDays/hourlyMap with let but only
assigned inside if (stats.summary); TypeScript flagged them as used
before assignment (TS2454). Initialize with sensible defaults so the
function degrades gracefully when summary is missing.
The usage-summary tz-bucketing test used hardcoded May 2026 timestamps
with a comment dated 2026-06-02. Those entries aged out of
getUsageSummary's 30-day rolling window, so days came back empty.
Recompute timestamps relative to now and assert on dynamically-derived
local-day date strings.
Calls refreshNostrEvents() after initial bootstrap and in the 21-minute
recurring job, covering provider discovery (38421) and lgtm reviews
(38425) which were previously only refreshed on manual trigger.
Split the monolithic render() into two distinct functions:
- fetchData(): async background fetch that updates state and triggers
a repaint, guarded against overlapping calls
- render(): synchronous paint that reads current state and writes to
stdout, safe to call from key handlers without blocking
Run all four daemon calls concurrently via Promise.all to cut the
blocked window. Remove redundant isDaemonRunning() checks from each
fetch function in data.ts — the single check now lives in fetchData().
Key handlers now call the sync render() directly instead of
void render(false), so scrolling and tab switching feel instant.
The SDK dropped minTotalTokens/maxTotalTokens from AggregateUsageOptions
(routstr-sdk c98de6b), so replace the five aggregate() calls with a single
list() and bucket entries in-process.