automation
홈서버 운영과 콘텐츠 파이프라인을 한데 모은 개인 자동화 모노레포
개인기획·개발·배포 단독2024.10 — 현재
PythonShellNode.jsGitHub ActionsTailscale
프로젝트 소개
개인 홈서버(24/7 macOS 미니PC) 운영 도구와 콘텐츠 수집·발행 파이프라인을 한 repo 에 모은 모노레포다. scripts/<name>/ 하위 디렉토리 각각이 독립 미니 프로젝트에 가깝다 — SNS 백업, 블로그 콘텐츠 크롤링·발행, 홈서버 상태 대시보드, 발열 감시 로거 등 10여 개를 운영 중이다.
배포 아키텍처
디렉토리 간 결합이 느슨해서 전체 rsync 는 낭비였다. git diff 로 바뀐 폴더만 골라 배포하는 스크립트를 만들고, GitHub Actions 가 Tailscale 로 홈서버 내부망에 직접 접속해 배포까지 자동화했다.
운영하다 보니 배포 대상이 두 종류로 나뉜다는 걸 알게 됐다:
- git 체크아웃형(대시보드류) — CI 가
git fetch && reset --hard <배포 SHA>로 트리 전체를 한 번 정렬한 뒤, 바뀐 폴더만 서비스 재시작. 배포 불변식(체크아웃 = 항상 배포 커밋)을 지켜서 자가 치유되게 만들었다. - rsync형(체크아웃 밖 별도 디렉토리에서 도는 것들) — 파일만 동기화.
이 구분을 안 하고 rsync 만 쓰던 시절엔 오래된 워킹트리 위에 최신 내용이 덮어써지면서 영구적인 drift 가 생기고 git pull 자체가 막히는 문제를 겪었다. 체크아웃형은 reset --hard 로, rsync형은 그대로 두는 걸로 역할을 분리해서 해결했다.
배운 점
배포 파이프라인에서 “코드가 배포됐다”와 “실행 중인 프로세스가 새 코드로 떠 있다”는 다른 얘기라는 걸 여러 번 실감했다. 어떤 배포 스크립트는 여러 단계(빌드 → 데이터 번들 생성 → 재시작)가 순차 의존인데, 중간 단계가 조용히 실패하면 뒤 재시작 자체가 통째로 스킵된다. 겉보기엔 “머지는 됐다”라 착각하기 쉬운데 실제로는 며칠 동안 구버전이 계속 서빙되고 있었던 적도 있었다. 이후로는 CI 성공 여부와 별개로, 실행 중 프로세스의 시작 시각이나 헬스체크 API 응답으로 실제 반영 여부를 다시 확인하는 습관이 생겼다. 상태 파일을 여러 크론 잡이 동시에 non-atomic 하게 덮어쓰다 파일이 깨지는 레이스 컨디션도 겪어서, 이후엔 임시 파일 쓰고 교체하는 atomic write 를 우선 고려하게 됐다.