Keep your shell, editor, and tool config in sync across every machine you use.
π Rendered documentation: docs.georgeharker.com/dotfiler Β· dev
- Why Dotfiler?
- Documentation
- Quick Start
- Installation
- New Machine Setup
- Day-to-Day
- Updates at Login
- Modular Install System
- Shell Completions
- Directory Structure
- Troubleshooting
Your dotfiles -- .zshrc, .gitconfig, nvim/init.lua -- accumulate over
years. They live in your home directory, scattered and unversioned. When you set
up a new machine, you copy them by hand or write a fragile bootstrap script.
When you improve something on one machine, the others never hear about it.
Dotfiler fixes this with three ideas:
Git under the hood. Your dotfiles live in a single git repository
(~/.dotfiles). Every change is a commit; every machine gets the same history.
You already know the workflow: edit, commit, push. There is no proprietary
format, no lock-in, no database -- just a git repo you can inspect with any
tool you already know.
Symlink trees for seamless editing. Dotfiler replaces your config files
with symlinks that point into the repo. When you edit ~/.vimrc, you're
editing the file in ~/.dotfiles/.vimrc -- the change is already staged for
commit. No import/export step, no sync daemon, no copy-on-write tricks. Your
editor, shell, and tools see exactly the same paths they always did.
Automatic multi-machine updates. At login, dotfiler checks whether the remote has new commits. Depending on your configured mode, it can prompt you, apply silently, or run entirely in the background. Only files that changed in incoming commits get re-linked -- fast and safe. Third-party components (like zdot) can register as update hooks to ride the same update cycle.
| Dotfiler | GNU Stow | chezmoi | yadm | |
|---|---|---|---|---|
| Symlink management | Yes | Yes | No (templates) | Yes |
| Auto-update on login | Yes | No | No | No |
| Modular install system | Yes | No | Run scripts | No |
| Component update hooks | Yes | No | No | No |
| GUI | Yes (TUI) | No | No | No |
| Shell completions | Yes | N/A | Yes | Yes |
| Underlying storage | Plain git | Directory tree | Templates + git | git + encryption |
Why not GNU Stow? Stow handles the symlink-tree part well. Dotfiler adds the auto-update-on-login loop, a modular install system for bootstrapping new machines (packages, languages, apps), a hook system for coordinating component updates, and a TUI for exploring and managing tracked files. If you just want symlinks, Stow is simpler. If you want a full dotfile lifecycle -- track, sync, install, update -- dotfiler has you covered.
Dotfiler pairs naturally with zdot, a modular zsh configuration framework. Store zdot as a git submodule inside your dotfiles repo and dotfiler will automatically keep it updated alongside your config files. See docs/zdot-integration.md for the full setup, or the zdot README for a quickstart that covers both tools together.
| I want to⦠| Document |
|---|---|
| Look up a command or flag | CLI Reference |
| Configure anything (zstyles, exclusions, env vars) | Configuration Reference |
| Understand and tune updates | How Updates Work |
| Bootstrap machines with install scripts | Authoring Install Files |
| Hook my own component into updates | Update Hooks |
| See how updates work under the hood | Update Internals |
| Pair dotfiler with zdot | zdot Integration |
# Clone your dotfiles repo (or create one)
git clone <your-repo> ~/.dotfiles && cd ~/.dotfiles
# Add dotfiler as a git submodule inside the repo
git submodule add https://github.com/georgeharker/dotfiler .nounpack/dotfiler
chmod +x .nounpack/dotfiler/dotfiler
# Copy exclusion rules
cp ~/.dotfiles/.nounpack/dotfiler/dotfiles_exclude ~/.dotfiles/
# Track some dotfiles and create symlinks
.nounpack/dotfiler/dotfiler setup -i ~/.zshrc ~/.vimrc ~/.gitconfig
.nounpack/dotfiler/dotfiler setup -u
# Enable auto-update on login (add to your .zshrc)
echo '[[ -f ~/.dotfiles/.nounpack/dotfiler/check_update.zsh ]] && source ~/.dotfiles/.nounpack/dotfiler/check_update.zsh' >> ~/.zshrc
# Enable shell completions (optional)
echo 'source ~/.dotfiles/.nounpack/dotfiler/completions.zsh' >> ~/.zshrcKeeps dotfiler as a versioned dependency inside your dotfiles repo.
cd ~/.dotfiles
git submodule add https://github.com/georgeharker/dotfiler .nounpack/dotfiler
chmod +x .nounpack/dotfiler/dotfiler
git commit -m "Add dotfiler as submodule"On a new machine, after cloning your dotfiles repo:
git submodule update --init --recursive(Updating dotfiler itself is automatic once update mode is enabled β or run
dotfiler update-self.)
Embeds dotfiler's history directly into your dotfiles repo β no submodule dependency at clone time.
cd ~/.dotfiles
git remote add dotfiler https://github.com/georgeharker/dotfiler.git
git subtree add --prefix=.nounpack/dotfiler dotfiler main --squash
chmod +x .nounpack/dotfiler/dotfilerSelf-update defaults to a remote named dotfiler pointing at the canonical
repo, with the branch resolved through the normal chain (so
zstyle ':dotfiler:update' branch dev works). Override only for a custom
remote name or fork:
zstyle ':dotfiler:update' subtree-remote 'dotfiler'
zstyle ':dotfiler:update' subtree-url 'https://github.com/you/dotfiler.git'Prefer the single-word form β '<remote> <branch>' hard-pins the branch and
bypasses the branch zstyle entirely. See
Configuration.
git clone https://github.com/georgeharker/dotfiler ~/.dotfiler
chmod +x ~/.dotfiler/dotfilerThen point your dotfiles at it β in your .zshrc, before sourcing
check_update.zsh:
zstyle ':dotfiles:scripts' path "$HOME/.dotfiler"Clone your repo and restore all symlinks in one go:
git clone <your-repo> ~/.dotfiles
cd ~/.dotfiles
git submodule update --init --recursive # if using submodule
.nounpack/dotfiler/dotfiler setup -uThen optionally bootstrap your full environment:
.nounpack/dotfiler/dotfiler install(With registered hook components like zdot, use
dotfiler setup --bootstrap instead of setup -u β it also initializes
submodules for you, so the git submodule update step above can be skipped.
See zdot Integration.)
# Track a new config file
dotfiler setup -i ~/.config/newsoftware/config.toml
dotfiler commit -m "Track newsoftware config" && dotfiler push
# Edit a tracked config β symlinks mean it's already in the repo
vim ~/.vimrc
dotfiler add .vimrc && dotfiler commit -m "Update vim config" && dotfiler push
# Pull updates on another machine (or just open a new shell)
dotfiler update
# See repo state
dotfiler status --fetchThe full command set β setup flags, update flags, the git wrappers, the
TUI β is in the CLI Reference.
Source check_update.zsh from your .zshrc (Quick Start above) and pick a
mode:
zstyle ':dotfiler:update' mode 'prompt' # ask [Y/n] at login β default
zstyle ':dotfiler:update' mode 'auto' # pull silently
zstyle ':dotfiler:update' mode 'background' # never block the prompt
zstyle ':dotfiler:update' mode 'reminder' # print a nudge, never pull
zstyle ':dotfiler:update' mode 'disabled' # no checksFrequency, release channels (updates only on tagged releases by default), branch overrides, and the mechanics of the check are covered in How Updates Work.
Numbered zsh modules bootstrap a full development environment on a new machine. Copy the templates, customise, commit them alongside your config:
cp -r .nounpack/dotfiler/example_install/ .nounpack/install/
vim .nounpack/install/02-shell-utils.zsh
dotfiler install # run all modules
dotfiler install-module shell-utils # or just oneThe shipped example covers (in run order): dotfiler bootstrap, package
manager + fonts, shell utils, development tools, editors/terminals, editor
extras, programming languages, AI tools, applications, and post-install β
00-dotfiler-install.zsh through 09-post-install.zsh. Install scripts are
idempotent; --force re-installs, and profiles (INSTALL_PROFILE=work)
let one script set serve different machines.
Writing your own modules: Authoring Install Files. The helper library reference lives with the example: example_install/README.md.
# Add to .zshrc
source ~/.dotfiles/.nounpack/dotfiler/completions.zshProvides tab completion for all commands, flags, component names, and install-module names.
~/.dotfiles/
βββ .nounpack/
β βββ dotfiler/ # Dotfiler (submodule, subtree, or standalone elsewhere)
β β βββ dotfiler # Main command dispatcher
β β βββ setup.zsh / update.zsh / check_update.zsh / install.zsh
β β βββ completions.zsh
β β βββ dotfiles_exclude
β β βββ example_install/
β βββ install/ # Your customised install modules (committed)
βββ dotfiles_exclude # Your exclusion patterns
βββ .zshrc # Tracked dotfiles (symlinked into ~/)
βββ .vimrc
βββ .config/
βββ nvim/init.lua
Nothing under .nounpack/ is ever symlinked into $HOME.
# Scripts not executable after clone
chmod +x ~/.dotfiles/.nounpack/dotfiler/dotfiler
# Re-create all symlinks
dotfiler setup -u
# Force an update check / see what an update would do
dotfiler check-updates --force
dotfiler update -D
# Trace the login check
dotfiler check-updates --debugMore: How Updates Work β Debugging the login check.
Linting throughout this codebase is checked with shuck β a fast shell linter with first-class zsh support. Thanks to the shuck project for catching the bugs that bash-targeted linters miss.
