Appearance
Connect & SSH
Once you're paired, three commands open sessions: connect gives you a local TCP port that pipes to a service behind your gateway, ssh is the same thing with the system ssh exec'd against that port for you, and open is the config-driven form — it resolves a named host from an alias file and forwards to it, for any service.
burrowee connect
sh
burrowee connect --svc <name> [--local <addr>]connect starts a local listener, opens a carrier to the relay, completes the per-service end-to-end handshake with the gateway, and pipes every connection to the local port through that encrypted stream. The relay only ever sees sealed bytes.
--svc is the only required flag — it names the target on the gateway side, e.g. "22" for SSH or "8080" for a web service. By default the listener binds 127.0.0.1:0, i.e. an ephemeral loopback port; connect prints what it picked:
listening on 127.0.0.1:54321 → relay wss://relay.example.com gw=1a2b3c4d5e6f7a8b svc=8080Pass --local to pin the port instead:
sh
burrowee connect --svc 8080 --local 127.0.0.1:8080Then point any TCP client at it — curl http://127.0.0.1:8080/, a browser, a database client. connect runs until you Ctrl-C it.
burrowee ssh
sh
burrowee ssh --svc 22 [ssh args…]ssh = connect on an ephemeral loopback port, then exec the system ssh against it. Anything after the Burrowee flags is appended to the underlying ssh invocation, which is always ssh -p <port> localhost [your args…] — so the host is always localhost, and you pick the remote user with ssh's own -l flag rather than user@host:
sh
burrowee ssh --svc 22 -l aliceYour ssh config, keys, and agent all apply as usual; Burrowee only supplies the transport underneath.
Named hosts — the alias files
Rather than pass --svc/--gw/--relay every time, you can define a host once in ~/.burrowee/cli/ssh_config (an OpenSSH-format file) and connect it by name:
sh
burrowee ssh myhost # resolve the alias, forward, then exec ssh
burrowee ssh myhost@relay2 # same, but force this alias onto a specific relay
burrowee ssh myhost -l alice # extra ssh args pass through as usual
burrowee ssh list # print the configured aliases as a tableA Host <alias> block carries the Burrowee routing in #@ comments; every other keyword (User, IdentityFile, IdentitiesOnly, …) is handed to the system ssh unchanged (via -F):
Host myhost
#@gateway home # gateway name or id (default: the Host alias itself)
#@service 22 # target on the gateway (default: ssh)
#@relay seoul # relay id or host; repeatable, first = highest priority
User alice
IdentityFile ~/.ssh/id_ed25519#@relay may be repeated to express a priority order; a trailing @<relay> on the command line (myhost@relay2) overrides it for one invocation. Everything else works exactly like the flag form — an ephemeral loopback forward under the hood, then ssh exec'd against it, keyed in known_hosts by the persistent alias rather than the ephemeral port. A token with no matching Host block is treated as a bare gateway name/id, so burrowee ssh home reaches the home gateway's ssh service even with no alias file at all.
One rule worth knowing: every token after the host is handed to the system ssh verbatim — including flags this CLI would otherwise read as its own, and including --help. Only a help spelling in first position (burrowee ssh --help) is read as a help request.
burrowee open — any service by name
ssh is really a special case of a general pattern, and open is that pattern for every other service:
sh
burrowee open --svc vnc myhost # resolve myhost from ~/.burrowee/cli/vnc_config
burrowee open --svc vnc myhost@relay2 # same, forced onto a specific relay--svc <name> is required and does double duty: it names the target service and selects the alias file — open --svc <s> reads ~/.burrowee/cli/<s>_config, the same OpenSSH format with the same #@gateway/#@service/#@relay directives as ssh_config (here #@service defaults to the --svc value). open starts the local forward and prints the address it's listening on, but execs no client of its own — you point your VNC viewer, browser, or database client at the printed port. As with ssh, the @<relay> suffix and explicit --relay/--gw flags override the alias block, and a token with no matching Host block is read as a bare gateway id or name. The other flags are the familiar subset: --local, --gw, --relay, --config.
The CLI ships example shell functions built on open/ssh and the <svc>_config aliases — shell/burrowee.bash, .zsh, and .fish in the release — meant to be copied and adapted (e.g. a vnc myhost function in your shell rc).
Flags and where defaults come from
connect and ssh share the same flag set (open takes the subset noted above):
| Flag | Meaning |
|---|---|
--svc <name> | service name, e.g. "22" (required) |
--local <addr> | local listen address (default 127.0.0.1:0; connect only — ssh always uses an ephemeral port) |
--relay <id|url|pick> | relay id, host, name, or full origin URL from the matrix — or a pick token wss://<lan-address>@<short-id> naming one LAN address on one relay; default from config |
--relay-quic <addr> | relay QUIC address (host:port); default from config, empty disables QUIC |
--transport <mode> | transport mode: auto|ws|quic (default auto; ws forces WSS-only, no QUIC) |
--gw <id|name> | gateway id or friendly name (matrix gateway); default from config |
--isolated | request isolated transport — a dedicated connection per local connection, instead of multiplexing over the shared carrier. The gateway-advertised mode is authoritative: a multiplex-only service falls back regardless of this flag |
--warm <n> | pre-dialed dedicated-connection depth to keep warm toward the entry edge for an isolated service (0 disables pre-warming) |
--handshake <dur> | max time for the initial cold connection setup (carrier dial + per-service handshake) before giving up (default 30s; 0 = no limit) |
--gw-pub <file> | gateway ed25519 public key (hex file); default from config |
--psk <file> | pairing PSK file; default from config |
--config <path> | config.json path (default ~/.burrowee/cli/config.json) |
Precedence is simple: an explicit flag always wins, individually; anything you leave off is filled from the bootstrap config (--config just changes where that file is read from). --gw and --relay together resolve one (gateway, relay) pair out of the matrix — passing just one infers the other, or errors if it's ambiguous. If you pass all of --relay, --gw-pub, and --psk (an ad-hoc target not in your matrix at all), the config file is not read; with no usable config and missing flags you get a pointer back to burrowee bootstrap.
With no --relay, the gateway answers on its own default path: its relays.default while that is still one of its edges, else its system relay. There is no global default relay to set — see Relays.
A pick token goes one step further and names a single address on a single relay:
sh
burrowee connect --relay wss://10.0.0.5:8448@b41c9f22 --svc 22The suffix is the relay's short id (the first eight hex digits of its console relay id), and the relay is named first on purpose: two relays in front of different gateways can publish the same LAN address. burrowee relays list prints every token that resolves. The pick applies to that one connection and is never written to config.json.
--relay also accepts a bridge's entry origin (from burrowee gateways bridges) when paired with an explicit --gw — this is how you connect through a bridged edge rather than a directly-paired relay.
QUIC and WSS
Each connection's carrier prefers QUIC when the relay advertises a QUIC address (from config or --relay-quic) and falls back to WebSocket over TLS (WSS) when QUIC can't get through — strict firewalls and some corporate networks block UDP. You don't have to choose; the selector races QUIC first and falls back automatically. Pass --transport ws to force WSS outright (useful on a network you already know blocks QUIC), or --relay-quic "" to the same effect.
Warm, resilient connections
When the daemon is running, connect and ssh hand your session to its warm carrier pool instead of dialing a fresh one from scratch. That pool keeps a live connection to each relay you use, with idle standbys ready for isolated services, and it watches for carrier death (a relay restart, a dropped network) and for network changes (wifi ↔ cellular, sleep/wake) — on either, it re-bridges your session onto a fresh carrier automatically, without dropping your local socket. This is transparent for a raw byte pipe: the local end never notices. It's most useful for reconnect-tolerant traffic; a stateful target (an interactive ssh shell, a database session) still sees its connection reset even though the local port survives, because the far end of the re-bridged carrier is a fresh relay→gateway→target hop.
If the daemon isn't running, connect/ssh still work — they just dial a plain carrier directly for that one invocation, with none of the pooling or migration above.
Dialing over the LAN
A relay is dialled at one face, and which face is derived from the relay's own addresses rather than configured:
- a relay with a public origin is dialled at that origin;
- a relay the console minted with no public origin — an edge on your own network — has exactly one face, so
connectandsshdial its published LAN addresses with nothing extra typed, trying each with a short ~2-second budget and verifying the relay's pinned certificate fingerprint on every one.
There is no fallback between the two: a face is dialled over its own addresses, and a relay with neither a public origin nor a publishable LAN address is an error naming that relay, not a silent switch. There is also nothing to set — no stored dial preference exists, and the command that used to write one is gone (see No stored dial face).
To use one specific LAN address on a relay that has a public origin too, pass its pick token: --relay wss://10.0.0.5:8448@b41c9f22 dials that address and only that address, pinned with that relay's fingerprint, instead of trying each published address in turn. It applies to that connection alone.