pai-sho 0.2.2

What happens when you want dumbpipe to stay running, handle a few ports at once, and reconnect when your laptop wakes up
pai-sho-0.2.2 is not a library.

My workflow is generally a dedicated VM per task -- a vibenv. These environments tend to have no open inbound ports. iroh's dumbpipe worked nicely for reaching a single port, but when you need 3-4 ports you have to run 3-4 dumbpipes on each side -- so 6-8 processes just to share a few ports. pai-sho gives you a single long-lived daemon that manages multiple ports. You can expose and unexpose them on the fly, and if the connection drops it comes back on its own.

Example

Say I have a VM running an http-nu app on :3001 and stellar on :7331 for live CSS editing. I start the daemon with both ports exposed:

pai-sho daemon -e 3001 -e 7331
# Ticket: 5hc4bjqfp6booceusm3jrfebbegyfi6aiqwbgx4xxqmpvg5usoyq

On my laptop, I connect with the ticket:

pai-sho daemon -a 5hc4bjqfp6booceusm3jrfebbegyfi6aiqwbgx4xxqmpvg5usoyq

Now localhost:3001 and localhost:7331 on my laptop reach the VM. Close the laptop, reopen it -- the connection restores on its own.

Later, I spin up something new on the VM:

http-nu :3002 -c '{|req| "hello from a new experiment"}'
pai-sho expose 3002

It's immediately there at http://localhost:3002 in my local browser. Done with it? pai-sho unexpose 3002.

Install

cargo install pai-sho
brew install cablehead/tap/pai-sho
eget cablehead/pai-sho

Or grab a binary from releases.

Usage

pai-sho [--socket <path>] <command>

Commands

daemon [options]        Start the daemon
ticket                  Print daemon's ticket
add-peer <ticket>       Connect to a peer
remove-peer <ticket>    Disconnect from a peer
expose <port>           Expose a local port to peers
unexpose <port>         Stop exposing a port
list                    Show peers, exposed ports, bindings

Daemon Options

Option Default Description
--host 127.0.0.1 Address to forward exposed ports to
-a, --add Add peer on startup (repeatable)
-e, --expose Expose port on startup (repeatable)
--socket /tmp/pai-sho.sock Unix socket path

How it works

Each daemon gets a unique ticket (an iroh endpoint ID). When you add a peer by ticket, iroh handles discovery and NAT traversal -- connecting directly when possible, falling back through relay servers when needed.

Exposed ports are announced to peers automatically. When a peer exposes port 3001, a local TCP listener binds 127.0.0.1:3001 on your side. Traffic goes over an encrypted QUIC connection. It goes both ways -- if you have something running locally on :4001, pai-sho expose 4001 makes it available in the remote session too.

If the connection drops, both sides reconnect with exponential backoff. Existing port bindings stay active and resume when the connection comes back.

See also

ngrok and Cloudflare Tunnel are great when you need a public URL anyone can reach. pai-sho is more for connecting your own machines or sharing a ticket with a friend so they can see something you're working on.

SSH tunnels need inbound access on at least one side. pai-sho works when neither machine has open inbound ports.

WireGuard, Tailscale, NetBird -- mesh VPNs that give every machine an IP on a virtual network. pai-sho is narrower -- you expose specific ports, not your whole machine. It can be easier to reason about what you're exposing when you go port by port.

dumbpipe is the direct inspiration.