Hogyan váltottam GNU Stow-ra a dotfile-jaim kezeléséhez

Évekig egy saját symlink scripttel kezeltem a dotfile-jaimat. Működött, de minden új konfig hozzáadásakor kézzel kellett bővíteni. A GNU Stow ezt az egészet feleslegessé teszi — egyetlen paranccsal linkeli a konfigokat, és a mappastruktúra maga a dokumentáció.

A kiindulás

Évek óta egy ~/.files mappában tartom a konfigurációimat. Bash, Git, Neovim, Starship, GTK témák — minden itt él, egy git repóban. Eddig egy saját symlink.sh script gondoskodott arról, hogy a rendszer a megfelelő helyeken találja őket.

A script jól működött. Idempotens volt, tudott --remove módot, backupolta a meglévő fájlokat mielőtt felülírta volna őket. Éveken át nem okozott gondot.

De volt néhány dolog, ami zavart.

Minden egyes alkalommal, amikor egy új konfigot akartam kezelni, meg kellett nyitnom a scriptet és hozzáadni egy újabb sort a LINKS tömbhöz. A Hyprland és a Studio konfig külön install scriptekkel rendelkezett, mert az eredeti script nem volt elég rugalmas hozzájuk. Három különböző mechanizmus, három különböző hely — és ha új gépre kellett telepíteni, három különböző parancsot kellett futtatni a megfelelő sorrendben.

Tudtam, hogy létezik a GNU Stow, de sokáig nem volt elég nyomás arra, hogy átálljak. Most viszont a tmux és a mise (programozási nyelvek kezelője) konfigját is verziókezelni akartam, és éreztem, hogy a régi módszer nem skálázódik tovább.

Volt egy másik szempont is, ami felgyorsította a döntést. Az utóbbi időben egyre többet dolgozom LLM-ekkel — konfig fájlok írása, scriptek debugolása, install folyamatok megtervezése. Egy-egy ilyen munkamenet során nagyon gyorsan születnek új fájlok, módosulnak meglévők, kerülnek be új eszközök a rendszerbe. Ha a dotfile-kezelés maga is egy egyedi script, amit minden alkalommal kézzel kell bővíteni, az lassítja a tempót. Egy szabványos, jól ismert eszköz — amit az LLM is ismer, amit a dokumentáció is lefed — sokkal jobban illeszkedik ebbe a munkafolyamatba. A stow pont ilyen: nincs benne egyedi logika amit el kéne magyarázni, nincs rejtett állapot amit karban kéne tartani. A struktúra maga a konfiguráció, és aki ránéz — ember vagy gép — azonnal érti, mi hová kerül.

Mi az a GNU Stow?

A GNU Stow egy egyszerű eszköz, ami symlinkeket hoz létre egy mappából a szülő könyvtárba, tükrözve a belső struktúrát. Ennyi. Nincs benne konfig fájl, nincs daemon, nincs adatbázis.

A trükk az, hogy a dotfile repódon belül minden konfig egy külön „package" mappában él, és azon belül pontosan úgy néz ki a struktúra, ahogy a $HOME-ban kellene. Például:

~/.files/
├── bash/
│   └── .bashrc              → ~/.bashrc
├── nvim/
│   └── .config/
│       └── nvim/
│           └── init.lua     → ~/.config/nvim/init.lua
├── starship/
│   └── .config/
│       └── starship.toml    → ~/.config/starship.toml

A telepítés ennyi:

cd ~/.files
stow bash nvim starship

És kész. A stow végigmegy a package mappákon, és minden fájlhoz létrehozza a megfelelő symlinket a $HOME-ban.

Az eltávolítás ugyanilyen egyszerű:

stow -D bash nvim starship

A tervezés

Mielőtt bármit mozgattam volna, végigmentem azon, hogy mi hová kell kerüljön. Nem minden konfig alkalmas stow-ra — a lényeg az, hogy a stow csak a $HOME-ba tud linkelni. Ami /etc/ alá megy (például a realtime audio jogosultságok), azt továbbra is kézzel kell kezelni.

Három kategóriát azonosítottam:

Stow package-ek — ezek a konfig fájlok, amik a $HOME-ba vagy a ~/.config/-ba kerülnek. Bash, Git, Starship, Neovim, GTK, mise, tmux.

Kézzel kezelt — SSH config (jogosultsági okokból), scriptek (PATH-ba téve a mappa), hledger journal-ök. Ezek a repóban maradnak, de nem a stow linkeli őket.

Külön telepítők — Hyprland és Studio konfig, amiknek saját install scriptjeik vannak. Ezek egyelőre maradtak ahogy voltak.

A tervezésnél az is fontos volt, hogy az átrendezésnél git mv-t használjak mindenhol — így a git history megmarad, és utólag is látom, honnan hová került egy fájl.

A migráció

Az átállás lényegében három lépésből állt.

Először a régi symlinkek eltávolítása. A symlink.sh által létrehozott hat link törlése — .bashrc, .gitconfig, .config/nvim, .config/starship.toml, .config/gtk-3.0, .config/gtk-4.0. Ez önmagában ártalmatlan művelet, hiszen az eredeti fájlok a repóban maradnak.

Másodszor a fájlok átrendezése a stow struktúrába. Például a bashrc fájl átkerült bash/.bashrc-be, a gitconfig a git/.gitconfig-be, a starship.toml a starship/.config/starship.toml-be.

A Neovim mappánál volt egy trükk: a git mv nvim nvim/.config/nvim nem működik, mert nem lehet egy mappát önmagába mozgatni. A megoldás egy ideiglenes név használata — nvim-stow —, majd átnevezés a véglegesre.

A mise konfig máshogy működött, mint a többi. Ez nem volt a régi repóban, hanem valódi fájlként élt a ~/.config/mise/config.toml-ban. Bemásoltam a repóba a mise/.config/mise/config.toml helyre, töröltem az eredetit, és innentől a stow linkeli.

Harmadszor az új install.sh létrehozása. Ez egy egyszerű wrapper a stow körül:

#!/usr/bin/env bash
set -euo pipefail

FILES_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$FILES_DIR"

ALL_PACKAGES=(bash git starship nvim gtk mise tmux)

for pkg in "$@"; do
    # ha van argumentum, csak azokat telepíti
done

# Ha nincs argumentum, mindet telepíti
for pkg in "${ALL_PACKAGES[@]}"; do
    stow -v --no-folding -t "$HOME" "$pkg"
done

A --no-folding flag fontos. Enélkül a stow az egész .config mappát egyetlen symlinkre cserélné, ami azt jelentené, hogy más programok a repómba írnának ha a .config-ba akarnak írni. A --no-folding arra kényszeríti a stow-t, hogy fájlonként linkeljen.

Buktatók

Néhány dolog, amire érdemes figyelni.

A git config csapdája. A migráció első lépése a régi symlinkek törlése. De ha a .gitconfig symlinket törlöd, a git nem fogja tudni, ki vagy — nem tud commitolni. Megoldás: a git package-et stow-old először, utána commitolj.

GTK bookmarks. A GNOME fájlkezelő közvetlenül írja a ~/.config/gtk-3.0/bookmarks fájlt. Ha a stow --no-folding módban fut, ez a fájl valódi fájlként megmaradhat, és a stow nem fogja tudni felülírni symlinkkel. Ilyenkor kézzel kell a fájlt törölni (a tartalmat előtte a repóba másolva), majd újra stow-olni.

Az SSH config kimarad. Az SSH mappa jogosultságokra kényes — a ~/.ssh mappának 700-nak, a config fájlnak 600-nak kell lennie. A symlink megoldás működik, de én úgy döntöttem, hogy az SSH config marad kézzel linkelve, a stow-on kívül.

Az eredmény

A végső struktúra:

~/.files/
├── bash/                   ← stow package
├── git/                    ← stow package
├── starship/               ← stow package
├── nvim/                   ← stow package
├── gtk/                    ← stow package
├── mise/                   ← stow package
├── tmux/                   ← stow package
│
├── ssh/                    ← kézzel linkelve
├── scripts/                ← PATH-ba téve
├── hyprland-werkstatt/     ← külön telepítő
├── studio/                 ← külön telepítő
│
└── install.sh              ← stow wrapper

Új package hozzáadása: csinálsz egy mappát a megfelelő belső struktúrával, hozzáadod az ALL_PACKAGES tömbhöz, futtatod a stow packagenév parancsot.

Új gépre telepítés: klónozod a repót, ./install.sh, kész.

Tanulság

A GNU Stow nem old meg bonyolult problémákat, mert a dotfile-kezelés nem bonyolult probléma. Az egyetlen dolog, amit csinál: a mappastruktúrát symlinkekre fordítja. Nincs benne konfig fájl, nincs állapot, nincs mágia.

Pontosan ettől jó.

A régi symlink.sh scriptem jól működött, de minden módosításnál a scriptet kellett szerkeszteni. A stow-val a mappastruktúra maga a konfiguráció. Ha egy fájl a bash/.bashrc helyen van, a stow tudja, hogy ~/.bashrc-nek kell lennie. Nincs mit elrontani, nincs mit elfelejteni.

Ha te is kézzel kezeled a dotfile-jaidat — legyen az symlinkekkel, install scripttel vagy egyszerű másolgatással — a stow megéri a húsz percet, amit az átállás igényel.

Fedora alatt:

sudo dnf install stow

A többit megcsinálja a mappastruktúra.

A GNU Stow nem csinál semmi mágiát. Fogja a mappáidat és symlinkeket hoz létre belőlük. Pont ettől jó: nincs mit debuggolni, nincs mit megérteni. A struktúra az, amit látsz.