Lab Maintenance: Nightly MikroTik Config Backups to Gitea

A while ago a VLAN change took down one of my MikroTik switches, and I had to reset it by hand. What I wanted afterwards was simple: a copy of every switch config from last night, and a way to see exactly what changed. Now that the lab has its own Git server, that takes one script and a cron job.

Every night at 02:15, git01 exports all three switches, and if anything changed, it commits the new config to a private repository. If nothing changed, it does nothing. After any change I can open the commit and see the lines that moved.

The IP addresses below are examples, not the ones in my lab.

Script for this post: switch-backup.sh, with example addresses, free on GitHub.

How it fits together

PieceWhat it does
netbackup (Linux user on git01)Runs the job, owns an SSH key with no passphrase
netbackup (user on each switch)Read-only, key login only, allowed from git01’s address alone
Deploy key in GiteaLets the job push to this one repository and nothing else
switch-backup.shExports, cleans, commits only on a real change
cronRuns it at 02:15, before the nightly Gitea dump

No password is stored anywhere, and the account the job uses on the switches cannot change a thing.

1. A service user and key on the Git server

sudo adduser --disabled-password --gecos 'Switch backup' netbackup
sudo -u netbackup ssh-keygen -t rsa -b 4096 -N '' -f /home/netbackup/.ssh/id_rsa -C netbackup@git01
sudo -u netbackup git config --global user.name  "Switch Backup"
sudo -u netbackup git config --global user.email "netbackup@lab.internal"

I used RSA because every RouterOS version accepts it. The key has no passphrase so it can run unattended, and it only unlocks a read-only account.

2. A read-only user on each switch

From the Git server, for each switch:

sudo cp /home/netbackup/.ssh/id_rsa.pub /tmp/netbackup.pub
ssh admin@192.168.10.1 '/user group add name=backup policy=ssh,read'
ssh admin@192.168.10.1 "/user add name=netbackup group=backup address=192.168.10.28/32 password=$(openssl rand -base64 24)"
ssh admin@192.168.10.1 "/user ssh-keys add user=netbackup key=\"$(cut -d' ' -f1,2 /tmp/netbackup.pub)\""

The backup group only has ssh and read. Without the sensitive policy, RouterOS leaves passwords and keys out of the export, so no secrets end up in Git. The address setting means the account only works from the Git server. The password is random and never used.

On older RouterOS versions you upload the key file with scp and run /user ssh-keys import instead. On one of my switches the uploaded file never appeared, and adding the key as text worked first time.

Test it. BatchMode means it fails instead of asking for a password:

sudo -u netbackup ssh -o BatchMode=yes netbackup@192.168.10.1 '/system identity print'

3. A deploy key in Gitea

In the repository, Settings, Deploy Keys, Add Deploy Key: paste the public key and tick Enable Write Access. A deploy key belongs to one repository, so the job can push to lab-configs and nowhere else. Then clone it as the service user:

sudo -u netbackup git clone git@git.lab.internal:labgit/lab-configs.git /home/netbackup/lab-configs

4. The script

/usr/local/bin/switch-backup.sh:

#!/bin/bash
# Nightly MikroTik config backup to Gitea
set -u
REPO=/home/netbackup/lab-configs
SWITCHES="crs309:192.168.10.1 crs326-24s:192.168.10.3 crs326-24g:192.168.10.4"
cd "$REPO" || exit 1
git pull -q --ff-only || { echo "$(date -Is) git pull failed"; exit 1; }
failed=""
for s in $SWITCHES; do
  name=${s%%:*}; ip=${s#*:}
  if ssh -o BatchMode=yes -o ConnectTimeout=15 netbackup@"$ip" /export > "mikrotik/$name.tmp" 2>/dev/null && [ -s "mikrotik/$name.tmp" ]; then
    tr -d '\r' < "mikrotik/$name.tmp" | sed '1{/^# .* by RouterOS/d}' > "mikrotik/$name.rsc"
  else
    failed="$failed $name"
  fi
  rm -f "mikrotik/$name.tmp"
done
git add mikrotik/*.rsc
if git diff --cached --quiet; then
  echo "$(date -Is) no changes${failed:+, failed:$failed}"
else
  git commit -q -m "Nightly switch backup $(date +%F)${failed:+ (failed:$failed)}"
  git push -q && echo "$(date -Is) committed and pushed${failed:+, failed:$failed}"
fi

Three details make it useful rather than noisy:

  • The first line of every RouterOS export is a timestamp. Left in, every night would count as a change. The sed removes it.
  • RouterOS sends Windows line endings. tr strips them so the diffs stay clean.
  • If a switch is down, the others are still backed up, and the commit message names the one that failed.

Run it twice by hand. The first run commits. The second should say no changes, which proves it only commits when something really changed.

5. Schedule it

sudo touch /var/log/switch-backup.log
sudo chown netbackup:netbackup /var/log/switch-backup.log
echo '15 2 * * * netbackup /usr/local/bin/switch-backup.sh >> /var/log/switch-backup.log 2>&1' | sudo tee /etc/cron.d/switch-backup

02:15 is deliberate: the nightly Gitea dump runs at 02:30, so it always includes the latest switch configs.

What I get from it

Each night adds one line to the log. When I do change a switch, the next morning there is a commit from Switch Backup, and clicking it shows the exact lines that changed, in red and green. The next time a VLAN change goes wrong, I will know what it was and what to put back.

Things that caught me out

  • Because the job pushes on its own, run git pull before working in the repository from another machine, or your push is refused.
  • The Windows side of Git (internal CA trust, sign-in, PowerShell ISE) has its own gotchas. They are in Part 6.