koh
A Rust, peer-to-peer remote shell inspired by mosh, built on iroh / QUIC.
koh gives you a responsive remote shell that survives network changes, suspend/resume, and reconnects — without SSH, open ports, or server-side accounts.
Install and usage
koh authorizes by endpoint id. There are no passwords or accounts.
# On the client, print its id:
# On the server, allow that client and start a shell host:
# On the client, connect to the server:
Useful commands:
Useful flags:
Keys live under ~/.config/koh/ by default.
Platforms: Linux, macOS, and Android via Termux. Windows is not supported; use WSL2.
Android / Termux install
-
Install Termux from the Termux GitHub releases. Do not use the old Play Store build.
-
In Termux, install Rust and build tools:
-
Install koh:
If DNS resolution is broken on your Android device, try setting an explicit resolver:
KOH_DNS=1.1.1.1
To get a phone notification when the remote shell rings the bell (a finished build, an agent
waiting for input), hook termux-notification:
The hook runs detached from the terminal at most once per second; KOH_BELL_COUNT and
KOH_TITLE are set in its environment, and every other KOH_* variable is scrubbed. Bells that
rang before you attached do not fire it; bells during a reconnect do.
As a library
koh's server and client are callable from another binary. Depend on it without the cli feature
(clap stays out of your tree) and pick exactly one backend-* terminal feature:
[]
= { = "0.11", = false, = ["backend-termina"] }
The stable surface is the four config types and their entry points: koh::server::{serve, ServeConfig}, koh::client::{connect, ConnectConfig, run_id, IdConfig} and
koh::keycmd::{run, KeyConfig}. ServeConfig::command is an argv, so any program can be hosted,
not only a shell:
use ;
serve
.await?;
The koh binary is the same code behind clap; cargo install koh is unaffected.
Syncing a state of your own
Since 0.11 the server hosts any SyncState producer and the client renders any ClientState, so
a program can use koh's transport (SSP over iroh: loss-tolerant, reconnecting, detachable) for
something other than a terminal screen. The state type a connection carries is selected by its
ALPN, so old koh peers are never confused. A twenty-line sketch: a shared String that every
authorized peer appends to.
use ;
use SyncState;
;
;
let hosts = new.with;
serve_with.await?;
On the client side, implement koh::client::ClientState for Log (title, exit code, echo-ack)
and a ClientTerminal<Log> that prints it, then connect_with(config, b"example/log/1", || Ok(term), input_rx, resize_rx). tests/e2e_generic_host.rs is the complete, runnable version of this over a
real loopback connection.
Highlights
- Built in Rust on iroh peer-to-peer QUIC; connects by endpoint id instead of hostname/port.
- Mosh-style predictive local echo and screen-state sync for responsive shells on bad networks.
- Detachable sessions survive suspend/resume, IP changes, and reconnects without tmux.
- No SSH bootstrap, no listening port, and no port forwarding needed.
- Not wire-compatible with mosh or SSH; koh is its own protocol/tool.
- Intended for personal machines you control; not a full SSH replacement.
- Does not provide multi-user accounts, file transfer, scrollback sync, or Windows support.
Status
koh is experimental and intended for personal use on machines you control.
See docs/THREAT_MODEL.md for the security model, SECURITY.md for vulnerability reporting, and docs/ARCHITECTURE.md for implementation details.
License
MIT, from 0.10.0 onward. Releases before 0.10.0 remain available under GPL-3.0-or-later.