mirror 레포 도입

This commit is contained in:
Lectom C Han
2025-12-19 15:10:26 +09:00
parent 73a4a286c2
commit 34eea87207
6 changed files with 78 additions and 17 deletions

View File

@@ -25,7 +25,13 @@ jobs:
mkdir -p ~/.ssh
echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -p 22 172.16.42.118 >> ~/.ssh/known_hosts
ssh-keyscan -p 22 172.16.10.191 >> ~/.ssh/known_hosts
- name: Refresh source mirror repositories (git fetch --mirror)
run: |
set -euo pipefail
echo "Refreshing mirror repos on 172.16.10.191 ..."
ssh engdev@172.16.10.191 'set -euo pipefail; shopt -s nullglob; for repo in */.git; do dir="${repo%/.git}"; echo "Updating ${dir}"; (cd "${dir}" && git fetch --mirror --prune); done'
- name: Backup Branches (pre-scan → decision → execution)
env:
@@ -41,11 +47,17 @@ jobs:
CENTER_ORG="center_dev"
AUTH_HEADER="Authorization: token ${BASE_GITEA_TOKEN}"
SOURCE_SSH_HOST="engdev@172.16.42.118"
SOURCE_SSH_HOST="engdev@172.16.10.191"
ROOT_DIR="$(pwd)"
NOTIFY_WEBHOOK="${NOTIFY_WEBHOOK:-}"
SYNC_TAGS="${SYNC_TAGS:-true}"
TARGET_SEED_DEPTH="${TARGET_SEED_DEPTH:-50}"
if ! [[ "${TARGET_SEED_DEPTH}" =~ ^[0-9]+$ ]] || (( TARGET_SEED_DEPTH <= 0 )); then
echo "::warning::TARGET_SEED_DEPTH(${TARGET_SEED_DEPTH}) is invalid; resetting to 50"
TARGET_SEED_DEPTH=50
fi
CACHE_BASE="${ROOT_DIR}/.cache_sources"
mkdir -p "${CACHE_BASE}"
TOTAL_SUCCESS=0
TOTAL_SKIP=0
TOTAL_ERROR=0
@@ -155,6 +167,7 @@ jobs:
declare -A ENTRY_ALIAS
declare -A ENTRY_REPO
declare -A SOURCE_REPOS
declare -A SOURCE_CACHE_PATH
declare -a ENTRY_KEYS=()
add_entry() {
@@ -210,14 +223,49 @@ jobs:
exit 1
fi
prepare_cache() {
local source_repo="$1" cache_dir="${CACHE_BASE}/${source_repo//\//_}.git"
if [[ ! -d "${cache_dir}" ]]; then
echo "Initializing local cache for ${source_repo} at ${cache_dir}"
if ! git clone --mirror "${SOURCE_SSH_HOST}:${source_repo}" "${cache_dir}"; then
echo "::warning::Failed to clone cache for ${source_repo}"
return 1
fi
else
echo "Refreshing cache for ${source_repo}"
if ! git -C "${cache_dir}" fetch --mirror --prune; then
echo "::warning::Failed to refresh cache for ${source_repo}"
return 1
fi
fi
SOURCE_CACHE_PATH["${source_repo}"]="${cache_dir}"
}
echo "Preparing per-source caches..."
for source_repo in "${!SOURCE_REPOS[@]}"; do
prepare_cache "${source_repo}" || echo "::warning::Cache unavailable for ${source_repo}; will fallback to direct fetch"
done
echo "Step 1) 소스 브랜치 해시 스캔"
for source_repo in "${!SOURCE_REPOS[@]}"; do
cache_dir="${SOURCE_CACHE_PATH[${source_repo}]:-}"
source_repo_url="${SOURCE_SSH_HOST}:${source_repo}"
if [[ -n "${cache_dir}" && -d "${cache_dir}" ]]; then
echo " - using cache: ${cache_dir}"
remote_output=$(git -C "${cache_dir}" for-each-ref --format='%(objectname)\t%(refname)' 'refs/heads/*') || {
echo "::warning::Failed to read heads from cache ${cache_dir}; falling back to remote."
remote_output=$(git ls-remote --heads "${source_repo_url}") || {
echo "::warning::Failed to ls-remote ${source_repo_url}. Entries for this repo may fail."
continue
}
}
else
echo " - ${source_repo_url}"
if ! remote_output=$(git ls-remote --heads "${source_repo_url}"); then
echo "::warning::Failed to ls-remote ${source_repo_url}. Entries for this repo may fail."
continue
fi
fi
while IFS=$'\t' read -r commit ref || [[ -n "${commit}" ]]; do
[[ -z "${commit}" || -z "${ref}" ]] && continue
branch="${ref#refs/heads/}"
@@ -352,8 +400,9 @@ jobs:
echo "Working directory: ${CLONE_DIR}"
if ${just_created}; then
echo "Target repo newly created; cloning source branch for initial push..."
if ! git clone --bare --no-tags --single-branch --branch "${branch_name}" "${SOURCE_SSH_HOST}:${source_repo}" "${CLONE_DIR}"; then
echo "::error::Failed to clone source repository ${SOURCE_SSH_HOST}:${source_repo}"
SOURCE_FETCH_REMOTE="${SOURCE_CACHE_PATH[${source_repo}]:-${SOURCE_SSH_HOST}:${source_repo}}"
if ! git clone --bare --no-tags --single-branch --branch "${branch_name}" "${SOURCE_FETCH_REMOTE}" "${CLONE_DIR}"; then
echo "::error::Failed to clone source repository ${SOURCE_FETCH_REMOTE}"
notify_status "error" "${repo_name}" "${branch_name}" "${backup_mode}" "${start_epoch}" "source clone 실패" "${heads_detail}"
rm -rf "${CLONE_DIR}"
continue
@@ -371,7 +420,8 @@ jobs:
continue
fi
git remote add origin "${GITEA_REMOTE}"
git remote add source "${SOURCE_SSH_HOST}:${source_repo}"
SOURCE_FETCH_REMOTE="${SOURCE_CACHE_PATH[${source_repo}]:-${SOURCE_SSH_HOST}:${source_repo}}"
git remote add source "${SOURCE_FETCH_REMOTE}"
fi
echo "Fetching latest branch '${branch_name}' from source..."

View File

@@ -23,7 +23,13 @@ jobs:
mkdir -p ~/.ssh
echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -p 22 172.16.42.118 >> ~/.ssh/known_hosts
ssh-keyscan -p 22 172.16.10.191 >> ~/.ssh/known_hosts
- name: Refresh source mirror repositories (git fetch --mirror)
run: |
set -euo pipefail
echo "Refreshing mirror repos on 172.16.10.191 ..."
ssh engdev@172.16.10.191 'set -euo pipefail; shopt -s nullglob; for repo in */.git; do dir="${repo%/.git}"; echo "Updating ${dir}"; (cd "${dir}" && git fetch --mirror --prune); done'
- name: Mirror Branches
env:

View File

@@ -1,14 +1,14 @@
# AGENTS
## 목적
- 외부 SSH Git 저장소(`engdev@172.16.42.118:dev_Net8.git`)의 특정 브랜치를 Gitea `center_dev` 조직으로 순차 미러링하는 워크플로우를 관리합니다.
- 외부 SSH Git 저장소(`engdev@172.16.10.191:dev_Net8.git`)의 특정 브랜치를 Gitea `center_dev` 조직으로 순차 미러링하는 워크플로우를 관리합니다.
- 데이터가 크므로 병렬 대신 순차 처리하며, 브랜치별로 대응되는 Gitea 저장소에 강제 푸시합니다.
## 주요 흐름 (mirror.yml 설계 요약)
- 트리거: cron `0 2 * * *``workflow_dispatch`.
- 기본 데이터 소스: `branch_list` 파일을 한 줄씩 읽어 순차 처리.
- 브랜치→저장소 매핑 규칙: `Develop_Net8``base`, `Develop_Net8_*``*` 부분을 저장소 이름으로 사용하며, 그 외 브랜치는 브랜치명을 그대로 저장소 이름으로 사용.
- 실행 단계: 워크플로우 리포지토리 체크아웃 → SSH 키 저장/권한 설정 후 `ssh-keyscan 172.16.42.118` → 각 브랜치마다 Gitea API로 `center_dev` 조직에 저장소 존재 여부 확인(있으면 대상 저장소를 mirror clone 후 source 브랜치를 fetch/pull해 업데이트만, 없으면 저장소 생성 시 기본 브랜치를 `main`으로 설정) → 소스 브랜치를 대상 저장소 `main`으로 강제 푸시 → 푸시 후에도 기본 브랜치를 `main`으로 패치 → 임시 폴더 정리. 오류가 나도 다음 브랜치로 계속 진행하도록 처리.
- 실행 단계: 워크플로우 리포지토리 체크아웃 → SSH 키 저장/권한 설정 후 `ssh-keyscan 172.16.10.191``engdev@172.16.10.191` 홈 내 `.git` 디렉터리를 가진 하위 디렉터리마다 `git fetch --mirror --prune`로 사전 동기화 → 워크플로우 내부에서 소스별 로컬 캐시를 생성/갱신해 동일 커밋의 중복 fetch를 최소화 → 각 브랜치마다 Gitea API로 `center_dev` 조직에 저장소 존재 여부 확인(있으면 대상 저장소를 mirror clone 후 source 브랜치를 fetch/pull해 업데이트만, 없으면 저장소 생성 시 기본 브랜치를 `main`으로 설정) → 소스 브랜치를 대상 저장소 `main`으로 강제 푸시 → 푸시 후에도 기본 브랜치를 `main`으로 패치 → 임시 폴더 정리. 오류가 나도 다음 브랜치로 계속 진행하도록 처리.
## 시크릿/변수
- 현재 요구: `SSH_PRIVATE_KEY`(소스 접근), `GITEA_TOKEN`(Gitea API/푸시), `GITEA_URL`(Gitea 인스턴스 URL).

View File

@@ -8,7 +8,7 @@
## 2. 주요 요구사항
- **소스 저장소**: `engdev@172.16.42.118:dev_Net8.git` (SSH 프로토콜)
- **소스 저장소**: `engdev@172.16.10.191:dev_Net8.git` (SSH 프로토콜)
- **인증**: SSH 비공개 키 (`engdev` -> Gitea Secret `SSH_PRIVATE_KEY`로 저장)
- **대상**: Gitea `center_dev` 조직 (Organization)
- **작업 트리거**: 스케줄링 (Cron) 및 수동 실행 (workflow_dispatch)
@@ -35,7 +35,7 @@ on:
Gitea 저장소의 `Settings > Secrets`에 다음 정보들을 추가해야 합니다.
1. **`SSH_PRIVATE_KEY`**: 소스 Git 저장소(`engdev@172.16.42.118`)에 접근하기 위한 SSH 비공개 키.
1. **`SSH_PRIVATE_KEY`**: 소스 Git 저장소(`engdev@172.16.10.191`)에 접근하기 위한 SSH 비공개 키.
2. **`GITEA_TOKEN`**: Gitea 저장소를 생성하고 코드를 Push 하기 위한 Gitea 개인용 액세스 토큰. 이 토큰은 `center_dev` 조직에 저장소를 생성하고 코드를 쓸 수 있는 권한을 가져야 합니다.
3. **`GITEA_URL`**: Gitea 인스턴스의 전체 URL (예: `https://gitea.yourdomain.com`)
@@ -47,7 +47,7 @@ Gitea 저장소의 `Settings > Secrets`에 다음 정보들을 추가해야 합
2. **SSH 환경 설정**:
- Gitea Secret으로 등록된 `SSH_PRIVATE_KEY`를 runner의 특정 파일(`~/.ssh/id_rsa`)에 저장합니다.
- 저장된 키 파일의 권한을 `600`으로 설정하여 SSH 클라이언트가 사용할 수 있도록 합니다.
- `ssh-keyscan`을 사용하여 소스 Git 서버(`172.16.42.118`)의 호스트 키를 `~/.ssh/known_hosts`에 추가하여, "man-in-the-middle" 공격 경고 없이 SSH 접속이 가능하도록 설정합니다.
- `ssh-keyscan`을 사용하여 소스 Git 서버(`172.16.10.191`)의 호스트 키를 `~/.ssh/known_hosts`에 추가하여, "man-in-the-middle" 공격 경고 없이 SSH 접속이 가능하도록 설정합니다.
3. **미러링 스크립트 실행**:
- `branch_list` 파일을 한 줄씩 읽어 반복문을 실행합니다.

View File

@@ -1,7 +1,7 @@
# Git Repository Mirroring Workflow
## 목적
- 외부 SSH Git 저장소(센터 공용 코드 관리용 깃)의 특정 브랜치를 Gitea `center_dev` 조직으로 순차 복제합니다.
- 외부 SSH Git 저장소(센터 공용 코드 관리용 깃, `engdev@172.16.10.191`)의 특정 브랜치를 Gitea `center_dev` 조직으로 순차 복제합니다.
- 브랜치→저장소 매핑 규칙을 적용해 `main` 브랜치로 강제 푸시하며, 태그 동기화 옵션을 제공합니다.
- `backup.yml`은 소스/타겟의 head 커밋을 먼저 스캔해 건너뜀/증분/신규 여부를 사전 결정하고, `mirror.yml`은 즉시 복제합니다.
@@ -39,7 +39,8 @@
- 입력이 비어 있으면 `branch_list` 전체를 처리
## backup.yml (프리스캔 백업) 동작 개요
1) 입력 또는 `branch_list`를 파싱해 유효 항목을 확정하고, 소스 저장소별로 `git ls-remote --heads`를 한 번씩 수행해 브랜치→커밋 해시를 수집합니다.
1) 입력 또는 `branch_list`를 파싱해 유효 항목을 확정하고, 사전 단계에서 미러 서버(`engdev@172.16.10.191`) 홈 내 `.git` 디렉터리마다 `git fetch --mirror --prune`로 최신 상태로 맞춥니다.
2) 소스별 로컬 캐시(워크플로우 내 생성/갱신)를 이용해 브랜치 해시를 수집하고 필요한 브랜치만 fetch하므로 동일 커밋 다중 브랜치에서도 중복 전송을 줄입니다. 캐시가 없으면 원본을 직접 조회합니다.
2) Gitea에 해당 저장소가 있는지 조회 후 `main` 해시를 확인합니다.
3) 사전 판정: 타겟이 없거나 `main`이 없으면 “신규 백업”, 해시가 같으면 “건너뜀”, 다르면 “증분 백업”으로 결정합니다.
4) 해시 스냅샷과 판정 테이블을 `backup_reports/` 폴더에 `source_heads_*.tsv`, `target_heads_*.tsv`, `decisions_*.tsv`로 기록합니다.

View File

@@ -11,6 +11,9 @@
- [x] `backup.yml`: 프리스캔 기반 백업 워크플로우 신규 작성(소스/타겟 head 해시 기록 → 판정 테이블 생성 → skip/증분/신규 분기 실행), 알림에 heads 정보 포함
- [x] `backup.yml`: 스케줄을 매일 02:00 KST(UTC 17:00)로 설정, `mirror.yml`은 수동 실행만 유지
- [x] 문서: README에 backup.yml 흐름/스케줄과 보고서 경로를 추가하고 mirror.yml을 수동 전용으로 명시
- [x] `backup.yml`: TARGET_SEED_DEPTH를 검증해 0/음수/비숫자 입력 시 50으로 보정, fallback depth 오류 메시지 최소화
- [x] `backup.yml`/`mirror.yml`: 소스 호스트를 172.16.10.191로 교체하고 실행 전 미러 서버 홈 내 모든 .git에 대해 `git fetch --mirror --prune` 수행
- [x] `backup.yml`: 소스별 로컬 캐시(mirror 클론)를 생성/갱신해 동일 커밋 다중 브랜치 처리 시 중복 fetch를 최소화
---
@@ -35,3 +38,4 @@
2025-12-17 09:50:00 KST 추가 업데이트: Gitea API(조회/생성/기본 브랜치 설정)에 최대 3회 재시도(5초 대기)를 추가해 일시 오류 발생 시에도 작업이 이어지도록 개선, README 반영.
2025-12-18 08:55:36 KST 추가 업데이트: 최근 크론 실행에서 푸시 성공 후 set -e가 미처리된 curl 실패에 반응해 Step이 실패한 사례 대응. Gitea API curl 호출이 실패해도 000 코드로 재시도하도록 가드했고, 전체 처리 후 TOTAL_ERROR>0이면 명시적으로 exit 1, 아니면 0으로 종료하도록 종료 코드를 고정해 불필요한 실패를 방지함.
2025-12-19 14:09:09 KST 추가 업데이트: backup.yml을 프리스캔 기반으로 신규 작성하여 소스/타겟 head 해시 스냅샷과 판정 테이블을 TSV로 기록하고 heads 정보를 포함한 알림을 전송하도록 구성. backup.yml 스케줄을 02:00 KST로 설정하고 mirror.yml은 수동 실행 전용으로 유지. README에 신규 워크플로우와 보고서 경로를 반영.
2025-12-19 14:23:07 KST 추가 업데이트: backup.yml에서 TARGET_SEED_DEPTH가 0/음수/비숫자일 때 50으로 자동 보정해 depth 0 fetch 오류와 반복 경고를 방지.