This repository contains my personal dotfiles, managed securely across multiple machines using chezmoi.
This setup is designed around a flexible and secure bootstrap process. It uses a primary age private key to decrypt secrets, but this primary key itself is never stored in plain text. Instead, it is created on-demand using either a forwarded SSH key for convenience or a master password as a fallback.
If the private key material isn't available (at ~/.config/age/key.txt) chezmoi apply will only place the config files that don't require decryption.
By default, Homebrew packages are synced weekly. To force an immediate update:
BREW_FORCE_UPDATE=1 chezmoi applyThis will bypass the weekly timer and immediately sync your Homebrew packages with your brewfile.
Example: Editing the RClone secrets
chezmoi edit ~/.config/rclone/secrets.conf
chezmoi apply- Install Homebrew (if not already installed):
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"- Install required tools:
brew install chezmoi age# Install age (example for Ubuntu/Debian)
sudo apt install age
# Install chezmoi
sh -c "$(curl -fsLS get.chezmoi.io)"Need to clone down the repo with SSH but I don't have any config files setup, so get a GITHUB key locally and...
GIT_SSH_COMMAND='ssh -i ~/.ssh/andy-anywhere' git clone [email protected]:andreweick/dotfiles.git
chezmoi init --source="$HOME/code/dotfiles" --applyTwo-Phase Bootstrap Process:
-
Phase 1 - Apply Non-Encrypted Files:
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply andreweickThis installs chezmoi and applies all non-encrypted dotfiles. Encrypted files are automatically skipped when the age key is missing.
-
Phase 2 - Decrypt and Apply Secrets:
"$(chezmoi source-path)/setup-age-key.sh" chezmoi applyThe setup script creates your age decryption key, then the second
chezmoi applyprocesses all the encrypted files.
If chezmoi is already installed, use the same two-phase process:
-
Phase 1 - Apply Non-Encrypted Files:
chezmoi init github.com/andreweick/dotfiles --apply
-
Phase 2 - Decrypt and Apply Secrets:
"$(chezmoi source-path)/setup-age-key.sh" chezmoi apply
Note: The master password for fallback decryption is stored in 1Password at:
op://Private/xcjfxrcih4tzajtsocvlkpjgm4/password
To pull the latest changes from your repository and apply them:
chezmoi updateThe entire security model hinges on a single script, setup_age_key.sh, which creates the master decryption key (~/.config/age/key.txt). This key is required by chezmoi (as configured in chezmoi.toml) to decrypt all other secret files.
The setup script intelligently chooses one of two methods to create this key:
-
SSH Agent (Primary Method): If you are connected to a machine with a forwarded SSH agent (
ssh -A), the script will automatically try to use any of your available SSH keys to decrypt a special file (master_key_ssh.age) in this repository. This provides a convenient, passwordless setup experience. -
Master Password (Fallback Method): If the SSH agent is not available or fails, the script will fall back to prompting you for a master password. It uses this password to decrypt a different file (
master_key_passphrase.age).
This hybrid approach provides the best of both worlds: passwordless convenience on remote systems and a reliable password fallback for your local machines.
graph TD
A(Run ./setup_age_key.sh) --> B{Does ~/.config/age/key.txt exist?};
B -- Yes --> C[✅ Done. Nothing to do.];
B -- No --> D{Is SSH Agent Forwarded?};
D -- Yes --> E[Try all SSH keys to decrypt `master_key_ssh.age`];
E --> F{Success?};
F -- Yes --> G[🎉 Creates `key.txt`];
D -- No --> H[Fallback to Password];
F -- No --> H;
H -- Prompts for Password --> I[Use password to decrypt `master_key_passphrase.age`];
I --> J{Success?};
J -- Yes --> G;
J -- No --> K[❌ Error: Decryption Failed];
To add a new file with secrets to your dotfiles:
-
Create the file in your home directory first:
# Create and edit the file where it will live touch ~/.config/myapp/secrets.conf nvim ~/.config/myapp/secrets.conf
-
Add it to chezmoi with encryption:
chezmoi add ~/.config/myapp/secrets.conf --encryptedThis will copy the file to your chezmoi source directory and encrypt it using your
~/.config/age/key.txt. -
For future edits, always use
chezmoi edit:chezmoi edit ~/.config/myapp/secrets.conf
Chezmoi intelligently handles missing decryption keys:
- When
~/.config/age/key.txtis missing:chezmoi applywill automatically skip all encrypted.agefiles without errors - When the key is present:
chezmoi applywill decrypt and apply all encrypted files normally
This enables the elegant bootstrap workflow:
- Fresh machine →
chezmoi apply→ only non-encrypted configs are applied - Run
./setup-age-key.sh→ creates your decryption key - Run
chezmoi applyagain → now encrypted files are also applied
No manual configuration needed - chezmoi automatically detects key availability.
You cannot edit encrypted source files directly. Instead, use the chezmoi edit command, which handles the decryption and re-encryption for you automatically.
Example: Editing the RClone secrets
chezmoi edit ~/.config/rclone/secrets.confchezmoi will find the corresponding source file (.../encrypted_private_secrets.conf.age), decrypt it into a temporary file, open your editor, and then re-encrypt it when you save and close.
Files that contain a mix of public and private data are split into two parts:
- A Base Template (
.../rclone.conf.tmpl): A template file containing all the non-sensitive configuration. This file contains logic to conditionally include the secrets. - An Encrypted Secrets File (
.../encrypted_private_secrets.conf.age): A small, fully encrypted file containing only the sensitive parts.
The base template uses chezmoi's template functions to check if the key.txt exists. If it does, it decrypts the secrets file in memory and appends its contents to the final generated file.
Atuin provides encrypted, searchable, and synced shell history across all your machines.
Note: On first run after bootstrapping a new system, you'll need to log in to Atuin and sync your history:
# Log in to Atuin (you'll need your username and password/key)
atuin login
# Force an initial sync to download your history from other machines
atuin sync --forceAtuin automatically syncs your history in the background. You can search your history by pressing Ctrl+R in your shell.
To manually sync at any time:
atuin syncThis system uses two helper scripts: setup_age_key.sh for bootstrapping and generate_encrypted_keys.sh for maintenance.
When you get a new computer or generate a new SSH key, you need to grant it permission to decrypt your master age key.
- Ensure the new public key is in
~/.sshon the machine where you are running the generation script. - Run the generation script:
./generate_encrypted_keys.sh
- The script will detect
master_key_ssh.agealready exists and askOverwrite? (y/N). Typeyand press Enter. - The script will then ask to overwrite the passphrase file. You can safely type
nunless you also want to change your master password. - A new
master_key_ssh.agefile will be created. This new file is now encrypted to all your SSH keys, both old and new. - Copy this new
master_key_ssh.ageinto yourchezmoisource directory, overwriting the old one (e.g., atprivate_dot_config/age/). - Commit the change to your Git repository.
- Run the generation script:
./generate_encrypted_keys.sh
- When it asks to overwrite the SSH key file, you can safely type
n. - When it asks to overwrite
master_key_passphrase.age, typeyand press Enter. - Enter and confirm your new master password when prompted.
- A new
master_key_passphrase.agefile will be created. - Copy this new
master_key_passphrase.ageinto yourchezmoisource directory, overwriting the old one. - Commit the change to your Git repository.
The ~/.config/private-files/ directory contains decrypted sensitive files like licensed fonts, software licenses, and installation scripts. These files are automatically decrypted during chezmoi apply when your age key is available.
~/.config/private-files/
├── fonts/ # Licensed font files (.otf, .ttf)
├── licenses/ # License files and installation scripts
└── README # Explains the directory purpose
To add a new licensed font or sensitive file:
-
Create the final directory structure:
mkdir -p ~/.config/private-files/fonts -
Place your file in the final location:
cp your-licensed-font.otf ~/.config/private-files/fonts/ -
Add it to chezmoi with encryption:
chezmoi add --encrypt ~/.config/private-files/fonts/your-licensed-font.otf -
Verify the encrypted file was created:
ls "$(chezmoi source-path)/private_dot_config/private-files/fonts/" # You should see: encrypted_your-licensed-font.otf.age
- Encrypted Storage: Files are stored as
encrypted_*.agein the chezmoi source directory - Automatic Decryption: During
chezmoi apply, files automatically decrypt to~/.config/private-files/if your age key (~/.config/age/key.txt) exists - Graceful Skipping: If no age key is available, encrypted files are silently skipped without errors
- Manual Usage: Files are decrypted for you to manually install/use as needed (no automatic installation)
- The
~/.config/private-files/directory is created with0700permissions (owner-only access) - This directory should never be added to git
- Only the encrypted
.ageversions are stored in your repository - Original files are safely encrypted using your age recipient key
Adding a licensed font:
cp OperatorMono-Bold.otf ~/.config/private-files/fonts/
chezmoi add --encrypt ~/.config/private-files/fonts/OperatorMono-Bold.otfAdding a license file:
cp software-license.txt ~/.config/private-files/licenses/
chezmoi add --encrypt ~/.config/private-files/licenses/software-license.txtAdding an installation script:
cp install-licensed-software.sh ~/.config/private-files/licenses/
chezmoi add --encrypt ~/.config/private-files/licenses/install-licensed-software.shThis setup automatically generates and installs Zsh completion scripts for installed command-line tools. It is designed to be robust, only creating a completion file if the corresponding tool is actually found on the system. This prevents errors during chezmoi apply on a new machine where tools may not be installed yet.
The entire process is managed declaratively by chezmoi using templates.
The automation relies on three key chezmoi features working together:
-
Conditional File Creation (
create_prefix) The template filenames indot_zsh/completions/are prefixed withcreate_(e.g.,create__gh.tmpl). This special prefix tellschezmoito only create the final file in~/.zsh/completionsif the template's output is not empty. -
Conditional Content (
lookPath) Inside each template, anif lookPath "tool-name"block checks if the command is available in the system's$PATH. If the tool is not found, the template produces no output. This triggers thecreate_rule, andchezmoisilently skips creating the file. -
Dynamic Content (
output) If thelookPathcheck succeeds, the{{ output "tool-name" ... }}function runs the tool's built-in command to generate the completion script. This output becomes the content of the final file.
This combination ensures that chezmoi apply is always safe to run and the completion scripts are always up-to-date with the installed versions of the tools.
Adding a new completion is a two-step process.
First, find the command the new tool uses to generate its Zsh completions. Check the tool's documentation or help flag. Common patterns include:
new-tool completion zshnew-tool --completions zshnew-tool gen-completions --shell zsh
Create a new template file in the dot_zsh/completions/ directory within this chezmoi repo.
-
Filename: Must follow the pattern
create__[tool-name].tmpl -
Content: Must follow this template structure:
{{- /* Check if [tool-name] is installed */ -}} {{- if lookPath "[tool-name]" -}} {{- /* If it is, run the command to generate the script */ -}} {{- output "[tool-name]" "[arg1]" "[arg2...]" -}} {{- end -}}
- Find the command:
gh completion --shell zsh - Create the template file:
- Filename:
dot_zsh/completions/create__gh.tmpl - Content:
{{- if lookPath "gh" -}} {{ output "gh" "completion" "--shell" "zsh" }} {{- end -}}
- Filename:
After adding the new template file, simply run chezmoi apply to generate the new completion script in your home directory.
This system uses chezmoi's built-in age encryption. When the age identity file exists at ~/.config/age/key.txt, chezmoi automatically decrypts all .age files during apply operations. Files are stored in the chezmoi source directory with the encrypted_ prefix and .age suffix. The decryption process is completely handled by chezmoi's templating system - no custom scripts needed.
.oooooooooo.
.:%%%%((<<<<<<<%:.
.:%%%%(((=(((((<<<%%%%:.
/%%l/~ `-.=(((((((<<'~\l%%\
l/:%%: ~~~~ :%%:\l
/l:%:: ::==l\
l/ =:: ::= \l
/l =:: · · ::= l\
l| =:::: · · ::::= |l
// /i:::: · · · · ::::i\ \\
/l//!:·:: ::·:!\\l\
/l///: _ ... ... _ :\\l\\
ll/: oOOOOoo.._ :::::: _..ooOOOOo :\ll
//oohHHHHHHHHhOo_.) (._oOhHHHHHHHHhoo\\
_J##7°~:¯¯¯=:887###OOO###788:=¯¯¯:~°7##L_
i `%#:L:.o<#▓)>%8%%#7¯7##%8%<#▓)>o..J:#i' i
|/)!#:::¯¯=-~~=::%H/ \H%::=~~-=¯¯: :#!(\|
:))%°L. ¨ .:::H/) (\H:: J°%((:
i!%:°Lo._____.o#°:. .:°#o._____.oJ°:%!i
|(|:::¯°°°°°°°¯ ::l l:: ¯°°°°°°°¯:::|)|
!_l:::: :( : : ): ::::l_!
l:::::. .:/:`°7/%/7°':\:. .:::::l
::::::::::: ¯=¯ :::::::::::
l:::::: (::..__ !=! __..::) ::::::l
:i:::: JLoJOOOL6JOOOLoJL ::::i:
!::::: : `·o_¯¯¯¯¯_o·' : :::::!
o:::::::. :::!888!::: .:::::::o
l8::::( ·.::°°~°°::.· )::::8l
J:O°oo:l: :l:oo°O:L
o8##:::°8%%Oo...·::::·....oO%%8°:::##8o
o8#####i:::°%%°87oooOOOOOooo78°%%°:::i#####8o
o8########|. ::::°°%%°°88888°°%%°°:::: .|########8o
##########!\: :::::°%%%%%%%°::::: :/!##########
##########i!\:. ::::::::::: .:/!i##########
Andy######|:i\::. .::/i:|#######adl