Replace nginx with Caddy for TLS termination and reverse proxying.

- Replace nginx with Caddy as the web server for both Docker and native
  (RPi/Debian) deployments. Caddy handles HTTPS termination, reverse
  proxying to all services (API, React app, Controller, Kiwix, Help, Map),
  and serves the media/download file browsers.
- Generate a Root CA and signed leaf certificates for HTTPS. The Root CA
  uses Name Constraints to restrict signing to local addresses only
  (.local, localhost, and RFC 1918 private IP ranges), mitigating MITM
  risk if the CA key is compromised.
- Add an HTTP landing page that guides users through one-time CA
  certificate installation on iOS, Android, macOS, Windows, Linux, and
  Firefox, with auto-redirect when the cert is already trusted.
- Add trailing-slash redirects for /controller, /docs, /media, and
  /download so bare paths don't fall through to the React catch-all.
- Add a Root CA download link on the Settings page under Configs.
- Update help.sh diagnostics for IPv6 listener detection and HTTP/2
  status codes.
- Remove all nginx config files and the https_proxy Docker container.
- Bump wrolpi-help submodule with certificate documentation.
This commit is contained in:
Roland 2026-03-07 12:08:27 -07:00 • committed by lrnselfreliance
parent dd270b37dc
commit fbb71e012d
43 changed files with 881 additions and 583 deletions

View file

@ -1,19 +1,94 @@
#!/usr/bin/env bash
# Generate self-signed SSL certificates for nginx if they don't exist.
# Generate a root CA and signed leaf certificate for WROLPi.
#
# Root CA: stored in ${MEDIA_DIRECTORY}/config/ssl/ — persists forever.
# Leaf cert: stored in ${CERT_DIR}/ — regenerated each run to pick up hostname/IP changes.
#
# Users trust the root CA once; leaf certs can be regenerated without re-trusting.
set -e
CERT_FILE="/etc/nginx/cert.crt"
KEY_FILE="/etc/nginx/cert.key"
MEDIA_DIRECTORY="${MEDIA_DIRECTORY:-/media/wrolpi}"
CERT_DIR="${CERT_DIR:-/etc/ssl/wrolpi}"
if [[ -f "${CERT_FILE}" && -f "${KEY_FILE}" ]]; then
echo "Certificates already exist at ${CERT_FILE} and ${KEY_FILE}"
exit 0
CA_DIR="${MEDIA_DIRECTORY}/config/ssl"
CA_KEY="${CA_DIR}/ca.key"
CA_CERT="${CA_DIR}/ca.crt"
LEAF_KEY="${CERT_DIR}/cert.key"
LEAF_CERT="${CERT_DIR}/cert.crt"
mkdir -p "${CA_DIR}" "${CERT_DIR}"
# --- Root CA (only created once) ---
if [[ -f "${CA_CERT}" && -f "${CA_KEY}" ]]; then
echo "Root CA already exists at ${CA_CERT}"
else
echo "Generating WROLPi Root CA..."
openssl genrsa -out "${CA_KEY}" 4096
openssl req -new -x509 -nodes -key "${CA_KEY}" -out "${CA_CERT}" -days 36500 \
-subj "/C=US/ST=State/L=City/O=WROLPi/CN=WROLPi Root CA" \
-addext "basicConstraints = critical, CA:TRUE" \
-addext "keyUsage = critical, keyCertSign, cRLSign" \
-addext "nameConstraints = critical, permitted;DNS:.local, permitted;DNS:localhost, permitted;IP:127.0.0.0/255.0.0.0, permitted;IP:10.0.0.0/255.0.0.0, permitted;IP:172.16.0.0/255.240.0.0, permitted;IP:192.168.0.0/255.255.0.0, permitted;IP:::1/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff, permitted;IP:fc00::/fe00::, permitted;IP:fe80::/ffc0::"
chmod 600 "${CA_KEY}"
chmod 644 "${CA_CERT}"
echo "Root CA generated."
fi
echo "Generating nginx certificates..."
openssl genrsa -out "${KEY_FILE}" 2048
openssl req -new -x509 -nodes -key "${KEY_FILE}" -out "${CERT_FILE}" -days 3650 \
-subj "/C=US/ST=State/L=City/O=Org/OU=WROLPi/CN=$(hostname).local"
chmod 640 "${KEY_FILE}" "${CERT_FILE}"
echo "Certificates generated successfully."
# --- Leaf certificate (always regenerated) ---
echo "Generating WROLPi leaf certificate..."
# Build SAN list dynamically.
SAN="DNS:localhost,DNS:wrolpi.local"
HOSTNAME_SHORT="$(hostname)"
if [[ -n "${HOSTNAME_SHORT}" ]]; then
SAN="${SAN},DNS:${HOSTNAME_SHORT}.local"
fi
SAN="${SAN},IP:127.0.0.1"
# Add all local IPs.
for ip in $(hostname -I 2>/dev/null || true); do
SAN="${SAN},IP:${ip}"
done
# Append any extra SANs (e.g. host IP when running in Docker).
if [[ -n "${EXTRA_SANS:-}" ]]; then
SAN="${SAN},${EXTRA_SANS}"
fi
echo "SANs: ${SAN}"
# Create CSR and sign with root CA.
openssl genrsa -out "${LEAF_KEY}" 2048
openssl req -new -key "${LEAF_KEY}" \
-subj "/C=US/ST=State/L=City/O=WROLPi/CN=${HOSTNAME_SHORT:-wrolpi}.local" \
-out "${CERT_DIR}/cert.csr"
openssl x509 -req -in "${CERT_DIR}/cert.csr" \
-CA "${CA_CERT}" -CAkey "${CA_KEY}" -CAcreateserial \
-days 825 \
-extfile <(printf "subjectAltName=%s\nbasicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth" "${SAN}") \
-out "${LEAF_CERT}"
rm -f "${CERT_DIR}/cert.csr"
chmod 640 "${LEAF_KEY}" "${LEAF_CERT}"
# Allow the caddy user to read the leaf cert/key (native installs).
if id caddy >/dev/null 2>&1; then
chown root:caddy "${LEAF_KEY}" "${LEAF_CERT}"
fi
# Validate the chain.
openssl verify -CAfile "${CA_CERT}" "${LEAF_CERT}"
echo "Leaf certificate generated and verified."
# Install root CA into system trust store so the device trusts its own certs.
if command -v update-ca-certificates >/dev/null 2>&1; then
cp "${CA_CERT}" /usr/local/share/ca-certificates/wrolpi-ca.crt
update-ca-certificates
echo "Root CA installed in system trust store."
fi

View file

@ -39,6 +39,14 @@ if [[ ! -f /opt/wrolpi-blobs/map-db-gis.dump || ! -s /opt/wrolpi-blobs/map-db-gi
wget https://wrolpi.nyc3.cdn.digitaloceanspaces.com/map-db-gis.dump -O /opt/wrolpi-blobs/map-db-gis.dump
fi
# Install Caddy from official apt repo if not already installed.
if ! command -v caddy &>/dev/null; then
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt-get update
apt-get install -y caddy
fi
# Install WROLPi Help.
/opt/wrolpi/scripts/install_help_service.sh

View file

@ -39,6 +39,14 @@ if [[ ! -f /opt/wrolpi-blobs/map-db-gis.dump || ! -s /opt/wrolpi-blobs/map-db-gi
wget https://wrolpi.nyc3.cdn.digitaloceanspaces.com/map-db-gis.dump -O /opt/wrolpi-blobs/map-db-gis.dump
fi
# Install Caddy from official apt repo if not already installed.
if ! command -v caddy &>/dev/null; then
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt-get update
apt-get install -y caddy
fi
# Install WROLPi Help.
/opt/wrolpi/scripts/install_help_service.sh

View file

@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Check if the WROLPi leaf certificate is expired or expiring within 30 days.
# If so, regenerate it and reload Caddy.
set -e
CERT_DIR="${CERT_DIR:-/etc/ssl/wrolpi}"
LEAF_CERT="${CERT_DIR}/cert.crt"
if [ ! -f "${LEAF_CERT}" ]; then
echo "Leaf certificate not found at ${LEAF_CERT}, generating..."
/opt/wrolpi/scripts/generate_certificates.sh
systemctl reload caddy || systemctl restart caddy || echo "Warning: failed to reload Caddy"
exit 0
fi
# Check if cert expires within 30 days.
if openssl x509 -checkend 2592000 -noout -in "${LEAF_CERT}" 2>/dev/null; then
echo "Certificate is still valid for more than 30 days, no renewal needed."
exit 0
fi
echo "Certificate is expired or expiring within 30 days, renewing..."
/opt/wrolpi/scripts/generate_certificates.sh
# Reload Caddy to pick up new cert.
systemctl reload caddy || systemctl restart caddy || echo "Warning: failed to reload Caddy"
echo "Certificate renewed and Caddy reloaded."

View file

@ -23,5 +23,5 @@ find /media/wrolpi/zims -iname '*.zim' -exec kiwix-manage ${LIBRARY} add {} \;
[ ! -f ${LIBRARY} ] && echo "Could not find any Zim files to import" && exit 2
# Serve HTTP kiwix on 9085. nginx will serve HTTPS on 8085.
# Serve HTTP kiwix on 9085. Caddy will serve HTTPS on 8085.
kiwix-serve --library ${LIBRARY} --address 0.0.0.0 --port 9085

View file

@ -104,5 +104,18 @@ fi
# Migrate map DB if necessary. Do this before repair because it will reset map if map db is empty.
/opt/wrolpi/wrolpi/scripts/migrate_map_db.sh || echo "Map DB migration failed."
# Migrate from nginx to Caddy if necessary.
if ! command -v caddy &>/dev/null; then
echo "Installing Caddy (replacing nginx)..."
# Disable and stop nginx if it was previously used.
systemctl stop nginx 2>/dev/null || :
systemctl disable nginx 2>/dev/null || :
# Install Caddy from official apt repo.
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt-get update
apt-get install -y caddy
fi
# Install any configs, restart services.
/opt/wrolpi/repair.sh