Installing Farrier
Phase 0 ships no write capability. What you get from this is a fleet that reports: inventory, systemd unit state, pending updates with security separated from the rest, and which services still hold replaced libraries. Nothing here can change anything on a host, and the parts that eventually will are refused with a distinguishable error rather than being absent.
The control plane
You need PostgreSQL 14 or newer, and one binary.
# 1. The certificate authority that issues agent certificates.
sudo farrier-server ca init --ca-dir /var/lib/farrier-server/ca
# 2. A database.
sudo -u postgres createuser farrier --pwprompt
sudo -u postgres createdb --owner farrier farrier
# 3. Run it. The schema is created on first start.
export FARRIER_DATABASE_URL='postgres://farrier:...@localhost/farrier?sslmode=disable'
export FARRIER_ADMIN_TOKEN="$(openssl rand -hex 32)"
farrier-server serve --addr :8443 --ca-dir /var/lib/farrier-server/ca
Two things about TLS are worth knowing before you reach them.
The agent protocol authenticates hosts with client certificates, which do not exist without TLS —
so a control plane with no certificate does not serve agents insecurely, it cannot serve them at all.
farrier-server serve therefore refuses to start without one, and issues one from its own CA if you do
not supply one. An enrolled agent trusts that automatically, because it is handed the CA bundle at
enrolment. A browser will not, so pass --tls-cert and --tls-key from whatever issues your public
certificates before operators use the interface in earnest.
Back up ca.key separately from the database. An attacker with both can impersonate hosts to this
control plane; an attacker with the database alone cannot. Neither lets them run code on a host: an
agent authorises a job by its class and its signature, not by who asked.
A host
# On the control plane, or through the web interface:
curl -sX POST https://farrier.example.org/api/v1/tokens \
-H "Authorization: Bearer $FARRIER_ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"label":"web tier","group":"web-prod"}'
The token comes back once and is not recoverable: only its hash is stored, so a database dump does not let its holder enrol hosts. It is single-use and expires in a day by default.
# On the host:
curl -fsSL https://farrier.tools/apt/farrier-archive-keyring.gpg \
| sudo tee /usr/share/keyrings/farrier-archive-keyring.gpg > /dev/null
curl -fsSL https://farrier.tools/apt/farrier.sources \
| sudo tee /etc/apt/sources.list.d/farrier.sources > /dev/null
sudo apt-get update && sudo apt-get install farrier-agent
sudo farrier enroll --server https://farrier.example.org --token frr_…
sudo systemctl restart farrier-agent
The .sources file uses deb822 with Signed-By: naming an explicit keyring, so the Farrier key is
trusted for the Farrier repository only. apt-key is never used: it installs a key that is trusted for
every repository on the system, which turns one compromised project into root on the machine.
What a fresh host will and will not do
Straight after installation, before you change anything:
| Reports inventory, services, pending updates and reboot state | yes |
Applies security updates on its own timer, via unattended-upgrades |
yes — and it keeps doing this if the control plane is unreachable, or if you never enrol it at all |
| Applies updates because the control plane asked | not in phase 0 |
| Restarts a service, or reboots | no, and not once phase 1 lands either, until you change the two files below |
The two files are the whole of it:
/etc/farrier/policy.toml — root-owned, a dpkg conffile, and the control plane cannot modify it.
It ships permitting security updates, no reboots, and no restartable units. Effective permission for
any job is min(what the control plane asked for, what this file allows) — never the maximum. Check an
edit with farrier-agent policy check before restarting anything: a file that does not parse makes the
host refuse all privileged work rather than fall back to a default, which is deliberate and is a
miserable way to discover a typo.
/etc/farrier/trusted-signers — root-owned, a dpkg conffile, and empty. Every destructive
operation needs a signature from a key listed here, and the control plane holds none of them. Generate
one on your own machine:
farrier key generate --out ~/.config/farrier/signing.key --id ops-laptop
# prints the line to paste into /etc/farrier/trusted-signers on the hosts that key may act on
A package upgrade never replaces either file. There is a test in testfleet/ that asserts it, and for
trusted-signers that is a security test rather than a convenience one.
Stopping a host
sudo touch /etc/farrier/paused # refuse everything, immediately
sudo systemctl stop farrier-agent # or just stop it
Neither can be undone by the control plane. There is deliberately no agent.resume operation: an off
switch that something else can flip back on is not an off switch. The host keeps patching from its
local policy either way — a paused host should not become an unpatched host.
Upgrading the agent
Through APT, on the host's own schedule, like any other package. There is no agent.updateFromURL and
there never will be: a self-update from a control-plane-supplied URL replaces the binary that enforces
every other rule.
Building from source
make build # all binaries into ./dist
make web # the Angular application, embedded into farrier-server
make deb # the farrier-agent package
make test lint guarantee
Go 1.26 or newer. make deb needs nfpm; make web needs Node
and pnpm.
Where to look when something is wrong
journalctl -u farrier-agent -n 200 # the agent logs JSON; paste it whole rather than summarising
farrier-agent policy check # most "it refused" reports are the policy working correctly
farrier-server catalogue # everything the control plane can ask a host to do
The last one is worth running once even when nothing is wrong. It prints the complete, closed set of operations plus the list this project has permanently refused to implement, and it is the fastest way to check that the claim on the front page is true of the binary you actually installed.