Some checks failed
VyOS already has rollback, but `rollback 1` returns you to the *previous*
revision, which may itself be broken — you can end up walking backwards through
several bad commits hunting for the one that worked, at exactly the moment you
have no network to look things up with. This pins a state a human has actually
used and found working, so recovery is one step and needs no memory of how many
changes ago things were fine.
/config/vyos-known-good save pin the running config
/config/vyos-known-good status when it was taken, how running differs
/config/vyos-known-good diff what a restore would change
/config/vyos-known-good restore go back to it
Deliberately not automatic. A config is only known-good once someone has used
the network; a snapshot taken after every commit would faithfully preserve the
broken one.
The restore is itself commit-confirmed, so even the recovery path is protected:
if the snapshot is somehow wrong, or access is still broken and nothing can be
confirmed, the router undoes the restore rather than leaving you worse off.
Silence reverts.
`save` refuses when there are uncommitted changes — a snapshot that did not
match what is actually running would look like a safety net without being one.
Two things found while building it, both of which made the script silently
useless rather than fail loudly:
- Sourcing `script-template` **resets the positional parameters**, so `$1` was
empty by the time the case statement ran and every invocation fell through
to the usage message. Arguments are captured before the source.
- `0600` made the snapshot unreadable to the `vyos` user, so `status` and
`diff` — the two commands you run while deciding whether to restore — showed
nothing. Now 0660 root:vyattacfg, matching /config/config.boot.
Installed on both routers with the current, verified-working config pinned
(vyos001 1029 lines, vyos002 1019).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DMVzWZgiKW2wquf5z8S1yH
121 lines
4.8 KiB
Plaintext
Executable File
121 lines
4.8 KiB
Plaintext
Executable File
#!/bin/vbash
|
|
# Pin a config you have SEEN working, and get back to it with one command.
|
|
#
|
|
# Why this exists when VyOS already has rollback: `rollback 1` returns you to the
|
|
# previous revision, which may itself be broken -- you can walk backwards through
|
|
# several bad commits looking for the one that worked. This pins a state you
|
|
# explicitly confirmed was good, so recovery is one step and does not require
|
|
# remembering how many changes ago things last worked.
|
|
#
|
|
# It is deliberately NOT automatic. A config is only "known good" once a human
|
|
# has used the network and found it working; a script cannot judge that, and a
|
|
# snapshot taken automatically after every commit would faithfully preserve the
|
|
# broken one.
|
|
#
|
|
# vyos-known-good save mark the running config as known-good
|
|
# vyos-known-good status when it was taken, and how it differs from running
|
|
# vyos-known-good restore go back to it (commit-confirmed, so even this is safe)
|
|
# vyos-known-good diff what would change if you restored
|
|
#
|
|
# Lives in /config so it survives image upgrades, like vyos-unifi-switch.
|
|
|
|
# Capture the arguments BEFORE sourcing script-template: sourcing it resets the
|
|
# positional parameters, so $1 is empty by the time the case statement runs and
|
|
# every invocation silently falls through to the usage message.
|
|
ACTION="${1:-status}"
|
|
|
|
source /opt/vyatta/etc/functions/script-template
|
|
|
|
GOOD="/config/known-good.boot"
|
|
META="/config/known-good.meta"
|
|
RUNNING="/config/config.boot"
|
|
CONFIRM_MINUTES="${CONFIRM_MINUTES:-5}"
|
|
|
|
say() { printf '\033[0;36m[known-good]\033[0m %s\n' "$*"; }
|
|
warn() { printf '\033[1;33m[known-good]\033[0m %s\n' "$*" >&2; }
|
|
die() { printf '\033[0;31m[known-good]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
|
|
# The running config on disk is only current if nothing is uncommitted-and-unsaved.
|
|
# Saving a snapshot that does not match what is actually running would be worse
|
|
# than having no snapshot at all -- it would look like a safety net and not be one.
|
|
require_saved() {
|
|
if ! cli-shell-api sessionChanged >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
die "there are uncommitted changes; commit and save first, or this snapshot would not match reality"
|
|
}
|
|
|
|
cmd_save() {
|
|
require_saved
|
|
[ -r "$RUNNING" ] || die "cannot read $RUNNING"
|
|
sudo cp "$RUNNING" "$GOOD"
|
|
# 0660 root:vyattacfg, matching /config/config.boot. 0600 would make the
|
|
# snapshot unreadable to the vyos user, so `status` and `diff` -- the two you
|
|
# run while deciding whether to restore -- would silently show nothing.
|
|
sudo chmod 0660 "$GOOD"; sudo chgrp vyattacfg "$GOOD"
|
|
sudo chmod 0660 "$META" 2>/dev/null; sudo chgrp vyattacfg "$META" 2>/dev/null
|
|
{
|
|
echo "saved_at=$(date -Is)"
|
|
echo "saved_by=${SUDO_USER:-$USER}"
|
|
echo "hostname=$(hostname)"
|
|
echo "lines=$(wc -l < "$RUNNING")"
|
|
} | sudo tee "$META" >/dev/null
|
|
say "pinned $(wc -l < "$GOOD") lines as known-good on $(hostname)"
|
|
say "restore with: /config/vyos-known-good restore"
|
|
}
|
|
|
|
cmd_status() {
|
|
[ -r "$GOOD" ] || { warn "no known-good snapshot on $(hostname) -- run 'save' while things work"; return 1; }
|
|
say "known-good on $(hostname):"
|
|
sed 's/^/ /' "$META" 2>/dev/null
|
|
local n
|
|
n="$(diff <(grep -vE '^\s*$' "$GOOD") <(grep -vE '^\s*$' "$RUNNING") 2>/dev/null | grep -c '^[<>]')"
|
|
if [ "${n:-0}" -eq 0 ]; then
|
|
say "running config MATCHES known-good"
|
|
else
|
|
warn "running config differs from known-good by $n line(s) -- 'diff' to see them"
|
|
fi
|
|
}
|
|
|
|
cmd_diff() {
|
|
[ -r "$GOOD" ] || die "no known-good snapshot"
|
|
diff -u "$GOOD" "$RUNNING" | sed -E "s/(password|key|secret)[[:space:]]+\S+/\1 <REDACTED>/I" || true
|
|
}
|
|
|
|
cmd_restore() {
|
|
[ -r "$GOOD" ] || die "no known-good snapshot to restore"
|
|
say "restoring known-good on $(hostname) (taken $(grep -m1 saved_at "$META" 2>/dev/null | cut -d= -f2-))"
|
|
|
|
# Commit-confirmed even here. If the known-good snapshot is itself somehow
|
|
# wrong, or the restore cannot be confirmed because access is still broken,
|
|
# the router undoes it rather than leaving you worse off. Silence reverts.
|
|
local script; script="$(mktemp)"
|
|
{
|
|
echo 'source /opt/vyatta/etc/functions/script-template'
|
|
echo 'configure'
|
|
echo "load $GOOD"
|
|
printf 'sudo sg vyattacfg "/usr/bin/config-mgmt commit_confirm -y -t=%s"\n' "$CONFIRM_MINUTES"
|
|
echo 'export IN_COMMIT_CONFIRM=t'
|
|
echo 'commit'
|
|
echo 'unset IN_COMMIT_CONFIRM'
|
|
echo 'exit'
|
|
} > "$script"
|
|
vbash "$script"; local rc=$?
|
|
rm -f "$script"
|
|
|
|
[ $rc -eq 0 ] || die "restore failed (rc=$rc) -- nothing was committed"
|
|
say ""
|
|
say "RESTORED under a ${CONFIRM_MINUTES} minute timer."
|
|
say "Check the network NOW. If it works, confirm it:"
|
|
say " sudo sg vyattacfg '/usr/bin/config-mgmt confirm'"
|
|
say "If you do nothing, the router reverts on its own."
|
|
}
|
|
|
|
case "$ACTION" in
|
|
save) cmd_save ;;
|
|
status) cmd_status ;;
|
|
diff) cmd_diff ;;
|
|
restore) cmd_restore ;;
|
|
*) die "usage: vyos-known-good {save|status|diff|restore}" ;;
|
|
esac
|