# Adding a machine in one command

URL: https://tuios.dev/blog/adding-a-machine-in-one-command

> To link a second machine, you had to know four things first, and a failed link said only that the host did not answer. tuios hosts setup now reads the machine over ssh, shows a plan, installs and starts tuios, and names each ssh failure with the one command that fixes it. The app has the same steps as a sheet, and offers them when a pane logs in with ssh.

tuios can link machines: a session on your build box shows up in the rail, its
agents land in your Inbox, and `fan`, `start-agent` and `cp` work across. It
was also one of the hardest parts to start using, because a link needs four
things, and nothing told you about them:

1. That `[hosts]` exists at all.
2. That the other machine needs tuios, and a tuios daemon running.
3. That ssh must log in with a key, because the link runs ssh with `BatchMode`
   on and can never type a password.
4. That the host key must already be known, for the same reason.

Miss one, and the link said "The host did not answer." That was the message
for an unknown host key, a refused key, a name that does not resolve, a closed
port and a machine that is off. Each one has a different fix, and the only way
to find out which was to run ssh by hand.

## One command, with a plan first

`tuios hosts setup` takes the machine as ssh takes it, `user@machine` or a name
from your ssh config, and does the steps. It reads the machine over ssh before
it changes anything there. Here is a real dry run, against a test machine in a
container that already has tuios, with no daemon running:

```
$ tuios hosts setup buildbox --name build --install copy --dry-run
Debian GNU/Linux 12 (bookworm), arm64. apt, systemd. tuios dev is installed on build. Its daemon does not run.
This changed nothing.

The plan. Nothing runs.
   1  build      /usr/local/bin/tuios start-server
   2  here       write [hosts.build] addr = "buildbox" to /home/me/.config/tuios/config.toml
   3  here       Test the link to build.

Run it again without --dry-run, with --yes, to do it.
```

The plan has only the steps this machine needs. A machine without tuios gets an
install step first, and `--install` says how: a copy of the tuios you run, the
release of your version, the package manager there, or the install script.
`--service` adds a step that starts the daemon at boot, and `--both-ways` adds
this machine on the other one too, once that machine has tested its own way
back.

The order matters. `[hosts.build]` is written only after the daemon there
answers, and a link that does not come up is removed again. So a setup that
fails halfway does not leave a half-configured host in your config.

## A failure with its fix

The other half of the work was in the link itself. When ssh gives up (exit 255),
tuios now reads what ssh said and puts it in one of six classes: a new host
key, a changed host key, a refused key, a name that does not resolve, a refused
connection, and a timeout. Each class has its own words, and two of them come
with the exact command. From the same container, with a user that has no key
there (the empty plan lines are cut, and the last line is wrapped):

```
$ tuios hosts setup nobody@buildbox --dry-run
buildbox refused your ssh key.
This changed nothing.
...
buildbox refused your ssh key. Copy your ssh key to buildbox with this
command. It asks for your password on buildbox one time: ssh-copy-id nobody@buildbox
```

`tuios hosts`, `hosts test` and `hosts sync` print the same fix, and their JSON
has `failure`, `fix` and `fix_command`, so an agent can show it to you.

Two things it never does: it never takes a password, and it never accepts a
changed host key. Those stay with you, and the setup stops with the command to
run (exit 3). Without a terminal, every choice comes from a flag, and a missing
one stops the setup before it changes anything (exit 2). That is the form an
agent or a script uses.

## The same steps, three front ends

On a terminal, `tuios hosts setup` asks for each choice the flags leave open.
`--tailnet` shows the machines on your tailnet as a checklist: tick the ones
you want, give them one set of answers, and up to four are set up at once.

The app has it too. "Add a machine" in the palette, or on the Hosts page of
Settings, opens the setup as a sheet in five steps. And when a pane logs in to
a machine with ssh or mosh, and no `[hosts]` entry reaches that machine, the
dock asks: "buildbox is not a machine here. Add it?". Add opens a pane that
runs the setup for it. Never remembers your answer.

All three draw the same steps. The setup engine holds the flow, the text of
each step and each question, and the front ends only draw it. At first the
terminal setup and the app's sheet each kept their own copy of the steps and
their words, and the second front end was the moment to make it one.

## What I got wrong along the way

A tuios built from a commit has a version like
`v0.12.2-0.20261105101010-abcdef123456`, and the first version of the setup
took that for a release. So on a dev build it offered to install a release
that does not exist. A dev build now offers a copy of itself or the
package manager, and never a release.

The commands are on the [Remote Hosts](https://tuios.dev/docs/remote-hosts#hosts-setup) page.
