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>
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>
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>
The Gitea conflict resolution kept main's prom-client/metrics-shim/microsub
changes but dropped our two new AP patch registrations. Re-add them to both
postinstall and serve.
https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX
Preloads metrics-shim.cjs via `node --require` into the Indiekit process
so heap, GC, event loop lag, CPU and handle metrics are exposed at
:9209/metrics for Prometheus scraping. Uses prom-client collectDefaultMetrics.
- Add metrics-shim.cjs (prom-client HTTP server, port 9209)
- Add prom-client ^15.1.3 to dependencies
- Wire --require ./metrics-shim.cjs into start.example.sh and npm serve script
- Grafana: NodeJS Application Dashboard (11159) at console.giersig.eu
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`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>
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>
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>
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>
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>
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>
- Remove patch-preset-eleventy-ai-frontmatter: upstream now writes AI
frontmatter natively using hyphenated keys (ai-text-level etc.)
- Remove patch-endpoint-posts-ai-cleanup: upstream beta.44 natively
removes empty ai-text-level/ai-code-level/ai-tools/ai-description fields
- Remove patch-endpoint-posts-ai-fields: upstream beta.44 has AI form UI
inline in post-form.njk; our separate templates would have duplicated fields
- Remove patch-micropub-ai-block-resync: one-time stale-block migration,
no longer relevant
- Remove patch-endpoint-posts-prefill-url: upstream beta.44 has native
prefill from query params; our patch would have conflicted
- Remove patch-endpoint-posts-search-tags: upstream beta.44 has native
search/filter/sort UI; patch already detected this and was a no-op
- Bump @rmdes/indiekit-endpoint-posts beta.25→beta.44,
override beta.41→beta.44
- Update indiekit.config.mjs: remove camelCase ai field names from
all postTypes.fields (ai-* fields now rendered inline by upstream)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reposts with a body (commentary) were silently broken in two ways:
1. jf2ToAS2Activity() always emitted a bare Announce pointing at the
external URL (e.g. fromjason.xyz). That URL doesn't serve AP JSON,
so Mastodon couldn't fetch the object and dropped the activity from
followers' timelines — the post only appeared when explicitly searched.
2. jf2ToActivityStreams() (content negotiation / search) hard-coded the
Note content to just '🔁 <url>', completely ignoring properties.content.
Fix via patch-ap-repost-commentary.mjs (4 targeted replacements):
- jf2ToAS2Activity(): skip the Announce early-return when commentary is
present and fall through to the existing Create(Note) path instead.
Pure reposts (no body) keep the Announce behaviour unchanged.
- jf2ToAS2Activity() content block: add a repost branch that formats
the Note as '<commentary><br><br>🔁 <url>' (mirrors bookmark/like).
- jf2ToActivityStreams(): extract commentary and prepend it to the Note
content when present.
Patch registered in both postinstall and serve chains.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds patch-webmention-sender-hentry-syntax.mjs to fix a typo in
@rmdes/indiekit-endpoint-webmention-sender@1.0.8 that prevents the
module from loading:
_html.includes("h-entry"") → _html.includes("h-entry")
The extra closing quote causes a SyntaxError at startup, which means
the webmention sender never runs and the background sync never starts.
Patch runs before the other webmention-sender patches in both
postinstall and serve so the file is valid JS before further transforms.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Like and Announce activities were missing the followers collection in
their to/cc addressing. Mastodon shared inboxes silently drop activities
without cc:followers, so likes and reposts were delivered but never
appeared on remote instances.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch @rmdes/indiekit-endpoint-youtube to forked repo with OAuth 2.0
liked-videos sync. Add OAuth client config and likes sync settings.
Also document outgoing webmentions architecture in README.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The signatureTimeWindow patch was deleted in e52e98c5c (assumed fixed
in fork), but the lockfile still pins the fork to v2.10.1 which lacks
it. This broke the patch-ap-allow-private-address patch chain: it
expected signatureTimeWindow in its OLD_SNIPPET, never matched, and
silently skipped — leaving the server without both signatureTimeWindow
AND allowPrivateAddress. Without allowPrivateAddress, Fedify's SSRF
guard blocks own-site URL resolution (blog.giersig.eu → 10.100.0.10),
breaking federation delivery.
- Fix patch-ap-allow-private-address to handle fresh v2.10.1 (adds
both signatureTimeWindow and allowPrivateAddress in one step)
- Restore patch-ap-object-url-trailing-slash (also lost in e52e98c5c)
- Add both patches to postinstall and serve scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Retry behavior is already covered by the livefetch patch, but keeping
this patch ensures the skip-on-failure guard applies even if livefetch
is removed or the upstream code changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a post was processed but had no discoverable external links, the
expanded detail row rendered completely blank — result.details was
truthy ({}) so the 'noDetails' fallback never fired, but all three
arrays were empty so no tables rendered either.
Adds a patch script for the template that shows "No external links
discovered in this post." in that case, and wires it into both
postinstall and serve scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
uploadMedia, uploadImageFromUrl, and fetchOpenGraphData all fetch from
the blog's public URL which is unreachable behind the nginx jail. Rewrite
own-domain URLs to http://localhost:PORT, same as micropub-fetch-internal-url.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The webmention sender was using stored post content (just the body text)
instead of the live page, missing template-rendered links like
u-in-reply-to, u-like-of, u-bookmark-of. This caused reply/like/bookmark
posts to be marked as sent with 0 webmentions. Bump reset-stale migration
to v4 so affected posts are retried.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Node can't reach its own public HTTPS URL (ECONNREFUSED 127.0.0.1:443)
because port 443 only exists on the nginx jail. Rewrite self-referential
fetch URLs to http://localhost:3000 in endpoint-posts, endpoint-syndicate,
and endpoint-share.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Re-add PeerTube View activity patches that were prematurely removed in
e52e98c5c — the upstream fork doesn't reliably include these fixes on
all server deployments, causing noisy "Unsupported activity type" errors.
Also add fetch diagnostic patch to surface the real cause of
"TypeError: fetch failed" when posting articles via the form controller.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tags like `on/art/music` are reduced to their last segment (`#music`)
in both buildPlainTags and buildFedifyTags so ActivityPub hashtags are
valid on Mastodon and other platforms.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
blog.giersig.eu resolves to 10.100.0.10 (private LAN) from the server,
causing Fedify's SSRF guard to block lookupObject() and WebFinger calls
for own posts when processing incoming ActivityPub activities.
Adds patch-ap-allow-private-address.mjs which sets allowPrivateAddress: true
on createFederation(), wired into both postinstall and serve scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Before the raw-body digest fix, every Mastodon inbox delivery was
rejected with a Digest mismatch. Mastodon queued those activities for
retry. After the digest fix, the retried deliveries arrive with their
original HTTP Signatures which are now > 1 hour old. Fedify's default
signatureTimeWindow: { hours: 1 } rejects them with "Date is too far
in the past", logged as "Failed to verify the request's HTTP Signatures."
Extending to 12 hours allows those retried deliveries to be accepted.
The signature still must be cryptographically valid — only the replay
window is relaxed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Fedify object dispatcher constructs the post lookup URL from the
{+id} path variable (e.g. "replies/bd78a"), which has no trailing slash.
Posts in MongoDB store their URL with a trailing slash, so the exact
findOne() match was silently returning null → Fedify serving 404 →
mountains.social showing "Could not connect to the given address".
Fix uses $in to try both variants so the dispatcher works regardless
of whether the request URL has a trailing slash or not.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- patch-ap-inbox-raw-body-digest: preserve raw request bytes through the
AP inbox buffer guard so Fedify's HTTP Signature Digest verification
passes (JSON.stringify re-encoding broke SHA-256 digest check, causing
Mastodon likes/replies/boosts to be silently rejected)
- patch-ap-url-lookup-api: add GET /activitypub/api/ap-url endpoint that
maps a blog post URL to its Fedify-served AP object URL, enabling
reliable content negotiation for authorize_interaction redirects
- wire both patches into postinstall and serve scripts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the backend Syndicate button is pressed on a post with no
mp-syndicate-to and no prior syndication URLs, fall back to targets
with checked:true (e.g. ActivityPub) instead of no-oping.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- patch-endpoint-micropub-source-filter: support ?category= and ?search=
query params in the Micropub ?q=source endpoint, filtering MongoDB
documents by properties.category and a case-insensitive regex across
name/content fields
- patch-endpoint-posts-search-tags: forward category/search params from
the posts controller to Micropub, expose tagLinks on each item, and
replace the posts.njk cardGrid with a custom loop that renders clickable
tag chips and a search form above the grid
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace `contentRoot ?? $` with `contentRoot ?? $.root()` in webmention.js.
When a post has no .h-entry, <article>, or <main>, contentRoot is null and
`$` is a Cheerio constructor function — not a Cheerio object — so .find()
throws "scope.find is not a function". $.root() returns the document root
as a proper Cheerio object that supports .find().
Rewrites patch-webmention-sender-content-scope.mjs for the new 1.0.7 package
shape (content-scope logic already baked in, only the $.root() fix needed)
and registers it in postinstall + serve.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blog is now a native ActivityPub actor; Mastodon syndication via
troet.cafe is no longer needed. Removes the syndicator package,
config vars, patch script, and env example entries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The .on(View) inbox handler from the previous commit is never reached
because Fedify crashes parsing PeerTube's View activity before dispatch —
its JSON-LD deserialiser doesn't recognise Schema.org InteractionCounter,
throwing "Failed to parse activity: TypeError: Invalid type".
Add a guard at the top of createFedifyMiddleware that short-circuits any
POST with body.type === "View" and returns 200 immediately, bypassing
federation.fetch() entirely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PeerTube broadcasts non-standard View (WatchAction) activities to all
followers on every video watch. Fedify has no handler for this type,
causing noisy "Unsupported activity type" errors in the federation log.
Adds a no-op .on(View, ...) handler at the end of the inbox listener
chain via the existing patch script mechanism.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>