SSH & remote hosts
A real SSH client inside the cockpit, and the option to run Muster's daemon on the far machine instead of tunnelling everything.
The client
- Terminals with the same panes, scrollback and search as local ones, plus saved per-host command snippets.
- A dual-pane file manager, local ↔ remote, with drag-and-drop transfers over rsync or SFTP and live progress.
- An in-app editor for remote files.
- Port forwarding — local, reverse and SOCKS, with live indicators. Tunnel a remote database port and point a saved connection at it.
- Host monitor — the top 20 processes by %CPU (pid, %CPU, %MEM, command)
and
dffilesystem rows. No load average, memory total or network figures; a failingpsand a failingdfare reported separately. - Log tailing — follow a remote file as it grows.
- Jump hosts, per-host keys, and password or key auth.
Transfers: which tool, and what you get
Muster prefers rsync and probes for it by running --info=progress2
together with --version, because the check has to distinguish a real rsync from one
that merely answers --version:
| Available tool | Progress | Resume |
|---|---|---|
| rsync 3.1+ (typical Linux) | Real percentage | Yes — --partial --append-verify continues an interrupted transfer |
| openrsync (stock macOS) or rsync 2.6.9 | Indeterminate — SFTP gives no incremental percent | No. The SFTP path restarts from zero |
openrsync as rsync, and it rejects
--info=progress2. So on a stock Mac every transfer takes the SFTP path: the bar is
indeterminate and asking to resume silently restarts. Install a real rsync (Homebrew) if you
move large files over flaky links. The fallback itself is deliberate — the alternative was every
transfer dying on "unrecognized option".Other behaviour worth knowing before you rely on it
- An agent already running is not upgraded. Every Muster version uploads
its own agent binary to a version-scoped directory, but they all share one socket — so
if an older agent is still up it keeps the socket, the new binary exits
AlreadyRunning, and you stay on the old daemon. Muster records and logs the version that actually answered rather than the one it uploaded. Picking up a new agent means stopping the running one, which ends its remote sessions, so Muster will not do it to you automatically. - Password auth is macOS/Linux only. Feeding a saved password to
sshwithout putting it on a command line needs anSSH_ASKPASShelper, and Muster's is a POSIX shell script Windows cannot execute. On Windows use key authentication: the terminal, the port forward and every background command all refuse up front and name the remedy, rather than arming a helper that cannot run. - Unknown host keys are refused, not adopted. Everything that runs without
you watching — agent bootstrap, background commands, SFTP, rsync, transfers, the monitor,
tunnels and port forwards — uses
StrictHostKeyChecking=yesand fails outright on a host whose key is not already inknown_hosts. The interactive Terminal tab of a KEY-auth host usesask: Muster shows you the fingerprint and you Accept or Reject, and that is the only place a key is ever written toknown_hosts. One TOFU path remains — the interactive session of a password host, which stays onaccept-newbecause ssh routes the host-key confirmation through the same channel as the password and anaskthere would deadlock. A key that has CHANGED is refused everywhere; clearing it is a deliberate action in the app. - A profile's default SSH key applies to the remote-daemon path only. The
terminal, file manager, transfers, monitor, tail viewer and port forwards use the host's
own
ssh_key_path, falling back to ssh's defaults when it is blank. - Directory transfers ask before overwriting — but only when there is
something to lose. Dropping a folder onto a destination that is absent or empty just
transfers; onto one that already holds files it raises the same four-way prompt a single
file does, because a directory transfer MERGES and would otherwise replace same-named files
with no way back. A destination holding only dotfiles counts as occupied. The four choices
are Overwrite (replace the destination), Resume (continue
the interrupted transfer — needs a real rsync 3.1+, since it is
--partial --append-verify), Keep both (re-run to a fresh, non-colliding name) and Skip (abandon it, destination untouched). - Transfers run concurrently, not in a queue. Each drop starts its own worker immediately; "Queued" is the instant before its thread starts.
- A green "Live" forward is a 450 ms timer, not a confirmed bind. Most bind failures exit inside that window and show as failures, but a slow one flips green first and only corrects when the process exits.
Remote daemon
A project can live on another machine, with Muster's daemon running there and the app attaching to it. Two transports:
- Over SSH — the daemon is reached through a forwarded socket. Nothing extra to open.
- Direct TLS — the daemon binds a port and the app connects to it.
The direct path is authenticated two ways at once: a shared token, and
certificate-fingerprint pinning — the client refuses a certificate it has not
pinned, so swapping the certificate does not silently work. Pairing is one copy-paste credential
(MUSTER-PAIR-…) carrying the address, the token and the fingerprint together, so
nobody is asked to eyeball a fingerprint by hand.
One token rule applies: a daemon bound to a non-loopback address requires a token of at least 32 characters. A loopback bind requires only a non-empty one — it is not network-reachable, and the accept-loop throttle covers local guessing.
muster-daemon's generated tokens are 64 hex characters (256 bits), comfortably
above that floor.
What runs on the remote daemon
Once a project points at a host, the daemon there does the work, not your machine:
- Agents and terminals spawn on the remote, in the repo root.
- Worktrees and git — worktree create/list/remove and post-create hooks
(
WtCreate/WtList/WtRemove/WtHook), plus the fullWorktreeOpsset overReq::WtGit: diff review with hunk and line staging, discard, commit, update-from-base with the conflict resolver, blame, history, the repo panel, and PR creation —create_prpushes and runsgh pr createon the remote host, with the compare-URL fallback whenghisn't signed in there. - Automation actions —
open-prandrun-commandexecute on the remote host through a scopedReq::Execwhile the app is connected. Headless flows (the daemon running with no app attached) FAIL CLOSED for remote projects: they refuse rather than run local git against a remote path, so those flows need the app running. - Search and file tree — global search and the repo file tree run
daemon-side (
FsSearch/FsListDir/FsStat), honor.gitignorewhere the files live, and merge results with the host named on each. An unreachable host shows an honest warning row instead of silently dropping results.
Version skew: capability-probed, refused cleanly
Each remote feature set is advertised by the daemon as a capability at connect —
worktrees, git-ops, exec, remote-fs — and the
app probes the capability BEFORE issuing an op. A remote daemon older than the feature gets an
actionable refusal, not a protocol error: "the remote agent on this host is too old … stop
the remote agent (this ends its remote sessions) and reconnect so it picks up the current
build." Stopping is never done for you automatically, because it ends the remote sessions
that daemon owns.
What does not reach remote agents
Scoped MCP permissions with Ask approvals are local-only: a remote spawn gets the policy
environment (MUSTER_MCP_POLICY and friends), but no
MUSTER_CONTROL_SOCK and no MCP config injection — the MCP server's per-call policy
re-resolution and audit trail open the app's SQLite database, which lives on your machine, so
a remote agent has no muster MCP server entry at all. The narrowest capability
set, never a wider one — and no live Ask prompts on remote tasks.
New here? Start the 7-day trial — every feature, card up front, $0 today, key on screen. Or buy outright: $39 once per device, no subscription.