Standalone auth proxy for routstrd. Sits in front of the daemon, validates Authorization: Bearer sk-... tokens, and forwards requests.
Keeps auth as a separate, replaceable layer. The daemon itself runs unauthenticated on localhost only; the proxy is the public-facing gatekeeper.
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Clients │ ───► │ routstrd-auth │ ───► │ routstrd │
│ (CLI, Pi, │ │ :8008 (public) │ │ :8009 (local)│
│ OpenCode) │ │ Bearer token │ │ No auth │
└─────────────┘ └──────────────────┘ └──────────────┘
All values have sensible defaults and can be overridden via environment variables or CLI flags.
| Variable | Default | Description |
|---|---|---|
ROUTSTRD_AUTH_PORT |
8008 |
Public port |
ROUTSTRD_AUTH_HOST |
0.0.0.0 |
Bind host |
ROUTSTRD_UPSTREAM |
http://localhost:8009 |
Upstream routstrd URL |
ROUTSTRD_DB_PATH |
~/.routstrd/routstr.db |
Shared SQLite DB |
ROUTSTRD_DIR |
~/.routstrd |
Base config directory |
COCOD_DIR |
~/.cocod |
Wallet data directory |
Cloudron runs the routstrd server. Team members connect to it as clients using the routstrd CLI.
# Install the CLI client globally
bun i -g routstrd
# Connect to your Cloudron instance
routstrd remote <cloudron-url>
# Register yourself as the first admin (first npub gets admin by default)
routstrd npubs registerThat's it — you now have full access to routstrd on Cloudron. Integrate it with your coding agents:
routstrd clients add --pi-agent # for Pi Agent
routstrd clients add --claude-code # for Claude Code
# ...and moreAnyone joining an existing routstrd Cloudron instance does the same initial steps:
bun i -g routstrd— install the CLIroutstrd remote <cloudron-url>— connect to the instance (this generates a unique npub for the new person)- Give that npub to someone who already has access
- That person runs:
routstrd npubs add <npub>— grants access - The new member can now add clients (e.g.
routstrd clients add --claude-code) and start using routstrd
Every team member gets their own npub, and every client they register gets a unique ID — making it easy to track team usage from the Cloudron terminal.
# Individual: see your own usage
routstrd top
# Anyone with access to the Cloudron terminal: see everything
# The clients tab shows individual usage, with the last 7 chars of each npub
# appended to client IDs for easy identification
routstrd topThis project is intended to be run with plain Docker. Docker Compose is not required.
Build the image:
docker build -t routstrd-pro-image .Run the container with a persistent bind mount:
docker run -d \
--name routstrd-pro \
--restart unless-stopped \
-p 18008:8008 \
-v $HOME/routstrd-data:/app/data \
routstrd-pro-imageImportant: The bind mount
$HOME/routstrd-data:/app/datais crucial for persisting data across container restarts and updates. Without this volume mount, all data will be lost when the container is removed.
The service is now available on the host at:
http://localhost:18008Check the health endpoint:
curl http://localhost:18008/healthFollow logs:
docker logs -f routstrd-proStop and remove the container:
docker stop routstrd-pro
docker rm routstrd-proRebuild and restart after changes:
docker stop routstrd-pro
docker rm routstrd-pro
docker build -t routstrd-pro-image .
docker run -d \
--name routstrd-pro \
--restart unless-stopped \
-p 18008:8008 \
-v $HOME/routstrd-data:/app/data \
routstrd-pro-imageNote:
EXPOSE 8008in theDockerfileonly documents the container port. Host port publishing, restart policy, container name, and volume mounting are configured withdocker runflags.
# Install dependencies
bun install
# Validate config & DB connectivity
bun run src/index.ts validate
# Start the proxy
bun run src/index.ts start
# Start on a different port
bun run src/index.ts start -p 8080
# Point at a custom DB
bun run src/index.ts start -d /path/to/routstr.dbsrc/nip98-client.ts signs each request with a Nostr private key and can add a client through /clients/add, then fetch /clients and print the current list.
The private key must belong to one of the configured admin pubkeys (ROUTSTRD_AUTH_ADMIN_NPUBS, ROUTSTRD_AUTH_ADMIN_PUBKEYS, or an admin added through POST /npubs) because /clients/add is admin-only.
# Add a client, then list all clients
bun run client -- \
--url http://localhost:8008 \
--key nsec1... \
--name "My Laptop"
# Or use env vars
export ROUTSTRD_AUTH_URL=http://localhost:8008
export NOSTR_NSEC=nsec1...
bun run client -- --name "My Laptop"
# Just list clients
bun run client -- --jsonThe helper creates Authorization: Nostr <base64-event> headers whose signed event binds the exact URL, HTTP method, and POST body hash, matching the validation in src/nip98.ts.
- Start routstrd on a local-only port (e.g.
8009). - Start
routstrd-authon the public port (8008). - Clients send
Authorization: Bearer sk-...to:8008as normal.
- Public endpoints (health, models, balance, etc.) — forwarded immediately, no token needed.
- Protected endpoints — require a valid
Bearertoken that exists in the shared DB. - Admin npubs — stored in the shared
routstr.dbtableroutstr_auth_admins. Env-configured admins are bootstrapped into that table at startup. - List admins —
GET /npubsreturns{ "npubs": [...] }with the configured admin npubs. - Add admins —
POST /npubswith{ "npub": "npub1..." }or{ "pubkey": "<64-char hex>" }. If no admins are configured yet, this first add is unauthenticated; after that it requires NIP-98 auth from an existing admin. - Remove admins —
DELETE /npubs/<npub-or-pubkey>(orDELETE /npubs?npub=...) requires NIP-98 auth from an existing admin. - Forwarded headers — the proxy strips
Authorizationand injectsx-routstr-client-idso the daemon knows which client made the request.
MIT