194 lines
15 KiB
Markdown
194 lines
15 KiB
Markdown
# 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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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-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 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`.
|