Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .chezmoi.toml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{- /* Valid classes: mac, linux, wsl, ephemeral — see CLAUDE.md.
The prompt text stays exactly "machineClass" so scripts can answer it
with --promptString machineClass=<class>. */ -}}
{{- $machineClass := promptStringOnce . "machineClass" "machineClass" -}}

{{ if eq $machineClass "mac" -}}
# The development clone doubles as the chezmoi source directory on macs.
sourceDir = "~/code/dotfiles"

{{ end -}}
[data]
machineClass = {{ $machineClass | quote }}
27 changes: 27 additions & 0 deletions .chezmoiignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Entries here are TARGET paths (relative to $HOME) that chezmoi must not
# manage. This file is parsed as a template, so ignores can vary by machine.

# Repo-level files — never deployed. (Dot-prefixed source files like .macos,
# .github, and .gitignore are invisible to chezmoi already and need no entry.)
README.md
LICENSE-MIT.txt
Brewfile
bootstrap.sh
tests
init
theme

{{ if ne .chezmoi.os "darwin" -}}
# macOS GUI configuration has no business on Linux/WSL
com.googlecode.iterm2.plist
.mackup.cfg
{{ end -}}

{{ if eq .machineClass "ephemeral" -}}
# Disposable boxes get shell config only: no identity, no secrets, and no
# tooling that assumes a long-lived machine.
.extra
.gitconfig.local
.claude
CLAUDE.md
{{ end -}}
11 changes: 0 additions & 11 deletions .extra.tmpl

This file was deleted.

13 changes: 11 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ jobs:
- name: Install Bats on macOS
if: matrix.os == 'macos-latest'
run: brew install bats-core
- name: Install chezmoi
run: |
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Run tests
run: bats tests
- name: Apply .macos on the throwaway runner and verify
Expand All @@ -33,12 +37,15 @@ jobs:
container: rockylinux:9
steps:
- name: Install dependencies
# No explicit curl: the image ships curl-minimal, which conflicts
# with the full package but already provides the curl binary.
run: dnf install -y git zsh rsync
- uses: actions/checkout@v3
- name: Install Bats
- name: Install Bats and chezmoi
run: |
git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats-core
/tmp/bats-core/install.sh /usr/local
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b /usr/local/bin
- name: Run tests
run: bats tests

Expand All @@ -58,6 +65,8 @@ jobs:
- uses: Vampire/setup-wsl@v5
with:
distribution: Ubuntu-24.04
additional-packages: bats zsh rsync
additional-packages: bats zsh rsync curl
- name: Install chezmoi
run: sh -c "$(curl -fsLS get.chezmoi.io)" -- -b /usr/local/bin
- name: Run tests
run: bats tests
1 change: 1 addition & 0 deletions Brewfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ brew 'bat' # Better cat with syntax highlighting and git integration
brew 'bats-core' # Bash Automated Testing System (used by dotfiles tests)
brew 'bfg' # Removes large or troublesome blobs like git-filter-branch does, but faster. And written in Scala
brew 'binutils' # The GNU Binutils are a collection of binary tools
brew 'chezmoi' # manage your dotfiles across multiple diverse machines, securely
brew 'cifer' # Work on automating classical cipher cracking in C.
brew 'coreutils' # The GNU Core Utilities are the basic file, shell and text manipulation utilities of the GNU operating system.
brew 'curl'
Expand Down
64 changes: 47 additions & 17 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,49 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## What this repo is

Personal dotfiles for macOS/zsh, including shell configuration, macOS system defaults, and a Homebrew bundle. The primary shell is **zsh** (`.bash_profile` auto-upgrades any bash session to zsh).
Personal dotfiles managed by **chezmoi**, targeting macOS, Linux, WSL, and
disposable remote boxes. Shell configuration (zsh-first; `.bash_profile`
auto-upgrades bash sessions to zsh), macOS system defaults, and a Homebrew
bundle. A **machine class** chosen once at `chezmoi init` (`mac` / `linux` /
`wsl` / `ephemeral`, answerable non-interactively with
`--promptString machineClass=<class>`) drives all per-machine templating:
`ephemeral` deploys shell config only (no identity, no secrets), `wsl` adds
1Password's ssh.exe forwarding pattern, `mac` pins chezmoi's `sourceDir` to
`~/code/dotfiles` so the clone is the source of truth.

Source naming follows chezmoi conventions: `dot_zshrc` deploys to
`~/.zshrc`, `private_dot_extra.tmpl` renders to `~/.extra` (mode 0600),
`bin/executable_has-glyphs` to `~/bin/has-glyphs`, `create_` files are
written only when missing. Repo-level files (`.gitignore`, `.macos`,
`.github`, dot-prefixed in general) are invisible to chezmoi; non-dot
repo files are excluded via `.chezmoiignore`.

## Installation

```bash
# Clone and apply dotfiles to $HOME
source bootstrap.sh
# Fresh machine (no root needed)
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply hadees

# Apply macOS system preferences (requires sudo)
# Optionally set a machine name first:
# Disposable box — applies, then removes chezmoi and all traces of itself
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --one-shot hadees

# Development clone (this Mac)
chezmoi init --source ~/code/dotfiles --apply

# Day-to-day: edit in ~/code/dotfiles, then
chezmoi diff && chezmoi apply # `dotfiles` is aliased to `chezmoi apply`

# Apply macOS system preferences (requires sudo; run manually, chezmoi
# never touches it). Optionally set a machine name first:
COMPUTER_NAME="My-Mac" ./.macos

# Install Homebrew packages
# Install Homebrew packages (macOS)
brew bundle
```

`bootstrap.sh` is a deprecated wrapper that just calls
`chezmoi init --source . --apply`; it will be removed.

## Running tests

Tests use [bats-core](https://github.com/bats-core/bats-core). Install it first (`brew install bats-core`), then:
Expand Down Expand Up @@ -54,7 +81,9 @@ VMs; the test skips everywhere else unless `MACOS_APPLY_OK=1` is set.

### Key files

- **`bootstrap.sh`** — uses `rsync` to copy repo files to `$HOME`; excludes `bootstrap.sh`, `README.md`, `Brewfile`, `LICENSE-MIT.txt`
- **`.chezmoi.toml.tmpl`** — config template; prompts once for the machine class and pins `sourceDir` to the clone on macs
- **`.chezmoiignore`** — target paths chezmoi must not manage (repo-level files everywhere; macOS GUI config off-mac; identity/secrets on `ephemeral`)
- **`bootstrap.sh`** — deprecated wrapper around `chezmoi init --source . --apply`
- **`.macos`** — macOS `defaults write` settings; reads `$COMPUTER_NAME` env var for machine-specific naming
- **`Brewfile`** — Homebrew formulae, casks, and Mac App Store apps
- **`bin/`** — personal scripts added to `$PATH`
Expand Down Expand Up @@ -89,17 +118,18 @@ Add `~/.extra` (not committed) for per-machine overrides. Add `~/.path` for per-

### Machine-local secrets (~/.extra)

`~/.extra` is rendered from the tracked `.extra.tmpl` by 1Password's
`op inject`. bootstrap.sh runs it automatically when `~/.extra` is missing;
re-render manually with `op inject -i .extra.tmpl -o ~/.extra -f`. The
template stores only secret *references*, never values:
`~/.extra` is rendered by chezmoi from `private_dot_extra.tmpl` (mode 0600)
on every `chezmoi apply`, using chezmoi's built-in 1Password template
functions — secret *values* come from the 1Password CLI at apply time, only
references live in the repo:

export GITHUB_TOKEN="{{ op://Private/GitHub PAT/token }}"
export GITHUB_TOKEN={{ onepasswordRead "op://Private/GitHub PAT/token" | quote }}

Get a reference path from the 1Password app: right-click a field →
"Copy Secret Reference".
"Copy Secret Reference". The `ephemeral` machine class never deploys this
file (see `.chezmoiignore`).

CAUTION: `op inject` parses the whole template, comments included, and
errors on any curly-brace pair or bare `op://` text that isn't a real
brace-wrapped reference. That's why these instructions live here and not in
the template itself. `tests/extra-tmpl.bats` enforces the invariant.
CAUTION: chezmoi parses the template as a Go template, comments included
a stray `{{` anywhere breaks rendering. That's why these instructions live
here and not in the template itself. `tests/extra-tmpl.bats` enforces the
invariant.
58 changes: 28 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
# Hadees’ dotfilesßß
# Hadees’ dotfiles

Managed with [chezmoi](https://www.chezmoi.io/). One repo targets macOS,
Linux, WSL, and disposable remote boxes; a **machine class** chosen at init
time (`mac` / `linux` / `wsl` / `ephemeral`) controls what gets deployed.

## Installation

**Warning:** If you want to give these dotfiles a try, you should first fork this repository, review the code, and remove things you don’t want or need. Don’t blindly use my settings unless you know what that entails. Use at your own risk!

### Using Git and the bootstrap script

You can clone the repository wherever you want. (I like to keep it in `~/code/dotfiles`, with `~/.dotfiles` as a symlink.) The bootstrapper script will pull in the latest version and copy the files to your home folder.
### Fresh machine (one-liner, no root needed)

```bash
git clone https://github.com/hadees/dotfiles.git && cd dotfiles && source bootstrap.sh
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply hadees
```

To update, `cd` into your local `dotfiles` repository and then:

```bash
source bootstrap.sh
```
Installs chezmoi to `~/.local/bin`, clones this repo, prompts once for the
machine class, and applies.

Alternatively, to update while avoiding the confirmation prompt:
### Disposable box (leave no trace)

```bash
set -- -f; source bootstrap.sh
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --one-shot hadees
```

### Git-free install
Applies the dotfiles, then removes chezmoi, its source clone, and its config
from the machine. Answer `ephemeral` to the machine-class prompt: that class
deploys shell config only — no git identity and no secrets ever touch disk.
For commit signing on a box you keep around, forward your local 1Password
SSH agent (per-host `ForwardAgent yes`) instead of putting keys there.

To install these dotfiles without Git:
### Development clone (macOS)

```bash
cd; curl -#L https://github.com/hadees/dotfiles/tarball/master | tar -xzv --strip-components 1 --exclude={README.md,bootstrap.sh,.osx,LICENSE-MIT.txt}
brew install chezmoi
git clone git@github.com:hadees/dotfiles.git ~/code/dotfiles
chezmoi init --source ~/code/dotfiles --apply
```

To update later on, just run that command again.
The `mac` machine class pins chezmoi’s `sourceDir` to the clone, so the
day-to-day loop is: edit files in `~/code/dotfiles`, review with
`chezmoi diff`, deploy with `chezmoi apply` (aliased to `dotfiles`).

### Specify the `$PATH`

Expand All @@ -46,20 +53,11 @@ export PATH="/usr/local/bin:$PATH"

### Add custom commands without creating a new fork

If `~/.extra` exists, it will be sourced along with the other files. You can use this to add a few custom commands without the need to fork this entire repository, or to add commands you don’t want to commit to a public repository.

My `~/.extra` looks something like this:

```bash
# Git credentials
# Not in the repository, to prevent people from accidentally committing under my name
GIT_AUTHOR_NAME="John Doe"
GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
git config --global user.name "$GIT_AUTHOR_NAME"
GIT_AUTHOR_EMAIL="mathias@mailinator.com"
GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
git config --global user.email "$GIT_AUTHOR_EMAIL"
```
If `~/.extra` exists, it will be sourced along with the other files. On
managed machines it is rendered by chezmoi from `private_dot_extra.tmpl`,
which pulls secret values out of 1Password at apply time — see CLAUDE.md
“Machine-local secrets” for the syntax. On the `ephemeral` machine class it
is never deployed at all.

You could also use `~/.extra` to override settings, functions and aliases from my dotfiles repository. It’s probably better to [fork this repository](https://github.com/hadees/dotfiles/fork) instead, though.

Expand Down
File renamed without changes.
File renamed without changes.
1 change: 0 additions & 1 deletion bin/subl

This file was deleted.

1 change: 1 addition & 0 deletions bin/symlink_subl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl
68 changes: 14 additions & 54 deletions bootstrap.sh
Original file line number Diff line number Diff line change
@@ -1,59 +1,19 @@
#!/usr/bin/env bash

cd "$(dirname "${BASH_SOURCE}")";
# DEPRECATED: these dotfiles are now managed by chezmoi. This wrapper
# survives one release for muscle memory, then goes away.
#
# Fresh machine: sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply hadees
# This clone: chezmoi init --source . && chezmoi apply

git pull --ff-only origin master || echo "Could not pull latest changes; continuing with local copy.";
cd "$(dirname "${BASH_SOURCE}")" || { return 1 2>/dev/null || exit 1; };

function doIt() {
rsync --exclude ".git/" \
--exclude ".claude/settings.local.json" \
--exclude ".DS_Store" \
--exclude ".macos" \
--exclude "tests/" \
--exclude "bootstrap.sh" \
--exclude "README.md" \
--exclude "LICENSE-MIT.txt" \
--exclude "Brewfile" \
-avh --no-perms . ~;
if [ ! -f ~/.claude/CLAUDE.local.md ]; then
mkdir -p ~/.claude;
printf '# Machine-local memory (generated by bootstrap.sh, edit freely)\n\nTerminal: %s (TERM=%s), OS: %s%s%s\n' \
"${TERM_PROGRAM:-unknown}" "${TERM:-unknown}" "$(uname -s)" \
"${WSL_DISTRO_NAME:+, WSL: $WSL_DISTRO_NAME}" \
"${TMUX:+, tmux}" > ~/.claude/CLAUDE.local.md;
fi;
# Render machine-local secrets from the tracked template (op:// references
# only — values come from 1Password). Never overwrites an existing ~/.extra.
if command -v op > /dev/null 2>&1 && [ ! -f ~/.extra ]; then
op inject -i .extra.tmpl -o ~/.extra \
|| echo "op inject failed (not signed in?) — render ~/.extra manually later.";
fi;
# ~/.zshrc is zsh-only syntax; only source it when actually running in zsh
if [ -n "$ZSH_VERSION" ]; then
source ~/.zshrc;
else
echo "Restart your shell (or run: exec zsh) to pick up the new config.";
fi;
echo "";
echo "Next steps on a new machine:";
echo " brew bundle — install Brewfile packages";
echo " 1Password → set up SSH agent + op-ssh-sign, then add signing config";
echo " to ~/.gitconfig.local (commit.gpgsign=true,";
echo " gpg.format=ssh, signingkey=*.pub)";
echo " bash init/mackup.sh — restore app settings from ~/.config/Mackup/";
echo " op inject -i .extra.tmpl -o ~/.extra";
echo " — render secrets (auto-runs above when op is";
echo " installed and ~/.extra doesn't exist yet)";
}

if [ "$1" = "--force" -o "$1" = "-f" ]; then
doIt;
else
printf "This may overwrite existing files in your home directory. Are you sure? (y/n) ";
read REPLY;
echo "";
if [[ $REPLY =~ ^[Yy]$ ]]; then
doIt;
fi;
if ! command -v chezmoi > /dev/null 2>&1; then
echo "chezmoi is not installed. Install it first:";
echo " brew install chezmoi # macOS";
echo " sh -c \"\$(curl -fsLS get.chezmoi.io)\" -- -b ~/.local/bin # anywhere";
return 1 2>/dev/null || exit 1;
fi;
unset doIt;

echo "bootstrap.sh is deprecated — applying via chezmoi instead.";
chezmoi init --source "$PWD" --apply;
4 changes: 2 additions & 2 deletions .aliases → dot_aliases
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ alias afk="/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resource
# Reload the shell (i.e. invoke as a login shell)
alias reload="exec ${SHELL} -l"

# Sync dotfiles repo to $HOME
alias dotfiles='source ~/code/dotfiles/bootstrap.sh -f'
# Sync dotfiles repo to $HOME (chezmoi's sourceDir points at the clone)
alias dotfiles='chezmoi apply'

# Print each PATH entry on a separate line
alias path='echo -e ${PATH//:/\\n}'
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions dot_claude/create_CLAUDE.local.md.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Machine-local memory (generated by chezmoi, edit freely)

Terminal: {{ env "TERM_PROGRAM" | default "unknown" }} (TERM={{ env "TERM" | default "unknown" }}), OS: {{ if eq .chezmoi.os "darwin" }}Darwin (macOS){{ else }}{{ .chezmoi.os }}{{ end }}{{ if env "WSL_DISTRO_NAME" }}, WSL: {{ env "WSL_DISTRO_NAME" }}{{ else }}, no WSL{{ end }}{{ if env "TMUX" }}, tmux{{ else }}, no tmux{{ end }}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions dot_gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Automatically normalize line endings for all text-based files
#* text=auto
# Disabled because of https://github.com/mathiasbynens/dotfiles/issues/149 :(
File renamed without changes.
Loading
Loading