104 lines
4.5 KiB
Plaintext
104 lines
4.5 KiB
Plaintext
|
|
#!/bin/bash
|
||
|
|
# Install (or verify) the WAN-follows-VRRP mechanism on a VyOS router.
|
||
|
|
#
|
||
|
|
# This exists because on 2026-09-05 the sim's failover proof was obtained from
|
||
|
|
# scripts that had been hand-`sed`-ed in place: /config/vrrp-wan-health and
|
||
|
|
# -reconcile differed from git by an edited VIP, so the tested behaviour was not
|
||
|
|
# the committed behaviour and any reinstall would have silently reverted it.
|
||
|
|
# `--check` makes that class of drift a hard failure instead of a discovery.
|
||
|
|
#
|
||
|
|
# It installs ONLY the mechanism -- scripts, units, drop-in, settings. It never
|
||
|
|
# touches VyOS configuration: the `interfaces pppoe` node, `vif 53 disable` and
|
||
|
|
# the VRRP sync-group hooks are config and belong in the config model
|
||
|
|
# (labsim/sim-*.py for the sim, infra/vyos/subtrees/overrides.json for
|
||
|
|
# production), not in an installer.
|
||
|
|
#
|
||
|
|
# vrrp-wan-install --vip 192.168.1.1 [--host vyos@10.0.1.253]
|
||
|
|
# vrrp-wan-install --check [--host ...] # exits non-zero on any drift
|
||
|
|
#
|
||
|
|
# With no --host it operates on the local machine, so it can be scp'd to a
|
||
|
|
# router and run there.
|
||
|
|
set -uo pipefail
|
||
|
|
|
||
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
|
VIP=""; HOST=""; MODE=install; PW="${VYOS_PW:-vyos}"
|
||
|
|
|
||
|
|
while [ $# -gt 0 ]; do
|
||
|
|
case "$1" in
|
||
|
|
--vip) VIP="$2"; shift 2 ;;
|
||
|
|
--host) HOST="$2"; shift 2 ;;
|
||
|
|
--check) MODE=check; shift ;;
|
||
|
|
*) echo "usage: $0 [--vip A.B.C.D] [--host user@ip] [--check]" >&2; exit 2 ;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
|
||
|
|
SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
|
||
|
|
-o LogLevel=ERROR -o ConnectTimeout=8 -o PreferredAuthentications=password)
|
||
|
|
run() { # run a command on the target
|
||
|
|
if [ -n "$HOST" ]; then timeout 60 sshpass -p "$PW" ssh "${SSH_OPTS[@]}" "$HOST" "$@"
|
||
|
|
else bash -c "$*"; fi
|
||
|
|
}
|
||
|
|
put() { # copy a file to the target
|
||
|
|
if [ -n "$HOST" ]; then timeout 60 sshpass -p "$PW" scp "${SSH_OPTS[@]}" "$1" "$HOST:$2" >/dev/null
|
||
|
|
else cp "$1" "$2"; fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# script -> destination. take/release are hooks keepalived calls; both exec the
|
||
|
|
# reconciler, so there is one code path.
|
||
|
|
SCRIPTS="vrrp-wan-reconcile vrrp-wan-apply vrrp-wan-health vrrp-wan-guard vrrp-wan-take vrrp-wan-release"
|
||
|
|
UNITS="vrrp-wan-reconcile.service vrrp-wan-reconcile.timer vrrp-wan-guard.service vrrp-wan-guard.timer"
|
||
|
|
GATE_DIR=/etc/systemd/system/ppp@pppoe0.service.d
|
||
|
|
GATE=$GATE_DIR/10-vrrp-wan-gate.conf
|
||
|
|
|
||
|
|
if [ "$MODE" = check ]; then
|
||
|
|
rc=0
|
||
|
|
for f in $SCRIPTS; do
|
||
|
|
local_sum=$(md5sum "$HERE/$f" | cut -d' ' -f1)
|
||
|
|
remote_sum=$(run "md5sum /config/$f 2>/dev/null | cut -d' ' -f1")
|
||
|
|
[ "$local_sum" = "$remote_sum" ] || { echo " DRIFT /config/$f"; rc=1; }
|
||
|
|
done
|
||
|
|
for f in $UNITS; do
|
||
|
|
local_sum=$(md5sum "$HERE/$f" | cut -d' ' -f1)
|
||
|
|
remote_sum=$(run "md5sum /etc/systemd/system/$f 2>/dev/null | cut -d' ' -f1")
|
||
|
|
[ "$local_sum" = "$remote_sum" ] || { echo " DRIFT /etc/systemd/system/$f"; rc=1; }
|
||
|
|
done
|
||
|
|
gate_sum=$(md5sum "$HERE/ppp-vrrp-gate.conf" | cut -d' ' -f1)
|
||
|
|
remote_gate=$(run "md5sum $GATE 2>/dev/null | cut -d' ' -f1")
|
||
|
|
[ "$gate_sum" = "$remote_gate" ] || { echo " DRIFT $GATE (a VyOS upgrade wipes /etc -- both routers would dial)"; rc=1; }
|
||
|
|
run "[ -r /config/vrrp-wan.conf ]" || { echo " MISSING /config/vrrp-wan.conf"; rc=1; }
|
||
|
|
for t in vrrp-wan-reconcile.timer vrrp-wan-guard.timer; do
|
||
|
|
[ "$(run "systemctl is-enabled $t 2>/dev/null")" = enabled ] || { echo " NOT ENABLED $t"; rc=1; }
|
||
|
|
done
|
||
|
|
[ "$rc" -eq 0 ] && echo " vrrp-wan in sync"
|
||
|
|
exit "$rc"
|
||
|
|
fi
|
||
|
|
|
||
|
|
[ -n "$VIP" ] || { echo "--vip is required to install" >&2; exit 2; }
|
||
|
|
|
||
|
|
for f in $SCRIPTS; do
|
||
|
|
put "$HERE/$f" "/tmp/$f"
|
||
|
|
# root:vyattacfg 0775 -- vrrp-wan-apply enters config mode, which requires
|
||
|
|
# membership of vyattacfg.
|
||
|
|
run "sudo install -o root -g vyattacfg -m 0775 /tmp/$f /config/$f"
|
||
|
|
done
|
||
|
|
|
||
|
|
# Settings, with the VIP substituted. One file, read by BOTH the reconciler and
|
||
|
|
# the health check -- keepalived invokes the latter with no environment at all,
|
||
|
|
# so an Environment= line in the unit would be read by one and not the other.
|
||
|
|
sed "s|^VRRP_WAN_VIP=.*|VRRP_WAN_VIP=${VIP}|" "$HERE/vrrp-wan.conf" > /tmp/vrrp-wan.conf.gen
|
||
|
|
put /tmp/vrrp-wan.conf.gen /tmp/vrrp-wan.conf.gen
|
||
|
|
run "sudo install -o root -g vyattacfg -m 0664 /tmp/vrrp-wan.conf.gen /config/vrrp-wan.conf"
|
||
|
|
|
||
|
|
for f in $UNITS; do
|
||
|
|
put "$HERE/$f" "/tmp/$f"
|
||
|
|
run "sudo install -m 0644 /tmp/$f /etc/systemd/system/$f"
|
||
|
|
done
|
||
|
|
|
||
|
|
put "$HERE/ppp-vrrp-gate.conf" /tmp/ppp-vrrp-gate.conf
|
||
|
|
run "sudo mkdir -p $GATE_DIR && sudo install -m 0644 /tmp/ppp-vrrp-gate.conf $GATE"
|
||
|
|
|
||
|
|
run "sudo systemctl daemon-reload && sudo systemctl enable --now vrrp-wan-reconcile.timer vrrp-wan-guard.timer" >/dev/null 2>&1
|
||
|
|
|
||
|
|
echo " installed (vip=$VIP)"
|
||
|
|
run "sudo /config/vrrp-wan-reconcile --status"
|