Mijn .zshrc is ouder dan de carrière van sommige collega’s. Geldt ook voor mijn .vimrc en mijn tmux config. Ze zijn uitgegroeid tot een zorgvuldig afgestemd systeem dat precies doet wat ik wil, en ik zou verloren zijn zonder. Het probleem: ik draai meerdere machines. Een laptop, een desktop, soms een wegwerp-VM om iets te testen. Dat allemaal in sync houden was vroeger een stapel geïmproviseerde hacks aan elkaar geknoopt met hoop.
De kernwaarde hier is portability. Ik wil effectief zijn op elke machine die ik aanraak, niet hulpeloos zodra ik weg ben van mijn eigen toetsenbord. Een dotfile setup vinden die dat echt waarmaakt heeft me jaren aan doodlopende wegen gekost.
De lange zoektocht
Het begon met de klassieker: een bare git repo in mijn home directory. git init --bare ~/.dotfiles, een paar aliassen, klaar. Werkt prima tot het misgaat. Eén onhandige git clean en je hebt je eigen configs weggevaagd. Machine-specifieke instellingen? Zoek het zelf maar uit.
Toen vond ik GNU Stow. Het idee is echt netjes: organiseer je dotfiles in directories en symlink ze naar de juiste plek. Ik heb het een lange tijd gebruikt. Maar Stow kent geen templates, en mijn werk-laptop en persoonlijke desktop hebben verschillende .gitconfig instellingen nodig. Met Stow eindig je met gitconfig-work en gitconfig-personal en een handmatige switch die je vergeet om te zetten. Vervelend.
Op een gegeven moment dacht ik: “Ik draai al Ansible voor mijn servers, waarom dan niet voor mijn dotfiles?” Dus bouwde ik een playbook voor mijn workstation. Roles, templates, handlers, het hele circus. Het was prachtig. Het was ook belachelijk over-engineered. ansible-playbook draaien elke keer dat ik mijn .zshrc aanpaste voelde absurd, en YAML-soep schrijven om één bestand te kopiëren was een eigen soort pijn.
Ik heb er onderweg nog een paar geprobeerd:
- yadm - git-based, een stap omhoog van de bare repo, maar de templating voelde erop geplakt
- dotbot - nette YAML config, niet flexibel genoeg voor wat ik wilde
- home-manager (Nix) - heel krachtig, maar ik was nog niet klaar om volledig op Nix over te gaan
Niets bleef plakken. Elke tool loste één ding op en gaf me een nieuw probleem terug.
Toen kwam ik uit op chezmoi en koppelde het aan mise. Die combo is de setup die ik echt ben blijven gebruiken. Deze post loopt hem door, van de simpelst mogelijke versie tot aan mijn echte bootstrap.
Waarom dit lastiger is dan het lijkt
Op papier is dotfile management triviaal. Het zijn gewoon config bestanden, toch? De ellende begint op het moment dat je meer dan één machine hebt:
- Machine-specifieke config: mijn laptop wil andere instellingen dan mijn desktop
- Secrets: mijn
.gitconfigdraagt tokens en mijn.ssh/configbevat hostnames die ik niet in een publieke repo wil - Tool versies: ik heb Go 1.21 nodig op de ene machine en 1.22 op de andere
- Bootstrap: op een verse machine wil ik binnen minuten productief zijn, niet een middag besteden aan bestanden rondkopiëren
Een simpele git repo dekt niks daarvan. Je hebt templating, secret management en een herhaalbare bootstrap nodig. Precies dat gat vult chezmoi.
De simpelste versie: chezmoi
Chezmoi is een dotfile manager die al het bovenstaande afdekt. De naam is Frans voor “bij mij thuis” (chez moi), wat past, want hij neemt je home environment overal mee naartoe.
Kernconcept
Chezmoi onderhoudt een “source state” (in ~/.local/share/chezmoi/) en past die toe op je home directory. De source state kan bevatten:
- Gewone bestanden
- Templates (met variabelen per machine)
- Scripts (voor dingen die je niet als statisch bestand kunt vastleggen)
- Versleutelde bestanden (voor secrets)
Je begrijpt precies wat waar terechtkomt, en dat is het hele punt. Geen black box die dingen namens jou beslist.
Aan de slag
# Installeren
brew install chezmoi # macOS
sudo pacman -S chezmoi # Arch
sudo apt install chezmoi # Debian/Ubuntu
# Initialiseren
chezmoi init
# Je eerste bestand toevoegen
chezmoi add ~/.zshrc
Dat is de hele minimale setup. Je .zshrc wordt nu beheerd door chezmoi, en als dat alles is wat je nodig hebt kun je hier stoppen.
De basis workflow
# Bestand toevoegen aan chezmoi
chezmoi add ~/.config/nvim/init.lua
# Bestand bewerken (opent in $EDITOR)
chezmoi edit ~/.zshrc
# Bekijk wat er zou veranderen
chezmoi diff
# Pas wijzigingen toe op home directory
chezmoi apply
# Push naar git
chezmoi cd
git add -A && git commit -m "Update zshrc" && git push
Laag 1: machine-specifieke config met templates
Hier verdient chezmoi zijn plek. Je kunt Go templates gebruiken om uit één bron verschillende config per machine te renderen.
Definieer eerst je data in ~/.config/chezmoi/chezmoi.toml:
[data]
hostname = "laptop"
work = true
email = "tom@example.com"
Gebruik het dan in templates. Hernoem een bestand met .tmpl suffix:
chezmoi add --template ~/.gitconfig
Nu ~/.local/share/chezmoi/dot_gitconfig.tmpl:
[user]
name = Tom Meurs
email = {{ .email }}
{{ if .work -}}
[url "git@gitlab.work.com:"]
insteadOf = https://gitlab.work.com/
{{- end }}
Op mijn werk-laptop wordt de GitLab URL rewrite ingerenderd. Op mijn persoonlijke machines blijft hij eruit. Eén bestand, twee uitkomsten, geen handmatige switch om te vergeten.
Machine detectie
Je kunt machine-eigenschappen automatisch detecteren:
# ~/.config/chezmoi/chezmoi.toml
[data]
hostname = {{ .chezmoi.hostname | quote }}
os = {{ .chezmoi.os | quote }}
{{ if eq .chezmoi.hostname "work-laptop" -}}
work = true
{{- else -}}
work = false
{{- end }}
Of gebruik een interactieve prompt bij eerste setup:
# chezmoi.toml.tmpl
[data]
email = {{ promptString "email" | quote }}
work = {{ promptBool "work machine" }}
Laag 2: secrets met age
Commit nooit secrets in plain text. Chezmoi integreert met age voor encryptie, zodat je secrets in dezelfde repo kunnen leven zonder te lekken.
# Genereer age key
age-keygen -o ~/.config/chezmoi/key.txt
# Configureer chezmoi om het te gebruiken
chezmoi edit-config
encryption = "age"
[age]
identity = "~/.config/chezmoi/key.txt"
recipient = "age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Nu kun je gevoelige bestanden versleutelen:
chezmoi add --encrypt ~/.ssh/config
Het bestand belandt versleuteld in je repo. Alleen machines met de age key kunnen het ontsleutelen, dus een publieke dotfiles repo blijft veilig.
Scripts: wanneer bestanden niet genoeg zijn
Soms is een bestand niet het juiste gereedschap en moet je een commando draaien. Chezmoi ondersteunt scripts voor precies dat:
# ~/.local/share/chezmoi/run_once_install-packages.sh
#!/bin/bash
# Dit draait één keer (bijgehouden via hash)
if command -v brew &> /dev/null; then
brew install ripgrep fd bat fzf
elif command -v apt &> /dev/null; then
sudo apt install -y ripgrep fd-find bat fzf
elif command -v pacman &> /dev/null; then
sudo pacman -S --noconfirm ripgrep fd bat fzf
fi
De prefix bepaalt wanneer een script draait:
run_once_- draait één keer, daarna nooit meer (bijgehouden via content hash)run_onchange_- draait wanneer de script inhoud verandertrun_- draait elke keer
mise erbij voor tool versies
Chezmoi handelt dotfiles af, maar zegt niks over welke versie van Go, Node, Python of Terraform je geïnstalleerd hebt. Die wil ik ook consistent over machines heen, anders wordt “werkt op mijn laptop” een terugkerende grap op mijn eigen kosten.
Dat is de taak van mise. Het is een polyglot version manager in de geest van asdf, maar sneller en een stuk prettiger in gebruik.
Waarom mise?
Ik heb jarenlang asdf gebruikt. Het werkt, maar het heeft scherpe randjes:
- Traag, want gebouwd op shell scripts
- Een gefragmenteerd plugin ecosysteem
- Geen native manier om zijn config in je dotfiles te houden
Mise lost alle drie op:
- Snel, geschreven in Rust
- Ingebouwde support voor de meeste tools out of the box
- Config leeft in
~/.config/mise/config.toml, wat direct in chezmoi past
Aan de slag met mise
# Installeren
curl https://mise.run | sh
# Of via package manager
brew install mise
Toevoegen aan je shell:
# ~/.zshrc
eval "$(mise activate zsh)"
Definieer je tools
# ~/.config/mise/config.toml
[tools]
go = "1.22"
node = "lts"
python = "3.12"
terraform = "1.7"
kubectl = "latest"
helm = "latest"
Voer nu uit:
mise install
Elke tool wordt geïnstalleerd op de vastgepinde versie. Wissel van machine, draai mise install, en je kijkt naar exact dezelfde omgeving. Die reproduceerbaarheid is het ding waar het me om gaat.
Project-specifieke versies
Je kunt versies ook per project vastpinnen:
# ~/projects/legacy-app/.mise.toml
[tools]
node = "16" # Oud project heeft Node 16 nodig
Mise wisselt automatisch zodra je de directory in cd’t. Geen nadenken nodig, en dat is precies het doel: minder frictie, meer flow.
Het volledige plaatje: chezmoi + mise samen
Dit is hoe de twee in mijn echte setup samenkomen.
1. mise config in chezmoi
chezmoi add ~/.config/mise/config.toml
Nu reizen mijn tool versies mee met mijn dotfiles. Dezelfde versies overal, vastgelegd in git.
2. Bootstrap scripts
# ~/.local/share/chezmoi/run_once_before_10-install-mise.sh
#!/bin/bash
if ! command -v mise &> /dev/null; then
curl https://mise.run | sh
fi
# ~/.local/share/chezmoi/run_once_after_90-mise-install.sh
#!/bin/bash
# Draai nadat dotfiles op hun plek staan
mise install
De before en after in de bestandsnaam bepalen de uitvoervolgorde, dus mise wordt eerst geïnstalleerd en mise install draait pas als de config daadwerkelijk op zijn plek staat.
3. Shell integratie
Mijn .zshrc.tmpl:
# mise
if command -v mise &> /dev/null; then
eval "$(mise activate zsh)"
fi
# ... rest van config ...
4. Machine-specifieke tools
Verschillende machines hebben verschillende tools nodig, en templating regelt dat ook hier:
# ~/.config/mise/config.toml.tmpl
[tools]
go = "1.22"
node = "lts"
python = "3.12"
{{ if .work -}}
terraform = "1.7"
kubectl = "1.29"
helm = "3.14"
{{- end }}
{{ if eq .chezmoi.os "darwin" -}}
# macOS-specifieke tools
{{- end }}
Mijn bootstrap proces
Dit is de beloning. Op een verse machine komt het hele ding neer op één regel:
# 1. Installeer chezmoi en init vanuit repo
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply yourusername
# 2. Dat is het
Het init script:
- Kloont mijn dotfiles repo
- Vraagt om machine-specifieke data (work/personal, email)
- Ontsleutelt secrets (vraagt om age key)
- Draait bootstrap scripts (installeert mise, packages)
- Past alle dotfiles toe
Binnen vijf minuten zit ik in een volledig geconfigureerde omgeving, secrets en al. Vergelijk dat met de bare-repo dagen waarin ik een uur lang configs zat te repareren en een week later alsnog iets ontbrekends ontdekte.
Tips en tricks
Externe bestanden
Bestanden nodig vanaf URLs of andere git repos?
# ~/.local/share/chezmoi/.chezmoiexternal.toml
[".oh-my-zsh"]
type = "archive"
url = "https://github.com/ohmyzsh/ohmyzsh/archive/master.tar.gz"
exact = true
stripComponents = 1
refreshPeriod = "168h"
[".config/nvim/lazy-lock.json"]
type = "file"
url = "https://raw.githubusercontent.com/you/nvim-config/main/lazy-lock.json"
refreshPeriod = "24h"
Bestanden negeren
# ~/.local/share/chezmoi/.chezmoiignore
# Sync deze nooit
.DS_Store
*.swp
.zsh_history
# Machine-specifieke ignores
{{ if not .work }}
.config/work-vpn/
{{ end }}
Re-add na bewerken
Als je een bestand direct bewerkt in plaats van via chezmoi edit:
# Re-add om source state te updaten
chezmoi re-add
Check voor drift
Wil je weten of je home directory is afgeweken van wat chezmoi verwacht? Drift verifiëren houdt je eerlijk:
chezmoi verify
Waar dit me brengt
Na jaren bare git repos, GNU Stow, Ansible playbooks, en een handvol andere tools is chezmoi plus mise de setup die eindelijk blijft plakken. Als ik terugkijk naar de drie-regel-install van het begin van deze post, dekt hij nu dit:
- Cross-machine sync via git
- Machine-specifieke config via templates
- Secrets via age encryptie
- Tool versies via mise
- Bootstrap via scripts
De eerste setup kost je een avond. Daarna heb je een reproduceerbare development omgeving die je naar elke machine volgt, en dat is portability in de meest letterlijke zin.
Nieuwe machine? Eén commando. Nieuwe tool versie nodig? Update config.toml, push, pull op de andere machines, klaar.
Is het overkill als je maar één machine aanraakt? Waarschijnlijk. Maar als je VMs opspint, vaak herinstalleert, of gewoon de stille zekerheid wilt dat je config versioned en gebackupt staat op een plek die jij beheert, dan is het de avond waard:
chezmoi init --apply yourusername
Je zult blij zijn dat je het deed, de volgende keer dat een machine onder je handen sterft.
Resources:
- chezmoi.io - officiële documentatie
- mise.jdx.dev - mise documentatie
- age encryption - simpele, moderne encryptie
