wrolpi/help.sh
Roland fbb71e012d 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.
2026-03-07 18:48:44 -07:00

460 lines
13 KiB
Bash
Executable file

#!/usr/bin/env bash
MEDIA_DIRECTORY=/media/wrolpi
# Source environment file after declaring globals.
[ -f /opt/wrolpi/.env ] && source /opt/wrolpi/.env
# Re-execute this script if it wasn't called with sudo.
if [ $EUID != 0 ]; then
sudo "$0" "$@"
exit $?
fi
function check_directory() {
directory=$1
good_message=$2
bad_message=$3
if [ -d "${directory}" ]; then
echo "OK: ${good_message}"
else
echo "FAILED: ${bad_message}"
fi
}
function check_file() {
file=$1
good_message=$2
bad_message=$3
if [ -f "${file}" ]; then
echo "OK: ${good_message}"
else
echo "FAILED: ${bad_message}"
fi
}
echo
echo "WROLPi version: $(cat /opt/wrolpi/wrolpi/version.txt)"
echo
rpi=false
rpi4=false
rpi5=false
debian12=false
grep -q 'Raspberry Pi' /proc/cpuinfo >/dev/null && rpi=true
grep -q 'Raspberry Pi 4' /proc/cpuinfo >/dev/null && rpi4=true
grep -q 'Raspberry Pi 5' /proc/cpuinfo >/dev/null && rpi5=true
grep -q 'PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"' /etc/os-release >/dev/null && debian12=true
if [[ ${rpi4} == true ]]; then
echo 'OK: Running on Raspberry Pi 4'
elif [[ ${rpi5} == true ]]; then
echo 'OK: Running on Raspberry Pi 5'
elif [[ ${rpi} == true ]]; then
echo 'OK: Running on unknown Raspberry Pi'
elif [[ ${debian12} == true ]]; then
echo 'OK: Running on Debian 12'
else
echo "FAILED: Running on unknown operating system"
fi
if dmesg | grep -q 'Undervoltage detected' >/dev/null 2>&1; then
echo 'FAILED: Under voltage detected! Use a more powerful power supply.'
fi
if dmesg | grep -q 'over-current change' >/dev/null 2>&1; then
echo 'FAILED: Over current detected! Your peripherals are using too much power!'
fi
if id -u wrolpi >/dev/null 2>&1; then
echo "OK: Found wrolpi user"
else
echo "FAILED: No wrolpi user found"
fi
check_directory /opt/wrolpi "The WROLPi directory exists" "The WROLPi directory does not exist at /opt/wrolpi"
check_directory /opt/wrolpi-blobs "The WROLPi blobs directory exists" "The WROLPi blobs directory does not exist at /opt/wrolpi-blobs"
check_file /opt/wrolpi/main.py "The WROLPi main script exists" "The WROLPi main script does not exist at /opt/wrolpi/main.py"
check_file ${MEDIA_DIRECTORY}/config/wrolpi.yaml "The WROLPi config file exists" "The WROLPi config file does not exist"
echo
# Postgresql
systemctl status postgresql.service >/dev/null &&
echo "OK: Postgres found" ||
echo "FAILED: Postgres service does not exist"
if netstat -ant | grep LISTEN | grep -E '[:\.]5432\b' >/dev/null; then
echo "OK: Port 5432 is occupied"
else
echo "FAILED: Port 5432 is not occupied"
fi
[ -S /var/run/postgresql/.s.PGSQL.5432 ] &&
echo "OK: Postgres is using file socket" ||
echo "FAILED: Postgres is not using file socket"
if sudo -i -u wrolpi psql -l 2>/dev/null | grep wrolpi >/dev/null; then
echo "OK: Found wrolpi database"
if sudo -i -u wrolpi psql wrolpi -c '\d' | grep "file_group" >/dev/null; then
echo "OK: WROLPi database is initialized"
if [ "$(sudo -i -u wrolpi psql wrolpi -c 'copy (select count(*) from file_group) to stdout' 2>/dev/null)" -gt 0 ]; then
echo "OK: WROLPi database has files"
else
echo "FAILED: WROLPi database has no files. You need to refresh your files: https://$(hostname).local/files"
fi
else
echo "FAILED: WROLPi is not initialized"
fi
else
echo "FAILED: Unable to find wrolpi database"
fi
echo
# Controller
if [ -f /opt/wrolpi/controller/venv/bin/python3 ]; then
echo "OK: Controller Python virtual environment exists"
if /opt/wrolpi/controller/venv/bin/python3 --version 2>/dev/null > /dev/null; then
echo 'OK: Controller Python can be run'
else
echo "FAILED: Controller Python could not be run"
fi
else
echo "FAILED: Controller Python virtual environment does not exist"
fi
if systemctl list-unit-files "*wrolpi-controller*" >/dev/null 2>&1; then
echo "OK: WROLPi Controller systemd exists"
else
echo "FAILED: WROLPi Controller systemd does not exist"
fi
if [ -f /etc/systemd/system/wrolpi-controller.service ]; then
if systemctl status wrolpi-controller.service >/dev/null 2>&1; then
echo "OK: WROLPi Controller service is up"
else
echo "FAILED: WROLPi Controller service is not up"
fi
fi
if curl -s http://0.0.0.0:8087/api/health --max-time 5 >/dev/null 2>&1; then
echo "OK: WROLPi Controller health check passed"
else
echo "FAILED: WROLPi Controller health check failed"
fi
# WROL Mode status
if [ -f /media/wrolpi/config/.wrol_mode ]; then
echo "Note: WROL Mode is ACTIVE"
else
echo "OK: WROL Mode is inactive"
fi
echo
# API
if [ -f /opt/wrolpi/venv/bin/python3 ]; then
echo "OK: WROLPi Python virtual environment exists"
if /opt/wrolpi/venv/bin/python3 /opt/wrolpi/main.py -h 2>/dev/null > /dev/null; then
echo 'OK: WROLPi main can be run'
else
echo "Failed: WROLPi main could not be run"
fi
if /opt/wrolpi/venv/bin/sanic --help 2>/dev/null > /dev/null; then
echo 'OK: Sanic can be run'
else
echo "Failed: Sanic could not be run"
fi
else
echo "FAILED: WROLPi Python virtual environment does not exist"
fi
if systemctl list-unit-files "*wrolpi-api*" >/dev/null; then
echo "OK: WROLPi API systemd exists"
else
echo "FAILED: WROLPi API systemd does not exist"
fi
if [ -f /etc/systemd/system/wrolpi-api.service ]; then
if systemctl status wrolpi-api.service >/dev/null; then
echo "OK: WROLPi API service is up"
else
echo "FAILED: WROLPi API service is not up"
fi
fi
if curl -s http://0.0.0.0:8081/api/echo --max-time 5 | grep -i '"method":"GET"' >/dev/null; then
echo "OK: WROLPi API echoed correctly"
else
echo "FAILED: WROLPi API did not echo correctly"
fi
echo
# Webapp
if [ -d /opt/wrolpi/app ]; then
echo "OK: WROLPi app directory exists"
if (cd /opt/wrolpi/app && npm ls >/dev/null); then
echo "OK: WROLPi app exists"
else
echo "FAILED: WROLPi app does not exist"
fi
else
echo "FAILED: WROLPi app directory does not exist"
fi
if systemctl list-unit-files "*wrolpi-app*" >/dev/null; then
echo "OK: WROLPi app systemd exists"
else
echo "FAILED: WROLPi app systemd does not exist"
fi
if [ -f /etc/systemd/system/wrolpi-app.service ]; then
if systemctl status wrolpi-app.service >/dev/null; then
echo "OK: WROLPi app service is up"
else
echo "FAILED: WROLPi app service is not up"
fi
fi
if curl -s http://0.0.0.0:3000 --max-time 5 | grep -i wrolpi >/dev/null; then
echo "OK: WROLPi app responded with UI"
else
echo "FAILED: WROLPi app did not respond with UI"
fi
if curl -s http://0.0.0.0:3000/epub/epub.html --max-time 5 | grep -i epub.js >/dev/null; then
echo "OK: WROLPi app responded with ebook interface"
else
echo "FAILED: WROLPi app did not respond with ebook interface"
fi
echo
# Caddy
if command -v caddy &>/dev/null; then
echo "OK: Caddy is installed ($(caddy version 2>/dev/null | head -1))"
else
echo "FAILED: Caddy is not installed"
fi
if systemctl list-unit-files "*caddy*" >/dev/null 2>&1; then
echo "OK: Caddy systemd exists"
if systemctl status caddy >/dev/null 2>&1; then
echo "OK: Caddy service is running"
else
echo "FAILED: Caddy service is not running"
fi
else
echo "FAILED: Caddy systemd does not exist"
fi
check_file /etc/caddy/Caddyfile "Caddyfile exists" "Caddyfile does not exist at /etc/caddy/Caddyfile"
check_file /etc/ssl/wrolpi/cert.crt "TLS certificate exists" "TLS certificate does not exist at /etc/ssl/wrolpi/cert.crt"
check_file /etc/ssl/wrolpi/cert.key "TLS key exists" "TLS key does not exist at /etc/ssl/wrolpi/cert.key"
check_file ${MEDIA_DIRECTORY}/config/ssl/ca.crt "Root CA certificate exists" "Root CA certificate does not exist at ${MEDIA_DIRECTORY}/config/ssl/ca.crt"
check_file ${MEDIA_DIRECTORY}/config/ssl/ca.key "Root CA key exists" "Root CA key does not exist at ${MEDIA_DIRECTORY}/config/ssl/ca.key"
check_file /var/www/landing.html "Landing page exists" "Landing page does not exist at /var/www/landing.html"
if [ -f ${MEDIA_DIRECTORY}/config/ssl/ca.crt ] && [ -f /etc/ssl/wrolpi/cert.crt ]; then
if openssl verify -CAfile ${MEDIA_DIRECTORY}/config/ssl/ca.crt /etc/ssl/wrolpi/cert.crt >/dev/null 2>&1; then
echo "OK: Leaf certificate is signed by root CA"
else
echo "FAILED: Leaf certificate is NOT signed by root CA (regenerate with /opt/wrolpi/scripts/generate_certificates.sh)"
fi
fi
for port in 80 443 8084 8085 8086; do
if netstat -ant | grep LISTEN | grep -E "(0\.0\.0\.0|:::)${port}\b" >/dev/null; then
echo "OK: Caddy port ${port} is occupied"
else
echo "FAILED: Caddy port ${port} is not occupied"
fi
done
if curl -k -s https://0.0.0.0/ --max-time 5 | grep -i wrolpi >/dev/null; then
echo "OK: Caddy HTTPS proxy responded on :443"
else
echo "FAILED: Caddy HTTPS proxy did not respond on :443"
fi
echo
# Map
if netstat -ant | grep LISTEN | grep -E '[:\.]5433\b' >/dev/null; then
echo "OK: Port 5433 is occupied"
else
echo "FAILED: Port 5433 is not occupied"
fi
if sudo -iu postgres psql --port=5433 -l 2>/dev/null | grep gis >/dev/null; then
echo "OK: Found map database"
if sudo -iu postgres psql --port=5433 gis -c '\d' | grep water_polygons >/dev/null; then
echo "OK: Map database is initialized"
else
echo "FAILED: Map database is not initialized"
fi
else
echo "FAILED: Unable to find map database"
fi
if systemctl list-unit-files "*renderd*" >/dev/null; then
echo "OK: renderd systemd exists"
else
echo "FAILED: renderd systemd does not exist"
fi
if curl -k -s https://0.0.0.0:8084 --max-time 5 2>/dev/null | grep -i openstreetmap >/dev/null; then
echo "OK: Map app responded"
else
echo "FAILED: Map app did not respond"
fi
check_file /opt/wrolpi-blobs/map-db-gis.dump "Map initialization blob exists" "Map initialization blob does not exist"
check_file /var/www/html/leaflet.js "Leaflet.js exists" "Leaflet.js does not exist"
echo
# Kiwix
check_file /media/wrolpi/zims/library.xml "The kiwix library file exists" "The kiwix library file does not exist at /media/wrolpi/zims/library.xml"
if curl -k -s https://0.0.0.0:8085 --max-time 5 2>/dev/null | grep -i kiwix >/dev/null; then
echo "OK: Kiwix app responded"
else
echo "FAILED: Kiwix app did not respond"
fi
echo
# Media Directory
check_directory "${MEDIA_DIRECTORY}" "The media directory exists" "The media directory does not exist at ${MEDIA_DIRECTORY}"
if [ -d "${MEDIA_DIRECTORY}" ] && [ -d ${MEDIA_DIRECTORY}/config ]; then
if touch ${MEDIA_DIRECTORY}/config; then
echo "OK: Can modify media directory"
else
echo "FAILED: Cannot modify media directory"
fi
fi
if grep -qs "${MEDIA_DIRECTORY}" /proc/mounts >/dev/null 2>&1 ; then
echo "OK: Media directory is a mounted drive"
else
echo "FAILED: Media directory is not a mounted drive. (This is fine if you don't have an external drive.)"
fi
if curl -k -s https://0.0.0.0/media/ --max-time 5 | grep -i "media" >/dev/null; then
echo "OK: Media directory files are served by caddy"
if curl -k -s -I https://0.0.0.0/media/config/wrolpi.yaml --max-time 5 | grep -E '200( OK)?' >/dev/null; then
echo "OK: Config can be fetched from caddy"
else
echo "FAILED: Media directory files are not being served"
fi
else
echo "FAILED: Media directory files are not being served"
fi
if curl -k -s -I https://0.0.0.0/download/ --max-time 5 | grep -E '200( OK)?' >/dev/null; then
echo "OK: Media download directory files are served by caddy"
if curl -k -s -I https://0.0.0.0/download/config/wrolpi.yaml --max-time 5 | grep -E '200( OK)?' >/dev/null; then
echo "OK: Config can be downloaded from caddy"
else
echo "FAILED: Media download directory files are not being served"
fi
else
echo "FAILED: Media download directory files are not being served"
fi
if [ "$(stat -c '%U' /media/wrolpi/)" == 'wrolpi' ]; then
echo "OK: Media directory is owned by wrolpi user"
else
echo "FAILED: Media directory is not owned by wrolpi user"
fi
echo
# 3rd party commands
if single-file -h >/dev/null 2>&1; then
echo "OK: Singlefile can be run"
else
echo "FAILED: Singlefile cannot be run"
fi
if [ -f /usr/bin/readability-extractor ]; then
echo "OK: readability exists"
else
if [ -f /usr/local/bin/readability-extractor ]; then
echo "OK: readability exists"
else
echo "FAILED: readability does not exist"
fi
fi
if wget -h >/dev/null 2>&1; then
echo "OK: wget can be run"
else
echo "FAILED: wget cannot be run"
fi
if aria2c -h >/dev/null 2>&1; then
echo "OK: aria2c can be run"
else
echo "FAILED: aria2c cannot be run"
fi
if /opt/wrolpi/venv/bin/yt-dlp -h >/dev/null 2>&1; then
echo "OK: yt-dlp can be run"
else
echo "FAILED: yt-dlp cannot be run"
fi
if chromium-browser -h >/dev/null 2>&1; then
echo "OK: Chromium can be run"
else
if chromium -h >/dev/null 2>&1; then
echo "OK: Chromium can be run"
else
echo "FAILED: Chromium cannot be run"
fi
fi
if ffmpeg -h >/dev/null 2>&1; then
echo "OK: ffmpeg can be run"
else
echo "FAILED: ffmpeg cannot be run"
fi
echo
# Help Service
if /opt/wrolpi-help/venv/bin/mkdocs --help &>/dev/null; then
echo "OK: Help mkdocs can be run"
else
echo "FAILED: Help mkdocs cannot be run"
fi
if curl -k -s https://0.0.0.0:8086/ --max-time 5 | grep MkDocs 2>/dev/null >/dev/null; then
echo "OK: Help service is running"
else
echo "FAILED: Help service is not running"
fi
echo
# Internet
if ping -c1 1.1.1.1 -W 5 &>/dev/null; then
echo "OK: Can ping 1.1.1.1"
else
echo "FAILED: Cannot ping 1.1.1.1. Is internet working?"
fi
if ping -c1 one.one.one.one -W 5 &>/dev/null; then
echo "OK: Can ping one.one.one.one"
else
echo "FAILED: Cannot ping one.one.one.one Is internet working?"
fi