Skip to content
Open
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
18 changes: 18 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2

[*.py]
indent_size = 4

[Makefile]
indent_style = tab

[*.md]
trim_trailing_whitespace = false
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
16 changes: 13 additions & 3 deletions .github/workflows/ansible.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Symlink checkout to ~/.dotfiles
run: ln -s "$GITHUB_WORKSPACE" "$HOME/.dotfiles"

- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5

- name: Set up Python 3.11
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.11"

Expand All @@ -29,6 +29,16 @@ jobs:
sudo tar -C /opt -xzf nvim-linux-x86_64.tar.gz
echo "/opt/nvim-linux-x86_64/bin" >> "$GITHUB_PATH"

- name: Install mise
uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2.4.4
with:
install: false

- name: Install Galaxy requirements
run: >
uvx --from ansible-core ansible-galaxy
install -r "$HOME/.dotfiles/ansible/requirements.yml"

- name: Check playbook syntax
run: >
uvx --from ansible-core ansible-playbook
Expand Down
17 changes: 9 additions & 8 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
fetch-depth: 1

- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5

- name: Install mise and pinned tool versions
uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2.4.4

- name: Install lint tools
run: |
sudo apt-get update
sudo apt-get install -y shellcheck fish-common
curl -sSfL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash | bash -s -- 1.7.12
sudo mv actionlint /usr/local/bin/
uv tool install ansible-lint

- name: Check formatting
run: make fmt_check
Expand All @@ -34,19 +34,20 @@ jobs:
runs-on: macos-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
fetch-depth: 1

- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5

- name: Install Ansible dependencies
run: brew install ansible neovim tmux
run: brew install ansible neovim tmux mise

- name: Run dotfiles playbook
env:
CI: true
run: |
ln -sfn "$GITHUB_WORKSPACE" "$HOME/.dotfiles"
ansible-galaxy install -r ansible/requirements.yml
ansible-playbook -i ansible/inventory.ini ansible/dotfiles.yml
24 changes: 9 additions & 15 deletions Brewfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,14 @@ brew "jq"
brew "just"
brew "kubernetes-cli"
brew "lazygit"
brew "lefthook"
brew "mise"
brew "neovim"
brew "ollama"
brew "openjdk@17"
brew "openssl@1.1"
brew "pandoc"
brew "parallel"
brew "pigz"
brew "pipx"
brew "pixz"
brew "pngcrush"
brew "prettierd"
Expand All @@ -60,23 +59,18 @@ brew "zoxide"
cask "amethyst"
cask "db-browser-for-sqlite"
cask "docker-desktop", args: { appdir: "/Applications" }
cask "dockutil"
cask "font-fira-code", args: { appdir: "/Applications" }
cask "font-fira-code-nerd-font", args: { appdir: "/Applications" }
cask "font-fira-mono", args: { appdir: "/Applications" }
cask "font-fira-mono-for-powerline", args: { appdir: "/Applications" }
cask "font-fira-mono-nerd-font", args: { appdir: "/Applications" }
cask "font-fira-sans", args: { appdir: "/Applications" }
cask "font-fira-sans-condensed", args: { appdir: "/Applications" }
cask "font-fira-sans-extra-condensed", args: { appdir: "/Applications" }
cask "font-inconsolata-dz-for-powerline", args: { appdir: "/Applications" }
cask "font-raleway", args: { appdir: "/Applications" }
cask "font-fira-code"
cask "font-fira-code-nerd-font"
cask "font-fira-mono"
cask "font-fira-mono-nerd-font"
cask "font-fira-sans"
cask "font-fira-sans-condensed"
cask "font-fira-sans-extra-condensed"
cask "font-raleway"
cask "ghostty"
cask "hammerspoon"
cask "iterm2", args: { appdir: "/Applications" }
cask "mactex"
cask "quarto"
cask "rectangle", args: { appdir: "/Applications" }
cask "spotify"
npm "@anthropic-ai/claude-code"
npm "prettier"
32 changes: 16 additions & 16 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ LUA_FILES := $(shell git ls-files '*.lua' | while IFS= read -r file; do [ -f "$$
SHELL_FILES := $(shell git ls-files | while IFS= read -r file; do [ -f "$$file" ] || continue; case "$$file" in (*.sh|*.bash) printf "%s " "$$file"; continue;; esac; head -1 "$$file" 2>/dev/null | grep -qE '^\#!.*/(env +)?(sh|bash|dash|ksh)$$' && printf "%s " "$$file"; done)
FISH_FILES := $(shell git ls-files '*.fish' | while IFS= read -r file; do [ -f "$$file" ] && printf "%s " "$$file"; done)

PRETTIER_VERSION := 3.5.3
STYLUA_VERSION := 2.5.2
RUFF_VERSION := 0.15.17
.PHONY: all apply galaxy fmt fmt_check nvim-health nvim-check nvim-update packages packages-bio

.PHONY: all apply fmt fmt_check nvim-health nvim-check nvim-update packages packages-bio
all: fmt galaxy apply nvim-check

all: fmt apply nvim-check
galaxy:
uv run ansible-galaxy install -r ansible/requirements.yml

apply:
apply: galaxy
uv run ansible-playbook -i ~/.dotfiles/ansible/inventory.ini ~/.dotfiles/ansible/dotfiles.yml

packages:
Expand All @@ -28,22 +27,23 @@ packages-bio:
brew bundle install --file=Brewfile.bio --no-upgrade

fmt:
@if [ -n "$(YAML_FILES)" ]; then npx --loglevel error --yes prettier@$(PRETTIER_VERSION) --write $(YAML_FILES); fi
@if [ -n "$(YAML_FILES)" ]; then mise exec -- prettier --write $(YAML_FILES); fi
@if [ -n "$(MARKDOWN_FILES)" ]; then uvx --with mdformat-gfm --with mdformat-frontmatter mdformat --wrap 80 --number $(MARKDOWN_FILES); fi
@if [ -n "$(PYTHON_FILES)" ]; then uvx ruff@$(RUFF_VERSION) format --line-length=100 $(PYTHON_FILES); fi
@if [ -n "$(PYTHON_FILES)" ]; then uvx ruff@$(RUFF_VERSION) check --fix --line-length=100 $(PYTHON_FILES); fi
@if [ -n "$(LUA_FILES)" ]; then npx --loglevel error --yes @johnnymorganz/stylua-bin@$(STYLUA_VERSION) -- $(LUA_FILES); fi
@if [ -n "$(PYTHON_FILES)" ]; then mise exec -- ruff format --line-length=100 $(PYTHON_FILES); fi
@if [ -n "$(PYTHON_FILES)" ]; then mise exec -- ruff check --fix --line-length=100 $(PYTHON_FILES); fi
@if [ -n "$(LUA_FILES)" ]; then mise exec -- stylua -- $(LUA_FILES); fi

fmt_check:
@if [ -n "$(YAML_FILES)" ]; then npx --loglevel error --yes prettier@$(PRETTIER_VERSION) --check $(YAML_FILES); fi
@if [ -n "$(YAML_FILES)" ]; then mise exec -- prettier --check $(YAML_FILES); fi
@if [ -n "$(MARKDOWN_FILES)" ]; then uvx --with mdformat-gfm --with mdformat-frontmatter mdformat --check --wrap 80 --number $(MARKDOWN_FILES); fi
@if [ -n "$(PYTHON_FILES)" ]; then uvx ruff@$(RUFF_VERSION) format --check --line-length=100 $(PYTHON_FILES); fi
@if [ -n "$(PYTHON_FILES)" ]; then uvx ruff@$(RUFF_VERSION) check --line-length=100 $(PYTHON_FILES); fi
@if [ -n "$(LUA_FILES)" ]; then npx --loglevel error --yes @johnnymorganz/stylua-bin@$(STYLUA_VERSION) --check -- $(LUA_FILES); fi
@if [ -n "$(PYTHON_FILES)" ]; then mise exec -- ruff format --check --line-length=100 $(PYTHON_FILES); fi
@if [ -n "$(PYTHON_FILES)" ]; then mise exec -- ruff check --line-length=100 $(PYTHON_FILES); fi
@if [ -n "$(LUA_FILES)" ]; then mise exec -- stylua --check -- $(LUA_FILES); fi
@if [ -n "$(SHELL_FILES)" ]; then shellcheck -e SC1091 $(SHELL_FILES); fi
@for f in $(FISH_FILES); do fish --no-execute $$f; fish_indent --check $$f; done
@ansible-lint ansible/dotfiles.yml
@actionlint .github/workflows/*.yml
@mise exec -- ansible-lint ansible/dotfiles.yml
@mise exec -- actionlint .github/workflows/*.yml
@./scripts/check_pep723.py

nvim-health:
nvim --headless "+checkhealth" +qa
Expand Down
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,41 @@ For machine-specific settings, create:

- `~/.local/environment.bash` for Bash settings
- `~/.local/environment.fish` for Fish settings

### Work git identity

`GIT_AUTHOR_*`/`GIT_COMMITTER_*` env vars are not set globally because they
override every gitconfig level, making per-repo identity impossible. Instead,
`git/gitconfig` includes `~/.config/git/config-local` if present. On a machine
that needs a work identity, create it by hand (never committed — it names your
employer):

```gitconfig
[includeIf "hasconfig:remote.*.url:git@github.com:<org>/**"]
path = ~/.config/git/config-work
```

And `~/.config/git/config-work`:

```gitconfig
[user]
email = you@work-example.com
name = Your Name
```

Requires git 2.36+ for `hasconfig:`. Verify with:

```bash
cd <a work checkout> && git var GIT_AUTHOR_IDENT # expect work address
cd ~/.dotfiles && git var GIT_AUTHOR_IDENT # expect personal address
```

### Machine-local Claude config

Tracked `claude/settings.json` holds only shareable defaults (hooks,
permissions, `effortLevel`, `tui`, `theme`). Machine-local keys — `model`,
`enabledPlugins`, `extraKnownMarketplaces` — live in
`~/.claude/settings.local.json`, which is already gitignored. Claude Code writes
those when you change model or theme via `/config`; keeping them out of the
tracked file stops this public repo from churning with local state or publishing
work-only marketplace entries.
14 changes: 11 additions & 3 deletions ansible/dotfiles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
python_version: "3.11" # Adjust this to your preferred Python version
dotfiles_dir: "{{ ansible_env.HOME }}/.dotfiles"
config_dir: "{{ ansible_user_dir }}/.config"
# The one machine this repo treats as "home" — gates tasks that must
# never run on a work computer (e.g. paperless auto-start).
personal_hostname: Michaels-MacBook-Air

tasks:
- name: Create necessary directories
Expand All @@ -38,6 +35,8 @@
- { path: "{{ config_dir }}/atuin" }
- { path: "{{ config_dir }}/lazygit" }
- { path: "{{ config_dir }}/ripgrep" }
- { path: "{{ config_dir }}/git" }
- { path: "{{ config_dir }}/tmux" }
- { path: "{{ config_dir }}/uv" }
- { path: "{{ config_dir }}/homebrew" }
- { path: "{{ ansible_user_dir }}/cache" }
Expand Down Expand Up @@ -114,3 +113,12 @@
tags:
- links
- setup

- name: Install lefthook git hooks
ansible.builtin.command:
cmd: lefthook install
chdir: "{{ dotfiles_dir }}"
changed_when: false
tags:
- lefthook
- setup
12 changes: 11 additions & 1 deletion ansible/inventory.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
[personal]
Michaels-MacBook-Air.local ansible_connection=local
michael.barton ansible_connection=local

# Add a second group here when these dotfiles are checked out on a work
# machine, e.g.:
# [work]
# work-hostname ansible_connection=local
#
# Host-gated tasks (see ansible/tasks/paperless.yml) check `groups['personal']`
# against ansible_fqdn, so membership here is what actually gates them —
# unlike the single hardcoded hostname string this replaced, which had
# silently stopped matching after this machine was renamed.

10 changes: 10 additions & 0 deletions ansible/tasks/agent_hooks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@
- hooks
- setup

- name: Link global Claude CLAUDE.md
ansible.builtin.file:
src: "{{ dotfiles_dir }}/claude/CLAUDE.md"
dest: "{{ ansible_user_dir }}/.claude/CLAUDE.md"
state: link
force: true
tags:
- hooks
- setup

- name: Link Cursor rules
ansible.builtin.file:
src: "{{ dotfiles_dir }}/cursor/rules"
Expand Down
28 changes: 21 additions & 7 deletions ansible/tasks/link_files.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
---
- name: Stat legacy dotfile locations superseded by XDG paths
ansible.builtin.stat:
path: "{{ ansible_user_dir }}/{{ item }}"
loop:
- .gitconfig
- .gitignore
- .tmux.conf
register: legacy_dotfile_paths

- name: Remove legacy symlinks now that these live under XDG paths
ansible.builtin.file:
path: "{{ ansible_user_dir }}/{{ item.item }}"
state: absent
loop: "{{ legacy_dotfile_paths.results }}"
loop_control:
label: "{{ item.item }}"
when: item.stat.exists and item.stat.islnk

- name: Link dotfiles
ansible.builtin.file:
src: "{{ dotfiles_dir }}/{{ item.src }}"
Expand Down Expand Up @@ -32,19 +50,15 @@
- { src: "parquet/parquet_to_csv.py", dest: ".bin/parquet_to_csv" }

# Misc. scripts and config
- { src: "git/gitconfig", dest: ".gitconfig" }
- { src: "git/gitconfig", dest: ".config/git/config" }
- { src: "ssh/config", dest: ".ssh/config" }
- { src: "gitignore", dest: ".gitignore" }
- { src: "gitignore", dest: ".config/git/ignore" }
- { src: "duckdb/duckdbrc", dest: ".duckdbrc" }
- { src: "visidata/visidatarc", dest: ".visidatarc" }
- { src: "markdown/md2docx.sh", dest: ".bin/md2docx" }
- {
src: "poetry/update_poetry_dependencies.py",
dest: ".bin/update_poetry_dependencies",
}
- { src: "hammerspoon/init.lua", dest: ".hammerspoon/init.lua" }
- { src: "ghostty/config", dest: ".config/ghostty/config" }
- { src: "tmux/tmux.conf", dest: ".tmux.conf" }
- { src: "tmux/tmux.conf", dest: ".config/tmux/tmux.conf" }
- { src: "ripgrep/rc", dest: ".config/ripgrep/rc" }
- {
src: "python/scratch_python_project.sh",
Expand Down
Loading
Loading