Files
server-deploy/gitea/migrate.sh
2026-04-07 01:49:16 +08:00

514 lines
17 KiB
Bash
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.
#!/usr/bin/env bash
set -euo pipefail
# ============================================
# Gitea 迁移脚本(旧服务器 → 新服务器)
# 用法:
# bash migrate.sh export # 旧服务器:导出迁移包
# bash migrate.sh import <迁移包路径> # 新服务器:导入迁移包
# bash migrate.sh verify # 迁移后:验证完整性
#
# 迁移流程:
# 旧服务器 → export → scp 传输 → 新服务器 → import → verify
# ============================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
log() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
step() { echo -e "\n${CYAN}========== $* ==========${NC}"; }
confirm() {
echo -en "${YELLOW}[确认]${NC} $* [y/N]: "
read -r reply
[[ "$reply" =~ ^[Yy]$ ]]
}
# ===== 前置检查 =====
preflight() {
if [ "$(id -u)" -ne 0 ]; then
error "请使用 root 用户运行: sudo bash migrate.sh"
exit 1
fi
}
load_env() {
if [ ! -f .env ]; then
error ".env 文件不存在"
exit 1
fi
set -a
source .env
set +a
}
# =============================================================
# 导出模式 — 在旧服务器执行
# =============================================================
do_export() {
step "迁移导出 — 旧服务器"
load_env
GITEA_DATA_DIR="${GITEA_DATA_DIR:-/var/lib/gitea}"
MYSQL_DATA_DIR="${MYSQL_DATA_DIR:-/var/lib/mysql/gitea}"
BACKUP_DIR="${BACKUP_DIR:-/var/backups/gitea}"
DATE=$(date +%Y%m%d_%H%M%S)
EXPORT_DIR="${BACKUP_DIR}/migrate_${DATE}"
EXPORT_ARCHIVE="${BACKUP_DIR}/gitea_migrate_${DATE}.tar.gz"
mkdir -p "$EXPORT_DIR"
# --- 1. 显示当前状态 ---
step "1/6 当前环境信息"
log "域名: ${GITEA_DOMAIN:-未设置}"
log "数据目录: ${GITEA_DATA_DIR}"
log "MySQL 数据: ${MYSQL_DATA_DIR}"
if docker compose ps --status running 2>/dev/null | grep -q gitea; then
GITEA_VER=$(curl -sf http://127.0.0.1:3000/api/v1/version 2>/dev/null | grep -oP '"version"\s*:\s*"\K[^"]+' || echo "未知")
log "Gitea 版本: ${GITEA_VER}"
fi
MYSQL_VER=$(docker compose exec -T db mysql --version 2>/dev/null | grep -oP 'Distrib \K[0-9.]+' || echo "未知")
log "MySQL 版本: ${MYSQL_VER}"
echo ""
warn "导出过程中 Gitea 将停止服务,以保证数据一致性。"
if ! confirm "是否继续?"; then
log "已取消"
rm -rf "$EXPORT_DIR"
exit 0
fi
# --- 2. 停止 Gitea保留 MySQL 用于导出 ---
step "2/6 停止 Gitea 容器"
docker compose stop server 2>/dev/null || true
log "Gitea 已停止"
# --- 3. 导出 MySQL 数据库 ---
step "3/6 导出 MySQL 数据库"
log "使用 mysqldump 导出(比 gitea dump 的 XORM 更可靠)..."
docker compose exec -T db mysqldump \
-u root -p"${DB_ROOT_PASSWORD}" \
--single-transaction \
--routines \
--triggers \
--databases gitea \
> "${EXPORT_DIR}/gitea-db.sql"
DB_SIZE=$(du -h "${EXPORT_DIR}/gitea-db.sql" | cut -f1)
log "数据库导出完成: gitea-db.sql (${DB_SIZE})"
# --- 4. 停止所有容器 ---
step "4/6 停止全部容器"
docker compose down
log "全部容器已停止"
# --- 5. 打包 Gitea 数据 ---
step "5/6 打包 Gitea 数据目录"
if [ -d "$GITEA_DATA_DIR" ]; then
log "正在打包 ${GITEA_DATA_DIR} ..."
tar czf "${EXPORT_DIR}/gitea-data.tar.gz" \
-C "$(dirname "$GITEA_DATA_DIR")" \
"$(basename "$GITEA_DATA_DIR")"
DATA_SIZE=$(du -h "${EXPORT_DIR}/gitea-data.tar.gz" | cut -f1)
log "数据打包完成: gitea-data.tar.gz (${DATA_SIZE})"
else
warn "数据目录 ${GITEA_DATA_DIR} 不存在,跳过"
fi
# --- 5b. 打包部署配置 ---
log "正在打包部署配置..."
tar czf "${EXPORT_DIR}/gitea-config.tar.gz" \
-C "$SCRIPT_DIR" \
--exclude='.git' \
--exclude='data' \
--exclude='backups' \
.env docker-compose.yml .env.example \
$([ -d nginx ] && echo "nginx/") \
$([ -f backup.sh ] && echo "backup.sh") \
$([ -f deploy.sh ] && echo "deploy.sh") \
$([ -f upgrade.sh ] && echo "upgrade.sh") \
$([ -f migrate.sh ] && echo "migrate.sh") \
$([ -f .gitignore ] && echo ".gitignore") \
$([ -f README.md ] && echo "README.md") \
2>/dev/null || true
log "配置打包完成: gitea-config.tar.gz"
# --- 5c. 记录元信息 ---
cat > "${EXPORT_DIR}/migrate-meta.txt" <<EOF
# Gitea 迁移元信息
export_date=${DATE}
gitea_version=${GITEA_VER:-unknown}
mysql_version=${MYSQL_VER:-unknown}
gitea_domain=${GITEA_DOMAIN:-}
gitea_data_dir=${GITEA_DATA_DIR}
mysql_data_dir=${MYSQL_DATA_DIR}
source_hostname=$(hostname)
EOF
# --- 6. 生成最终迁移包 ---
step "6/6 生成迁移包"
tar czf "$EXPORT_ARCHIVE" -C "$BACKUP_DIR" "migrate_${DATE}"
TOTAL_SIZE=$(du -h "$EXPORT_ARCHIVE" | cut -f1)
# 清理临时目录
rm -rf "$EXPORT_DIR"
# 重启服务(旧服务器可继续运行)
echo ""
if confirm "导出完成。是否重启旧服务器的 Gitea"; then
docker compose up -d
log "服务已重启"
fi
echo ""
log "===== 导出完成 ====="
log "迁移包: ${EXPORT_ARCHIVE} (${TOTAL_SIZE})"
echo ""
log "下一步:将迁移包传输到新服务器并执行导入"
echo -e "${CYAN} scp ${EXPORT_ARCHIVE} root@新服务器IP:/opt/gitea/${NC}"
echo -e "${CYAN} # 在新服务器上:${NC}"
echo -e "${CYAN} cd /opt/gitea${NC}"
echo -e "${CYAN} bash migrate.sh import $(basename "$EXPORT_ARCHIVE")${NC}"
}
# =============================================================
# 导入模式 — 在新服务器执行
# =============================================================
do_import() {
local archive="$1"
if [ ! -f "$archive" ]; then
error "迁移包不存在: $archive"
echo "用法: bash migrate.sh import <迁移包路径>"
exit 1
fi
step "迁移导入 — 新服务器"
# --- 1. 解压迁移包 ---
step "1/9 解压迁移包"
WORK_DIR=$(mktemp -d)
tar xzf "$archive" -C "$WORK_DIR"
MIGRATE_DIR=$(find "$WORK_DIR" -maxdepth 1 -type d -name 'migrate_*' | head -1)
if [ -z "$MIGRATE_DIR" ]; then
error "无效的迁移包格式"
rm -rf "$WORK_DIR"
exit 1
fi
# 显示元信息
if [ -f "${MIGRATE_DIR}/migrate-meta.txt" ]; then
log "迁移包信息:"
grep -v '^#' "${MIGRATE_DIR}/migrate-meta.txt" | while IFS='=' read -r key val; do
printf " %-20s %s\n" "$key:" "$val"
done
fi
# --- 2. 检查必要文件 ---
step "2/9 检查迁移包内容"
local has_db=false has_data=false has_config=false
[ -f "${MIGRATE_DIR}/gitea-db.sql" ] && has_db=true
[ -f "${MIGRATE_DIR}/gitea-data.tar.gz" ] && has_data=true
[ -f "${MIGRATE_DIR}/gitea-config.tar.gz" ] && has_config=true
log "数据库导出: $(${has_db} && echo '✓' || echo '✗')"
log "数据目录: $(${has_data} && echo '✓' || echo '✗')"
log "部署配置: $(${has_config} && echo '✓' || echo '✗')"
if ! $has_db || ! $has_data; then
error "迁移包缺少必要文件(需要 gitea-db.sql 和 gitea-data.tar.gz"
rm -rf "$WORK_DIR"
exit 1
fi
echo ""
warn "导入将覆盖当前服务器上的 Gitea 数据。"
if ! confirm "是否继续?"; then
log "已取消"
rm -rf "$WORK_DIR"
exit 0
fi
# --- 3. 恢复配置文件 ---
step "3/9 恢复部署配置"
if $has_config; then
tar xzf "${MIGRATE_DIR}/gitea-config.tar.gz" -C "$SCRIPT_DIR"
log "配置文件已恢复到 ${SCRIPT_DIR}/"
fi
# 加载 .env
load_env
GITEA_DATA_DIR="${GITEA_DATA_DIR:-/var/lib/gitea}"
MYSQL_DATA_DIR="${MYSQL_DATA_DIR:-/var/lib/mysql/gitea}"
# --- 4. 提示修改配置 ---
step "4/9 检查配置"
echo ""
warn "如果新服务器的域名或 IP 与旧服务器不同,请先修改 .env"
echo -e " ${CYAN}vi ${SCRIPT_DIR}/.env${NC}"
echo ""
echo " 当前配置:"
echo " GITEA_DOMAIN=${GITEA_DOMAIN:-未设置}"
echo " GITEA_DATA_DIR=${GITEA_DATA_DIR}"
echo " MYSQL_DATA_DIR=${MYSQL_DATA_DIR}"
echo ""
if ! confirm "配置正确,继续导入?(如需修改请选 N改完后重新运行 import"; then
log "已暂停。修改 .env 后重新运行:"
echo -e " ${CYAN}bash migrate.sh import ${archive}${NC}"
rm -rf "$WORK_DIR"
exit 0
fi
# --- 5. 停止现有服务 ---
step "5/9 停止现有服务"
docker compose down 2>/dev/null || true
# --- 6. 安装基础设施(如新服务器未部署过) ---
step "6/9 检查基础设施"
if ! command -v docker &>/dev/null; then
warn "Docker 未安装。请先运行 deploy.sh 安装基础设施,或手动安装 Docker。"
echo -e " ${CYAN}bash deploy.sh${NC}"
rm -rf "$WORK_DIR"
exit 1
fi
if ! docker compose version &>/dev/null; then
error "Docker Compose V2 未安装"
rm -rf "$WORK_DIR"
exit 1
fi
log "Docker 和 Compose 已就绪"
# --- 7. 恢复 Gitea 数据目录 ---
step "7/9 恢复 Gitea 数据"
mkdir -p "$(dirname "$GITEA_DATA_DIR")"
if [ -d "$GITEA_DATA_DIR" ] && [ "$(ls -A "$GITEA_DATA_DIR" 2>/dev/null)" ]; then
warn "数据目录 ${GITEA_DATA_DIR} 非空"
if confirm "是否清空后恢复?(选 N 则覆盖合并)"; then
rm -rf "${GITEA_DATA_DIR:?}/"*
fi
fi
tar xzf "${MIGRATE_DIR}/gitea-data.tar.gz" -C "$(dirname "$GITEA_DATA_DIR")"
chown -R 1000:1000 "$GITEA_DATA_DIR"
log "Gitea 数据已恢复到 ${GITEA_DATA_DIR}"
# --- 8. 恢复数据库 ---
step "8/9 恢复 MySQL 数据库"
# 确保 MySQL 数据目录存在
mkdir -p "$MYSQL_DATA_DIR"
# 启动 MySQL清空后让其自动初始化
log "启动 MySQL 容器..."
docker compose up -d db
# 等待 MySQL 就绪
log "等待 MySQL 就绪..."
local retries=0
while ! docker compose exec -T db mysqladmin ping -h localhost -u root -p"${DB_ROOT_PASSWORD}" --silent 2>/dev/null; do
retries=$((retries + 1))
if [ "$retries" -ge 60 ]; then
error "MySQL 启动超时60 次重试)"
rm -rf "$WORK_DIR"
exit 1
fi
sleep 2
done
log "MySQL 已就绪"
# 导入数据库
log "正在导入数据库(这可能需要几分钟)..."
docker compose exec -T db mysql \
-u root -p"${DB_ROOT_PASSWORD}" \
--default-character-set=utf8mb4 \
< "${MIGRATE_DIR}/gitea-db.sql"
log "数据库导入完成"
# --- 9. 启动 Gitea 并执行迁移后操作 ---
step "9/9 启动 Gitea 并验证"
docker compose up -d
log "容器已启动,等待 Gitea 就绪..."
local retries=0
while ! curl -sf http://127.0.0.1:3000/api/v1/version &>/dev/null; do
retries=$((retries + 1))
if [ "$retries" -ge 60 ]; then
warn "Gitea 启动超时,请手动检查: docker compose logs server"
break
fi
sleep 2
done
if curl -sf http://127.0.0.1:3000/api/v1/version &>/dev/null; then
GITEA_VER=$(curl -sf http://127.0.0.1:3000/api/v1/version | grep -oP '"version"\s*:\s*"\K[^"]+')
log "Gitea ${GITEA_VER} 启动成功"
fi
# 重新生成 Git Hooks迁移后必须执行
log "重新生成 Git Hooks..."
docker compose exec -u git server \
/usr/local/bin/gitea -c /data/gitea/conf/app.ini admin regenerate hooks || \
warn "regenerate hooks 失败,请手动执行"
# 运行 doctor 检查
log "运行 doctor 诊断..."
docker compose exec -u git server \
/usr/local/bin/gitea -c /data/gitea/conf/app.ini doctor check --all --fix 2>&1 | \
tail -5 || warn "doctor check 返回异常,请检查日志"
# 清理
rm -rf "$WORK_DIR"
echo ""
log "===== 导入完成 ====="
log "Gitea 已在新服务器运行"
echo ""
warn "迁移后请检查以下事项:"
echo " 1. 访问 https://${GITEA_DOMAIN:-你的域名} 确认页面正常"
echo " 2. 如域名/IP 变更,需更新 DNS 解析"
echo " 3. 如需 HTTPS需重新申请 SSL 证书:"
echo -e " ${CYAN}certbot certonly --webroot -w /var/www/certbot -d ${GITEA_DOMAIN:-你的域名}${NC}"
echo " 4. 测试 Git clone / push 操作"
echo " 5. 测试 SSH 克隆: git clone ssh://git@${GITEA_DOMAIN:-你的域名}:${SSH_PORT:-2222}/用户/仓库.git"
echo ""
log "运行验证命令检查完整性: bash migrate.sh verify"
}
# =============================================================
# 验证模式 — 迁移后验证完整性
# =============================================================
do_verify() {
step "迁移后验证"
load_env
GITEA_DATA_DIR="${GITEA_DATA_DIR:-/var/lib/gitea}"
local pass=0 fail=0
check() {
if eval "$2" &>/dev/null; then
echo -e " ${GREEN}${NC} $1"
pass=$((pass + 1))
else
echo -e " ${RED}${NC} $1"
fail=$((fail + 1))
fi
}
echo ""
log "检查服务状态:"
check "Gitea 容器运行中" "docker compose ps --status running | grep -q gitea"
check "MySQL 容器运行中" "docker compose ps --status running | grep -q gitea-db"
log "检查 API:"
check "Gitea API 可达" "curl -sf http://127.0.0.1:3000/api/v1/version"
if curl -sf http://127.0.0.1:3000/api/v1/version &>/dev/null; then
GITEA_VER=$(curl -sf http://127.0.0.1:3000/api/v1/version | grep -oP '"version"\s*:\s*"\K[^"]+')
log " Gitea 版本: ${GITEA_VER}"
fi
log "检查数据库:"
check "MySQL 连接正常" "docker compose exec -T db mysqladmin ping -h localhost -u root -p'${DB_ROOT_PASSWORD}' --silent"
# 检查仓库数量
REPO_COUNT=$(docker compose exec -T db mysql -u root -p"${DB_ROOT_PASSWORD}" -N -e \
"SELECT COUNT(*) FROM gitea.repository;" 2>/dev/null || echo "0")
log " 仓库数量: ${REPO_COUNT}"
USER_COUNT=$(docker compose exec -T db mysql -u root -p"${DB_ROOT_PASSWORD}" -N -e \
"SELECT COUNT(*) FROM gitea.user;" 2>/dev/null || echo "0")
log " 用户数量: ${USER_COUNT}"
log "检查数据目录:"
check "Gitea 数据目录存在" "[ -d '${GITEA_DATA_DIR}/gitea' ]"
check "仓库目录存在" "[ -d '${GITEA_DATA_DIR}/gitea/repositories' ] || [ -d '${GITEA_DATA_DIR}/git/repositories' ]"
check "app.ini 存在" "[ -f '${GITEA_DATA_DIR}/gitea/conf/app.ini' ]"
# 统计仓库目录下的裸仓库数量
if [ -d "${GITEA_DATA_DIR}/gitea/repositories" ]; then
DISK_REPOS=$(find "${GITEA_DATA_DIR}/gitea/repositories" -name "*.git" -type d -maxdepth 3 2>/dev/null | wc -l)
log " 磁盘上的仓库目录: ${DISK_REPOS}"
fi
log "检查网络:"
check "SSH 端口可监听" "docker compose port server 2222"
if command -v nginx &>/dev/null; then
check "Nginx 运行中" "systemctl is-active nginx"
check "Nginx 配置正确" "nginx -t"
else
warn " Nginx 未安装(如需 HTTPS 请运行 deploy.sh 或手动安装)"
fi
echo ""
log "===== 验证结果: ${pass} 通过, ${fail} 失败 ====="
if [ "$fail" -gt 0 ]; then
warn "存在失败项,请检查上方输出并排查问题"
echo " 查看日志: docker compose logs"
return 1
else
log "所有检查通过!迁移成功。"
fi
}
# =============================================================
# 主入口
# =============================================================
usage() {
echo "Gitea 迁移脚本"
echo ""
echo "用法:"
echo " bash migrate.sh export # 旧服务器:导出迁移包"
echo " bash migrate.sh import <迁移包路径> # 新服务器:导入迁移包"
echo " bash migrate.sh verify # 迁移后:验证完整性"
echo ""
echo "迁移流程:"
echo " 1. 旧服务器: bash migrate.sh export"
echo " 2. 传输: scp gitea_migrate_xxx.tar.gz root@新服务器:/opt/gitea/"
echo " 3. 新服务器: bash migrate.sh import gitea_migrate_xxx.tar.gz"
echo " 4. 新服务器: bash migrate.sh verify"
}
main() {
preflight
case "${1:-}" in
export)
do_export
;;
import)
if [ -z "${2:-}" ]; then
error "请指定迁移包路径"
echo "用法: bash migrate.sh import <迁移包路径>"
exit 1
fi
do_import "$2"
;;
verify)
do_verify
;;
-h|--help|help)
usage
;;
*)
usage
exit 1
;;
esac
}
main "$@"