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
10 changes: 10 additions & 0 deletions .chezmoi.toml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{{- /* Runs on `chezmoi init` on a fresh machine. Prompts once, writes chezmoi.toml. */ -}}
{{- $name := promptStringOnce . "name" "Full name" -}}
{{- $email := promptStringOnce . "email" "Git email" -}}
{{- $isWork := promptBoolOnce . "isWork" "Is this a work machine" -}}
sourceDir = {{ .chezmoi.sourceDir | quote }}

[data]
name = {{ $name | quote }}
email = {{ $email | quote }}
isWork = {{ $isWork }}
7 changes: 7 additions & 0 deletions .chezmoiignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Repo-meta files that must NOT be deployed into $HOME by `chezmoi apply`.
README.md
PLAN.md
CLAUDE.md
justfile
packages/**
secrets/**
49 changes: 49 additions & 0 deletions .chezmoiscripts/run_onchange_before_10-install-packages.sh.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Re-runs whenever the package manifests change (hashes below).
# Brewfile hash: {{ include "packages/Brewfile" | sha256sum }}
# devbox hash: {{ include "packages/devbox.global.json" | sha256sum }}
set -euo pipefail

SRC="{{ .chezmoi.sourceDir }}"

log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }

# --- Homebrew ---
if ! command -v brew >/dev/null 2>&1; then
log "Installing Homebrew"
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
eval "$(/opt/homebrew/bin/brew shellenv 2>/dev/null || /usr/local/bin/brew shellenv)"

log "brew bundle (Brewfile)"
brew bundle --file="$SRC/packages/Brewfile"

# --- oh-my-zsh ---
if [ ! -d "$HOME/.oh-my-zsh" ]; then
log "Installing oh-my-zsh"
RUNZSH=no KEEP_ZSHRC=yes sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
fi

# --- devbox global ---
if command -v devbox >/dev/null 2>&1; then
log "Syncing devbox global profile"
mkdir -p "$HOME/.local/share/devbox/global/default"
cp "$SRC/packages/devbox.global.json" "$HOME/.local/share/devbox/global/default/devbox.json"
devbox global install || true
fi

# --- node via nvm (LTS + repo's pinned version if present) ---
export NVM_DIR="$HOME/.nvm"
if [ -s "$NVM_DIR/nvm.sh" ]; then
# shellcheck disable=SC1091
. "$NVM_DIR/nvm.sh"
nvm install --lts >/dev/null 2>&1 || true
fi

# --- tmux plugin manager ---
if [ ! -d "$HOME/.tmux/plugins/tpm" ]; then
log "Installing tmux TPM"
git clone --depth 1 https://github.com/tmux-plugins/tpm "$HOME/.tmux/plugins/tpm"
fi

log "Package bootstrap complete."
52 changes: 52 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: dotfiles-ci

on:
push:
pull_request:

permissions:
contents: read

jobs:
secrets-scan:
name: gitleaks (no plaintext secrets)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2

validate:
name: chezmoi apply --dry-run
runs-on: ubuntu-latest
# Full render needs to decrypt sops secrets. Add repo secret SOPS_AGE_KEY
# (contents of ~/.config/sops/age/keys.txt) to enable this job.
if: ${{ secrets.SOPS_AGE_KEY != '' }}
steps:
- uses: actions/checkout@v4
- name: Install chezmoi, sops, age
run: |
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b "$HOME/.local/bin"
sudo apt-get update && sudo apt-get install -y age
curl -fsSL -o /usr/local/bin/sops https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64
sudo chmod +x /usr/local/bin/sops
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Restore age key
env:
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
run: |
mkdir -p "$HOME/.config/sops/age"
printf '%s\n' "$SOPS_AGE_KEY" > "$HOME/.config/sops/age/keys.txt"
- name: Write chezmoi config (non-interactive)
run: |
mkdir -p "$HOME/.config/chezmoi"
cat > "$HOME/.config/chezmoi/chezmoi.toml" <<EOF
sourceDir = "$PWD"
[data]
name = "CI Runner"
email = "ci@example.com"
isWork = false
EOF
- name: chezmoi apply --dry-run
run: chezmoi apply --dry-run --verbose --destination "$RUNNER_TEMP/home"
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# --- Secrets & keys: NEVER commit ---
*.key
keys.txt
age/keys.txt
*.age
.env
.env.*
!.env.example
# plaintext secret files (only the sops-encrypted *.enc.* forms are tracked)
secrets/*.plain.*
secrets/*.dec.*

# --- Machine state / history that must not be synced ---
*_history
.DS_Store

# --- chezmoi local artifacts ---
.chezmoiroot.local
23 changes: 23 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Install once: pre-commit install
# The secret gate: a commit is rejected if any plaintext secret is detected.
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.4
hooks:
- id: gitleaks

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-added-large-files
- id: detect-private-key
- id: check-merge-conflict

- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.10.0
hooks:
- id: shellcheck
# chezmoi .tmpl scripts contain {{ }} and are not valid standalone shell.
exclude: '\.tmpl$'
5 changes: 5 additions & 0 deletions .sops.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# sops encryption policy. Any file under secrets/ is encrypted to this age key.
# The matching private key lives ONLY at ~/.config/sops/age/keys.txt (never committed).
creation_rules:
- path_regex: secrets/.*\.(yaml|yml|json|env)$
age: age1gn0mpr6klym62q98tzu0et29xyl93mk8ehhp4ueyatgdx7nhx5eqsydpyc
62 changes: 62 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# context-mode — MANDATORY routing rules

You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session.

## BLOCKED commands — do NOT attempt these

### curl / wget — BLOCKED
Any Bash command containing `curl` or `wget` is intercepted and replaced with an error message. Do NOT retry.
Instead use:
- `ctx_fetch_and_index(url, source)` to fetch and index web pages
- `ctx_execute(language: "javascript", code: "const r = await fetch(...)")` to run HTTP calls in sandbox

### Inline HTTP — BLOCKED
Any Bash command containing `fetch('http`, `requests.get(`, `requests.post(`, `http.get(`, or `http.request(` is intercepted and replaced with an error message. Do NOT retry with Bash.
Instead use:
- `ctx_execute(language, code)` to run HTTP calls in sandbox — only stdout enters context

### WebFetch — BLOCKED
WebFetch calls are denied entirely. The URL is extracted and you are told to use `ctx_fetch_and_index` instead.
Instead use:
- `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` to query the indexed content

## REDIRECTED tools — use sandbox equivalents

### Bash (>20 lines output)
Bash is ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, and other short-output commands.
For everything else, use:
- `ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call
- `ctx_execute(language: "shell", code: "...")` — run in sandbox, only stdout enters context

### Read (for analysis)
If you are reading a file to **Edit** it → Read is correct (Edit needs content in context).
If you are reading to **analyze, explore, or summarize** → use `ctx_execute_file(path, language, code)` instead. Only your printed summary enters context. The raw file content stays in the sandbox.

### Grep (large results)
Grep results can flood context. Use `ctx_execute(language: "shell", code: "grep ...")` to run searches in sandbox. Only your printed summary enters context.

## Tool selection hierarchy

1. **GATHER**: `ctx_batch_execute(commands, queries)` — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls.
2. **FOLLOW-UP**: `ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call.
3. **PROCESSING**: `ctx_execute(language, code)` | `ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context.
4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context.
5. **INDEX**: `ctx_index(content, source)` — Store content in FTS5 knowledge base for later search.

## Subagent routing

When spawning subagents (Agent/Task tool), the routing block is automatically injected into their prompt. Bash-type subagents are upgraded to general-purpose so they have access to MCP tools. You do NOT need to manually instruct subagents about context-mode.

## Output constraints

- Keep responses under 500 words.
- Write artifacts (code, configs, PRDs) to FILES — never return them as inline text. Return only: file path + 1-line description.
- When indexing content, use descriptive source labels so others can `ctx_search(source: "label")` later.

## ctx commands

| Command | Action |
|---------|--------|
| `ctx stats` | Call the `ctx_stats` MCP tool and display the full output verbatim |
| `ctx doctor` | Call the `ctx_doctor` MCP tool, run the returned shell command, display as checklist |
| `ctx upgrade` | Call the `ctx_upgrade` MCP tool, run the returned shell command, display as checklist |
75 changes: 75 additions & 0 deletions PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Senior-Grade Dotfiles — Build Plan

**Stack:** chezmoi (source dir = this repo) + sops/age for secrets. Target: one-command
bootstrap on a fresh Mac, zero secrets in git, per-machine templating.

Audited machine state: zsh + oh-my-zsh + starship, nvim, tmux, ghostty/iterm2/wave,
yazi, lazygit; devbox(global) + colima; nvm→node24, bun, pnpm, go(devbox), pyenv/chruby;
~80 brew leaves + casks; heavy `~/.claude`. Repo currently empty.

---

## Phase 0 — Security first (do before ANY commit)
- [ ] **Revoke** the `gho_…` token found in `~/.gitconfig` (GitHub → Settings → Developer settings → revoke).
- [ ] Remove the `url.https://x-access-token:…insteadOf` line from `~/.gitconfig`; keep the `gh` credential helper you already have.
- [ ] Generate age key: `age-keygen -o ~/.config/sops/age/keys.txt` (this file is **never** committed).
- [ ] Add repo-root `.sops.yaml` binding the age recipient; add `.gitignore` for `*.key`, `keys.txt`, `.env`, `*_history`.

## Phase 1 — Wire chezmoi to THIS repo
- [ ] `chezmoi init --source /Users/antono/workspace/dotfiles` (repo becomes the source tree).
- [ ] Create `.chezmoi.toml.tmpl` with machine vars: `{ email, name, isWork, hostname }` — prompts on first apply.
- [ ] Add `README.md` one-liner bootstrap:
`sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply <git-url>`
(⚠️ sandbox blocks curl here — this line is for the fresh-machine README only.)

## Phase 2 — Migrate configs (managed = `chezmoi add`)
Bring in incrementally, templatize the machine-specific bits:
- [ ] `dot_zshrc.tmpl` — split into `dot_config/zsh/` fragments (aliases, exports, path, tool-init); keep `.zshrc` thin. Guard oh-my-zsh vs starship.
- [ ] `dot_gitconfig.tmpl` — templatize `user.email`/signing per `.isWork`; **no tokens**; personal-vs-work `insteadOf` via includeIf.
- [ ] `private_dot_config/` — starship.toml, ghostty, yazi, htop, lazygit, gh, atlassian-cli.
- [ ] `dot_zshenv`, `dot_zprofile`, `dot_tmux.conf`, nvim (`private_dot_config/nvim` or reference existing).
- [ ] `~/.claude/{settings.json,CLAUDE.md,rules,agents,commands}` — managed; **exclude** `history.jsonl`, `sessions`, `cache`, `*.bak`, `security_warnings_*`, `mcp-needs-auth-cache.json`.

## Phase 3 — Reproducible packages (idempotent)
- [ ] `run_once_before_10-install-packages.sh.tmpl`:
- `Brewfile` from `brew bundle dump` (leaves + casks) → `brew bundle`.
- devbox global: commit `~/.local/share/devbox/global/default/devbox.json`, `devbox global install`.
- nvm + `.nvmrc` (node 24), bun, pnpm via corepack.
- [ ] `run_once_after_*` for oh-my-zsh install + tmux TPM if missing.

## Phase 4 — Secrets via sops/age
- [ ] `secrets/env.enc.yaml` (committed, encrypted): github_token, npm authToken, any API keys.
- [ ] Template consumes them at apply: e.g. `~/.npmrc.tmpl`, `dot_config/zsh/secrets.zsh.tmpl` using `{{ (index (fromYaml (decrypt (include "secrets/env.enc.yaml"))) "github_token") }}`.
- [ ] Verify a plaintext secret NEVER appears in `chezmoi cat` diff of a committed file.

## Phase 5 — Quality gates (the "senior" signal)
- [ ] `.github/workflows/ci.yml`: `shellcheck` all scripts, `chezmoi verify`, dry-run `chezmoi apply --dry-run` on a clean runner.
- [ ] `pre-commit`: gitleaks/trufflehog scan so no secret can be committed even by accident.
- [ ] `Makefile` / `justfile`: `apply`, `diff`, `update`, `edit`, `lint`, `bootstrap`.
- [ ] `README.md`: fresh-machine setup, structure map, how secrets work.
- [ ] Optional: test bootstrap in a throwaway colima/UTM VM before trusting it.

---

## Suggested repo layout
```
dotfiles/
.chezmoi.toml.tmpl # machine vars (prompted)
.sops.yaml .gitignore
dot_zshrc.tmpl dot_zshenv dot_zprofile dot_gitconfig.tmpl dot_tmux.conf
private_dot_config/
zsh/{aliases,exports,path,init,secrets.zsh.tmpl}
starship.toml ghostty/ yazi/ htop/ lazygit/ gh/ nvim/
private_dot_claude/ # settings, rules, agents, commands (no state)
secrets/env.enc.yaml # sops-encrypted, safe to push
.chezmoiscripts/
run_once_before_10-install-packages.sh.tmpl
Brewfile devbox.global.json
.github/workflows/ci.yml
justfile README.md
```

## Sequencing
Phase 0 today (security). Then 1→2 in small commits (one config group per commit,
`chezmoi diff` before each). 3–4 once shell/git are stable. 5 last. Each phase is
independently verifiable — never a big-bang migration.
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,75 @@
# dotfiles

Senior-grade, reproducible macOS dev environment managed with
[chezmoi](https://chezmoi.io) + [sops](https://github.com/getsops/sops)/[age](https://github.com/FiloSottile/age).

For an AI-native fullstack stack: React/TypeScript · Go · Postgres · devbox + colima · Claude Code.

## Principles

- **One command** rebuilds a machine from scratch.
- **Zero secrets in git** — everything sensitive is sops-encrypted or gh-helper-injected.
- **Per-machine** differences handled by templates, not forks.
- **Tested** — CI dry-runs `chezmoi apply` and gitleaks-scans every push.

## Fresh machine

```sh
# 1. install + apply dotfiles (installs chezmoi, prompts for name/email/work-flag)
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply <this-repo-url>

# 2. restore the age private key (from your password manager / backup) so secrets decrypt
mkdir -p ~/.config/sops/age
cp /path/to/backup/keys.txt ~/.config/sops/age/keys.txt && chmod 600 ~/.config/sops/age/keys.txt

# 3. re-apply now that secrets can decrypt
chezmoi apply
```

The `run_onchange_*` bootstrap script installs Homebrew + the `Brewfile`, oh-my-zsh,
the devbox global profile, node (nvm), and tmux TPM.

## Daily use

```sh
just diff # preview changes before applying
just apply # apply source -> $HOME
just edit ~/.zshrc # edit a managed file
just add ~/.config/foo # capture a new dotfile
just secrets # edit the encrypted secret store
just freeze-packages # re-snapshot Brewfile + devbox after installing something
just update # git pull + apply
```

## Secrets (sops + age)

- Encrypted store: `secrets/env.enc.yaml` (safe to commit — values are AES-encrypted).
- Private key: `~/.config/sops/age/keys.txt` — **never** committed; back it up in your password manager.
- Policy: `.sops.yaml`. Templates read secrets at apply time, e.g. `dot_npmrc.tmpl`.

To enable the CI apply-dry-run, add the age private key as repo secret `SOPS_AGE_KEY`.

## Layout

```
.chezmoi.toml.tmpl machine vars (prompted on init)
.sops.yaml .gitignore .chezmoiignore
dot_zshrc dot_zshenv dot_zprofile shell
dot_gitconfig.tmpl git identity (templated, no tokens)
dot_npmrc.tmpl npm auth (from sops)
dot_config/ starship, ghostty, yazi, htop, gh
dot_claude/ Claude Code settings + rules (go/ts/common)
secrets/env.enc.yaml sops-encrypted secrets
packages/Brewfile devbox.global.json reproducible package sets
.chezmoiscripts/run_onchange_* idempotent bootstrap
.github/workflows/ci.yml gitleaks + apply dry-run
justfile task runner
```

## Security note

If a token ever lands in git history, revoke it immediately — history is permanent.
The gitleaks pre-commit hook + CI job exist to make that near-impossible.
```sh
pre-commit install # enable the local secret gate
```
6 changes: 6 additions & 0 deletions dot_claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@RTK.md

# Ruflo Integration (auto-generated by ruflo init)
When working on multi-file tasks or complex features, use ToolSearch to find and invoke ruflo MCP tools.
Key tools: memory_store, memory_search, hooks_route, swarm_init, agent_spawn.
Check system-reminder tags for [INTELLIGENCE] pattern suggestions before starting work.
Loading