Appearance
Connect & SSH
Once you're paired, two commands open sessions: connect gives you a local TCP port that pipes to a service behind your gateway, and ssh is the same thing with the system ssh exec'd against that port for you.
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 ssh alias file
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 stable 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.
Flags and where defaults come from
Both commands share the same flag set:
| 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 <url|id> | relay WebSocket URL, or a relay id/host from the matrix; 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 = default) |
--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 gateway×relay matrix — passing just one infers the other from that entry's configured default, 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.
--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.
LAN-first dialing
If your default relay has published LAN origins (a relay on the same network as you — see Relays), connect and ssh try those origins first with a short ~2-second budget per hop, verifying the relay's pinned certificate fingerprint, before falling back to the stored internet-facing URL. On your home or office network this keeps traffic local and fast; away from it, the LAN attempts fail quickly and the normal relay path takes over. No flags needed — it's driven entirely by the relay entries in config.json.