172 Commits

Author SHA1 Message Date
Sven
55923be69c Fix AP inbox HTTP Signature verification failures by normalising host header
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m15s
Root cause of "Failed to verify the request's HTTP Signatures" errors:
patch-ap-federation-bridge-base-url fixed Fedify URL routing (using the
canonical publicationUrl to build the Request URL) but left the "host"
header in the Headers object untouched.

Fedify's HTTP Signature verifier reads request.headers.get("host") when
reconstructing the signed-string for Cavage-style signatures. If nginx
forwards an internal Host value (e.g. "10.100.0.20") instead of the public
hostname, the reconstructed string differs from what the remote server signed
→ every inbox POST fails with a cryptographic verification error → remote
servers receive 401, exhaust retries, and stop delivering.

Fix (patch-ap-signature-host-header):
After the header-copy loop in fromExpressRequest(), override "host" with
new URL(publicationUrl).host ("blog.giersig.eu") when publicationUrl is
provided. This ensures the signed-string Fedify reconstructs matches what
Mastodon/Pleroma/etc. signed, regardless of what nginx forwards.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 16:40:26 +02:00
Sven
8b1b5d990a Add AP inbox diagnostics: surface signature errors and request logging
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m20s
- patch-ap-inbox-delivery-debug: two fixes for diagnosing missing inbound
  AP interactions (likes, boosts, replies not appearing in notifications)

  Fix A (federation-setup.js): change ["fedify","federation","inbox"] log
  category from lowestLevel "fatal" → "error" so HTTP Signature verification
  failures are now visible in server logs instead of being silently swallowed.
  The original "fatal" level was hiding real delivery rejections (401s) that
  cause remote servers to stop retrying.

  Fix B (federation-bridge.js): add a pre-signature-check console.info for
  every inbox POST when AP_DEBUG=1 or AP_LOG_LEVEL=debug. Confirms whether
  remote servers are reaching our inbox at all (nginx/routing check).

- memory/project_activitypub.md: document full inbound activity pipeline,
  _publicationUrl dependency, body buffering, and how to use new diagnostics

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 16:32:06 +02:00
Sven
e791c06b79 feat: propagate Micropub deletes to ActivityPub and Bluesky
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m13s
When a post is deleted from the web backend (Micropub action=delete),
call each registered syndicator's delete() method so the post is also
removed from the Fediverse (AP Delete/Tombstone) and Bluesky
(com.atproto.repo.deleteRecord).

- patch-bluesky-syndicator-delete: adds Bluesky#deletePost(bskyUrl) to
  lib/bluesky.js and BlueskySyndicator#delete(url, syndication) to
  index.js; the bsky.app URL is resolved from the syndication array
  that postData.delete() preserves in _deletedProperties
- patch-micropub-delete-propagation: patches action.js case "delete"
  to iterate publication.syndicationTargets after postContent.delete()
  and fire syndicator.delete() fire-and-forget for any syndicator that
  exposes the method (errors logged, never break the 200 response)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 15:41:00 +02:00
Sven
63bc41ebb5 fix(activitypub): inbound replies/notifications broken — publicationUrl missing in inbox handlers
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m14s
collections._publicationUrl was never set, so every pubUrl guard in
handleCreate/handleAnnounce evaluated to undefined:
  - Reply notifications were never created (if pubUrl && ...) always false
  - Boost notifications for our content never created
  - Replies from non-followed accounts never stored in ap_timeline

Fix A: set collections._publicationUrl = publicationUrl before
registerInboxListeners() in federation-setup.js.

Fix B: in handleCreate, add an else-if branch that stores replies to
our own posts in ap_timeline even when the sender is not in ap_following,
so they appear in Mastodon client conversation/notification views.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 15:28:24 +02:00
Sven
12ee80f4a4 fix(activitypub): populate in_reply_to_id in Mastodon status serializer
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m11s
- status.js: in_reply_to_id was always null (both branches of ternary
  returned null — TODO left unfilled). Changed to item.inReplyToId || null.
- statuses.js POST handler: timeline insert now stores inReplyToId from
  the in_reply_to_id cursor the client already sent, so own replies are
  threaded correctly in Phanpy/Elk.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 15:01:55 +02:00
Sven
71a8b60d96 fix(activitypub) delete request - Added broadcastDelete to mastodonPluginOptions
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m13s
2026-04-01 14:46:56 +02:00
Sven
c0f847c96f fix(activitypub) dedup: query ap_activities for an existing outbound Create/Announce/Update
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m18s
2026-04-01 14:38:12 +02:00
Sven
2211b1d6f7 fix: activitypun fav/boost
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m14s
2026-04-01 14:27:45 +02:00
Sven
f28552a5b2 fix: activitypub -> time confusion created / published times
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m17s
2026-04-01 14:17:42 +02:00
Sven
bc661282c7 fix: del gitea dispatch
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 3m17s
2026-03-31 18:24:49 +02:00
Sven
5342cd1ff0 fix: skip workflow_dispatch for delete — Gitea DELETE commits trigger on:push
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m11s
Gitea Contents API DELETE commits fire on:push CI; POST/PUT do not.
delete was triggering both on:push and workflow_dispatch → 2 CI runs.
Now dispatch is skipped for delete; on:push handles the rebuild.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 17:19:34 +02:00
Sven
40ec8dbce3 fix: add Content-Type: application/json to store-github requests
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m15s
GitHub API infers JSON without Content-Type; Gitea requires it explicitly.
Without the header, Gitea cannot parse the POST/PUT body and returns 422
Unprocessable Entity on all content write operations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 17:09:15 +02:00
Sven
302316c8d0 fix: use POST for file create in Gitea Contents API
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m14s
Gitea's Contents API differs from GitHub's:
  POST /contents/{path} = create new file (no SHA)
  PUT  /contents/{path} = update existing file (SHA required)

store-github used PUT for createFile() because GitHub accepts PUT for
both — Gitea's PUT without SHA returns 422. Also updates the
update-fallback patch to bail to createFile() instead of falling through
to PUT-without-SHA when the file doesn't exist in the store.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 16:58:14 +02:00
Sven
53102a03b0 fix: fall back to create when updateFile gets 404 from Gitea
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m15s
Posts that exist in MongoDB but not in Gitea (e.g. due to a previous
failed write) caused HTTP 500 on re-publish: updateFile() tried to read
the file's SHA, got 404, and threw instead of creating. Now detects
Not Found and falls through to a create-style PUT (no sha field).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 16:37:53 +02:00
Sven
263e6e081a feat: dispatch Gitea workflow_dispatch after each Micropub action
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m15s
Gitea Contents API commits don't trigger on:push CI workflows.
Patches action.js to fire a workflow_dispatch to giersig.eu/indiekit-blog
after every create/update/delete/undelete so the Eleventy build runs
immediately after a post is published.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 16:24:39 +02:00
Sven
0bac69090d fix: rewrite gitea.giersig.eu to internal IP before npm install
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m15s
npm fetches git deps before node_modules exist, so git URL rewriting
must happen in preinstall. Detects jail env via INDIEKIT_BIND_HOST /
INTERNAL_FETCH_URL — no-ops on local dev.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 15:09:14 +02:00
Sven
2d5b713b7d chore: point svemagie fork deps and docs at Gitea
Some checks failed
Deploy Indiekit Server / deploy (push) Failing after 7s
- 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
Sven
6372a9f422 fix: startup and funkwhale listening 2026-03-31 13:01:53 +02:00
Sven
97d99976ea fix(ap): fix reply threading — pre-check AP syndication and resolve in_reply_to_id immediately
Two bugs caused replies-to-replies to be posted as 'note' type without
ActivityPub federation:

1. patch-ap-compose-default-checked: The AP reader compose form had
   defaultChecked hardcoded to '@rick@rmendes.net' (original dev's handle),
   so the AP syndication checkbox was never pre-checked. Fixed to use
   target.checked from the Micropub q=config response, which already
   carries checked: true for the AP syndicator.

2. patch-ap-mastodon-reply-threading: POST /api/v1/statuses deferred
   ap_timeline insertion until the Eleventy build webhook fired (30–120 s).
   If the user replied to their own new post before the build finished,
   findTimelineItemById returned null → inReplyTo = null → no in-reply-to
   in JF2 → post-type-discovery returned 'note' → reply saved at /notes/
   and sent without inReplyTo in the AP activity, breaking thread display
   on remote servers. Fixed by eagerly inserting the provisional timeline
   item immediately after postContent.create() ($setOnInsert — idempotent;
   syndicator upsert later is a no-op).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 08:12:43 +02:00
Sven
b4fc7ffb4f fix(bluesky): guard uploadMedia() against non-image HTTP responses
uploadMedia() had no content-type check, so an HTML login-redirect response
from an auth-protected internal endpoint was uploaded to Bluesky as a blob
with encoding "text/html". uploadBlob() accepts it, but record validation
rejects the post with 'Expected "image/*" (got "text/html")'.

The patch mirrors the guard already present in uploadImageFromUrl() and also
wraps per-photo uploads in try/catch so one bad photo doesn't abort the
entire syndication — other photos and the post text are still published.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 09:53:46 +02:00
Sven
4aa1554f3a fix(ap): patch federation-bridge to use publication URL as Fedify base URL
Without nginx forwarding Host/X-Forwarded-Proto headers, fromExpressRequest()
builds a wrong URL (e.g. http://127.0.0.1:3000/...) that Fedify doesn't
recognise as its own base URL — so it calls next() and requests fall through
to auth middleware, returning 302 to the login page. This breaks webfinger,
actor lookups, and AP inbox delivery.

The patch overrides the URL construction in createFedifyMiddleware() and
fromExpressRequest() to use the configured publicationUrl as the base,
bypassing the dependency on proxy headers entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 09:51:00 +02:00
Sven
bc76e25361 fix(ap): use photo attachment URL for OG image on photo posts (v2)
/og/{slug}.png is only generated by Eleventy for replies, bookmarks, and
articles — not for photo post types. Photo posts now use properties.photo[0]
directly as the ActivityPub image field; all other post types keep the
/og/{slug}.png fallback. Patch handles all known file states (original
upstream, v1 patched, already v2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 09:05:37 +02:00
Claude
f19f7e1741 fix(ap): correct webfinger patch snippet to match current fork (318720c)
The OLD_SNIPPET had wrong indentation (8 spaces vs 6) and was missing the
method guard line added in the fork:
  if (req.method !== "GET" && req.method !== "HEAD") return next();

Without this fix the patch silently skips patching and webfinger continues
to return 302 → 401 on fediverse delivery.

https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
2026-03-28 18:51:16 +00:00
Claude
ebf17341ee fix(ap): wire og-image and webfinger-before-auth patches into postinstall/serve
- Add scripts/patch-ap-og-image.mjs to both postinstall and serve so OG
  preview images are included in ActivityPub activities (flat URL slug
  extraction instead of date-based regex).
- Add scripts/patch-ap-webfinger-before-auth.mjs (new file) to both
  postinstall and serve. Extends contentNegotiationRoutes Fedify delegation
  to also forward /.well-known/* paths, ensuring webfinger is served by
  Fedify before indiekit auth middleware can issue a 302 redirect. Fixes
  401 Unauthorized errors from remote fediverse instances.

https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
2026-03-28 18:25:59 +00:00
svemagie
034e944c9d Merge branch 'main' into claude/fix-activitypub-og-image-CrCGI 2026-03-27 20:00:25 +01:00
Sven
0cc5187990 fix(media-browser): fix mixed-content error causing 'Browse media' to fail
`getEndpointUrls()` resolved relative endpoint paths (e.g. `/media`) using
`getUrl(request)`, which returns `http://` because Express sees HTTP from nginx
without trust proxy. This produced `http://blog.giersig.eu/media` as the
endpoint attribute in the file-input component, causing Safari to block the
fetch as mixed content ('Load failed').

Fix: prefer `application.url` (the configured HTTPS base URL) over
`getUrl(request)` when resolving relative endpoint paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 10:29:02 +01:00
Sven
02f7db46e1 fix(media): fix image upload size limit and session token bug
- nginx: add client_max_body_size 20M to blog vhost (default 1MB was
  silently killing uploads, returning 413 as a JSON parse error in UI)
- patch: fix session.token → session.access_token in micropub action
  controller; caused Bearer undefined on internal /media fetch, giving
  500s for Micropub clients that upload files directly (OwnYourSwarm)
- npmrc: add sharp_from_source=true to survive future npm install on
  FreeBSD without breaking sharp's native bindings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 10:13:30 +01:00
Sven
36c7f33a34 feat(changelog): add chore and refactor commit categories
The patch now recognises chore: and refactor: commit prefixes as their
own categories instead of lumping them into "other".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 10:05:04 +01:00
Sven
18a946c9ea chore(patches): remove 10 obsolete AP patches now baked into fork
All of the following are now native in svemagie/indiekit-endpoint-activitypub:
- patch-ap-url-lookup-api (AP URL lookup endpoint)
- patch-ap-allow-private-address (allowPrivateAddress in federation-setup)
- patch-ap-like-note-dispatcher (fake-Note revert)
- patch-ap-like-activity-id (canonical Like activity id URI)
- patch-ap-like-activity-dispatcher (Like setObjectDispatcher)
- patch-ap-url-lookup-api-like (likeOf URL in /api/ap-url)
- patch-ap-remove-federation-diag (inbox diagnostic log removed)
- patch-ap-og-image (orphan, not in package.json)
- patch-ap-normalize-nested-tags (orphan, no-op)
- patch-ap-object-url-trailing-slash (orphan, no-op)

patch-ap-skip-draft-syndication kept — draft guard in syndicate() not yet in fork.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 20:21:26 +01:00
Sven
81abaee160 chore(patches): remove patch-ap-repost-commentary (baked into fork)
Repost commentary changes are now native in svemagie/indiekit-endpoint-activitypub.
Patch is no longer needed and was causing a duplicate repost block on every deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 20:16:22 +01:00
Sven
dc850c350f fix(patches): update repost-commentary patch to detect native fork implementation
Fix D's old snippet (the bare `} else {` content block) still matched after
the fork absorbed the repost-commentary changes natively, causing a duplicate
`} else if (postType === "repost") {` block to be inserted on every deploy
and preventing startup. Added NATIVE_MARKER check for the fork's own repost
branch so the patch skips cleanly when the changes are already present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 20:13:48 +01:00
Sven
4b1dfa20be fix(patches): update federation-diag silence patch for new fork version
The fork added an Accept-header upgrade block between the diagnostic
log and the return statement, breaking the OLD_SNIPPET match. Patch
now handles both the original form and the updated form.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 17:26:12 +01:00
Sven
67eacf6b99 fix(webmention): livefetch v6 with diagnostic log + reset-stale v11
livefetch v6:
- Adds console.log showing which property links were built per post
  (e.g. "in-reply-to" for replies) — makes it debuggable without server access
- Fixes retryPatchedBlock to include the two comment lines the retry patch
  actually inserts (was missing them, causing "Target block not found" on
  fresh upstream → retry → livefetch path)
- Adds v5 to priorMarkersNoContinue with contentToProcess-line end detection
  so v5 → v6 in-place upgrade works correctly

reset-stale: bump to v11 to retry ca3d8 and any other posts stuck
before v5/v6 deployment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 11:40:57 +01:00
Sven
2a674c8eea feat(draft): prevent draft posts from being syndicated or federated
Add two new patches:
- patch-ap-skip-draft-syndication: guards the AP syndicator's syndicate()
  method against draft posts (mirrors existing unlisted visibility check)
- patch-microsub-compose-draft-guard: forwards post-status from microsub
  compose to Micropub and suppresses mp-syndicate-to targets for drafts

The syndicate endpoint DB queries already filter post-status != draft
(patch-federation-unlisted-guards). These patches add defence in depth
at the AP syndicator and at the microsub compose submission layer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 11:10:23 +01:00
Claude
ad5e6b2235 fix(ap): use slug-only OG image path /og/{slug}.png
The previous fix incorrectly generated /og/{year}-{month}-{day}-{slug}.png
but the Eleventy blog generates OG images at /og/{slug}.png (e.g. /og/2615b.png).
Remove the unnecessary date extraction and simplify to slug-only.

https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
2026-03-21 20:19:27 +00:00
Claude
9511b96a89 fix(ap): fix OG image not included in ActivityPub activities
Root cause: jf2-to-as2.js (both 842fc5af and 45f8ba9) uses a regex
expecting date-based URLs like /articles/YYYY/MM/DD/slug/ to extract
the post date for the OG image filename. This blog uses flat URLs like
/articles/slug/ so the regex never matches — no `image` property is
set on Note/Article ActivityPub objects and fediverse clients show no
preview card.

Fix:
- Add scripts/patch-ap-og-image.mjs: replaces the URL-pattern regex with
  slug-from-last-path-segment + date-from-properties.published, producing
  /og/{year}-{month}-{day}-{slug}.png matching the Eleventy OG filenames.
  Handles both 842fc5af (slug-only URL) and 45f8ba9 (date+slug) variants.
  Applied to both jf2ToActivityStreams() and jf2ToAS2Activity().
- Register patch in package.json postinstall and serve after patch-ap-repost-commentary.
- Bump package-lock.json to 45f8ba9 fork commit (correct date+slug filename
  format, likes-as-bookmarks, announce cc reverted).

https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
2026-03-21 20:18:09 +00:00
svemagie
58478f1885 Merge branch 'main' into claude/fix-activitypub-og-image-CrCGI 2026-03-21 21:16:59 +01:00
Claude
769720b33f fix(ap): use slug-only OG image path /og/{slug}.png
The previous fix incorrectly generated /og/{year}-{month}-{day}-{slug}.png
but the Eleventy blog generates OG images at /og/{slug}.png (e.g. /og/2615b.png).
Remove the unnecessary date extraction and simplify to slug-only.

https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
2026-03-21 20:04:19 +00:00
Sven
c0b9878033 fix(webmention): silence retry noise for all livefetch versions, bump stale migration to v10
retry patch: regex now matches [patched:livefetch] and [patched:livefetch:vN]
so it silently skips for any livefetch version, not just v2.

reset-stale: bump to v10 to retry posts stuck during v5 rollout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 20:56:02 +01:00
Sven
2548825793 chore: silence github contribution log 2026-03-21 17:31:01 +01:00
Sven
109d39dd25 fix(activitypub): remove federation-diag inbox logging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 15:47:23 +01:00
Sven
535e6f5e9c fix(activitypub): add Like vocab import in activity dispatcher patch
On fresh installs where the old wrong patch was never applied, Like was
absent from the @fedify/fedify/vocab imports, causing a ReferenceError
at startup. The dispatcher patch now adds Like to the import block if
missing, making it self-contained and install-order independent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:05:34 +01:00
Sven
99d2e38066 fix(activitypub): serve AP-likes with canonical id and proper Like dispatcher
Replace the semantically incorrect fake-Note approach with strict AP protocol compliance:

- patch-ap-like-note-dispatcher: rewritten to revert the fake-Note block
- patch-ap-like-activity-id: adds canonical id URI to Like activities (AP §6.2.1)
- patch-ap-like-activity-dispatcher: registers setObjectDispatcher(Like, ...) so
  /activitypub/activities/like/{id} is dereferenceable (AP §3.1)
- patch-ap-url-lookup-api-like: /api/ap-url now returns the likeOf URL for AP-likes
  so the "Also on: Fediverse" widget's authorize_interaction flow opens the
  original Mastodon post on the remote instance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 08:58:15 +01:00
Sven
34d5fde54d fix(syndicate): normalize syndication property to array before dedup check
Micropub's replaceEntries() stores single-value arrays as plain strings
(JF2 normalization). Spreading a string into [...str] gives individual
characters, so hasSyndicationUrl() never matches existing syndication URLs
and alreadySyndicated is always false — causing re-syndication on every
webhook trigger.

Fix: use [].concat() which safely handles both string and array values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 07:49:18 +01:00
Sven
396bd5ab6f chore: del script 2026-03-20 22:34:59 +01:00
Sven
17b93b3a2a fix(webmention): build synthetic h-entry from stored properties, drop live fetch
Root cause: blog.giersig.eu DNS resolves internally to 10.100.0.10 (the
indiekit admin nginx), which returns the login page for post URLs of
certain types (notes, photos, replies). Live page fetching is inherently
unreliable in this split-DNS / jailed setup.

The fix: indiekit already stores all microformat target URLs in MongoDB
(in-reply-to, like-of, bookmark-of, repost-of) and content.html has
inline links. We can build a synthetic h-entry HTML snippet directly
from post.properties — no network fetch required for the source post.

Bumps livefetch patch to v5:
- Replace live page fetch with synthetic HTML built from post.properties
- Handles string values, mf2 objects ({properties.url[0]}), and plain
  value strings for each microformat property
- Simplifies patch script: single full-block replacement handles all
  prior versions (v1–v4) via marker detection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 22:23:30 +01:00
Sven
7f9f02bc36 fix(webmention): fetch live pages from public URL, not INTERNAL_FETCH_URL
Root cause: INTERNAL_FETCH_URL (10.100.0.10) points to the nginx reverse
proxy in front of indiekit's admin interface. Post URLs like /bookmarks/…
require authentication there, so the fetch returned the login page
("Anmelden - Indiekit") which has no .h-entry.

The blog HTML is served by an external host (GitHub Pages), reachable
from the jail over the public URL. INTERNAL_FETCH_URL should only be
used for indiekit API calls, not for fetching blog post pages.

Bumps livefetch patch to v4:
- Remove INTERNAL_FETCH_URL rewrite for live page fetches
- Fetch from postUrl (public URL) directly by default
- Add WEBMENTION_LIVEFETCH_URL env var as opt-in override for setups
  where a local static server can serve blog pages faster
- Add v3→v4 in-place upgrade logic to the patch script

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 22:04:10 +01:00
Claude
1cf5b01788 fix(ap): fix OG image not included in ActivityPub activities
Root cause: jf2-to-as2.js (both 842fc5af and 45f8ba9) uses a regex
expecting date-based URLs like /articles/YYYY/MM/DD/slug/ to extract
the post date for the OG image filename. This blog uses flat URLs like
/articles/slug/ so the regex never matches — no `image` property is
set on Note/Article ActivityPub objects and fediverse clients show no
preview card.

Fix:
- Add scripts/patch-ap-og-image.mjs: replaces the URL-pattern regex with
  slug-from-last-path-segment + date-from-properties.published, producing
  /og/{year}-{month}-{day}-{slug}.png matching the Eleventy OG filenames.
  Handles both 842fc5af (slug-only URL) and 45f8ba9 (date+slug) variants.
  Applied to both jf2ToActivityStreams() and jf2ToAS2Activity().
- Register patch in package.json postinstall and serve after patch-ap-repost-commentary.
- Bump package-lock.json to 45f8ba9 fork commit (correct date+slug filename
  format, likes-as-bookmarks, announce cc reverted).

https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
2026-03-20 14:39:21 +00:00
Sven
11d600058d fix(webmention): send Host header on internal fetches, add fetchUrl diagnostics
Without a Host header, nginx routes internal fetches to the wrong vhost
(sees the internal IP as Host), returning a page with no .h-entry and
causing all posts to retry indefinitely.

Bumps livefetch patch to v3:
- Sends `Host: blog.giersig.eu` when fetching via internal URL so nginx
  routes to the correct virtual host
- Logs the actual fetchUrl on every internal fetch
- Logs the first 200 chars of the response body when h-entry check fails
  so the root cause (wrong vhost, indiekit page, etc.) is visible
- Adds v2→v3 in-place upgrade logic to the patch script

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 14:48:58 +01:00
Sven
750267b175 fix: removed double quote on "h-entry\"" 2026-03-20 14:45:35 +01:00