bitbang is a single static binary remote-access multitool: open an interactive shell, browse and transfer files, and access web apps on the remote machine's network from any browser, no port forwarding, no configuring, and no account.
| ngrok | Cloudflare Tunnel | Tailscale | tailcat | frp | bitbang |
|
|---|---|---|---|---|---|---|
| Account required | Yes | Yes | Yes | No | No | No |
| Browser access: what's included | BYO web server | BYO web server | BYO web server, install client on browser's machine | files and text | BYO web server | a terminal, a file browser, and web apps on the remote network |
| Browser access: data path | their servers | their servers | P2P (relay fallback) | relay only, no direct | your server | direct P2P |
| Data path, CLI to CLI | their servers | their servers | P2P (relay fallback) | P2P (relay fallback) | your server (P2P optional) | P2P (relay fallback) |
| End-to-end encrypted | Not by default | No | Yes | Yes | No -- your server sees traffic | Yes |
| Self-hostable server (open source) | No | No | No (Headscale is third-party) | Yes | Yes | Yes |
| Setup before first use | Account + authtoken | Account + DNS | Account + login on each device | Run one command | A public-IP server + TOML both ends | Run one command |
tailcat is the closest thing here: no
account, one static binary, WireGuard end to end, and a direct path when hole
punching works. The difference is that it needs its binary on both ends.
bitbang was built the other way round: the far end opens a URL and gets a
terminal, a file browser, and web apps on the remote network -- on a phone if
that is what is to hand.
That is what the two browser rows are for. Everything else here that a browser can open only hands back a web server you were already running -- install ttyd first if you want a terminal -- and hands it back through somebody's servers. tailcat's browser build moves files and text, and by its own README is "relayed over DERP only, with no direct connections".
On the machine you want to reach:
curl -sSfL bitba.ng/install | sh
bitbang serve
serve prints a URL. Open it in any browser and you get a terminal, a file browser, and a proxy to that machine's network -- or connect from another terminal with bitbang connect <url> using the same binary. The connection is end-to-end encrypted and peer-to-peer; the bitba.ng server introduces the two ends, then steps aside.
bitbang is a single static Go binary. It's part of the BitBang project; this whitepaper covers the design in depth.
Reach a service at home
- Mount your home NAS from anywhere (SMB)
- Watch your media library from anywhere (Jellyfin)
- Use your own LLM from anywhere (Ollama, Open WebUI)
- Check your security cameras (Frigate)
- Reach your home automation without exposing it (Home Assistant)
- Print to your home printer (IPP, CUPS)
Get on a machine
- Get a shell on a machine behind NAT
- Get a shell from your phone
- Remote desktop into a Windows machine (RDP)
- Reach a Linux or Mac desktop (VNC)
- SSH to a machine with no open port (OpenSSH)
- Set up a headless Raspberry Pi
Share with someone else
Sharing entails simply giving someone a unique URL or QR code that gives them access. Permissions can be tailored and set to expire in minutes, hours, etc.
- Share files without uploading them anywhere
- Show someone your project
- Give someone access that expires
- Check your agent session from your phone (Claude Code, tmux)
- Fix someone else's router
Development and devices
- Reach a database from your dev machine (Postgres, MySQL)
- Sync devices that cannot find each other (Syncthing)
- Watch a robot from a browser (ROS, Foxglove)
Techniques
Every connection has two ends: a listener (bitbang serve, running on the machine being reached) and a connector (a browser, or the bitbang CLI, on the machine doing the reaching). One listener URL serves both kinds of connector.
bitbang serve # everything: shell + proxy + files + forward
bitbang serve shell # just a terminal
bitbang serve files ~/share # just a directory (-files-upload to allow uploads)
bitbang serve proxy localhost:8080 # just one web app, straight at the URL
bitbang serve proxy a.lan:80,b.lan:80 # ...or several, chosen in the browser
bitbang serve forward 127.0.0.1:22 # just TCP, for `connect -L`
bitbang serve shell files ~/share proxy nas.lan:8096 # any combination
Each prints a QR code, URL and a pairing code. The mode picks what the
listener can do at all: serve shell has no forwarding to grant, and a
forward-only listener never starts a shell, so there is nothing to escalate
to.
One default worth knowing: forwarding and the proxy reach any host:port the
listener can reach, not only the one you had in mind, so a link handed out
for a database also reaches the rest of that network. Naming targets after the
word narrows it -- forward db.internal:5432 reaches that and nothing else.
serve shell starts a new shell. share publishes a tmux session that is
already running:
bitbang share # publish the current tmux session
bitbang share --read-only # publish without a control URL
bitbang share status|stop|rotate
The command returns after publishing, so Ctrl-Z, bitbang share, fg
works for a task already in progress. Hosting requires tmux 3.2+ on Unix or
WSL. Native Windows clients can open the URLs but cannot host a share.
By default, the command prints two bearer URLs:
- The Control URL can type with the same authority as the local keyboard. One controller may connect at a time.
- The View URL is watch-only. Input is dropped before it reaches tmux, and
up to
--max-viewersviewers may connect at once (default 16).
--read-only omits the control credential entirely. Viewer and controller
limits are held for each connection's lifetime, even before it opens a shell.
Shares run until stopped by default; --ttl sets a lifetime (e.g. --ttl 1h).
Share URLs are ephemeral and are never saved to devices.json. share stop,
TTL expiry, or removal of the source session disconnects remote peers without
stopping the source session.
Re-running bitbang share reprints the running share's URLs. If you
pass a flag that disagrees with what is running (say --read-only
against a share that has a control URL), it says so rather than
handing back the old URLs; bitbang share rotate replaces the share
with one that uses the new flags.
A background worker runs in a detached _bbshare_* tmux management session,
so there is no daemon or PID file to manage.
Sharing changes no tmux options. With tmux's default window-size latest, the
window follows the active read-write client; a lone viewer still supplies the
only available size. If window-size has been overridden, share reports it
but does not change the user's configuration.
One listener, one URL, and as many access links as you need. Each is a separate code on that same URL, granting a subset of what the listener offers and optionally lapsing at a fixed time:
bitbang link edit # add entries in $EDITOR
bitbang link ls # what you have handed out
bitbang link rm <label> # revoke one
bitbang link qr <label> # its URL and QR code
An entry is a line of JSON in ~/.bitbang/bitbang/links.json. Write one with no
code, reload the listener at its console, and it mints one:
[
{"label": "ana", "grant": "files", "expires": "2026-09-01T00:00:00Z"},
{"label": "ben", "grant": "files /srv/photos"},
{"label": "dev", "grant": "shell forward 127.0.0.1:5432"}
] 0) owner files forward proxy shell
https://bitba.ng/8ach_I7oQk2vBb9xYzT0Lw#_vtQ0JCPe7s
1) ana files expires in 6d
https://bitba.ng/8ach_I7oQk2vBb9xYzT0Lw#T-Ty_HhvLfY
2) ben files /srv/photos
https://bitba.ng/8ach_I7oQk2vBb9xYzT0Lw#L6La8OzBO74
3) dev forward 127.0.0.1:5432 shell
https://bitba.ng/8ach_I7oQk2vBb9xYzT0Lw#8kmI3LYzB7E
owner is the identity's own code and grants everything the listener serves; send
one of the others instead. The console takes either the label or the number beside
it, so rm 2 and rm ben do the same thing.
A grant is written in the words serve takes, and it can only narrow what the
listener already serves. That means a link is not limited to picking capabilities:
it can name a subdirectory of the shared folder, a subset of the forward targets, or
a single command for shell. Omit grant and the link grants whatever the listener
does. Ask for something outside the listener's reach and the console refuses it with
the same message serve would give you.
The label is what identifies a link, not its terms, so two people can hold links with identical grants and expiry and you can still revoke one without touching the other.
Revocation and expiry reach sessions that are already open: the connection closes and the holder is told why, rather than going quiet. And an expired code is retired rather than paused -- renewing an entry mints a new one, so the URL you already sent stays dead.
When you can't paste a URL or scan a QR code, such as when you're on the phone, or within yelling distance, bitbang serve also prints a short pairing code. The other party opens bitba.ng/<code> (or runs bitbang connect <code>), their screen shows a second 6-digit number, and they read that one back to you. You type it in to approve. A machine-in-the-middle can't make the two numbers match, and pairing saves the device connection credentials for next time, e.g. bitbang connect nas1. If you know Magic Wormhole, the shape is similar -- a spoken code that securely introduces two machines.
Most connections go straight peer-to-peer. When both ends sit behind a NAT that
won't hole-punch, the traffic needs a relay, and by default that's ours. -ice-servers
points the listener at your own instead:
bitbang serve -ice-servers ~/turn.json
The listener hands the config to the signaling server at registration, and the server gives it to whoever connects -- so both ends use your relay and ours is never involved. Any coturn, or a hosted provider like Cloudflare or Twilio, works.
The file is JSON, in whichever of these three shapes your provider handed you:
[{"urls": ["turn:turn.example.net:3478"], "username": "user", "credential": "pass"}]{"ice_servers": [{"urls": "stun:stun.example.net:3478"}]}{"iceServers": [{"urls": ["turn:turn.example.net:3478"], "username": "u", "credential": "p"}]}urls takes a string or a list; username and credential are for TURN and can be
left off a STUN-only entry. The path may be absolute, relative, or ~-rooted. A file
that doesn't parse stops the listener at startup rather than quietly falling back.
If a session ends up relayed without being asked to, bitbang connect says so
rather than leaving you to wonder why it feels slow. The listener logs it
either way (via RELAY), and -relay / -norelay force the question one way
or the other when you are diagnosing a path.
Worth saying: this is about who carries the bytes, not who can read them. A relay only ever sees DTLS ciphertext, ours included. Run your own when you need more TURN than we can provide (we currently limit the time).
Open the URL. Depending on what's served, you get:
- Shell -- a full terminal in the page (colors, resize, copy/paste).
- Files -- browse, preview, download, and upload.
- Proxy -- type a LAN address (
nas.local,192.168.1.10:8080,localhost:3000/admin) and use the app as if you were local. Logins, cookies, uploads, and streaming all work.
bitbang connect <url> # interactive shell
bitbang connect <url> -- tail -f /var/log/syslog # one-shot command
bitbang connect <url> -L 15432:db.internal:5432 # local TCP forwarding
bitbang connect <url> -L 14450:nas.local:445 -L 15900:[fd00::20]:5900
bitbang cp <url>:/var/log/app.log ./app.log # copy files, scp-style
bitbang cp - <url>:/tmp/firmware.bin < firmware.bin # stdin/stdout work too
-L forwards TCP only, like ssh -L. -L binds 127.0.0.1 unless you pass
-g, which makes the forwarded port reachable from your local network -- and
anyone who reaches it gets whatever the tunnel reaches, with no BitBang
credential in front of it.
The listener needs bitbang serve forward or bitbang serve. By default a
forward link reaches any host:port the listener can reach, not only the
one you had in mind, so a link handed out for a database also reaches the rest
of that network. Narrow it by naming what it may reach:
bitbang serve forward db.internal:5432 # this link reaches one service
Every successful connect or pairing is saved to ~/.bitbang/devices.json, so from then on a short name is enough: bitbang connect nas1.
One binary per platform, no runtime dependencies. Everything works everywhere except the two rows called out below.
| Linux | macOS | Windows | |
|---|---|---|---|
Shell, files, proxy (bitbang serve) |
yes | yes | yes |
TCP forwarding (-L) |
yes | yes | yes |
| Access links -- grant, expiry, revocation | yes | yes | yes |
| Bring your own TURN | yes | yes | yes |
| Pairing with a 6-digit code | yes | yes | yes |
| The listener console (Enter) | yes | yes | yes |
bitbang connect, bitbang cp |
yes | yes | yes |
| Viewing a shared session | yes | yes | yes |
Hosting a share (bitbang share) |
yes | yes | no * |
| Terminal resize while connected | yes | yes | no ** |
* bitbang share publishes a tmux session, so hosting one needs tmux --
Linux, macOS, or WSL. Native Windows can still open share URLs with
bitbang connect.
** A Windows connector does not notice its terminal being resized, so the
remote shell keeps the size it started with until you reconnect. Unix
gets this from SIGWINCH, which Windows has no equivalent of.
- Self-certifying identity. On first run,
bitbanggenerates an RSA keypair under~/.bitbang/<program>/; the device UID is derived from the public key, so impersonating a device means finding a second preimage of its UID. - The secret never touches the server. The access code lives in the URL fragment (
#…), which browsers never send --bitba.ngbrokers the connection without ever seeing the credential that authorizes it. - End-to-end encryption. All traffic rides WebRTC's DTLS. The signaling server sees only the public key, the derived UID, and connection metadata -- never your data. A TURN relay, if one is needed, sees ciphertext only.
- Verified pairing. The read-aloud number in code pairing is a short authentication string (SAS), computed independently on both ends from the negotiated DTLS fingerprints and two committed nonces -- a machine-in-the-middle, whose fingerprints necessarily differ, can't make the two numbers match.
- The URL is a bearer credential. Anyone who has it gets whatever you chose to serve -- a shell, if you ran
serve shell. Share it accordingly. - Optional PIN (
--pin) for permanent or headless setups, and throwaway mode (-ephemeral) for a fresh identity each run.
How the two ends authenticate each other without trusting the signaling server is covered in detail here: Trustless Signaling: Authentication Without a Central Authority.
- Nothing to forward or configure. Works from behind NAT, CGNAT, or a locked-down network -- no router changes, no VPN, no tunnel daemon.
- Nothing to install on the connecting side. A browser is enough. A CLI is there when you want scripting, pipes, and file copy.
- Private by design. Traffic is WebRTC/DTLS, peer-to-peer. The signaling server never sees it; if a direct path isn't possible, a TURN relay carries ciphertext only.
- No account, no telemetry.
bitbang is shaped like ssh: serve, connect, and cp map to sshd, ssh, and scp, with WebRTC as the transport instead of TCP. For a machine you can already SSH into comfortably, that difference doesn't buy you much. But most of bitbang came out of annoyances I seem to hit more often than I should:
Reach. Remote SSH access needs an inbound path, and on most networks opening one isn't your call -- CGNAT (cellular, Starlink, many ISPs), corporate, university, municipal. So in practice you bolt on a second system: Tailscale, a VPN, ngrok -- another install, another account, another daemon to keep running. bitbang serve needs no open port and works from anywhere.
Setup. SSH has to be enabled and configured before it will let you in. It's disabled by default on Raspberry Pi OS, and often key-only, which means getting your public key onto the machine first. And how do you do that? Email or a USB stick are usually the most painless options. bitbang sets up the connection with a 6-digit code exchange instead -- something you can do safely over the phone, or call out across the room. It also runs as an ordinary user -- no root, no daemon, no config file.
Proxying. If you want a web app on that machine's network, SSH gives you a separate tunnel per app, named in advance. The bitbang proxy is generic: specify the web app's URL at connection time.
Browser client. SSH needs an SSH client and a key or password on the connecting side. bitbang needs a browser -- which means a phone, a borrowed laptop, or someone who has never opened a terminal. Hand them the URL and they get the access that you've granted them.
curl -sSfL bitba.ng/install | sh
Linux and macOS. Detects your OS and arch (amd64, arm64, and armv7 on Linux), downloads the binary from the latest GitHub release, verifies its SHA-256 against the release's checksums.txt, and installs to ~/.local/bin/bitbang.
Windows builds are published as bitbang-windows-amd64.exe and
bitbang-windows-arm64.exe. Download the appropriate binary from Releases,
rename it to bitbang.exe, and place it on your PATH.
Build from source: see below.
macOS and Gatekeeper. The install one-liner above is unaffected: curl does
not set the com.apple.quarantine attribute, so the binary it fetches runs
normally. If you instead download bitbang-darwin-arm64 from the Releases page
in a browser, macOS quarantines it and refuses to open it, because the release
binaries are not notarized. Clear it with either of:
xattr -d com.apple.quarantine ./bitbang-darwin-arm64
or right-click the file in Finder and choose Open, which offers a one-time override. Alternatively, build from source, which never quarantines.
Windows and SmartScreen. The same thing happens on Windows, for the same
reason. A browser download attaches Mark-of-the-Web, so the first run shows
"Windows protected your PC" -- choose More info, then Run anyway. The
release binaries are not code-signed, so this is expected rather than a sign
anything is wrong. Fetching the .exe with curl or PowerShell's
Invoke-WebRequest does not attach it, and neither does building from source.
Pin a version, change the location, or read the script before running it:
curl -sSfL bitba.ng/install | sh -s -- --version 0.5.0
curl -sSfL bitba.ng/install | sh -s -- --prefix /usr/local/bin
curl -sSfL bitba.ng/install -o install.sh && less install.sh && sh install.sh
Release tags have no v prefix (0.5.0, not v0.5.0).
bitba.ng/install is a redirect, not a hosted script. The chain:
curlhitshttps://bitba.ng/install, which 302s toinstall.shin this repo (onmain).- The script runs in your shell, detects OS+arch, and downloads the binary asset from
https://github.com/richlegrand/bitbang-cli/releases/latest/download/bitbang-linux-<arch>. - It fetches
checksums.txtfrom the same release and verifies the binary's SHA-256. - Installs to
~/.local/bin(overridable).
The install script lives in this repo, next to the code it installs -- so you can review it alongside the binary, and the canonical bitba.ng host owns only the short URL. Self-hosters can point their own host's /install at whatever script they ship: the signaling server's INSTALL_URL env var controls the redirect target (empty → 404).
Flags accept either form (-pin or --pin). Boolean flags default off unless noted.
bitbang serve Everything: shell + proxy + files + forward
bitbang serve WORD [ARG] ... Name what to serve, in any combination:
shell, proxy [TARGET,...],
files [PATH], forward [HOST:PORT,...]
bitbang share [flags] Publish a running tmux session
bitbang share status|stop|rotate Inspect, stop, or replace a share
bitbang connect <target> [-- cmd …] Client shell (interactive or one-shot)
bitbang cp <src> <dst> Copy files (one side is <URL>:/path, or '-')
bitbang version Print version (also --version)
bitbang help Usage (also --help, -h)
Name what to serve. Each word takes the one thing it serves; bare serve
means all four.
bitbang serve everything, files from cwd
bitbang serve shell a terminal, nothing else
bitbang serve files ~/share one directory
bitbang serve proxy nas.lan:8096 one web app, straight at the URL
bitbang serve proxy a.lan:80,b.lan:80 several, chosen in the browser
bitbang serve forward 127.0.0.1:22 TCP for connect -L, one target
bitbang serve shell files ~/share proxy nas.lan:8096 forward db:5432
bitbang serve shell tmux attach a command, which may be several words
| Word | Argument | Without one |
|---|---|---|
shell [COMMAND] |
the command to run, quoted if it is more than one word | $SHELL, or %COMSPEC% on Windows |
files [PATH] |
a directory or file | the working directory |
proxy [TARGET…] |
one target, or a comma list | the browser names its own |
forward [HOST:PORT…] |
one target, or a comma list | any host:port the listener can reach |
One proxy target pins it. With nothing else served, the bare device URL is that app -- no landing page. Alongside other capabilities it becomes an entry in the caret menu that goes straight there. Several targets are offered as a choice, and in both cases the proxy can reach only what was named.
A capability word is never eaten as another word's argument, so
serve files proxy shares the working directory and serves a proxy. A
directory genuinely called proxy needs ./proxy.
A command of more than one word is quoted. Every word takes exactly one
argument, so quoting is what says where a command ends -- nothing has to guess,
and a flag inside the quotes belongs to the command rather than to bitbang:
bitbang serve shell "ssh -p 2222 host"
bitbang serve shell "tmux attach" forward # a command, and forwarding
bitbang serve shell "tmux attach forward" # one command, no forwarding
bitbang serve shell tmux attach # error: "attach" is not something to serve
An argument that itself contains a space is quoted again inside, which is how
a Windows path is spelled: shell "'C:\Program Files\Git\bin\bash.exe' --login".
The rule for the flags below: a word says what is served, a flag says how. A flag whose capability was not named is an error rather than a setting that does nothing.
| Flag | Needs | Default | Description |
|---|---|---|---|
-server HOST |
-- | bitba.ng |
Signaling server hostname |
-pin PIN |
-- | (none) | Require this PIN for connections |
-ephemeral |
-- | off | Temporary identity: a fresh URL each run, and connectors do not save it to devices.json |
-program NAME |
-- | bitbang |
Identity name; keypair stored at ~/.bitbang/<NAME>/identity.pem |
-ice-servers PATH |
-- | (ours) | JSON file of your own STUN/TURN servers; see Bring your own TURN |
-nocode |
-- | off | Disable code-exchange pairing -- no 6-digit code is issued; the URL still works. For headless listeners that can't complete the SAS prompt. |
-v |
-- | off | Verbose logging (adds the browser !debug overlay) |
-shell-max-sessions N |
shell |
10 |
Max concurrent shell sessions (0 = unlimited) |
-disable-shell-mirror |
shell |
off | Stop echoing shell output to the listener's console |
-files-upload |
files |
off | Allow uploads into the shared directory |
-proxy-client-ip |
proxy |
off | Stamp the real browser IP as X-Forwarded-For. Enable only when the backend trusts localhost for auth. |
Targets are matched as written and never resolved: allowing
192.168.1.50:22 does not allow nas.lan:22 even when the name points there.
Resolving would check a name at one moment and dial it a moment later, and the
two can disagree. Given no targets at all, forwarding reaches any host:port the
listener can reach.
(Advanced: -video-fd N passes an inherited socketpair FD to an external
video helper; for internal/embedding use, and hidden from --help.)
| Flag | Default | Description |
|---|---|---|
-read-only |
off | Do not generate a control credential |
-ttl DURATION |
0 (no expiry) |
Lifetime up to 8760h; 0 means until stopped |
-target SESSION |
enclosing session | Session name or $id; required when run outside tmux |
-socket PATH |
enclosing server | tmux socket; needed for a non-default server outside tmux |
-max-viewers N |
16 |
Maximum concurrent view-only peers |
-server HOST |
bitba.ng |
Signaling server hostname |
-v |
off | Verbose logging |
share status, share stop, and share rotate accept the same target and
socket flags. rotate also accepts publication flags and issues fresh URLs.
| Command | What it does |
|---|---|
bitbang link ls |
List this listener's links: grant, expiry, code |
bitbang link edit |
Open links.json in $EDITOR, validated on save |
bitbang link rm LABEL |
Delete a link (reload the listener to close its sessions) |
bitbang link qr LABEL |
Print a link's URL and QR code |
| Entry field | Meaning |
|---|---|
label |
Names the link; identifies it to rm and qr, and must be unique |
grant |
What the link reaches, in the words serve takes: files [DIR], proxy [TARGETS], forward [TARGETS], shell [COMMAND]. Can only narrow what the listener serves. Omit for all of it |
expires |
RFC 3339 timestamp. Omit for a link that does not lapse |
code |
Filled in by the listener on reload. Leave it out to have one minted |
--program NAME picks a listener other than the default, matching serve --program.
<target> may be any of:
- a saved name -- e.g.
nas1; resolved from the known-hosts table (see below) - a 6-digit pair code -- e.g.
482731; runs the pairing flow, then connects - a URL --
https://bitba.ng/<id>#<code>,bitba.ng/<id>#<code>, or bare<id>#<code>
With no -- command, opens an interactive shell (a PTY when stdin is a terminal). With -- command args…, runs that single command non-interactively and exits with its status (signal exits report 128).
| Flag | Default | Description |
|---|---|---|
-L LOCAL_PORT:REMOTE_HOST:REMOTE_PORT |
(none) | Forward LOCAL_PORT to REMOTE_HOST:REMOTE_PORT without a shell. TCP only (repeatable; bracket IPv6 hosts) |
-g |
off | Bind forwarded ports on 0.0.0.0 instead of 127.0.0.1 |
-name NAME |
(auto) | Remember this host under NAME (new hosts only; auto-assigns device<N> if omitted) |
-relay |
off | Request a TURN relay up front instead of only on fallback (ICE still prefers a direct path if one succeeds) |
-norelay |
off | Refuse STUN/TURN entirely -- host candidates only, so a connection that would need a relay fails instead. Answers whether the direct path actually works. |
-nosave |
off | Do not write this device to ~/.bitbang/devices.json. That table stores the access code, which is a working credential -- so this is what you want on a machine that is not yours. Cannot be combined with -name. |
-pin PIN |
(prompt) | PIN to send if the listener requires one (skips the interactive prompt) |
-timeout DUR |
30s |
Dial timeout (e.g. 45s, 1m) |
-server HOST |
bitba.ng |
Signaling server -- pair-code mode only; the URL form carries its own host |
-v |
off | Verbose logging |
Exactly one of <src> / <dst> is remote, written <URL>:/path (URL in any form accepted by connect). - means stdin/stdout, so cp <URL>:/f - streams to stdout and cp - <URL>:/f uploads from stdin. A trailing / or . on the local side keeps the remote basename (scp-style).
| Flag | Default | Description |
|---|---|---|
-relay |
off | Request a TURN relay up front (as in connect) |
-pin PIN |
(prompt) | PIN to send if required |
-timeout DUR |
30s |
Dial timeout |
-v |
off | Verbose logging |
Every successful connect or pairing is remembered in ~/.bitbang/devices.json (mode 0600), so you can reconnect by a short name instead of a URL or code:
bitbang connect 482731 -name nas1 # pair once, save it as "nas1"
bitbang connect nas1 # thereafter, just the name
-name NAMEchooses the name; it applies only to a new host. Without it, an auto name (device1,device2, …) is assigned and printed (Saved as "device1".).- Naming rules: a name must start with a letter and contain only letters, digits,
-, or_. That guarantees it can never be mistaken for a 6-digit code or a URL. Lookups and uniqueness are case-insensitive. - No renaming via connect:
bitbang connect nas1 -name nas2is rejected ---nameis for first-time saves only. - When it's saved: a pairing is recorded as soon as the SAS is verified (so a flaky reconnect doesn't lose it); a URL connect is recorded once connected.
- Each entry stores
{name, uid, access_code, server, paired_at}. Reconnecting a known host (by name or URL) refreshes it in place and keeps the name.
Requires Go 1.25+. Pure Go, statically linked (CGO_ENABLED=0) -- trivial cross-compilation, no runtime dependencies.
go build ./cmd/bitbang/
# cross-compile:
GOOS=linux GOARCH=arm64 go build -o bitbang-arm64 ./cmd/bitbang/
GOOS=linux GOARCH=arm GOARM=7 go build -o bitbang-armv7 ./cmd/bitbang/
GOOS=windows GOARCH=amd64 go build -o bitbang.exe ./cmd/bitbang/
GOOS=darwin GOARCH=arm64 go build -o bitbang-macos ./cmd/bitbang/
From Windows Command Prompt:
go build -o bitbang.exe .\cmd\bitbang
go test .\...
run_tests.cmd unitShell commands, file sharing, proxying, and the CLI client are supported on Windows. Interactive browser and CLI shells use Windows ConPTY, including terminal input echo, line editing, VT output, and resize events. ConPTY requires Windows 10 version 1809 or Windows Server 2019 or later.
Shipping today: shell, files, and proxy, reachable from the browser or the CLI, plus TCP port forwarding, scp-style file copy, ad-hoc pairing with a saved device table, terminal sharing (bitbang share), and access links (bitbang link) that narrow and expire what a URL grants. Designed and on the way:
- Serial bridging -- drive a remote
/dev/ttyUSB0from a local virtual port (e.g. run Arduino IDE over the internet). An issue has been opened here. - Remote desktop -- screen over a WebRTC video track, keyboard/mouse over the data channel.
MIT -- see LICENSE.
Issues and PRs welcome.
Recipes are different: they live in the cookbook, in the bitbang repo, because they span every project rather than this one. Adding a recipe is a PR there.
Getting it listed is a second, small PR per project whose README should surface it -- the Recipes list above is maintained here by hand. That is deliberate: each project decides which recipes are worth putting in front of its own readers, rather than every README growing every recipe.



