20241107-chezmoi-cut.png

<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.

Initial setup with encryption

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.

chezmoi’s file naming

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:

For more details, also refer to https://www.chezmoi.io/reference/target-types/

Preparing for encryption with age

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

Adding new files: chezmoi add

The chezmoi add command is used for incorporating existing files and directories into ChezMoi. The command offers various options to handle different scenarios:

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

Deployment from an existing GitHub on a new system

<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

Untitled

Untitled