Setting Up a New Machine¶
With statemate, a new machine needs four things: the mate binary, your
repository, your age key if you use encryption, and one mate apply. This
page covers doing that by hand, and then automating it with a bootstrap script.
By hand¶
# 1. Install mate
brew install subbeh/tap/statemate # or yay/paru -S statemate-bin
# 2. Restore the age key (skip if you don't use encryption)
mkdir -p ~/.config/statemate
bw login
bw get item statemate | jq -r '.fields[] | select(.name=="age-key").value' \
> ~/.config/statemate/key.txt
chmod 600 ~/.config/statemate/key.txt
# 3. Clone and register the repository
git clone https://github.com/you/dotfiles.git ~/dotfiles
cd ~/dotfiles && mate init
# 4. Fetch secrets, check, then apply
mate profile
mate secrets fetch # skip if you don't use Bitwarden references
mate status
mate apply
mate status only reads the secrets cache, so fetching first matters when a
source's .mate.yaml uses bitwarden, which would otherwise fail to render.
mate apply fetches missing secrets itself.
Step 2 assumes the key is stored as a custom field age-key on a Bitwarden
item called statemate, as suggested in Encryption. Adapt it
to wherever you keep yours.
Check the profile first¶
mate profile should name the profile you expect for this machine. If none
matches, #profile: files are not filtered and every variant is deployed (see
Different Machines). Fix the detection rules, or pin the profile
for this machine in the local config:
# ~/.config/statemate/mate.yaml (written by mate init)
source_dir: "~/dotfiles"
profile: work
What the first apply does¶
The first mate apply on a new machine is the biggest it will ever be:
- Secrets referenced by templates are fetched from Bitwarden. You are asked for your master password if the vault is locked.
#beforescripts marked#oncerun, each confirmed first.- Files are written. Any target that already exists with different content,
such as the default
~/.zshrcthe OS ships, is a conflict, because there is no record of which side is newer. Answer[d]to compare and[o]to overwrite. - Packages missing on this machine are offered for install, one confirmation per package manager.
- Hooks triggered by the written files run.
#afterscripts marked#oncerun, such as changing the login shell or installing editor plugins.
mate apply --dry-run shows all of it beforehand, and mate scripts list shows
the scripts that are still pending.
Every #once script is recorded in this machine's state database, so it is not
offered again. Answering [s]kip marks one as done without running it.
A bootstrap script¶
To make a new machine a single command, put a script at the root of the
repository and fetch it with curl:
curl -fsSL https://raw.githubusercontent.com/you/dotfiles/main/install.sh | bash
A useful bootstrap script is interactive and safe to re-run. It asks before each step and skips what is already done. The steps:
#!/usr/bin/env bash
set -euo pipefail
# Prerequisites: a package manager and the Bitwarden CLI
command -v brew >/dev/null || /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
command -v bw >/dev/null || brew install bitwarden-cli
# statemate itself
command -v mate >/dev/null || brew install subbeh/tap/statemate
# The age key, and an SSH key to clone with, from the vault
export BW_SESSION=$(bw unlock --raw)
bw sync
mkdir -p ~/.config/statemate
bw get item statemate | jq -r '.fields[] | select(.name=="age-key").value' \
> ~/.config/statemate/key.txt
chmod 600 ~/.config/statemate/key.txt
# Clone, register, apply
[ -d ~/dotfiles ] || git clone git@github.com:you/dotfiles.git ~/dotfiles
cd ~/dotfiles
mate init
mate apply
BW_SESSION is exported, so the mate apply at the end reuses the unlocked
vault for its secrets instead of asking again.
A complete example, with prompts for each step, branch selection, and building
statemate from source, is
install.sh in
statemate's author's dotfiles.
--force in scripts¶
mate apply --force answers every prompt with yes: it overwrites every
conflicting file without a backup, runs every pending script and hook, and
installs every missing package. That suits a machine with nothing on it worth
keeping. On a machine you have used, run a plain mate apply and answer the
prompts.
Without a terminal, for example in CI or under ssh host 'mate apply', scripts,
hooks and package installs are skipped with a warning, and conflicts stop the
apply. Pass --force or --no-scripts to decide explicitly.