small-package/luci-app-amlogic/root/usr/sbin/openwrt-backup

579 lines
22 KiB
Bash
Executable File

#!/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}"
BACKUP_LIST='./etc/AdGuardHome.yaml \
./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/ \
./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/'
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) : ;;
*) echo "Unknown platform, only support amlogic or rockchip or allwinner h6 or qemu-aarch64!"
exit 1
;;
esac
fi
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
echo "Backup failed!"
exit 1
fi
}
restore() {
# Find the partition where root is located
ROOT_PTNAME=$(df / | tail -n1 | awk '{print $1}' | awk -F '/' '{print $3}')
if [ "${ROOT_PTNAME}" == "" ]; then
echo "Cannot find the partition corresponding to the root file system!"
exit 1
fi
# 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=""
;;
*)
echo "Unable to recognize the disk type of ${ROOT_PTNAME}!"
exit 1
;;
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
echo "The backup file [ ${BACKUP_FILE} ] not found!"
exit 1
fi
}
gen_fstab() {
# Find the partition where root is located
ROOT_PTNAME=$(df / | tail -n1 | awk '{print $1}' | awk -F '/' '{print $3}')
if [ "${ROOT_PTNAME}" == "" ]; then
echo "Cannot find the partition corresponding to the root file system!"
exit 1
fi
# 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=""
;;
*)
echo "Unable to recognize the disk type of ${ROOT_PTNAME}!"
exit 1
;;
esac
ROOT_MSG=$(lsblk -l -o NAME,PATH,MOUNTPOINT,UUID,FSTYPE,LABEL | awk '$3 ~ /^\/$/ {print $0}')
if [ "$ROOT_MSG" == "" ]; then
echo "Get rootfs message failed!"
exit 1
fi
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 <<EOF
config global
option anon_swap '0'
option anon_mount '1'
option auto_swap '0'
option auto_mount '1'
option delay_root '5'
option check_fs '0'
config mount
option target '/overlay'
option uuid '${ROOT_UUID}'
option enabled '1'
option enabled_fsck '1'
option fstype '${ROOT_FSTYPE}'
EOF
if [ "${ROOT_FSTYPE}" == "btrfs" ]; then
echo " option options 'compress=zstd:${ZSTD_LEVEL}'" >>/etc/config/fstab
fi
cat >>/etc/config/fstab <<EOF
config mount
option target '/boot'
EOF
if [ "${BOOT_FSTYPE}" == "vfat" ]; then
echo " option label '${BOOT_LABEL}'" >>/etc/config/fstab
else
echo " option uuid '${BOOT_UUID}'" >>/etc/config/fstab
fi
cat >>/etc/config/fstab <<EOF
option enabled '1'
option enabled_fsck '1'
option fstype '${BOOT_FSTYPE}'
EOF
echo "/etc/config/fstab generated."
echo "please reboot"
exit 0
}
print_list() {
echo "${BACKUP_LIST}"
exit 0
}
list_snapshot() {
echo "----------------------------------------------------------------"
btrfs subvolume list -rt /
echo "----------------------------------------------------------------"
read -p "Press [ enter ] to return." q
}
create_snapshot() {
default_snap_name="etc-$(date +"%m.%d.%H%M%S")"
echo "The default snapshot name is: ${default_snap_name}"
echo "If you want to modify the snapshot name, please enter it below. Cannot contain spaces."
echo "If you do not want to modify it, just press [ Enter ]. Or press the [ q ] key to go back directly."
while :; do
read -p "[${default_snap_name}] : " nname
if [ "${nname}" == "" ]; then
snap_name="${default_snap_name}"
break
elif echo "${nname}" | grep -E "\s+" >/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=$(lsblk -l -o NAME,MOUNTPOINT | awk '$2~/^\/$/ {print $1}')
if [ "${cur_rootdev}" == "" ]; then
echo "The disk device corresponding to the current rootfs cannot be found!"
read -p "Press [ enter ] to return." q
return
fi
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 <<EOF
============================================================================================================================
1. What is a snapshot?
A snapshot is the state record of a certain subvolume at a certain point in time, and a snapshot is a special subvolume;
When the local snapshot is first generated, it shares the disk space with the original subvolume,
so it does not occupy additional space, and only the files that have changed subsequently occupy space;
Snapshots migrated from other places are not snapshots in essence, but just ordinary subvolumes, so they take up space.
2. How to display existing snapshots?
input the command: btrfs subvolume list -rt /
---------------------------------------------------------------------------------------------
EOF
btrfs subvolume list -rt /
cat <<EOF
---------------------------------------------------------------------------------------------
2. How to create a snapshot?
btrfs subvolume snapshot -r /etc /.snapshots/snapshot_1 # -r It means to generate a read-only snapshot
3. How to delete a snapshot?
btrfs subvolume delete -c /.snapshots/snapshot_1 # -c 是 commit 的意思
4. How to rename a snapshot?
Just use the [ mv ] command of the operating system, for example:
mv /.snapshots/snapshot_1 /.snapshots/snapshot_2
5. How to restore the snapshot?
mv /etc /etc.backup # Back up /etc to /etc.backup, that is, rename the subvolume
btrfs subvolume snapshot /.snapshots/etc-001 /etc # Use snapshot etc-001 to generate snapshot etc, no -r parameter
# (Yes, snapshots can also be re-generated snapshots)
btrfs delete -c /etc.backup # After the previous step is successful, you can delete the etc backup just now
6. How to restore a certain file from the snapshot?
Example A: Restore the mount point configuration file from snapshot etc-000: /etc/config/fstab
cp /.snapshots/etc-000/config/fstab /etc/config/
Example B: Restore network configuration from snapshot etc-001:
cp /.snapshots/etc-001/config/network /etc/config/
Example C: Restore the ssr configuration file from snapshot etc-001:
cp /.snapshots/etc-001/config/shadowsocksr /etc/config/
# (Yes, it is the [ cp ] command of the operating system)
7. How to migrate snapshots from remote?
btrfs supports ssh remote migration snapshots, for example:
ssh 192.168.1.1 btrfs send /.snapshots/snapshot_x | btrfs receive /.snapshots
After the command is completed, the remote snapshot_x is copied to the local /.snapshots/snapshot_x
(as mentioned above, the migration is a subvolume, which takes up space)
============================================================================================================================
EOF
exit 0
}
print_help() {
echo "Usage: $0 -b [ backup ]"
echo " $0 -r [ restore ]"
echo " $0 -g [ generate fstab ]"
echo " $0 -p [ print backup list ]"
echo " $0 -l [ list snapshots ]"
echo " $0 -c [ create snapshot ]"
echo " $0 -s [ restore snapshot ]"
echo " $0 -d [ delete snapshot ]"
echo " $0 -h [ help ]"
echo " $0 -q [ quit ]"
exit 0
}
menu() {
while :; do
clear
cat <<EOF
┌────────[ backup config ]────────┐
│ │
│ b. backup config │
│ r. restore config │
│ g. generate fstab │
│ p. print backup list │
│ │
├─────[ Snapshot management ]─────┤
│ │
│ l. list snapshots │
│ c. create snapshot │
│ d. delete snapshot │
│ R. restore snapshot │
│ m. migrate snapshot │
│ s. snapshot help │
│ │
╞═════════════════════════════════╡
│ │
│ h. help │
│ q. quit │
│ │
└─────────────────────────────────┘
EOF
echo -ne "please select: [ ]\b\b"
read select
case $select in
b | backup) backup ;;
r | restore)
restore
gen_fstab
;;
g | gen_fstab) gen_fstab ;;
p | print_list) print_list ;;
l | list_snapshot) list_snapshot ;;
c | create_snapshot) create_snapshot ;;
d | delete_snapshot) delete_snapshot ;;
R | restore_snapshot) restore_snapshot ;;
m | migrate_snapshot) migrate_snapshot ;;
s | snapshot_help) snapshot_help ;;
h | help) print_help ;;
q | quit) exit 0 ;;
esac
done
}
getopts 'brgplcRmsdhq' opts
case $opts in
b | backup) backup ;;
r | restore)
restore
gen_fstab
;;
g | gen_fstab) gen_fstab ;;
p | print_list) print_list ;;
l | list_snapshot) list_snapshot ;;
c | create_snapshot) create_snapshot ;;
d | delete_snapshot) delete_snapshot ;;
R | restore_snapshot) restore_snapshot ;;
m | migrate_snapshot) migrate_snapshot ;;
s | snapshot_help) snapshot_help ;;
h | help) print_help ;;
q | quit) exit 0 ;;
*) menu ;;
esac