Mert Özen Her Satırda Daha İleri
Geliştirici Araçları Productivity ve Automation — #04

Dotfiles: Ayarlarını Git'te Tutmak, chezmoi veya Stow ile Senkronlamak ve Secret Sızdırmamak

Mert Özen 15 Kas 2026 10 dk 6 görüntülenme
Dotfiles: Ayarlarını Git'te Tutmak, chezmoi veya Stow ile Senkronlamak ve Secret Sızdırmamak

Dotfiles'ı Git'te tutmanın üç yolu var: bare repo, GNU Stow ve chezmoi; birden çok işletim sistemi ve secret varsa chezmoi en az elle iş bırakanı. Üçünü sahte bir home klasöründe deniyoruz: bare repo'da add -A tuzağı, Stow'un .config klasörünü katlaması ve --adopt'un repo'yu ezmesi, chezmoi'de template, .chezmoiignore, Windows çıktısını --override-data ile görmek, age şifrelemesi ve gitleaks'in yakalamadığı uydurma token.

Sürüm notu: Bu yazıdaki örnekler chezmoi 2.74.1, Stow 2.4.1 (GNU projesinin aracı), Git 2.54 ve gitleaks 8.30.1 ile, gerçek home klasörüne dokunmadan, sahte dotfile'lar içeren ayrı bir test home klasöründe çalıştırılmıştır.

Önce Tahmin Et

Starship ayarını GNU Stow ile yönetiyorsun: repo'da starship/.config/starship/starship.toml var, stow starship çalıştırıyorsun ve home klasöründe henüz .config diye bir klasör yok. Ertesi gün GitHub CLI ilk kez çalışıyor ve ayarını ~/.config/gh/config.yml dosyasına yazıyor.

Bu dosya diskte nereye düşer? Bir ipucu: dotfiles repo'nda git status çalıştırınca bir sürprizle karşılaşabilirsin. Cevap GNU Stow bölümünde.

Neyi Versiyonlamalı?

Dotfiles, home klasöründe duran ve adı nokta ile başlayan ayar dosyalarına verilen genel ad: .zshrc, .gitconfig, .tmux.conf, ~/.config altındaki dosyalar. Windows tarafında karşılığı PowerShell'in $PROFILE dosyası ve Windows Terminal'in settings.json'ı. Bu dosyaları bir Git repo'sunda tutmak iki şey kazandırıyor: değişikliklerin geçmişi ve yeni bir makinede aynı ortamı dakikalar içinde kurmak.

Repo'ya girenler: shell ayarları (.zshrc, $PROFILE), .gitconfig, prompt ayarı (starship.toml), terminal ve tmux ayarları, .editorconfig gibi araçlardan bağımsız dosyalar. Bu dosyaların içeriği, yani alias'lar, function'lar, prompt ve plugin seçimi, Terminal ve Command Line serisinin konusu; bu yazı yalnız onları nasıl versiyonlayıp dağıtacağını anlatıyor. Git alias'ları ise Git & GitHub serisinin "Git Geçmişine Bakmak" yazısında.

Repo'ya girmeyenler: history dosyaları, cache'ler, IDE'nin kendi Settings Sync'iyle taşıdığı ayarlar (IDE'ler serisinin "IDE Extension'ları" yazısında anlatıldı; iki mekanizmanın aynı dosyayı yönetmesi çakışma üretir), makineye özel yollar ve tabii ki secret'lar.

Aşağıda üç yaklaşımı aynı sahte dosyalarla deniyoruz: .zshrc, .gitconfig (kullanıcı adı Ada Lovelace, e-posta ada@example.com) ve Starship ayarı.

04-three-approaches-tr.png

Yaklaşım 1: Bare Git Repo

En az araçla yapılan yol: home klasörünün kendisini work tree olarak kullanan bir bare repo. Ek bir program yok, symlink yok; dosyalar yerinde duruyor, Git yalnız hangilerini izlediğini biliyor.

# zsh ve bash
alias dot='git --git-dir=$HOME/.dotfiles --work-tree=$HOME'

# PowerShell
function dot { git --git-dir="$HOME/.dotfiles" --work-tree="$HOME" @args }

Repo'yu oluşturup ilk status'a bakınca sorun hemen görünüyor: home klasöründeki her şey untracked.

$ git init --bare $HOME/.dotfiles
$ dot status --short
?? .config/
?? .dotfiles/
?? .gitconfig
?? .zshrc
?? Downloads/

Gerçek bir home klasöründe bu liste yüzlerce satır olur. Untracked dosyaları gizleyip yalnız istediğin dosyaları eklersin:

$ dot config status.showUntrackedFiles no
$ dot add .zshrc .gitconfig .config/starship/starship.toml
$ dot commit -m "Add zsh, git and starship settings"
$ dot ls-files
.config/starship/starship.toml
.gitconfig
.zshrc

Bu yaklaşımın tuzağı alışkanlıkla dot add -A yazmak. status artık untracked dosyaları göstermediği için ne ekleneceğini görmezsin; --dry-run ile baktım:

$ dot add -A --dry-run
add '.dotfiles/COMMIT_EDITMSG'
add '.dotfiles/HEAD'
add '.dotfiles/config'
...
add '.dotfiles/refs/heads/main'
add 'Downloads/book.pdf'

Repo kendi iç dosyalarını ve indirilenler klasörünü eklemeye hazır. .dotfiles klasörünü repo'nun info/exclude dosyasına yazmak ve yalnız dosya adıyla add yapmak bu yöntemin vazgeçilmez kuralı. Bare repo'nun bir sınırı da OS'e göre içerik üretememesi: macOS'ta ve Windows'ta farklı olması gereken satırlar için ya iki dosya ya da shell içinde if gerekiyor.

Yaklaşım 2: GNU Stow

Stow, "symlink farm" yöneticisi. Dotfiles repo'su paketlere bölünür; her paketin içi home klasörünün yapısını taklit eder. stow git, git paketindeki dosyalar için home'da symlink oluşturur:

$ find dotfiles -type f | sort
dotfiles/git/.gitconfig
dotfiles/starship/.config/starship/starship.toml
dotfiles/zsh/.zshrc

Home'da zaten bir .gitconfig varken önce -n (hiçbir şey yapma) ve -v ile ne olacağına baktım:

$ stow -d dotfiles -t home -n -v zsh git starship
LINK: .zshrc => ../dotfiles/zsh/.zshrc
LINK: .config => ../dotfiles/starship/.config
WARNING! stowing git would cause conflicts:
  * cannot stow ../dotfiles/git/.gitconfig over existing target .gitconfig since neither a link nor a directory and --adopt not specified
All operations aborted.

Stow var olan bir dosyanın üzerine yazmıyor ve bir paket çakışınca hiçbirini uygulamıyor. Eski dosyayı kaldırınca:

$ stow -d dotfiles -t home zsh git starship
$ ls -la home
.config -> ../dotfiles/starship/.config
.gitconfig -> ../dotfiles/git/.gitconfig
.zshrc -> ../dotfiles/zsh/.zshrc

Önce Tahmin Et'in cevabı ikinci satırda. Home'da .config klasörü olmadığı için Stow tek tek dosya yerine bütün .config klasörünü repo'ya giden bir symlink yaptı (belgede "tree folding" deniyor). GitHub CLI ayarını yazınca:

$ git -C dotfiles status --short
?? starship/.config/gh/

Başka bir uygulamanın ayarı, belki de token'ı, dotfiles repo'sunun içine yazıldı. Çözüm --no-folding: Stow klasörleri gerçek klasör olarak oluşturur, yalnız dosyaları link'ler.

$ stow -d dotfiles -t home --no-folding starship
$ ls -la home/.config/starship
starship.toml -> ../../../dotfiles/starship/.config/starship/starship.toml

Çakışmayı çözmek için akla gelen --adopt ise ters yönde ezer: home'daki dosyayı repo'ya taşır, repo'daki sürümün yerine koyar.

$ stow -d dotfiles -t home --adopt git
$ git -C dotfiles diff
--- a/git/.gitconfig
+++ b/git/.gitconfig
@@ -1,3 +1,3 @@
-[user]
-    name = Ada Lovelace
-    email = ada@example.com
+# old local file
+[core]
+    autocrlf = true

--adopt yalnız commit edilmiş, temiz bir repo'da ve hemen ardından git diff ile kontrol ederek kullanılmalı. Stow Perl ile yazılmış bir araç; Windows'ta symlink için geliştirici modu ya da yönetici yetkisi gerekiyor ve Stow orada pek kullanılmıyor. Ekipte Windows kullanan varsa Stow tek başına yetmez.

Yaklaşım 3: chezmoi

chezmoi dosyaları symlink'lemiyor, bir "source state"ten üretip kopyalıyor. Source klasörü kendi Git repo'su; dosya adları ne yapılacağını söylüyor: dot_zshrc home'da .zshrc olur, .tmpl uzantısı template demek, encrypted_ öneki şifreli dosya demek. Denemeleri sahte home klasöründe --source, --destination ve --config option'larıyla yaptım; aşağıda kısalık için bu option'ları yazmıyorum.

$ chezmoi init
$ chezmoi add ~/.zshrc
$ chezmoi add --template ~/.gitconfig
$ find src -type f -not -path '*/.git/*' | sort
src/dot_gitconfig.tmpl
src/dot_zshrc

Template Go'nun text/template syntax'ını kullanıyor. Makineye özel değerler chezmoi'nin configuration dosyasındaki [data] bölümünden geliyor; dosya her makinede ayrı, repo'ya girmiyor:

[data]
    email = "ada@example.com"
    work = true
[user]
    name = Ada Lovelace
    email = {{ .email }}
[core]
{{- if eq .chezmoi.os "windows" }}
    autocrlf = true
{{- else }}
    autocrlf = input
{{- end }}
{{- if .work }}
[includeIf "gitdir:~/src/work/"]
    path = ~/.gitconfig-work
{{- end }}

work değerini false'tan true'ya çevirdikten sonra chezmoi diff, uygulamadan önce neyin değişeceğini gösteriyor:

$ chezmoi diff
diff --git a/.gitconfig b/.gitconfig
index 2cf1464a3f4842e0ef731b116dd5a5304a418f50..b167194813d7770782a9ed9aaec130a0172960c2 100644
--- a/.gitconfig
+++ b/.gitconfig
@@ -3,3 +3,5 @@     name = Ada Lovelace
     email = ada@example.com
 [core]
     autocrlf = input
+[includeIf "gitdir:~/src/work/"]
+    path = ~/.gitconfig-work

chezmoi apply değişikliği yazıyor, sonrasında chezmoi status boş dönüyor. Yeni makinede tek komut yetiyor: chezmoi init --apply <repo-url> repo'yu klonlayıp uyguluyor. Önce görmek istersen chezmoi init, chezmoi diff ve chezmoi apply adımlarını ayrı çalıştır. İlk kurulumda e-posta gibi değerleri sormak için source'a bir .chezmoi.toml.tmpl konup promptStringOnce kullanılabiliyor; bunu bir sonraki yazıdaki bootstrap'te kullanacağız.

macOS, Windows ve WSL Arasında Tek Dotfiles

chezmoi'de OS farkı iki yerde çözülüyor. Dosyanın içi için yukarıdaki gibi .chezmoi.os koşulu, dosyanın var olup olmaması için .chezmoiignore. Bu dosya da template; "şu OS değilse yok say" diye yazılıyor:

{{ if ne .chezmoi.os "windows" }}
Documents
{{ end }}
{{ if eq .chezmoi.os "windows" }}
.zshrc
{{ end }}

PowerShell 7'nin profili Windows'ta Documents\PowerShell\Microsoft.PowerShell_profile.ps1 yolunda; source'ta Documents/PowerShell/ altında duruyor. macOS'ta yönetilen dosyalar:

$ chezmoi managed
.config
.config/bookstore
.config/bookstore/feed.env
.gitconfig
.zshrc

Windows'ta ne olacağını görmek için Windows makinesi açmana gerek yok. --override-data template verisini geçici olarak değiştiriyor:

$ chezmoi --override-data '{"chezmoi":{"os":"windows"}}' cat ~/.gitconfig
[user]
    name = Ada Lovelace
    email = ada@example.com
[core]
    autocrlf = true
[includeIf "gitdir:~/src/work/"]
    path = ~/.gitconfig-work
$ chezmoi --override-data '{"chezmoi":{"os":"windows"}}' managed
.config
.config/bookstore
.config/bookstore/feed.env
.gitconfig
Documents
Documents/PowerShell
Documents/PowerShell/Microsoft.PowerShell_profile.ps1

WSL (Windows Subsystem for Linux; Windows içinde Linux çalıştıran katman) chezmoi için linux görünüyor. chezmoi belgesi WSL'i ayırt etmek için .chezmoi.kernel.osrelease değerinde "microsoft" geçip geçmediğine bakmayı öneriyor. Satır sonları için dotfiles repo'suna bir .gitattributes koymak iyi olur; shell dosyaları eol=lf olmalı. Windows'ta core.autocrlf=true ile klonlanan bir .zshrc CRLF (Windows'un satır sonu karakter çifti) satır sonlarıyla gelir ve zsh bu konuda hata bile vermez. CRLF'li bir dosyayı source edip değişkenin içine baktım:

$ zsh -f -c 'source ./crlf.zsh; echo "EDITOR=[$EDITOR]" | od -c | head -2'
0000000    E   D   I   T   O   R   =   [   c   o   d   e  \r   ]  \n
0000017

EDITOR değeri sessizce code\r olmuş; bu değişkeni kullanan bir araç code\r adında bir program arar ve bulamaz. Hata mesajında görünmeyen bir karakter olduğu için bunu bulmak uzun sürer.

Secret'ları Dışarıda Tutmak

En güvenli secret, dotfile'a hiç yazılmayan secret. npm bunun için iyi bir örnek: .npmrc içinde değeri değil ortam değişkeninin adını tutabilirsin, npm değeri çalışma anında okur:

//registry.example.com/:_authToken=${NPM_TOKEN}

Değer bir dosyada durmak zorundaysa chezmoi dosyayı şifreleyebilir. age şifrelemesini chezmoi'nin içindeki age implementasyonuyla denedim (chezmoi age-keygen ile anahtar üretip configuration'a yazarak). Sahte değer içeren bir dosyayı ekleyince:

$ chezmoi add --encrypt ~/.config/bookstore/feed.env
$ find src -name 'encrypted_*'
src/dot_config/bookstore/encrypted_feed.env.age
$ head -1 src/dot_config/bookstore/encrypted_feed.env.age
-----BEGIN AGE ENCRYPTED FILE-----
$ chezmoi cat ~/.config/bookstore/feed.env
BOOKSTORE_FEED_TOKEN=example-secret-not-real

Repo'ya yalnız şifreli dosya giriyor; grep ile baktığımda düz metin source'ta yoktu. Anahtar dosyası ise repo'ya asla girmez, her makineye ayrıca taşınır. Üçüncü yol password manager: chezmoi template'leri 1Password, Bitwarden ve başka araçlardan değer okuyabiliyor, örneğin onepasswordRead "op://..." function'ı arkada 1Password CLI'ını çağırıyor. Bu durumda değer repo'da da şifreli olarak bile durmuyor, apply sırasında password manager'dan geliyor.

Son savunma hattı commit öncesi secret taraması (Git & GitHub serisinin hook'lar ve commit imzalama yazısında anlatıldı). Ama tarayıcıya güvenip dikkat etmeyi bırakma: dotfiles repo'suna fake- ile başlayan uydurma bir token'ı stage edip gitleaks'i varsayılan kurallarıyla çalıştırdım ve yakalamadı.

$ gitleaks git --pre-commit --staged
3:26PM INF 0 commits scanned.
3:26PM INF scanned ~132 bytes (132 bytes) in 68.5ms
3:26PM INF no leaks found

Tarayıcılar bilinen sağlayıcıların token biçimlerini ve belli kalıpları arıyor; senin iç feed'inin token'ı o kalıplara uymayabilir.

Karar Tablosu

Bare Git repoGNU Stowchezmoi
Ek araçyokPerl + Stowtek binary
Dosya home'daasıl dosyasymlinküretilmiş kopya
OS'e göre içerikyokayrı paketlerletemplate ve .chezmoiignore
WindowsGit ile çalışırzayıfdoğrudan destekleniyor
Secretelleelleşifreleme, password manager
Yeni makineklon + checkoutklon + stowchezmoi init --apply

Tek işletim sisteminde, birkaç dosyayla çalışıyorsan bare repo ya da Stow yeter. macOS, Windows ve WSL arasında gidip geliyorsan ve dosyaların en az bir kısmı makineye göre değişiyorsa chezmoi'nin öğrenme maliyeti kendini çabuk ödüyor.

Üretimde Dikkat

Dotfiles repo'larının çoğu public; başkalarının ayarlarından öğrenmek bu ekosistemin güzel tarafı. Ama public yapmadan önce geçmişin tamamını oku (git log -p), yalnız son hâli değil. Sık sızan şeyler: .gitconfig'teki iş e-postası, includeIf yollarındaki müşteri ya da proje adları, .ssh/config içindeki iç sunucu adları ve IP adresleri, shell history'den kopyalanmış komutlar, .npmrc ya da NuGet.Config içindeki feed token'ları. Bir kez push edilmiş bir secret için geçmişi temizlemek yetmez; secret'ı iptal edip yenisini üretmek gerekir.

chezmoi'nin configuration dosyası (chezmoi.toml) makineye özeldir ve repo'ya girmez; age anahtarı ve password manager oturumu da öyle. Yeni makineye taşınması gereken tek gizli parça bunlar olmalı.

Sık Düşülen Tuzaklar

Birincisi başkasının dotfiles'ını anlamadan kopyalamak. Popüler bir repo'dan alınan .zshrc senin makinende olmayan araçları çağırır, yavaş açılır ya da rm'yi sessizce başka bir komuta bağlar. Satır satır anlamadığın bir ayarı alma; alacaksan kendi repo'na küçük parçalar halinde, commit mesajında kaynağıyla ekle.

İkincisi çakışmada yerel değişikliği ezmek. Yerelde .zshrc'ye bir alias ekleyip source'u da değiştirdiğimde chezmoi durumu MM diye gösterdi ve apply soru sordu:

$ chezmoi status
MM .zshrc
$ chezmoi apply
.zshrc has changed since chezmoi last wrote it (diff/overwrite/all-overwrite/skip/Skip-conflicts/quit)?

chezmoi apply --force ise sormadan yazıyor ve yereldeki alias gs="git status" satırı kayboldu. Doğrusu önce chezmoi diff, sonra yerel değişikliği source'a almak için chezmoi re-add ya da chezmoi merge. Stow'daki karşılığı --adopt'un repo'yu ezmesiydi. Üçüncüsü Stow'un klasör katlaması; --no-folding kullanmadıysan git status'ta tanımadığın klasörleri ara.

Küçük Bir Deneme

★☆☆ Kendi home klasöründe versiyonlamak istediğin beş dosyayı listele ve her biri için "secret içeriyor mu, makineye özel bir satırı var mı" sorusunu cevapla.

★★☆ Boş bir test klasörü oluştur ve HOME'u ona yönlendirerek chezmoi --source ... --destination ... ile bu yazıdaki .gitconfig template'ini kur. --override-data ile Windows çıktısını gör.

★★★ Aynı üç dosyayı Stow ile yönet, sonra chezmoi'ye taşı. Taşıma sırasında symlink'lerin nasıl gerçek dosyaya döndüğünü ls -la ile izle ve geçişi geri alınabilir yapmak için hangi sırayla çalışman gerektiğini not et.

Ayarlarımız artık bir repo'da ve tek komutla uygulanabiliyor. Eksik kalan, bu ayarların çalıştırdığı araçlar: Git, .NET SDK, Node.js, just, chezmoi'nin kendisi. Serinin son yazısında boş bir Mac ve boş bir Windows makinesinde Brewfile, WinGet Configuration ve mise ile bunları kuran, dotfiles'ı uygulayan ve Bookstore'u just dev'e kadar getiren bir bootstrap yazıyoruz.