Adds an authUrl field to RoutstrdConfig so CLI management commands
(npubs, clients, usage) can be directed to the routstrd-auth proxy
instead of the daemon. This fixes 'routstrd npubs list' failing with
'Only POST is supported' when the auth proxy and daemon run on
separate ports (e.g., Cloudron setup: proxy on 8008, daemon on 8009).
Changes:
- config.ts: add authUrl to RoutstrdConfig
- daemon-client.ts: add getAuthBaseUrl(), callAuth(), refactor callDaemon
- cli.ts: npubs/usage commands use callAuth, remote command accepts --auth-url
- clients.ts: clients commands use callAuth
- Add Dockerfile, docker-compose.yml, and .dockerignore for Bun dev container
- Fix sdkLogger to use logger.warn instead of logger.log for warnings
- Add proper warn method to logger (writes to file instead of console)
- Use console.log for daemon startup messages to keep them visible
- Update cashu-ts getDecodedToken call with explicit well-known-pubkeys
- Use proof.amount.toNumber() for updated cashu-ts Amount type
- Add ownerNpub?: string to ClientEntry and DaemonClient interfaces
- Update getClientsFromStore() to preserve ownerNpub field
- GET /clients: include ownerNpub in daemon response
- POST /clients/add: accept and store ownerNpub from auth proxy
- Remove getNpubSuffix/addSuffixToId/removeSuffixFromId from clients.ts (moved to auth proxy)
- Remove CLI-side suffixing in addDaemonClient and deleteClientAction
- Remove config loading from getClientsList (no longer needed for filtering)
- CLI output now shows raw client IDs without suffix stripping
When multiple routstrd processes (or CLI auto-starts) bootstrap simultaneously,
each process found cocod/routstrd unreachable, spawned its own copy, and
contended for the same database socket. The paired ~200ms burst timestamps
in the logs were the symptom.
Fixes applied:
- Add cross-process startup lock (process-lock.ts)
- Uses atomic mkdir as the lock primitive.
- Stores PID + UUID token; verifies ownership on release to avoid
removing a lock taken by a new process after the original crashed.
- Removes stale locks (unreachable PID or lock older than staleAfterMs).
- Exports withCrossProcessLock() for simple RAII-style usage.
- Fix cocod startup race (cocod-client.ts)
- Wrap ensureDaemonRunning() in a socket-derived lock path.
- Re-ping inside the lock before spawning; if another process just started
cocod, the current process detects it and connects instead of spawning.
- Switch from 'cocod daemon' to 'cocod init' for cleaner daemonization.
- Allow exit code 0 from 'cocod init' (normal daemon exit without error).
- Consolidate routstrd daemon startup (daemon-client.ts)
- startDaemonProcess() now delegates to startDaemon() from start-daemon.ts,
sharing the same lock and health-check logic.
- Add cross-process lock to routstrd daemon startup (start-daemon.ts)
- Performs pre-lock isDaemonHealthy() check to skip lock entirely when
already running.
- Performs re-check inside the lock before spawning to avoid duplicate
daemon processes.
- Add addSuffixToId() and removeSuffixFromId() helper functions
- Only suffix client IDs, never names
- Update getClientsList, deleteClientAction, and addClientAction to use helpers
Replace ensureDaemonClient (try-create → catch 'already exists' → fetch list) with
a single addDaemonClient that fetches the list first, checks if client exists,
then creates only if needed. Returns { client, created } in all cases.
- Moved addDaemonClient and ensureDaemonClient from daemon-client.ts to clients.ts
- Moved clients list/delete/add action handlers from cli.ts to clients.ts
- Updated cli.ts to call the new action functions from clients.ts
- Updated integrations/index.ts to import ensureDaemonClient from clients.ts
- Fixed circular dependency by having clients.ts import from ../integrations/registry instead of ../integrations
Replace direct store.getState().clientIds access with a shared utility:
- getClientsFromStore(store) for daemon/local usage
- getClientsList() for remote CLI usage via daemon API
This ensures integration refresh logic and client management work
consistently whether running inside the daemon or connecting to a
remote daemon where store access is unavailable.
Updated:
- src/daemon/index.ts: use getClientsFromStore for integration refresh
- src/daemon/http/index.ts: use getClientsFromStore in /clients endpoints
- src/utils/daemon-client.ts: use getClientsList in ensureDaemonClient
- src/cli.ts: use getClientsList in clients list command
- Move get-or-create client logic from integrations/index.ts into
ensureDaemonClient() in daemon-client.ts as a generic utility.
- Update setupIntegration() and the CLI clients add --<integration>
paths to use the new helper.
- Return { client, created } so callers can decide whether to log.
- Remove IntegrationClient alias and ensureIntegrationClient export.
- Add shared DaemonClient type and addDaemonClient() helper in
daemon-client.ts as the single source for POST /clients/add.
- Update ensureIntegrationClient() to use the new helper.
- Update CLI clients add command to use the new helper.
- Keeps existing error handling and 'already exists' fallback in
ensureIntegrationClient().
- clients add: appends _<npub_suffix> to name when daemonUrl+nsec are set
- clients list: filters to only clients matching our npub suffix, strips suffix for display
- clients delete: auto-appends suffix to id before sending if not already present
- adds getNpubSuffix() helper in daemon-client.ts
- Add normalizeNostrPubkey, npubFromPubkey, npubFromSecretKey to nip98.ts
- Extend callDaemon to accept DELETE method
- Add subcommands:
- list: GET /npubs
- add <npub>: POST /npubs (accepts hex pubkey or npub1...)
- delete <npub>: DELETE /npubs/:npub (accepts hex pubkey or npub1...)
- All npubs commands go through callDaemon, so they work with
remote daemons and are automatically NIP-98 signed
- Export getDaemonBaseUrl from daemon-client for shared URL resolution
- Replace all hardcoded http://localhost: in client integrations
(opencode, claudecode, openclaw, pi) with getDaemonBaseUrl(config)
- Replace raw fetch(http://localhost:/models) with callDaemon("/models")
so model fetching works with remote daemons and gets NIP-98 signed
- Fix clients add command output to print the actual daemon base URL
instead of hardcoded localhost
- Add nostr-tools dependency for NIP-98 event signing
- Add nsec field to RoutstrdConfig for remote daemon authentication
- Extract NIP-98 auth utilities into src/utils/nip98.ts (parseSecretKey,
createNIP98Authorization with SHA-256 payload tags)
- Wire NIP-98 Authorization headers into callDaemon and isDaemonRunning
whenever both daemonUrl and nsec are configured
- Refactor refund command to use callDaemon instead of hardcoded localhost
so it works correctly with remote daemon URLs
- Add optional daemonUrl field to RoutstrdConfig
- Update callDaemon and isDaemonRunning to use configurable base URL
- Disable auto-start when daemonUrl is set (Option A)
- Gate local-only commands: onboard, start, restart, mode, logs, service install
- Moved daemon communication helpers (loadConfig, callDaemon,
isDaemonRunning, startDaemonProcess, ensureDaemonRunning,
handleDaemonCommand) from cli-shared.ts to src/utils/daemon-client.ts
- Moved the refund command from cli-shared.ts into cli.ts with the
rest of the CLI commands
- cli.ts now imports program directly from commander and daemon
helpers from utils/daemon-client
- TUI imports are updated to use utils/daemon-client
- Removed src/cli-shared.ts
- Add mode config option (xcashu, lazyrefund, apikeys) to RoutstrdConfig
- Pass mode parameter to routeRequests in daemon HTTP handler
- Add 'routstrd mode' CLI command to set mode interactively
- Mode command shows current mode, prompts for selection, updates config,
and restarts the daemon