claude: document the repo layout, the fleet and the singleton guard

The file covered the inference stack in detail and said almost nothing about
the repo it now lives in, so every session rediscovered the same handful of
conventions.

Adds the four hosts with their addresses, roles and package managers --
including that sctfw004 is US-government adjacent and stays on OpenAI and
local models only -- and the five things here that actually cost an hour: the
allowlist .gitignore, and why a new directory being invisible to git is
expected rather than broken; secrets living only in an untracked
~/.secrets.zsh and never in a tracked zshenv; ConditionHost as the guard that
keeps a one-machine timer from firing on all four; and dots / pkg-snapshot as
the maintenance pair, with the deliberate silence of dots --nag explained.

Also notes that ~/.claude/CLAUDE.md is a symlink into this repo, since
editing it silently edits tracked config, and sharpens the .pre-<thing> note:
that convention is for the untracked ~/config and ~/docs trees, where it is
the only backup those files get. Inside a tracked repo git already is the
backup and .gitignore excludes the pattern anyway.
This commit is contained in:
connor committed 2026-09-15 12:06:25 -04:00
1 parent 79da785ed4
commit a82d467aae
1 file changed
+69 -3
+69 -3
View File
@@ -66,11 +66,77 @@ The five things most likely to waste an hour if you don't know them:
Cost work: read `model_group`, never `model`, to attribute a request. Cost is
driven by cache-read volume and context size far more than by model choice.
## The dotfiles repo and the fleet
`~/.dotfiles` is **one branch linked onto four machines** by dotbot. Shared
config is in `common/`, per-machine config in `hosts/<hostname>/`, and what gets
linked where is `meta/base.conf.yaml` plus `meta/hosts/<hostname>.conf.yaml`.
There are deliberately no per-host branches: the layout this replaced was three
machines with two histories that had never met, and host layers cost a few small
files and remove the merge entirely.
| Host | Address | Role | Packages |
|---|---|---|---|
| `mainframe` | 10.0.0.2 | the server — containers, inference stack, Moonlight host | pacman + AUR |
| `nas` | 10.0.0.3 | bulk storage; NFS-exports `audio books docs downloads photo video`; own 03:30 backup run | pacman + AUR |
| `system76` | 10.0.0.4 | personal laptop | pacman + AUR |
| `sctfw004` | 10.0.0.8 | work laptop — `work_laptop` in ssh config, user `connorjohnstone` | apt + flatpak |
`sctfw004` is US-government adjacent: **OpenAI and local models only, never
Anthropic.** Don't propose an Anthropic-backed workflow for that machine.
Five things about this repo that will cost you an hour:
1. **The `.gitignore` is an allowlist, and it is load-bearing.** It opens with
`**/*` and then names what is allowed back in. A new top-level directory is
invisible to git until it is explicitly un-ignored, so "I added the file and
git doesn't see it" is expected, not a bug — fix it by adding the `!` entry,
never by relaxing the `**/*`. The denylist it replaced accumulated browser
caches, mail caches and vendored binaries. It also re-excludes credential
*shapes* (`**/credentials`, `**/*.env`, `**/hosts.yml`, `**/password`,
`.secrets.zsh`); every one of those has held a live credential on these
machines. Leave them alone.
2. **Secrets never live in a tracked file, and specifically not in `zshenv`.**
`common/zsh/zshenv` ends by sourcing `~/.secrets.zsh`, which is untracked
and mode 600 — `common/zsh/secrets.zsh.example` is the shape to copy. Keys
for OpenCode are read out of `rbw` at launch and passed in, never written to
disk. A new secret goes in `~/.secrets.zsh`, never anywhere under
`~/.dotfiles`.
3. **Any unit that must run on exactly one machine carries
`ConditionHost=<host>`.** One branch is linked everywhere, so an unguarded
timer fires on all four. `inbox-tidy`, `movie-recs`, `rent-utilities` and
`ddns` mutate a live mailbox, send notifications, send bills and update live
DNS records, so they are guarded twice: the units exist only in
`hosts/mainframe/systemd/` and never in `common/`, *and* each carries the
condition. Genuinely per-machine things like `hypr_refresh` are correctly
unguarded — don't "fix" them.
4. **`dots` is the one command that answers "is this machine clean".**
Committed, pushed, and does the package manifest still match reality. It is
wired into `zshrc` as `dots --nag`, which prints nothing when everything is
clean. That silence is the point — a login banner that always fires is one
you stop reading.
5. **`pkg-snapshot` writes what is installed into `packages/<host>.*`** —
pacman, AUR, apt and flatpak as separate files. Run it after installing
something worth keeping and commit the diff; the diff doubles as the log of
what was added and when. `pkg-restore` goes the other way.
**This file is a symlink.** `~/.claude/CLAUDE.md` →
`~/.dotfiles/common/claude/CLAUDE.md`. Editing it edits the repo, so a change
here wants a commit and reaches every machine at the next `install`.
## General
- Repos here use an **allowlist** `.gitignore` (`**/*` plus explicit `!` entries).
`~/config/` and `~/docs/` are deliberately untracked — back up config edits by
hand as `<file>.pre-<thing>` snapshots, which is the established convention.
- `~/config/` and `~/docs/` are deliberately untracked. Back up edits there by
hand as `<file>.pre-<thing>` snapshots — the established convention, and the
only backup those trees get. Inside a tracked repo git already is that
backup, so a `.pre-*` file there is redundant (and `.gitignore` excludes the
pattern anyway).
- `~/guides/` is tracked in the `~` repo and is where reference material goes,
`~/.dotfiles` is its own repo. Check which one you are in before committing.
- Don't create project-level `CLAUDE.md` files in `~` — Connor keeps guidance at
the `~/.claude` level and reference material in `~/guides/`.
- Measure before concluding, and when a previous conclusion turns out to have