1. 必要的准备工作

创建必要的访问令牌和SSH密钥

  1. GitHub Token
    • 创建一个具有 repo 权限的个人访问令牌(PAT)
    • 访问:GitHub → Settings → Developer settings → Personal access tokens
  2. GitHub SSH 密钥
    • 创建一个SSH密钥对用于访问GitHub仓库
    • 运行:ssh-keygen -t rsa -b 4096 -f github_id_rsa
    • 将公钥添加到GitHub账户
  3. GitLab API Token
    • 创建一个具有API访问权限的GitLab个人访问令牌
    • 访问:GitLab → Preferences → Access Tokens
  4. GitLab SSH 密钥
    • 创建一个SSH密钥对用于访问GitLab仓库
    • 运行:ssh-keygen -t rsa -b 4096 -f gitlab_id_rsa
    • 将公钥添加到GitLab账户

2. 在GitLab中设置CI/CD变量

在GitLab项目中,设置以下CI/CD变量:

变量名 描述 是否受保护
GITHUB_TOKEN GitHub个人访问令牌
GITHUB_SSH_PRIVATE_KEY GitHub SSH私钥内容
GITLAB_API_TOKEN GitLab API访问令牌
GITLAB_SSH_PRIVATE_KEY GitLab SSH私钥内容
GITLAB_USER_NAME 用于提交的Git用户名
GITLAB_USER_EMAIL 用于提交的Git邮箱

3. 设置定时任务

  1. 在GitLab项目中,前往 CI/CD → Schedules
  2. 点击 "New schedule" 按钮
  3. 设置如下参数:
    • 描述:GitHub仓库每周镜像
    • 间隔模式:自定义,设置为 0 2 * * 1(每周一凌晨2点)
    • 目标分支mainmaster(取决于你的默认分支)
    • 启用:确保勾选了这一选项

4. 将配置文件添加到GitLab项目

将以下.gitlab-ci.yml文件添加到你的GitLab项目中。你可以通过Web界面或者Git命令行来完成这一步。

.gitlab-ci.yml的CI CD配置示例:

# 每周一自动将 GitHub 仓库镜像到 GitLab

stages:
  - mirror

variables:
  # 设置时区为亚洲/上海
  TZ: "Asia/Shanghai"
  # 设置 Git 拉取深度,0表示完整克隆(包含所有历史记录)
  GIT_DEPTH: 0
  # 设置 Git 策略为克隆,确保每次作业都有一个干净的工作区
  GIT_STRATEGY: clone
  # 超时设置,避免长时间运行
  MIRROR_TIMEOUT: 3600

mirror_github_repos:
  stage: mirror
  image: alpine:latest
  # 仅在每周一运行
  only:
    - schedules
  # 设置最大运行时间为1小时
  timeout: 1h
  # 设置作业可重试次数和间隔
  retry:
    max: 2
    when:
      - runner_system_failure
      - stuck_or_timeout_failure
  
  before_script:
    # 安装所需的工具
    - apk update
    - apk add --no-cache git curl jq openssh-client bash
    # 配置 Git
    - git config --global user.name "${GITLAB_USER_NAME}"
    - git config --global user.email "${GITLAB_USER_EMAIL}"
    # 设置 SSH 配置
    - mkdir -p ~/.ssh
    - echo "${GITHUB_SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa_github
    - echo "${GITLAB_SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa_gitlab
    - chmod 600 ~/.ssh/id_rsa_github ~/.ssh/id_rsa_gitlab
    - ssh-keyscan github.com >> ~/.ssh/known_hosts
    - ssh-keyscan ${CI_SERVER_HOST} >> ~/.ssh/known_hosts
    # 创建SSH配置文件以区分不同主机的密钥
    - |
      cat > ~/.ssh/config << EOF
      Host github.com
        HostName github.com
        User git
        IdentityFile ~/.ssh/id_rsa_github
        IdentitiesOnly yes
        
      Host ${CI_SERVER_HOST}
        HostName ${CI_SERVER_HOST}
        User git
        IdentityFile ~/.ssh/id_rsa_gitlab
        IdentitiesOnly yes
      EOF
    - chmod 600 ~/.ssh/config
    # 创建工作目录
    - mkdir -p /tmp/mirror_workspace
    - cd /tmp/mirror_workspace

  script:
    - |
      # 创建日志文件
      LOG_FILE="/tmp/mirror_log_$(date +%Y%m%d).log"
      echo "开始镜像任务: $(date)" > $LOG_FILE
      
      # 获取所有GitHub仓库(包括私有仓库)
      echo "正在获取GitHub仓库列表..." | tee -a $LOG_FILE
      REPOS=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" "https://api.github.com/user/repos?per_page=100" | jq -r '.[] | .ssh_url + "," + .name')
      if [ -z "$REPOS" ]; then
        echo "错误: 无法获取GitHub仓库列表,请检查TOKEN权限" | tee -a $LOG_FILE
        exit 1
      fi
      
      # 计数器
      TOTAL_REPOS=$(echo "$REPOS" | wc -l)
      SUCCESS_COUNT=0
      FAILED_COUNT=0
      
      echo "发现 $TOTAL_REPOS 个仓库需要镜像" | tee -a $LOG_FILE
      
      # 处理每个仓库
      echo "$REPOS" | while IFS="," read -r REPO_URL REPO_NAME; do
        echo "============================================" | tee -a $LOG_FILE
        echo "开始处理仓库: $REPO_NAME" | tee -a $LOG_FILE
        
        # 创建GitLab仓库URL
        GITLAB_URL="git@${CI_SERVER_HOST}:${CI_PROJECT_NAMESPACE}/${REPO_NAME}.git"
        
        # 检查GitLab上是否已存在该仓库
        REPO_EXISTS=$(curl -s -H "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" "${CI_API_V4_URL}/projects/${CI_PROJECT_NAMESPACE}%2F${REPO_NAME}" | jq -r '.id')
        
        # 如果仓库不存在,则创建
        if [ "$REPO_EXISTS" == "null" ]; then
          echo "GitLab上不存在该仓库,正在创建: $REPO_NAME" | tee -a $LOG_FILE
          CREATE_RESPONSE=$(curl -s -H "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" \
            -X POST "${CI_API_V4_URL}/projects" \
            -d "name=${REPO_NAME}&visibility=private")
          
          # 验证创建是否成功
          NEW_REPO_ID=$(echo $CREATE_RESPONSE | jq -r '.id')
          if [ "$NEW_REPO_ID" == "null" ]; then
            echo "创建仓库失败: $REPO_NAME, 错误: $(echo $CREATE_RESPONSE | jq -r '.message')" | tee -a $LOG_FILE
            FAILED_COUNT=$((FAILED_COUNT + 1))
            continue
          fi
          echo "仓库创建成功,ID: $NEW_REPO_ID" | tee -a $LOG_FILE
        else
          echo "GitLab上已存在该仓库,将进行更新: $REPO_NAME" | tee -a $LOG_FILE
        fi
        
        # 克隆GitHub仓库
        echo "正在克隆GitHub仓库: $REPO_NAME..." | tee -a $LOG_FILE
        if ! git clone --mirror "$REPO_URL" "/tmp/mirror_workspace/$REPO_NAME"; then
          echo "克隆失败: $REPO_NAME" | tee -a $LOG_FILE
          FAILED_COUNT=$((FAILED_COUNT + 1))
          continue
        fi
        
        # 进入仓库目录
        cd "/tmp/mirror_workspace/$REPO_NAME"
        
        # 推送到GitLab
        echo "正在推送到GitLab: $REPO_NAME..." | tee -a $LOG_FILE
        git push --mirror "$GITLAB_URL" || {
          # 处理冲突
          echo "推送失败,可能存在冲突,尝试强制更新..." | tee -a $LOG_FILE
          git fetch --prune origin
          git update-ref -d refs/remotes/origin/HEAD 2>/dev/null || true
          
          # 第二次尝试推送
          if ! git push --force --mirror "$GITLAB_URL"; then
            echo "推送失败: $REPO_NAME" | tee -a $LOG_FILE
            FAILED_COUNT=$((FAILED_COUNT + 1))
            cd ..
            continue
          fi
        }
        
        # 确保仓库是私有的
        echo "确保GitLab仓库是私有的..." | tee -a $LOG_FILE
        curl -s -H "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" \
          -X PUT "${CI_API_V4_URL}/projects/${CI_PROJECT_NAMESPACE}%2F${REPO_NAME}" \
          -d "visibility=private" > /dev/null
        
        echo "成功镜像仓库: $REPO_NAME" | tee -a $LOG_FILE
        SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
        
        # 清理
        cd ..
        rm -rf "/tmp/mirror_workspace/$REPO_NAME"
      done
      
      # 统计并记录结果
      echo "============================================" | tee -a $LOG_FILE
      echo "镜像任务完成: $(date)" | tee -a $LOG_FILE
      echo "总仓库数: $TOTAL_REPOS" | tee -a $LOG_FILE
      echo "成功: $SUCCESS_COUNT" | tee -a $LOG_FILE
      echo "失败: $FAILED_COUNT" | tee -a $LOG_FILE
      
      # 如果有失败的仓库,退出码不为0
      if [ $FAILED_COUNT -gt 0 ]; then
        echo "有 $FAILED_COUNT 个仓库镜像失败,请查看日志" | tee -a $LOG_FILE
        exit 1
      fi

  after_script:
    # 在任务完成后上传日志文件作为构建工件
    - |
      if [ -f "/tmp/mirror_log_$(date +%Y%m%d).log" ]; then
        mkdir -p mirror_logs
        cp "/tmp/mirror_log_$(date +%Y%m%d).log" mirror_logs/
        echo "日志文件已保存,可在作业工件中查看"
      fi

  # 保存日志文件
  artifacts:
    paths:
      - mirror_logs/
    expire_in: 1 week
    when: always
❤️转载请注明出处❤️