- backup-gitea.sh: gitea dump(저장소+SQLite+설정을 일관된 시점으로) | S3 업로드 볼륨 tar 대신 gitea dump를 쓴 이유를 주석에 명시 (실행 중 tar는 시점이 어긋날 수 있음) - zip 무결성(unzip -t)·크기 검증 후에만 업로드 - systemd timer 매일 03:10 KST (DB 백업 10분 뒤 — 겹치지 않게) - IAM 정책에 gitea/ 경로 추가 + 복원용 GetObject (여전히 해당 버킷 두 경로로만 제한) - S3 수명주기에 gitea/ 30일 규칙 추가 - BACKUP.md: Gitea 복원 절차·검증법·'무엇이 이 서버에만 있는가' 관점 정리 검증: 백업 2.8MB, zip 무결성 OK, 백업 속 저장소에서 clone → HEAD·커밋20·파일269 운영과 일치 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
3.6 KiB
Bash
68 lines
3.6 KiB
Bash
#!/bin/bash
|
|
# 이 파일이 하는 일: 사내 Git 서버(Gitea)를 통째로 백업해 S3에 올린다.
|
|
# systemd timer가 매일 새벽 3시 10분(KST)에 실행한다. (DB 백업 10분 뒤 — 겹치지 않게)
|
|
#
|
|
# 학습 포인트 ① — 왜 볼륨을 그냥 tar로 묶지 않고 `gitea dump`를 쓰나?
|
|
# Gitea는 세 가지가 한 몸이다: 저장소 파일(/data/git) + SQLite DB(이슈·PR·계정) + 설정.
|
|
# 돌아가는 중에 tar로 묶으면 "파일은 새 것, DB는 옛 것" 같은 어긋난 시점이 섞일 수 있다.
|
|
# gitea dump는 이 셋을 일관된 시점으로 묶어 zip 하나로 만들어 준다.
|
|
# → 교훈: 애플리케이션이 자기 백업 도구를 제공하면 그걸 먼저 찾아본다.
|
|
# "파일만 복사하면 되겠지"가 통하지 않는 경우가 생각보다 많다(DB, 검색엔진 등).
|
|
#
|
|
# 학습 포인트 ② — 무엇을 지키려는 백업인가?
|
|
# 소스 코드 자체는 개발자 PC마다 clone 사본이 있어 사실 덜 급하다(git의 분산 구조!).
|
|
# 진짜 잃으면 아픈 건 Gitea에만 있는 것들: 이슈, PR 리뷰 코멘트, 계정, 권한 설정.
|
|
# "무엇이 이 서버에만 있는가"를 묻는 것이 백업 설계의 출발점이다.
|
|
set -euo pipefail
|
|
|
|
BUCKET="awesomedev-mirim-backup-apne2"
|
|
CONTAINER="mirim-gitea"
|
|
BACKUP_DIR="/home/ec2-user/backups"
|
|
LOCAL_KEEP_DAYS=7 # 로컬 사본 보관 (S3는 30일 — 수명주기 규칙이 자동 삭제)
|
|
|
|
TIMESTAMP="$(date +%Y-%m-%d_%H%M%S)"
|
|
FILENAME="gitea-${TIMESTAMP}.zip"
|
|
LOCAL_PATH="${BACKUP_DIR}/${FILENAME}"
|
|
|
|
mkdir -p "$BACKUP_DIR"
|
|
|
|
echo "[$(date '+%F %T')] Gitea 백업 시작"
|
|
|
|
# ── 1) 컨테이너 안에서 dump 생성 ──
|
|
# 학습 포인트: -u git — Gitea 프로세스를 돌리는 사용자로 실행해야 파일 권한이 꼬이지 않는다.
|
|
# --file 로 이름을 지정하고, /tmp에 만든다(컨테이너가 지워지면 같이 사라지는 임시 공간).
|
|
docker exec -u git "$CONTAINER" gitea dump --file "/tmp/${FILENAME}" --quiet --work-path /data/gitea
|
|
|
|
# ── 2) 컨테이너 밖으로 꺼내기 ──
|
|
# 학습 포인트: docker cp 는 컨테이너 안팎으로 파일을 옮긴다.
|
|
# 컨테이너는 언제든 지워질 수 있는 존재라, 백업을 그 안에 두면 백업이 아니다.
|
|
docker cp "${CONTAINER}:/tmp/${FILENAME}" "$LOCAL_PATH"
|
|
docker exec -u git "$CONTAINER" rm -f "/tmp/${FILENAME}" # 컨테이너 안 임시본은 지운다
|
|
|
|
# ── 3) 결과 검증 ──
|
|
# 명령이 성공했다고 결과물이 쓸모 있는 건 아니다. 크기와 zip 무결성을 확인한다.
|
|
SIZE=$(stat -c%s "$LOCAL_PATH")
|
|
if [ "$SIZE" -lt 10000 ]; then
|
|
echo "[오류] 백업 파일이 너무 작습니다(${SIZE} bytes). 실패로 간주하고 중단합니다."
|
|
rm -f "$LOCAL_PATH"
|
|
exit 1
|
|
fi
|
|
# unzip -t : 압축 파일이 깨지지 않았는지 검사 (풀지 않고 검증만)
|
|
if ! unzip -tq "$LOCAL_PATH" > /dev/null 2>&1; then
|
|
echo "[오류] zip 무결성 검사 실패. 손상된 백업이므로 올리지 않습니다."
|
|
rm -f "$LOCAL_PATH"
|
|
exit 1
|
|
fi
|
|
echo " 덤프 완료: ${FILENAME} ($(numfmt --to=iec "$SIZE")) — zip 무결성 OK"
|
|
|
|
# ── 4) S3 업로드 ──
|
|
# 액세스 키가 이 파일 어디에도 없다 — EC2에 붙인 IAM 역할이 임시 자격증명을 자동 제공한다.
|
|
aws s3 cp "$LOCAL_PATH" "s3://${BUCKET}/gitea/${FILENAME}" --only-show-errors
|
|
echo " S3 업로드 완료: s3://${BUCKET}/gitea/${FILENAME}"
|
|
|
|
# ── 5) 오래된 로컬 사본 정리 ──
|
|
find "$BACKUP_DIR" -name "gitea-*.zip" -mtime "+${LOCAL_KEEP_DAYS}" -delete
|
|
echo " 로컬 사본 ${LOCAL_KEEP_DAYS}일 초과분 정리 완료"
|
|
|
|
echo "[$(date '+%F %T')] Gitea 백업 성공 ✅"
|