From 89e84c41c145c3fe92e65e5a986c5b8ec4f21661 Mon Sep 17 00:00:00 2001 From: Justin Oros Date: Mon, 20 Apr 2026 14:37:10 -0700 Subject: [PATCH] hub/setup-opds.sh: add OPDS server setup script with dir2opds and Caddy running in Docker on shared network, with SSL and auth options --- hub/setup-opds.sh | 223 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100755 hub/setup-opds.sh diff --git a/hub/setup-opds.sh b/hub/setup-opds.sh new file mode 100755 index 0000000..fd90d7c --- /dev/null +++ b/hub/setup-opds.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +info() { echo -e "${GREEN}[+]${NC} $*"; } +warn() { echo -e "${YELLOW}[!]${NC} $*"; } +die() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; } +header() { echo -e "\n${CYAN}══════════════════════════════════════════${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}══════════════════════════════════════════${NC}"; } + +[ "$(id -u)" -eq 0 ] || die "Run as root" + +command -v docker >/dev/null 2>&1 || die "Docker not found. Install Docker first." + +header "TinyBoard OPDS Setup" +echo "" +echo "Choose an OPDS server:" +echo " 1) dir2opds — lightweight, no database, serves files directly from a folder" +echo " 2) Stump — full-featured book/comic server with web UI and OPDS support" +echo "" +read -rp "Choose [1/2]: " OPDS_CHOICE + +case "$OPDS_CHOICE" in +1) + OPDS_SERVER="dir2opds" + OPDS_IMAGE="ghcr.io/dubyte/dir2opds:latest" + OPDS_INTERNAL_PORT="8080" + ;; +2) + OPDS_SERVER="stump" + OPDS_IMAGE="aaronleopold/stump" + OPDS_INTERNAL_PORT="10801" + ;; +*) + die "Invalid choice." + ;; +esac + +info "Selected: $OPDS_SERVER" +echo "" + +read -rp "OPDS domain (e.g. opds.yourdomain.com): " OPDS_DOMAIN +[ -n "$OPDS_DOMAIN" ] || die "Domain cannot be empty" + +read -rp "Hub user [armbian]: " HUB_USER +HUB_USER="${HUB_USER:-armbian}" +HUB_HOME="/home/$HUB_USER" + +read -rp "Path to books directory [${HUB_HOME}/mnt/grace/st/data/books]: " BOOKS_PATH +BOOKS_PATH="${BOOKS_PATH:-${HUB_HOME}/mnt/grace/st/data/books}" +[ -d "$BOOKS_PATH" ] || die "Books directory not found: $BOOKS_PATH" + +echo "" +echo "SSL certificate management:" +echo " 1) Automatic — Caddy obtains and renews certs from Let's Encrypt (recommended)" +echo " 2) Manual — provide paths to existing certificate files" +echo "" +read -rp "Choose [1/2]: " SSL_CHOICE + +AUTO_HTTPS=true +CERT_PATH="" +KEY_PATH="" + +case "$SSL_CHOICE" in +1) + AUTO_HTTPS=true + info "Automatic HTTPS selected — DNS must point ${OPDS_DOMAIN} to this server." + ;; +2) + AUTO_HTTPS=false + read -rp "Path to fullchain.pem: " CERT_PATH + [ -f "$CERT_PATH" ] || die "Certificate file not found: $CERT_PATH" + read -rp "Path to privkey.pem: " KEY_PATH + [ -f "$KEY_PATH" ] || die "Key file not found: $KEY_PATH" + ;; +*) + die "Invalid choice." + ;; +esac + +echo "" +read -rp "Protect with a password? [y/N]: " USE_AUTH +USE_AUTH="${USE_AUTH:-n}" +OPDS_USER="" +OPDS_PASS="" +HASHED_PASS="" +if [[ "${USE_AUTH,,}" == "y" ]]; then + read -rp "Username [opds]: " OPDS_USER + OPDS_USER="${OPDS_USER:-opds}" + read -rsp "Password: " OPDS_PASS + echo "" + [ -n "$OPDS_PASS" ] || die "Password cannot be empty" +fi + +OPDS_DIR="${HUB_HOME}/opds" +mkdir -p "$OPDS_DIR" +chown "$HUB_USER":"$HUB_USER" "$OPDS_DIR" + +header "Creating Docker Network" +docker network create opds-net 2>/dev/null || info "Network opds-net already exists." + +header "Starting $OPDS_SERVER" +docker rm -f "$OPDS_SERVER" 2>/dev/null || true + +case "$OPDS_SERVER" in +dir2opds) + docker run -d \ + --name dir2opds \ + --restart unless-stopped \ + --network opds-net \ + -v "${BOOKS_PATH}:/books:ro" \ + "$OPDS_IMAGE" + ;; +stump) + mkdir -p "${OPDS_DIR}/stump-config" + chown -R "$HUB_USER":"$HUB_USER" "${OPDS_DIR}" + docker run -d \ + --name stump \ + --restart unless-stopped \ + --network opds-net \ + -v "${BOOKS_PATH}:/books:ro" \ + -v "${OPDS_DIR}/stump-config:/config" \ + -e PUID=1000 \ + -e PGID=1000 \ + "$OPDS_IMAGE" + ;; +esac + +info "$OPDS_SERVER started on internal network opds-net." + +header "Writing Caddyfile" +CADDY_DIR="${OPDS_DIR}/caddy" +mkdir -p "$CADDY_DIR" + +if [ "$AUTO_HTTPS" = true ]; then + TLS_BLOCK="" +else + TLS_BLOCK=" tls ${CERT_PATH} ${KEY_PATH}" +fi + +if [[ "${USE_AUTH,,}" == "y" ]]; then + docker run --rm caddy:alpine caddy hash-password --plaintext "$OPDS_PASS" > /tmp/opds_hash.txt 2>/dev/null + HASHED_PASS=$(cat /tmp/opds_hash.txt) + rm -f /tmp/opds_hash.txt + AUTH_BLOCK=" basicauth { + ${OPDS_USER} ${HASHED_PASS} + }" +else + AUTH_BLOCK="" +fi + +cat > "${CADDY_DIR}/Caddyfile" << EOF +$([ "$AUTO_HTTPS" = false ] && echo '{ + auto_https off +} + +:80 { + redir https://{host}{uri} permanent +} +') +${OPDS_DOMAIN} { + encode gzip +${TLS_BLOCK:+$TLS_BLOCK +}${AUTH_BLOCK:+$AUTH_BLOCK +} reverse_proxy http://${OPDS_SERVER}:${OPDS_INTERNAL_PORT} { + header_up Host {host} + header_up X-Real-IP {remote} + } +} +EOF + +info "Caddyfile written to ${CADDY_DIR}/Caddyfile" + +header "Starting Caddy" +docker rm -f caddy-opds 2>/dev/null || true + +if [ "$AUTO_HTTPS" = true ]; then + docker run -d \ + --name caddy-opds \ + --restart unless-stopped \ + --network opds-net \ + -p 80:80 \ + -p 443:443 \ + -v "${CADDY_DIR}/Caddyfile:/etc/caddy/Caddyfile:ro" \ + -v "${CADDY_DIR}/data:/data" \ + -v "${CADDY_DIR}/config:/config" \ + caddy:alpine +else + CERT_DIR=$(dirname "$CERT_PATH") + docker run -d \ + --name caddy-opds \ + --restart unless-stopped \ + --network opds-net \ + -p 80:80 \ + -p 443:443 \ + -v "${CADDY_DIR}/Caddyfile:/etc/caddy/Caddyfile:ro" \ + -v "${CERT_DIR}:/certs:ro" \ + caddy:alpine +fi + +sleep 3 +docker logs caddy-opds --tail 5 2>/dev/null || true + +header "OPDS Setup Complete" +echo "" +echo -e " OPDS URL: ${GREEN}https://${OPDS_DOMAIN}${NC}" +if [[ "${USE_AUTH,,}" == "y" ]]; then + echo -e " Username: ${GREEN}${OPDS_USER}${NC}" + echo -e " Password: ${GREEN}(as entered)${NC}" +else + echo -e " Auth: ${YELLOW}None — publicly accessible${NC}" +fi +if [ "$AUTO_HTTPS" = true ]; then + echo -e " SSL: ${GREEN}Automatic (Let's Encrypt)${NC}" + warn "DNS must point ${OPDS_DOMAIN} to this server's IP for HTTPS to work." +else + echo -e " SSL: ${GREEN}Manual certificates${NC}" +fi +echo ""