This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a chezmoi-managed dotfiles repository that synchronizes shell configuration, environment variables, and system settings across macOS and Linux machines. The repository uses templating to handle platform-specific configurations and secrets management.
- Source directory:
~/.local/share/chezmoi/- This is the git repository where you edit files - Target directory:
~/(home directory) - Where chezmoi applies the processed files - Files in the source directory use special naming conventions that determine how they're processed and where they're installed
Chezmoi uses prefixes to control file processing:
dot_→ Creates a dotfile (e.g.,dot_zshrc→~/.zshrc).tmplsuffix → Processes as a Go template before applying (e.g.,dot_zshrc.tmpl→~/.zshrcwith templates rendered)encrypted_→ Encrypted file requiring age key (e.g.,encrypted_dot_zshrc_private.age→~/.zshrc_private)executable_→ Sets executable permissionsprivate_→ Sets restrictive permissions (chmod 600)
Files with .tmpl extension are processed as Go templates with access to:
.chezmoi.os- Operating system ("darwin" for macOS, "linux" for Linux).secrets.*- Variables from~/.config/chezmoi/chezmoi.tomlunder[data.secrets].machine.*- Machine-specific configuration from the config file
Example from dot_zshrc.tmpl:
{{- if eq .chezmoi.os "darwin" }}
# macOS-specific configuration
{{- else }}
# Linux-specific configuration
{{- end }}
export ANTHROPIC_VERTEX_PROJECT_ID={{ .secrets.anthropic_vertex_project_id }}
This repository uses a multi-layered secrets approach:
-
Private encrypted secrets repository (https://github.com/shanemcd/dotfiles-secrets):
- Added as a git submodule at
external_secrets/ - Contains
chezmoi-secrets.toml.age(encrypted with age) for template variables - Contains encrypted dotfiles like gcloud configuration
- Automatically initialized by chezmoi when cloning with submodules
- Requires both GitHub access AND the age encryption key
- Added as a git submodule at
-
Symlink-based encrypted file management:
- Encrypted files (e.g.,
dot_config/gcloud/encrypted_*.age) are stored in the secrets submodule - Individual file symlinks in the main repo point to files in
external_secrets/ - Key insight: Chezmoi processes individual file symlinks correctly (but not directory symlinks)
- Public dotfiles repo contains only symlinks, no encrypted content
- Example:
dot_config/gcloud/encrypted_credentials.db.age -> ../../external_secrets/dot_config/gcloud/encrypted_credentials.db.age
- Encrypted files (e.g.,
-
Local configuration (
~/.config/chezmoi/chezmoi.toml):- NOT in git repository
- Automatically generated from
.chezmoi.toml.tmplwhich decrypts secrets on-the-fly - Contains actual secret values for template rendering
- Machine-specific and never committed
-
Age encryption key (
~/.config/chezmoi/key.txt):- Required to decrypt any encrypted files
- Automatically prompted during
chezmoi initif not present - Can also be restored from secure backup (1Password, USB) before running init
- Public key:
age1wuc38w6748e7l0za4v5paccs9muasjuuqrdqq8npqyxl0dfseclsfh386e
Option A: Automated with Ansible (recommended)
# 1. Install prerequisites
# macOS: brew install chezmoi age ansible-core
# Linux (Fedora/RHEL): sudo dnf install chezmoi age ansible-core
# 2. Ensure 1Password CLI is authenticated
op whoami
# 3. Run the automated setup
ansible-playbook shanemcd.toolbox.dotfiles
# 4. Reload shell
exec zshThe Ansible role automatically fetches the age key from 1Password and handles the entire setup.
Option B: Manual setup
# 1. Install prerequisites
# macOS: brew install chezmoi age
# Linux (Fedora/RHEL): sudo dnf install chezmoi age
# 2. One command to bootstrap everything
chezmoi init --apply git@github.com:shanemcd/dotfiles.git
# This automatically:
# - Clones dotfiles repo
# - Initializes external_secrets submodule
# - Prompts for the age private key (if not already present)
# - Writes the key to ~/.config/chezmoi/key.txt with secure permissions
# - Generates config with decrypted secrets (via .chezmoi.toml.tmpl)
# - Applies all dotfiles
# 3. Reload shell
exec zsh# Edit a managed file (ALWAYS use this, not direct editing)
chezmoi edit ~/.zshrc
# Preview what changes will be applied
chezmoi diff
# Apply changes from source directory to home directory
chezmoi apply -v
# Add a new file to chezmoi management
chezmoi add ~/.newfile# Navigate to the source directory
chezmoi cd
# Make changes, commit, and return home
git add .
git commit -m "Update config"
git push
exit# Pull latest changes from git and apply
# --init recreates config from template (re-decrypts secrets)
chezmoi update --init# Edit secrets in the private submodule
cd ~/.local/share/chezmoi/external_secrets
age -d -i ~/.config/chezmoi/key.txt -o chezmoi-secrets.toml chezmoi-secrets.toml.age
vim chezmoi-secrets.toml
# Re-encrypt and push
age -r age1wuc38w6748e7l0za4v5paccs9muasjuuqrdqq8npqyxl0dfseclsfh386e \
-o chezmoi-secrets.toml.age chezmoi-secrets.toml
rm chezmoi-secrets.toml
git add chezmoi-secrets.toml.age
git commit -m "Update secrets"
git push
# On other machines: pull and apply
chezmoi update --init # Pulls dotfiles, auto-updates submodule, re-generates configThis repository uses chezmoi hooks to automatically keep the external_secrets submodule up-to-date:
How it works:
- A
read-source-state.prehook runs before every chezmoi command - The hook executes
.hooks/update-submodule.shwhich runsgit submodule update --remote - This fetches the latest commits from the secrets repo without needing to update the parent repo's submodule pointer
Configuration details (in .chezmoi.toml.tmpl):
[hooks.read-source-state.pre]- defines the hook that runs the update script[update]- customizeschezmoi updateto usegit pullwithout--rebase(avoids conflicts with submodule changes)
Result: When you push new secrets to the secrets repo, other machines will automatically get them on the next chezmoi update --init - no need to update the submodule pointer in the parent repo.
# See all files managed by chezmoi
chezmoi managed
# Check how a template will be rendered
chezmoi execute-template < ~/.local/share/chezmoi/dot_zshrc.tmpl
# View current configuration data
chezmoi dataThe repository handles macOS and Linux differently:
- Homebrew path management (
/opt/homebrew/bin) - iTerm2 integration (line 161-163)
- Bun JavaScript runtime setup
- Tinty theme auto-switching based on system dark mode
- fzf installed via Homebrew
- Krew (kubectl plugin manager) in PATH
- Podman aliases and socket configuration for toolbox environments
- fzf from system packages (
/usr/share/fzf/shell/key-bindings.zsh) - Hardcoded username path in some locations (line 141, 148)
dot_zshrc.tmpl- Main shell configuration, extensively templated for cross-platform usedot_zprofile.tmpl- Platform-specific profile (Homebrew setup on macOS only).chezmoi.toml.tmpl- Template that generates local config, auto-decrypts secrets on-the-fly.chezmoiexternal.toml- Copies encrypted secrets from the external_secrets submodule to config directory.gitmodules- Defines the external_secrets submodule (private secrets repo).chezmoiignore- Files managed by chezmoi but not committed to git.gitignore- Prevents committing private dotfiles to the repositoryencrypted_dot_zshrc_private.age- Encrypted private shell configuration (requires age key)
The following secrets are required in ~/.config/chezmoi/chezmoi.toml:
[data.secrets]
anthropic_vertex_project_id = "your-project-id" # Claude Vertex
google_cloud_project = "your-project-id" # Gemini / gcloud
google_cloud_location = "global"
gemini_model = "gemini-3.6-flash"
google_chat_project_id = "your-project-id"
google_chat_topic = "hermes-chat-events"
google_chat_subscription = "hermes-chat-events-sub"
google_chat_allowed_users = "you@example.com"
google_chat_service_account = "hermes-chat-bot@your-project-id.iam.gserviceaccount.com"
slack_allowed_users = "U00000000"
jira_email = "you@example.com"
gitlab_host = "gitlab.example.com"These are referenced in dot_zshrc.tmpl (and dot_gemini/dot_env.tmpl) and
exported as environment variables for Claude Code, Gemini CLI, Google Cloud, and
Clankr Hermes (Chat / Slack allowlist / Jira email / GitLab host).
# Add the file
chezmoi add ~/.gitconfig
# If it needs templating, rename in source directory
cd ~/.local/share/chezmoi
mv dot_gitconfig dot_gitconfig.tmpl
# Edit to add template variables
chezmoi apply -v- Add variable to
.chezmoi.toml.tmplas documentation - Use in templates with
{{ .secrets.variable_name }}or{{ .machine.variable_name }} - Users must add actual values to their local
~/.config/chezmoi/chezmoi.toml
Use conditional blocks in .tmpl files:
{{- if eq .chezmoi.os "darwin" }}
# macOS code
{{- else if eq .chezmoi.os "linux" }}
# Linux code
{{- end }}
To add new encrypted files (like additional config directories):
# 1. Add the encrypted file to the secrets repo
cd ~/.local/share/dotfiles-secrets
chezmoi add --encrypt ~/path/to/file
git add dot_path/to/encrypted_file.age
git commit -m "Add encrypted file"
git push
# 2. Update the submodule in the main repo
cd ~/.local/share/chezmoi
git submodule update --remote external_secrets
# 3. Create symlink in the main repo
cd ~/.local/share/chezmoi
ln -s ../../external_secrets/dot_path/to/encrypted_file.age dot_path/to/encrypted_file.age
# 4. Commit the symlink
git add dot_path/to/encrypted_file.age
git commit -m "Add symlink to encrypted file"
git pushImportant: Always symlink individual FILES, not directories. Chezmoi follows file symlinks but not directory symlinks.
This repository has an associated Ansible collection at https://github.com/shanemcd/toolbox that provides automated setup and provisioning.
The shanemcd.toolbox.dotfiles Ansible role automates the entire dotfiles setup process:
What it does:
- Creates
~/.config/chezmoidirectory with secure permissions (0700) - Fetches the age encryption key from 1Password (if not already present)
- Initializes chezmoi with
chezmoi init --apply --force git@github.com:shanemcd/dotfiles.git - Automatically applies the configuration
Usage:
ansible-playbook shanemcd.toolbox.dotfilesRequirements:
chezmoiinstalled on the target systemcommunity.generalAnsible collection for 1Password integration- 1Password CLI (
op) installed and authenticated - 1Password item named "Chezmoi Key" containing the age private key
How the key is fetched:
The role uses the community.general.onepassword_doc lookup plugin to securely retrieve the age key from 1Password. This eliminates the manual step of restoring the key from backup during new machine setup.
- Never edit files in
~/directly - they'll be overwritten bychezmoi apply - Always use
chezmoi edit <file>or edit in~/.local/share/chezmoi/ - Test with
chezmoi diffbefore applying - Consider both platforms - changes to
.tmplfiles affect macOS and Linux - Secrets workflow:
- Template variables (like project IDs) live in
external_secrets/chezmoi-secrets.toml.age - Encrypted files (like gcloud config) live in the
external_secrets/submodule with symlinks in main repo - Never commit actual secret values or encrypted content to the public dotfiles repo
- Document new secret variables in
.chezmoi.toml.tmplas placeholders (in the fallback section) - After updating secrets in the submodule, commit both the submodule change AND the parent repo's submodule reference
- Template variables (like project IDs) live in
- Template-based decryption -
.chezmoi.toml.tmplautomatically decrypts secrets when chezmoi processes it (no manual steps needed) - Submodule management - Chezmoi automatically initializes the
external_secretssubmodule on firstchezmoi init