From 2ae6b63bdf5c4da99941e194204350d4ae74b2c5 Mon Sep 17 00:00:00 2001 From: chunzhi Date: Tue, 10 Feb 2026 08:53:32 -0500 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=20/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back.sh | 583 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 583 insertions(+) create mode 100644 back.sh diff --git a/back.sh b/back.sh new file mode 100644 index 0000000..1506bc8 --- /dev/null +++ b/back.sh @@ -0,0 +1,583 @@ +#!/bin/bash +# +# 一键配置:定时备份目录到 Google Drive +# 运行方式:sudo bash setup-backup-gdrive.sh +# + +set -euo pipefail + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +# 默认配置 +DEFAULT_BACKUP_DIR="/var/backups/gdrive-backups" +DEFAULT_RCLONE_REMOTE="gdrive" +DEFAULT_REMOTE_DIR="Backups" +DEFAULT_BACKUP_PREFIX="backup" +DEFAULT_LOCAL_RETENTION=7 +DEFAULT_REMOTE_RETENTION=30 + +SCRIPT_PATH="/usr/local/bin/backup-to-gdrive.sh" +SERVICE_PATH="/etc/systemd/system/backup-to-gdrive.service" +TIMER_PATH="/etc/systemd/system/backup-to-gdrive.timer" +LOG_FILE="/var/log/backup-gdrive.log" + +# 存储多个备份目录 +declare -a SOURCE_DIRS=() + +# 打印函数 +print_header() { + echo "" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE} $1${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" +} + +print_success() { echo -e "${GREEN}✓${NC} $1"; } +print_warning() { echo -e "${YELLOW}⚠${NC} $1"; } +print_error() { echo -e "${RED}✗${NC} $1"; } +print_info() { echo -e "${CYAN}→${NC} $1"; } +print_input() { echo -e "${BOLD}$1${NC}"; } + +# 检查 root 权限 +check_root() { + if [[ $EUID -ne 0 ]]; then + print_error "请使用 root 权限运行此脚本" + echo " sudo bash $0" + exit 1 + fi +} + +# 检查并安装 rclone +install_rclone() { + print_header "检查 rclone" + + if command -v rclone &>/dev/null; then + print_success "rclone 已安装: $(rclone version | head -1)" + else + print_warning "rclone 未安装,正在安装..." + curl -fsSL https://rclone.org/install.sh | bash + print_success "rclone 安装完成" + fi +} + +# 交互式输入备份目录 +input_source_dirs() { + print_header "配置备份目录" + + echo -e "${CYAN}请输入要备份的目录路径${NC}" + echo -e " • 支持添加多个目录" + echo -e " • 输入目录路径后按 Enter 确认" + echo -e " • 输入完所有目录后,直接按 Enter(留空)结束" + echo -e " • 支持使用 Tab 键自动补全路径" + echo "" + + local count=1 + while true; do + # 启用 readline 以支持 Tab 补全 + read -e -p "$(echo -e "${BOLD}目录 ${count}:${NC} ")" dir_input + + # 空输入表示结束 + if [[ -z "$dir_input" ]]; then + if [[ ${#SOURCE_DIRS[@]} -eq 0 ]]; then + print_error "至少需要输入一个目录!" + continue + fi + break + fi + + # 去除首尾空格 + dir_input=$(echo "$dir_input" | xargs) + + # 展开 ~ 为 home 目录 + if [[ "$dir_input" == "~"* ]]; then + local user_home + user_home=$(getent passwd "${USER:-root}" 2>/dev/null | cut -d: -f6) || user_home="/root" + dir_input="${dir_input/#\~/$user_home}" + fi + + # 去除末尾的 / + dir_input="${dir_input%/}" + + # 验证目录存在 + if [[ ! -d "$dir_input" ]]; then + print_error "目录不存在: $dir_input" + echo -e " 请检查路径是否正确" + continue + fi + + # 检查是否重复 + local is_duplicate=false + if [[ ${#SOURCE_DIRS[@]} -gt 0 ]]; then + for existing_dir in "${SOURCE_DIRS[@]}"; do + if [[ "$existing_dir" == "$dir_input" ]]; then + is_duplicate=true + break + fi + done + fi + + if $is_duplicate; then + print_warning "目录已添加,请勿重复" + continue + fi + + # 显示目录信息 + local dir_size=$(du -sh "$dir_input" 2>/dev/null | cut -f1 || echo "未知") + SOURCE_DIRS+=("$dir_input") + print_success "已添加: $dir_input (大小: $dir_size)" + + ((count++)) + done + + # 显示汇总 + echo "" + echo -e "${CYAN}已选择 ${#SOURCE_DIRS[@]} 个目录:${NC}" + for i in "${!SOURCE_DIRS[@]}"; do + echo -e " $((i+1)). ${SOURCE_DIRS[$i]}" + done + echo "" + + read -p "确认以上目录?[Y/n]: " confirm + if [[ "$confirm" =~ ^[Nn]$ ]]; then + SOURCE_DIRS=() + input_source_dirs + fi +} + +# 配置其他参数 +get_config() { + print_header "配置备份参数" + + # 本地备份目录 + print_input "本地备份存放目录" + read -e -p " [${DEFAULT_BACKUP_DIR}]: " BACKUP_DIR + BACKUP_DIR="${BACKUP_DIR:-$DEFAULT_BACKUP_DIR}" + + # rclone remote 名称 + echo "" + print_input "rclone remote 名称" + echo -e " (稍后会引导你配置 Google Drive 连接)" + read -p " [${DEFAULT_RCLONE_REMOTE}]: " RCLONE_REMOTE + RCLONE_REMOTE="${RCLONE_REMOTE:-$DEFAULT_RCLONE_REMOTE}" + + # 远程目录 + echo "" + print_input "Google Drive 上的目标文件夹" + read -p " [${DEFAULT_REMOTE_DIR}]: " REMOTE_DIR + REMOTE_DIR="${REMOTE_DIR:-$DEFAULT_REMOTE_DIR}" + + # 备份前缀 + echo "" + print_input "备份文件名前缀" + echo -e " (生成的文件名格式: 前缀_日期时间.tar.gz)" + read -p " [${DEFAULT_BACKUP_PREFIX}]: " BACKUP_PREFIX + BACKUP_PREFIX="${BACKUP_PREFIX:-$DEFAULT_BACKUP_PREFIX}" + + # 保留天数 + echo "" + print_input "本地备份保留天数" + read -p " [${DEFAULT_LOCAL_RETENTION}]: " LOCAL_RETENTION + LOCAL_RETENTION="${LOCAL_RETENTION:-$DEFAULT_LOCAL_RETENTION}" + + echo "" + print_input "远程备份保留天数" + read -p " [${DEFAULT_REMOTE_RETENTION}]: " REMOTE_RETENTION + REMOTE_RETENTION="${REMOTE_RETENTION:-$DEFAULT_REMOTE_RETENTION}" + + # 执行时间 + echo "" + print_input "定时执行时间" + echo " 1) 每天凌晨 2:00(推荐)" + echo " 2) 每天凌晨 4:00" + echo " 3) 每天中午 12:00" + echo " 4) 每12小时(0:00 和 12:00)" + echo " 5) 每6小时" + echo " 6) 每小时" + echo " 7) 自定义时间" + read -p " 选择 [1]: " schedule_choice + + case "${schedule_choice:-1}" in + 1) SCHEDULE="*-*-* 02:00:00" ;; + 2) SCHEDULE="*-*-* 04:00:00" ;; + 3) SCHEDULE="*-*-* 12:00:00" ;; + 4) SCHEDULE="*-*-* 00,12:00:00" ;; + 5) SCHEDULE="*-*-* 00,06,12,18:00:00" ;; + 6) SCHEDULE="hourly" ;; + 7) + echo "" + echo " OnCalendar 格式示例:" + echo " 每天 3:30 → *-*-* 03:30:00" + echo " 每周一 5:00 → Mon *-*-* 05:00:00" + echo " 每月1号 4:00 → *-*-01 04:00:00" + read -p " 输入: " SCHEDULE + ;; + *) SCHEDULE="*-*-* 02:00:00" ;; + esac +} + +# 显示配置确认 +confirm_config() { + print_header "配置确认" + + echo -e "${BOLD}备份目录:${NC}" + for dir in "${SOURCE_DIRS[@]}"; do + echo " • $dir" + done + echo "" + echo -e "${BOLD}本地备份目录:${NC} $BACKUP_DIR" + echo -e "${BOLD}rclone remote:${NC} $RCLONE_REMOTE" + echo -e "${BOLD}远程目录:${NC} $REMOTE_DIR" + echo -e "${BOLD}文件名前缀:${NC} $BACKUP_PREFIX" + echo -e "${BOLD}本地保留:${NC} $LOCAL_RETENTION 天" + echo -e "${BOLD}远程保留:${NC} $REMOTE_RETENTION 天" + echo -e "${BOLD}执行计划:${NC} $SCHEDULE" + echo "" + + read -p "确认以上配置?[Y/n]: " confirm + if [[ "$confirm" =~ ^[Nn]$ ]]; then + SOURCE_DIRS=() + input_source_dirs + get_config + confirm_config + fi +} + +# 配置 rclone +configure_rclone() { + print_header "配置 rclone 连接 Google Drive" + + # 检查是否已有配置 + if rclone listremotes 2>/dev/null | grep -q "^${RCLONE_REMOTE}:$"; then + print_success "已存在 remote: ${RCLONE_REMOTE}" + + # 测试连接 + print_info "测试连接..." + if rclone lsd "${RCLONE_REMOTE}:" &>/dev/null; then + print_success "Google Drive 连接正常" + read -p "是否重新配置?[y/N]: " reconfigure + [[ ! "$reconfigure" =~ ^[Yy]$ ]] && return 0 + else + print_warning "连接测试失败,需要重新配置" + fi + fi + + echo "" + print_info "即将打开 rclone 配置向导" + echo "" + echo " 请按以下步骤操作:" + echo " ─────────────────────────────────────" + echo " 1. 输入 ${BOLD}n${NC} 创建新 remote" + echo " 2. 名称输入: ${BOLD}${RCLONE_REMOTE}${NC}" + echo " 3. 选择 ${BOLD}Google Drive${NC} (输入对应数字)" + echo " 4. client_id: 直接按 ${BOLD}Enter${NC}" + echo " 5. client_secret: 直接按 ${BOLD}Enter${NC}" + echo " 6. scope: 选择 ${BOLD}1${NC} (完全访问)" + echo " 7. service_account_file: 直接按 ${BOLD}Enter${NC}" + echo " 8. Edit advanced config: 输入 ${BOLD}n${NC}" + echo " 9. Use web browser: 输入 ${BOLD}y${NC}" + echo " 10. 在浏览器中登录 Google 账号并授权" + echo " 11. Configure as Shared Drive: 输入 ${BOLD}n${NC}" + echo " 12. 确认配置: 输入 ${BOLD}y${NC}" + echo " 13. 退出配置: 输入 ${BOLD}q${NC}" + echo " ─────────────────────────────────────" + echo "" + read -p "按 Enter 开始配置..." + + rclone config + + # 验证配置 + echo "" + if rclone listremotes | grep -q "^${RCLONE_REMOTE}:$"; then + print_success "rclone 配置成功" + print_info "测试连接..." + if rclone lsd "${RCLONE_REMOTE}:" &>/dev/null; then + print_success "Google Drive 连接正常" + else + print_error "连接测试失败" + read -p "是否重试配置?[Y/n]: " retry + [[ ! "$retry" =~ ^[Nn]$ ]] && configure_rclone + fi + else + print_error "未找到 remote: ${RCLONE_REMOTE}" + read -p "是否重试配置?[Y/n]: " retry + [[ ! "$retry" =~ ^[Nn]$ ]] && configure_rclone + fi +} + +# 创建备份脚本 +create_backup_script() { + print_header "创建备份脚本" + + mkdir -p "$(dirname "$SCRIPT_PATH")" + mkdir -p "$BACKUP_DIR" + + # 将目录数组转换为脚本中的格式 + local dirs_array="" + for dir in "${SOURCE_DIRS[@]}"; do + dirs_array+=" \"$dir\"\n" + done + + cat > "$SCRIPT_PATH" << 'SCRIPT_EOF' +#!/bin/bash +set -euo pipefail + +# ==================== 配置 ==================== +SOURCE_DIRS=( +__SOURCE_DIRS__ +) +BACKUP_DIR="__BACKUP_DIR__" +RCLONE_REMOTE="__RCLONE_REMOTE__" +REMOTE_DIR="__REMOTE_DIR__" +BACKUP_PREFIX="__BACKUP_PREFIX__" +LOCAL_RETENTION_DAYS=__LOCAL_RETENTION__ +REMOTE_RETENTION_DAYS=__REMOTE_RETENTION__ +LOG_FILE="__LOG_FILE__" +# ============================================== + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"; } +error_exit() { log "错误: $1"; exit 1; } + +main() { + log "========== 备份任务开始 ==========" + + # 检查依赖 + command -v rclone >/dev/null 2>&1 || error_exit "rclone 未安装" + command -v tar >/dev/null 2>&1 || error_exit "tar 未安装" + + # 创建目录 + mkdir -p "$BACKUP_DIR" + touch "$LOG_FILE" 2>/dev/null || LOG_FILE="/tmp/backup-gdrive.log" + + local timestamp=$(date +%Y%m%d_%H%M%S) + local backup_name="${BACKUP_PREFIX}_${timestamp}.tar.gz" + local backup_path="${BACKUP_DIR}/${backup_name}" + + # 检查所有源目录 + local valid_dirs=() + for dir in "${SOURCE_DIRS[@]}"; do + if [[ -d "$dir" ]]; then + valid_dirs+=("$dir") + log "备份目录: $dir" + else + log "警告: 目录不存在,跳过: $dir" + fi + done + + [[ ${#valid_dirs[@]} -eq 0 ]] && error_exit "没有有效的备份目录" + + # 创建备份(多目录打包) + log "创建备份: $backup_path" + tar -czf "$backup_path" "${valid_dirs[@]}" 2>/dev/null \ + || error_exit "创建备份失败" + + local size=$(du -h "$backup_path" | cut -f1) + log "备份完成,大小: ${size}" + + # 上传 + log "上传到 ${RCLONE_REMOTE}:${REMOTE_DIR}/" + rclone copy "$backup_path" "${RCLONE_REMOTE}:${REMOTE_DIR}/" \ + --progress --log-file="$LOG_FILE" --log-level INFO \ + || error_exit "上传失败" + log "上传完成" + + # 清理本地 + log "清理 ${LOCAL_RETENTION_DAYS} 天前的本地备份..." + find "$BACKUP_DIR" -name "${BACKUP_PREFIX}_*.tar.gz" -type f -mtime +${LOCAL_RETENTION_DAYS} -delete 2>/dev/null || true + + # 清理远程 + log "清理 ${REMOTE_RETENTION_DAYS} 天前的远程备份..." + rclone delete "${RCLONE_REMOTE}:${REMOTE_DIR}/" \ + --min-age "${REMOTE_RETENTION_DAYS}d" \ + --include "${BACKUP_PREFIX}_*.tar.gz" 2>/dev/null || true + + log "========== 备份任务完成 ==========" +} + +main "$@" +SCRIPT_EOF + + # 替换配置值 + sed -i "s|__SOURCE_DIRS__|${dirs_array}|g" "$SCRIPT_PATH" + sed -i "s|__BACKUP_DIR__|$BACKUP_DIR|g" "$SCRIPT_PATH" + sed -i "s|__RCLONE_REMOTE__|$RCLONE_REMOTE|g" "$SCRIPT_PATH" + sed -i "s|__REMOTE_DIR__|$REMOTE_DIR|g" "$SCRIPT_PATH" + sed -i "s|__BACKUP_PREFIX__|$BACKUP_PREFIX|g" "$SCRIPT_PATH" + sed -i "s|__LOCAL_RETENTION__|$LOCAL_RETENTION|g" "$SCRIPT_PATH" + sed -i "s|__REMOTE_RETENTION__|$REMOTE_RETENTION|g" "$SCRIPT_PATH" + sed -i "s|__LOG_FILE__|$LOG_FILE|g" "$SCRIPT_PATH" + + chmod +x "$SCRIPT_PATH" + print_success "备份脚本已创建: $SCRIPT_PATH" +} + +# 创建 systemd 服务 +create_systemd_service() { + print_header "创建 systemd 服务" + + # Service 文件 + cat > "$SERVICE_PATH" << EOF +[Unit] +Description=备份目录到 Google Drive +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=$SCRIPT_PATH +User=root +TimeoutStartSec=3600 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +EOF + print_success "Service 文件: $SERVICE_PATH" + + # Timer 文件 + cat > "$TIMER_PATH" << EOF +[Unit] +Description=定时备份到 Google Drive + +[Timer] +OnCalendar=$SCHEDULE +Persistent=true +RandomizedDelaySec=300 + +[Install] +WantedBy=timers.target +EOF + print_success "Timer 文件: $TIMER_PATH" + + # 重载并启用 + systemctl daemon-reload + systemctl enable backup-to-gdrive.timer + systemctl start backup-to-gdrive.timer + + print_success "定时任务已启用" +} + +# 显示完成信息 +show_completion() { + print_header "🎉 安装完成!" + + echo -e "${BOLD}常用命令:${NC}" + echo "" + echo " # 查看定时器状态" + echo -e " ${CYAN}systemctl status backup-to-gdrive.timer${NC}" + echo "" + echo " # 查看下次执行时间" + echo -e " ${CYAN}systemctl list-timers backup-to-gdrive.timer${NC}" + echo "" + echo " # 手动立即执行备份" + echo -e " ${CYAN}systemctl start backup-to-gdrive.service${NC}" + echo "" + echo " # 查看备份日志" + echo -e " ${CYAN}tail -f $LOG_FILE${NC}" + echo -e " ${CYAN}journalctl -u backup-to-gdrive.service -f${NC}" + echo "" + echo " # 停止定时任务" + echo -e " ${CYAN}systemctl stop backup-to-gdrive.timer${NC}" + echo "" + echo " # 编辑备份配置" + echo -e " ${CYAN}nano $SCRIPT_PATH${NC}" + echo "" + + echo -e "${BOLD}定时器状态:${NC}" + systemctl list-timers backup-to-gdrive.timer --no-pager 2>/dev/null || true + + echo "" + read -p "是否立即执行一次备份测试?[y/N]: " run_now + if [[ "$run_now" =~ ^[Yy]$ ]]; then + echo "" + print_info "开始执行备份..." + systemctl start backup-to-gdrive.service + echo "" + print_success "备份测试完成!查看日志: tail $LOG_FILE" + fi +} + +# 卸载函数 +uninstall() { + print_header "卸载备份服务" + + read -p "确认卸载?[y/N]: " confirm + [[ ! "$confirm" =~ ^[Yy]$ ]] && exit 0 + + systemctl stop backup-to-gdrive.timer 2>/dev/null || true + systemctl disable backup-to-gdrive.timer 2>/dev/null || true + rm -f "$SCRIPT_PATH" "$SERVICE_PATH" "$TIMER_PATH" + systemctl daemon-reload + + print_success "卸载完成" + echo "" + echo "以下文件未删除(如需清理请手动删除):" + echo " • 本地备份: /var/backups/gdrive-backups/" + echo " • 日志文件: $LOG_FILE" + echo " • rclone 配置: ~/.config/rclone/rclone.conf" +} + +# 显示帮助 +show_help() { + echo "用法: sudo bash $0 [选项]" + echo "" + echo "选项:" + echo " (无参数) 交互式安装配置" + echo " uninstall 卸载备份服务" + echo " status 查看服务状态" + echo " run 立即执行一次备份" + echo " log 查看备份日志" + echo " help 显示此帮助" +} + +# 主函数 +main() { + case "${1:-}" in + uninstall|--uninstall) + check_root + uninstall + ;; + status|--status) + systemctl status backup-to-gdrive.timer + echo "" + systemctl list-timers backup-to-gdrive.timer + ;; + run|--run) + check_root + systemctl start backup-to-gdrive.service + journalctl -u backup-to-gdrive.service -n 50 --no-pager + ;; + log|--log) + tail -f "$LOG_FILE" + ;; + help|--help|-h) + show_help + ;; + *) + echo "" + echo -e "${BOLD}╔════════════════════════════════════════════════╗${NC}" + echo -e "${BOLD}║ 📦 定时备份到 Google Drive 一键配置脚本 ║${NC}" + echo -e "${BOLD}╚════════════════════════════════════════════════╝${NC}" + + check_root + install_rclone + input_source_dirs + get_config + confirm_config + configure_rclone + create_backup_script + create_systemd_service + show_completion + ;; + esac +} + +main "$@"