#!/bin/bash set -euo pipefail # ============================================================ # OCIFLAB 仓库备份脚本 # 每天从 whitelist.md 拉取仓库列表,克隆/更新仓库,压缩备份 # 需要使用 Access Token 进行认证 # ============================================================ # ---------- 配置区 ---------- GIT_ACCESS_TOKEN="" # 填写你的 Git 访问令牌 WHITELIST_REPO_URL="" # 白名单所在仓库地址 (例: https://git.ociflab.icu/user/repo.git) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BACKUP_DIR="${SCRIPT_DIR}/backup" WORK_DIR="${SCRIPT_DIR}/work" ARCHIVE_DIR="${SCRIPT_DIR}/archives" WHITELIST_CACHE="${SCRIPT_DIR}/whitelist_cache.md" RETENTION_DAYS=7 DATE_STAMP="$(date +%Y%m%d)" # server酱3 配置 SERVERCHAN_UID="" # 填写你的 UID SERVERCHAN_SENDKEY="" # 填写你的 SendKey # ---------- 函数区 ---------- log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" } send_warning() { local title="$1" local desp="$2" if [[ -z "${SERVERCHAN_UID}" ]] || [[ -z "${SERVERCHAN_SENDKEY}" ]]; then log "server酱未配置,跳过推送: ${title}" return 0 fi local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --data-urlencode "title=${title}" --data-urlencode "desp=${desp}" "https://${SERVERCHAN_UID}.push.ft07.com/send/${SERVERCHAN_SENDKEY}.send") if [[ "${resp}" == "200" ]]; then log "警告推送成功: ${title}" else log "警告推送失败(HTTP ${resp}): ${title}" fi } git_with_auth() { if [[ -n "${GIT_ACCESS_TOKEN}" ]]; then git -c "http.https://git.ociflab.icu/.extraHeader=Authorization: Bearer ${GIT_ACCESS_TOKEN}" "$@" else git "$@" fi } fetch_whitelist() { local whitelist_repo_dir="${SCRIPT_DIR}/whitelist_repo" log "正在更新白名单仓库..." if [[ -d "${whitelist_repo_dir}/.git" ]]; then # 更新 if ! git_with_auth -C "${whitelist_repo_dir}" pull --ff-only --quiet 2>&1; then log "更新白名单仓库失败,尝试重新克隆..." rm -rf "${whitelist_repo_dir}" fi fi if [[ ! -d "${whitelist_repo_dir}/.git" ]]; then if ! git_with_auth clone --depth 1 --quiet "${WHITELIST_REPO_URL}" "${whitelist_repo_dir}" 2>&1; then log "克隆白名单仓库失败" fi fi # 读取 whitelist.md 到缓存 if [[ -f "${whitelist_repo_dir}/whitelist.md" ]]; then cp "${whitelist_repo_dir}/whitelist.md" "${WHITELIST_CACHE}" log "whitelist.md 已更新" return 0 fi log "whitelist.md 获取失败,尝试使用前一天缓存..." if [[ -f "${SCRIPT_DIR}/whitelist_cache.md.yesterday" ]]; then cp "${SCRIPT_DIR}/whitelist_cache.md.yesterday" "${WHITELIST_CACHE}" log "已使用前一天缓存" return 0 fi log "前一天缓存也不存在,无法继续" return 1 } parse_whitelist() { grep -Eo 'https://git\.ociflab\.icu/[^[:space:]]+' "${WHITELIST_CACHE}" | sed 's/[[:space:]]*$//' } clone_or_pull_repo() { local repo_url="$1" local repo_name repo_name=$(echo "${repo_url}" | sed 's|https://git\.ociflab\.icu/||' | tr '/' '_') local repo_path="${WORK_DIR}/${repo_name}" if [[ -d "${repo_path}/.git" ]]; then log " 更新: ${repo_url}" if ! git_with_auth -C "${repo_path}" pull --ff-only --quiet 2>&1; then log " 更新失败: ${repo_url},尝试重新克隆..." rm -rf "${repo_path}" if ! git_with_auth clone --depth 1 --quiet "${repo_url}" "${repo_path}" 2>&1; then log " 重新克隆也失败: ${repo_url}" return 1 fi fi else log " 克隆: ${repo_url}" rm -rf "${repo_path}" if ! git_with_auth clone --depth 1 --quiet "${repo_url}" "${repo_path}" 2>&1; then log " 克隆失败: ${repo_url}" return 1 fi fi return 0 } backup_repos() { local whitelist=("$@") local total=${#whitelist[@]} local failed=0 local failed_list="" log "共 ${total} 个仓库待备份" for repo_url in "${whitelist[@]}"; do [[ -z "${repo_url}" ]] && continue if ! clone_or_pull_repo "${repo_url}"; then failed=$((failed + 1)) failed_list="${failed_list}${repo_url}\n" fi done # 检查错误率:超过三分之一则发送警告 if [[ ${total} -gt 0 ]]; then local threshold=$((total / 3)) if [[ ${failed} -gt ${threshold} ]]; then local warn_desp="备份失败 ${failed}/${total} 个仓库,超过三分之一。\n失败列表:\n${failed_list}" send_warning "OCIFLAB备份异常 - ${failed}/${total} 仓库失败" "${warn_desp}" fi fi log "备份完成: 成功 $((total - failed)) 个, 失败 ${failed} 个" } create_archive() { log "正在压缩备份..." mkdir -p "${ARCHIVE_DIR}" local archive_name="ociflab-backup-${DATE_STAMP}.zip" local archive_path="${ARCHIVE_DIR}/${archive_name}" if [[ -d "${WORK_DIR}" ]] && [[ -n "$(ls -A "${WORK_DIR}" 2>/dev/null)" ]]; then cd "${WORK_DIR}" zip -r "${archive_path}" . -q cd "${SCRIPT_DIR}" log "压缩完成: ${archive_path}" else log "工作目录为空,跳过压缩" fi } cleanup_old_archives() { log "清理 ${RETENTION_DAYS} 天前的备份..." find "${ARCHIVE_DIR}" -name "ociflab-backup-*.zip" -type f -mtime +${RETENTION_DAYS} -delete 2>/dev/null || true log "清理完成" } persist_yesterday_whitelist() { if [[ -f "${WHITELIST_CACHE}" ]]; then cp "${WHITELIST_CACHE}" "${SCRIPT_DIR}/whitelist_cache.md.yesterday" fi } # ---------- 主流程 ---------- main() { log "========== OCIFLAB 备份开始 ==========" mkdir -p "${WORK_DIR}" "${ARCHIVE_DIR}" # 1. 拉取白名单 if ! fetch_whitelist; then log "无法获取白名单,退出" send_warning "OCIFLAB备份失败" "无法拉取 whitelist.md 且无前一天缓存" exit 1 fi # 2. 解析仓库列表 mapfile -t repos < <(parse_whitelist) if [[ ${#repos[@]} -eq 0 ]]; then log "白名单为空,退出" send_warning "OCIFLAB备份失败" "whitelist.md 中无有效仓库地址" exit 1 fi # 3. 克隆/更新仓库 backup_repos "${repos[@]}" # 4. 压缩归档 create_archive # 5. 清理旧备份 cleanup_old_archives # 6. 保存今天的白名单供明天备用 persist_yesterday_whitelist log "========== OCIFLAB 备份完成 ==========" } main "$@"