svemagie 5df7314f8d chore: integrate AP patches into fork — remove 3 scripts, trim federation-unlisted-guards
Deleted scripts (logic now built into the fork):
- patch-endpoint-activitypub-docloader-loglevel.mjs
- patch-endpoint-activitypub-migrate-alias-clear.mjs
- patch-endpoint-activitypub-like-boost-methods.mjs

Trimmed patch-federation-unlisted-guards.mjs to only cover
endpoint-syndicate (separate package). AP unlisted guards are
now in the fork's federation-setup.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 08:07:36 +01:00

Indieweb/kit Blog Server

Admin login

  • The IndieKit admin uses root auth/session paths (for example: /session/login, /auth, /auth/new-password).
  • Login uses PASSWORD_SECRET (bcrypt hash), not INDIEKIT_PASSWORD.
  • If no PASSWORD_SECRET exists yet, open /auth/new-password once to generate it.
  • If login is blocked because PASSWORD_SECRET is missing/invalid, set INDIEKIT_ALLOW_PASSWORD_SETUP=1 temporarily, restart, generate a new hash via /auth/new-password, set PASSWORD_SECRET to that hash, then remove INDIEKIT_ALLOW_PASSWORD_SETUP.
  • If login appears passwordless, first check for an existing authenticated session cookie. Use /session/logout to force a fresh login challenge.
  • Upstream IndieKit auto-authenticates in dev mode (NODE_ENV=development). This repository patches that behavior so dev auto-auth only works when INDIEKIT_ALLOW_DEV_AUTH=1 is explicitly set.
  • Production startup now fails closed when auth/session settings are unsafe (NODE_ENV not production, INDIEKIT_ALLOW_DEV_AUTH=1, weak SECRET, missing/invalid PASSWORD_SECRET, or empty-password hash).
  • Post management UI should use /posts (@indiekit/endpoint-posts.mountPath).
  • Do not set post-management mountPath to frontend routes like /blog, otherwise backend publishing can be shadowed by the public site.

Backend endpoints

  • Configured endpoint mount paths:
  • Posts management: /posts
  • Files: /files
  • Webmentions moderation + API: /webmentions
  • Webmentions proxy API: /webmentions-api
  • Webmention sender + API: /webmention-sender
  • Homepage builder UI + API: /homepage
  • Conversations + API: /conversations
  • GitHub activity + API: /github
  • Funkwhale activity + API: /funkwhale
  • Last.fm activity + API: /lastfmapi
  • Podroll dashboard + API: /podrollapi
  • ActivityPub federation + admin reader: /activitypub
  • ActivityPub discovery: /.well-known/webfinger, /nodeinfo/2.1

MongoDB

  • Preferred: set MONGO_USERNAME and MONGO_PASSWORD explicitly; config builds the URL from MONGO_USERNAME, MONGO_PASSWORD, MONGO_HOST, MONGO_PORT, MONGO_DATABASE, MONGO_AUTH_SOURCE.
  • You can still use a full MONGO_URL (example: mongodb://user:pass@host:27017/indiekit?authSource=admin).
  • If both MONGO_URL and MONGO_USERNAME/MONGO_PASSWORD are set, decomposed credentials take precedence by default to avoid stale URL mismatches. Set MONGO_PREFER_URL=1 to force MONGO_URL precedence.
  • Startup scripts now fail fast when MONGO_URL is absent and MONGO_USERNAME is missing, to avoid silent auth mismatches.
  • Startup now runs scripts/preflight-mongo-connection.mjs before boot. Preflight is strict by default and aborts start on Mongo auth/connect failures; set REQUIRE_MONGO=0 to bypass strict mode intentionally.
  • For MongoServerError: Authentication failed, first verify MONGO_PASSWORD, then try MONGO_AUTH_SOURCE=admin.

Content paths

  • This setup writes post files to the content repo blog under content/.
  • Photo upload binaries are written to images/{filename} and published at ${PUBLICATION_URL}/images/{filename}.
  • Current paths in publication.postTypes are:
  • content/articles/{slug}.md
  • content/notes/{slug}.md
  • content/bookmarks/{slug}.md
  • content/likes/{slug}.md
  • content/photos/{slug}.md
  • content/replies/{slug}.md
  • content/pages/{slug}.md
  • If these paths do not match the content repo structure, edit/delete actions can fail with GitHub Not Found.
  • Reposts are configured as a dedicated post type (repost) and stored at content/reposts/{slug}.md.

Post URLs

  • Current post URLs in publication.postTypes are:
  • https://blog.giersig.eu/articles/{slug}/
  • https://blog.giersig.eu/notes/{slug}/
  • https://blog.giersig.eu/bookmarks/{slug}/
  • https://blog.giersig.eu/likes/{slug}/
  • https://blog.giersig.eu/photos/{slug}/
  • https://blog.giersig.eu/replies/{slug}/
  • https://blog.giersig.eu/{slug}/ (page post type)

GitHub tokens

  • Recommended for two-repo setups:
  • GH_CONTENT_TOKEN: token for content repo (blog), used by @indiekit/store-github.
  • GH_ACTIVITY_TOKEN: token for GitHub dashboard/activity endpoint, used by @rmdes/indiekit-endpoint-github.
  • GITHUB_USERNAME: GitHub user/owner name.
  • Backward compatibility: if GH_CONTENT_TOKEN or GH_ACTIVITY_TOKEN are not set, config falls back to GITHUB_TOKEN.

Listening tokens

  • Funkwhale endpoint requirements:
  • FUNKWHALE_INSTANCE (for example https://your-funkwhale.example, root server URL only)
  • FUNKWHALE_USERNAME
  • FUNKWHALE_TOKEN (read API token)
  • Last.fm endpoint requirements:
  • LASTFM_API_KEY
  • LASTFM_USERNAME
  • Listening endpoint plugins target Node.js 20+; older runtimes can produce inconsistent fetch/JSON behavior.
  • If FUNKWHALE_INSTANCE points to a host that does not expose Funkwhale's API routes, API responses now degrade to empty data instead of repeated 500 errors.
  • If these variables are missing, the endpoints still exist but return empty activity until credentials are configured.

Podroll endpoint

  • Podroll endpoint is enabled via @rmdes/indiekit-endpoint-podroll and mounted at /podrollapi by default.
  • Optional environment variables:
  • PODROLL_MOUNT_PATH (default /podrollapi)
  • PODROLL_EPISODES_URL (FreshRSS greader endpoint URL used for episode sync)
  • PODROLL_OPML_URL (FreshRSS OPML export URL used for podcast source sync)
  • If PODROLL_EPISODES_URL and PODROLL_OPML_URL are not set, the endpoint still loads and can be configured from its admin dashboard.

Webmention sender

  • Webmention sender endpoint is enabled via @rmdes/indiekit-endpoint-webmention-sender and mounted at /webmention-sender by default.
  • Optional environment variables:
  • WEBMENTION_SENDER_MOUNT_PATH (default /webmention-sender)
  • WEBMENTION_SENDER_TIMEOUT (default 10000, endpoint discovery timeout in milliseconds)
  • WEBMENTION_SENDER_USER_AGENT (default ${SITE_NAME} Webmention Sender)
  • Startup polling loop variables (used by start.example.sh):
  • WEBMENTION_SENDER_AUTO_POLL (default 1, set 0 to disable)
  • WEBMENTION_SENDER_POLL_INTERVAL (default 300, seconds)
  • WEBMENTION_SENDER_HOST (default 127.0.0.1)
  • WEBMENTION_SENDER_PORT (default ${PORT} or 3000)
  • WEBMENTION_SENDER_ORIGIN (optional JWT me claim override, defaults PUBLICATION_URL -> SITE_URL)
  • WEBMENTION_SENDER_ENDPOINT (optional full URL override)
  • POST /webmention-sender requires authentication (update scope) and sends pending webmentions for unpublished targets.

Webmentions proxy

  • Webmentions proxy endpoint is enabled via @rmdes/indiekit-endpoint-webmentions-proxy and mounted at /webmentions-api by default.
  • Optional environment variables:
  • WEBMENTIONS_PROXY_MOUNT_PATH (default /webmentions-api)
  • WEBMENTIONS_PROXY_CACHE_TTL (default 60, cache TTL in seconds)
  • Uses existing WEBMENTION_IO_TOKEN and WEBMENTION_IO_DOMAIN configuration for upstream webmention.io requests.
  • Public JSON API route: GET /webmentions-api/api/mentions (supports page, per-page, target, wm-property query parameters).

ActivityPub

  • ActivityPub federation is enabled via @rmdes/indiekit-endpoint-activitypub.
  • Actor handle resolution order is: AP_HANDLE, then ACTIVITYPUB_HANDLE, then GITHUB_USERNAME, then publication hostname first label.
  • Actor profile seed values come from AUTHOR_NAME, AUTHOR_BIO, AUTHOR_AVATAR, and SITE_DESCRIPTION.
  • AUTHOR_AVATAR can be absolute (https://...) or slash-relative (/images/avatar.jpg); startup normalizes it to an absolute URL.
  • Optional ActivityPub variables:
  • AP_ALSO_KNOWN_AS (Mastodon migration alias URL)
  • AP_LOG_LEVEL (debug|info|warning|error|fatal, default info)
  • AP_DEBUG (1 or true enables debug dashboard)
  • AP_DEBUG_PASSWORD (required when debug dashboard is enabled)
  • REDIS_URL (recommended for production delivery queue durability)
  • Startup preflight scripts/preflight-activitypub-rsa-key.mjs ensures ap_keys contains a usable RSA key pair (publicKeyPem + privateKeyPem) so outgoing inbox deliveries are HTTP-signed and not rejected with Request not signed.
  • Startup preflight scripts/preflight-activitypub-profile-urls.mjs normalizes existing ActivityPub profile URL fields in MongoDB (url, icon, image, alsoKnownAs) so WebFinger/actor responses do not fail on invalid URL values.
  • The ActivityPub private-url docloader patch (scripts/patch-endpoint-activitypub-private-url-docloader.mjs) allows Fedify lookups for your own publication hostname when split-horizon DNS resolves it to a private jail IP.
  • The ActivityPub locale patch creates/repairs locales/de.json from locales/en.json so backend UI keys do not render as raw activitypub.* translation strings when SITE_LOCALE=de.
  • Quick verification commands:
  • curl -s "https://blog.giersig.eu/.well-known/webfinger?resource=acct:<handle>@blog.giersig.eu" | jq .
  • curl -s -H "Accept: application/activity+json" "https://blog.giersig.eu/" | jq .
  • curl -s "https://blog.giersig.eu/nodeinfo/2.1" | jq .
  • If a reverse proxy serves static HTML, ensure AP requests are proxied to Indiekit for /activitypub*, /.well-known/*, /nodeinfo/*, and content-negotiated Accept: application/activity+json / application/ld+json requests on / and post URLs.

Startup script

  • start.sh is intentionally ignored by Git (.gitignore) so server secrets are not committed.
  • Use start.example.sh as the tracked template and keep real credentials in environment variables (or .env on the server).
  • Startup scripts parse .env with the dotenv parser (not shell source), so values containing spaces are handled safely.
  • start.example.sh includes an optional background webmention sender polling loop for bare-metal deployments (including FreeBSD jails).
  • For FreeBSD service management, use indiekit.rcd.example as a template for /usr/local/etc/rc.d/indiekit.
  • Important: do not use daemon -r in the rc.d command args. Let service indiekit restart control restart behavior; -r can keep the supervisor alive during stop/restart.
  • The rc.d template uses daemon supervisor pidfile -P (and child pidfile -p) and supports indiekit_stop_timeout in rc.conf (default 20 seconds).
  • FreeBSD rc.d install example:
install -m 0555 /usr/local/indiekit/indiekit.rcd.example /usr/local/etc/rc.d/indiekit
sysrc indiekit_enable=YES
service indiekit restart
  • FreeBSD jail env example for auto-send polling:
SITE_URL=https://blog.example.net
PORT=3000

WEBMENTION_SENDER_AUTO_POLL=1
WEBMENTION_SENDER_POLL_INTERVAL=300
WEBMENTION_SENDER_HOST=127.0.0.1
WEBMENTION_SENDER_PORT=3000
WEBMENTION_SENDER_MOUNT_PATH=/webmention-sender

# Optional overrides
# WEBMENTION_SENDER_ORIGIN=https://blog.example.net
# WEBMENTION_SENDER_ENDPOINT=http://127.0.0.1:3000/webmention-sender
  • Startup scripts run preflight + patch helpers before boot (scripts/preflight-production-security.mjs, scripts/preflight-mongo-connection.mjs, scripts/preflight-activitypub-rsa-key.mjs, scripts/preflight-activitypub-profile-urls.mjs, scripts/patch-lightningcss.mjs, scripts/patch-endpoint-media-scope.mjs, scripts/patch-endpoint-media-sharp-runtime.mjs, scripts/patch-frontend-sharp-runtime.mjs, scripts/patch-endpoint-files-upload-route.mjs, scripts/patch-endpoint-files-upload-locales.mjs, scripts/patch-endpoint-activitypub-locales.mjs, scripts/patch-endpoint-activitypub-docloader-loglevel.mjs, scripts/patch-endpoint-activitypub-private-url-docloader.mjs, scripts/patch-endpoint-activitypub-migrate-alias-clear.mjs, scripts/patch-endpoint-homepage-locales.mjs, scripts/patch-frontend-serviceworker-file.mjs, scripts/patch-endpoint-comments-locales.mjs, scripts/patch-conversations-collection-guards.mjs, scripts/patch-indiekit-routes-rate-limits.mjs, scripts/patch-indiekit-error-production-stack.mjs, scripts/patch-indieauth-devmode-guard.mjs, scripts/patch-listening-endpoint-runtime-guards.mjs).
  • The production security preflight blocks startup on insecure auth/session configuration and catches empty-password bcrypt hashes.
  • One-time recovery mode is available with INDIEKIT_ALLOW_PASSWORD_SETUP=1 to bootstrap/reset PASSWORD_SECRET when locked out. Remove this flag after setting a valid hash.
  • The media scope patch fixes a known upstream issue where file uploads can fail if the token scope is create update delete without explicit media.
  • The ActivityPub RSA key preflight repairs or creates a usable type="rsa" key document in ap_keys, so outgoing federation requests can be signed and accepted by stricter inboxes.
  • The ActivityPub profile URL preflight repairs invalid URL fields in the ap_profile document (for example relative icon paths), preventing /.well-known/webfinger and actor responses from failing with TypeError: Invalid URL.
  • The media sharp runtime patch makes image transformation resilient on FreeBSD: if sharp cannot load, uploads continue without resize/rotation instead of crashing the server process.
  • The frontend sharp runtime patch makes icon generation non-fatal on FreeBSD when sharp cannot load, preventing startup crashes in asset controller imports.
  • The files upload route patch fixes browser multi-upload by posting to /files/upload (session-authenticated) instead of direct /media calls without bearer token.
  • The files upload locale patch adds missing files.upload.dropText/files.upload.browse/files.upload.submitMultiple labels in endpoint locale files so UI text does not render raw translation keys.
  • The ActivityPub locale patch backfills missing de locale keys from the endpoint's en locale and applies German admin title labels for notifications/profile.
  • The comments locale patch backfills missing comments endpoint locale files, adds translations for de/es/fr/nl/pt/sv, and localizes dashboard labels that were hardcoded in the comments template.
  • The frontend serviceworker patch ensures @indiekit/frontend/lib/serviceworker.js exists at runtime, forces network-only handling for /auth and /session pages, patches frontend layout templates to unregister stale service workers and clear caches on load, and suppresses sidebar rendering whenever app--minimalui is present.
  • The conversations guard patch prevents Cannot read properties of undefined (reading 'find') when the conversation_items collection is temporarily unavailable.
  • The indiekit routes rate-limit patch (ported from rmdes/indiekit-cloudron) keeps strict limits on /session/*, applies generous limits to public API/well-known routes, and removes extra rate limiting from authenticated routes to avoid admin-side 429 spikes.
  • The indiekit error stack patch (ported from rmdes/indiekit-cloudron) suppresses stack traces in production error pages/JSON responses to avoid leaking internal runtime details.
  • The indieauth dev-mode guard patch prevents accidental production auth bypass by requiring explicit INDIEKIT_ALLOW_DEV_AUTH=1 to enable dev auto-login, and broadens safe local redirect validation to allow common path characters (-, ., %) used by routes such as /auth/new-password.

AI transparency

AI disclosure metadata is captured per-post and surfaced in the blog's frontend as a badge, a sidebar widget, and a full /ai/ stats page.

Frontmatter fields

Four optional fields are stored under the ai: key in each post's frontmatter:

ai:
  textLevel: "0"   # 0 = none, 1 = editorial, 2 = co-drafted, 3 = AI-generated
  codeLevel: "0"   # same scale, optional
  tools: ""        # comma-separated tool names, optional
  description: ""  # free-text disclosure note, optional

Articles and notes support all four fields. Other post types (bookmarks, likes, etc.) do not.

Backend fields (Micropub form)

scripts/patch-endpoint-posts-ai-fields.mjs patches the Nunjucks templates inside @indiekit/endpoint-posts to add aiTextLevel, aiCodeLevel, aiTools, and aiDescription inputs to the article/note edit form. scripts/patch-endpoint-posts-ai-cleanup.mjs patches form.js in the same endpoint to strip empty AI fields from the Micropub payload before submission so unused optional fields are not written as empty strings.

Frontmatter generation (preset-eleventy patch)

scripts/patch-preset-eleventy-ai-frontmatter.mjs patches post-template.js inside @rmdes/indiekit-preset-eleventy. The patch adds a block that writes the ai: YAML section from the JF2 aiTextLevel/aiCodeLevel/aiTools/aiDescription properties when converting a Micropub post to markdown frontmatter.

Root cause of the v4 fix: @indiekit/endpoint-micropub/lib/utils.jsgetPostTemplateProperties() — explicitly deletes post-type before calling postTemplate(). Earlier patch versions (v1v3) relied on properties["post-type"] or properties.postType to detect whether a post is an article or note, so supportsAiDisclosure was always false and the ai: block was never written. The v4 fix detects post type from properties.permalink via URL path regex instead:

const permalink = String(properties.permalink ?? "");
const supportsAiDisclosure =
  postType === "article" || postType === "note" ||
  /\/articles(?:\/|$)/.test(permalink) || /\/notes(?:\/|$)/.test(permalink);

The patch script is idempotent and versioned: it detects the current patch level (v1/v2/v3/upstream) by matching a unique marker string and upgrades to v4 in place.

Blog frontend

  • _includes/layouts/post.njk — renders an AI disclosure badge below article/note content, reading ai.textLevel and ai.codeLevel from the post's frontmatter.
  • _includes/components/widgets/ai-usage.njk — compact sidebar widget showing totals, level breakdown, and a per-year contribution graph. Hidden when no posts have AI metadata.
  • _includes/components/sections/ai-usage.njk — full-width homepage section version of the same stats.
  • eleventy.config.js — defines aiStats and aiPosts Eleventy filters that scan collections.posts for ai.textLevel values to compute totals, percentages, and by-level counts.

Re-saving existing posts

Posts published before the v4 fix lack the ai: frontmatter block. To add it, open each post in the Indiekit backend (/posts) and save it again without changes. The patched postTemplate() will then write the ai: block with default values (textLevel: "0", codeLevel: "0"). AI level values previously entered in the form will now be persisted correctly on save.

Description
Indiekit deployment config
https://blog.giersig.eu
Readme 65 MiB
Languages
JavaScript 98.3%
Shell 1.7%