How the dotfiles command works
When dotfiles is run for the first time, it does a few things:
- Git is installed if necessary (via APT on Ubuntu, already present on macOS).
- This repo is cloned into your user directory, under
~/.dotfiles. - On a fresh Mac, Homebrew and its bash are installed and the script restarts under that bash: everything after this needs bash 5.0 or newer, and macOS only ships 3.2. (read more)
- Files in
/copyare copied into~/, except on RHEL, which runs only the link and init steps. (read more) - Files in
/linkare symlinked into~/. (read more) - You are prompted to choose scripts in
/init. The installer auto-selects by OS (_macos_scripts only on macOS);_personal_scripts are default unchecked, and your choice is remembered incaches/init/selected. - Your chosen init scripts are sourced, in alphanumeric order. (read more)
On subsequent runs step 1 is skipped, step 2 updates the existing repo, step 3 is skipped once Homebrew's bash is the one running, and step 6 remembers your last selection.
Other subdirectories
- The
/backupsdirectory is created when needed: anything in~/that/copyor/linkwould overwrite goes there. - The
/bindirectory contains executable shell scripts (including dotfiles) and symlinks to them, and is added to$PATH. - The
/cachesdirectory contains cached files used by some scripts or functions. - The
/claudedirectory holds shared Claude Code config —CLAUDE.md,skills/,rules/,docs/,hooks/,shims/and the statusline script — symlinked into~/.claude/by init/52_macos_claude.sh, leaving machine-local state (sessions, history) alone.settings.jsonstays machine-local too; the script only points itsstatusLineat the linked script and, throughbin/claude-settings-merge, registers the two hook entries for the work-context guard inhooks/work-context.sh, and aPostToolUseone forhooks/watch-main-ci.shthat after a push to a default branch or a PR merge tells Claude to watch that branch's CI to green. Itshardeningcall adds deny rules:Readof credential files (~/.sshkeys,~/.aws/credentials,~/.kube/config, git, GnuPG andghcredentials, this repo's revealed~/.sshfiles, and the checkout'slink/.ssh/keysby its$DOTFILESpath),Editof the shell rc files, creating the.claude-files-issuesmarker, so issue filing stays the user's opt-in, andEditof~/.claude-customers, which only the user changes. TheReadrules also cover Bash'scat,head,tail,sedand<, but notgrep -rover a parent directory or an interpreter (Python, Node) opening the file itself. The marker rules cover the Write and Edit tools, shell redirects andtouch;cpormvfrom a script and interpreters get past them, and only the guard hook backs that up. It also loads a repo'sAGENTS.mdbeside anyCLAUDE.md(why:claude/docs/claude-local-md.md). Static rules can't follow each repo's git-secret list, so the guard hook keeps revealed git-secret files from Claude in any repo: aRead,Grep, file write or shell word naming a path.gitsecret/paths/mapping.cfglists, andgit secret reveal,catandchanges. AGrepover a whole directory and interpreters opening a file get past it. - The
/confdirectory stores app configuration (Sublime Text, IntelliJ), linked into place by dedicated init scripts. - The
/docsdirectory holds the guide pages split out of the README — this one and the installation guide. - The
/hooksdirectory contains git hooks that are symlinked into.git/hooks/by init/12_git_hooks.sh. - The
/renovatedirectory holds the shared Renovate presets my other repos extend (renovate/README.md). - The
/sourcedirectory contains files sourced whenever a new shell opens, in alphanumeric order. - The
/testdirectory contains unit tests for especially complicated bash functions, and the checks on the/renovatepresets. - The
/vendordirectory contains third-party libraries (zsh plugins as git submodules). - The
/websitedirectory is a Docusaurus site rendering the README, these guide pages and the tips for GitHub Pages, deployed by .github/workflows/deploy-docs.yml.cd website && npm install && npm startto develop locally.
The "copy" step
Any file in the /copy subdirectory will be copied into ~/.
Sensitive information is managed via git-secrets, but files that may need local edits
(like copy/.gitconfig) should be copied rather than linked as a second line of defense.
Because the file you'll be editing is no longer in ~/.dotfiles, it's less likely to be accidentally committed
into your public dotfiles repo.
The "link" step
Any file in the /link subdirectory gets symlinked into ~/ with ln -sf.
Edit one or the other, and you change the file in both places.
Don't link files containing sensitive data, or you might accidentally commit that data!
If you're linking a directory that might contain sensitive data (like ~/.ssh) add the sensitive files to your
.gitignore file!
The "init" step
Scripts in the /init subdirectory will be executed. A whole bunch of things will be installed, but only if they aren't already.
macOS
- XCode Command Line Tools via init/11_macos_xcode.sh
- Homebrew via init/20_homebrew.sh
- Homebrew recipes via init/31_homebrew_recipes.sh
- Homebrew casks via init/32_macos_homebrew_casks.sh
- Node.js versions via init/33_node_versions.sh: the two latest LTS lines plus the latest release, the same rule as the JDKs
Linux / WSL
- Flatpak (Linux desktop) via init/21_linux_flatpak.sh and init/32_linux_flatpak_apps.sh
- Winget (WSL) via init/22_wsl_winget.sh and init/32_wsl_winget_apps.sh
- Homebrew via init/20_homebrew.sh
- Homebrew recipes via init/31_homebrew_recipes.sh
- Node.js versions via init/33_node_versions.sh: the two latest LTS lines plus the latest release, the same rule as the JDKs
All platforms
- SSH config and private keys via init/10_ssh_private_keys.sh
- Git hooks (symlinked from
/hooks) via init/12_git_hooks.sh - Sublime Text settings via init/51_sublime_text.sh
- Strict line breaks in every registered Obsidian vault (macOS and Linux) via init/54_obsidian.sh