Skill: Config File Versioning
'작고 + 중요하며 + 조용히 덮어쓰여도 모르는' 로컬 설정 파일들을 자동 버전 백업합니다.
감시 중인 파일의 실제 내용이 변경될 때마다 10초 이내에 자동으로 git commit을 생성하여 로컬 separate-git-dir 저장소에 보관합니다. 원클릭으로 원하는 과거 버전으로 롤백할 수 있습니다.
언제 사용하는가
사용자에게 다음과 같은 트리거 신호가 나타나면 이 스킬을 사용합니다:
- "<특정 파일>에 자동 버전/백업/덮어쓰기 방지를 추가해줘"
- "어느 프로그램이 또 내 을 덮어쓰지 않았으면 좋겠어"
- "<설정>이 변경되면 자동 commit 해줘"
- "<기존 백업 디렉토리>를 참고해서 <다른 파일>도 백업해줘"
- 사용자가 주요 설정 파일이 어떤 도구나 실수로 덮어쓰여서 손실된 적이 있고, 앞으로 어떻게 예방할지 묻는 경우
적용되지 않는 경우:
- 대규모 코드 저장소 (일반 git workflow 사용)
- 머신 간 동기화 (이 스킬은 로컬 버전 히스토리만 제공; 동기화하려면 syncthing 추가 또는 수동 git push to private remote)
- 빈번하고 빠르게 변하는 파일 (
.bash_history,history.jsonl등 — 엄청난 수의 commit이 생성됨)
아키텍처 개요
<보호 대상 파일> (실제 위치, 예: ~/.ssh/config)
│ 수정
▼
OS 수준 파일 감시
┌──────────────────────────────┬──────────────────────┬─────────┐
│ macOS: launchd WatchPaths │ Linux: systemd .path │ 기타 │
└──────────────────────────────┴──────────────────────┴─────────┘
│ 트리거 (10초 쓰로틀링 후)
▼
auto-commit.sh
│ git add -A → staged diff가 비어있지 않을 때만 commit
▼
<BACKUP_ROOT>/<DOMAIN>-config.git/ ← 실제 git repo (separate-git-dir)
▲
│ worktree 루트의 ".git"(한 줄 `gitdir:` 텍스트)으로 연결
주요 설계 포인트:
| 설계 | 이유 |
|---|---|
| 각 설정 도메인별 독립 저장소 (하나의 큰 저장소로 통합하지 않음) | 히스토리 깔끔; 개별 활성화/비활성화/마이그레이션 가능 |
| separate-git-dir | worktree 전체가 어떤 프로그램에 의해 삭제되어도 <BACKUP_ROOT>에 히스토리가 남아 있음 |
화이트리스트 .gitignore (* + !<보호 대상 파일>) | 같은 디렉토리의 런타임 데이터/캐시/임시 파일을 실수로 포함하지 않음 |
| 이벤트 기반 감시 + 쓰로틀링 (기본 10초) | 평시에 상주 프로세스 없음; 한 번의 쓰기로 여러 commit이 생성되는 것을 방지 |
commit 스크립트 내 git diff --cached --quiet 검사 | 내용이 실제로 변경되지 않았을 때(mtime만 변경) 조용히 종료 |
구현 단계
단계 0: 사용자와 4가지 변수 합의
| 변수 | 의미 | 권장 기본값 |
|---|---|---|
<DOMAIN> | 설정 도메인 약어 (소문자, 숫자, 하이픈만 사용; 디렉토리명/Label 접미사로 사용) | 사용자와 논의; 예: ssh / zsh / vscode / claude |
<WORKTREE> | 보호 대상 파일이 있는 디렉토리의 절대 경로 (git worktree 루트) | 일반적으로 감시 대상 파일이 있는 디렉토리 |
<FILES> | 감시할 절대 경로 목록 (1개 이상) | 사용자가 지정 |
<BACKUP_ROOT> | 저장소 + 스크립트 + 서비스 템플릿을 저장할 루트 디렉토리 | 반드시 질문: 기본값은 ~/.local/share/config-versioning/<DOMAIN>/ 제안 (XDG 데이터 디렉토리 규약); 사용자가 이미 전용 백업/동기화 디렉토리(예: ~/Documents/backups/, ~/Sync/..., 자신의 샌드박스 디렉토리)를 가지고 있다면 사용자가 결정 |
단계 1: 데이터 디렉토리 생성 + git 저장소 초기화
mkdir -p "<BACKUP_ROOT>"
mkdir -p "<WORKTREE>"
cd "<WORKTREE>" && git init -q -b main
단계 2: 화이트리스트 .gitignore 작성
templates/gitignore-whitelist를 <WORKTREE>/.gitignore로 복사한 다음, <FILES>의 각 항목에 대해 <WORKTREE> 기준 상대 경로를 !<rel-path> 줄로 추가합니다.
예: worktree=~/.ssh, ~/.ssh/config와 ~/.ssh/known_hosts를 보호하려면 다음을 추가:
!config
!known_hosts
단계 3: 초기 commit
cd "<WORKTREE>"
git add -A
git commit -q -m "chore: initial snapshot of <DOMAIN> config"
-c user.name/-c user.email로 강제로 덮어쓰지 마세요. git이 사용자 글로벌 설정을 사용하도록 두세요. 저장소는 순수 로컬이며, push하지 않으므로 커밋 작성자는 중요하지 않습니다.
단계 4: .git 디렉토리를 <BACKUP_ROOT>로 이동하고 gitdir 포인터 남기기
GITDIR="<BACKUP_ROOT>/<DOMAIN>-config.git"
mv "<WORKTREE>/.git" "$GITDIR"
echo "gitdir: $GITDIR" > "<WORKTREE>/.git"
git -C "$GITDIR" config core.worktree "<WORKTREE>"
단계 5: auto-commit 스크립트 복사 + 수정
templates/auto-commit.sh를 <BACKUP_ROOT>/auto-commit.sh로 복사하고, 2개의 플레이스홀더를 다음과 같이 바꿉니다:
| 플레이스홀더 | 바꿀 내용 |
|---|---|
__WORKTREE__ | <WORKTREE> 절대 경로 |
__LOG__ | 로그 경로, macOS는 ~/Library/Logs/config-file-versioning.log, Linux는 ~/.local/state/config-file-versioning.log 권장 |
chmod +x를 실행합니다.
단계 6: 감시 계층 설치 (운영체제별 분기)
6a. macOS (launchd LaunchAgent)
templates/launchagent.plist를 복사하고 플레이스홀더를 바꿉니다:
| 플레이스홀더 | 바꿀 내용 |
|---|---|
__LABEL__ | local.<USER>.<DOMAIN>-config-watch (local. 접두사는 충돌 방지; <USER>는 $USER 사용) |
__SCRIPT_PATH__ | <BACKUP_ROOT>/auto-commit.sh 절대 경로 |
__WATCH_PATHS__ | <FILES>의 각 파일을 <string>...</string> 쌍으로 감싸고 전체를 <array> 안에 배치 |
__LOG__ | 단계 5와 동일 |
두 곳에 작성 (디렉토리 이전 가능하도록):
<BACKUP_ROOT>/<LABEL>.plist(백업 템플릿)~/Library/LaunchAgents/<LABEL>.plist(실제 적용)
로드:
launchctl unload "$HOME/Library/LaunchAgents/<LABEL>.plist" 2>/dev/null
launchctl load "$HOME/Library/LaunchAgents/<LABEL>.plist"
launchctl list | grep "<LABEL>" # 등록 확인
6b. Linux (systemd path unit)
templates/systemd.path와 templates/systemd.service를 ~/.config/systemd/user/로 복사하고, 이름을 <DOMAIN>-config-watch.path 및 <DOMAIN>-config-watch.service로 변경한 후, 파일 내 플레이스홀더를 바꿉니다. 그런 다음:
systemctl --user daemon-reload
systemctl --user enable --now <DOMAIN>-config-watch.path
6c. 기타 / 폴백
'watch + run command'가 가능한 모든 도구를 사용할 수 있습니다:
- gitwatch (크로스 플랫폼 셸):
brew install gitwatch또는apt install gitwatch - fswatch + 자체 관리 데몬:
fswatch <FILES> | xargs -n1 <BACKUP_ROOT>/auto-commit.sh - inotifywait (Linux) + 셸 루프
watcher 프로세스를 해당 플랫폼의 서비스 관리자(OpenRC, runit, launchd, systemd)에 위임합니다.
단계 7: 종단 간 테스트 (필수, 건너뛰지 마세요)
감시 중인 파일 하나를 수정하고, 쓰로틀 간격보다 2초 더 기다린 후 로그와 git log에 각각 하나씩 추가되었는지 확인합니다. 먼저 임시 필드를 추가했다가 다시 제거하여 2개의 commit이 생성될 것으로 예상합니다:
# 예: JSON 파일에 임시 키 _autoCommitTest 추가
# sleep 12 (쓰로틀=10초 가정)
tail -2 "<LOG>"
git -C "<WORKTREE>" log --oneline | head -3
단계 8: README 작성
각 <BACKUP_ROOT>에 README.md를 포함시켜 나중에 이전하기 쉽게 합니다. 최소한 다음 내용을 포함합니다:
- 목적 (이 시스템이 왜 필요한지)
- 파일 목록 표
- 일상 명령어 (히스토리 보기, 롤백, watcher 상태 확인, 로그 보기)
- 유지보수 (새 감시 파일 추가, 쓰로틀 간격 변경)
- 새 머신에서 재구축 단계
- 완전 제거 단계
안티 패턴 (명시적으로 피하기)
| ❌ 잘못된 방법 | ✅ 올바른 방법 |
|---|---|
| 여러 설정 도메인을 같은 git 저장소에 넣기 | 도메인당 하나의 저장소 |
.git을 worktree 내부에 유지 | <BACKUP_ROOT>로 이동하고 gitdir 포인터 사용 |
| 블랙리스트 .gitignore 사용 | 화이트리스트 사용 |
| 종단 간 테스트를 건너뛰고 전달 | 반드시 수행, commit이 실제로 생성되는지 확인 |
plist/service 유닛을 시스템 디렉토리에만 배치하고 <BACKUP_ROOT>에 템플릿을 남기지 않음 | 두 곳 모두 배치하여 디렉토리가 자체 포함되어 마이그레이션 가능하도록 보장 |
| commit할 때 공개 원격으로 push | 금지 — 설정에는 토큰/민감 정보가 포함될 수 있으므로 순수 로컬 저장소 |
auto-commit.sh에 git user.name/email 하드코딩 | git 글로벌 설정에 맡기기; 스크립트에 개인 정보를 포함하지 않음 |
| fswatch를 KeepAlive=true로 상주 실행 | 이벤트 기반(WatchPaths / .path unit) 우선 사용, 상주 프로세스 없음 |
경계 케이스 처리
- worktree 디렉토리가 존재하지 않는 경우: 먼저
mkdir -p를 실행합니다. 처음이라면 감시 대상 파일 자체도 존재하지 않을 수 있습니다 — 먼저 빈 파일을 생성할지 확인합니다. - 감시 대상 파일이 심볼릭 링크인 경우: watcher에서 링크 경로 와 실제 대상 경로를 동시에 감시하여, 일부 쓰기 방식이 둘 중 하나만 트리거하는 경우를 방지합니다.
- 감시 대상 파일이 바이너리인 경우: 정상 작동하지만(git이 blob 저장), diff로 내용을 볼 수 없습니다.
- 동일한 Label/서비스가 이미 존재하는 경우: 새 것을 로드하기 전에 먼저 기존 것을 unload/disable합니다.
- 쓰로틀 기간 내에 여러 번 쓰기가 발생하는 경우: 쓰로틀 기간 내의 여러 변경 사항은 단일 commit으로 병합됩니다 (의도된 동작입니다).
- 원자적 쓰기(write-temp + rename): launchd WatchPaths는 일반적으로 올바르게 트리거합니다. 특정 프로그램의 쓰기 방식으로 인해 commit이 누락되는 경우, 사용자에게 KeepAlive + gitwatch / fswatch 메인 루프 사용을 권장합니다.
템플릿 파일
templates/auto-commit.sh— 자동 commit 스크립트 뼈대templates/gitignore-whitelist— 화이트리스트 .gitignore 템플릿templates/launchagent.plist— macOS LaunchAgenttemplates/systemd.path+templates/systemd.service— Linux systemd path unit
설계 철학
- 가시성 + 가독성 + 수동 편집 가능: 버전 히스토리는 텍스트 git입니다. git을 아는 사람이라면 누구나 위치를 찾고 롤백할 수 있으며, GUI에 의존하지 않습니다.
- 이벤트 기반 ≫ 폴링: 유휴 CPU/디스크 소모를 줄입니다.
- 실패 안전: commit 스크립트는 부작용이 없습니다 — cd 실패, diff가 비어도 조용히 종료되며 worktree를 손상시키지 않습니다.
- 디렉토리 자체 포함:
<BACKUP_ROOT>에는 데이터 + 스크립트 + plist/unit 템플릿 + README가 모두 포함되어 있어, 디렉토리 전체를 복사하여 새 머신에서 재구축할 수 있습니다. - push 금지: 민감한 설정이 원격에 업로드되는 것을 방지합니다. 원격 백업이 필요하면 파일 수준 솔루션(Time Machine / Backblaze / 암호화된 syncthing)을 사용하세요.