Files

4.0 KiB

crossbar on hyperborea

crossbar runs on hyperborea (Raspberry Pi, Debian 13, aarch64) as a systemd --user unit, bound to its tailnet address only. Clients on the tailnet reach it at

http://hyperborea.scylla-hammerhead.ts.net:7777/<route>/v1

Why hyperborea: it is always on, wired on titan's LAN segment (192.168.88.154, which wake-on-LAN needs — magic packets are L2 broadcast), and not itself an inference host, so a router rebuild or a sleeping titan never takes crossbar down with it.

Files

file purpose
crossbar.toml the production config: hosts titan/straylight/dixie with their configured models and parallel, the routes
crossbar.service the user unit (/srv/crossbar, Restart=always)
install.sh cross-compiles for arm64 on the machine you run it from, copies binary + config + unit, restarts, prints the hosts view

On hyperborea: binary, config and SQLite database live in /srv/crossbar/; the unit is ~/.config/systemd/user/crossbar.service (loginctl linger is on, so it survives logout).

Install / upgrade

deploy/hyperborea/install.sh          # from any checkout on a host with Go 1.26 and ssh to hyperborea

Re-running upgrades in place (binary is replaced atomically, the unit restarted; leases persist in the database). Config-only changes: edit crossbar.toml, re-run.

Verify

curl -s http://hyperborea.scylla-hammerhead.ts.net:7777/_crossbar/hosts | jq .
curl -s http://hyperborea.scylla-hammerhead.ts.net:7777/_crossbar/routes | jq .
curl -s 'http://hyperborea.scylla-hammerhead.ts.net:7777/_crossbar/usage?by=route'
ssh hyperborea journalctl --user -u crossbar -f

A cheap end-to-end check uses the probe route (dixie's 9B first):

curl -s -D - -X POST -H 'Content-Type: application/json' \
  -d '{"model":"ornith-1.5-9b-uncensored","max_tokens":8,"messages":[{"role":"user","content":"Reply with pong."}]}' \
  http://hyperborea.scylla-hammerhead.ts.net:7777/probe/v1/chat/completions

The response carries X-Crossbar-Host (which router served it) and X-Crossbar-Lease (new or reused).

Pointing clients at it

OpenCode (project-local opencode.json, or the global one with a per-project route):

"provider": { "crossbar": { "npm": "@ai-sdk/openai-compatible",
  "options": { "baseURL": "http://hyperborea.scylla-hammerhead.ts.net:7777/opencode-a/v1" },
  "models": { "ornith-1.5-35b-a3b": {} } } }

Hermes (custom_providers[].base_url, and the same in delegation/auxiliary blocks):

base_url: http://hyperborea.scylla-hammerhead.ts.net:7777/hermes-straylight/v1

Routes must exist in crossbar.toml; an unknown first path segment is 404 unknown route. Known gap: PLAN.md's one-route-per-instance launcher (CROSSBAR_ROUTE="$(basename "$PWD")-$$") needs a route template (e.g. [routes."opencode-*"]) that the code does not have yet; until then add each instance's route explicitly.

Wake-on-LAN for titan

The [hosts.titan.wake] block is present but commented out until the MAC is settled. Titan is on Wi-Fi (active private address 5e:fc:f2:3f:23:6b, hardware 60:3e:5f:33:6f:b8) with its dock's three Ethernet ports (d2:30:99:9a:ee:03/04/05) unplugged. Wired + womp 1 is the reliable path; magic-packet wake over Wi-Fi on Apple Silicon is not guaranteed and the private address may rotate. Broadcast address is 192.168.88.255:9.

Security notes

  • The bind is the tailnet address; only tailnet members can reach it. identity = "tailscale" with per-route peers is available when a route should be limited to named nodes; tailscale whois already works unprivileged on hyperborea.
  • Plain HTTP over the tailnet is WireGuard-encrypted on the wire. Hermes agents' terminal calls to this URL may trip tirith's plain_http_to_sink; prefer the MagicDNS name (never the raw IP) and add a rule-scoped trust entry rather than --broad if a prompt recurs. Provider traffic from the OpenAI client library is not scanned by tirith.
  • Bodies are never logged or stored; the database holds leases and per-request accounting only.