Dotfiles as Code : un dépôt, trois OS, zéro friction

7 mars 2026

dotfiles with chezmoi

Réinstaller une machine, c'est souvent une journée perdue. Retrouver le bon alias, le plugin zsh oublié, la font qui ne s'affiche pas, le.gitconfig oublié...

Lorsqu'on utilise plusieurs systèmes. Dans mon cas Manjaro et macOS en perso, Ubuntu en pro. Il devient vite nécessaire de traiter sa configuration comme du code: versionnée, reproductible. Le résultat: une seule commande pour retrouver un environnement complet.

chezmoi init --apply <user>

Dans cet article: l'architecture de mes dotfiles, les choix techniques, et comment reproduire cette approche.


Le problème

Les dotfiles, ce sont ces dizaines de fichiers cachés qui définissent un environnement de travail : .zshrc, .gitconfig, .ssh/config, les settings VS Code...

Pas de versioning. On modifie un alias, ça casse quelque chose, impossible de revenir en arrière.

Pas de portabilité. Une config Arch ne marche pas sur Ubuntu. On maintient mentalement les différences entre ses machines.

Secrets éparpillés. Clés SSH, tokens npm, configs privées. Certains finissent copiés à la main, d'autres sont perdus.

Setup manuel. Pour chaque nouvelle machine, c'est interminable. Il manque toujours un outil, une config, un réglage.

Pendant longtemps, j'avais un repo git avec des fichiers plus ou moins organisés que je restaurais manuellement sur chaque nouveau système, en espérant qu'ils soient à jour. Ça fonctionnait, mais c'était fragile, source d'erreurs, et il manquait souvent des éléments importants au moment de la réinstallation.


Pourquoi chezmoi ?

Parmi les outils disponibles pour gérer ses dotfiles (stow, yadm, etc.), j'ai choisi chezmoi. Sa philosophie est simple: maintenir un dépôt git comme source de vérité, puis générer automatiquement les fichiers dans le $HOME. Contrairement aux approches basées uniquement sur des symlinks, chezmoi apporte ce qu'il faut quand on travaille sur plusieurs machines: templating par OS ou par hôte, gestion des secrets, scripts d'initialisation et application idempotente de la configuration.

Des solutions plus poussées comme Nix existent, mais la configuration système n'est clairement pas un sujet sur lequel je souhaite passer trop de temps. L'objectif est simple: gagner du temps et du confort. Il me faut un système suffisamment puissant pour gérer des environnements hétérogènes, tout en restant simple à initialiser sur une nouvelle machine.

J'ai utilisé principalement 3 fonctionnalités de chezmoi:

Le templating : un seul fichier source peut générer des configs différentes selon l'OS, le rôle de la machine ou des préférences personnelles. Le .zshrc, la config git, les settings VS Code sont tous templatés.

Le chiffrement natif : chezmoi intègre age pour chiffrer les fichiers sensibles directement dans le repo. Le .npmrc avec ses tokens est versionné, mais chiffré.

Les scripts run_onchange_ : des scripts qui se ré-exécutent automatiquement quand leur contenu (ou celui d'un fichier tracké) change. Pas besoin de se souvenir de ce qu'il faut relancer.


La boîte à outils

Avant de parler architecture, voici les outils que j'ai retenus. Le critère principal: des outils modernes, rapides, et qui remplacent avantageusement les commandes classiques.

Shell

J'ai longtemps utilisé oh-my-zsh, qui est une excellente solution. Mais dorénavant, je préféré passer à du zsh natif pour un système plus simple et plus performant. Pas de framework, pas de plugin manager. Juste des modules sourcés depuis un .zshrc minimal :

# ~/.zshrc - managed by chezmoi
source ~/.config/zsh/path.zsh
source ~/.config/zsh/exports.zsh
source ~/.config/zsh/completion.zsh
source ~/.config/zsh/aliases.zsh
source ~/.config/zsh/functions.zsh
source ~/.config/zsh/integrations.zsh

Chaque fichier a une responsabilité claire. Besoin de modifier un alias ? C'est dans aliases.zsh. Un problème de PATH ? C'est dans path.zsh.

Système

Les classiques ls, cat, find, du ou ps font le job, mais leurs alternatives modernes offrent une meilleure ergonomie: coloration, sortie lisible, performances.

Outil

Remplace

Pourquoi

eza

ls

Coloration, icônes, arborescence intégrée (eza --tree)

bat

cat

Coloration syntaxique, numéros de ligne, intégration git

fd

find

Syntaxe simple, rapide, respecte .gitignore

ripgrep

grep

Recherche récursive ultra-rapide, respecte .gitignore

fzf

Recherche floue interactive pour fichiers, historique, branches...

dust

du

Visualisation de l'espace disque en arborescence

procs

ps

Liste des processus lisible, colorée, avec filtrage

Développement

Outil

Rôle

mise

Gestionnaire de runtimes (Node, Python, Bun). Remplace nvm/pyenv/asdf en un seul outil

direnv

Charge automatiquement des variables d'environnement par projet via un .envrc

just

Task runner. Comme make, mais plus simple. Fichier justfile lisible

lazygit

Interface TUI pour git. Staging, rebase, stash en quelques touches

gh

CLI GitHub officielle. PRs, issues, actions depuis le terminal

Interface

Outil

Rôle

Ghostty

Terminal GPU-accelerated, rapide, configurable. Mon terminal principal

Starship

Prompt shell personnalisable écrit en Rust. Affiche contextuellemnt la branche git, la version du runtime, le status de la dernière commande. Compatible zsh, bash, fish. Très rapide

Ces outils sont installés automatiquement par les scripts de bootstrap, selon les feature flags de la machine.


Architecture du dépôt

Voici la structure de mon dépôt dotfiles :

~/.local/share/chezmoi/
├── .chezmoidata.yaml                        # Variables & feature toggles
├── .chezmoi.toml.tmpl                       # Config chezmoi (encryption, données promptées)
├── run_onchange_bootstrap-system.sh.tmpl    # Packages de base, CLI modernes, fonts
├── run_onchange_bootstrap-dev.sh.tmpl       # mise, direnv, lazygit, docker
├── run_onchange_bootstrap-desktop.sh.tmpl   # Ghostty, VS Code, Starship
├── dot_zshrc.tmpl                           # Point d'entrée zsh (modulaire)
├── dot_config/
   ├── zsh/                                 # Modules : aliases, path, completion...
   ├── git/
   └── config.tmpl                      # Config git (XDG)
   ├── ghostty/
   └── config.tmpl                      # Terminal Ghostty
   ├── starship.toml                        # Prompt Starship
   └── mise/
       └── config.toml.tmpl                 # Runtimes (Node, Python, Bun)
└── dot_local/
    └── bin/
        ├── executable_dotfiles-doctor.sh.tmpl   # Health check
        └── executable_dotfiles-backup.sh.tmpl   # Backup chiffré

Chaque fichier suit les conventions de nommage de chezmoi. Les préfixes et suffixes du nom source déterminent le comportement dans le $HOME :

Préfixe / Suffixe

Effet

dot_

Fichier caché (.): dot_zshrc.zshrc

.tmpl

Fichier interprété comme template Go

executable_

Ajoute les bits d'exécution sur la cible

private_

Retire toutes les permissions groupe et world (équivalent 0600)

readonly_

Retire les bits d'écriture sur la cible

encrypted_

Fichier source chiffré (déchiffré à l'apply)

run_onchange_

Script ré-exécuté quand son contenu change

run_once_

Script exécuté une seule fois (jamais rejoué)

exact_

Répertoire strict : supprime les entrées absentes de la source

modify_

Script qui reçoit le fichier existant en stdin et produit la cible en stdout

create_

Fichier créé uniquement s'il n'existe pas déjà

remove_

Supprime l'entrée correspondante dans le $HOME

Les préfixes se combinent : private_dot_gitconfig.tmpl produit un .gitconfig templaté en 0600.


Feature toggles : un setup modulaire

Toutes les machines n'ont pas les mêmes besoins. Un serveur n'a pas besoin de Ghostty. Une machine perso n'a pas besoin des outils de dev. La solution : un fichier .chezmoidata.yaml qui pilote tout.

machine:
  role: "desktop"       # desktop ou server
  desktop: "kde"        # gnome, kde, sway, none

features:
  gui: true
  docker: true
  kubernetes: false
  cloud_sdk: false
  work_profile: false

development:
  editor: "code"
  terminal: "ghostty"

tools:
  node_version: "lts"
  python_version: "3.11"

Les scripts de bootstrap lisent ces flags :

{{ if not .features.install_dev_tools -}}
echo "Dev tools install disabled, skipping."
exit 0
{{ end -}}

Besoin de Docker sur une machine ? Il suffit de passer docker: true, puis chezmoi apply. Le script se ré-exécute car le hash de .chezmoidata.yaml a changé. Le même repo, adapté à chaque contexte.


Gestion des secrets avec age

Les clés SSH privées ne sont jamais dans le repo (.chezmoiignore). Elles sont restaurées via un script de backup dédié, présenté plus bas. Le fichier .npmrc avec ses tokens est chiffré avec age, versionné dans git mais illisible sans la clé.

La clé age elle-même vit à ~/.config/sops/age/chezmoi.txt, protégée par le .gitignore. Pour une nouvelle machine, il suffit de la restaurer depuis un backup.


Bootstrap en 3 phases

L'installation se déroule dans un ordre précis :

Phase 1 / System : packages de base, outils CLI modernes (eza, bat, fd, ripgrep, fzf, dust, procs), fonts Nerd Font, zsh comme shell par défaut.

Phase 2 / Dev : mise pour les runtimes (Node, Python, Bun), direnv pour les variables d'environnement par projet, lazygit, gh, just, Docker.

Phase 3 / Desktop : Ghostty, VS Code, Starship, Chrome, Discord, Obsidian, Spotify.

Chaque phase est conditionnée par ses feature flags. Un serveur headless s'arrête à la phase 2 (voire 1). Le support multi-OS (Arch/Manjaro, Ubuntu, macOS) est géré par des blocs conditionnels dans les templates. Pas de magie ici, c'est une partie fastidieuse à maintenir :

{{ if or (eq $osId "arch") (eq $osId "manjaro") -}}
sudo pacman -S --needed --noconfirm mise direnv lazygit
{{ else if eq $osId "ubuntu" -}}
curl https://mise.run | sh
sudo apt install -y direnv
{{ else if eq $os "darwin" -}}
brew install mise direnv lazygit
{{ end -}}

Outils de sécurité : doctor et backup

Deux scripts utilitaires complètent le setup :

dotfiles-doctor, un health check qui vérifie que tout est en place : zsh comme shell par défaut, starship actif, ghostty installé, mise configuré, docker disponible. Chaque vérification renvoie [OK], [WARN] ou [FAIL].

dotfiles-doctor.sh
# [OK]  zsh is default shell
# [OK]  starship prompt active
# [OK]  ghostty installed
# [FAIL] direnv not found

dotfiles-backup crée une archive chiffrée contenant les clés SSH, les clés GPG, la liste des extensions VS Code, les packages installés et les repos du workspace. Indispensable avant un changement de machine.


Résultat

Sur une machine neuve, tout se résume à :

sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply kepennar

En quelques minutes :

  • Tous les paquets sont installés

  • Le shell est configuré (zsh + compinit + starship)

  • Git, VSCode,Obsidian sont prêts à l'emploi

  • Les secrets sont déchiffrés et en place

Le dépôt complet est disponible sur GitHub :

github.com/kepennar/dotfiles