Skip to content

Commit 38bfd82

Browse files
docs: add CLAUDE.md — project context for Claude Code sessions
1 parent 25b73b6 commit 38bfd82

1 file changed

Lines changed: 265 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
# CLAUDE.md — dotfiles
2+
3+
## Repository purpose
4+
5+
Personal dotfiles for Ubuntu/Debian. One-command install (`install.sh`) with four
6+
profiles, managed updates (`update.sh`), and a post-install test suite (`test.sh`).
7+
CI tests all profile × Ubuntu-version combinations on every push.
8+
9+
---
10+
11+
## Directory layout
12+
13+
```
14+
install.sh entry point — profile selection, module orchestration
15+
update.sh managed tool updates with --check mode + PATH shadow check
16+
test.sh post-install validation suite (profile-aware)
17+
get.sh curl-pipe bootstrap (clones repo → runs install.sh)
18+
lib/utils.sh shared logging, sudo detection, GitHub helpers, binary utils
19+
modules/
20+
base.sh apt packages + per-tool installers (fzf, zoxide, delta, eza, …)
21+
zsh.sh oh-my-zsh, plugins, .zshrc symlink, default shell
22+
tmux.sh tmux config symlinks, plugin cloning
23+
neovim.sh GitHub binary release, config symlink, shadow detection
24+
tools.sh uv, ruff, cheat, ripgrep/ranger config
25+
scripts/
26+
install-fonts.sh MesloLGS NF installer (local workstation only)
27+
install-x11.sh Caps Lock remapping (X11 only)
28+
zsh/ .zshrc
29+
nvim/ .config/nvim/ (lazy.nvim, LSP, treesitter, etc.)
30+
git/ .gitconfig, .gitattributes
31+
tmux/ .tmux.conf, .tmux.conf.local
32+
ripgrep/ .config/ripgrep/rc
33+
ranger/ .config/ranger/ (individual file symlinks, not directory)
34+
x11/ .xprofile, caps-remap.sh, autostart .desktop
35+
VERSION semver string
36+
CHANGELOG.md keep-a-changelog format
37+
```
38+
39+
---
40+
41+
## Install profiles
42+
43+
| Profile | Sudo | Installs | Notes |
44+
|---------|------|----------|-------|
45+
| `minimal` | required | base pkgs, zsh, tmux, git config | Terminal baseline |
46+
| `workstation` | required | minimal + neovim + tools (uv, ruff, cheat) | Full dev setup |
47+
| `docker` | optional | base pkgs, zsh, tmux, git config | No shell change, no fonts |
48+
| `nosudo` | none | all tools fetched to `~/.local/bin` | `NOSUDO=1 ./install.sh minimal` |
49+
50+
Profile selection: CLI arg → interactive wizard (tty only) → workstation default.
51+
52+
Module orchestration in `install.sh`:
53+
- `_run_minimal` → base → zsh → tmux → git link
54+
- `_run_workstation` → base → zsh → tmux plugins → tools → neovim → git link
55+
- `_run_docker` → base\_docker → zsh(no shell change) → tmux → git link
56+
57+
---
58+
59+
## Logging
60+
61+
```bash
62+
log_step "Section name" # bold header: ── Section name ──
63+
log_info "..." # blue →
64+
log_ok "..." # green ✓
65+
log_warn "..." >&2 # yellow ! (stderr)
66+
log_error "..." >&2 # red ✗ (stderr)
67+
die "..." # log_error + exit 1
68+
```
69+
70+
Always use these — never `echo` directly.
71+
72+
---
73+
74+
## Sudo handling
75+
76+
`detect_sudo` (called once early) sets three globals:
77+
78+
```bash
79+
SUDO=""/"sudo" # prefix for privileged commands
80+
CAN_SUDO=true/false # boolean for branching
81+
SUDO_STATUS=root|sudo_passwordless|sudo_password|nosudo
82+
```
83+
84+
Patterns:
85+
```bash
86+
if $CAN_SUDO; then
87+
$SUDO apt-get ...
88+
fi
89+
90+
# Refresh credential cache before long operations
91+
sudo -v 2>/dev/null || true
92+
93+
# Environment override
94+
NOSUDO=1 ./install.sh minimal # forces CAN_SUDO=false everywhere
95+
```
96+
97+
---
98+
99+
## Tool install pattern (apt-first / GitHub fallback)
100+
101+
```
102+
CAN_SUDO + apt has it? → apt_install
103+
CAN_SUDO + apt missing? → GitHub .deb (via _gh_release_info) or PPA
104+
no sudo? → GitHub tarball → ~/.local/bin
105+
```
106+
107+
Key helpers in `lib/utils.sh`:
108+
109+
| Function | Purpose |
110+
|----------|---------|
111+
| `has CMD` | `command -v` wrapper |
112+
| `apt_install` | apt-get with DEBIAN_FRONTEND=noninteractive |
113+
| `_gh_latest_tag_noapi REPO` | Tag via HTTP redirect (no API, no rate limit) |
114+
| `_gh_latest_tag REPO` | Tag via GitHub JSON API |
115+
| `_gh_latest_release REPO PATTERN` | Download URL matching pattern |
116+
| `_gh_release_info REPO PATTERN` | Tag + URL in one API call → `"TAG URL"` |
117+
| `_download_tar_bin URL BIN DEST` | Download tarball, extract named binary |
118+
| `_cmd_version CMD [ARGS]` | Extract `\d+\.\d+(\.\d+)?` from `--version` |
119+
| `_ver_older_than A B` | True if A < B via `sort -V` |
120+
| `_verify_dest BIN DEST` | Warn if `command -v BIN` ≠ DEST |
121+
| `_resolve_dest BIN FALLBACK` | Use current binary location; never `/usr/*` |
122+
| `symlink SRC DST` | `mkdir -p + ln -sf` |
123+
124+
**Use `_gh_release_info` (not URL construction) for `.deb` assets** — asset names
125+
change between releases (e.g. delta 0.19.0 renamed `git-delta``git-delta-musl`).
126+
127+
---
128+
129+
## Architecture triples
130+
131+
```bash
132+
# Rust tools (most tools)
133+
x86_64) arch="x86_64-unknown-linux-musl" ;;
134+
aarch64) arch="aarch64-unknown-linux-gnu" ;; # or musl depending on tool
135+
136+
# Debian arch (for .deb)
137+
_deb_arch() # dpkg --print-architecture; fallback: x86_64→amd64, aarch64→arm64
138+
139+
# Bare (shellcheck)
140+
x86_64 / aarch64
141+
```
142+
143+
Always handle unsupported arch with `log_warn "... — skipping"; return`.
144+
145+
---
146+
147+
## Tmpdir cleanup
148+
149+
```bash
150+
local tmp; tmp=$(mktemp -d)
151+
# shellcheck disable=SC2064
152+
trap "rm -rf '$tmp'" RETURN # RETURN fires on explicit return AND implicit exit
153+
```
154+
155+
Use `RETURN` (not `EXIT`) so the trap is function-scoped, not script-scoped.
156+
157+
---
158+
159+
## PATH shadow detection
160+
161+
Two surfaces:
162+
163+
1. **install time** (`modules/neovim.sh` `_nvim_warn_shadows`): direct file probes
164+
(`[ -e "$HOME/.local/bin/nvim" ]`) — never `command -v`, which resolves via
165+
install-time bash PATH and may return a shadow binary.
166+
167+
2. **update time** (`update.sh` `_check_path_shadows`): PATH walk looking for
168+
executables before `/usr/local/bin`. Runs always, read-only.
169+
170+
Only `/usr/local/bin` tools need shadow checks — tools at `~/.local/bin` are
171+
already at the highest dotfiles-managed PATH priority.
172+
173+
---
174+
175+
## Symlink layout
176+
177+
| Dotfiles path | Linked to |
178+
|---------------|-----------|
179+
| `zsh/.zshrc` | `~/.zshrc` |
180+
| `tmux/.tmux.conf` | `~/.tmux.conf` |
181+
| `tmux/.tmux.conf.local` | `~/.tmux.conf.local` |
182+
| `nvim/.config/nvim` | `~/.config/nvim` (directory symlink) |
183+
| `git/.gitconfig` | `~/.gitconfig` |
184+
| `git/.gitattributes` | `~/.gitattributes` |
185+
| `ripgrep/rc` | `~/.config/ripgrep/rc` |
186+
| `ranger/*.conf` | `~/.config/ranger/*.conf` (individual files) |
187+
| `x11/.xprofile` | `~/.xprofile` |
188+
189+
Ranger is linked file-by-file (not as a directory) to keep runtime state
190+
(`bookmarks`, `history`, `tags`) out of git.
191+
192+
Nvim: if `~/.config/nvim` is a real directory (not a symlink), `link_nvim_config`
193+
warns and bails rather than creating a link inside it.
194+
195+
---
196+
197+
## Global flags
198+
199+
| Flag | Set by | Read by |
200+
|------|--------|---------|
201+
| `_SHELL_IS_ZSH` | `_set_default_shell()` in `modules/zsh.sh` | `install.sh` "next steps" |
202+
| `CAN_SUDO` | `detect_sudo()` in `lib/utils.sh` | everywhere |
203+
| `CHECK_ONLY` | `--check` arg in `update.sh` | update.sh per-tool blocks |
204+
205+
---
206+
207+
## Idempotency rules
208+
209+
- Check before installing: `has cmd && { log_ok "already installed"; return; }`
210+
- Check before cloning: `[ -d dest ] && return`
211+
- Locale: `locale -a | grep -q 'en_US.utf8'` guard before `locale-gen`
212+
- `apt_install` is naturally idempotent
213+
214+
---
215+
216+
## test.sh
217+
218+
```bash
219+
bash test.sh [docker|minimal|workstation|nosudo]
220+
```
221+
222+
Exits 0 (all pass), 1 (any fail). Skips are not failures.
223+
Runs in CI after every install + again after update.sh.
224+
225+
Profile-specific checks:
226+
- `workstation` — nvim, delta, uv, cheat, config symlinks
227+
- `minimal` — ranger, tig, parallel
228+
- `nosudo` — all `~/.local/bin` binaries present + sudo NOT available
229+
230+
---
231+
232+
## CI matrix
233+
234+
**Job `install`** (9 combinations):
235+
- Ubuntu 20.04 / 22.04 / 24.04 × docker / minimal / workstation
236+
- testuser with passwordless sudo; curl auth via `~/.curlrc`
237+
- Flow: install → idempotency re-run → test → update → re-test
238+
239+
**Job `install-nosudo`** (3 combinations):
240+
- Ubuntu 20.04 / 22.04 / 24.04
241+
- Root pre-installs: git, curl, wget, ca-certificates, zsh, tmux, python3
242+
- Regular user with no sudo; `NOSUDO` path exercised
243+
244+
---
245+
246+
## Versioning & release
247+
248+
- `VERSION` — semver (e.g. `1.2.2`)
249+
- `CHANGELOG.md` — keep-a-changelog; update `[Unreleased]` section then add `[X.Y.Z] - DATE`
250+
- Tags: `vX.Y.Z`
251+
- Patch: bug fixes. Minor: new features. Major: breaking changes.
252+
253+
---
254+
255+
## What NOT to do
256+
257+
- **Never construct GitHub release asset URLs from version + arch** — use
258+
`_gh_release_info` or `_gh_latest_release` to get the actual URL; names change.
259+
- **Never use `command -v` to check for binaries at install time** when PATH order
260+
matters — use absolute paths or direct `[ -x /path/to/bin ]` probes.
261+
- **Never commit generated files** (`*_pb2.py`, `*.pb.go`, etc.) — not relevant
262+
here but applies if protobuf ever appears.
263+
- **Never `git push` without confirmation** — always ask first.
264+
- **Never add Co-Authored-By: Claude** to commit messages.
265+
- **Never skip hooks** (`--no-verify`) or force-push master.

0 commit comments

Comments
 (0)