#!/bin/sh # "smb" or "nfs" STORAGE_TYPE="nfs" # ip or hostname S_HOST="nfs.lan" # /path/to/backup S_PATH="/mnt/media1/backup" SMB_USER="guest" SMB_PASS="guest" LOCAL_KEEP=10 HOST_KEEP=30 HNAME=$(uci get system.@system[0].hostname) DATE=$(date +%Y-%m-%d_%H-%M) S_MOUNT="/tmp/share" C_LIST=" /etc/config/ /etc/xray/ /etc/unbound/ /root/.ssh/ /opt/xray-config/ /opt/backup-manager/ /etc/nftables.d/10-netmap.nft " C_EXCLUDE=" /opt/backup-manager/local_backups /etc/xray/ipset /etc/xray/ipnet " H_REPLACE="wpad dnsmasq" DIR="/opt/backup-manager" L_BACKUP_DIR="$DIR/local_backups" SCR_BACKUP="$DIR/backup-configs.sh" SCR_OPKG="$DIR/backup-opkg.sh" mkdir -p "$L_BACKUP_DIR" if [ "$STORAGE_TYPE" = "nfs" ]; then FULL_ADDR="${S_HOST}:${S_PATH}" else FULL_ADDR="//${S_HOST}${S_PATH}" fi check_network() { printf "Checking network %s... " "$S_HOST" if ! ping -c 2 "$S_HOST" >/dev/null 2>&1; then echo "FAILED" echo "[!] Error: Remote host $S_HOST is unreachable." echo " Check if the NAS is powered on and the IP/Hostname is correct." return 1 fi echo "OK" return 0 } check_dependencies() { printf "Checking %s dependencies... " "$STORAGE_TYPE" local pkgs="" [ "$STORAGE_TYPE" = "nfs" ] && pkgs="kmod-fs-nfs kmod-fs-nfs-v3 kmod-fs-nfs-v4 nfs-utils" [ "$STORAGE_TYPE" = "smb" ] && pkgs="kmod-fs-cifs cifs-utils" local missing="" for pkg in $pkgs; do opkg list-installed | grep -q "^$pkg " || missing="$missing $pkg" done if [ -n "$missing" ]; then echo " [!] Missing: $missing. Install them to use remote backup." return 1 fi echo "OK" return 0 } mount_share() { mkdir -p "$S_MOUNT" if grep -qs "$S_MOUNT" /proc/mounts; then umount -l "$S_MOUNT" >/dev/null 2>&1 fi printf "Mounting %s storage... " "$STORAGE_TYPE" local mount_err if [ "$STORAGE_TYPE" = "nfs" ]; then mount_err=$(mount -t nfs "$FULL_ADDR" "$S_MOUNT" -o nolock,tcp 2>&1) else mount_err=$(mount -t cifs "$FULL_ADDR" "$S_MOUNT" -o user=$SMB_USER,pass=$SMB_PASS,vers=3.0,sec=ntlmssp 2>&1) fi if grep -qs "$S_MOUNT" /proc/mounts; then echo "OK" return 0 else echo "FAILED" echo "[!] Error: Failed to mount $STORAGE_TYPE ($FULL_ADDR)" [ -n "$mount_err" ] && echo " System message: $mount_err" return 1 fi } manage_local_backups() { printf "Cleaning up local storage (keeping last %s)... " "$LOCAL_KEEP" local tail_val=$((LOCAL_KEEP + 1)) ls -t "$L_BACKUP_DIR"/*.tar.gz 2>/dev/null | tail -n +$tail_val | xargs rm -f ls -t "$L_BACKUP_DIR"/*.lst 2>/dev/null | tail -n +$tail_val | xargs rm -f echo "OK" } clean_old_remote_backups() { printf "Cleaning up remote backups older than %s days... " "$HOST_KEEP" find "$S_MOUNT" -maxdepth 1 -type f \( -name "*.tar.gz" -o -name "*.lst" \) -mtime +$HOST_KEEP -exec rm -f {} \; echo "OK" } select_file() { local search_dir="$1" local ext="$2" echo "Select $ext file" >&2 FILES=$(ls -t "$search_dir"/*"$ext" 2>/dev/null) [ -z "$FILES" ] && return 1 i=1 for f in $FILES; do echo " [$i] $(basename "$f") ($(du -sh "$f" | awk '{print $1}'))" >&2 i=$((i+1)) done printf "Selection (0 to cancel): " >&2 read choice [ -z "$choice" ] || [ "$choice" -eq 0 ] || [ "$choice" -ge "$i" ] && return 1 SELECTED_PATH=$(echo "$FILES" | sed -n "${choice}p") } case "$1" in backup) LOCAL_FILE="$L_BACKUP_DIR/backup_${HNAME}_${DATE}.tar.gz" LOCAL_LST="$L_BACKUP_DIR/packages_${HNAME}_${DATE}.lst" REMOTE_STATUS="FAILED" $SCR_BACKUP "$LOCAL_FILE" "$LOCAL_LST" "$SCR_OPKG" "$C_LIST" "$H_REPLACE" "$C_EXCLUDE" manage_local_backups if check_network && check_dependencies && mount_share; then printf "Copying to %s storage... " "$STORAGE_TYPE" if cp "$LOCAL_FILE" "$LOCAL_LST" "$S_MOUNT/"; then echo "OK" REMOTE_STATUS="OK" clean_old_remote_backups else echo "FAILED" fi sync umount "$S_MOUNT" else echo " Backup remains only in $L_BACKUP_DIR" fi echo "Summary:" echo " [+] Local: $LOCAL_FILE" if [ "$REMOTE_STATUS" = "OK" ]; then echo " [+] Remote: ${S_HOST}:${S_PATH}/$(basename "$LOCAL_FILE")" else echo " [!] Remote: Not saved" fi ;; restore-pkgs) check_network && mount_share || { echo "Remote unavailable"; exit 1; } select_file "$S_MOUNT" ".lst" [ -n "$SELECTED_PATH" ] && $SCR_OPKG restore "$SELECTED_PATH" "$H_REPLACE" umount "$S_MOUNT" ;; restore-sys-net) check_network && mount_share || { echo "Remote unavailable"; exit 1; } select_file "$S_MOUNT" ".tar.gz" [ -n "$SELECTED_PATH" ] && sysupgrade -r "$SELECTED_PATH" umount "$S_MOUNT" ;; restore-sys-local) select_file "$L_BACKUP_DIR" ".tar.gz" [ -n "$SELECTED_PATH" ] && sysupgrade -r "$SELECTED_PATH" ;; restore) echo "1. Local storage ($L_BACKUP_DIR)" echo "2. Remote storage ($STORAGE_TYPE)" printf "Select source: " read s_choice R_DIR="" [ "$s_choice" -eq 1 ] && R_DIR="$L_BACKUP_DIR" [ "$s_choice" -eq 2 ] && { check_network && mount_share && R_DIR="$S_MOUNT"; } if [ -z "$R_DIR" ]; then echo " [!] Source unavailable or cancelled." exit 1 fi echo "1. System configs (.tar.gz)" echo "2. Package list (.lst)" printf "Select type: " read t_choice case "$t_choice" in 1) select_file "$R_DIR" ".tar.gz" if [ -n "$SELECTED_PATH" ]; then [ "$s_choice" -eq 2 ] && { cp "$SELECTED_PATH" /tmp/restore.tar.gz; SELECTED_PATH="/tmp/restore.tar.gz"; umount "$S_MOUNT"; } sysupgrade -r "$SELECTED_PATH" fi ;; 2) select_file "$R_DIR" ".lst" if [ -n "$SELECTED_PATH" ]; then $SCR_OPKG restore "$SELECTED_PATH" "$H_REPLACE" fi ;; *) echo "Cancelled." ;; esac grep -qs "$S_MOUNT" /proc/mounts && umount "$S_MOUNT" ;; *) echo "Usage: $0 {backup|restore}" exit 1 ;; esac