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:
# Ticket: 5hc4bjqfp6booceusm3jrfebbegyfi6aiqwbgx4xxqmpvg5usoyq
On my laptop, I connect with the ticket:
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:
It's immediately there at http://localhost:3002 in my local browser. Done with it? pai-sho unexpose 3002.
Install
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.