#!/bin/bash # GitLab 数据保护和管理脚本 # 支持完整的 GitLab 服务管理、备份和恢复 set -euo pipefail # 配置变量 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_NAME="${PROJECT_NAME:-gitlab}" GITLAB_DIR="${GITLAB_DIR:-${SCRIPT_DIR}}" GITLAB_DATA_DIR="${GITLAB_DATA_DIR:-~/ops/data/gitlab}" GITLAB_CONFIG_DIR="${GITLAB_CONFIG_DIR:-${GITLAB_DIR}/config}" LOG_FILE="${LOG_FILE:-~/ops/logs/gitlab-protection.log}" BACKUP_DIR="${BACKUP_DIR:-~/ops/backups/gitlab}" DOCKER_COMPOSE_FILE="${DOCKER_COMPOSE_FILE:-${GITLAB_DIR}/docker-compose.yml}" # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # 日志函数 log() { echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "${LOG_FILE}" } info() { log "${BLUE}[INFO]${NC} $1" } success() { log "${GREEN}[SUCCESS]${NC} $1" } warning() { log "${YELLOW}[WARNING]${NC} $1" } error() { log "${RED}[ERROR]${NC} $1" } # 检查必要的依赖 check_dependencies() { local deps=("docker" "docker-compose") for dep in "${deps[@]}"; do if ! command -v "$dep" &> /dev/null; then error "$dep 未安装,请先安装 $dep" exit 1 fi done # 检查 Docker 是否运行 if ! docker info &> /dev/null; then error "Docker 未运行,请启动 Docker Desktop" exit 1 fi } # 创建必要的目录 ensure_directories() { mkdir -p "$(dirname "${LOG_FILE}")" mkdir -p "${BACKUP_DIR}" mkdir -p "$(dirname "${GITLAB_DATA_DIR}")" mkdir -p "${GITLAB_CONFIG_DIR}" } # 检查 GitLab 配置 check_gitlab_config() { if [[ ! -f "${DOCKER_COMPOSE_FILE}" ]]; then error "GitLab Docker Compose 配置文件不存在: ${DOCKER_COMPOSE_FILE}" return 1 fi return 0 } # 服务状态检查 check_service_status() { info "检查 GitLab 服务状态..." local running_containers running_containers=$(docker-compose -f "${DOCKER_COMPOSE_FILE}" ps -q 2>/dev/null || echo "") if [[ -z "$running_containers" ]]; then warning "GitLab 服务未运行" return 1 fi local healthy_containers healthy_containers=$(docker-compose -f "${DOCKER_COMPOSE_FILE}" ps --filter "status=running" --format "table {{.Service}}\t{{.Status}}" 2>/dev/null | grep -c "healthy\|Up" || echo "0") info "运行中的容器数量: $(echo "$running_containers" | wc -l)" info "健康容器数量: $healthy_containers" # 检查 GitLab 主服务 if docker-compose -f "${DOCKER_COMPOSE_FILE}" ps --services --filter "status=running" | grep -q "gitlab"; then info "GitLab 主服务运行正常" else warning "GitLab 主服务未运行" return 1 fi return 0 } # 启动 GitLab 服务 start_services() { info "启动 GitLab 服务..." # 检查配置 if ! check_gitlab_config; then error "GitLab 配置检查失败,无法启动服务" return 1 fi # 确保数据目录存在 mkdir -p "${GITLAB_DATA_DIR}" # 启动服务 cd "${GITLAB_DIR}" docker-compose -f "${DOCKER_COMPOSE_FILE}" up -d # 等待服务启动 info "等待 GitLab 服务启动完成..." info "这可能需要几分钟时间,请耐心等待..." # 等待 GitLab 健康检查通过 local max_wait=300 # 5 分钟 local wait_time=0 while [[ $wait_time -lt $max_wait ]]; do if docker-compose -f "${DOCKER_COMPOSE_FILE}" ps --format "table {{.Service}}\t{{.Status}}" | grep -q "healthy"; then success "GitLab 服务启动成功" return 0 fi info "等待服务启动... ($((wait_time/60))分$((wait_time%60))秒)" sleep 10 wait_time=$((wait_time + 10)) done warning "GitLab 服务可能还在启动中,请稍后检查状态" return 0 } # 停止 GitLab 服务 stop_services() { info "停止 GitLab 服务..." if [[ ! -f "${DOCKER_COMPOSE_FILE}" ]]; then warning "Docker Compose 配置文件不存在,跳过停止操作" return 0 fi cd "${GITLAB_DIR}" docker-compose -f "${DOCKER_COMPOSE_FILE}" down || true success "GitLab 服务已停止" } # 重启 GitLab 服务 restart_services() { info "重启 GitLab 服务..." stop_services sleep 5 start_services } # 健康检查 health_check() { info "执行 GitLab 健康检查..." local status=0 # 检查服务状态 if ! check_service_status; then error "服务状态检查失败" status=1 fi # 检查端口 local ports=("8890" "8929") for port in "${ports[@]}"; do if lsof -i ":$port" &> /dev/null; then info "端口 $port 正在使用" else warning "端口 $port 未被使用" fi done # 检查数据目录 if [[ -d "${GITLAB_DATA_DIR}" ]]; then local data_size data_size=$(du -sh "${GITLAB_DATA_DIR}" 2>/dev/null | cut -f1) info "GitLab 数据目录大小: $data_size" else warning "GitLab 数据目录不存在" status=1 fi # 检查配置目录 if [[ -d "${GITLAB_CONFIG_DIR}" ]]; then info "GitLab 配置目录存在" else warning "GitLab 配置目录不存在" status=1 fi if [[ $status -eq 0 ]]; then success "GitLab 健康检查通过" else error "GitLab 健康检查发现问题" fi return $status } # 获取 GitLab 统计信息 get_stats() { info "获取 GitLab 统计信息..." # 服务状态 check_service_status # 数据目录统计 if [[ -d "${GITLAB_DATA_DIR}" ]]; then echo "" info "=== 数据目录统计 ===" du -sh "${GITLAB_DATA_DIR}"/* 2>/dev/null | sort -hr || true # 仓库数量统计 local repos_dir="${GITLAB_DATA_DIR}/repositories" if [[ -d "$repos_dir" ]]; then local repo_count repo_count=$(find "$repos_dir" -maxdepth 2 -type d -name "*.git" 2>/dev/null | wc -l || echo "0") info "Git 仓库数量: $repo_count" fi fi # 备份文件统计 if [[ -d "${BACKUP_DIR}" ]]; then echo "" info "=== 备份文件统计 ===" ls -lah "${BACKUP_DIR}"/*.tar.gz 2>/dev/null | tail -10 || true fi } # 备份 GitLab 数据 backup_gitlab() { local backup_name="${1:-gitlab-$(date +%Y%m%d-%H%M%S)}" local backup_file="${BACKUP_DIR}/${backup_name}.tar.gz" info "开始备份 GitLab 数据..." # 检查服务状态 local service_running=false if check_service_status &>/dev/null; then service_running=true info "检测到服务运行中,将创建在线备份" fi # 创建备份 local backup_items=() # GitLab 配置 if [[ -d "${GITLAB_CONFIG_DIR}" ]] && [[ -n "$(ls -A "${GITLAB_CONFIG_DIR}" 2>/dev/null)" ]]; then backup_items+=("${GITLAB_CONFIG_DIR}") fi # GitLab 数据目录 if [[ -d "${GITLAB_DATA_DIR}" ]]; then backup_items+=("${GITLAB_DATA_DIR}") fi # Docker Compose 配置 if [[ -f "${DOCKER_COMPOSE_FILE}" ]]; then backup_items+=("${DOCKER_COMPOSE_FILE}") fi if [[ ${#backup_items[@]} -eq 0 ]]; then error "没有找到需要备份的文件或目录" return 1 fi # 执行备份 info "创建备份文件: $backup_file" tar -czf "${backup_file}" "${backup_items[@]}" 2>/dev/null if [[ $? -eq 0 ]]; then # 创建备份元数据 local meta_file="${backup_file}.meta" cat > "${meta_file}" << EOF backup_name="${backup_name}" backup_date="$(date -Iseconds)" backup_size="$(stat -f%z "${backup_file}" 2>/dev/null || stat -c%s "${backup_file}" 2>/dev/null || echo "unknown")" service_running="${service_running}" gitlab_version="16.9.1" items="$(printf '%s;' "${backup_items[@]}")" EOF success "GitLab 备份完成: ${backup_file}" # 显示备份信息 local backup_size backup_size=$(ls -lh "${backup_file}" | awk '{print $5}') info "备份大小: ${backup_size}" # 清理旧备份 (保留最近 10 个) cleanup_old_backups 10 else error "GitLab 备份失败" return 1 fi } # 恢复 GitLab 数据 restore_gitlab() { local backup_file="${1:-}" if [[ -z "$backup_file" ]]; then error "请指定备份文件" show_backups return 1 fi if [[ ! -f "$backup_file" ]]; then error "备份文件不存在: $backup_file" return 1 fi info "开始从备份恢复 GitLab..." # 创建安全备份 local safety_backup="${BACKUP_DIR}/safety-backup-$(date +%Y%m%d-%H%M%S).tar.gz" if [[ -d "${GITLAB_DATA_DIR}" ]] || [[ -d "${GITLAB_CONFIG_DIR}" ]]; then info "创建当前状态的安全备份: $safety_backup" backup_gitlab "safety-backup-$(date +%Y%m%d-%H%M%S)" fi # 停止现有服务 stop_services # 恢复备份 info "恢复备份文件: $backup_file" cd / tar -xzf "$backup_file" 2>/dev/null if [[ $? -eq 0 ]]; then success "GitLab 数据恢复完成" # 启动服务 sleep 5 start_services # 验证恢复结果 if health_check; then success "GitLab 恢复验证通过" else error "GitLab 恢复验证失败,请检查日志" return 1 fi else error "GitLab 数据恢复失败" return 1 fi } # 显示可用备份 show_backups() { info "可用的 GitLab 备份文件:" if [[ -d "${BACKUP_DIR}" ]]; then local backups=($(ls -t "${BACKUP_DIR}"/gitlab-*.tar.gz 2>/dev/null || true)) if [[ ${#backups[@]} -gt 0 ]]; then for i in "${!backups[@]}"; do local backup_file="${backups[$i]}" local backup_size backup_size=$(ls -lh "$backup_file" | awk '{print $5}') local backup_date backup_date=$(stat -f%Sm -t%Y-%m-%d\ %H:%M:%S "$backup_file" 2>/dev/null || stat -c%y "$backup_file" 2>/dev/null | cut -d'.' -f1) printf "%2d. %-40s %10s %s\n" $((i+1)) "$(basename "$backup_file")" "$backup_size" "$backup_date" done else warning "没有找到 GitLab 备份文件" fi else warning "备份目录不存在: ${BACKUP_DIR}" fi } # 清理旧备份 cleanup_old_backups() { local keep_count="${1:-5}" info "清理旧备份文件,保留最近 $keep_count 个..." if [[ -d "${BACKUP_DIR}" ]]; then local old_backups old_backups=($(ls -t "${BACKUP_DIR}"/gitlab-*.tar.gz 2>/dev/null | tail -n +$((keep_count+1)) || true)) if [[ ${#old_backups[@]} -gt 0 ]]; then for backup in "${old_backups[@]}"; do info "删除旧备份: $(basename "$backup")" rm -f "$backup" "${backup}.meta" done success "已清理 ${#old_backups[@]} 个旧备份文件" else info "没有需要清理的旧备份文件" fi fi } # 设置定时任务 setup_cron() { local cron_interval="${1:-"0 3 * * *"}" # 默认每天凌晨 3 点 info "设置 GitLab 备份定时任务..." # 创建 cron 脚本 local cron_script="${SCRIPT_DIR}/gitlab-cron-backup.sh" cat > "$cron_script" << EOF #!/bin/bash # GitLab 自动备份 cron 任务 $(declare -f log info success warning error) $(declare -f check_dependencies ensure_directories backup_gitlab) LOG_FILE="${LOG_FILE}" BACKUP_DIR="${BACKUP_DIR}" GITLAB_DATA_DIR="${GITLAB_DATA_DIR}" GITLAB_CONFIG_DIR="${GITLAB_CONFIG_DIR}" DOCKER_COMPOSE_FILE="${DOCKER_COMPOSE_FILE}" # 执行备份 check_dependencies ensure_directories backup_gitlab EOF chmod +x "$cron_script" # 添加到 crontab local cron_entry="$cron_interval $cron_script >> ${LOG_FILE} 2>&1" (crontab -l 2>/dev/null | grep -v "gitlab-cron-backup" || true; echo "$cron_entry") | crontab - # 设置 launchd 任务 (macOS 原生) local launchd_plist="$HOME/Library/LaunchAgents/com.ljwx.gitlab.backup.plist" cat > "$launchd_plist" << EOF Label com.ljwx.gitlab.backup ProgramArguments $cron_script StartCalendarInterval Hour 3 Minute 0 StandardOutPath ${LOG_FILE}.cron.stdout.log StandardErrorPath ${LOG_FILE}.cron.stderr.log RunAtLoad EOF # 加载 launchd 任务 launchctl unload "$launchd_plist" 2>/dev/null || true launchctl load -w "$launchd_plist" success "GitLab 备份定时任务已设置" info "Cron 任务: $cron_entry" info "Launchd 任务已加载" } # 显示帮助信息 show_help() { cat << EOF GitLab 数据保护和管理脚本 用法: $0 [命令] [参数] 命令: start 启动 GitLab 服务 stop 停止 GitLab 服务 restart 重启 GitLab 服务 status 检查服务状态 health 执行健康检查 stats 显示统计信息 backup [名称] 创建备份 restore [文件] 从备份恢复 list 显示可用备份 cleanup [数量] 清理旧备份 (默认保留 5 个) setup 设置定时任务 help 显示帮助信息 示例: $0 start # 启动服务 $0 backup # 创建备份 $0 backup my-backup # 创建命名备份 $0 restore backup-2023.tar.gz # 从备份恢复 $0 cleanup 10 # 保留最近 10 个备份 环境变量: GITLAB_DIR GitLab 配置目录 (默认: 脚本所在目录) GITLAB_DATA_DIR GitLab 数据目录 (默认: ~/ops/data/gitlab) GITLAB_CONFIG_DIR GitLab 配置目录 (默认: ~/ops/gitlab/config) BACKUP_DIR 备份目录 (默认: ~/ops/backups/gitlab) LOG_FILE 日志文件 (默认: ~/ops/logs/gitlab-protection.log) EOF } # 主函数 main() { local command="${1:-help}" local parameter="${2:-}" # 设置环境变量 export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH" case "$command" in "start") check_dependencies ensure_directories start_services ;; "stop") stop_services ;; "restart") check_dependencies ensure_directories restart_services ;; "status") check_service_status ;; "health") check_dependencies ensure_directories health_check ;; "stats") get_stats ;; "backup") check_dependencies ensure_directories backup_gitlab "$parameter" ;; "restore") check_dependencies ensure_directories restore_gitlab "$parameter" ;; "list"|"ls") show_backups ;; "cleanup") cleanup_old_backups "$parameter" ;; "setup") setup_cron "$parameter" ;; "help"|"-h"|"--help") show_help ;; *) error "未知命令: $command" show_help exit 1 ;; esac } # 如果脚本被直接执行,调用主函数 if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi