Fertigkeit: Config File Versioning
Erstellt automatische Versions-Backups für "kleine + wichtige + nicht unbemerkt überschreibbare" lokale Konfigurationsdateien.
Bei jeder tatsächlichen Inhaltsänderung einer überwachten Datei wird innerhalb von 10 Sekunden automatisch ein Git-Commit erstellt, der in einem lokalen, separaten Git-Repository gespeichert wird. Ein-Klick-Rollback auf jede historische Version.
Wann verwenden
Verwende diese Fertigkeit, wenn der Benutzer eines der folgenden Auslösesignale von sich gibt:
- "Füge für automatische Versionierung/Backup/Überschreibschutz hinzu"
- "Ich möchte nicht, dass ein Programm wieder meine überschreibt"
- "Automatischer Commit, wenn sich ändert"
- "Referenziere und erstelle Backup für "
- Der Benutzer erwähnt, dass eine wichtige Konfiguration durch ein Tool oder versehentlich überschrieben/verloren wurde und fragt, wie man das in Zukunft verhindern kann.
Nicht geeignet für:
- Große Code-Repositories (hier reicht der normale Git-Workflow)
- Maschinenübergreifende Synchronisation (diese Fertigkeit erstellt nur lokale Versionshistorie; für Synchronisation zusätzlich Syncthing oder manuellen Git-Push zu einem privaten Remote verwenden)
- Häufig sich ändernde Dateien (
.bash_history,history.jsonlusw. – das würde zu vielen Commits führen)
Architekturübersicht
<Geschützte Datei> (tatsächlicher Pfad, z.B. ~/.ssh/config)
│ Änderung
▼
OS-Dateiüberwachung
┌──────────────────────────────────┬──────────────────────┬─────────┐
│ macOS: launchd WatchPaths │ Linux: systemd .path │ Sonstige│
└──────────────────────────────────┴──────────────────────┴─────────┘
│ Auslösung (nach 10s Drosselung)
▼
auto-commit.sh
│ git add -A → nur wenn staged Diff nicht leer ist, commit
▼
<BACKUP_ROOT>/<domain>-config.git/ ← eigentliches Git-Repo (separate-git-dir)
▲
│ Verbindung durch ".git" im Worktree (eine Zeile `gitdir: ...`)
Wichtige Designentscheidungen:
| Design | Begründung |
|---|---|
| Jeder Config-Bereich eigenes Repository (nicht in einem großen Repository zusammengefasst) | Saubere Historie; unabhängiges Aktivieren/Deaktivieren/Migrieren möglich |
| Separate-git-dir | Wenn das Worktree-Verzeichnis durch ein Programm gelöscht wird, bleibt die Historie im <BACKUP_ROOT> erhalten |
Whitelist .gitignore (* + !<geschützte Datei>) | Verhindert das versehentliche Aufnehmen von Laufzeitdaten, Caches oder temporären Dateien im selben Verzeichnis |
| Ereignisgesteuerte Überwachung + Drosselung (Standard 10s) | Kein ständig laufender Prozess im Leerlauf; vermeidet mehrere Commits bei einem Schreibvorgang |
Prüfung im Commit-Skript: git diff --cached --quiet | Wenn sich der Inhalt nicht geändert hat (nur mtime), wird still beendet |
Implementierungsschritte
Schritt 0: 4 Variablen mit dem Benutzer abstimmen
| Variable | Bedeutung | Empfohlener Standardwert |
|---|---|---|
<DOMAIN> | Kurzname des Konfigurationsbereichs (nur Kleinbuchstaben, Ziffern, Bindestriche – als Verzeichnisname/Label-Suffix) | Mit Benutzer besprechen; z.B. ssh / zsh / vscode / claude |
<WORKTREE> | Absoluter Pfad des Verzeichnisses mit der zu schützenden Datei (Git-Worktree-Root) | Normalerweise das Verzeichnis, in dem sich die zu überwachende Datei befindet |
<FILES> | Liste der absoluten Pfade der zu überwachenden Dateien (≥ 1) | Vom Benutzer festgelegt |
<BACKUP_ROOT> | Stammverzeichnis für Repository + Skripte + Service-Vorlagen | Muss erfragt werden: Standardempfehlung ~/.local/share/config-versioning/<DOMAIN>/ (XDG-Datenverzeichnis-Konvention); falls der Benutzer bereits ein dediziertes Backup-/Sync-Verzeichnis hat (z.B. ~/Documents/backups/, ~/Sync/..., eigene Sandbox), entscheidet der Benutzer |
Schritt 1: Datenverzeichnis erstellen + Git-Repo initialisieren
mkdir -p "<BACKUP_ROOT>"
mkdir -p "<WORKTREE>"
cd "<WORKTREE>" && git init -q -b main
Schritt 2: Whitelist .gitignore schreiben
Kopiere templates/gitignore-whitelist nach <WORKTREE>/.gitignore und füge für jeden Pfad in <FILES> relativ zu <WORKTREE> eine Zeile !<relativer-Pfad> hinzu.
Beispiel: worktree=~/.ssh, zu schützen: ~/.ssh/config und ~/.ssh/known_hosts, füge hinzu:
!config
!known_hosts
Schritt 3: Ersten Commit erstellen
cd "<WORKTREE>"
git add -A
git commit -q -m "chore: initial snapshot of <DOMAIN> config"
Nicht
-c user.name/-c user.emailverwenden, um die globale Git-Konfiguration zu überschreiben – lass Git die Benutzereinstellungen verwenden. Das Repository ist rein lokal, wird nicht gepusht, der Autor ist unwichtig.
Schritt 4: .git-Verzeichnis nach <BACKUP_ROOT> verschieben und Gitdir-Zeiger setzen
GITDIR="<BACKUP_ROOT>/<DOMAIN>-config.git"
mv "<WORKTREE>/.git" "$GITDIR"
echo "gitdir: $GITDIR" > "<WORKTREE>/.git"
git -C "$GITDIR" config core.worktree "<WORKTREE>"
Schritt 5: Auto-Commit-Skript kopieren und anpassen
Kopiere templates/auto-commit.sh nach <BACKUP_ROOT>/auto-commit.sh und ersetze die 2 Platzhalter:
| Platzhalter | Ersetzen durch |
|---|---|
__WORKTREE__ | Absoluter Pfad von <WORKTREE> |
__LOG__ | Pfad für Logdatei, empfohlen ~/Library/Logs/config-file-versioning.log (macOS) oder ~/.local/state/config-file-versioning.log (Linux) |
chmod +x darauf ausführen.
Schritt 6: Überwachungsebene installieren (nach Betriebssystem verzweigen)
6a. macOS (launchd LaunchAgent)
Kopiere templates/launchagent.plist und ersetze die Platzhalter:
| Platzhalter | Ersetzen durch |
|---|---|
__LABEL__ | local.<BENUTZER>.<DOMAIN>-config-watch (Präfix local. vermeidet Konflikte; <BENUTZER> mit $USER ersetzen) |
__SCRIPT_PATH__ | Absoluter Pfad von <BACKUP_ROOT>/auto-commit.sh |
__WATCH_PATHS__ | Jede Datei in <FILES> in ein Paar <string>...</string> einschließen, alles in <array> |
__LOG__ | Wie in Schritt 5 |
Zwei Kopien erstellen (zur Sicherstellung der Portabilität):
<BACKUP_ROOT>/<LABEL>.plist(Backup-Vorlage)~/Library/LaunchAgents/<LABEL>.plist(tatsächlich aktiv)
Laden:
launchctl unload "$HOME/Library/LaunchAgents/<LABEL>.plist" 2>/dev/null
launchctl load "$HOME/Library/LaunchAgents/<LABEL>.plist"
launchctl list | grep "<LABEL>" # Überprüfen, ob registriert
6b. Linux (systemd path unit)
Kopiere templates/systemd.path und templates/systemd.service nach ~/.config/systemd/user/ und benenne sie in <DOMAIN>-config-watch.path und <DOMAIN>-config-watch.service um. Ersetze die Platzhalter in den Dateien. Dann:
systemctl --user daemon-reload
systemctl --user enable --now <DOMAIN>-config-watch.path
6c. Sonstige / Fallback
Jedes Tool, das "watch + Befehl ausführen" kann, ist geeignet:
- gitwatch (plattformübergreifend, Shell):
brew install gitwatchoderapt install gitwatch - fswatch + eigener Daemon:
fswatch <FILES> | xargs -n1 <BACKUP_ROOT>/auto-commit.sh - inotifywait (Linux) + Shell-Schleife
Den Watcher-Prozess dem entsprechenden Service-Manager der Plattform anvertrauen (OpenRC, runit, launchd, systemd).
Schritt 7: End-to-End-Test (zwingend, nicht überspringbar)
Eine überwachte Datei ändern, etwas länger als das Drosselungsintervall warten (z.B. 12 Sekunden bei 10s Drosselung), dann Log und Git-Log überprüfen, ob jeweils ein Eintrag hinzugekommen ist. Zuerst ein temporäres Feld einfügen, dann wieder entfernen – erwartet werden 2 Commits:
# Beispiel: JSON-Datei mit temporärem Schlüssel _autoCommitTest
# sleep 12 (bei Drosselung=10s)
tail -2 "<LOG>"
git -C "<WORKTREE>" log --oneline | head -3
Schritt 8: README schreiben
Jedes <BACKUP_ROOT> sollte eine eigene README.md enthalten, um zukünftige Migrationen zu erleichtern. Mindestens enthalten:
- Zweck (warum dieses Setup existiert)
- Tabelle der Dateien
- Tägliche Befehle (Historie anzeigen, Rollback, Watcher-Status prüfen, Log anzeigen)
- Wartung (neue Überwachungsdatei hinzufügen, Drosselungsintervall ändern)
- Schritte zum Wiederaufbau auf einem neuen Rechner
- Vollständige Deinstallationsschritte
Anti-Patterns (explizit vermeiden)
| ❌ Falscher Ansatz | ✅ Richtig |
|---|---|
| Mehrere Config-Bereiche in dasselbe Git-Repository packen | Ein Bereich ein Repository |
.git im Worktree belassen | Nach <BACKUP_ROOT> verschieben und Gitdir-Zeiger verwenden |
Blacklist .gitignore verwenden | Whitelist verwenden |
| End-to-End-Test überspringen | Zwingend durchführen, sicherstellen, dass Commits tatsächlich erstellt werden |
Plist/Service-Unit nur im Systemverzeichnis ablegen, nicht als Vorlage im <BACKUP_ROOT> | An beiden Orten ablegen, damit das Verzeichnis autark und migrierbar ist |
| Beim Commit auf ein öffentliches Remote pushen | Verboten – Konfigurationen können Tokens / sensible Daten enthalten, rein lokales Repository |
git user.name/email im auto-commit.sh fest codieren | Der globalen Git-Konfiguration überlassen; Skripte sollten keine persönlichen Daten enthalten |
| Fswatch mit KeepAlive=true dauerhaft laufen lassen | Bevorzugt ereignisgesteuert (WatchPaths / .path unit), kein dauerhafter Prozess |
Behandlung von Grenzfällen
- Worktree-Verzeichnis existiert nicht: Zuerst
mkdir -pausführen. Wenn es das erste Mal ist, existiert die zu überwachende Datei möglicherweise auch nicht – klären, ob zuerst eine leere Datei erstellt werden soll. - Überwachte Datei ist ein symbolischer Link: Im Watcher sowohl den Link-Pfad als auch das reale Ziel überwachen, da manche Schreibvorgänge nur einen der beiden auslösen.
- Überwachte Datei ist binär: Funktioniert trotzdem (Git speichert Blob), aber der Diff zeigt keinen Inhalt.
- Gleiches Label/Service existiert bereits: Zuerst das alte unloaden/deaktivieren, dann das neue laden.
- Mehrere Schreibvorgänge innerhalb des Drosselungsfensters: Mehrere Änderungen innerhalb des Drosselungsfensters werden zu einem einzigen Commit zusammengefasst (erwünschtes Verhalten).
- Atomare Schreibvorgänge (write-temp + rename): launchd WatchPaths löst normalerweise korrekt aus; falls ein bestimmtes Programm zu einem verpassten Commit führt, dem Benutzer empfehlen, auf KeepAlive + gitwatch / fswatch-Hauptschleife umzusteigen.
Vorlagendateien
templates/auto-commit.sh– Auto-Commit-Skriptgerüsttemplates/gitignore-whitelist– Whitelist-.gitignore-Vorlagetemplates/launchagent.plist– macOS LaunchAgenttemplates/systemd.path+templates/systemd.service– Linux systemd path unit
Designphilosophie
- Sichtbar + lesbar + manuell bearbeitbar: Die Versionshistorie ist reines Text-Git; jeder, der Git versteht, kann Änderungen lokalisieren/zurücksetzen, ohne auf eine GUI angewiesen zu sein.
- Ereignisgesteuert ≫ Polling: Reduziert CPU-/Festplattenverbrauch im Leerlauf.
- Ausfallsicher: Das Commit-Skript hat keine Nebenwirkungen – schlägt cd fehl oder ist der Diff leer, wird still beendet, ohne das Worktree zu beschädigen.
- Verzeichnis autark:
<BACKUP_ROOT>enthält Daten + Skripte + plist/unit-Vorlagen + README – das gesamte Verzeichnis kann kopiert werden, um die Funktionalität auf einem neuen Rechner wiederherzustellen. - Kein Push: Vermeidet das Hochladen privater Konfigurationen auf ein entferntes Repository; für externe Backups dateibasierte Lösungen verwenden (Time Machine / Backblaze / verschlüsseltes Syncthing).