<aside> π
Managing our dotfiles across multiple machines and operating systems can be complex. A tool designed for this purpose, chezmoi, can streamline this process and store the dotfiles in a GitHub repository, manage the differences between systems (using a template), and secure some of those files using encryption.
</aside>
Revision: 20241108-0 (init
: 20241101)
ChezMoi is a versatile tool designed to manage our personal configuration files, or dotfiles
, across multiple machines and operating systems. It enables us to maintain a consistent environment by seamlessly synchronizing configurations like .zshrc
or .gitconfig
. Its key features include templating to handle machine-specific differences, integration with various password managers for secure secret management, and support for full file encryption using tools like GPG or age
.
We note that we will use GitHub but not use the toolβs capability to commit and push to our GitHub repository automatically.
The tool provides multiple ways to install it; see https://www.chezmoi.io/install/ for details.
chezmoi
supports age
for encryption. Age is a modern, user-friendly encryption tool and Go library designed for secure file encryption, and it emphasizes simplicity and security by utilizing small, explicit keys and eliminating complex configuration options.
We will install the tools using brew
.
brew install chezmoi age
After installation, initialize chezmoi
to set up its source directory:
chezmoi init
This command creates a new Git repository in ~/.local/share/chezmoi
, where chezmoi
stores its source state.
<aside> π‘
We note that you need an authorized local SSH key to push/pull from GitHub.
</aside>
For persistence, we will add it to a new dotfiles
GitHub repository. This repository can be public or private. Other Git-compatible sites such as GitLab, Gitea or BitBucket will also work. After creating our new dotfiles
repository for our example
user, we must configure chezmoi
to use this remote repository:
# cd into chezmoi's directory (at ~/.local/share/chezmoi)
chezmoi cd
# adapt EXAMPLE to match your repository
git remote add origin [email protected]:EXAMPLE/dotfiles.git
git branch -M main
# add a single file (that will exist as is), for example ~/.gitconfig
chezmoi add ~/.gitconfig
# Add it to git
git add dot_gitconfig
git commit -m "Adding .gitconfig"
# Push it to the remote repo
git push -u origin main
We see that the file named .gitconfig
was renamed by ChezMoi as dot_gitconfig
. Chezmoi uses special prefixes and suffixes in filenames to encode metadata and attributes about how files should be managed, such as permissions, encryption, and templating.
File naming conventions in the chezmoi
source directory are crucial for defining how files are managed and applied to the destination directory. These conventions use specific prefixes and suffixes to indicate the desired behavior for each file or directory. Among the common prefixes are:
dot_
is replaced with a .
in the destination directory. For example, a source file named dot_bashrc
becomes .bashrc
in the destination.private_
: Sets the file or directory's permissions to be private (i.e., readable and writable only by the owner); i.e. private_dot_ssh
ensures the .ssh
directory has restricted permissions.executable_
makes the file executable, such that executable_dot_script.sh
results in a .script.sh
file with execute permissions.symlink_
to create a symbolic link at the destination. The file's contents specify the link's target. For example, a symlink_dot_config
whose file content is /etc/config
would create a symlink for a .config
pointing to /etc/config
.run_ ****
designates the file as a script to be executed. For example as run_install.sh
would be executed during the apply
process..tmpl
****indicates that the given file is a template, allowing dynamic content generation based on variables. A dot_gitconfig.tmpl
would be processed as a template to produce .gitconfig
(see more details on file templates for ChezMoi at https://www.chezmoi.io/user-guide/templating/ and in the later chezmoi add
section of this document).For more details, also refer to https://www.chezmoi.io/reference/target-types/
The details on using age
with chezmoi
are available at https://www.chezmoi.io/user-guide/encryption/age/ and the FAQ has the process we will follow, at https://www.chezmoi.io/user-guide/frequently-asked-questions/encryption/
chezmoi cd
# Note: the following files are in chezmoi's local git repository to be uploaded to GitHub
# after git add, git commit and git push are performed
# Generate an age private key encrypted with a passphrase in the file key.txt.age
age-keygen | age --armor --passphrase > key.txt.age
# make a note of the public key and passphrase as those will be needed later
# Add key.txt.age to .chezmoiignore to avoid it being copied out of the chezmoi location
echo key.txt.age >> .chezmoiignore
# create a template script to decrypt the passphrase-encrypted private key if needed:
cat > run_once_before_decrypt-private-key.sh.tmpl <<EOF
#!/bin/sh
if [ ! -f "${HOME}/.config/chezmoi/key.txt" ]; then
mkdir -p "${HOME}/.config/chezmoi"
chezmoi age decrypt --output "${HOME}/.config/chezmoi/key.txt" --passphrase "{{ .chezmoi.sourceDir }}/key.txt.age"
chmod 600 "${HOME}/.config/chezmoi/key.txt"
fi
EOF
Create a ~/.config/chezmoi/chezmoi.toml
file (making sure encryption
is at the top of the file, before any other section), adapting the AGE_PUBLIC_KEY
with the value obtained earlier:
encryption = "age"
[age]
identity = "~/.config/chezmoi/key.txt"
recipient = "AGE_PUBLIC_KEY"
Use chezmoi
to generate its configuration file and decrypt the private key; we will be prompted for the secret passphrase (make sure to store it is a safe location such as a password manager, as it will be needed for any new setup).
chezmoi init --apply
This will create a ${HOME}/.config/chezmoi/key.txt
file containing the decrypted private key on that system.
The final step of the encryption setup is to add the four files present in the chezmoi cd
location to GitHub.
chezmoi cd
# Copy the newly created chezmoi configuration file to the chezmoi source path
# per <https://github.com/twpayne/chezmoi/issues/3638#issuecomment-1986937646>
cp ~/.config/chezmoi/chezmoi.toml "$(chezmoi source-path)/.chezmoi.toml.tmpl"
git status
# should report: .chezmoi.toml.tmpl .chezmoiignore key.txt.age
# and: run_once_before_decrypt-private-key.sh.tmpl
# "git" add, commit, and push the files to GitHub
git add .
git commit -m "age set additions"
git push
The chezmoi add
command is used for incorporating existing files and directories into ChezMoi. The command offers various options to handle different scenarios:
to add regular files, use ****chezmoi add ~/.bashrc
which will copy the current state of ~/.bashrc
into chezmoi
's source directory
adding files as Templates: If a file contains machine-specific configurations, chezmoi add --template ~/.gitconfig
will creates a template version of ~/.gitconfig
, allowing for dynamic content based on variables, such as {{ .chezmoi.hostname }}
or more complex if ... else
selections such as:
{{ if eq .chezmoi.os "darwin" }}
# darwin
{{ else if eq .chezmoi.os "linux" }}
# linux
{{ else }}
# other operating system
{{ end }}
sprig
library..tmpl
suffix or are located in the .chezmoitemplates
directory.
.chezmoitemplates
directory can be used to store reusable template snippets that can be included in other templates.chezmoi execute-template
command.chezmoi add --autotemplate ~/.gitconfig
which implies the --template
option and attempts to create a template with variable substitutions.Adding and encrypting sensitive files**:** chezmoi add --encrypt ~/.ssh/id_ed25519
encrypts the private SSH key using age
, ensuring its security within the git repository and because of we used passphrase for age
, the file is stored encrypted and protected by this passphrase (as always, protect your secrets).
Adding directories:
chezmoi add --recursive ~/.vim
includes all files and subdirectories within ~/.vim
chezmoi add --prompt ~/.config
interactively prompts before adding each file within ~/.config
.chezmoi add --exact ~/.config
Adding symbolic links by following targets (if the target is a symlink and you want to add the actual file it points to): chezmoi add --follow ~/.config/symlinked_file
adds the target of the symlink instead of the symlink itself.
For further details on add
see https://www.chezmoi.io/reference/commands/add/
After having added a few files and uploaded those on GitHub, we can use those dotfiles
on other systems. Below is a list of some of the files on that GitHub repository, where we can see various dot_
, .tmpl
, .age
, private_
, encrypted_
and run_
ChezMoi files.
β― tree
.
βββ dot_gitconfig
βββ dot_p10k.zsh
βββ dot_zshrc.tmpl
βββ key.txt.age
βββ private_dot_ssh
βΒ Β βββ config.d
βΒ Β βΒ Β βββ infra
βΒ Β βΒ Β βββ services
βΒ Β βββ encrypted_private_id_ed25519.age
βΒ Β βββ encrypted_private_id_rsa.age
βΒ Β βββ id_ed25519.pub
βΒ Β βββ id_rsa.pub
βΒ Β βββ private_config
βΒ Β βββ private_id_ed25519-pwl.pub
βββ run_once_before_decrypt-private-key.sh.tmpl
<aside> π‘
The ssh
key to git pull
the repository from GitHub needs to be on this new system.
</aside>
<aside> π
If used, update oh-my-zsh
and powerline10k
to avoid older version not recognizing our configuration files.
omz update
git -C ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k pull
</aside>
On the new system, make sure we have chezmoi
and age
available:
brew install chezmoi age
Initialize chezmoi
from git
(adapt EXAMPLE
to match your GitHub repository)
chezmoi init [email protected]:EXAMPLE/dotfiles.git
<aside> π
</aside>
First, we need to extract the encrypted age
key so we can check any changes before applying those changes. chezmoi apply
would perform this as well but it would also force apply the changes. To be able to see what would be modified, we need to prepare chezmoi
to recognize all our imported files.
chezmoi cd
chezmoi execute-template < ./run_once_before_decrypt-private-key.sh.tmpl > ~/chezmoi-decrypt.sh
chmod +x ~/chezmoi-decrypt.sh
~/chezmoi-decrypt.sh
# We will be prompted for the passphrase
# Check that the local decrypted key was generated
ls $HOME/.config/chezmoi/key.txt
# Erase the one-time use decryption script
rm ~/chezmoi-decrypt.sh
Use chezmoi status
to obtain the list of files that would be modified by a chezmoi apply
.
For each file, we can see if the file is to be Added, Modified or Removed. For those files that are modified, we can chezmoi diff
to see the changes. For example if ~/.gitconfig
appeared on the list, we can chezmoi diff ~/.gitconfig
and see the changes. The +
entries are changes from the chezmoi
controlled files, while -
are removals from the existing local files.
To alter some changes, edit the chezmoi
controlled file, before applying the changes to that file.
# Check the difference for modified files
chezmoi diff ~/.gitconfig
# + entries are additions in the chezmoi controlled files
# - are removals from the local file
# In some cases, before applying the chezmoi version of a file, we may want
# to incorporate content from the local files
# We must edit the chezmoi version of that file
EDITOR="nano" chezmoi edit ~/.gitconfig
# We can apply the modified (and maybe edit-ed) version
chezmoi apply ~/.gitconfig
# After this apply, when running git status
# the ~/.gitconfig file is now removed from that list
# We can then continue the diff/edit/apply steps
# until there are no more changes to review
# or we are comfortable with the leftover files to update
After performing the check for each file to be Modified, we can then apply any other changes:
chezmoi apply