From 981e4833d6d85446ac483e3dd3101a81182af5c7 Mon Sep 17 00:00:00 2001 From: Sven Date: Thu, 14 May 2026 20:17:31 +0200 Subject: [PATCH] docs: document hairpin NAT problem and git URL rewrite solution --- CLAUDE.md | 24 ++++++++++ memory/project_architecture.md | 86 ++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 memory/project_architecture.md diff --git a/CLAUDE.md b/CLAUDE.md index c7a179a8..08fb5053 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,10 +57,34 @@ const NEW_SNIPPET = `replacement text ${MARKER}`; ``` Internet → nginx (web jail 10.100.0.10) → Indiekit (node jail 10.100.0.20:3000) + → Gitea (gitea jail 10.100.0.90:3000) ``` The node jail **cannot reach its own public HTTPS URL**. Internal self-fetches must use `INTERNAL_FETCH_URL=http://10.100.0.20:3000` directly. All such fetches go through `_toInternalUrl()` (injected by `patch-micropub-fetch-internal-url`). +### Hairpin NAT — jails cannot reach git.wildwuchs.work + +pf's `rdr` rules only fire on `$ext_if` (vtnet0). Jail-originated traffic to the public IP bypasses RDR entirely → `git.wildwuchs.work:443` unreachable from any jail. This breaks: + +- `actions/checkout@v4` in the act_runner (gitea-Jail) — can't clone the repo +- `npm ci` fetching `git+https://git.wildwuchs.work/...` packages (node-Jail) + +**Solution:** per-user `.gitconfig` URL rewrite in each affected jail: + +```ini +[url "http://10.100.0.90:3000/"] + insteadOf = https://git.wildwuchs.work/ +``` + +| Jail | File on host | +|------|-------------| +| gitea (act_runner, user `git`) | `/usr/local/bastille/jails/gitea/root/usr/local/git/.gitconfig` | +| node (indiekit, user `indiekit`) | `/usr/local/bastille/jails/node/root/usr/local/indiekit/.gitconfig` | + +`/etc/hosts` in the gitea-Jail also has `10.100.0.90 git.wildwuchs.work`. + +**Adding a new jail:** add the same `[url]` block to the user's `.gitconfig` at the host path above. + ### nginx / Fedify nginx must forward `Host: svemagie.net` and `X-Forwarded-Proto: https` or AP lookups 302-redirect to the login page. See `patch-ap-federation-bridge-base-url`. diff --git a/memory/project_architecture.md b/memory/project_architecture.md new file mode 100644 index 00000000..5f4046f3 --- /dev/null +++ b/memory/project_architecture.md @@ -0,0 +1,86 @@ +# Server Architecture + +## Infrastructure + +- **FreeBSD jails** — Indiekit runs in an isolated jail +- **nginx** — reverse proxy; must forward `Host: blog.giersig.eu` and `X-Forwarded-Proto: https` + for Fedify to construct correct canonical URLs (see `patch-ap-federation-bridge-base-url`) +- **MongoDB** — `10.100.0.20:27017`, database `indiekit`, auth source `admin` +- **Redis** — optional; URL via `REDIS_URL` env var; used for AP activity queue + +## Publication URLs + +- `publicationBaseUrl` = `https://blog.giersig.eu` (from `PUBLICATION_URL` env or hardcoded default) +- `applicationBaseUrl` = same (from `INDIEKIT_URL` env) +- GitHub repo: `svemagie/blog`, branch `main` + +## Internal Fetch + +Several patches rewrite outbound HTTP fetches to use internal jail addresses instead of +going through the public internet / nginx: +- `patch-micropub-fetch-internal-url` — Micropub post creation fetches +- `patch-bluesky-syndicator-internal-url` — Bluesky syndicator +- `_toInternalUrl()` helper in microsub/activitypub controllers + +## Hairpin NAT — git.wildwuchs.work from inside jails + +**Problem:** pf's `rdr` rules only fire on `$ext_if` (vtnet0). Traffic originating from a jail +(node-Jail `10.100.0.20`, gitea-Jail `10.100.0.90`) to the public IP never hits the RDR rule, +so `https://git.wildwuchs.work` is unreachable from inside any jail. This breaks: + +- `actions/checkout@v4` in the act_runner (gitea-Jail) — can't clone the repo +- `npm ci` fetching `git+https://git.wildwuchs.work/...` package dependencies (node-Jail) +- `git fetch origin` in deploy scripts that use the public URL + +**Solution — git URL rewrite in each affected jail:** + +Each jail that runs git operations has a `.gitconfig` that rewrites the public URL to the +internal Gitea address: + +```ini +[url "http://10.100.0.90:3000/"] + insteadOf = https://git.wildwuchs.work/ +``` + +| Jail | User | `.gitconfig` path | +|------|------|-------------------| +| gitea-Jail (act_runner) | `git` (home: `/usr/local/git`) | `/usr/local/bastille/jails/gitea/root/usr/local/git/.gitconfig` | +| node-Jail (indiekit) | `indiekit` (home: `/usr/local/indiekit`) | `/usr/local/bastille/jails/node/root/usr/local/indiekit/.gitconfig` | + +`/etc/hosts` in the gitea-Jail also has `10.100.0.90 git.wildwuchs.work` for DNS resolution. + +**If a new jail needs git access to git.wildwuchs.work:** add the same `[url]` block to the +relevant user's `.gitconfig` on the host at `/usr/local/bastille/jails//root//.gitconfig`. + +## Collections (MongoDB) + +| Collection | Contents | +|------------|----------| +| `posts` | Micropub post data (path + properties) | +| `ap_timeline` | Incoming + outgoing AP posts; key: `uid` | +| `ap_notifications` | Mentions, replies, likes, boosts received | +| `ap_followers` | Follower actor URLs | +| `ap_following` | Following actor URLs | +| `ap_activities` | Activity log (outbound + inbound) | +| `ap_profile` | Own actor profile (name, icon, url) | +| `ap_interactions` | Likes and boosts performed by own account | + +## ActivityPub Actor + +- Handle: `activityPubHandle` from `AP_HANDLE` env → `GITHUB_USERNAME` (`svemagie`) → hostname prefix +- Full handle: `@svemagie@blog.giersig.eu` +- Actor URL: `https://blog.giersig.eu/activitypub/actor` +- AP objects served at: `https://blog.giersig.eu/activitypub/objects/note/{+id}` + - Own reply posts: `/activitypub/objects/note/replies/{slug}` + +## Patch Infrastructure + +Patches live in `scripts/patch-*.mjs`. Each script: +1. Checks if already applied (MARKER string) +2. Looks for OLD_SNIPPET in node_modules target file +3. Replaces with NEW_SNIPPET if found +4. Reports result to stdout + +Both `postinstall` and `serve` scripts in `package.json` run all patches in order. +Some patches (e.g. `patch-microsub-reader-ap-dispatch`) only appear in `serve`, not `postinstall`. +New AP patches are appended at the end of the AP patch chain (after `patch-ap-federation-bridge-base-url`).