#!/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" # Cluster-level checks. The umbrella postgresql.service reports active even # when every cluster is down, so inspect the real clusters. if command -v pg_lsclusters >/dev/null 2>&1; then cluster_line=$(pg_lsclusters -h 2>/dev/null | head -1) if [ -z "${cluster_line}" ]; then echo "FAILED: No Postgres clusters exist (pg_lsclusters printed nothing)" elif [ "$(echo "${cluster_line}" | awk '{print $4}')" = "online" ]; then echo "OK: Postgres cluster is online (port $(echo "${cluster_line}" | awk '{print $3}'))" else echo "FAILED: Postgres cluster is not online: ${cluster_line}" fi cluster_data_dir=$(echo "${cluster_line}" | awk '{print $6}') if [ -n "${cluster_data_dir}" ]; then case "${cluster_data_dir}" in /var/lib/postgresql/*) echo "OK: Postgres data directory is at ${cluster_data_dir}" ;; *) echo "FAILED: Postgres data directory is in an unexpected location: ${cluster_data_dir}" ;; esac fi fi # A mount over /var/lib/postgresql means the Portable bind layout (expected # only on live boots). On an installed system this indicates the cluster is # on the media drive — the wrong spot; see mount-issues.md Problem 8. if findmnt -n /var/lib/postgresql >/dev/null 2>&1; then echo "Note: /var/lib/postgresql is a mount: $(findmnt -n -o SOURCE,FSTYPE /var/lib/postgresql)" echo " (Portable/live-boot layout; on an installed system this is WRONG)" else echo "OK: /var/lib/postgresql is on the root filesystem (installed layout)" fi # Connect directly instead of `sudo -i -u wrolpi` so sudo/PAM do not spam the journal. wrolpi_psql() { PGPASSWORD=wrolpi psql -h 127.0.0.1 -p 5432 -U wrolpi wrolpi "$@" } if wrolpi_psql -c 'select 1' >/dev/null 2>&1; then echo "OK: Found wrolpi database" if wrolpi_psql -c '\d' 2>/dev/null | grep "file_group" >/dev/null; then echo "OK: WROLPi database is initialized" if [ "$(wrolpi_psql -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:80/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 2>&1); 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" 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 if [ -f ${MEDIA_DIRECTORY}/config/ssl/ca.crt ]; then if ! openssl x509 -noout -in ${MEDIA_DIRECTORY}/config/ssl/ca.crt >/dev/null 2>&1; then echo "FAILED: Root CA certificate cannot be parsed (regenerate with /opt/wrolpi/scripts/generate_certificates.sh)" elif ! openssl x509 -checkend 0 -noout -in ${MEDIA_DIRECTORY}/config/ssl/ca.crt >/dev/null 2>&1; then echo "FAILED: Root CA certificate is expired (delete ${MEDIA_DIRECTORY}/config/ssl and regenerate with /opt/wrolpi/scripts/generate_certificates.sh)" else echo "OK: Root CA certificate is valid (expires $(openssl x509 -enddate -noout -in ${MEDIA_DIRECTORY}/config/ssl/ca.crt | cut -d= -f2))" fi fi if [ -f /etc/ssl/wrolpi/cert.crt ]; then if ! openssl x509 -noout -in /etc/ssl/wrolpi/cert.crt >/dev/null 2>&1; then echo "FAILED: TLS certificate cannot be parsed (regenerate with /opt/wrolpi/scripts/generate_certificates.sh)" elif ! openssl x509 -checkend 0 -noout -in /etc/ssl/wrolpi/cert.crt >/dev/null 2>&1; then echo "FAILED: TLS certificate is expired (regenerate with /opt/wrolpi/scripts/generate_certificates.sh)" elif ! openssl x509 -checkend 2592000 -noout -in /etc/ssl/wrolpi/cert.crt >/dev/null 2>&1; then echo "Note: TLS certificate expires within 30 days ($(openssl x509 -enddate -noout -in /etc/ssl/wrolpi/cert.crt | cut -d= -f2))" else echo "OK: TLS certificate is valid (expires $(openssl x509 -enddate -noout -in /etc/ssl/wrolpi/cert.crt | cut -d= -f2))" fi if openssl x509 -in /etc/ssl/wrolpi/cert.crt -noout -checkhost "$(hostname).local" 2>/dev/null | grep -q "does match certificate"; then echo "OK: TLS certificate covers $(hostname).local" else echo "FAILED: TLS certificate does not cover $(hostname).local (was the hostname changed? regenerate with /opt/wrolpi/scripts/generate_certificates.sh)" fi fi if [ -f /etc/ssl/wrolpi/cert.crt ] && [ -f /etc/ssl/wrolpi/cert.key ]; then cert_pubkey=$(openssl x509 -in /etc/ssl/wrolpi/cert.crt -noout -pubkey 2>/dev/null) key_pubkey=$(openssl pkey -in /etc/ssl/wrolpi/cert.key -pubout 2>/dev/null) if [ -n "${cert_pubkey}" ] && [ "${cert_pubkey}" = "${key_pubkey}" ]; then echo "OK: TLS certificate matches its private key" else echo "FAILED: TLS certificate does not match its private key (regenerate with /opt/wrolpi/scripts/generate_certificates.sh)" fi fi # Compare the certificate Caddy is actually serving to the one on disk. A mismatch means # Caddy was not reloaded after the certificate was regenerated. if [ -f /etc/ssl/wrolpi/cert.crt ]; then served_fingerprint=$(echo | timeout 5 openssl s_client -connect 127.0.0.1:443 2>/dev/null | openssl x509 -noout -fingerprint -sha256 2>/dev/null) disk_fingerprint=$(openssl x509 -in /etc/ssl/wrolpi/cert.crt -noout -fingerprint -sha256 2>/dev/null) if [ -z "${served_fingerprint}" ]; then echo "FAILED: Could not fetch the TLS certificate being served on port 443" elif [ "${served_fingerprint}" = "${disk_fingerprint}" ]; then echo "OK: Caddy is serving the TLS certificate on disk" else echo "FAILED: Caddy is serving a different TLS certificate than the one on disk (reload with: systemctl reload caddy)" fi fi if [ -x /opt/wrolpi/scripts/renew_certificates.sh ]; then echo "OK: Certificate renewal script is executable" else echo "FAILED: Certificate renewal script is not executable (fix with: chmod +x /opt/wrolpi/scripts/renew_certificates.sh)" fi if systemctl is-enabled wrolpi-cert-renew.timer >/dev/null 2>&1; then echo "OK: Certificate renewal timer is enabled" else echo "FAILED: Certificate renewal timer is not enabled (fix with: systemctl enable --now wrolpi-cert-renew.timer)" 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 curl -k -s https://0.0.0.0:8084 --max-time 5 2>/dev/null | grep -i "WROLPi Map" >/dev/null; then echo "OK: Map viewer responded" else echo "FAILED: Map viewer did not respond on port 8084" fi check_directory /opt/wrolpi/modules/map/static "Map static assets exist" "Map static assets missing at /opt/wrolpi/modules/map/static" if ls /media/wrolpi/map/*.pmtiles >/dev/null 2>&1; then echo "OK: Found $(ls /media/wrolpi/map/*.pmtiles 2>/dev/null | wc -l | tr -d ' ') PMTiles map files" else echo "Note: No PMTiles map files found in /media/wrolpi/map/" fi if [ -f /opt/wrolpi/.protomaps_installed ]; then echo "OK: Protomaps migration completed" else echo "Note: Protomaps migration has not been run (old map stack may still be active)" fi 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