#!/bin/bash #====================================================================== # Function: Backup and restore config files in the /etc directory # Copyright (C) 2020-- https://github.com/unifreq/openwrt_packit # Copyright (C) 2021-- https://github.com/ophub/luci-app-amlogic #====================================================================== VERSION="v1.3" ZSTD_LEVEL=6 SNAPSHOT_PRESTR=".snapshots/" BACKUP_DIR="/.reserved" BACKUP_NAME="openwrt_config.tar.gz" BACKUP_FILE="${BACKUP_DIR}/${BACKUP_NAME}" # Customize backup list backup_list_conf="/etc/amlogic_backup_list.conf" if [[ -s "${backup_list_conf}" ]]; then while IFS= read -r line; do BACKUP_LIST+="${line} " done <"${backup_list_conf}" else BACKUP_LIST='./etc/AdGuardHome.yaml \ ./etc/amlogic_backup_list.conf \ ./etc/adblocklist/ \ ./etc/amule/ \ ./etc/balance_irq \ ./etc/bluetooth/ \ ./etc/china_ssr.txt \ ./etc/cifs/cifsdpwd.db \ ./etc/smbd/smbdpwd.db \ ./etc/ksmbd/ksmbdpwd.db \ ./etc/config/ \ ./etc/crontabs/ \ ./etc/dae/ \ ./etc/daed/ \ ./usr/share/openclash/core/ \ ./etc/openclash/backup/ \ ./etc/openclash/config/ \ ./etc/openclash/custom/ \ ./etc/openclash/game_rules/ \ ./etc/openclash/rule_provider/ \ ./etc/openclash/proxy_provider/ \ ./etc/dnsforwarder/ \ ./etc/dnsmasq.conf \ ./etc/dnsmasq.d/ \ ./etc/dnsmasq.oversea/ \ ./etc/dnsmasq.ssr/ \ ./etc/docker/daemon.json \ ./etc/docker/key.json \ ./etc/dropbear/ \ ./etc/easy-rsa/ \ ./etc/environment \ ./etc/exports \ ./etc/firewall.user \ ./etc/gfwlist/ \ ./etc/haproxy.cfg \ ./etc/hosts \ ./etc/ipsec.conf \ ./etc/ipsec.d/ \ ./etc/ipsec.secrets \ ./etc/ipsec.user \ ./etc/ipset/ \ ./etc/mosdns/config.yaml \ ./etc/mwan3.user \ ./etc/nginx/nginx.conf \ ./etc/ocserv/ \ ./etc/openvpn/ \ ./etc/pptpd.conf \ ./etc/qBittorrent/ \ ./etc/rc.local \ ./etc/samba/smbpasswd \ ./etc/shadow \ ./etc/smartdns/ \ ./etc/sqm/ \ ./etc/ssh/*key* \ ./etc/ssl/private/ \ ./etc/ssrplus/ \ ./etc/sysupgrade.conf \ ./etc/tailscale/ \ ./etc/transmission/ \ ./etc/uhttpd.crt \ ./etc/uhttpd.key \ ./etc/urandom.seed \ ./etc/v2raya/ \ ./etc/verysync/ \ ./root/.ssh/' fi error_msg() { echo -e " [ERROR] ${1}" exit 1 } if dmesg | grep 'meson' >/dev/null 2>&1; then PLATFORM="amlogic" elif dmesg | grep 'rockchip' >/dev/null 2>&1; then PLATFORM="rockchip" elif dmesg | grep 'sun50i-h6' >/dev/null 2>&1; then PLATFORM="allwinner" else source /etc/flippy-openwrt-release case ${PLATFORM} in amlogic | rockchip | allwinner | qemu-aarch64) : ;; *) error_msg "Unknown platform, only support amlogic or rockchip or allwinner h6 or qemu-aarch64!" ;; esac fi get_root_partition_name() { local paths=("/" "/overlay" "/rom") local partition_name for path in "${paths[@]}"; do partition_name=$(df "${path}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}') [[ -n "${partition_name}" ]] && break done [[ -z "${partition_name}" ]] && error_msg "Cannot find the root partition!" echo "${partition_name}" } # Get the partition message of the root file system get_root_partition_msg() { local paths=("/" "/overlay" "/rom") local partition_name for path in "${paths[@]}"; do partition_msg=$(lsblk -l -o NAME,PATH,MOUNTPOINT,UUID,FSTYPE,LABEL | awk '$3 ~ "^" "'"${path}"'" "$" {print $0}') [[ -n "${partition_msg}" ]] && break done [[ -z "${partition_msg}" ]] && error_msg "Cannot find the root partition message!" echo "${partition_msg}" } backup() { cd / echo -n "Backup config files ... " [ -d "${BACKUP_DIR}" ] || mkdir -p "${BACKUP_DIR}" eval tar czf "${BACKUP_FILE}" "${BACKUP_LIST}" 2>/dev/null sync if [ -f "${BACKUP_FILE}" ]; then echo "Has been backed up to [ ${BACKUP_FILE} ], please download and save." exit 0 else error_msg "Backup failed!" fi } restore() { # Find the partition where root is located ROOT_PTNAME="$(get_root_partition_name)" # Find the disk where the partition is located, only supports mmcblk?p? sd?? hd?? vd?? and other formats case ${ROOT_PTNAME} in mmcblk?p[1-4]) EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}') PARTITION_NAME="p" LB_PRE="EMMC_" ;; [hsv]d[a-z][1-4]) EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-1)}') PARTITION_NAME="" LB_PRE="" ;; *) error_msg "Unable to recognize the disk type of ${ROOT_PTNAME}!" ;; esac [ -d "${BACKUP_DIR}" ] || mkdir -p "${BACKUP_DIR}" [ -f "/tmp/upload/${BACKUP_NAME}" ] && mv -f "/tmp/upload/${BACKUP_NAME}" ${BACKUP_FILE} [ -f "/mnt/${EMMC_NAME}${PARTITION_NAME}4/${BACKUP_NAME}" ] && mv -f "/mnt/${EMMC_NAME}${PARTITION_NAME}4/${BACKUP_NAME}" ${BACKUP_FILE} sync if [ -f "${BACKUP_FILE}" ]; then echo -n "restore config files ... " cd / tar xzf "${BACKUP_FILE}" 2>/dev/null && sync echo "Successful recovery. Will start automatically, please refresh later!" sleep 3 reboot exit 0 else error_msg "The backup file [ ${BACKUP_FILE} ] not found!" fi } gen_fstab() { # Find the partition where root is located ROOT_PTNAME="$(get_root_partition_name)" # Find the disk where the partition is located, only supports mmcblk?p? sd?? hd?? vd?? and other formats case ${ROOT_PTNAME} in mmcblk?p[1-4]) EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}') PARTITION_NAME="p" ;; [hsv]d[a-z][1-4]) EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-1)}') PARTITION_NAME="" ;; *) error_msg "Unable to recognize the disk type of ${ROOT_PTNAME}!" ;; esac ROOT_MSG="$(get_root_partition_msg)" ROOT_NAME=$(echo $ROOT_MSG | awk '{print $1}') ROOT_DEV=$(echo $ROOT_MSG | awk '{print $2}') ROOT_UUID=$(echo $ROOT_MSG | awk '{print $4}') ROOT_FSTYPE=$(echo $ROOT_MSG | awk '{print $5}') ROOT_LABEL=$(echo $ROOT_MSG | awk '{print $6}') BOOT_NAME="${EMMC_NAME}${PARTITION_NAME}1" BOOT_MSG=$(lsblk -l -o NAME,UUID,FSTYPE,LABEL | grep "${BOOT_NAME}") BOOT_DEV="/dev/${BOOT_NAME}" BOOT_UUID=$(echo $BOOT_MSG | awk '{print $2}') BOOT_FSTYPE=$(echo $BOOT_MSG | awk '{print $3}') BOOT_LABEL=$(echo $BOOT_MSG | awk '{print $4}') cat >/etc/config/fstab <>/etc/config/fstab fi cat >>/etc/config/fstab <>/etc/config/fstab else echo " option uuid '${BOOT_UUID}'" >>/etc/config/fstab fi cat >>/etc/config/fstab </dev/null; then echo "The name [${nname}] contains spaces, please re-enter!" continue elif [ "${nname}" == "q" ] || [ "${nname}" == "Q" ]; then return else if btrfs subvolume list -rt / | awk '{print $4}' | grep "^\\${SNAPSHOT_PRESTR}${nname}$" >/dev/null; then echo "Name: [ ${nname} ] has been used, please re-enter!" continue else snap_name="${nname}" break fi fi done ( cd / chattr -ia etc/config/fstab btrfs subvolume snapshot -r /etc "${SNAPSHOT_PRESTR}${snap_name}" if [[ "$?" -eq "0" ]]; then echo "The snapshot is created successfully: ${snap_name}" else echo "Snapshot creation failed!" fi ) read -p "Press [ enter ] to return." q } restore_snapshot() { echo "Below are the existing etc snapshots, please enter the name of one of them." echo "Tip: [ etc-000 ] This is the factory initial configuration." echo " [ etc-001 ] if it exists, it is the initial configuration after upgrading from the previous version." echo "----------------------------------------------------------------" btrfs subvolume list -rt / echo "----------------------------------------------------------------" read -p "Please enter the name of the snapshot to be restored (only the part after ${SNAPSHOT_PRESTR} needs to be entered): " snap_name if btrfs subvolume list -rt / | grep "${SNAPSHOT_PRESTR}${snap_name}" >/dev/null; then while :; do echo "Once the snapshot is restored, the current [ /etc ] will be overwritten!" read -p "Are you sure you want to restore the snapshot: [$snap_name]? y/n [n] " yn case $yn in y | Y) ( cd / chattr -ia etc/config/fstab mv etc etc.backup btrfs subvolume snapshot "${SNAPSHOT_PRESTR}${snap_name}" etc if [[ "$?" -eq "0" ]]; then btrfs subvolume delete -c etc.backup echo "Successfully restored, please enter [ reboot ] to restart the openwrt." else rm -rf etc mv etc.backup etc echo "Recovery failed, [ etc ] has not changed!" fi ) read -p "Press [ enter ] to return." q break ;; *) break ;; esac done else read -p "The snapshot name is incorrect, please run the program again! Press [ Enter ] to go back." q fi } delete_snapshot() { echo "Below are the existing [ etc ] snapshots, please enter the name of one of them." echo "Tip: [ etc-000 ] This is the factory initial configuration (cannot be deleted)" echo " [ etc-001 ] if it exists, it is the initial configuration after upgrading from the previous version (cannot be deleted)" echo "----------------------------------------------------------------" btrfs subvolume list -rt / echo "----------------------------------------------------------------" read -p "Please enter the name of the snapshot to be deleted (only the part after ${SNAPSHOT_PRESTR} needs to be entered): " snap_name if [ "${snap_name}" == "etc-000" ] || [ "${snap_name}" == "etc-001" ]; then read -p "The key snapshot cannot be deleted! Press [ enter ] to return." q elif [ "${snap_name}" == "" ]; then read -p "Name is empty! Press [ enter ] to return." q else if btrfs subvolume list -rt / | grep "${SNAPSHOT_PRESTR}${snap_name}" >/dev/null; then read -p "Are you sure you want to delete ${snap_name}? y/n [n] " yn case $yn in y | Y) ( cd / btrfs subvolume delete -c "${SNAPSHOT_PRESTR}${snap_name}" if [[ "$?" -eq "0" ]]; then echo "Snapshot [ ${snap_name} ] has been deleted." else echo "Snapshot [ ${snap_name} ] failed to delete!" fi ) read -p "Press [ Enter ] to return." q ;; *) break ;; esac else read -p "The name of the snapshot is incorrect, press [ Enter ] to return." q fi fi } migrate_snapshot() { cur_rootdev="$(get_root_partition_name)" dev_pre=$(echo "${cur_rootdev}" | awk '{print substr($1, 1, length($1)-1);}') rootdev_idx=$(echo "${cur_rootdev}" | awk '{print substr($1, length($1),1);}') case $rootdev_idx in 2) old_rootpath="/mnt/${dev_pre}3" ;; 3) old_rootpath="/mnt/${dev_pre}2" ;; *) echo "Judge the old version of rootfs path failed!" read -p "Press [ enter ] to return." q return ;; esac echo "The following are snapshots of etc found from the old version of rootfs, please enter the name of one of them." echo "Tip: Automatically exclude etc-000 and etc-001" echo "-----------------------------------------------------------------------------------" btrfs subvolume list -rt "${old_rootpath}" | grep -v "${SNAPSHOT_PRESTR}etc-000" | grep -v "${SNAPSHOT_PRESTR}etc-001" echo "-----------------------------------------------------------------------------------" read -p "Please enter the name of the snapshot to be migrated (only the part after $(SNAPSHOT_PRESTR) needs to be entered): " old_snap_name if [ "${old_snap_name}" == "" ]; then read -p "The name is empty, Press [ enter ] to return." q return elif ! btrfs subvolume list -rt "${old_rootpath}" | awk '{print $4}' | grep "^${SNAPSHOT_PRESTR}${old_snap_name}$" >/dev/null; then echo "The name was entered incorrectly, and the corresponding snapshot was not found!" read -p "Press [ enter ] to return." q return elif [ "${old_snap_name}" == "etc-000" ] || [ "${old_snap_name}" == "etc-001" ]; then echo "Critical snapshots are not allowed to migrate!" read -p "Press [ enter ] to return." q return fi # Find out if there is a snapshot with the same name under the current rootfs if btrfs subvolume list -rt / | awk '{print $4}' | grep "^\\${SNAPSHOT_PRESTR}${old_snap_name}$" >/dev/null; then echo "A snapshot with the name [ ${old_snap_name} ] already exists and cannot be migrated! (But you can delete the existing snapshot with the same name and then migrate)" read -p "Press [ enter ] to return." q return fi need_size=$(du -h -d0 ${old_rootpath}/${SNAPSHOT_PRESTR}${old_snap_name} | tail -n1 | awk '{print $1}') echo "----------------------------------------------------------------------------------------------" df -h echo "----------------------------------------------------------------------------------------------" echo -e "Note: To migrate the snapshot [ ${old_snap_name} ] of [ ${old_rootpath} ] to the current rootfs, it takes about [ ${need_size} ] space," echo -e " Please confirm whether the partition [/dev/${cur_rootdev}] where [/] is located has enough free space (Available)?" read -p "Are you sure to migrate? y/n [n] " yn if [ "$yn" == "y" ] || [ "$yn" == "Y" ]; then ( cd / btrfs send ${old_rootpath}/${SNAPSHOT_PRESTR}${old_snap_name} | btrfs receive ${SNAPSHOT_PRESTR} if [ $? -eq 0 ]; then btrfs property set -ts ${SNAPSHOT_PRESTR}${old_snap_name} ro false cp ${SNAPSHOT_PRESTR}etc-000/config/fstab ${SNAPSHOT_PRESTR}${old_snap_name}/config/ cp ${SNAPSHOT_PRESTR}etc-000/fstab ${SNAPSHOT_PRESTR}${old_snap_name}/ cp ${SNAPSHOT_PRESTR}etc-000/openwrt_release ${SNAPSHOT_PRESTR}${old_snap_name}/ cp ${SNAPSHOT_PRESTR}etc-000/openwrt_version ${SNAPSHOT_PRESTR}${old_snap_name}/ cp ${SNAPSHOT_PRESTR}etc-000/flippy-openwrt-release ${SNAPSHOT_PRESTR}${old_snap_name}/ cp ${SNAPSHOT_PRESTR}etc-000/banner ${SNAPSHOT_PRESTR}${old_snap_name}/banner btrfs property set -ts ${SNAPSHOT_PRESTR}${old_snap_name} ro true echo "The migration is complete, if you want to apply the snapshot [ ${old_snap_name} ], please use the restore snapshot function." else echo "The migration failed!" fi read -p "Press [ enter ] to return." q return ) fi } snapshot_help() { clear cat <