
Description
Setting up a new machine is rarely about installing software — it is the scattered config that hurts. Two years of aliases in .zshrc, nvim's init.lua, the tmux prefix binding, those few internal host entries in /etc/hosts: each one has to be dug out of the old machine and pasted back, and you find the one you missed only when you need it. dotkeep reduces that to a plain-text list — one path per line in .dotkeep.conf. Run
What sets it apart from the usual options is that the tool and the backup are completely separate: this repo is just the bash script, while your config lives in a directory you create, laid out as two trees,
One file is the source of truth:
home/ and root/ trees: user config and system config share one list, and the repo mirrors the live filesystem with the same nesting and the same names, so reading the repo is the same as reading the machine.
Copies both ways, no symlinks: backup and restore are rsync copies and live paths stay ordinary files. Unlike Stow or rcm, an editor that saves by writing a tempfile and renaming it over the path cannot silently replace a link and leave the repo holding the old content.
$HOME is not the store: bare-git setups and yadm make your home directory the work tree, where one
It asks before writing: both commands confirm before touching disk, and restore parks the existing live targets under
Git is optional: the store is the file tree itself. Only after a copy does it offer
Validate first:
Sensible skips by default: symlinks (leaf and nested), devices, fifos and sockets are never synced; nested
Clear precedence rules: if the list names both a directory and paths beneath it, the directory wins and the children are dropped; unreadable files are skipped while the rest of the directory still copies, so one bad file does not abort the run.
Requirements and limits: pure bash, needing bash 5, git, rsync, tree and GNU coreutils; Linux only, no Windows. The script never calls
dotkeep backup to pull everything on the list into a repo, and on the new box a clone plus dotkeep restore puts it all back.What sets it apart from the usual options is that the tool and the backup are completely separate: this repo is just the bash script, while your config lives in a directory you create, laid out as two trees,
home/ and root/, with names identical to what is on disk — home/.zshrc is $HOME/.zshrc, root/etc/hosts is /etc/hosts. No symlinks, no treating $HOME as a git work tree, and no renaming things to dot_zshrc.Features
One file is the source of truth:
.dotkeep.conf is plain text, one path per line, # for comments, and every entry must start with home/ or root/. Adding or dropping a path means editing that file — no commands to memorize, no second copy of the state.home/ and root/ trees: user config and system config share one list, and the repo mirrors the live filesystem with the same nesting and the same names, so reading the repo is the same as reading the machine.
Copies both ways, no symlinks: backup and restore are rsync copies and live paths stay ordinary files. Unlike Stow or rcm, an editor that saves by writing a tempfile and renaming it over the path cannot silently replace a link and leave the repo holding the old content.
$HOME is not the store: bare-git setups and yadm make your home directory the work tree, where one
git add -A can commit SSH keys and caches, and /etc does not fit at all. dotkeep only touches paths the manifest names.It asks before writing: both commands confirm before touching disk, and restore parks the existing live targets under
/tmp/dotkeep.XXXXXX/ first. A bad path is skipped and the rest still goes through.Git is optional: the store is the file tree itself. Only after a copy does it offer
git add -A, commit and push, and the default answer is no every time — Syncthing or a copy to an external disk works just as well.Validate first:
dotkeep check parses the manifest, resolves each entry to its real location and reports whether it is present in the repo, so you can see exactly what will be touched before running anything.Sensible skips by default: symlinks (leaf and nested), devices, fifos and sockets are never synced; nested
.git and node_modules content is stripped; files over 10 MiB are skipped with a warning (DOTKEEP_MAX raises it, capped at 100 MiB because GitHub rejects the push above that); and directory sync respects the state repo's .gitignore.Clear precedence rules: if the list names both a directory and paths beneath it, the directory wins and the children are dropped; unreadable files are skipped while the rest of the directory still copies, so one bad file does not abort the run.
Requirements and limits: pure bash, needing bash 5, git, rsync, tree and GNU coreutils; Linux only, no Windows. The script never calls
sudo itself, so restoring root/ requires write access to those paths. Templates, encryption and per-host variants are explicitly out of scope — the author points at chezmoi or dotdrop for those. MIT licensed.
