집 밖 백업도 암호화해서: age와 S3로 Git 저장소 보관하기
홈서버의 블로그 원본을 Git bundle로 묶고 age로 암호화해 S3에 백업한다. 올리기 전 암호화, 최소 권한, 실제 복원까지 확인한 기록.
블로그는 AWS에서 보이지만, 글의 원본과 배포 설정은 집 Gitea에 있다. 공개 사이트가 살아 있어도 집 서버와 NAS를 동시에 잃으면 다음 글을 쓸 원본이 없어진다. 그래서 매주 저장소를 집 밖에 한 벌 더 보관한다.
핵심은 S3에 올리기 전에 암호화한다는 것이다. 저장소에는 유료 테마와 운영 문서가 있어, 클라우드 보관 권한과 원문 열람 권한을 분리하고 싶었다.
Git 미러 대신 암호화 파일
GitHub의 비공개 저장소로 미러링할 수도 있다. 하지만 내 목적은 공동 작업이 아니라 재해 복구다. 주 1회 Gitea 저장소를 미러로 받아 Git bundle 파일로 만들고, age 공개키로 암호화한 뒤 S3에 올리기로 했다.
Gitea → mirror clone → Git bundle → age 암호화 → S3
개인키는 운영자에게만
age-keygen -o backup.key로 키 쌍을 만든다. 출력된 age1... 공개키는 백업 작업에 두고, AGE-SECRET-KEY-1... 개인키가 든 파일은 홈서버 밖에 보관한다. 공개키만 가진 CI는 백업을 만들 수 있지만 풀 수는 없다. 다만 CI나 AWS 계정이 침해되면 백업 작업을 방해할 수 있으므로, 암호화가 가용성까지 보장하는 것은 아니다.
매주 백업하는 과정
실제 작업은 임시 디렉터리에서 이 순서로 돈다. 아래는 핵심 명령만 추린 예시다.
git clone --mirror "$REPO_URL" "$WORK/repo.git"
git -C "$WORK/repo.git" bundle create "$WORK/blog.bundle" --all
git -C "$WORK/repo.git" bundle verify "$WORK/blog.bundle"
age -R infra/backup/age-recipients.txt \
-o "$WORK/blog.bundle.age" "$WORK/blog.bundle"
aws s3 cp "$WORK/blog.bundle.age" \
"s3://$BACKUP_BUCKET/repo/2026/blog-날짜-커밋.bundle.age" \
--storage-class STANDARD_IA
실제 스크립트는 인증 토큰, 날짜, 체크섬, 파일명까지 처리한다. git bundle에는 참조가 가리키는 커밋과 Git 객체가 담긴다. 작업 폴더의 미커밋 파일, Git LFS 원본, 서브모듈 저장소, Gitea 사용자·설정, 별도로 올린 미디어는 이 백업 범위 밖이다. bundle verify도 복원 시험을 대신하지 않는다.
올리는 계정의 권한
백업용 IAM 사용자는 버킷의 repo/ 경로에 객체 업로드와 미완료 멀티파트 업로드 중단 권한만 갖는다. 객체 읽기·삭제와 버킷 설정 변경은 거부한다. 파일명에는 시각과 커밋을 넣고 버킷 버전 관리도 켰다. 이전 버전을 일정 기간 뒤 지우는 수명 주기 규칙은 두지 않았다. 처음에는 30일 뒤 삭제 규칙이 있었는데, 그러면 누가 같은 이름으로 덮어쓸 때 원래 백업이 30일 뒤 사라진다. 백업이 1년에 1GB도 안 돼서 규칙을 뺐다.
여기에는 중요한 한계가 있다. 쓰기 권한이 있으면 같은 키로 새 버전을 올릴 수 있다. 시각을 넣은 파일명은 충돌 가능성을 줄이지만 덮어쓰기를 원천 차단하지는 않는다. 업로드 계정이 탈취되어도 기존 버전의 영구 삭제는 막지만, 새로 만든 잘못된 백업이 최신 파일처럼 보일 수 있다. 같은 키의 재사용을 막으려면 S3 조건부 쓰기(If-None-Match: *)를 강제해야 한다. Object Lock은 보존 기간 중 기존 버전의 삭제 방지를 위한 별도 선택지다.
복원해 보고 확인한 것
첫 백업은 약 11MB였다. S3에서 내려받아 개인키로 복호화하고 새 폴더에 복제했다.
age -d -i backup.key -o blog.bundle blog-20260927-....bundle.age
git clone blog.bundle restore-test
git -C restore-test log --oneline -1
git -C restore-test tag --list | wc -l
당시 마지막 커밋과 태그 15개, 파일 구성이 원본과 일치했다. 복원 시험에 쓴 서버에서는 개인키와 임시 파일을 지웠다. 키를 잃으면 백업을 풀 수 없으므로, 홈서버와 독립된 장소 두 곳에 보관하고 주기적으로 복원 시험을 다시 할 계획이다.
비용과 남은 범위
11MB를 매주 한 번씩 쌓으면 원본 크기가 그대로라는 가정에서 연간 약 0.6GB다. 실제 저장량은 저장소 성장과 이전 버전에 따라 달라진다. 이 작업은 S3 Standard-IA를 쓰므로 저장료 외에 요청·복원 요금이 있고, 30일 최소 보관 기간도 적용된다. 현재 규모에서는 작지만, 비용을 정확히 보려면 실제 청구 내역을 확인해야 한다.
이 백업은 블로그 Git 저장소의 재해 복구 수단이다. 사진과 Gitea 서비스 전체 백업은 별도 과제로 남겨 뒀다.
참고: Git bundle 문서, S3 조건부 쓰기, S3 요금