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>
110 lines
4.1 KiB
Bash
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}"
|