Host Setup
Bootstrap catch, configure Tailscale access, choose storage, upgrade hosts, and check container or VM support.
Host setup starts over SSH and then moves to Tailscale. yeet init installs
catch on the machine host, enrolls catch in your tailnet, prepares storage, and
checks optional host features such as Docker, ZFS, and VM support.
| Question | Start here |
|---|---|
| Which host name goes where? | Host names |
| What is the default catch hostname? | Host names |
| How do I install release or nightly yeet? | CLI install |
| What Tailscale policy and OAuth setup are required? | Tailscale access |
What does yeet init prompt for? | Bootstrap catch |
| When does Catch install Bubblewrap? | Native sandbox dependency |
| How do I upgrade yeet and catch? | Upgrade yeet and catch |
| Can this host run containers, VMs, or ZFS roots? | Host requirements |
Yeet uses two host names during setup:
<machine-host>is the SSH target foryeet init, such asroot@server.example.<catch-host>is the Tailscale hostname yeet uses after catch starts.
Use the machine host for installation. Use the catch host with CATCH_HOST,
--host, and <svc>@<catch-host> after installation. The machine name answers
"how do I SSH there?" The catch name answers "which daemon am I operating?"
The default catch hostname is catch. That is fine for one host. For more than
one catch host, choose a name before setup:
yeet --host=morpheus-catch init root@<machine-host>
That command still installs over SSH to <machine-host>. The --host value is
the catch hostname yeet asks Tailscale to assign. Use that same catch host for
normal commands:
yeet --host=morpheus-catch status
yeet run <svc>@morpheus-catch ./compose.yml
To make that catch host the local default:
yeet config --host=morpheus-catch
During setup, SSH gets catch onto the machine. After that, Tailscale is the control path. Mixing those names up is the fastest way to make a working host look unreachable.
Release install:
curl -fsSL https://yeetrun.com/install.sh | sh
Nightly build:
curl -fsSL https://yeetrun.com/install.sh | sh -s -- --nightly
Confirm the CLI is available:
yeet --help
Catch joins your tailnet as a tagged device, such as tag:catch. This is
required before the first yeet init and keeps catch tied to server-side
policy.
Your tailnet policy must also grant the setup user access to catch on TCP port
41548 with the yeetrun.com/app/yeet app permissions read, manage, and
ssh. First setup requires all three. Later, you can split access into
separate read, manage, and shell roles.
Before you run yeet init, create an OAuth credential in the Tailscale admin
console:
Trust credentials -> Credential -> OAuth
You have two choices:
- Simple broad setup: choose
All - Read & Writeif you are comfortable giving the credential broad Tailscale API access. - Least-privilege setup: choose custom scopes with Auth Keys write
(
auth_keys) and select the tag the credential may assign. For catch-only installs, selecttag:catchdirectly. If you plan to use service--net=tslater, use an owner tag such astag:yeetthat ownstag:catchand service tags such astag:app.
Keep the tskey-client-... OAuth client secret ready. Interactive yeet init
asks for it during first setup. This secret creates the catch node credential,
so do not lose track of where it came from. Follow
Tailscale Setup for the
minimal policy snippet, and use
Tailscale Access Grants when you are
ready to split roles.
Before deploying a service with --net=ts, read
Networking. Most Tailscale-exposed services should
use svc,ts so they keep yeet DNS and ordinary outbound internet through the
catch host.
Normal interactive setup runs from your workstation against the SSH machine host:
yeet init root@<machine-host>
With no saved or explicit catch host, this uses the Tailscale hostname catch.
Use yeet --host=<catch-host> init root@<machine-host> when adding another
catch host to the same tailnet.
Choose the workspace behavior explicitly when you do not want the interactive default:
yeet init --workspace ~/yeet-services root@<machine-host>
yeet init --no-workspace root@<machine-host>
--workspace creates the directory when needed and registers it for the
selected catch host. --no-workspace skips workspace setup entirely.
In non-interactive runs, workspace setup only happens when --workspace is
provided.
What changes by case:
| Case | What yeet init does |
|---|---|
| First catch enrollment | Prompts for the Tailscale OAuth client secret. |
| Fresh storage setup | Uses /var/lib/yeet for Catch state, with services under /var/lib/yeet/services. |
| Native service account | Creates the unprivileged yeet-svc account when it is not already present. |
| Existing catch install | Upgrades catch. An exact recorded legacy layout can be offered the guided move described below. |
| Missing Debian/Ubuntu packages | Asks before installing supported dependencies such as Docker or VM tools. |
| Non-root SSH user | Runs the remote install with sudo. |
| VM LAN bridge setup | Can prepare the host bridge when you plan to use VM --net=lan. |
The data directory is where live host state begins to accumulate, so treat it as infrastructure, not cache.
New native binaries, scripts, and cron jobs run as the shared yeet-svc
account by default. Yeet lets the host allocate that account's UID and GID; it
does not assume that UID 1000 is available or has the right meaning. Existing
native services are not changed during init or upgrade. Migrate one explicitly
with yeet service set <svc> --run-as=yeet-svc, or select an operator-created
account with --run-as=<user>[:<group>].
Only the native workload unit drops privilege. Catch, Tailscale, VM and networking helpers, and storage migration helpers continue to run with the host privileges they require. Docker users remain controlled by Compose, and VM application users remain controlled inside the guest.
Fresh native binaries, shebang scripts, and scheduled jobs run through
Bubblewrap. Catch installs and functionally probes /usr/bin/bwrap without an
extra prompt at three intent boundaries:
- A genuinely fresh Catch installation.
- A new native run or another native activation whose resulting sandbox state
is
on. yeet service setwhen the resulting sandbox state ison.
Installing or upgrading Yeet does not install Bubblewrap. Upgrading an existing
Catch installation does not install it either. Existing legacy services,
services explicitly set to off, and exposure edits that explicitly repeat
--sandbox=off in the same command do not require it. An exposure-only edit of
an off service results in on, so Catch installs and probes Bubblewrap before
activation. See Native Sandboxing for the
exact dormant-edit command.
Catch verifies ownership and permissions on the binary and runs a functional
namespace probe as a fixed non-root identity. On compatible Ubuntu hosts where
AppArmor restricts unprivileged user namespaces, Catch installs and loads the
exact Yeet-owned profile at /etc/apparmor.d/yeet-bwrap, then repeats the
probe. Debian and hosts without that restriction need only the Bubblewrap
package. A different file at the managed path is preserved and reported.
Catch never disables AppArmor or relaxes host-wide user-namespace policy. See Native sandbox activation fails for package and policy checks.
The service root defaults to <data-dir>/services. Pass --data-dir or
--services-root when the host needs a different filesystem path. Explicit
custom roots are preserved during init, upgrades, and guided migrations. Put a
custom service root below host-controlled directories: each parent must be
root-owned and not group- or world-writable. /srv/apps and ZFS mountpoints are
appropriate; a workload-owned home directory is rejected.
If the host has ZFS, setup can use datasets and create missing child datasets when the parent exists. Use this only when you want the host storage on ZFS:
yeet init --zfs --data-dir=flash/yeet/data --services-root=flash/yeet/services root@<machine-host>
ZFS datasets are not copied or deleted implicitly. A custom dataset and any nested datasets remain where they are unless you manage them explicitly.
If you plan to run VMs with --net=lan, answer yes when init asks to prepare
the host bridge. If you skip it, the first yeet run <vm> ... --net=lan
prompts before creating the VM service.
Many VPS providers do not expose nested virtualization. That is a host constraint. On hosts that are not VM capable, yeet warns and containers, binaries, scripts, and cron jobs still work.
After bootstrap, confirm yeet can reach catch:
yeet version
yeet status
If this is not your default catch host, pass it explicitly:
yeet --host=<catch-host> status
Then run the First-Run Validation playbook to test the payload types this host should support.
Check the local CLI and catch hosts:
yeet upgrade check
Upgrade from verified GitHub release assets:
yeet upgrade
When you run from a service workspace with yeet.toml, yeet upgrade includes
all project catch hosts plus the default catch host. Use --host=<catch-host>
only when you want to upgrade one catch host. Otherwise, the project file is the
source of truth and yeet follows it.
yeet upgrade --host=<catch-host>
To reinstall the latest public release even when a component already looks current, newer, or locally built:
yeet upgrade --force
To install the latest nightly release:
yeet upgrade --nightly
To install a specific public release, select the tag:
yeet upgrade --version v0.6.1 --force
--nightly and --version select different targets, so use one of them per command.
An interactive init or upgrade can offer to move the recorded legacy
home-directory layout to /var/lib/yeet. Accepting the prompt moves Catch state
and eligible service directories, restarts Catch, and validates the new state.
After validation, it removes the exact legacy tree you approved in the prompt.
When init could not prompt, run the migration explicitly from your workstation:
yeet init root@host
# Explicit migration when init could not prompt:
yeet host set \
--data-dir=/var/lib/yeet \
--services-root=/var/lib/yeet/services \
--migrate-services=all \
--yes
yeet host cleanup --from=/root/yeet-data --yes
Run yeet host cleanup only after yeet host set reconnects and reports that
the new state is valid. Cleanup refuses arbitrary paths. It accepts only the
exact journaled legacy source and revalidates the active Catch and service state
before deletion. If only deletion fails, rerun the same cleanup command; the
pending cleanup resumes safely.
Base host:
- Linux with systemd.
- SSH access from your workstation.
- A catch tag allowed by your Tailscale policy.
Native payloads:
- A compatible Bubblewrap at the trusted
/usr/bin/bwrappath. - Kernel and host security policy that allow the functional user-namespace probe. Catch installs the package automatically on supported apt-managed Debian/Ubuntu hosts at the intent boundaries above.
Container payloads:
- Docker on the host.
- Docker configured for local image pushes.
yeet inithandles this during catch install.
VM payloads:
- x86_64/amd64 Linux.
- KVM at
/dev/kvm. - TUN/TAP at
/dev/net/tun. qemu-img,zstd,e2fsck,resize2fs,mount,umount, andip.
ZFS is optional. Use it when you want dataset-backed host storage, service roots, snapshots, or fast VM disk clones. Skip it when a normal filesystem path is enough.
See Data Layout and ZFS.
Use yeet init unless you have a specific reason to install catch manually.
Manual install has fewer guardrails.
If you do install catch yourself, see the
catch reference for catch flags such as --data-dir
and --tsnet-host.