mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-30 07:37:39 +00:00
llms.txt mixed both roles in prose: two phases had "(operator)" in the heading, the rest said nothing, and Phase 4 interleaved both. Agents had to re-derive where their authority stopped. - Replace the fuzzy "ask for approval of …" boundaries with a closed list of six operator items (O1–O6) and one rule: anything not on the list is an agent step, done without asking. - Mark every phase with Agent/Operator blocks and a numbered gate (G0–G3) that names the operator artifact required to continue. - Phase 0 opens by asking O1 only: the domain/subdomain and the payout address. - Phase 2 hands the operator a literal two-item backup list (NSEC, master key) instead of a file list, and drops .env/keys.db/.wallet. - Remove the Cashu funding gate: a node needs no funding. The O6 paid test is a consumer credential, not node funding. - State two things that were implicit or contradictory: naming a domain approves first-boot Nostr publication, and the CLI token in ~/.routstr/config.json is a credential the agent may use but must not read.
239 lines
10 KiB
Plaintext
239 lines
10 KiB
Plaintext
# Routstr provider node — instructions for an operator's agent
|
|
|
|
Goal: help an operator set up a Routstr node without exposing the bootstrap admin
|
|
password or assuming that a running API is a working provider. This file is a
|
|
standalone entry point for agents without an existing checkout. The commands,
|
|
Compose file and detailed human steps live in the checked-out repository; read
|
|
`docs/provider/quickstart.md` and `docs/provider/deployment.md` after cloning.
|
|
|
|
## Who does what
|
|
|
|
The operator does exactly the items below. Everything else is the agent's job —
|
|
including DNS records, TLS, the reverse proxy, installing Docker, cloning,
|
|
checkout, `.env`, port selection, starting Compose, generating keys, dashboard
|
|
configuration that takes no secret, `check`, verification and every report. Do
|
|
not ask the operator to confirm or approve an agent step; do it and report it.
|
|
|
|
Each phase marks its operator items and ends at a gate. At a gate, stop and wait
|
|
for the operator's artifact. Report the blocker, not a fabricated success.
|
|
|
|
### Operator items (closed list)
|
|
|
|
- **O1 — Answer once, up front.** The domain or subdomain to serve
|
|
(`https://node.example`), or "private, no domain yet"; and the payout Lightning
|
|
address (`user@wallet.example`, `lnurl1…`, or an `https://` LNURL-pay endpoint).
|
|
Nothing else is needed to start. Naming a domain also approves publishing the
|
|
Nostr listing on first boot.
|
|
- **O2 — Privileged access.** When an agent step needs host root (installing
|
|
Docker, DNS, TLS, the reverse proxy, the firewall), ask then; the operator
|
|
enters it or grants it.
|
|
- **O3 — Provider secrets.** The operator picks and pays for the upstream
|
|
provider, pastes its API key into Providers themselves, and decides which model
|
|
to enable and its pricing. The agent guides and verifies; it never sees the key.
|
|
- **O4 — Bootstrap password.** Read the one-time admin password from the node logs
|
|
in the operator's own terminal and rotate it in Settings → Admin Settings. The
|
|
agent supplies the exact command and URL and never reads that log line.
|
|
- **O5 — Key backup.** Back up the master key and the NSEC off-host and test a
|
|
restore (Phase 2).
|
|
- **O6 — Paid acceptance.** With model, mint, prompt and limit approved, run small
|
|
paid non-streaming and streaming tests with the operator's own funded client
|
|
credential — a consumer credential, not node funding and not an admin token.
|
|
|
|
### Agent must never
|
|
|
|
- Put secrets in chat, shell arguments, Git, captured logs or generated reports,
|
|
and never ask the operator for one — the admin password, provider API keys,
|
|
Cashu credentials, CLI tokens and the `nsec`. A credential the agent may *use*
|
|
but not *read* is `~/.routstr/config.json` (0600), which authorises the Routstr
|
|
CLI.
|
|
- Run bare `docker compose up`: stock `compose.yml` publishes port 8000 on all
|
|
interfaces and starts Tor. Every Compose operation here uses
|
|
`-f compose.node.yml`.
|
|
- Run the log command to read the bootstrap password, or paste raw Docker logs or
|
|
admin responses into a report.
|
|
- Report a gate as passed without the operator's artifact.
|
|
|
|
## Default flow (public mode)
|
|
|
|
The default assumes the operator names a subdomain with HTTPS and a reverse proxy
|
|
forwarding to `127.0.0.1:8000`. The node then publishes its Nostr listing on first
|
|
boot, which O1 approved. If there is no public URL yet, use private mode and
|
|
publish later.
|
|
|
|
### Phase 0 — Host
|
|
|
|
**Agent**
|
|
|
|
- Open by asking O1, before touching the host:
|
|
- Which domain or subdomain will serve this node (`https://node.example`), or do
|
|
they want a loopback-only node with no public URL yet? A named domain still
|
|
needs DNS, TLS and a reverse proxy forwarding to `127.0.0.1:8000`.
|
|
- Which payout Lightning address should receive settled earnings? It is passed to
|
|
`start` as `--ln-address` and saved in `.env`; get it now so Phase 1 needs no
|
|
rerun.
|
|
- Confirm Docker with the Compose plugin, Python 3, available disk and durable
|
|
storage; install what is missing (O2 if root is needed).
|
|
- Clone `https://github.com/Routstr/routstr-core.git` if there is no checkout,
|
|
check out the tag of the latest release, and inspect the quickstart and
|
|
deployment guide before running commands. Only if the operator asks to test the
|
|
current development branch, use that branch and explicitly label it as
|
|
unreviewed. Reuse an existing checkout and state; do not overwrite `.env` or
|
|
recreate a wallet.
|
|
- For public mode, create the DNS record, TLS certificate and reverse proxy entry
|
|
for the O1 domain (O2 if root is needed), and verify **before** starting that
|
|
they forward that domain to `127.0.0.1:8000`. If any of that is missing, prefer
|
|
private mode. The dashboard is protected by the node's own admin password
|
|
(scrypt-hashed); restricting `/admin` at the proxy (IP allowlist or basic auth)
|
|
is an optional extra layer, not a setup requirement. The operator still reads
|
|
the one-time bootstrap password and rotates it (O4).
|
|
|
|
**Operator**
|
|
|
|
- O1: the domain (or "private") and the payout address.
|
|
- O2: root, only if the agent asks.
|
|
|
|
Gate G0 — Phases 0 and 1 need nothing beyond O1, plus O2 where root is required.
|
|
|
|
### Phase 1 — Start
|
|
|
|
**Agent**
|
|
|
|
- Check that the loopback port is free, then run (public):
|
|
|
|
```bash
|
|
python3 scripts/node_setup.py start --public-url https://node.example --ln-address user@wallet.example
|
|
```
|
|
|
|
or private (nothing published):
|
|
|
|
```bash
|
|
python3 scripts/node_setup.py start --private
|
|
```
|
|
|
|
- If another service owns port 8000, keep it untouched and pass `--port 18080`; use
|
|
that port for `check`, the dashboard URL, the reverse proxy and later
|
|
`ROUTSTR_NODE_PORT=18080 docker compose -f compose.node.yml …` commands.
|
|
- Report ref/version, port, reachability and model count.
|
|
|
|
`start` prepares `.env` (never clobbering existing values), starts
|
|
`compose.node.yml` (loopback only, no Tor, analytics off on first boot), waits for the local
|
|
`/v1/info` and `/v1/models`, generates the master key and Nostr identity, and — in
|
|
public mode — publishes the listing (approved by O1). It also mints a long-lived
|
|
CLI token and writes it to `~/.routstr/config.json` (0600) for the Routstr CLI;
|
|
skip that with `--no-cli-token`. If that file already points at another node,
|
|
`start` leaves it alone and creates no token (see the Routstr CLI section below).
|
|
A running API with zero models is **not configured**.
|
|
|
|
### Phase 2 — Back up
|
|
|
|
**Agent**
|
|
|
|
- Hand the operator the numbered list below, with the reveal command.
|
|
- Afterwards verify that both artifacts exist at the backup target (name, size,
|
|
hash). Never read their contents.
|
|
|
|
**Operator** (O5)
|
|
|
|
The master key and identity exist as soon as the node boots. Back up exactly two
|
|
things, off-host and encrypted:
|
|
|
|
1. The NSEC — the node's Nostr identity key. In your own terminal:
|
|
|
|
```bash
|
|
docker compose -f compose.node.yml exec routstr /.venv/bin/python scripts/reveal_nsec.py
|
|
```
|
|
|
|
2. `routstr_secret.key` — the master key file.
|
|
|
|
Test a restore once, somewhere isolated. Keep the two together: the master key
|
|
decrypts the `nsec` stored in `keys.db`. Do not ingest the revealed `nsec` or any
|
|
logs into the agent context.
|
|
|
|
Gate G1 — "both items stored and restore tested."
|
|
|
|
### Phase 3 — Dashboard
|
|
|
|
**Agent**
|
|
|
|
- Hand the operator the dashboard address (`https://node.example/admin` in public
|
|
mode, or `http://127.0.0.1:8000/admin` over an SSH tunnel).
|
|
- After the operator acts, verify model routing, pricing, payout address, mints and
|
|
relays through the admin API, without printing provider records or secrets.
|
|
|
|
**Operator**
|
|
|
|
- O4: read the generated password privately from
|
|
`docker compose -f compose.node.yml logs routstr`, log in, and immediately rotate
|
|
it in Settings → Admin Settings.
|
|
- O3: add the upstream in Providers with your own API key, enable at least one
|
|
routable model, and review pricing, payout address, mints and relays.
|
|
|
|
Gate G2 — "password rotated, upstream added, at least one model enabled."
|
|
|
|
### Phase 4 — Verify and accept
|
|
|
|
**Agent**
|
|
|
|
- Run:
|
|
|
|
```bash
|
|
python3 scripts/node_setup.py check --public-url https://node.example
|
|
```
|
|
|
|
Require exit code 0 and a nonzero public model count (exit code 2 means the API is
|
|
running but not configured). If it fails, diagnose without printing provider
|
|
records or secrets. This proves discovery only — it is **not** a paid-inference
|
|
test.
|
|
- Verify completion, accounting, upstream serving, refund status and payout
|
|
separately through the admin API.
|
|
|
|
**Operator**
|
|
|
|
- O6: with model, mint, prompt and limit approved, run small paid non-streaming and
|
|
streaming inference tests with your own funded client credential (not an admin
|
|
token).
|
|
|
|
Gate G3 — paid-inference verification, or an explicit "discovery only" outcome.
|
|
|
|
### Handoff
|
|
|
|
Report ref/version, private/public reachability, whether password rotation was
|
|
confirmed (never the password), model count, backup/restore status, paid-inference
|
|
verification status and which gates passed. "Local API ready" and "production
|
|
accepted" are separate outcomes. Never paste raw Docker logs or admin responses.
|
|
|
|
## Routstr CLI
|
|
|
|
The setup writes a long-lived admin token to `~/.routstr/config.json` (0600), so
|
|
the [Routstr CLI](https://github.com/routstr/routstr-cli) is ready to operate the
|
|
node. The agent may use that token but must never print or read it. If that file
|
|
already pointed at another node, `start` left it alone and minted no token. The
|
|
CLI only reads `$HOME/.routstr/config.json`, so re-run `start` with
|
|
`--cli-config /some/dir/.routstr/config.json` and run the CLI as
|
|
`HOME=/some/dir routstr …`:
|
|
|
|
```bash
|
|
git clone https://github.com/routstr/routstr-cli.git
|
|
cd routstr-cli && bun install
|
|
|
|
routstr instruct # canonical agent guide for this node
|
|
routstr schema # full command tree as JSON
|
|
routstr status
|
|
routstr providers list
|
|
routstr providers add openrouter --api-key <key> --base-url https://openrouter.ai/api/v1
|
|
```
|
|
|
|
The token is **full node admin**. Give each agent its own named, expiring token
|
|
and revoke it when done (**Settings → CLI Tokens**, or
|
|
`DELETE /admin/api/cli-tokens/{id}`). On a different machine, configure the CLI
|
|
with `routstr init --node-url <url> --token <token>`. Mint another token with
|
|
`docker compose -f compose.node.yml exec routstr /.venv/bin/python scripts/create_cli_token.py --name agent`.
|
|
Never paste the token, or the generated admin password, into a chat.
|
|
|
|
## Private mode (no public URL yet)
|
|
|
|
Everything above holds, except `HTTP_URL` is unset: the node is loopback-only and
|
|
publishes nothing. Administer it over an SSH tunnel, and promote it later by
|
|
setting the public **HTTP URL** in the dashboard (the listing is then published
|
|
automatically). Publication is an operator decision: ask before promoting.
|