Files
2fauth-worker/.github/workflows/rclone.yml
T

301 lines
12 KiB
YAML
Raw 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.
name: Backup Cloudflare D1 Database And R2 Bucket via Rclone
on:
schedule:
- cron: "0 5 * * *"
workflow_dispatch:
jobs:
rclone:
name: Secure Backup Pipeline
runs-on: ubuntu-latest
permissions:
contents: read
env:
# --- 全局配置 ---
TZ: 'Asia/Shanghai'
# --- Cloudflare ---
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
# --- Cloudflare D1 Database ---
CLOUDFLARE_D1_DATABASE_ID: ${{ secrets.CLOUDFLARE_D1_DATABASE_ID }}
CLOUDFLARE_D1_DATABASE_NAME: ${{ secrets.CLOUDFLARE_D1_DATABASE_NAME }}
# --- Cloudflare R2 Storage ---
CLOUDFLARE_R2_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
CLOUDFLARE_R2_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
CLOUDFLARE_R2_BUCKET_NAME: ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }}
# --- Cloudflare Zero Trust (Service Token) ---
CLOUDFLARE_ACCESS_CLIENT_ID: ${{ secrets.CLOUDFLARE_ACCESS_CLIENT_ID }}
CLOUDFLARE_ACCESS_CLIENT_SECRET: ${{ secrets.CLOUDFLARE_ACCESS_CLIENT_SECRET }}
# --- Encryption and Retention Policy ---
BACKUP_ENCRYPTION_KEY: ${{ secrets.BACKUP_ENCRYPTION_KEY }}
RETENTION_DAYS: ${{ secrets.BACKUP_RETENTION_DAYS || 30 }}
# --- WebDAV ---
WEBDAV_URL: ${{ secrets.WEBDAV_URL }}
WEBDAV_USER: ${{ secrets.WEBDAV_USER }}
WEBDAV_PASSWORD: ${{ secrets.WEBDAV_PASSWORD }}
WEBDAV_VENDOR: ${{ secrets.WEBDAV_VENDOR || 'other' }}
WEBDAV_BASE_PATH: ${{ secrets.WEBDAV_BASE_PATH }}
# --- Google Drive ---
GDRIVE_TOKEN: ${{ secrets.GDRIVE_TOKEN }}
GDRIVE_CLIENT_ID: ${{ secrets.GDRIVE_CLIENT_ID }}
GDRIVE_CLIENT_SECRET: ${{ secrets.GDRIVE_CLIENT_SECRET }}
GDRIVE_ROOT_FOLDER_ID: ${{ secrets.GDRIVE_ROOT_FOLDER_ID }}
# --- 工具版本 ---
WRANGLER_VERSION: "3.57.1"
steps:
# STEP 1: 安装 Rclone
- name: 🛠️ Setup Tools
uses: AnimMouse/setup-rclone@v1
with:
version: v1.66.0
# STEP 2: 检查配置并注入环境变量
# 将 Rclone 配置直接写入环境变量 (内存配置),不生成实体配置文件
- name: 🛡️ Pre-flight Check & Config Injection
id: config
run: |
# 初始化目标列表
TARGETS=""
# 获取仓库名称 (用于默认路径)
REPO_NAME="${GITHUB_REPOSITORY#*/}"
# --- 1. 检查 Cloudflare 凭据 ---
if [[ -z "$CLOUDFLARE_ACCOUNT_ID" || -z "$CLOUDFLARE_API_TOKEN" ]]; then
echo "::error::Missing Cloudflare credentials!"
exit 1
fi
# --- 2. 配置 WebDAV ---
if [[ -n "$WEBDAV_URL" && -n "$WEBDAV_USER" && -n "$WEBDAV_PASSWORD" ]]; then
echo "✅ WebDAV Detected: Enabling..."
TARGETS+="remote_webdav "
# 注入 Rclone WebDAV 配置
echo "RCLONE_CONFIG_REMOTE_WEBDAV_TYPE=webdav" >> $GITHUB_ENV
echo "RCLONE_CONFIG_REMOTE_WEBDAV_URL=$WEBDAV_URL" >> $GITHUB_ENV
echo "RCLONE_CONFIG_REMOTE_WEBDAV_VENDOR=$WEBDAV_VENDOR" >> $GITHUB_ENV
echo "RCLONE_CONFIG_REMOTE_WEBDAV_USER=$WEBDAV_USER" >> $GITHUB_ENV
# 设置 WebDAV 默认路径
if [[ -z "$WEBDAV_BASE_PATH" ]]; then
WEBDAV_BASE_PATH="$REPO_NAME"
echo "WEBDAV_BASE_PATH=$REPO_NAME" >> $GITHUB_ENV
echo "ℹ️ WEBDAV_BASE_PATH auto-set to: $REPO_NAME"
fi
# 加密 WebDAV 密码
OBSCURED=$(rclone obscure "$WEBDAV_PASSWORD")
echo "RCLONE_CONFIG_REMOTE_WEBDAV_PASS=$OBSCURED" >> $GITHUB_ENV
# 配置 Cloudflare Access 请求头
if [[ -n "$CLOUDFLARE_ACCESS_CLIENT_ID" && -n "$CLOUDFLARE_ACCESS_CLIENT_SECRET" ]]; then
echo "🛡️ Cloudflare Access Token Detected: Configuring headers..."
echo "RCLONE_CONFIG_REMOTE_WEBDAV_HEADERS=CF-Access-Client-Id,$CLOUDFLARE_ACCESS_CLIENT_ID,CF-Access-Client-Secret,$CLOUDFLARE_ACCESS_CLIENT_SECRET" >> $GITHUB_ENV
fi
fi
# --- 3. 配置 Google Drive ---
if [[ -n "$GDRIVE_TOKEN" && -n "$GDRIVE_CLIENT_ID" && -n "$GDRIVE_CLIENT_SECRET" ]]; then
echo "✅ GDrive Detected: Enabling..."
TARGETS+="remote_gdrive "
echo "RCLONE_CONFIG_REMOTE_GDRIVE_TYPE=drive" >> $GITHUB_ENV
echo "RCLONE_CONFIG_REMOTE_GDRIVE_SCOPE=drive" >> $GITHUB_ENV
echo "RCLONE_CONFIG_REMOTE_GDRIVE_TOKEN=$GDRIVE_TOKEN" >> $GITHUB_ENV
echo "RCLONE_CONFIG_REMOTE_GDRIVE_CLIENT_ID=$GDRIVE_CLIENT_ID" >> $GITHUB_ENV
echo "RCLONE_CONFIG_REMOTE_GDRIVE_CLIENT_SECRET=$GDRIVE_CLIENT_SECRET" >> $GITHUB_ENV
if [[ -n "$GDRIVE_ROOT_FOLDER_ID" ]]; then
echo "RCLONE_CONFIG_REMOTE_GDRIVE_ROOT_FOLDER_ID=$GDRIVE_ROOT_FOLDER_ID" >> $GITHUB_ENV
echo "GDRIVE_BASE_PATH=" >> $GITHUB_ENV
else
# 若未设置 Root Folder ID,使用仓库名作为子目录
echo "GDRIVE_BASE_PATH=$REPO_NAME/" >> $GITHUB_ENV
fi
fi
# --- 4. 配置 Cloudflare R2 源 ---
if [[ -n "$CLOUDFLARE_R2_ACCESS_KEY_ID" && -n "$CLOUDFLARE_R2_SECRET_ACCESS_KEY" ]]; then
echo "✅ Cloudflare R2 Credentials Detected: Configuring..."
echo "RCLONE_CONFIG_REMOTE_R2_TYPE=s3" >> $GITHUB_ENV
echo "RCLONE_CONFIG_REMOTE_R2_PROVIDER=Cloudflare" >> $GITHUB_ENV
echo "RCLONE_CONFIG_REMOTE_R2_ACCESS_KEY_ID=$CLOUDFLARE_R2_ACCESS_KEY_ID" >> $GITHUB_ENV
echo "RCLONE_CONFIG_REMOTE_R2_SECRET_ACCESS_KEY=$CLOUDFLARE_R2_SECRET_ACCESS_KEY" >> $GITHUB_ENV
echo "RCLONE_CONFIG_REMOTE_R2_ENDPOINT=https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com" >> $GITHUB_ENV
fi
# --- 5. 检查是否有可用备份目标 ---
if [[ -z "$TARGETS" ]]; then
echo "::error::No backup targets configured! Aborting."
exit 1
fi
# 输出目标列表
echo "BACKUP_TARGETS=$TARGETS" >> $GITHUB_ENV
# STEP 3: 导出并处理 D1 数据库
- name: 📦 Export & Encrypt Cloudflare D1 Database
run: |
# 安装 Wrangler
npm install -g wrangler@${{ env.WRANGLER_VERSION }} --quiet
# 获取数据库名称 (优先使用配置名,否则通过 ID 查询)
if [[ -n "$CLOUDFLARE_D1_DATABASE_NAME" ]]; then
echo "✅ Using configured D1 Database Name: $CLOUDFLARE_D1_DATABASE_NAME"
DB_NAME="$CLOUDFLARE_D1_DATABASE_NAME"
elif [[ -n "$CLOUDFLARE_D1_DATABASE_ID" ]]; then
echo "🔍 Resolving D1 Database Name from ID: $CLOUDFLARE_D1_DATABASE_ID"
DB_NAME=$(npx wrangler d1 list --json | jq -r ".[] | select(.uuid == \"$CLOUDFLARE_D1_DATABASE_ID\") | .name")
if [[ -z "$DB_NAME" || "$DB_NAME" == "null" ]]; then
echo "::error::Could not resolve D1 Name from UUID. Please check ID or configure CLOUDFLARE_D1_DATABASE_NAME."
exit 1
fi
echo "CLOUDFLARE_D1_DATABASE_NAME=$DB_NAME" >> $GITHUB_ENV
else
echo "::error::Neither CLOUDFLARE_D1_DATABASE_NAME nor CLOUDFLARE_D1_DATABASE_ID is configured!"
exit 1
fi
echo "🚀 Exporting: $DB_NAME"
npx wrangler d1 export "$DB_NAME" --remote --output=backup.sql
echo "🗜️ Compressing..."
gzip -9 backup.sql
# 生成带时间戳的文件名
DATE=$(date +'%Y-%m-%d_%H-%M-%S')
if [[ -n "$BACKUP_ENCRYPTION_KEY" ]]; then
echo "🔒 Encrypting (AES-256)..."
FILENAME="backup_${DATE}.sql.gz.enc"
openssl enc -aes-256-cbc -salt -pbkdf2 -iter 100000 \
-in backup.sql.gz -out "$FILENAME" -pass pass:"$BACKUP_ENCRYPTION_KEY"
else
echo "⚠️ Encryption skipped (Key not set)"
FILENAME="backup_${DATE}.sql.gz"
mv backup.sql.gz "$FILENAME"
fi
echo "FILENAME=$FILENAME" >> $GITHUB_ENV
# STEP 4: 同步 D1 备份文件
- name: 🔄 Sync Cloudflare D1 Database to Cloud
run: |
# 允许单次失败,避免中断整个流程
set +e
OVERALL_STATUS=0
for target in $BACKUP_TARGETS; do
echo "---------------------------------------------------"
echo "📡 Processing Remote: [${target}]"
# --- 确定目标路径 ---
if [[ "$target" == "remote_webdav" ]]; then
# WebDAV: 使用 d1-backup 子目录,防止误删
DEST_PATH="${target}:${WEBDAV_BASE_PATH}/d1-backup/${CLOUDFLARE_D1_DATABASE_NAME}/${FILENAME}"
PURGE_PATH="${target}:${WEBDAV_BASE_PATH}/d1-backup/${CLOUDFLARE_D1_DATABASE_NAME}/"
else
# GDrive: 使用 d1-backup 子目录
DEST_PATH="${target}:${GDRIVE_BASE_PATH}d1-backup/${CLOUDFLARE_D1_DATABASE_NAME}/${FILENAME}"
PURGE_PATH="${target}:${GDRIVE_BASE_PATH}d1-backup/${CLOUDFLARE_D1_DATABASE_NAME}/"
fi
# --- 1. 执行上传 ---
echo " 📤 Uploading..."
rclone --config /dev/null copyto "$FILENAME" "$DEST_PATH" --retries 3 --user-agent "Mozilla/5.0"
if [ $? -ne 0 ]; then
echo "::error::❌ Failed to upload to $target"
OVERALL_STATUS=1
continue # 跳过当前目标的后续步骤,继续下一个目标
fi
# --- 2. 清理旧备份 ---
echo " 🧹 Pruning old backups (> ${RETENTION_DAYS}d)..."
rclone --config /dev/null delete "$PURGE_PATH" --min-age ${RETENTION_DAYS}d -v
if [ $? -ne 0 ]; then
echo "::warning::⚠️ Cleanup failed for $target, but upload was successful."
# 清理失败不算核心失败,不修改 OVERALL_STATUS
fi
echo " ✅ Success: $target"
done
echo "---------------------------------------------------"
# 检查整体状态
if [ $OVERALL_STATUS -ne 0 ]; then
echo "::error::One or more backup targets failed."
exit 1
fi
echo "🎉 D1 backup tasks completed successfully."
# STEP 5: 同步 R2 存储桶
- name: 🔄 Sync Cloudflare R2 Bucket to Cloud
# R2 配置存在且有目标时运行 (即使 D1 失败也执行)
if: ${{ !cancelled() && env.CLOUDFLARE_R2_BUCKET_NAME != '' && env.CLOUDFLARE_R2_ACCESS_KEY_ID != '' && env.BACKUP_TARGETS != '' }}
run: |
# 允许单次失败
set +e
OVERALL_STATUS=0
echo "---------------------------------------------------"
echo "📡 Syncing R2 Bucket: [$CLOUDFLARE_R2_BUCKET_NAME]"
for target in $BACKUP_TARGETS; do
echo " ➡️ Target: $target"
# --- 确定目标路径 ---
if [[ "$target" == "remote_webdav" ]]; then
DEST_PATH="${target}:${WEBDAV_BASE_PATH}/r2-backup/${CLOUDFLARE_R2_BUCKET_NAME}"
else
DEST_PATH="${target}:${GDRIVE_BASE_PATH}r2-backup/${CLOUDFLARE_R2_BUCKET_NAME}"
fi
rclone --config /dev/null sync "remote_r2:$CLOUDFLARE_R2_BUCKET_NAME" "$DEST_PATH" \
--verbose \
--transfers 16 \
--checkers 8 \
--retries 3 \
--user-agent "Mozilla/5.0"
if [ $? -ne 0 ]; then
echo "::error::❌ Failed to sync R2 to $target"
OVERALL_STATUS=1
else
echo " ✅ Success: $target"
fi
done
if [ $OVERALL_STATUS -ne 0 ]; then
echo "::error::One or more R2 sync targets failed."
exit 1
fi
echo "🎉 R2 Sync tasks completed."
# STEP 6: 清理本地临时文件
- name: 🧹 Clean up local temp files
if: ${{ always() }}
run: |
echo "Cleaning up local backup files..."
rm -f backup*.sql.gz* backup.sql
echo "✅ Cleanup done."