mirror 레포 도입
This commit is contained in:
@@ -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,13 +223,48 @@ 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}"
|
||||
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
|
||||
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
|
||||
@@ -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..."
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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` 파일을 한 줄씩 읽어 반복문을 실행합니다.
|
||||
|
||||
@@ -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`로 기록합니다.
|
||||
|
||||
4
to-do.md
4
to-do.md
@@ -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 오류와 반복 경고를 방지.
|
||||
|
||||
Reference in New Issue
Block a user