termuna

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.

Next: Troubleshooting