Termuna on a server
A machine you never sit at can still carry your
sessions. termuna-daemon is the engine without the window: it
joins your account, connects outbound to the relay, and the machine
appears in every client's device list, ready to host its first session,
even when nothing is running on it.
Why a second binary
termuna --daemon and termuna-daemon run the
same code. The difference is what comes with it: the desktop binary
carries the GPU stack, fonts, and a display connection, none of which a
box in a rack will ever draw. termuna-daemon is shells, the
session tree, the local socket, and the relay connection - about 10 MB,
no GPU, no display. A laptop installs the app; a server installs
this.
Install
curl -fsSL https://raw.githubusercontent.com/termuna/termuna/main/install-daemon.sh | sh
Per-user, into ~/.local/bin, no root, nothing outside
your home directory. It fetches the binary, joins the machine to your
account, installs a systemd user service, and starts it. By
hand, the same three steps:
install -m755 termuna-daemon ~/.local/bin/ TERMUNA_TOKEN=<a token this account already has> termuna-daemon login systemctl --user enable --now termuna-daemon
termuna-daemon status tells you what the machine is
joined to and where its socket and data live. Joining a machine needs a
plan with cloud continuity (Solo or Team).
Join without typing a password into a server
TERMUNA_TOKEN hands the machine any token your account
already has - your laptop's will do - and it mints its own from that,
with its own name and id. The borrowed token is used for one call and
never written down on the server. Prefer this: a password typed into a
box in a rack afterwards lives in that box's shell history and in
whatever provisioned it. For an install with nothing to borrow,
termuna-daemon login also prompts for email and password, or
reads TERMUNA_EMAIL/TERMUNA_PASSWORD.
Lingering is not optional
The installer runs loginctl enable-linger. Without it,
systemd stops your user manager when you log out, so the service would be
"enabled" and never running on a machine nobody logs into, and any
session it held would die with your SSH connection. Keeping sessions
alive is the whole product, so the unit insists.
No inbound port, no jump host
The daemon connects out to the relay over an encrypted WebSocket, and everything - listing, creating sessions, typing - rides that outbound connection. The server needs no open port, no public address, and no bastion in front of it; it can sit behind NAT, a firewall, or inside a VPN and still answer from your phone. This is the opposite of SSH's model, where something must be reachable from the outside (the full explainer).
What it looks like from everywhere else
The machine appears beside your laptops in the sessions drawer and in
the phone's list, named <host> · <os>-<arch>.
Sessions can be created, renamed, killed, and woken on it from any
signed-in client - and it is offered whether or not anything is running
on it, because the machine holds a connection of its own. Liveness is the
machine answering, not a guess from old sessions: a busy server reads as
up, a closed laptop as down.
Custody
Commanding a machine costs your account's vault passphrase, not a
device token. The daemon keeps a random key of its own in
device.key (mode 0600) and publishes it wrapped to your
account's vault public key: the relay stores a blob it cannot read, and a
client unwraps it with your account key. A stolen device token buys what
it always bought - rows the thief cannot read. Revoking the machine is
revoking its device token from the account screen; its channel closes on
the next call and its sessions keep running locally.
Where things live
~/.local/bin/termuna-daemon the binary ~/.config/termuna/config.toml [cloud] only, mode 0600 ~/.local/share/termuna/sessions/ session trees + device.key $XDG_RUNTIME_DIR/termuna/mux.sock the local socket ~/.config/systemd/user/termuna-daemon.service the unit
It writes only the [cloud] keys it needs, so installing
it beside a desktop on the same box does not eat that desktop's
settings.
Upgrading loses nothing
Replace the binary and restart the service; the shells survive the swap:
install -m755 termuna-daemon ~/.local/bin/ systemctl --user restart termuna-daemon
The new daemon adopts the running shells from the old one - a build that was compiling keeps compiling, an editor keeps its buffer, and a shell that is mid-stream loses not a byte of output: the old daemon stops reading and drains before it hands over, so anything it had not shown anyone yet waits in the kernel's buffer for its successor. A remote viewer sees a brief reconnect; the shell sees nothing.
termuna-daemon status also reports the running
process - its pid, build, and the protocol version it speaks - and says
so when the binary on disk speaks a newer protocol than the process
does, which is exactly the moment the restart above is worth doing.
Replacing the file changes nothing by itself: the process keeps running
the code it started with.
Resident, and what a reboot means
Unlike the desktop's daemon, which exits after fifteen idle minutes
because the window will start another, termuna-daemon is
resident: a server with no sessions is not finished, it is waiting. After
a reboot the service comes back on its own; shells are processes and do
not, but the session trees persist, come back dormant, and wake into
fresh shells with their working directories remembered - from any device,
because the machine itself is addressable.