Files
indiekit-server/CLAUDE.md
Sven 2d5b713b7d
Some checks failed
Deploy Indiekit Server / deploy (push) Failing after 7s
chore: point svemagie fork deps and docs at Gitea
- Switch 4 svemagie fork deps from github: shorthand to git+https://gitea.giersig.eu/svemagie/...
- Add patch-store-github-error-message.mjs to replace hardcoded github.com token URL with gitea.giersig.eu
- Update CLAUDE.md and README.md fork dependency docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 15:05:49 +02:00

8.2 KiB
Raw Blame History

CLAUDE.md — indiekit-blog

Personal Indiekit deployment for blog.giersig.eu.

Always read memory files first

Before investigating or modifying anything:

File When to read
memory/project_activitypub.md Any AP / fediverse / reply threading work
memory/project_architecture.md Server layout, MongoDB, nginx, internal URLs
memory/feedback_patches.md Writing or debugging patch scripts

Running

npm run serve          # preflights + all patches + start Indiekit
npm run postinstall    # re-apply patches after npm install

Never start with node directly — patches must run first.


Patch system

All node_modules fixes live in scripts/patch-*.mjs. Both postinstall and serve run them in order.

Pattern

const MARKER = "// [patch] my-patch-name";
const OLD_SNIPPET = `exact source text (spaces not tabs, exact line endings)`;
const NEW_SNIPPET = `replacement text ${MARKER}`;

// 1. Read file — skip if MARKER already present
// 2. Warn if OLD_SNIPPET not found (upstream changed)
// 3. Replace + writeFile

Rules

  • Include both candidate paths: node_modules/@rmdes/... and node_modules/@indiekit/indiekit/node_modules/@rmdes/...
  • Escape template literals: \` and \${}
  • Append new AP patches after patch-ap-federation-bridge-base-url in both postinstall and serve
  • patch-microsub-reader-ap-dispatch is serve-only — check both scripts for microsub patches
  • After writing a patch script, run it immediately (node scripts/patch-*.mjs) to verify it applies cleanly

Architecture — things that affect code

Two-jail setup

Internet → nginx (web jail 10.100.0.10) → Indiekit (node jail 10.100.0.20: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).

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.

createFederation() requires allowPrivateAddress: true (blog resolves to a LAN IP) and signatureTimeWindow: { hours: 12 } (Mastodon retries with old signatures).

MongoDB collections

Collection Contents
posts Micropub post data — properties.url is the lookup key
ap_timeline AP posts (inbound + outbound) — keyed by uid
ap_notifications Mentions, replies, likes, boosts
ap_followers / ap_following Actor URLs
ap_activities Outbound/inbound activity log
ap_profile Own actor (name, icon, url)
ap_interactions Own likes/boosts
ap_keys RSA + Ed25519 key pairs
ap_featured Pinned posts

Post type discovery

getPostType(postTypes, properties) checks key presence only — value doesn't matter:

Key present Type Saved at
in-reply-to reply /replies/{slug}/
like-of like /likes/{slug}/
repost-of repost /reposts/{slug}/
photo photo /photos/{slug}/
(none) note /notes/{slug}/

If in-reply-to is silently absent, the post becomes a note with no error. This is the most common threading bug root cause.


Reply threading — compose paths

Three paths, different syndication mechanics:

Path AP checkbox mechanism
AP reader /activitypub/admin/reader/compose target.defaultChecked = target.checked === true (patched by patch-ap-compose-default-checked)
Microsub reader /microsub/admin/reader/compose target.checked from Micropub q=config — already true for AP syndicator
Mastodon client API POST /api/v1/statuses mp-syndicate-to hardcoded to publicationUrl — always AP

The AP reader template uses target.defaultChecked, not target.checked. These are different fields.

ap_timeline insertion timing

Own posts reach ap_timeline via two paths:

  • Mastodon API: inserted immediately after postContent.create() (patched by patch-ap-mastodon-reply-threading)
  • Micropub + syndication webhook: inserted by syndicator after Eleventy build (30120 s)

Any new code path that creates posts should insert to ap_timeline immediately — otherwise in_reply_to_id lookups fail during the build window.

Status ID format

encodeCursor(published) = ms-since-epoch string. findTimelineItemById resolves this with a ±1 s range query using MongoDB $dateFromString to handle TZ-offset ISO strings.


ActivityPub syndicator

syndicator.syndicate(properties) does not filter by post type. A note and a reply both become Create(Note). The difference is whether inReplyTo is set (from properties["in-reply-to"]).

JF2 → AS2 mapping:

Post type Activity Notes
note / reply Create(Note) reply has inReplyTo
like Create(Note) bookmark framing (🔖 emoji)
repost Announce
article Create(Article) has name

Visibility:

Value to cc
public as:Public followers
unlisted followers as:Public
followers followers

Fork dependencies

# Pull latest commit from a fork:
npm install git+https://gitea.giersig.eu/svemagie/<package-name>
npm install git+https://gitea.giersig.eu/svemagie/indiekit-endpoint-activitypub
Package Fork
@rmdes/indiekit-endpoint-activitypub git+https://gitea.giersig.eu/svemagie/indiekit-endpoint-activitypub
@rmdes/indiekit-endpoint-blogroll git+https://gitea.giersig.eu/svemagie/indiekit-endpoint-blogroll
@rmdes/indiekit-endpoint-microsub git+https://gitea.giersig.eu/svemagie/indiekit-endpoint-microsub
@rmdes/indiekit-endpoint-youtube git+https://gitea.giersig.eu/svemagie/indiekit-endpoint-youtube

Debugging — starting points

Symptom First check
Reply created as "note" not "reply" Is in-reply-to in the Micropub request? Check: form hidden field, submitComposeController, findTimelineItemById return value, formEncodedToJf2
Reply not federated to AP Is mp-syndicate-to set? Check target.defaultChecked / target.checked, getSyndicateToProperty in jf2.js
AP lookup returns 302 / auth redirect nginx not forwarding Host/X-Forwarded-Proto — see patch-ap-federation-bridge-base-url
findTimelineItemById returns null Item not yet in ap_timeline (build not finished) or TZ-offset date mismatch — $dateFromString range query should catch offsets
Favourite/reblog hangs in Mastodon client resolveAuthor timeout — Promise.race 5 s cap should prevent this
"Empty reply from server" on webmention poller Poller routing through nginx (returns 444 for wrong Host) — must use INDIEKIT_DIRECT_URL
HTTP Signature verify errors flooding logs Expected for deleted/migrated actors — suppressed to fatal level in federation-setup.js
"OAuth callback failed. Missing parameters." state parameter not echoed — fixed in fork (b54146c)
AP object 410 / Tombstone Post was deleted — correct, served by FEP-4f05

Environment variables

Var Purpose
AP_HANDLE AP handle (svemagie)
AP_ALSO_KNOWN_AS Migration alias (https://troet.cafe/users/svemagie)
AP_LOG_LEVEL Fedify log level (info default; debug for delivery tracing)
AP_DEBUG 1 to enable Fedify debug dashboard at /activitypub/__debug__/
PUBLICATION_URL Canonical blog URL
INDIEKIT_URL Application URL (same as publication URL)
INTERNAL_FETCH_URL Direct node jail URL for self-fetches (http://10.100.0.20:3000)
INDIEKIT_BIND_HOST Jail IP for webmention poller direct connect
REDIS_URL Redis for AP message queue + KV (production; without this, queue lost on restart)
MONGO_HOST / MONGO_URL MongoDB connection
GH_CONTENT_TOKEN GitHub token for writing posts to the blog repo
SECRET JWT signing secret (webmention poller auth)