Useful backup script

Description:
Captures everything needed to rebuild this router on a fresh flash: hardware/version identification, the APK package list and repository config, full UCI export, the standard sysupgrade backup, and the service enable/disable state from /etc/rc.d. It then diffs the overlay against what sysupgrade actually captured, drops anything already owned by an installed package (reproducible via apk add) or matching known secrets, and bundles the remaining orphaned files the backup would have silently left behind. Output is a single timestamped tarball in /tmp, with the archive path and package/modified/orphaned counts printed to stdout.

Good place to store is in ls -la /usr/bin/clone-router.sh

A few practical notes before you run it "for real":

  • It writes to /tmp on the router, which is tmpfs — the resulting tarball won't survive a reboot, so pull it off the router (scp won't work since there's no sftp-server; use ssh root@192.168.1.1 "cat /tmp/clone-*.tar.gz" > clone-backup.tar.gz like I did for testing) if you want it to persist.
  • The tarball still contains uci-export.txt and the sysupgrade config.tar.gz, both of which have your wifi/PPPoE passwords in plaintext — treat it accordingly once it leaves the router (don't drop it somewhere world-readable).
  • Each run leaves a new timestamped tarball behind; nothing auto-deletes old ones, so clean up /tmp/clone-*.tar.gz on the router periodically if you run this more than once.
  • Architecture (Intel/x86) itself doesn't matter

Picking the newest clone-.tar.gz by mtime*:
REMOTE=$(ssh root@192.168.1.1 'ls -t /tmp/clone-*.tar.gz | head -1') && scp -O "root@192.168.1.1:$REMOTE" .

Grab the latest from the router:
ssh root@192.168.1.1 "rm -f $REMOTE"

#!/bin/sh
SELF=/usr/bin/clone-router.sh
D=/tmp/clone-$(date +%Y%m%d-%H%M)
mkdir -p "$D" || exit 1

ubus call system board > "$D/board.json" 2>/dev/null
cat /etc/openwrt_release > "$D/openwrt_release.txt" 2>/dev/null
uname -a > "$D/uname.txt"
mount > "$D/mounts.txt"
df -h > "$D/df.txt"
cp /etc/apk/repositories "$D/" 2>/dev/null
cp -r /etc/apk/repositories.d "$D/repositories.d" 2>/dev/null

sed 's/[>=<].*//' /etc/apk/world | grep -vE '^(kernel|base-files|libc)$' | sort > "$D/packages.txt"
tr '\n' ' ' < "$D/packages.txt" > "$D/packages-oneline.txt"

ls -l /etc/rc.d/ > "$D/rc.d-state.txt" 2>/dev/null
uci export > "$D/uci-export.txt"
sysupgrade -b "$D/config.tar.gz" 2>/dev/null

if [ -s "$D/config.tar.gz" ]; then
  tar -tzf "$D/config.tar.gz" | sed -e 's|^\./||' -e 's|/$||' -e 's|^|/|' | sort > "$D/in-backup.txt"
else
  echo "SYSUPGRADE BACKUP FAILED" > "$D/in-backup.txt"
fi

if [ -d /overlay/upper ]; then
  find /overlay/upper \( -type f -o -type l \) 2>/dev/null | sed 's|^/overlay/upper||' | sort > "$D/modified.txt"
else
  find /etc /root /www /srv /opt /usr/bin /usr/sbin /usr/lib /usr/share \( -type f -o -type l \) -newer /rom/etc/banner 2>/dev/null | sort > "$D/modified.txt"
fi

grep -Fxv -f "$D/in-backup.txt" "$D/modified.txt" \
  | grep -vE '^/(tmp|var|dev|proc|sys)' \
  | grep -Fxv "$SELF" > "$D/orphan-candidates.txt"

: > "$D/MISSING-from-backup.txt"
while IFS= read -r p; do
  case "$p" in
    /etc/shadow-|/etc/passwd-|/etc/urandom.seed) continue ;;
  esac
  if { [ -e "$p" ] || [ -L "$p" ]; } && ! apk info -W "$p" >/dev/null 2>&1; then
    echo "$p" >> "$D/MISSING-from-backup.txt"
  fi
done < "$D/orphan-candidates.txt"
rm -f "$D/orphan-candidates.txt"

if [ -s "$D/MISSING-from-backup.txt" ]; then
  sed 's|^/||' "$D/MISSING-from-backup.txt" > /tmp/.rel-list.$$
  tar -czf "$D/missing-files.tar.gz" -C / -T /tmp/.rel-list.$$ 2>/dev/null
  rm -f /tmp/.rel-list.$$
fi

N_PKG=$(wc -l < "$D/packages.txt")
N_MOD=$(wc -l < "$D/modified.txt")
N_ORPH=$(wc -l < "$D/MISSING-from-backup.txt")

tar -czf "$D.tar.gz" -C /tmp "$(basename "$D")"
rm -rf "$D"

echo "archive:   $D.tar.gz"
echo "packages:  $N_PKG"
echo "modified:  $N_MOD"
echo "orphaned:  $N_ORPH"

I changed D in the script to read 'D=/mnt/ssd/backups/clone-$(date +%Y%m%d-%H%M)'

and when run got:

root@OpenWrt:/mnt/ssd/backups# clone-router.sh
tar: clone-20261006-0114: No such file or directory
tar: error exit delayed from previous errors
archive: /mnt/ssd/backups/clone-20261006-0114.tar.gz
packages: 67
modified: 126
orphaned: 14

I have broken the script! How can I fix it?

i could be totally wrong, but a quick scan makes me think it is this line:

tar -czf "$D.tar.gz" -C /tmp "$(basename "$D")"

i think the -C /tmp needs to instead be the directory you changed $D to?