Remote Hosts
Open sessions on other machines in your own client, run panes on another machine, and script across hosts.
TUIOS can reach other machines over ssh. Once a machine is added as a host, you can list its sessions and agents, open one of its sessions in your own client, run a single pane on it inside a local session, and send commands and messages to it.
Each host needs TUIOS installed. Your daemon holds one ssh link to each host and talks to the TUIOS daemon on the other side. There is no new listening port and no second login system: ssh decides who can connect.
$ tuios attach workAdding a host
tuios hosts add build gaurav@buildboxThe name (build) is what you type later. It takes letters, digits, dot, dash and underscore. The address is anything ssh understands, including an alias from ~/.ssh/config.
The command writes the [hosts] table in your config file. The link is tested at once and the result is printed. A host that does not answer is still added.
From a terminal outside TUIOS, the running daemon opens the new host with no restart. From a pane, the new host waits for tuios config apply from a terminal outside TUIOS. A host that you add with "Add a host" on the settings page applies at once. See Changes that wait.
tuios hosts # every host and the state of its link
tuios hosts test build # dial it now and print what ssh said
tuios hosts test build --start # start its daemon if it does not run
tuios hosts remove build # drop ithosts add also takes:
--ssh-option ARG: one extra ssh argument. Repeat it, for example--ssh-option -J --ssh-option bastionfor a jump host.--connect-timeout SECONDS: how long one dial may take (default 10).--command PATH: the TUIOS binary to run on the host. Without it, the link looks on thePATH, then in the usual install locations, then in a login shell.--repos-root DIR: where the host keeps its checkouts, such as~/src.fan --host,worktree new --hostandstart-agent -s HOST:SESSIONlook there for a repository by its origin URL.
Accept the host key first
The daemon runs ssh with BatchMode on, so a link never asks for a password or about a host key. Run ssh to the host once by hand to accept its key, and use key-based login.
Link states
| Status | Meaning |
|---|---|
up | The link is open and the remote daemon answers |
no_daemon | The machine is up and no TUIOS daemon runs on it. See Start the daemon on a host |
no_tuios | The machine is up and the link cannot find TUIOS on it. tuios hosts test shows where it looked |
unreachable | The last attempt failed. The line under the table says why |
reconnecting | The link dropped and TUIOS is dialling again |
tailscale_check | Tailscale SSH waits for you to approve the login. The line under the table gives the link to open. See Tailscale SSH check mode |
incompatible | The two sides speak different control protocol versions. Update one of them, or run tuios hosts sync |
connecting | The first attempt has not finished |
The hosts table
tuios hosts add writes this for you. You can also edit it by hand:
[hosts.build]
addr = "gaurav@buildbox"
[hosts.lab]
addr = "lab-01"
connect_timeout = 5
command = "/opt/tools/tuios"
ssh_options = ["-J", "bastion"]ssh_options accepts only safe options, such as -J, -p and -o Keyword=value. TUIOS drops a host with a refused option, such as ProxyCommand or a port forward, and tuios hosts names the reason. Put such options in ~/.ssh/config. See Rules for ssh_options.
A machine behind NAT is a reachability problem for ssh. Any tool that works as an ssh ProxyCommand in ~/.ssh/config works here too. See Configuration for an example.
Machines on a tailnet
If this machine is on a Tailscale tailnet, TUIOS can list the machines on it:
tuios hosts tailnet arch-btw arch-btw.example.ts.net offline
+ ente ente.example.ts.net
= forgejo forgejo.example.ts.net already the host forgejo
my-phone my-phone.example.ts.net cannot run tuios (iOS)Add one by its name:
tuios hosts add ente --tailnetNothing is added on its own. The list only saves you typing the MagicDNS name. A host added this way is still reached over ssh. TUIOS gets the list by running tailscale status --json, looked up on PATH and then inside the macOS app bundle, so it needs no root and no auth key. Without the tailscale command, the list is empty.
By default the list leaves out machines that are offline, this machine, machines shared from another tailnet, and phones and tablets. The [tailscale] table changes that:
[tailscale]
addr = "dns" # dns (MagicDNS name), name (short name) or ip (100.x address)
user = "ubuntu" # ssh login put in front of every address
os = ["linux", "macOS"] # an empty list offers every OS
offline = false
include = ["*"]
exclude = ["*-pad-*"]
[tailscale.users]
build = "root" # per-machine login, wins over usertuios hosts tailnet --json gives every machine with offered, and a skipped reason when it is false.
Tailscale SSH check mode
Tailscale SSH can hold a login until you approve it in a browser. This is "check" mode. ssh then waits, and Tailscale prints a link to open:
# Tailscale SSH requires an additional check.
# To authenticate, visit: https://login.tailscale.com/a/l1d9c7e392d3020TUIOS reads this link and shows it.
- The daemon's link shows
tailscale_checkintuios hosts, with the link to open. The link waits up to 10 minutes. It goes on when you approve the login. tuios hosts testandtuios hosts addreporttailscale_checkwith the link. Open it, approve the login, then run the command again.tuios hosts syncon a terminal lists the link of each host that waits. Then it asks to wait up to 5 minutes. Each host continues when you approve its login. Without a terminal, or with--json, the host fails at once. Its row has"error_kind": "tailscale_check"andapproval_url.
All ssh calls of one hosts sync run to a host use one shared connection. Thus one approval covers the whole run. The connection uses a private socket in $XDG_RUNTIME_DIR and stops when the run ends.
If the tailnet policy refuses the login, TUIOS names the user it refused. The row has "error_kind": "tailscale_policy". Change the user in the addr of the host in [hosts], for example addr = "ubuntu@ente".
Opening a session on a host
tuios attach --host build api # attach the session api on build
tuios new --host build # create a session on build and attach it
tuios new --host build ci --detach # create the session ci on build and return
tuios ls --all-hosts # sessions on every machineThe session is drawn in your client, with your theme, config and prefix key. Nothing is nested. The connection goes through your local daemon and its link to the host.
In the rail, sessions are grouped by machine. Press Enter on a session under a host to attach it, or on the + beside a host to create a session there. While you are on a host, the rail lists this machine's sessions under local. Press Enter on one to come back.
If the link drops, the session keeps running on the host. Your client keeps the pane on screen, the dock says it is reconnecting, and TUIOS dials again on its own. After three minutes it gives up, says why, and returns you to the session you left on this machine.
--ssh runs ssh to the host and starts the TUIOS there instead. Use it when the host's TUIOS is too old to serve your client. You then see the remote client nested in yours, and you press the prefix key twice to send it to the inner one.
Panes on another machine
A single pane can run on another machine while it stays in a local session:
tuios new-window deploy --host buildThe palette has the same thing as New window on another machine. The pane is drawn, laid out and closed here. Only its process runs on build. Its title bar reads build:deploy, so two panes side by side never look alike when they are on different machines.
The session is still an ordinary session: tuios ls, scripting, the mailbox, hooks and resurrection work on it as usual. The host runs only the process and its pty. It keeps no screen or scrollback for the pane, and the pane does not appear in tuios ls on the host.
Agent detection works in such a pane: the local daemon asks the host what the pane is running. An agent inside it can also report its own state and read its own mail with the usual commands and hooks: the report goes to the daemon on the host, which passes it back over the link as that window and nothing else. These panes have TUIOS_PANE_ID, TUIOS_PANE_HOSTED=1, TUIOS_HOST set to the host, and TUIOS_SESSION_REMOTE set to the session name here. They do not have TUIOS_SOCKET or TUIOS_SESSION, since those would name a daemon and a session on the host.
To paste an image from your clipboard into such a pane, press Ctrl+B V. TUIOS sends the image over the link. The host saves it to a file, and TUIOS pastes the path of that file into the pane. See Paste an image.
Limits:
- The pane outlives a dropped link for a while. The host keeps its process running for its
hosted_grace(10 minutes unless the host's config says otherwise) and keeps its last 64 KB of output. The title bar reads[reconnecting] build:deploy, and keys are refused rather than queued. When the link comes back, the pane is reattached and what it printed meanwhile is written to it. If the grace runs out first, the pane closes. - Closing the pane ends its process at once.
- A restart of this daemon does not reattach. The processes on the host wait out their grace and end.
- A resurrected session brings the pane back as a local shell.
- Both machines need a TUIOS new enough to open remote panes. An older one refuses with a message that says to update.
Global sessions
A session with panes from several machines belongs to none of them. Once a second machine is reachable, the rail draws a global group above the machines for such sessions.
tuios new deploy --globalA global session starts with no panes. Every way of making a pane in it (the key, the rail's +, the palette) asks which machine to run on. Turn the group off with appearance.global_session = false.
Scripting across machines
Name the host in the target and a command runs on that host's daemon, through the link:
| Target | Means |
|---|---|
-s HOST:SESSION | A session on a host |
-w HOST:SESSION:WINDOW | A pane in a session on a host |
-s local:NAME | A session on this machine whose name contains a colon |
tuios list-windows -s build:api
tuios capture-pane -w build:api:0
tuios send-text -s build:api -w 0 'make test'
tuios wait-for window-idle -w build:api:0
tuios list-agents --host build
tuios kill-session build:apiThe CLI says which host answered, and --json adds a host field. A host name is matched exactly. An unknown name fails with unknown_host and lists the configured names, and a host that is down fails with host_unreachable. A command is not queued for later.
Messages and files cross links too. Mail to a host whose link is down waits on this machine, under Waiting to send in the Inbox, and goes when the link is back. See Agent Messaging.
Agents on every linked host appear on the rail and in your Inbox, and fan, worktree and start-agent can run there. See Worktrees.
Keeping hosts on one version
tuios hosts sync installs the TUIOS version of this machine on each host that runs another version. A release build sends its own release. --dev sends a build of a checkout, and --binary sends a file.
tuios hosts sync --dry-run # see what would change
tuios hosts sync --dev # install a build of this checkout on every host
tuios hosts sync build lab # only these hostsThe host needs no internet: sync sends the binary over ssh. It never uses sudo. It installs over the tuios on the host, or to ~/.local/bin/tuios when it cannot write that folder.
The daemon on a host keeps running the old version, and its sessions keep running. The row for the host gives the command that restarts it. Add --restart to restart the daemons in the same run. sync lists the sessions and the programs that a restart ends, and asks first. Without a terminal, add --yes. The sessions come back with their layouts and new shells.
See hosts sync for every flag.
Start the daemon on a host
A link reaches a host only when a TUIOS daemon runs there. When none runs, tuios hosts test build reports no_daemon and prints the command to run on the host:
build gaurav@buildbox no_daemon
The host is up and no tuios daemon is running on it.
To start the daemon, run this command on the host:
/home/gaurav/.local/bin/tuios start-server
Or start it from this machine:
tuios hosts test build --startTo start it from this machine, add --start. It runs that command over ssh and tests the host again. --dry-run shows the command and changes nothing.
tuios hosts test build --start # start the daemon on build if it does not run
tuios hosts sync build --start # install TUIOS on build if needed, then start ithosts test --start does not install TUIOS. On a host with no TUIOS, use hosts sync --start.
Your ssh agent on a host
With ssh_agent = "follow" on both machines and agent forwarding on the link, a session that you attach on a host uses the ssh agent of your client here. TUIOS does not forward the agent by default. Forward it only to a host you trust. See SSH Agent.
What another machine may do here
The [hosts] table on the machine a link arrives at also says what the machine of that name may do there. With no entry, a linked machine may list, send mail, open sessions and panes, and type into panes. Answering prompts for you (respond) is off unless you allow it:
[hosts.laptop]
allow = ["list", "mail", "open", "write", "respond"]
hold_mail = true # hold its mail to agents here in your Inbox until you pass it on
[hosts."*"] # every machine with no entry of its own
allow = ["list", "mail"]Anything not allowed is refused with forbidden. See What another machine may do here for every capability and option.