From 1518dc74c7d9137fdf0031765b5d5bcd2b1975b1 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Sun, 26 Jul 2026 22:52:28 +0200 Subject: [PATCH] Add CLAUDE.md Orients future Claude Code sessions: this is an ops/docs repo for two live remote hosts, not an application codebase - documents the file map, the scripts/ deploy-and-diff workflow, and sanity-check.sh as the closest thing to a test suite. --- CLAUDE.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..40ff489 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,93 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repository is + +This is **not an application codebase** — there's nothing to build, lint, +or test. It's the operations/documentation repo for administering two +real, live hosts remotely over SSH: + +- **`alpha.jayfield.org`** (`89.58.8.149`) — public VPS, Ubuntu 24.04 LTS, + reached via `ssh -p 10022 jens@alpha.jayfield.org`. Runs authoritative + DNS (BIND9), a full mail stack (Postfix/Dovecot/rspamd/MariaDB), Apache + reverse-proxying into a Docker layer (Portainer, Nextcloud, Gitea, a + plain nginx container), and fail2ban. +- **`vlda-01`** — home intranet server (Unraid 7.3.2), reached via + `ssh root@vlda-01` from the same LAN. Not part of `alpha`'s public + `jayfield.org` zone — lives under the private, unresolvable + `home.jayfield.org` namespace. Currently the pull side of the Gitea + backup from `alpha`. + +**All actual changes happen live on those hosts over SSH, not in this +repo.** This repo records what's true afterward and keeps reference +copies of deployed scripts/configs. There is no local checkout of any +live service config to edit — the working pattern for every task is: +SSH in → make the change on the real host → verify it live (curl a vhost, +run `sanity-check.sh`, check a real log line) → record it here. + +## Document map (read the right one, not all of them) + +| File | Purpose | Rule | +|---|---|---| +| `README.md` | **Live state** — what's actually running on `alpha` right now | Source of truth when it disagrees with `SETUP.md` | +| `SETUP.md` | Fresh-install build runbook (currently: from-scratch on 24.04) | How to reproduce the host from nothing, not what's live | +| `TODO.md` | Full chronological audit log of every bug/fix/hardening pass | **Append-only. Never consolidate or summarize existing entries unprompted** — this has been corrected before; keep the full narrative detail even when it's long | +| `SYNC-PLAN.md` | Point-in-time plan for the `vlda-01` → `alpha` intranet migration | Move a service's write-up into `README.md` once it's actually built and live; only update the plan for what's still pending | +| `MAIL-ACCOUNTS.md` | Day-to-day recipe for adding/disabling mail accounts | References `SETUP.md` §3 for the underlying schema | +| `DIFF.md` | One-time (2026-07-19) diff-vs-clean-install investigation | Explicitly a snapshot, not a live inventory — don't treat its findings as current without re-checking | +| `scripts/` | Reference copies of everything deployed on `alpha` and its clients | **Not auto-deployed** — see below | + +## The `scripts/` deployment workflow + +`scripts/` is split by host (`scripts/alpha/`, `scripts/clients//`) +because each host has a genuinely different OS with different +persistence/deployment conventions — each subdirectory's own `README.md` +states the exact OS/version and the live deployment path for every file. +**Editing a file under `scripts/` changes nothing on the real host.** +After editing, the actual workflow is: + +1. Deploy the change to the live host (`scp`/`cat > file` over SSH to the + path listed in that host's `README.md`). +2. `diff` the deployed copy against the repo copy — they must match + byte-for-byte before moving on. +3. Test the actual behavior live (run the script, hit the endpoint, + reboot if it's boot-triggered) — not just "it deployed without error." +4. Only then update `README.md`/`TODO.md` to describe the change. + +`vlda-01` (Unraid) needs particular care here: `/` and `/usr` are +RAM-backed and don't survive a reboot, and `/boot` (the persistent USB +stick) is VFAT with no Unix execute bit — scripts there are deployed via +the **User Scripts** plugin and invoked with `bash `, never `exec`. +See `scripts/clients/vlda-01/README.md` before touching anything there. + +## Verifying a change is the closest thing to a test suite + +There's no CI here — "does it work" is checked directly against the live +host: + +- `sudo /usr/local/sbin/sanity-check.sh` on `alpha` — checks the whole + stack (packages, DNS/DNSSEC, mail, MariaDB, fail2ban, every public + vhost by expected HTTP status, all Docker containers, plus a version + dump of every involved service) and prints PASS/WARN/FAIL. Runs + automatically once per boot via `sanity-check.service`; run it manually + after any config change that doesn't itself trigger a reboot. +- `sudo /usr/local/sbin/attacker-check.py` — weekly fail2ban log analysis + (also runs on its own via cron); safe to run manually any time. +- For anything touching the mail stack, DNS, or a vhost: a real end-to-end + check beats trusting the config on paper (e.g. an actual `dig` against + the authoritative server, a real test email round-trip via + `/var/log/mail.log`, a real `curl` against the public hostname) — this + repo's history (`TODO.md`) has repeated cases of a config that "looked" + correct but silently didn't work. + +## A few standing facts worth knowing before editing docs + +- `jayfield.org` (public, this host's zone) and `home.jayfield.org` + (private home-LAN namespace, `vlda-01` and friends) are deliberately + separate — see `README.md`'s "Domain namespace" section. +- Gitea (`git.jayfield.org`) is the canonical git host for this repo + itself (migrated from `vlda-01`, which keeps a cold, stopped backup + copy). There is no GitHub remote. +- SSH access details (ports, users) belong in `README.md`/host + `README.md`s, not duplicated here — check there rather than assuming.