# Setting up a machine: `ferry fleet setup` A fleet only works if every machine is the same shape and each one does one job. This is the page for putting a machine there, and for what to run when it is not. The layout it builds is in [LAYOUT.md](LAYOUT.md); the worker it starts is in [FLEET_WORKER.md](FLEET_WORKER.md). The rule behind all of it: Ferryman works by fixed structure, not by agents remembering paths or steps. Every machine has the same layout, one command puts it there, and `doctor` and the watchdog check it and repair what is safe. Anything that needs the master's signature is printed for the master to run, never done for them. ## The short version On each machine, once: ```sh ferry fleet setup --dry-run # read what it would do; nothing is written ferry fleet setup --service # do it ``` Then run the lines it prints under "run these as the project master", on the machine that holds the master's key. ## What `ferry fleet setup` does 1. **Layout.** Makes the root's folders (`projects/`, `secrets/`, `logs/` in layout 2; `comms/`, `repos/`, `work/` in layout 1) (`--root ` makes the root first when there is none). 2. **Settings.** `--remote-template` writes where clones come from to `/ferry.toml`; `--exclude ` keeps a project out of the swarm on this machine only. Both are optional, both are local. 3. **Clones.** Runs `ferry repos sync`: every live project gets `repos/`, attached to its channel. A person's working copy is not touched, and is remembered in the manifest as a `human_checkouts` entry. 4. **Identity.** Picks the machine's one worker identity: `--identity` if given, else the identity a worker here already runs as, else the only `*-swarm` identity found, else `ichabod--swarm`. Its `agent.toml` and key live in a managed clone (`--home ` chooses which). A key already on this machine is copied, never regenerated and never moved: a second key under a name the rosters know reads as an impostor everywhere. 5. **Service (only with `--service`).** Writes one service that runs `ferry agent run --all-projects --workspace ` and turns the others off. See below. 6. **The master's lines.** Prints, exactly, the signed commands only the project master can run: enrolling the identity (`team approve`), revoking the ones it replaces (`team revoke --name`), releasing their stale claims (`order release`), taking projects out of the swarm (`root swarm off`) and re-offering orders addressed to a retired identity (`order reoffer`). It never signs. Without `--service` nothing is installed or turned off: it is the layout, the clones, the identity and the printed lines. `--dry-run` shows all of it and writes nothing (it does not clone either; it says what it would clone). Safe to run again. It reports "already" for what is there and changes nothing. ### Flags | Flag | Meaning | |---|---| | `--identity NAME` | The worker identity for this machine | | `--home PROJECT` | The project whose managed clone holds the worker's `agent.toml` and key | | `--retire NAME` | An identity this machine used to run as; prints the master's revoke and release lines for it. Repeatable | | `--exclude PROJECT` | Keep this project out of the swarm on this machine. Repeatable | | `--remote-template URL` | Where clones come from, with `{project}` for the id | | `--root FOLDER` | Make the ferry root here when there is none | | `--dry-run` | Say everything, write nothing | | `--service` | Write and enable the one service, and turn the others off | | `--no-syncthing` | Do not run Syncthing as a service of its own | | `--no-fetch` | No network. Clones and attaches still happen | ## Where clones come from (GitHub, SovGit) A project's remote is a per-machine setting in `/ferry.toml`, not something in a channel: the channel is shared by every machine, and a machine without `git-remote-sov` must not be told to use `sov://`. (A master-signed `repo_remote` would put one machine's transport in every machine's face, and a forged or stale one is a way to point every clone at someone else's server; the local file has neither problem.) ```toml [git] remote_template = "https://github.com/estejosh/{project}.git" # the default; GitHub # fallback_template = "https://github.com/estejosh/{project}.git" # tried when the first cannot be used [git.remotes] # One project that is on SovGit. git speaks sov:// through the helper `git-remote-sov`, which must be on PATH. quantly = "sov://my-bucket/quantly" ``` `ferry repos sync` tries, in order: the project's `[git.remotes]` entry, `remote_template`, `fallback_template`, then the `origin` of a checkout already on the machine (credentials stripped). A `sov://` remote whose helper is not installed is skipped with the reason in the row (`skipped sov://bucket/repo: git needs the program git-remote-sov for sov:// remotes and it is not installed`) and the next candidate is tried; if nothing else is usable the project is a failed row that says why, and nothing is written. The same check runs before every fetch: a clone whose `origin` needs a missing helper is reported as `not fetched - ...`, never as an error. A remote that runs a command (`ext::`, `fd::`) is always refused. Ferryman has no SovGit code; it calls `git`, and git calls the helper. `ferry repos sync` also moves Ferryman's own clone forward: after a fetch it fast-forwards `repos/` when, and only when, the clone was made by `repos sync`, has no changes to tracked files, and is strictly behind its upstream. A diverged or edited clone is fetched and left alone. A person's checkout is never moved. ## Layout 2 and moving to it A new root is layout 2: `projects//{repo,comms,handins,work,tmp,.env}`, `secrets/` and `logs/` (docs/LAYOUT.md, ADR 0024). `ferry fleet setup` makes the folders of whichever layout the root is in and clones into `projects//repo` in layout 2. `ferry enable` files a new project's channel at `projects//comms`; pass `--channel`, `--repo`, `--handins` or `--env` to place any of them yourself, and Ferryman records the project as custom and leaves it where you put it. An existing machine moves with one command, once per machine, with the worker stopped: ```sh ferry root migrate # dry run: says what would move and what blocks it; writes nothing ferry root migrate --apply # does it, logs to /logs/migrate-.md ferry agent run --all-projects # start the worker again; the iroh lane re-reads bridge.toml ``` A project with a running worker, a worker lock or a live worktree is refused, and the line names what blocks it. Custom projects and your own checkouts are listed as left in place. Nothing is deleted: superseded copies go to `/_to_delete/`. Where Ferryman's Syncthing has a project's folder, its path is changed and the folder id kept, so no other machine re-pairs; migrate the fleet together, or pause the folder on the machines that have not moved yet. Run it on grouchly the same way (root `/mnt/nvme-storage/repos/ferry`). Then `ferry access show` lists each agent's level and `ferry access set worker|member|orchestrator` (the master's command) changes one. ## The fleet folder (0.6.1) The fleet has one folder of its own, `/fleet/`, a synced channel (Syncthing folder id `fleet-ferryman`, carried by the iroh lane too) that holds what is true of the whole fleet: where each machine keeps each project, what each machine runs, notices, fleet orders and release approvals (ADR 0026, docs/LAYOUT.md). Setting it up is the master's, once: ```sh ferry fleet init # on the master's machine; makes /fleet, registers it, admits this machine ferry fleet admit --machine # for each other machine, once it has started a worker ferry fleet machines # who is publishing: version, layout, projects, last seen ferry fleet notices # layout changes, deprecations, new releases ferry fleet notice update-available "0.6.2 is out" --version 0.6.2 --severity warning ferry fleet order restart-worker --machine grouchly # a worker acts only on the master's signature ``` A worker on another machine puts itself on the fleet roster when it starts and says so if the master has not admitted it yet (`fleet folder: not publishing yet - ...`). Until it is admitted it can read the folder and write nothing. Once it is, it publishes its machine record and its directory entries each pass; `ferry enable`, `ferry root adopt`, `ferry root data` and `ferry root migrate --apply` publish at once. After a migrate the directory is updated first, then a signed `layout-change` notice lists every old and new path. `ferry where ` answers where a project is, on this machine or another, and nothing in a script needs to hardcode a path. `ferry fleet write-hints` puts a "ask `ferry where`" block into each clone's CLAUDE.md and AGENTS.md; it shows what it would do unless you pass `--apply`, and it never commits. The folder is additive. A machine on an older release never reads it and loses nothing. Release requests are written there when it exists and also, while older machines are about, to the `ferryman` project's channel, and both places are read. A worker does not update itself because a notice says a release is out: it says so, and follows the update policy it has (`FERRYMAN_NO_AUTO_UPDATE`). A fleet `update` order from the master does update it, unless that variable is set; a `migrate` order is reported and left to a person, because a migrate refuses to run while a worker does. ## One worker per machine Two workers on one machine claim the same orders, burn the same engines and race each other's worktrees. `ferry fleet setup --service` leaves exactly one: - **Linux:** a systemd user unit, `ferryman-worker.service`, with `Restart=on-failure`, `MemoryHigh=70%`, `MemoryMax=85%`, `CPUQuota=400%`, a task limit, and `EnvironmentFile=-` pointing at `service/worker.env`. - **Windows:** a Scheduled Task, `Ferryman Worker`, that runs at logon with restart on failure and no time limit, and runs a PowerShell wrapper (`service/ferryman-worker.ps1`). The wrapper loads `service/worker.env` (`KEY=VALUE` per line) and runs `ferry agent run --all-projects --workspace `. - **Environment.** Keys the worker needs go in `service/worker.env`, never inline in a unit, a task or a wrapper. The file is made once and never overwritten. - **The others are turned off, not deleted.** Every other ferry worker unit or task is disabled, and a copy is saved first in `service/backup/