Stage and commit from a script#
You will end up with a script that changes the router's configuration the same way a person does: stage the operations, look at what is staged, commit with a confirm window, and confirm once the router still answers.
The staging area is shared state. A browser tab, this script and the reconcile loop all put operations in the same working set, and one Commit Bar commits all of it. That is worth designing around rather than around.
Before you start#
- An API token with the operator role — Issue a token for automation.
- A licence. Every one of these calls answers 402 without one.
Step 1 — Stage the operations#
POST /api/stage takes a JSON array of operations.
R=https://<router>:8443
T=wh_...
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
-d '[
{"op":"set","path":["service","dns","forwarding","cache-size","20000"]},
{"op":"set","path":["system","host-name","edge"]}
]' "$R/api/stage"The response is the whole working set, not just what you added:
{
"staged": 2,
"ops": [ … ],
"commands": ["set service dns forwarding cache-size 20000", "set system host-name edge"]
}That is deliberate: the queue is shared, so a client that only appended locally could un-stage by an index that meant a different operation on the agent.
The agent refuses two things here, while the request can still say why:
- An op that is not
setordelete. The Commit Bar renders exactly those; anything else would render dishonestly. - A path with no elements, or an empty element in it.
description ''would otherwise fail later at commit, with an error that does not name this request.
Step 2 — Read what is staged#
curl -sk -H "Authorization: Bearer $T" "$R/api/staged"Both the operation list and the rendered CLI form come back. Log the commands array —
that is the human-readable record of what your script is about to do.
To remove one:
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
-d '{"index":1}' "$R/api/stage/remove"To clear it all:
curl -sk -X POST -H "Authorization: Bearer $T" "$R/api/discard"Step 3 — Commit with a confirm window#
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
-d '{"confirm_minutes":2}' "$R/api/commit"This is VyOS' native commit-confirm. If no confirmation arrives inside the window, the router reboots into the previous configuration — that reboot is how VyOS implements the rollback.
Use it for any change that can cut off the caller: uplink addressing, firewall rules, NAT, the SSH or API services. In a script, use it for everything; the cost is two minutes of patience and the benefit is that a bad change undoes itself.
Step 4 — Confirm, after checking#
# something that proves the router is still doing its job
curl -sfk -H "Authorization: Bearer $T" "$R/api/system" >/dev/null || exit 1
curl -sk -X POST -H "Authorization: Bearer $T" "$R/api/commit/confirm"Do the check through the path the change could have broken. Confirming from a shell on the router itself proves nothing about whether the LAN can still reach it.
A whole script#
#!/bin/sh
set -eu
R=${R:?router URL}
T=${T:?token}
api() { curl -sfk -H "Authorization: Bearer $T" "$@"; }
# Refuse to run if somebody else has something staged.
staged=$(api "$R/api/staged" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("staged",0))')
[ "$staged" = "0" ] || { echo "working set is not empty ($staged ops); refusing" >&2; exit 1; }
api -X POST -H 'Content-Type: application/json' \
-d @ops.json "$R/api/stage" | python3 -m json.tool
api -X POST -H 'Content-Type: application/json' \
-d '{"confirm_minutes":2}' "$R/api/commit"
sleep 5
api "$R/api/system" > /dev/null
api -X POST "$R/api/commit/confirm"
echo "committed and confirmed"The other write endpoints#
| Endpoint | Does |
|---|---|
POST /api/configure | Direct configure, bypassing staging. No diff, no review. Use it only where you have already decided, and prefer staging where a person might want to look. |
POST /api/rollback | Restore an archived revision. {"revision":1} undoes the last commit — Undo the last commit. |
POST /api/config/save | Write the running configuration to the boot configuration |
POST /api/config/load | Load a configuration file that is already on the router. Commits immediately, with no preview — Restore a configuration. |
POST /api/reconcile | Diff a desired-state document; apply:true stages the difference — Config as code |
Check it worked#
curl -sk -H "Authorization: Bearer $T" "$R/api/audit?limit=10"Every commit is audited with the actor, the source address, the role and the operations. A script's actor is the token's label, which is the reason to issue one token per consumer.
And confirm the change is really there:
curl -sk -H "Authorization: Bearer $T" "$R/api/config/commands" | grep cache-sizeUndoing it#
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
-d '{"revision":1}' "$R/api/rollback"See also#
- Your first API call
- Config as code with
agent.yaml— the declarative version - Undo the last commit
- Commit-confirm and Staging: the working set
Checked against agent/main.go ·
agent/staging.go ·
docs/deploy.md ·
docs/api-cookbook.md