From 1ad3bae3fc99650f9d74401ecf4e0f3708f1f639 Mon Sep 17 00:00:00 2001 From: Sven Date: Wed, 18 Mar 2026 19:18:40 +0100 Subject: [PATCH] fix(patches): restore lost AP patches and fix broken patch chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- package.json | 4 +- scripts/patch-ap-allow-private-address.mjs | 60 ++++++++++---- .../patch-ap-object-url-trailing-slash.mjs | 81 +++++++++++++++++++ 3 files changed, 126 insertions(+), 19 deletions(-) create mode 100644 scripts/patch-ap-object-url-trailing-slash.mjs diff --git a/package.json b/package.json index c4a3e6e1..1e5f77fe 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "postinstall": "xattr -w com.apple.fileprovider.ignore#P 1 node_modules 2>/dev/null || true && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-micropub-ai-block-resync.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-endpoint-posts-prefill-url.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-endpoint-posts-search-tags.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-inbox-skip-view-activity-parse.mjs && node scripts/patch-inbox-ignore-view-activity.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-ap-normalize-nested-tags.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs", - "serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-micropub-ai-block-resync.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-microsub-reader-ap-dispatch.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-endpoint-posts-prefill-url.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-endpoint-posts-search-tags.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-inbox-skip-view-activity-parse.mjs && node scripts/patch-inbox-ignore-view-activity.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-ap-normalize-nested-tags.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs", + "postinstall": "xattr -w com.apple.fileprovider.ignore#P 1 node_modules 2>/dev/null || true && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-micropub-ai-block-resync.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-endpoint-posts-prefill-url.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-endpoint-posts-search-tags.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-ap-url-lookup-api.mjs && node scripts/patch-ap-object-url-trailing-slash.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-inbox-skip-view-activity-parse.mjs && node scripts/patch-inbox-ignore-view-activity.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-ap-normalize-nested-tags.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs", + "serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-micropub-ai-block-resync.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-microsub-reader-ap-dispatch.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-endpoint-posts-prefill-url.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-endpoint-posts-search-tags.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-ap-url-lookup-api.mjs && node scripts/patch-ap-object-url-trailing-slash.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-inbox-skip-view-activity-parse.mjs && node scripts/patch-inbox-ignore-view-activity.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-ap-normalize-nested-tags.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], diff --git a/scripts/patch-ap-allow-private-address.mjs b/scripts/patch-ap-allow-private-address.mjs index 292ec58d..60f7b0fb 100644 --- a/scripts/patch-ap-allow-private-address.mjs +++ b/scripts/patch-ap-allow-private-address.mjs @@ -32,16 +32,19 @@ const candidates = [ const MARKER = "// allow private address fix"; -const OLD_SNIPPET = ` const federation = createFederation({ +const patchSpecs = [ + // Case 1: signatureTimeWindow already present (from old patch or fork) + { + name: "with-signature-time-window", + oldSnippet: ` const federation = createFederation({ kv, queue, // Accept signatures up to 12 h old. // signature time window fix // Mastodon retries failed deliveries with the original signature, which // can be hours old by the time the delivery succeeds. signatureTimeWindow: { hours: 12 }, - });`; - -const NEW_SNIPPET = ` const federation = createFederation({ + });`, + newSnippet: ` const federation = createFederation({ kv, queue, // Accept signatures up to 12 h old. // signature time window fix @@ -52,7 +55,29 @@ const NEW_SNIPPET = ` const federation = createFederation({ // blog.giersig.eu resolves to 10.100.0.10 on the home LAN. Without this, // Fedify's SSRF guard blocks lookupObject() / WebFinger for own posts. allowPrivateAddress: true, - });`; + });`, + }, + // Case 2: fresh v2.10.1 without signatureTimeWindow — add both + { + name: "fresh-without-signature-time-window", + oldSnippet: ` const federation = createFederation({ + kv, + queue, + });`, + newSnippet: ` const federation = createFederation({ + kv, + queue, + // Accept signatures up to 12 h old. // signature time window fix + // Mastodon retries failed deliveries with the original signature, which + // can be hours old by the time the delivery succeeds. + signatureTimeWindow: { hours: 12 }, + // Allow fetching own-site URLs that resolve to private IPs. // allow private address fix + // blog.giersig.eu resolves to 10.100.0.10 on the home LAN. Without this, + // Fedify's SSRF guard blocks lookupObject() / WebFinger for own posts. + allowPrivateAddress: true, + });`, + }, +]; async function exists(filePath) { try { @@ -72,26 +97,27 @@ for (const filePath of candidates) { } checked += 1; - const source = await readFile(filePath, "utf8"); + let source = await readFile(filePath, "utf8"); if (source.includes(MARKER) || source.includes("allowPrivateAddress")) { continue; } - if (!source.includes(OLD_SNIPPET)) { - console.log(`[postinstall] patch-ap-allow-private-address: snippet not found in ${filePath} — skipping`); - continue; + let applied = false; + for (const spec of patchSpecs) { + if (!source.includes(spec.oldSnippet)) continue; + const updated = source.replace(spec.oldSnippet, spec.newSnippet); + if (updated === source) continue; + await writeFile(filePath, updated, "utf8"); + patched += 1; + applied = true; + console.log(`[postinstall] Applied patch-ap-allow-private-address (${spec.name}) to ${filePath}`); + break; } - const updated = source.replace(OLD_SNIPPET, NEW_SNIPPET); - - if (updated === source) { - continue; + if (!applied) { + console.log(`[postinstall] patch-ap-allow-private-address: no matching snippet in ${filePath} — skipping`); } - - await writeFile(filePath, updated, "utf8"); - patched += 1; - console.log(`[postinstall] Applied patch-ap-allow-private-address to ${filePath}`); } if (checked === 0) { diff --git a/scripts/patch-ap-object-url-trailing-slash.mjs b/scripts/patch-ap-object-url-trailing-slash.mjs new file mode 100644 index 00000000..e12d094b --- /dev/null +++ b/scripts/patch-ap-object-url-trailing-slash.mjs @@ -0,0 +1,81 @@ +/** + * Patch: make the Fedify object dispatcher's post lookup tolerate trailing-slash + * differences between the AP object URL and the stored post URL. + * + * Root cause: + * setupObjectDispatchers resolvePost() builds postUrl from the {+id} template + * variable (e.g. "replies/bd78a") and does an exact findOne() match against + * posts.properties.url. Posts in MongoDB are stored with a trailing slash + * ("https://blog.giersig.eu/replies/bd78a/"), but the AP object URL returned + * by the /api/ap-url lookup endpoint has no trailing slash. The exact match + * fails → Fedify returns 404 → remote instance shows "Could not connect". + * + * Fix: + * Replace the single-value findOne() with a $in query that tries both the + * bare URL and the URL with a trailing slash appended. + */ + +import { access, readFile, writeFile } from "node:fs/promises"; + +const candidates = [ + "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js", + "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js", +]; + +const MARKER = "// trailing-slash url fix"; + +const OLD_SNIPPET = ` const postUrl = \`\${publicationUrl.replace(/\\/$/, "")}/\${id}\`; + const post = await collections.posts.findOne({ "properties.url": postUrl });`; + +const NEW_SNIPPET = ` const postUrl = \`\${publicationUrl.replace(/\\/$/, "")}/\${id}\`; // trailing-slash url fix + const post = await collections.posts.findOne({ + "properties.url": { $in: [postUrl, postUrl + "/"] }, + });`; + +async function exists(filePath) { + try { + await access(filePath); + return true; + } catch { + return false; + } +} + +let checked = 0; +let patched = 0; + +for (const filePath of candidates) { + if (!(await exists(filePath))) { + continue; + } + + checked += 1; + const source = await readFile(filePath, "utf8"); + + if (source.includes(MARKER)) { + continue; + } + + if (!source.includes(OLD_SNIPPET)) { + console.log(`[postinstall] patch-ap-object-url-trailing-slash: snippet not found in ${filePath}`); + continue; + } + + const updated = source.replace(OLD_SNIPPET, NEW_SNIPPET); + + if (updated === source) { + continue; + } + + await writeFile(filePath, updated, "utf8"); + patched += 1; + console.log(`[postinstall] Applied patch-ap-object-url-trailing-slash to ${filePath}`); +} + +if (checked === 0) { + console.log("[postinstall] patch-ap-object-url-trailing-slash: no target files found"); +} else if (patched === 0) { + console.log("[postinstall] patch-ap-object-url-trailing-slash: already up to date"); +} else { + console.log(`[postinstall] patch-ap-object-url-trailing-slash: patched ${patched}/${checked} file(s)`); +}