#!/bin/sh DIR=$(cd "$(dirname "$0")" && pwd) ENV_FILE="$DIR/.env" if [ -f "$ENV_FILE" ]; then . "$ENV_FILE" else echo " [!] Error: .env file not found at $ENV_FILE" exit 1 fi HNAME=$(uci get system.@system[0].hostname) DATE=$(date +%Y-%m-%d_%H-%M) L_BACKUP_DIR="$DIR/local_backups" SCR_BACKUP="$DIR/backup-configs.sh" SCR_APK="$DIR/backup-apk.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." return 1 fi echo "OK" return 0 } check_dependencies() { printf "Checking %s dependencies... " "$STORAGE_TYPE" local pkgs="" [ "$STORAGE_TYPE" = "nfs" ] && pkgs="nfs-utils kmod-fs-nfs kmod-fs-nfs-v3 kmod-fs-nfs-v4" [ "$STORAGE_TYPE" = "smb" ] && pkgs="cifsmount kmod-fs-cifs" local missing="" for pkg in $pkgs; do if ! apk info -e "$pkg" >/dev/null 2>&1; then missing="$missing $pkg" fi done if [ -n "$missing" ]; then echo "FAILED" 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_APK" "$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 backup: $LOCAL_FILE" echo " [+] Local packages: $LOCAL_LST" if [ "$REMOTE_STATUS" = "OK" ]; then echo " [+] Remote backup: ${S_HOST}:${S_PATH}/$(basename "$LOCAL_FILE")" echo " [+] Remote packages: ${S_HOST}:${S_PATH}/$(basename "$LOCAL_LST")" else echo " [!] Remote: Not saved" fi ;; 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" echo "Summary:" echo " [+] Restoring configs completed" echo " [!] Reboot required" fi ;; 2) select_file "$R_DIR" ".lst" if [ -n "$SELECTED_PATH" ]; then $SCR_APK 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