wrolpi/help.sh
lrnselfreliance ad56134851
install.sh installs on Debian 13 (#830)
install.sh only accepted Debian 12, which cannot run this version, and
rejected Debian 13.  It now requires Debian 13 or newer on both PCs and
Raspberry Pis.  The install scripts point to the drive wizard instead of
manual mounting.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:51:40 -06:00

560 lines
19 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
debian13=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
os_version=$(awk -F= '$1=="VERSION_ID"{gsub(/"/,"",$2);print $2}' /etc/os-release 2>/dev/null)
[[ ${os_version} =~ ^[0-9]+$ && ${os_version} -ge 13 ]] && debian13=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 [[ ${debian13} == true ]]; then
echo "OK: Running on Debian ${os_version}"
elif [[ ${debian12} == true ]]; then
echo 'FAILED: Debian 12 is not supported by new versions of WROLPi. Reinstall on Debian 13.'
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
# Database (SQLite in the media directory)
WROLPI_DB="${MEDIA_DIRECTORY}/config/wrolpi.db"
if [ -f "${WROLPI_DB}" ]; then
echo "OK: Found wrolpi database at ${WROLPI_DB}"
if command -v sqlite3 >/dev/null 2>&1; then
# Run as the wrolpi user: a read of a WAL database needs write access to
# the -shm sidecar, which root-created files would break for the API.
wrolpi_sqlite() {
sudo -u wrolpi sqlite3 "${WROLPI_DB}" "$@" 2>/dev/null
}
# NOTE: A full integrity scan (PRAGMA quick_check/integrity_check) reads every
# page of the database and can take many minutes on a large DB or slow disk.
# It is intentionally NOT run here; use scripts/check_database_integrity.sh.
if wrolpi_sqlite ".tables" | grep -q "file_group"; then
echo "OK: WROLPi database is initialized"
if [ "$(wrolpi_sqlite 'select count(*) from file_group;')" -gt 0 ] 2>/dev/null; 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 "Note: sqlite3 CLI not installed; skipping database integrity checks"
fi
else
echo "FAILED: Unable to find wrolpi database at ${WROLPI_DB} (the API creates it on first start)"
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
# Port 80 belongs to the Controller (checked above); Caddy proxies to it.
for port in 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
if systemctl list-unit-files "*wrolpi-kiwix*" >/dev/null 2>&1; then
echo "OK: WROLPi Kiwix systemd exists"
else
echo "FAILED: WROLPi Kiwix systemd does not exist"
fi
if [ -f /etc/systemd/system/wrolpi-kiwix.service ]; then
if systemctl status wrolpi-kiwix.service >/dev/null 2>&1; then
echo "OK: WROLPi Kiwix service is up"
else
echo "FAILED: WROLPi Kiwix service is not up"
fi
fi
# kiwix-serve listens on 9085 (HTTP); Caddy proxies it as HTTPS on 8085. A
# running service that is not listening here means kiwix-serve failed to start.
if netstat -ant | grep LISTEN | grep -E "(0\.0\.0\.0:|:::)9085\b" >/dev/null; then
echo "OK: Kiwix port 9085 is occupied"
else
echo "FAILED: Kiwix is not listening on port 9085 (kiwix-serve did not start)"
fi
if curl -s http://0.0.0.0:9085 --max-time 5 2>/dev/null | grep -i kiwix >/dev/null; then
echo "OK: Kiwix responded on http://0.0.0.0:9085"
else
echo "FAILED: Kiwix did not respond on http://0.0.0.0:9085"
fi
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"
# Compare zim files on disk to the books actually loaded into the library. A
# shortfall means some zims could not be added (corrupt or incomplete download).
zims_on_disk=$(find /media/wrolpi/zims -iname '*.zim' 2>/dev/null | wc -l | tr -d ' ')
if [ -f /media/wrolpi/zims/library.xml ]; then
zims_loaded=$(grep -c '<book' /media/wrolpi/zims/library.xml 2>/dev/null)
else
zims_loaded=0
fi
if [ "${zims_on_disk}" -gt 0 ] && [ "${zims_loaded}" -lt "${zims_on_disk}" ]; then
echo "FAILED: Only ${zims_loaded} of ${zims_on_disk} zim file(s) loaded into the library; the rest are corrupt or incomplete (re-download them)"
elif [ "${zims_on_disk}" -gt 0 ]; then
echo "OK: All ${zims_on_disk} zim file(s) are loaded into the library"
else
echo "Note: No zim files found in /media/wrolpi/zims/"
fi
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