This page takes you from nothing to a managed ~/.zshrc in about five minutes,
then sets up a second machine from the same repository.
# Homebrew (macOS, Linux)
brew install subbeh/tap/statemate
# Arch Linux (AUR)
yay -S statemate-bin # or: paru -S statemate-bin
# Prebuilt binary: mate_{linux,darwin}_{amd64,arm64}.tar.gz
mkdir -p ~/.local/bin
curl -fsSL https://github.com/subbeh/statemate/releases/download/latest/mate_linux_amd64.tar.gz \
| tar -xz -C ~/.local/bin mate
# From source (Go 1.25+)
go install github.com/subbeh/statemate/cmd/mate@latestThe binary is called mate. Check it with mate version.
statemate keeps your configuration in an ordinary git repository, usually
~/dotfiles:
mkdir ~/dotfiles && cd ~/dotfiles
mate initmate init asks for a config format (press Enter for YAML), then:
- writes a commented
mate.yaml - writes a
README.mdwith setup instructions, unless you already have one - runs
git init, unless the directory is already in a git repository - offers to register the directory: answer
y
Register this directory as your dotfiles location? [Y/n]: y
Saved to ~/.config/statemate/config.yaml
Registering writes source_dir: "~/dotfiles" to the
local config, which is what lets every mate
command work from any directory. mate config source-dir prints the directory in
use.
A source is a directory in the repository whose contents are deployed
relative to your home directory. Create one per application and list it in
mate.yaml:
mkdir zsh# mate.yaml
sources: [zsh]Inside a source, paths mirror your home directory: zsh/.zshrc deploys to
~/.zshrc, and nvim/.config/nvim/init.lua deploys to ~/.config/nvim/init.lua.
See Organising Your Repository for a layout that
scales.
mate add ~/.zshrcmate add copies the file into a source and leaves the original in place. It
asks which source to use, offering the ones listed in mate.yaml. Pick one with
the arrow keys, or press / to search:
? Select source:
▸ zsh
nvim
git
Skip the question with --source zsh, or set
default_source in mate.yaml. The source must
be listed under sources:. If --source names one whose directory does not
exist yet, mate add offers to create the directory and adds the file to it;
it does not edit mate.yaml, so list the new source under sources: yourself.
To add all files in a directory, including hidden files and nested directories:
mate add ~/.config/nvim --source nvimThe directory structure stays relative to the target base: this puts the files
under nvim/.config/nvim/ in your repository. --for-profile, --encrypt, and
--template apply to every added file. File permissions are copied and the
original files stay in place.
Files already managed by the selected source are left unchanged. If any file belongs to a different active source, the command reports an error before copying any files. Adding an individual managed file still reports an error. File symlinks are copied as ordinary files containing the linked file's contents; directory symlinks are skipped. Empty directories and configured source directories are also skipped, so adding a parent of your source cannot copy it into itself. Broken symlinks, unreadable files, and special files cause an error. If reading or writing a file fails during copying, files already added remain in the source; correct the error and run the command again.
~/dotfiles/
mate.yaml
zsh/
.zshrc → ~/.zshrc
Nothing is reported as pending yet, because ~/.zshrc and its source are
identical. statemate starts tracking it on the next mate apply:
mate apply # 1 unchangedFrom now on, edit the copy in the repository, not the deployed file. An edit
made to ~/.zshrc directly turns into a conflict on the next apply.
mate edit ~/.zshrc opens the right one for you.
Then, in increasing order of commitment:
mate status # which files would change
mate diff # exactly how they would change
mate apply # write them$ mate status
TARGET SOURCE
~ ~/.zshrc zsh
$ mate apply
applied 1
mate apply --dry-run walks the whole run, including scripts and packages,
without writing anything, and -l lists every file written.
If a deployed file was changed behind statemate's back, apply stops and asks what to do:
Conflict: /home/you/.zshrc
Target has been modified since last apply
[o]verwrite, [i]mport, [s]kip, [d]iff, [a]bort:
[i]mport copies the change back into the repository. See
Concepts for how statemate tells which side changed.
Commit and push the repository as you would any other:
cd ~/dotfiles && git add -A && git commit -m "Add zsh" && git pushInstall mate, then clone and register the repository:
git clone git@github.com:you/dotfiles.git ~/dotfiles
cd ~/dotfiles && mate init # finds mate.yaml, registers it
mate applyOn a machine that already has its own ~/.zshrc, the first apply reports a
conflict for it, since statemate has no record of which version is newer. Answer
[d] to compare, then [o] to take the repository's version or [i] to keep
the machine's.
Setting Up a New Machine covers the rest: encryption keys, secrets, packages, and a bootstrap script.
- Different files on different machines: Different Machines
- Values that vary per machine: Templates
- Private keys and tokens: Encryption and Secrets
- Install packages declaratively: Packages
- Run something after applying: Scripts and Hooks
- Files under
/etc: Managing System Files - Editor, shell and status bar integration: Tips & Tricks
- Something not working:
mate doctor, then Troubleshooting