nginx port 80 returns 444 (no response) for requests with an unrecognised Host header. The poller's curl sends Host: 10.100.0.10 (the IP) which doesn't match any server_name, causing the 180s readiness timeout and "empty reply from server" on every poll. Since livefetch v6 builds synthetic HTML from stored properties and no longer fetches live pages, the poller no longer needs to go through nginx. It now connects directly to Indiekit on INDIEKIT_BIND_HOST:PORT. Co-Authored-By: Claude Opus 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)
|
|
# Connects directly to Indiekit (not through nginx) so the Host header doesn't
|
|
# need to match any nginx server_name. nginx port 80 returns 444 for unknown hosts.
|
|
WEBMENTION_POLL_INTERVAL="${WEBMENTION_SENDER_POLL_INTERVAL:-300}"
|
|
INDIEKIT_DIRECT_URL="http://${INDIEKIT_BIND_HOST:-127.0.0.1}:${PORT:-3000}"
|
|
WEBMENTION_ENDPOINT="${INDIEKIT_DIRECT_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}"
|