tush publishes a shell over a tunnel and hands you back a URL. Anyone who
opens that URL — with tush, or in a browser — gets an interactive terminal on
the machine that published it: your own shell, your own configuration, job
control and all.
The session belongs to the machine, not to the connection. Close your laptop, lose your wifi, or detach on purpose, and the shell keeps running; reattach and you land back where you were, with what it printed while you were gone.
- 🐚 Your shell, not an emulation — runs
$SHELLon a real pseudo-terminal, so your prompt, aliases, completions, history and colours are the ones you already have. - 🌍 Nothing to install on the far end — open the URL in a browser and you get the same terminal. Whoever you sent it to needs no binary, no Go, no package manager.
- 🔌 Detach and come back — the shell outlives its clients. Reattach and the recent screen is replayed, so you arrive somewhere recognisable instead of a blank terminal.
- ⌨️ A real terminal — job control works: Ctrl+C interrupts, Ctrl+Z suspends,
fgresumes.vim,topand other full-screen programs draw correctly and follow your window size. - 🌐 No inbound ports — the tunnel is outbound-only, so it works from behind NAT, on hotel wifi, or inside a container.
- 🎲 Unguessable URLs — hostnames are opaque rather than memorable, so the address is not something anyone stumbles onto.
- 🤝 A protocol, not a bespoke wire format — speaks the same remote command protocol kubelet serves and
kubectl attachtalks to. - 🪶 One static binary — pure Go, no cgo, no runtime to install.
Homebrew (macOS and Linux):
brew tap scaffoldly/tap
brew install tushnpx — no install at all:
npx @scaffoldly/tushNode is the only thing needed, and it is only a launcher: npm fetches the prebuilt binary for your platform and runs it. Nothing is compiled, and no install script downloads anything.
Go:
go install github.com/scaffoldly/tush@latestBinaries — signed archives for macOS and Linux are attached to every release.
Publish a shell:
tushIt waits for the tunnel to start routing, then prints the command to run on any other machine:
Attach from another machine with:
npx @scaffoldly/tush https://<hostname>.tunneled.pizza/
or open that URL in a browser.
Anyone with that URL gets a shell as you.
Press Ctrl+C to stop the tunnel.
The command it prints is the one you used to start it — tush if it is
installed, npx @scaffoldly/tush if you got here through npx — because it is
meant to be typed on the other machine, where tush may not exist either.
You are now on a shell on the first machine. Press Ctrl+P Ctrl+Q to detach
and leave it running, or exit to end it — which also stops the tunnel.
Opening the URL gives a page with a terminal on it and nothing connected. It stays that way until you press Connect: fetching the page must be inert, because plenty of things fetch URLs without a person involved.
Ctrl+P Ctrl+Q detaches, the same as it does from the terminal client, and leaves you on the card with an Attach button. Closing the tab detaches too, and so does the Detach button in the corner.
Beside it is Stop, which ends the session and the tunnel for everyone — the
URL does not come back. It takes two presses. It is not a power the URL did not
already carry: anyone who can attach can type exit, which does the same
thing. The button only makes it obvious.
One client at a time, whichever kind, and the newest one wins. A browser tab and
a tush client contend for the same shell; whoever arrives last gets it, and
whoever had it is told so and can attach again. Nobody is turned away — losing
the shell this way costs you nothing but the trip back, and anyone who could
take it could equally have typed exit.
tush version and tush help do what they look like.
The host opens a pseudo-terminal, runs your shell on it, and serves it over the
Kubernetes remote command protocol — the one kubelet exposes and kubectl attach speaks. Clients attach to a shell that is already running rather than
starting one, which is what lets a session outlive the connection that created
it.
A browser speaks that protocol natively, so the page served at the same URL is another client rather than another interface: the same frames, the same channels, a terminal emulator instead of yours.
For the packages, the invariants, and the things that bite, see CONTRIBUTING.md.
There is no authentication. The URL is the credential. Anyone who has it gets a shell as the user who published it, so treat it like a password: share it deliberately, and end the session when you are done. Gating access belongs to the tunnel provider rather than to this binary — see CONTRIBUTING.md for why.
Because the URL now yields a shell to a browser as well, it is worth being blunt about what that means: pasting it somewhere is closer to pasting a password than to pasting a link, and a chat client that previews links or a history that syncs between devices carries it further than you intended.
What the page does about it: it attaches nothing until you press Attach, so
fetching it — by an unfurler, a crawler, a preview — opens no session, and
ending the session takes a POST, so nothing that merely follows a link can do
it either. It
carries no link preview metadata worth showing, asks not to be indexed, is
served no-store so it does not sit in a shared cache, and sends no referrer,
so the URL is not handed to anything the page loads. None of that is
authentication; it only stops the URL escaping by accident.
The terminal emulator is loaded from a CDN and pinned by hash, so a CDN that served something other than what was published cannot put script on a page that grants a shell.
Release archives are signed with keyless cosign and carry SLSA build provenance; verification steps are in CONTRIBUTING.md.
macOS and Linux, on x86-64 and ARM. Windows is not supported: the host needs a pseudo-terminal and a controlling terminal to give the shell job control, and Windows has no equivalent of either.
Tracked as issues. The larger ones:
- #2 —
tush [command], sotush k9stunnels k9s rather than only ever a shell.
CONTRIBUTING.md covers the layout, the dev loop, and how a change reaches a release. It is written for humans and AI agents alike; agents should start at CLAUDE.md.