backuptool/back.sh
2026-02-10 08:53:32 -05:00

584 lines
18 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 "$@"