上传文件至 /

This commit is contained in:
chunzhi 2026-02-10 08:53:32 -05:00
parent 7ca6329661
commit 2ae6b63bdf

583
back.sh Normal file
View File

@ -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 "$@"