fix: update Gitea org giersig.eu → svemagie.net, blog.giersig.eu → svemagie.net in docs + CLAUDE.md
Deploy Indiekit Server / deploy (push) Failing after 5s

This commit is contained in:
Sven
2026-05-14 19:56:33 +02:00
parent e0a48dc787
commit 30737fbb39
3 changed files with 34 additions and 34 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
restart_log=/tmp/indiekit-restart.log
# Update code as indiekit user; point remote at internal Gitea (no auth needed — public read).
doas bastille cmd node sh -lc 'su -l indiekit -c "cd /usr/local/indiekit && git remote set-url origin https://git.wildwuchs.work/giersig.eu/indiekit-server.git && git fetch origin && git reset --hard origin/main"'
doas bastille cmd node sh -lc 'su -l indiekit -c "cd /usr/local/indiekit && git remote set-url origin https://git.wildwuchs.work/svemagie.net/indiekit-server.git && git fetch origin && git reset --hard origin/main"'
# Install dependencies (postinstall runs all patches automatically).
doas bastille cmd node sh -lc 'su -l indiekit -c "cd /usr/local/indiekit && npm ci --legacy-peer-deps"'
+7 -7
View File
@@ -1,6 +1,6 @@
# CLAUDE.md — indiekit-blog
Personal [Indiekit](https://getindiekit.com/) deployment for [blog.giersig.eu](https://blog.giersig.eu).
Personal [Indiekit](https://getindiekit.com/) deployment for [svemagie.net](https://svemagie.net).
## Always read memory files first
@@ -63,7 +63,7 @@ The node jail **cannot reach its own public HTTPS URL**. Internal self-fetches m
### nginx / Fedify
nginx must forward `Host: blog.giersig.eu` and `X-Forwarded-Proto: https` or AP lookups 302-redirect to the login page. See `patch-ap-federation-bridge-base-url`.
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`.
`createFederation()` requires `allowPrivateAddress: true` (blog resolves to a LAN IP) and `signatureTimeWindow: { hours: 12 }` (Mastodon retries with old signatures).
@@ -182,7 +182,7 @@ npm install git+https://git.wildwuchs.work/svemagie/indiekit-endpoint-activitypu
| Favourited/reblogged state wrong on account statuses timeline | `accounts.js` added `ix.objectUrl` to the Sets instead of `item.uid` — fixed by `patch-ap-interactions-accounts-uid` |
| Liked posts show as not-liked in thread context (ancestors/descendants) | Context endpoint used empty interaction Sets — fixed by `patch-ap-interactions-context-state` |
| "Empty reply from server" on webmention poller | Poller routing through nginx (returns 444 for wrong Host) — must use `INDIEKIT_DIRECT_URL` |
| HTTP Signature 401 errors on all inbound activities | nginx forwarding wrong `Host` header — fixed by `patch-ap-signature-host-header` (overrides to `blog.giersig.eu`) |
| HTTP Signature 401 errors on all inbound activities | nginx forwarding wrong `Host` header — fixed by `patch-ap-signature-host-header` (overrides to `svemagie.net`) |
| HTTP Signature verify errors flooding logs for deleted/migrated actors | Expected noise — `patch-ap-inbox-delivery-debug` (in `patch-ap-federation-infra.mjs`) suppresses both `["fedify","federation","inbox"]` and `["fedify","runtime","docloader"]` to `lowestLevel: "fatal"`. Current marker: `ap-inbox-delivery-debug-A-fatal` |
| Mastodon client (Phanpy, etc.) gets 401 on all authenticated endpoints ~10 min after login | OAuth access token inherited the auth code's 10-min `expiresAt` — fixed by `patch-ap-oauth-token-expiry-fix` (`$unset: { expiresAt }` during code exchange) |
| Mastodon client gets 401 on all requests immediately (not just after 10 min) | "Autorisiertes Abrufen erfordern" (authorized fetch / secure mode) is enabled — unsigned GET requests to actor/collections are rejected. Error message "access token is invalid" is misleading; it comes from the authorized-fetch layer, not OAuth. Fix: disable authorized fetch in AP admin settings. Trade-off: blocked servers can still fetch public posts, but this is acceptable for a public blog. |
@@ -229,16 +229,16 @@ npm install git+https://git.wildwuchs.work/svemagie/indiekit-endpoint-activitypu
**`GITEA_BASE_URL`** must end with a trailing slash: `http://10.100.0.90:3000/api/v1/`
Without it, `new URL(apiPath, baseUrl)` silently strips the `v1` segment → 404 on all writes.
**`GH_CONTENT_TOKEN`** — the Gitea PAT for `svemagie`. `start.sh` rejects startup if neither `GH_CONTENT_TOKEN` nor `GITHUB_TOKEN` is present. The token must have repo read/write scope on `giersig.eu/indiekit-blog`.
**`GH_CONTENT_TOKEN`** — the Gitea PAT for `svemagie`. `start.sh` rejects startup if neither `GH_CONTENT_TOKEN` nor `GITHUB_TOKEN` is present. The token must have repo read/write scope on `svemagie.net/indiekit-blog`.
**`GITEA_CONTENT_USER`** = `giersig.eu` (the org, not the personal username)
**`GITEA_CONTENT_USER` = `svemagie.net` (the org, not the personal username)
**`GITEA_CONTENT_REPO`** = `indiekit-blog`
---
## Micropub → Gitea build dispatch
Gitea Contents API commits (what `store-github` does) do **not** trigger `on: push` CI workflows. `patch-micropub-gitea-dispatch-conditional.mjs` patches the Micropub endpoint to fire a `workflow_dispatch` event to `giersig.eu/indiekit-blog` after each create/update, so the blog rebuilds immediately after a post is published.
Gitea Contents API commits (what `store-github` does) do **not** trigger `on: push` CI workflows. `patch-micropub-gitea-dispatch-conditional.mjs` patches the Micropub endpoint to fire a `workflow_dispatch` event to `svemagie.net/indiekit-blog` after each create/update, so the blog rebuilds immediately after a post is published.
---
@@ -251,7 +251,7 @@ python3 << 'PYEOF'
import urllib.request, json, base64
TOKEN = "your-gitea-pat"
REPO = "giersig.eu/indiekit-blog"
REPO = "svemagie.net/indiekit-blog"
PATH = ".github/workflows/deploy.yml"
BASE = "http://10.100.0.90:3000/api/v1"
+26 -26
View File
@@ -25,16 +25,16 @@ In `package.json` these use the `git+https://git.wildwuchs.work/svemagie/repo` s
## ActivityPub federation
The blog is a native ActivityPub actor (`@svemagie@blog.giersig.eu`) powered by [Fedify](https://fedify.dev/) v2.1.0 via the `@rmdes/indiekit-endpoint-activitypub` package. All federation routes are mounted at `/activitypub`.
The blog is a native ActivityPub actor (`@svemagie@svemagie.net`) powered by [Fedify](https://fedify.dev/) v2.1.0 via the `@rmdes/indiekit-endpoint-activitypub` package. All federation routes are mounted at `/activitypub`.
### Actor identity
| Field | Value |
|---|---|
| Handle | `svemagie` (`AP_HANDLE` env var) |
| Actor URL | `https://blog.giersig.eu/activitypub/users/svemagie` |
| Actor URL | `https://svemagie.net/activitypub/users/svemagie` |
| Actor type | `Person` |
| WebFinger | `acct:svemagie@blog.giersig.eu` |
| WebFinger | `acct:svemagie@svemagie.net` |
| Migration alias | `https://troet.cafe/users/svemagie` (`AP_ALSO_KNOWN_AS`) |
### Key management
@@ -89,7 +89,7 @@ createFederation({
```
- **`signatureTimeWindow: { hours: 12 }`** — Mastodon retries failed deliveries with the original signature, which can be hours old. Without this, retries are rejected.
- **`allowPrivateAddress: true`** — blog.giersig.eu resolves to a private IP (10.100.0.10) on the home LAN. Without this, Fedify's SSRF guard blocks WebFinger and `lookupObject()` for own-site URLs, breaking federation.
- **`allowPrivateAddress: true`** — svemagie.net resolves to a private IP (10.100.0.10) on the home LAN. Without this, Fedify's SSRF guard blocks WebFinger and `lookupObject()` for own-site URLs, breaking federation.
### Inbox handling
@@ -189,7 +189,7 @@ The patch replaces the broken date-from-URL regex with a simple last-path-segmen
### Troubleshooting
**All inbound AP activities return 401 / remote servers stop delivering**
The root cause is usually the `host` header being forwarded as the nginx upstream IP instead of the canonical `blog.giersig.eu`. Fedify includes `host` in the signed-string for Cavage HTTP Signatures; if it doesn't match what the remote server signed, every inbox POST fails verification. Fixed by `patch-ap-signature-host-header`: overrides `"host"` with `new URL(publicationUrl).host` in `fromExpressRequest()` after copying headers from the Express request.
The root cause is usually the `host` header being forwarded as the nginx upstream IP instead of the canonical `svemagie.net`. Fedify includes `host` in the signed-string for Cavage HTTP Signatures; if it doesn't match what the remote server signed, every inbox POST fails verification. Fixed by `patch-ap-signature-host-header`: overrides `"host"` with `new URL(publicationUrl).host` in `fromExpressRequest()` after copying headers from the Express request.
**`ERR fedify·federation·inbox Failed to verify the request's HTTP Signatures`**
At low volume this is expected (deleted actors, migrated servers with stale keys). `patch-ap-inbox-delivery-debug` changes the log level for `["fedify","federation","inbox"]` from `"fatal"` to `"error"` so real delivery failures are visible. If you see it flooding, the most common cause is the `host` header mismatch above — check that `patch-ap-signature-host-header` is applied. The body buffering patch must also preserve raw bytes in `req._rawBody` — if `JSON.stringify(req.body)` is used instead, the Digest header won't match.
@@ -344,7 +344,7 @@ Like posts are created as **drafts** (`post-status: draft` → `draft: true` in
The YouTube Data API requires OAuth 2.0 (not just an API key) to access a user's liked videos.
1. Create an **OAuth 2.0 Client ID** (Web application) in [Google Cloud Console](https://console.cloud.google.com/apis/credentials)
2. Add authorized redirect URI: `https://blog.giersig.eu/youtube/likes/callback`
2. Add authorized redirect URI: `https://svemagie.net/youtube/likes/callback`
3. Ensure **YouTube Data API v3** is enabled for the project
4. Set environment variables:
@@ -444,7 +444,7 @@ Two bugs in the Mastodon API delete route. Bug 1 (ReferenceError): the route use
The LogTape logger for `["fedify","federation","inbox"]` was hardcoded to `"fatal"` in `federation-setup.js`, suppressing all inbox errors including genuine delivery failures. Fix: changes the log level to `"error"` so real failures are visible. Expected noise from deleted/migrated actors (whose keys no longer resolve) still floods at `"error"` but can be filtered at the log aggregator level.
**`patch-ap-signature-host-header.mjs`**
`patch-ap-federation-bridge-base-url` fixed Fedify URL routing to use the canonical `publicationUrl`, but left the `host` header in the copied Headers object untouched. nginx forwards an internal host (e.g. `10.100.0.20`) which Fedify reads from `request.headers.get("host")` when reconstructing the signed-string for Cavage HTTP Signatures. Signed-string mismatch → every inbox POST returns 401 → remote servers exhaust retries and stop delivering. Fix: after the header-copy loop in `fromExpressRequest()`, overrides `"host"` with `new URL(publicationUrl).host` (`"blog.giersig.eu"`) when `publicationUrl` is provided.
`patch-ap-federation-bridge-base-url` fixed Fedify URL routing to use the canonical `publicationUrl`, but left the `host` header in the copied Headers object untouched. nginx forwards an internal host (e.g. `10.100.0.20`) which Fedify reads from `request.headers.get("host")` when reconstructing the signed-string for Cavage HTTP Signatures. Signed-string mismatch → every inbox POST returns 401 → remote servers exhaust retries and stop delivering. Fix: after the header-copy loop in `fromExpressRequest()`, overrides `"host"` with `new URL(publicationUrl).host` (`"svemagie.net"`) when `publicationUrl` is provided.
### Conversations
@@ -510,7 +510,7 @@ Adds AI disclosure field UI (text level, code level, etc.) to the post creation/
Removes AI disclosure fields from the post form submission before saving, delegating persistence to the AI block sidecar system.
**`patch-endpoint-posts-fetch-diagnostic.mjs`**
In the two-jail setup the node jail cannot reach `https://blog.giersig.eu` directly; self-referential fetches in `@indiekit/endpoint-posts` fail with `ECONNREFUSED`. Fix: rewrites self-referential fetch URLs to `http://localhost:<PORT>` using `INTERNAL_FETCH_URL` (or an automatic fallback), and wraps the fetch in a try-catch that logs the URL and response status on failure to make networking problems easier to diagnose.
In the two-jail setup the node jail cannot reach `https://svemagie.net` directly; self-referential fetches in `@indiekit/endpoint-posts` fail with `ECONNREFUSED`. Fix: rewrites self-referential fetch URLs to `http://localhost:<PORT>` using `INTERNAL_FETCH_URL` (or an automatic fallback), and wraps the fetch in a try-catch that logs the URL and response status on failure to make networking problems easier to diagnose.
**`patch-endpoint-posts-uid-lookup.mjs`**
Fixes post editing 404s by adding `uid`-based lookup to the micropub source query. Without this, posts older than the first 40 results could not be opened for editing.
@@ -645,7 +645,7 @@ The production setup uses two FreeBSD jails managed by [Bastille](https://bastil
### Internal fetch URL
The node jail cannot reach the public HTTPS URL (`https://blog.giersig.eu`) because TLS terminates on the web jail. Several features need to fetch their own pages or static assets:
The node jail cannot reach the public HTTPS URL (`https://svemagie.net`) because TLS terminates on the web jail. Several features need to fetch their own pages or static assets:
- **Webmention sender** — fetches live page HTML for link extraction
- **Bluesky syndicator** — fetches photos for upload, OG metadata/images for link cards
@@ -657,9 +657,9 @@ All of these use a shared `_toInternalUrl()` helper (injected by patch scripts)
INTERNAL_FETCH_URL=http://10.100.0.20:3000
```
**Why not nginx (`http://10.100.0.10`)?** nginx's HTTP/80 listener for `blog.giersig.eu` returns a `301` redirect to `https://`. Node's fetch follows the redirect to the public HTTPS URL, which the node jail cannot reach: pf's `rdr` rule only fires on the external interface (`vtnet0`), so there is no hairpin NAT for jail-originated traffic. The result is `UND_ERR_SOCKET: other side closed` on every internal POST (editing posts, syndication, token introspection).
**Why not nginx (`http://10.100.0.10`)?** nginx's HTTP/80 listener for `svemagie.net` returns a `301` redirect to `https://`. Node's fetch follows the redirect to the public HTTPS URL, which the node jail cannot reach: pf's `rdr` rule only fires on the external interface (`vtnet0`), so there is no hairpin NAT for jail-originated traffic. The result is `UND_ERR_SOCKET: other side closed` on every internal POST (editing posts, syndication, token introspection).
### nginx configuration (`/usr/local/etc/nginx/sites/blog.giersig.eu.conf`)
### nginx configuration (`/usr/local/etc/nginx/sites/svemagie.net.conf`)
The full vhost config lives in the web jail. Key design points:
@@ -681,11 +681,11 @@ map $http_accept $is_activitypub {
# Passes responses through unmodified — no error interception.
server {
listen 10.100.0.10:80;
server_name blog.giersig.eu;
server_name svemagie.net;
# Hardcode Host so Indiekit sees the real domain, not the jail IP.
# X-Forwarded-Proto https prevents force-https from redirecting.
proxy_set_header Host blog.giersig.eu;
proxy_set_header Host svemagie.net;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@@ -698,27 +698,27 @@ server {
}
}
# ── 2. HTTP: giersig.eu + www → blog.giersig.eu ─────────────────────────────
# ── 2. HTTP: giersig.eu + www → svemagie.net ─────────────────────────────
server {
listen 80;
server_name giersig.eu www.giersig.eu;
return 301 https://blog.giersig.eu$request_uri;
return 301 https://svemagie.net$request_uri;
}
# ── 3. HTTP: blog.giersig.eu (ACME challenge + HTTPS redirect) ──────────────
# ── 3. HTTP: svemagie.net (ACME challenge + HTTPS redirect) ──────────────
server {
listen 80;
server_name blog.giersig.eu;
server_name svemagie.net;
location /.well-known/acme-challenge/ {
root /usr/local/www/letsencrypt;
}
location / {
return 301 https://blog.giersig.eu$request_uri;
return 301 https://svemagie.net$request_uri;
}
}
# ── 4. HTTPS: giersig.eu + www → blog.giersig.eu ────────────────────────────
# ── 4. HTTPS: giersig.eu + www → svemagie.net ────────────────────────────
server {
listen 443 ssl;
server_name giersig.eu www.giersig.eu;
@@ -726,16 +726,16 @@ server {
ssl_certificate_key /usr/local/etc/letsencrypt/live/giersig.eu/privkey.pem;
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
return 301 https://blog.giersig.eu$request_uri;
return 301 https://svemagie.net$request_uri;
}
# ── 5. HTTPS: blog.giersig.eu (main) ────────────────────────────────────────
# ── 5. HTTPS: svemagie.net (main) ────────────────────────────────────────
server {
listen 443 ssl;
http2 on;
server_name blog.giersig.eu;
ssl_certificate /usr/local/etc/letsencrypt/live/blog.giersig.eu/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/blog.giersig.eu/privkey.pem;
server_name svemagie.net;
ssl_certificate /usr/local/etc/letsencrypt/live/svemagie.net/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/svemagie.net/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
@@ -822,7 +822,7 @@ Environment variables are loaded from `.env` via `dotenv`. Copy `.env.example` t
| Variable | Default | Purpose |
|---|---|---|
| `PUBLICATION_URL` | `https://blog.giersig.eu` | Canonical blog URL |
| `PUBLICATION_URL` | `https://svemagie.net` | Canonical blog URL |
| `INDIEKIT_URL` | same as `PUBLICATION_URL` | Application base URL |
| `REDIS_URL` | — | Redis for AP message queue + KV store (production-required for persistence) |
| `INTERNAL_FETCH_URL` | `http://localhost:PORT` | Direct Indiekit URL for self-fetches, bypassing nginx |
@@ -1074,7 +1074,7 @@ Also bumped `@rmdes/indiekit-endpoint-posts` beta.25→beta.44 and removed `came
**fix(webmention): livefetch evolution v3→v5** (`11d600058`, `7f9f02bc3`, `17b93b3a2`)
Three successive fixes to the webmention sender livefetch patch, driven by split-DNS and jail networking constraints:
- **v3** (`11d600058`): Send `Host: blog.giersig.eu` on internal fetches so nginx routes to the correct vhost; add `fetchUrl` diagnostics and response body preview on h-entry check failure
- **v3** (`11d600058`): Send `Host: svemagie.net` on internal fetches so nginx routes to the correct vhost; add `fetchUrl` diagnostics and response body preview on h-entry check failure
- **v4** (`7f9f02bc3`): Remove `INTERNAL_FETCH_URL` rewrite for live page fetches — post URLs require authentication on the internal nginx vhost (returns login page). Fetch from `postUrl` (public URL) directly. Add `WEBMENTION_LIVEFETCH_URL` as an opt-in override
- **v5** (`17b93b3a2`): Replace live page fetch entirely with a synthetic h-entry HTML snippet built from `post.properties` stored in MongoDB (`in-reply-to`, `like-of`, `bookmark-of`, `repost-of`, `content.html`). No network fetch required — eliminates all split-DNS / auth reliability issues