247 lines
7.8 KiB
Bash
247 lines
7.8 KiB
Bash
#!/bin/bash
|
|
# =============================================================================
|
|
# Gitea 备份脚本 (套件版 / Docker 版 通用)
|
|
# =============================================================================
|
|
# 用途: 定期备份 Gitea 数据,防再次出现"卸载清空"惨案
|
|
#
|
|
# 备份内容:
|
|
# - Gitea 配置文件 (app.ini)
|
|
# - SQLite 数据库
|
|
# - 所有仓库 (git bare repos)
|
|
# - LFS 数据
|
|
# - 附件 / avatars
|
|
#
|
|
# 用法:
|
|
# sudo bash scripts/backup-gitea.sh # 默认备份到 /volume1/backups/gitea/
|
|
# sudo bash scripts/backup-gitea.sh /path/to/backup # 自定义备份目录
|
|
# sudo bash scripts/backup-gitea.sh --keep 30 # 保留 30 天
|
|
# sudo bash scripts/backup-gitea.sh --restore latest # 恢复最新备份(谨慎)
|
|
#
|
|
# Cron 建议 (每天凌晨 3 点):
|
|
# 0 3 * * * /volume1/docker/wecom-it-desk/scripts/backup-gitea.sh >> /var/log/gitea-backup.log 2>&1
|
|
# =============================================================================
|
|
|
|
set -e
|
|
|
|
# 颜色
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
|
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
|
|
|
# 默认参数
|
|
BACKUP_DIR="${1:-/volume1/backups/gitea}"
|
|
KEEP_DAYS=7
|
|
RESTORE=""
|
|
|
|
for arg in "$@"; do
|
|
case $arg in
|
|
--keep) KEEP_DAYS="$2"; shift 2 ;;
|
|
--restore) RESTORE="$2"; shift 2 ;;
|
|
esac
|
|
done
|
|
|
|
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
|
BACKUP_NAME="gitea-backup-${TIMESTAMP}"
|
|
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}"
|
|
|
|
# =============================================================================
|
|
# 路径探测
|
|
# =============================================================================
|
|
detect_gitea_paths() {
|
|
info "探测 Gitea 数据目录..."
|
|
|
|
# 套件版默认路径
|
|
if [ -d "/volume1/@appdata/gitea" ]; then
|
|
GITEA_HOME="/volume1/@appdata/gitea"
|
|
info " 套件版: $GITEA_HOME"
|
|
# Docker 版常见路径
|
|
elif [ -d "/volume1/docker/gitea" ]; then
|
|
GITEA_HOME="/volume1/docker/gitea"
|
|
info " Docker 版: $GITEA_HOME"
|
|
else
|
|
error "未找到 Gitea 数据目录,请手动指定 GITEA_HOME 环境变量"
|
|
fi
|
|
|
|
# 找配置和数据子目录
|
|
for sub in gitea data config repos lfs avatars; do
|
|
if [ -d "$GITEA_HOME/$sub" ]; then
|
|
declare -g "GITEA_${sub^^}=$GITEA_HOME/$sub"
|
|
fi
|
|
done
|
|
|
|
# 找 SQLite 数据库
|
|
for db in "$GITEA_HOME/gitea/data/gitea.db" "$GITEA_HOME/data/gitea.db"; do
|
|
[ -f "$db" ] && GITEA_DB="$db" && break
|
|
done
|
|
|
|
if [ -z "$GITEA_DB" ]; then
|
|
warn "未找到 SQLite 数据库,可能用 MySQL/Postgres(此脚本不备份 DB)"
|
|
else
|
|
info " 数据库: $GITEA_DB"
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# 备份模式
|
|
# =============================================================================
|
|
do_backup() {
|
|
info "=== Gitea 备份开始 ==="
|
|
info "时间: $TIMESTAMP"
|
|
info "目标: $BACKUP_PATH"
|
|
|
|
mkdir -p "$BACKUP_PATH"
|
|
detect_gitea_paths
|
|
|
|
# 1. 备份 app.ini 配置
|
|
if [ -f "$GITEA_HOME/gitea/conf/app.ini" ]; then
|
|
mkdir -p "$BACKUP_PATH/conf"
|
|
cp "$GITEA_HOME/gitea/conf/app.ini" "$BACKUP_PATH/conf/"
|
|
ok "备份配置 app.ini"
|
|
fi
|
|
|
|
# 2. 备份 SQLite(必须先停 Gitea,或用 .backup 命令)
|
|
if [ -n "$GITEA_DB" ] && [ -f "$GITEA_DB" ]; then
|
|
# 用 sqlite3 .backup(支持热备)
|
|
if command -v sqlite3 &> /dev/null; then
|
|
sqlite3 "$GITEA_DB" ".backup '$BACKUP_PATH/gitea.db'"
|
|
ok "SQLite 热备完成 (sqlite3 .backup)"
|
|
else
|
|
warn "无 sqlite3 命令,改用文件复制(可能不一致)"
|
|
cp "$GITEA_DB" "$BACKUP_PATH/gitea.db"
|
|
fi
|
|
fi
|
|
|
|
# 3. 备份仓库 (bare git)
|
|
if [ -d "$GITEA_HOME/gitea/repos" ]; then
|
|
info "备份仓库目录(可能大)..."
|
|
tar -czf "$BACKUP_PATH/repos.tar.gz" \
|
|
-C "$GITEA_HOME/gitea" repos 2>/dev/null || \
|
|
warn "仓库备份失败(可能权限不足,需 sudo)"
|
|
ok "仓库 tar 完成: $(du -h "$BACKUP_PATH/repos.tar.gz" 2>/dev/null | cut -f1)"
|
|
fi
|
|
|
|
# 4. 备份 LFS
|
|
if [ -d "$GITEA_HOME/gitea/lfs" ]; then
|
|
tar -czf "$BACKUP_PATH/lfs.tar.gz" \
|
|
-C "$GITEA_HOME/gitea" lfs 2>/dev/null || warn "LFS 备份失败"
|
|
fi
|
|
|
|
# 5. 备份附件 / avatars
|
|
if [ -d "$GITEA_HOME/gitea/avatars" ]; then
|
|
cp -r "$GITEA_HOME/gitea/avatars" "$BACKUP_PATH/" 2>/dev/null || true
|
|
fi
|
|
|
|
# 6. 备份元信息
|
|
cat > "$BACKUP_PATH/backup.meta" <<EOF
|
|
# Gitea 备份元信息
|
|
timestamp=$TIMESTAMP
|
|
hostname=$(hostname)
|
|
gitea_home=$GITEA_HOME
|
|
db_path=$GITEA_DB
|
|
backup_size=$(du -sh "$BACKUP_PATH" | cut -f1)
|
|
git_rev=$(cd "$GITEA_HOME" 2>/dev/null && git rev-parse HEAD 2>/dev/null || echo "N/A")
|
|
EOF
|
|
|
|
# 7. 打包成单文件(方便转存)
|
|
info "打包最终备份..."
|
|
tar -czf "${BACKUP_PATH}.tar.gz" -C "$BACKUP_DIR" "$BACKUP_NAME"
|
|
rm -rf "$BACKUP_PATH"
|
|
ok "最终备份: ${BACKUP_PATH}.tar.gz"
|
|
ok " 大小: $(du -h "${BACKUP_PATH}.tar.gz" | cut -f1)"
|
|
|
|
# 8. 清理旧备份
|
|
info "清理 $KEEP_DAYS 天前的旧备份..."
|
|
local cleaned=0
|
|
while IFS= read -r old; do
|
|
rm -f "$old"
|
|
cleaned=$((cleaned+1))
|
|
done < <(find "$BACKUP_DIR" -maxdepth 1 -name "gitea-backup-*.tar.gz" -mtime +$KEEP_DAYS)
|
|
ok "清理旧备份: $cleaned 个"
|
|
|
|
info "=== 备份完成 ==="
|
|
info "建议: 把 ${BACKUP_PATH}.tar.gz scp 到异地(例 NAS2 / 阿里云 OSS / 本地电脑)"
|
|
}
|
|
|
|
# =============================================================================
|
|
# 恢复模式
|
|
# =============================================================================
|
|
do_restore() {
|
|
if [ -z "$RESTORE" ]; then
|
|
error "未指定要恢复的备份名(--restore latest|YYYYMMDD-HHMMSS)"
|
|
fi
|
|
|
|
if [ "$RESTORE" = "latest" ]; then
|
|
RESTORE_FILE=$(ls -t "$BACKUP_DIR"/gitea-backup-*.tar.gz 2>/dev/null | head -1)
|
|
[ -z "$RESTORE_FILE" ] && error "找不到备份文件"
|
|
else
|
|
RESTORE_FILE="$BACKUP_DIR/gitea-backup-${RESTORE}.tar.gz"
|
|
[ -f "$RESTORE_FILE" ] || error "备份文件不存在: $RESTORE_FILE"
|
|
fi
|
|
|
|
warn "!!! 恢复操作会覆盖当前 Gitea 数据 !!!"
|
|
warn " 备份文件: $RESTORE_FILE"
|
|
warn " 按 Ctrl+C 取消,或 5 秒后继续"
|
|
sleep 5
|
|
|
|
info "解压备份..."
|
|
TMP_DIR=$(mktemp -d)
|
|
tar -xzf "$RESTORE_FILE" -C "$TMP_DIR"
|
|
BACKUP_CONTENT=$(ls "$TMP_DIR" | head -1)
|
|
[ -z "$BACKUP_CONTENT" ] && error "备份文件内容为空"
|
|
|
|
detect_gitea_paths
|
|
EXTRACT_DIR="$TMP_DIR/$BACKUP_CONTENT"
|
|
|
|
# 停 Gitea(套件 / Docker)
|
|
info "停 Gitea..."
|
|
if command -v synopkg &> /dev/null; then
|
|
sudo synopkg stop Gitea 2>/dev/null || true
|
|
fi
|
|
docker stop gitea 2>/dev/null || true
|
|
|
|
# 恢复 app.ini
|
|
if [ -f "$EXTRACT_DIR/conf/app.ini" ]; then
|
|
cp "$EXTRACT_DIR/conf/app.ini" "$GITEA_HOME/gitea/conf/app.ini"
|
|
ok "恢复 app.ini"
|
|
fi
|
|
|
|
# 恢复 DB
|
|
if [ -f "$EXTRACT_DIR/gitea.db" ]; then
|
|
cp "$EXTRACT_DIR/gitea.db" "$GITEA_DB"
|
|
ok "恢复 SQLite"
|
|
fi
|
|
|
|
# 恢复 repos
|
|
if [ -f "$EXTRACT_DIR/repos.tar.gz" ]; then
|
|
rm -rf "$GITEA_HOME/gitea/repos"
|
|
tar -xzf "$EXTRACT_DIR/repos.tar.gz" -C "$GITEA_HOME/gitea/"
|
|
ok "恢复 repos"
|
|
fi
|
|
|
|
# 启动 Gitea
|
|
info "启动 Gitea..."
|
|
if command -v synopkg &> /dev/null; then
|
|
sudo synopkg start Gitea
|
|
fi
|
|
docker start gitea 2>/dev/null || true
|
|
|
|
rm -rf "$TMP_DIR"
|
|
ok "恢复完成"
|
|
}
|
|
|
|
# =============================================================================
|
|
# 入口
|
|
# =============================================================================
|
|
if [ -n "$RESTORE" ]; then
|
|
do_restore
|
|
else
|
|
do_backup
|
|
fi
|