Files
hermes-config/skills/self-hosting/audiobookshelf-setup/SKILL.md
T
2026-07-12 10:17:17 -04:00

233 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: audiobookshelf-setup
description: Deploy Audiobookshelf on a self-hosted Linux server — Docker setup, reverse proxy with nginx + Let's Encrypt, DuckDNS for dynamic DNS, and DNS challenge for SSL when ISP blocks ports 80/443.
version: 1.0.0
author: Hermes Agent
license: MIT
platforms: [linux]
metadata:
hermes:
tags: [audiobookshelf, docker, nginx, lets-encrypt, duckdns, reverse-proxy, self-hosted]
category: self-hosting
related_skills: [plex-setup, selfhosted-migration]
---
# Audiobookshelf Setup
Deploy Audiobookshelf via Docker with secure HTTPS access — including reverse proxy, dynamic DNS, and SSL via Let's Encrypt DNS challenge (for residential ISPs that block ports 80/443).
## When to Use
- User asks to install Audiobookshelf
- User wants a self-hosted audiobook/podcast server
- User needs HTTPS access to a self-hosted service behind a residential ISP
- User needs a reverse proxy pattern for any Docker-hosted web service
## Prerequisites
- Docker + docker-compose-v2 installed
- A domain or DuckDNS subdomain (for SSL)
- Port forwarding in router (for external access)
## Setup Steps
### 1. Create directory structure
```bash
mkdir -p ~/docker/audiobookshelf/{config,metadata}
```
### 2. Create docker-compose.yml
```yaml
services:
audiobookshelf:
image: ghcr.io/advplyr/audiobookshelf:latest
container_name: audiobookshelf
network_mode: host
environment:
- TZ=America/New_York
- PORT=13378
volumes:
- /home/ray/docker/audiobookshelf/config:/config
- /home/ray/docker/audiobookshelf/metadata:/metadata
- /path/to/audiobooks:/audiobooks:ro
restart: unless-stopped
```
**Key points:**
- **`network_mode: host`** — Audiobookshelf defaults to port 80 internally. Set `PORT=13378` to move it off port 80 (needed for nginx later). With host networking, the port binds directly on the host.
- **Audiobook mount** — Use `:ro` (read-only). Audiobookshelf only reads files; no write access needed.
- The container needs ~5-10 seconds on first run to initialize its SQLite database and generate a JWT secret.
### 3. Start and verify
```bash
cd ~/docker/audiobookshelf && docker compose pull && docker compose up -d
sleep 10
curl -s -o /dev/null -w "HTTP %{http_code}" http://localhost:13378/
# Should return 200
```
### 4. Set up DuckDNS (free dynamic DNS)
If using a residential ISP without a static IP:
1. Go to https://duckdns.org → sign in with GitHub/Google
2. Create a subdomain (e.g., `mybooks`)
3. Copy the token from the top of the page
Install the updater script and cron job:
```bash
sudo mkdir -p /opt/duckdns
sudo tee /opt/duckdns/update.sh << 'EOF' > /dev/null
#!/bin/bash
echo url="https://www.duckdns.org/update?domains=<YOUR_SUBDOMAIN>&token=<YOUR_TOKEN>&ip=" | curl -k -s -o /opt/duckdns/duck.log -K -
EOF
sudo chmod +x /opt/duckdns/update.sh
# Run once to register
sudo /opt/duckdns/update.sh && cat /opt/duckdns/duck.log
# Should output "OK"
# Auto-update every 5 minutes
(sudo crontab -l 2>/dev/null | grep -v duckdns; echo "*/5 * * * * /opt/duckdns/update.sh") | sudo crontab -
```
Verify the domain resolves: `dig +short <subdomain>.duckdns.org`
### 5. Install nginx + certbot
```bash
sudo apt-get update && sudo apt-get install -y nginx certbot python3-certbot-nginx
# Install DuckDNS plugin for Let's Encrypt DNS challenge
sudo pip3 install --break-system-packages certbot-dns-duckdns
```
### 6. Get SSL certificate via DNS challenge
**Why DNS challenge:** Residential ISPs often block incoming ports 80/443. Let's Encrypt HTTP challenge requires port 80. DNS challenge works without any open ports — it adds a TXT record to the domain.
```bash
# Create credentials file
sudo mkdir -p /etc/letsencrypt
sudo tee /etc/letsencrypt/duckdns.ini << 'EOF' > /dev/null
dns_duckdns_token=<YOUR_TOKEN>
EOF
sudo chmod 600 /etc/letsencrypt/duckdns.ini
# Get certificate
sudo certbot certonly \
--authenticator dns-duckdns \
--dns-duckdns-credentials /etc/letsencrypt/duckdns.ini \
--dns-duckdns-propagation-seconds 30 \
-d <domain>.duckdns.org \
--non-interactive --agree-tos --email <your-email>
```
Cert will auto-renew via certbot's built-in systemd timer. No cron needed.
### 7. Configure nginx reverse proxy with SSL
Pick an **available high port** (since 80/443 are blocked by ISP). Check what's free:
```bash
sudo ss -tlnp | grep -E "3443|4443|5443"
```
Create the nginx config:
```nginx
server {
listen 3443 ssl;
server_name <domain>.duckdns.org;
ssl_certificate /etc/letsencrypt/live/<domain>.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<domain>.duckdns.org/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
client_max_body_size 500M;
location / {
proxy_pass http://127.0.0.1:13378;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
}
# Optional: redirect HTTP → HTTPS
server {
listen 80;
server_name <domain>.duckdns.org;
return 301 https://$host:3443$request_uri;
}
```
Enable and reload:
```bash
sudo cp audiobookshelf.conf /etc/nginx/sites-available/audiobookshelf
sudo ln -sf /etc/nginx/sites-available/audiobookshelf /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
```
### 8. Port forwarding
Forward the chosen high port (e.g., 3443 TCP) in your router to the server's LAN IP.
### 9. Access and create library
1. Visit `https://<domain>.duckdns.org:3443`
2. Create an admin account on first visit
3. Add a library → point it to `/audiobooks` (the container's internal mount path)
4. Audiobookshelf will scan and populate automatically
### 10. Client apps
- **Web player** — built-in, at the same URL
- **iOS** — [Audiobookshelf App](https://apps.apple.com/us/app/audiobookshelf/id6446379655)
- **Android** — [Audiobookshelf on Google Play](https://play.google.com/store/apps/details?id=com.audiobookshelf.app)
Connect the app to `https://<domain>.duckdns.org:3443` with your account. Supports offline downloads, sleep timer, and playback speed controls.
## Pitfalls
- **Port 80 conflicts with host networking** — Audiobookshelf defaults to port 80 internally. With `network_mode: host`, this binds directly on the host, conflicting with nginx. Set `PORT=13378` in the environment to move it before nginx setup.
- **Docker port conflicts** — Other containers may already use ports you want for nginx (e.g., Heimdall on 8443). Check with `docker ps --format '{{.Names}} {{.Ports}}'` before picking an nginx port.
- **Shell mangling of tokens** — If API tokens or Docker env vars contain special chars (underscores, dashes), bash may mangle them in variable expansion. Use Python for HTTP calls with tokens: `urllib.request.Request(url)` with `headers` works reliably. See `plex-setup` for the same pattern.
- **Hairpin NAT** — Testing the public URL from inside the LAN often fails (connection refused) even when the setup is correct. Always test from a mobile device on cellular data, not WiFi.
- **`certbot-dns-duckdns` not in apt** — On Ubuntu 26.04, this package isn't in apt. Use `pip3 install --break-system-packages certbot-dns-duckdns` instead.
- **Audiobookshelf first-run delay** — The container needs ~10 seconds to initialize its SQLite database, generate a JWT secret, and start listening. Don't assume it's broken if port 13378 isn't immediately available.
- **`docker compose restart` vs `up -d`** — `restart` reuses the old container config (doesn't pick up new env vars like `PORT`). Use `up -d` when changing environment variables, then `restart` for simple process cycling.
## Verification Checklist
- [ ] `curl http://localhost:13378/` returns 200
- [ ] DuckDNS updater returns "OK" on manual run
- [ ] `dig +short <domain>.duckdns.org` returns the correct public IP
- [ ] SSL cert obtained (`sudo certbot certificates`)
- [ ] nginx config passes `sudo nginx -t`
- [ ] `curl -sk https://localhost:3443/` returns 200 (SSL working locally)
- [ ] Port forwarding rule added in router
- [ ] External access confirmed from cellular data
## Support Files
- `templates/docker-compose.yml` — Starter Docker Compose file
- `templates/nginx-audiobookshelf.conf` — nginx reverse proxy config with SSL
- `scripts/duckdns-update.sh` — DuckDNS IP update script (set DOMAIN and TOKEN)
- `references/heimdall-dashboard.md` — Add the service to Heimdall dashboard via SQLite