Config as code with agent.yaml#
You will end up with a file that says what the router should look like, and a command that tells you the difference between the file and reality. Keep the file in version control and you have config as code, with the same review and history as anything else you keep there.
Before you start#
- Shell access to the router, or an API token with the operator role.
- A licence if you are going to apply anything. Planning is a read.
The format is the configuration tree#
service:
dns:
forwarding:
cache-size: 10000
ssh:
qos:
interface:
eth1:
egress: WAN-OUTThe format is sniffed, so agent.yaml is YAML and an inline document posted to the API is
JSON. Both describe the same tree.
A key with no value#
ssh: in YAML, or {} in JSON, is how the file says this node exists. What that means
depends on the router, so the engine reads it against live state:
| Live state | What the engine does |
|---|---|
| The node is missing | Plans a bare set, and the plan says so in a warning |
| The node already has children | Satisfied; nothing to do |
| The node holds a value | A contradiction; the plan refuses to run, naming the node and the value the router holds |
An explicit empty string (host-name: "") is refused where the document is read, before any
diffing.
Step 1 — Plan#
wheelhouse-agent plan --file /config/wheelhouse/agent.yamlset service dns forwarding cache-size 10000
set service ssh
warning: service.ssh: planned as a bare set (a node with no value)
2 change(s) neededThe exit code is the interface. plan exits 2 when it found a difference, the way
diff -q does, and 0 when the router already matches. That drops into a shell test or a CI
gate without parsing any text.
wheelhouse-agent plan --file agent.yaml || echo "the router does not match the file"Two more flags:
wheelhouse-agent plan --file agent.yaml --json # for scripts
wheelhouse-agent plan --file agent.yaml --full # also delete what the file omits--full is the dangerous one. Without it the file is a set of assertions: what it names
must be so, and everything else is left alone. With it the file is the whole truth, and
anything on the router the file does not mention is deleted. Read a --full plan very
carefully the first time.
Step 2 — Apply#
apply stages by default and only commits when told to — the same rule the UI obeys.
# Stage into a running agent's Commit Bar; a human still commits.
wheelhouse-agent apply --file agent.yaml --agent-url https://127.0.0.1:8443
# Or commit, protected by VyOS' own commit-confirm.
wheelhouse-agent apply --file agent.yaml --commit --confirm-minutes 2Two refusals worth knowing:
applyrefuses to stage without--agent-url. The staging area belongs to the running agent, not to a process that is about to exit.applyrefuses--commitwithout a confirm window. A committing automation without a rollback is how a fleet goes dark.
Staging through the agent authenticates with the break-glass token
(--admin-token or --admin-token-file), and nothing creates that file by default —
create one deliberately.
Step 3 — Or do it over the API#
POST /api/reconcile takes a desired document and returns the operations. apply: true
stages the difference; the Commit Bar still commits.
R=https://<router>:8443
T=wh_...
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
-d '{"desired_state":{"service":{"dns":{"forwarding":{"cache-size":"10000"}}}},"apply":false}' \
"$R/api/reconcile"Reconcile → the page does the same thing with a text box: paste or point at a document, read the plan, stage the difference.
GET /api/reconcile/file diffs the agent's own file against live. Its ?file= parameter
takes a .yaml, .yml or .json name inside the data directory, needs the operator
role, and answers 400 if the file plans an empty value.
Step 4 — Keep the file somewhere#
Version control, and the same review as your other infrastructure. The file is also something a backup has to contain — Take a backup that is actually complete names it.
Check it worked#
wheelhouse-agent plan --file /config/wheelhouse/agent.yaml
echo $? # 0 means the router matches the fileThen confirm the change is really on the router:
show configuration commands | match cache-sizeUndoing it#
An applied change is a commit like any other — roll it back. The file itself is a file; edit it, or the router will drift back the next time somebody applies it.
See also#
- Keep the router aligned on a timer
- Alert on drift
- Stage and commit from a script
- Desired state and The desired-state file
Checked against agent/desired.go ·
agent/reconcile.go ·
docs/deploy.md ·
docs/adr/001-desired-state-file.md ·
ui/src/pages/Reconcile.tsx