# User Guide The README shows xmux at work and how to install and launch it. This guide covers the rest of daily use: the command line, how machines are found and logged in to, the configuration file, and the control socket. [`keybind.md`](keybind.md) lists every key. ## Command Line ```sh xmux # open the app xmux ls # list every reachable session (scriptable) xmux attach # attach one session directly, e.g. xmux attach prod api xmux doctor # check config and per-host reachability xmux instances # list running instances xmux send # drive one of them over its control socket xmux update # update the installed binary xmux uninstall # remove the installed xmux, asking first xmux version ``` The nav sits on the left and the terminal view on the right shows the selected session's live grid. Keyboard focus is on one view at a time. ## Machines and Hosts A **machine** is a computer xmux can reach, such as `prod`, `local`, or `wsl.Ubuntu`. A **host** is one mux on one machine, so a machine running both psmux and zellij is two hosts. A **session** is one session of a mux on a host. A host is named `local:psmux` when its machine serves several muxes and `prod` when it serves one; that name is the host id, and it is what the nav shows. Commands name a session by its host and its session separately: `xmux attach ` and the control socket's `switch ` take the host id as `` (e.g. `xmux attach prod api`, `switch local:psmux api`). xmux probes remote machines after the app is up, so each host appears as its machine answers. ### Machine Login A remote machine that ssh cannot reach with the values it works out on its own shows `login required` (a `?` mark). Its panel in the terminal view takes the login: 1. The panel holds the values ssh does not ask for, each starting at what ssh would have used: - the address - the port - the username - an optional masked password 2. On submit, xmux hands those values to ssh and answers the host-key question and the password itself, so the login needs no further input. Esc ends the attempt. 3. A login that works re-probes that machine, and the panel gives way to the sessions it found. The submitted values become the machine's, so everything xmux runs there afterwards connects the way the login did. A login the values cannot finish reports what the server asked for. One radio choice decides what a working login leaves behind: - nothing - the values, recorded as an `~/.ssh/config` stanza - the user's public key, registered on the machine, and the working address, port, and username saved in `~/.ssh/config` for later runs The line xmux appends ends its comment with `xmux-registered`, which sshd ignores and which tells xmux's lines from the user's own. A machine that already holds the same key, under any comment or options, gets no second line, and its line stays unmarked. After registering the key, xmux runs one separate login that may use only that key, and reports the key registered only when that login runs a command. When the machine accepts the key but cannot open a session, xmux reports the server's error and removes the line this registration added, so the machine stays reachable by password. When that login cannot be tried at all, xmux keeps the key and reports it as not verified. The information screen's `SSH login` row shows the SSH authentication method reported by the selected session's display connection. On a machine card it shows the machine's last observed method. A connection that rides the machine's shared SSH connection reports no method of its own and uses the one that shared connection authenticated with, so it shows that method, which xmux keeps across runs for a shared connection an earlier run opened. A kept method names the shared connection it was reported on and is shown only while the machine rides that same connection; a shared connection opened by a channel that reports nothing says `not observed`, as does a machine no connection has reported a method for. A held password disappearing closes that machine's metadata and display connections. A new login or explicit re-scan is needed to connect again. On an SSH machine, `prefix L` opens a confirmation that states the machine it logs out of, whichever of its cards is selected, its observed SSH login, what happens to a held password and to this PC's key, and the machine whose connections close. Type `logout` to take this PC's public key off the machine, then clear the password xmux holds in memory and close that machine's connections, including its SSH master where present. Before closing anything, xmux looks for lines in the machine's key files that hold one of this PC's public keys, comparing the key type and body and ignoring options and the comment. The lines marked `xmux-registered` are removed. A matching line without the mark was not added by xmux, and removing it also stops ssh outside xmux from using the key, so a second confirmation opens in the same place and asks first: type `remove` to remove it too, or press Esc to keep it. When the machine cannot be reached or the removal fails, the logout still clears the password and the connections, and its toast says the key remains and why. After the key, xmux removes the machine from `~/.ssh/config`. The stanza a login saved under its `# xmux: ` line goes without asking. Every other `Host` entry that names the machine exactly, ignoring case, was written by someone else, so the second confirmation lists it with the line it leaves, together with any key line xmux did not add; one `remove` answers both, and Esc keeps both. An entry that names only this machine goes with its options, and an entry that names other machines too loses only this name. Wildcard and negated patterns, `Match` blocks, and every other line of the file stay as they were; the toast names each entry that changed and each one that stays. The machine's card and screen then read `logged out`, and its screen keeps the login form. A re-scan reconnects only with a key the machine still accepts; otherwise, log in again. For the requirements of a Windows machine and the limits of Entra-only accounts, see [`INSTALL.md`](../INSTALL.md#windows-machines). ## Roster The roster assembles the machines xmux offers. It gathers ssh target names from three providers: | Provider | What it names | | ---------------------- | ---------------------------------------------------------- | | ssh config | the aliases in `~/.ssh/config` | | neighbours | the machines this one already reaches in one hop and that answer ssh | | WSL | this PC's WSL distributions | The roster is rebuilt at startup and on every rescan. `local`, this machine reached without ssh, is not part of the roster, and a machine no provider names is a machine xmux has nothing to do with. The `[discovery]` table turns providers off one by one; all are on by default. Each host's first contact and session listing share a ten-second scan limit. An unanswered card stops scanning after ten seconds and shows a timeout. Every provider yields ssh target names, and xmux behaves the same whichever provider suggested a name. The suggesting provider is kept beside the name and shown when the machine becomes unreachable, which tells which provider to inspect or turn off. A provider whose command is missing, whose OS does not answer, or whose output cannot be parsed counts as an empty list rather than an error, so one failing provider never hides the machines the others suggest. ### Neighbour Discovery The neighbours provider reads the operating system's own network state, so it needs no VPN client and no account anywhere. It asks the OS directly (over netlink on Linux and Android, through IP Helper on Windows), so it also runs where the usual command-line tools are missing or, as on Android, refused. - **Who is reachable.** Two records say so. The routing table holds one route per peer that a mesh VPN writes, or one route for a handful of peers, which is read as those addresses. The neighbour table (the ARP cache) holds the machines on this link this machine has exchanged frames with. Where the OS refuses the neighbour table, as Android does, the provider asks the link this machine is on address by address instead. - **Which of them are machines.** An entry that resolved to nothing, and one hardware address answering for many addresses (a router speaking for a subnet), name no machine. Each remaining entry is asked whether it answers ssh, because a printer on the same switch is a neighbour and not a machine. - **What to call them.** The provider asks the system resolver first, which is where a mesh VPN's own naming already lives, so a peer arrives under the name its network gave it. A machine no resolver knows is asked for its own name, which it answers over mDNS whether or not anyone registered it. A name is used only when this machine can resolve it back, because the name is also what ssh is given; a machine whose name leads nowhere keeps its address as its name. ## Configuration Configuration is optional. xmux reads `~/.config/xmux/config.toml`: ```toml exclude = ["bastion", "wsl.docker-desktop"] # hide these machines [discovery] ssh-config = true neighbors = true wsl = true scan-concurrency = 6 # startup limit, clamped to 1 through 8 [local] mux = "auto" # "auto" (default): every mux installed here, # or a list: ["psmux", "zellij", "abduco", "tuios", "herdr"] [ui] theme = "auto-dark" # built-in ANSI theme: "auto-dark" (default) # or "auto-light" (for a light terminal) prefix = "C-g" # xmux's prefix (e.g. C-g, C-Space, C-b) auto-hide-nav = false # initial auto-hide-nav state renumbering = true # keep card numbers in sorted nav order notifications = true # show results as toasts (the prefix m history keeps them either way) braille-animation = true # show the central Braille X on scanning, machine, and host screens nav-position = "left" # the nav's default side (left|top|right|bottom|floating) max-fps = 30 # maximum xmux draws per second (10 to 120) nav-active-border-style = "green" # focused nav-border colour selection-style = "" # selection background; empty: the theme's accent primary = "brightwhite" # per-role colour overrides: primary, secondary, accent = "brightgreen" # accent, decoration, warning, error, disabled, bar-bg = "colour235" # and the prefix hint chip's bar-bg / bar-fg / bar-accent [update] check = true # allow startup checks and automatic updates [[hosts]] ssh = "prod" # an ssh-config alias mux = "tmux" # omitted or "auto": every mux the machine answers it has [[wsl]] distro = "Ubuntu-24.04" # bare distribution name mux = "auto" ``` The supported settings and their application times are: | Table | Keys | Default and application | | --- | --- | --- | | Root | `exclude` | Empty list; exact SSH machine names or `wsl.` names, applied on a full rescan. | | `[local]` | `mux` | `"auto"`; a mux name or list, applied on a full rescan. | | `[[hosts]]` | `ssh`, `mux` | SSH alias and an optional mux name or list; omitted mux or `"auto"` discovers muxes. Applied on a full rescan. | | `[[wsl]]` | `distro`, `mux` | Bare distribution name and the same mux choices; applied on a full rescan. | | `[discovery]` | `ssh-config`, `neighbors`, `wsl` | All `true`; applied on a full rescan. Explicit machine entries still apply when their discovery provider is off. | | `[discovery]` | `scan-concurrency` | `6`, clamped to 1 through 8; restart required. | | `[update]` | `check` | `true`; controls release checks and updates before the app starts; restart required. | | `[ui]` | `prefix` | `"C-g"`; restart required. | | `[ui]` | `auto-hide-nav` | `false`; startup default, overridden by the saved `prefix t` toggle. | | `[ui]` | `nav-position` | `"left"`; applied live unless a saved position is pinned. | | `[ui]` | `theme`, `max-fps` | `"auto-dark"` and `30`; applied live. Themes are `"auto-dark"` or `"auto-light"`; frame rate is 10 through 120. | | `[ui]` | `renumbering`, `notifications`, `braille-animation` | All `true`; applied live. | | `[ui]` | `nav-active-border-style`, `nav-border-style`, `nav-border-hover-style`, `selection-style` | Empty strings use theme defaults; applied live. | | `[ui]` | `primary`, `secondary`, `accent`, `decoration`, `warning`, `error`, `disabled`, `bar-bg`, `bar-fg`, `bar-accent` | Empty strings use theme defaults; applied live. | Run `xmux doctor` to report unknown keys and unsupported mux names. A mixed mux list such as `["auto", "tmux"]` is an explicit list: `auto` is not a mux name. Colour values accept ANSI colour names, `bright*` names, `colour0` through `colour255`, `#RRGGBB`, and `default`. Border styles accept a bare colour or `fg=`, the prefix hint chip accepts `bg=,fg=`, and selection style accepts a bare background colour or `bg=`. Invalid colours use the terminal default; an unknown theme uses `auto-dark`, and an unknown nav position uses `left`. Settings marked live apply when the file is created or replaced as well as when it is edited. A missing or malformed file keeps the last valid UI settings. Machine and roster edits take effect on a `prefix R` rescan. Changing an explicit mux list removes muxes it no longer names while preserving the machine's working login. Turning off neighbour discovery removes machines offered only by that provider; explicit entries and other enabled providers still apply. - **Nav position.** The nav rides on one of the four sides of the terminal view (a left or right column, a top or bottom horizontal nav) or floats as a box over the terminal's empty space. `[ui] nav-position` picks the default, and the nav never moves on its own. `prefix p` places it one side clockwise (left → top → right → bottom → floating) and remembers the choice in `~/.xmux/nav_position`, which wins over the default. The cycle unpins at `floating`, the last position: the default applies again, and the next step re-pins from it. - **Machines.** Machines you write down scan first: `[[hosts]]` and `[[wsl]]` entries lead, then discovery adds `~/.ssh/config` aliases and neighbors. The config file augments discovery and never replaces it. - **State.** The state kept between runs lives under `~/.xmux/`: - the last selected session - the live auto-hide-nav toggle - the pinned nav position - logs - control sockets ## Control Socket Every running instance has a name and listens on `~/.xmux/ctl-.sock`. Commands name a session by its host and its session separately (`switch `), which the nav shows joined as `/`. The socket takes navigation verbs (`ping`, `status`, `dump`, `rescan`, `switch`, `focus`, `width`, `toggle-auto-hide`, `quit`) and one session-lifecycle verb (`new-session`). It has no kill, rename, or window verbs, because the mux owns editing a session. ```sh xmux instances # NAME · PID · CWD · TTY · displayed · focus xmux send amber-otter switch prod api xmux send am focus terminal # any unambiguous name prefix xmux send - dump # `-` when exactly one is running ``` xmux answers each of these with an error that names the candidates, and never picks one by guessing: - an unknown name - a prefix matching several instances - `-` while several instances run