Files
indiekit-server/start.example.sh
Sven e63734ee2a fix(start): kill node process on service stop to prevent orphaned port binding
Trap previously only killed the webmention poller, leaving the node process
orphaned on service stop. On restart, the new node instance would fail to bind
port 3000 (EADDRINUSE) causing 502s with clean logs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 12:12:56 +01:00

110 lines
4.1 KiB
Bash

#!/bin/sh
set -eu
cd /usr/local/indiekit
NODE_BIN="${NODE_BIN:-/usr/local/bin/node}"
# Optional: load environment from local .env file
# (dotenv syntax, supports spaces in values).
if [ -f .env ]; then
eval "$("${NODE_BIN}" -e '
const fs = require("node:fs");
const dotenv = require("dotenv");
const parsed = dotenv.parse(fs.readFileSync(".env"));
for (const [key, value] of Object.entries(parsed)) {
const safe = String(value).split("\x27").join("\x27\"\x27\"\x27");
process.stdout.write(`export ${key}=\x27${safe}\x27\n`);
}
')"
fi
: "${SECRET:?SECRET is required}"
if [ "${INDIEKIT_ALLOW_PASSWORD_SETUP:-0}" != "1" ]; then
: "${PASSWORD_SECRET:?PASSWORD_SECRET is required}"
fi
# Allow either full Mongo URL or decomposed credentials.
if [ -z "${MONGO_URL:-}" ]; then
: "${MONGO_USERNAME:?MONGO_USERNAME is required when MONGO_URL is not set}"
: "${MONGO_PASSWORD:?MONGO_PASSWORD is required when MONGO_URL is not set}"
export MONGO_AUTH_SOURCE="${MONGO_AUTH_SOURCE:-admin}"
fi
if [ -z "${GH_CONTENT_TOKEN:-}" ] && [ -z "${GITHUB_TOKEN:-}" ]; then
echo "GH_CONTENT_TOKEN or GITHUB_TOKEN is required" >&2
exit 1
fi
# Force production runtime and keep debug logging disabled.
export NODE_ENV="production"
export INDIEKIT_DEBUG="0"
unset DEBUG
# Verify production auth/session hardening before launching server.
"${NODE_BIN}" scripts/preflight-production-security.mjs
# Verify MongoDB credentials/connectivity before launching server.
"${NODE_BIN}" scripts/preflight-mongo-connection.mjs
# Ensure ActivityPub has an RSA keypair for HTTP Signature delivery.
"${NODE_BIN}" scripts/preflight-activitypub-rsa-key.mjs
# Normalize ActivityPub profile URL fields (icon/image/aliases) in MongoDB.
"${NODE_BIN}" scripts/preflight-activitypub-profile-urls.mjs
for patch in scripts/patch-*.mjs; do
echo "[startup] Applying patch: $patch"
"${NODE_BIN}" "$patch"
done
"${NODE_BIN}" node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs &
INDIEKIT_PID="$!"
# Webmention sender — polls every N seconds (see @rmdes/indiekit-endpoint-webmention-sender README)
# Routes through INTERNAL_FETCH_URL (nginx) so the request gets correct Host header
# and X-Forwarded-Proto, avoiding empty-reply issues with direct jail connections.
WEBMENTION_POLL_INTERVAL="${WEBMENTION_SENDER_POLL_INTERVAL:-300}"
INDIEKIT_INTERNAL_URL="${INTERNAL_FETCH_URL:-http://${INDIEKIT_BIND_HOST:-127.0.0.1}:${PORT:-3000}}"
WEBMENTION_ENDPOINT="${INDIEKIT_INTERNAL_URL}${WEBMENTION_SENDER_MOUNT_PATH:-/webmention-sender}"
WEBMENTION_ORIGIN="${PUBLICATION_URL:-${SITE_URL:-}}"
(
echo "[webmention] Starting auto-send polling every ${WEBMENTION_POLL_INTERVAL}s (${WEBMENTION_ENDPOINT})"
# Wait for the webmention-sender endpoint itself to be ready (up to 3 minutes).
# Using the plugin's own /api/status ensures MongoDB collections and plugin
# routes are fully initialised, not just the bare Express server.
_i=0
until curl -sf "${WEBMENTION_ENDPOINT}/api/status" -o /dev/null 2>&1; do
_i=$((_i + 1))
[ $_i -lt 90 ] || { echo "[webmention] Warning: webmention-sender not ready after 180s, proceeding anyway"; break; }
sleep 2
done
echo "[webmention] Webmention sender ready"
while true; do
TOKEN="$(
WEBMENTION_ORIGIN="$WEBMENTION_ORIGIN" WEBMENTION_SECRET="$SECRET" \
"${NODE_BIN}" -e '
const jwt = require("jsonwebtoken");
const me = process.env.WEBMENTION_ORIGIN;
const secret = process.env.WEBMENTION_SECRET;
if (!me || !secret) process.exit(1);
process.stdout.write(jwt.sign({ me, scope: "update" }, secret, { expiresIn: "5m" }));
' 2>/dev/null || true
)"
if [ -n "$TOKEN" ]; then
RESULT="$(curl -sS --max-time 300 -X POST -d "" "${WEBMENTION_ENDPOINT}?token=${TOKEN}" 2>&1 || true)"
echo "[webmention] $(date '+%Y-%m-%d %H:%M:%S') - ${RESULT:-ok}"
else
echo "[webmention] $(date '+%Y-%m-%d %H:%M:%S') - token generation failed"
fi
sleep "$WEBMENTION_POLL_INTERVAL"
done
) &
POLLER_PID="$!"
trap 'kill "${INDIEKIT_PID}" "${POLLER_PID}" 2>/dev/null || true; wait "${INDIEKIT_PID}" 2>/dev/null || true' EXIT INT TERM
wait "${INDIEKIT_PID}"