dotfilers is a Rust CLI that helps you manage your personal configuration files by moving them into a Git-backed repository (DOTFILERS_HOME) and replacing the originals in your home directory (DOTFILERS_PREFIX) with symlinks. The tool keeps your dotfiles version-controlled, portable, and easy to restore on fresh machines.
- 🔁 Atomic moves + symlinks – files are relocated to the repo and safely linked back.
- 🧩 Per-file tracking by default – directories are walked and added file-by-file so you can ignore specific files with
.gitignore. - 🗂️ Directory mode (
-d) – opt in to treating a directory as a single symlink when that layout is preferable. - 📝 Built-in
.gitignoresupport – ignored patterns stay untouched in your working tree. - ♻️
restorecommand – revert every tracked entry (directories or files) back into place and unstage them. - 🛡️ Safety guardrails – prevents operating outside your
$HOME, refuses to clobber real files, and avoids circular symlinks.
# from source
cargo install --path .
# or build locally
cargo build --releaseCopy target/release/dotfilers somewhere on your $PATH if you build manually.
dotfilers needs two variables that define where the repo lives and which tree you are mirroring:
| Variable | Description | Default prompt value |
|---|---|---|
DOTFILERS_HOME |
Repository root that holds the real files and Git data | $HOME/dotfiles |
DOTFILERS_PREFIX |
The live filesystem tree to mirror with symlinks | $HOME |
If either variable is unset, dotfilers will prompt once and print an export snippet you can drop into your shell config.
Move the given files into DOTFILERS_HOME and leave symlinks in DOTFILERS_PREFIX.
- Without
-d, any directory arguments are traversed recursively and each file is moved individually. Patterns are matched via glob semantics, and results that match.gitignoreinDOTFILERS_PREFIXare skipped. - With
-d/--dir, directory arguments are treated as single units: the entire directory is moved and replaced by one symlink (the legacy behaviour)..gitignoreis ignored in this mode.
Examples:
# add your bash config
cd ~
dotfilers add .bashrc
# add an entire profile directory but skip *.bak files thanks to .gitignore (assuming .gitignore has *.bak line)
cd ~
dotfilers add ~/.config/pipewire
# preserve the directory symlink instead of per-file entries
dotfilers add -d ~/.config/pipewireIf a pattern matches nothing, the command exits with an error so you notice typos. If everything matched was ignored by .gitignore, a message is printed and the run succeeds without modifying the repo.
Run from inside DOTFILERS_HOME to untrack specific entries. Each target is moved back into DOTFILERS_PREFIX, the symlink is removed, and git rm --cached updates the index.
cd "$DOTFILERS_HOME"
dotfilers rm .bashrc .config/kitty/kitty.confRecreate every symlink from DOTFILERS_HOME into DOTFILERS_PREFIX. Parent directories are created with mkdir -p semantics, and existing symlinks are replaced. Non-symlink files blocking the target abort the run without overwriting user data.
dotfilers linkDestroy every symlink and move the tracked files or directories back into DOTFILERS_PREFIX, effectively undoing dotfilers add. Entries are unstaged (git rm --cached), and the repository tree is cleaned up afterward.
dotfilers restoreGreat for migrating machines—or when you simply want to stop tracking everything.
Create a .gitignore file in DOTFILERS_HOME. When using the default per-file mode, any file that matches the ignore rules is skipped and left untouched, letting you keep caches, secrets, or large blobs out of the repo.
Example .gitignore snippet:
# binaries
*.so
# local secrets
*.gpg
- All paths must resolve inside your
$HOME; absolute paths elsewhere are rejected. DOTFILERS_HOMEmust itself live within$HOMEand cannot equal$HOME.- The tool aborts rather than overwrite non-symlink files in your prefix, unless used with the
-fflag. - Circular symlink chains are detected and rejected before modifications happen.
cargo fmt
cargo clippy -- -D warnings -W clippy::pedantic
cargo testIntegration tests use temporary directories and mock git repos so they are safe to run locally.