#!/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 # 展开 ~ 为 home 目录 dir_input="${dir_input/#\~/$HOME}" # 去除末尾的 / dir_input="${dir_input%/}" # 验证目录存在 if [[ ! -d "$dir_input" ]]; then print_error "目录不存在: $dir_input" echo -e " 请检查路径是否正确" continue fi # 检查是否重复 local is_duplicate=false for existing_dir in "${SOURCE_DIRS[@]:-}"; do if [[ "$existing_dir" == "$dir_input" ]]; then is_duplicate=true break fi done 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 "$@"