Skill: Config File Versioning
「小規模+重要+知らないうちに上書きされたくない」ローカル設定ファイルを自動バージョン管理します。
監視対象ファイルの実際の内容が変化するたび、10 秒以内に自動で git commit が作成され、ローカルの独立した git リポジトリ(separate-git-dir)に保存されます。ワンクリックで任意の過去バージョンにロールバックできます。
使用タイミング
ユーザーが以下のトリガーシグナルのいずれかを発した場合に本スキルを使用します:
- 「<某ファイル> に自動バージョン管理/バックアップ/上書き防止を追加して」
- 「いつかまた某プログラムが私の <某 dotfile> を上書きしそうで心配」
- 「<某 config> が変更されたら自動 commit して」
- 「<既存のバックアップディレクトリ> を参考にして、<他のファイル> もバックアップして」
- ユーザーが重要な config をツールや手作業で上書き・消失した経験を話し、今後どう防ぐか質問している
適用外のケース:
- 大規模コードリポジトリ(通常の git workflow で十分)
- マシン間の同期(本スキルはローカルバージョン履歴のみ。同期が必要な場合は syncthing または手動 git push でプライベートリモートへ)
- 頻繁かつ高速に変化するファイル(
.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"(1行の gitdir: テキスト)で接続
主要な設計ポイント:
| 設計 | 理由 |
|---|---|
| 設定ドメインごとに独立したリポジトリ(1つの大リポジトリにまとめない) | 履歴がクリーン;独立して有効化/無効化/移行可能 |
| separate-git-dir | worktree 全体がプログラムに削除されても、履歴は <BACKUP_ROOT> に残る |
ホワイトリスト .gitignore(* + !<保護対象ファイル>) | 同じディレクトリ内のランタイムデータ/キャッシュ/一時ファイルを誤って取り込まない |
| イベント駆動監視+スロットル(デフォルト10秒) | 通常時は常駐プロセスなし;1回の書き込みで複数 commit が発生するのを防ぐ |
commit スクリプト内で git diff --cached --quiet チェック | 内容に実際の変更がない場合(mtime のみ変更)は静かに終了 |
実装手順
手順 0:ユーザーと 4 つの変数を確認する
| 変数 | 意味 | 推奨デフォルト値 |
|---|---|---|
<DOMAIN> | 設定ドメインの短縮名(小文字英数字とハイフンのみ;ディレクトリ名/ラベルサフィックスとして使用) | ユーザーと相談;例: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> からの相対パスを !<相対パス> の形で 1 行ずつ追加します。
例:worktree=~/.ssh、保護対象が ~/.ssh/config と ~/.ssh/known_hosts の場合:
!config
!known_hosts
手順 3:初期コミットを作成する
cd "<WORKTREE>"
git add -A
git commit -q -m "chore: initial snapshot of <DOMAIN> config"
-c user.name/-c user.emailで強制上書きしないこと。ユーザーのグローバル git 設定を使わせる。リポジトリは純粋にローカルで、プッシュもしないため、コミッター情報は重要ではない。
手順 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:監視層をインストールする(OS 別)
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 と同じ |
2 か所に書き込みます(ディレクトリ移行のため):
<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. その他 / フォールバック
「監視 + コマンド実行」ができる任意のツールで OK:
- gitwatch(クロスプラットフォームシェル):
brew install gitwatchまたはapt install gitwatch - fswatch + 手動デーモン:
fswatch <FILES> | xargs -n1 <BACKUP_ROOT>/auto-commit.sh - inotifywait(Linux)+ shell ループ
watcher プロセスは対応プラットフォームのサービス管理機能(OpenRC、runit、launchd、systemd)で管理します。
手順 7:エンドツーエンドテスト(必須、スキップ不可)
監視対象ファイルを変更し、スロットル間隔より 2 秒長く待って、ログと git log にそれぞれ 1 件追加されたことを確認します。まず一時フィールドを追加し、その後削除すると、2 つの commit が生成されるはずです:
# 例:JSON ファイルに一時キー _autoCommitTest を追加
# sleep 12(スロットル = 10s の場合)
tail -2 "<LOG>"
git -C "<WORKTREE>" log --oneline | head -3
手順 8:README を書く
各 <BACKUP_ROOT> には README.md を配置し、将来の移行に備えます。最低限以下の内容を含めます:
- 目的(なぜこの仕組みがあるのか)
- ファイル一覧表
- 日常コマンド(履歴参照、ロールバック、watcher 状態確認、ログ確認)
- メンテナンス(新しい監視ファイルの追加、スロットル間隔の変更)
- 新しいマシンでの再構築手順
- 完全なアンインストール手順
アンチパターン(明確に避けるべきこと)
| ❌ 間違ったやり方 | ✅ 正しいやり方 |
|---|---|
| 複数の config ドメインを 1 つの git リポジトリに詰め込む | ドメインごとに 1 リポジトリ |
.git を worktree 内に残す | <BACKUP_ROOT> に移動し、gitdir ポインタを使用 |
| ブラックリスト型 .gitignore | ホワイトリスト型 .gitignore |
| エンドツーエンドテストをスキップして納品 | 必ず実施し、commit が実際に生成されることを確認 |
plist/service ユニットをシステムディレクトリにのみ配置し、<BACKUP_ROOT> にテンプレートを残さない | 両方に配置し、ディレクトリが自己完結で移行可能であることを保証 |
| commit 時に公開リモートにプッシュする | 禁止——設定にはトークンや機密情報が含まれる可能性がある。純粋にローカルリポジトリ |
auto-commit.sh に git user.name/email をハードコードする | git のグローバル設定に任せる。スクリプトに個人情報を含めない |
| KeepAlive=true で fswatch を常駐させる | イベント駆動(WatchPaths / .path unit)を優先。常駐プロセスは不要 |
エッジケースの処理
- worktree ディレクトリが存在しない場合:先に
mkdir -pする。初回の場合、監視対象ファイル自体も存在しない可能性がある——空ファイルを先に作成するかどうか確認する。 - 監視対象ファイルがシンボリックリンクの場合:watcher でリンクパスとリンク先の実パスの両方を監視し、書き込み方法によって一方がトリガーされない問題を防ぐ。
- 監視対象ファイルがバイナリの場合:動作はする(git は blob として保存)が、diff では内容を確認できない。
- 同じ Label/サービスがすでに存在する場合:先に旧設定を unload/disable してから新しいものをロードする。
- スロットル期間内に複数回書き込みがあった場合:スロットルウィンドウ内の複数変更は 1 回のコミットにまとめられる(意図した動作)。
- アトミック書き込み(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 がすべて含まれており、ディレクトリごとコピーすれば新しいマシンで復元可能。 - プッシュしない:機密設定がリモートにアップロードされるのを防止。リモートバックアップが必要な場合は、ファイルレベルのソリューション(Time Machine / Backblaze / 暗号化 syncthing)を使用する。