CLI Reference
setup
Prepares a fresh host so odysseus can deploy to it as a non-root user.
Synopsis
odysseus setup [--config FILE] [--as USER] [--key PATH]
For every host in the config, setup creates the user ssh.user names, adds it to the docker group, installs your public key, creates its state directory, and installs Docker if the host doesn't have it. Then it opens a second SSH connection as the new user and proves Docker and the state directory both work from there — it reports success only if that passes. A failure at this last step leaves the bootstrap path, and everything already prepared, untouched: the host stays reachable and there's a way back in to try again.
setup only ever adds access. It never modifies root's configuration or the bootstrap user's, and it does not touch the firewall, swap, sshd_config, or unattended-upgrades.
For one host, not a fleet
setup gets a single host ready for odysseus to deploy to. Preparing servers at scale — many hosts, built from scratch — belongs to OpenTofu, Terraform, or an equivalent tool that can do it declaratively. setup is not a substitute for that, only for getting one host going: it opens no ports and touches no firewall, swap, sshd_config, or unattended-upgrades.
Options
--config FILE
Path to configuration file. Default: deploy.yml
--as USER
Identity to connect as while preparing the host. Default: ubuntu — the user Ubuntu's LTS cloud images ship, with passwordless sudo already configured.
odysseus setup --as root
odysseus setup --as deploy
--as root connects as root and needs no sudo at all. Any other identity needs passwordless sudo: odysseus runs non-interactively and cannot answer a password prompt, so a host without it is refused before anything is changed. If the default identity can't connect, the error names both flags that might fix it — --as root if root SSH is enabled, or --as USER for whichever identity can log in.
--as names the identity setup connects as while preparing the host. It's not the same as ssh.user in deploy.yml, which is the identity setup creates — see below.
--key PATH
Install this public key instead of the one resolved from ssh.keys. Repeatable.
odysseus setup --key ~/.ssh/deploy_key.pub
A --key path that doesn't resolve to a valid public key refuses, naming the path, rather than falling back to ssh.keys — installing a different key than the one you asked for would be worse than refusing, because you'd believe the named key was authorized when it wasn't.
What it reads
setup reads no new configuration keys:
| Value | Source |
|---|---|
| User to create | ssh.user |
| Hosts | servers.*.hosts |
| Public key | ssh.keys |
Each ssh.keys entry is a private key path; the key installed is its .pub sibling if that sibling has a valid public-key line in it, otherwise it's derived from the private key with ssh-keygen -y. A sibling that has content but nothing in it validates — for example a restricted command="..." ssh-ed25519 ... entry — refuses rather than being derived around, for the same reason --key refuses: installing a different, less restricted key than the one on disk is a silent substitution, not a fallback. Only an empty or whitespace-only .pub sibling falls through to deriving the key from its private half.
What it refuses
setup stops before changing anything, rather than leaving a host half-prepared, in these cases:
ssh.user: root. Setup exists to enable non-root deploys; root needs no user, no group, and noauthorized_keysof its own, and deploying as root already works with no setup at all. Changessh.userin deploy.yml to the non-root user you want to deploy as.- No passwordless sudo, for any
--asidentity other than root. Checked with a single harmless probe before the first real change is made. - A
--keythat doesn't resolve to a valid public key. Refused by naming the path, not by falling back to another key. - An unsupported distro. Only Ubuntu 24.04 and 26.04 are known; anything else is refused by name. This is stricter than
doctor, which only warns on an unsupported distro — a deploy just needs a working Docker daemon and doesn't care which distro provides it, but setup changes the host, so it stops at the first thing it can't verify rather than proceeding on a guess.
Docker install
If docker info doesn't answer, setup installs Docker from Docker's own official apt repository, following Docker's published procedure for Ubuntu — the keyring, the sources line, the apt install. It installs four packages: docker-ce, docker-ce-cli, containerd.io and docker-buildx-plugin, deliberately without docker-compose-plugin, which Docker's instructions do include. Nothing in odysseus invokes docker compose, so setup does not put it on your host — if you want it, install it yourself. Preparing the repository also installs ca-certificates and curl if they are missing. Success is decided by docker info answering after the install runs, not by apt exiting zero — apt exiting 0 only says a package was unpacked.
- The keyring and the apt sources file are replaced whole on every run, never appended, so a run interrupted partway through leaves a stale file the next run overwrites rather than a corrupt one with the repository listed twice.
- apt runs non-interactively with a 300-second wait for the dpkg lock — long enough to outlast cloud-init or unattended-upgrades on a host that's only minutes old — and a timeout reports the failure loudly, naming the process holding the lock when
fuserandpsare available to identify it. - The GPG key's fingerprint is deliberately not pinned. Docker's own instructions trust TLS rather than pin it; pinning here would turn Docker's routine key rotation into an outage for everyone running this command.
- None of this repairs an apt or dpkg state setup didn't create — a host with a broken apt is reported, not fixed.
A second run against an already-prepared host issues no apt command at all: the docker info check that opens the step already succeeds, so the install is skipped entirely.
Output
Each step reports :ok, :changed or :fail, rendered distinctly so a re-run shows what it actually did versus what was already correct. Running setup twice against a prepared host reports :ok for every step and changes nothing.
Odysseus Setup
Service: myapp
Bootstrap identity: ubuntu
app.example.com
escalation: passwordless sudo confirmed
distro: ubuntu 24.04
docker: installed docker 27.3.1
user: created deploy, home /home/deploy
group: added deploy to the docker group
keys: added 1 key(s)
state_dir: created /home/deploy/.odysseus
self_test: docker and /home/deploy/.odysseus reachable as deploy
Hosts are ready.
A :fail on any step halts that host's sequence — nothing after a failed gate (an unsupported distro, a missing daemon) is safe to attempt — and sets a non-zero exit once every host has been tried. A :fail on one host never stops setup from continuing to the next.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Every host ready, or ready with warnings |
| 1 | At least one step failed on at least one host |
Related commands
- deploy - Deploy to a host once it's ready