Skip to content

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=8080

Pass --local to pin the port instead:

sh
burrowee connect --svc 8080 --local 127.0.0.1:8080

Then 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 alice

Your 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 table

A 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:

FlagMeaning
--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
--isolatedrequest 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.