Add SABnzbd for Usenet NZB search and download

Wire Newshosting/Tweaknews via secrets-driven configure script, NZBgeek
through Prowlarr, and /data/usenet paths for hardlinks with the Arrs.
This commit is contained in:
tim 2026-07-22 11:59:51 -07:00
parent 0601de7217
commit 07ae1c0c71
8 changed files with 223 additions and 1 deletions

View file

@ -73,6 +73,9 @@ OPENHANDS_MODEL=openai/local
FLARESOLVERR_LOG_LEVEL=info FLARESOLVERR_LOG_LEVEL=info
FLARESOLVERR_CAPTCHA_SOLVER=none FLARESOLVERR_CAPTCHA_SOLVER=none
# SABnzbd (Usenet) — credentials in secrets/.env, not here
SABNZBD_PORT=8085
# Forgejo — self-hosted Git (web + SSH clone) # Forgejo — self-hosted Git (web + SSH clone)
FORGEJO_HTTP_PORT=3002 FORGEJO_HTTP_PORT=3002
FORGEJO_SSH_PORT=2222 FORGEJO_SSH_PORT=2222

2
.gitignore vendored
View file

@ -12,6 +12,8 @@ models/bonsai/*.gguf
models/bonsai/*.partial models/bonsai/*.partial
.openhands-state/ .openhands-state/
# secrets / local runtime # secrets / local runtime
secrets/*
!secrets/.env.example
**/wg0.conf **/wg0.conf
wireguard/wg0.conf wireguard/wg0.conf
workspace/* workspace/*

View file

@ -17,6 +17,7 @@ for the **Beelink SER5 PRO** (Ryzen 7 7735HS + Radeon 680M).
| Radarr | http://host:7878 | Movies | | Radarr | http://host:7878 | Movies |
| Bazarr | http://host:6767 | Subtitles | | Bazarr | http://host:6767 | Subtitles |
| Lidarr | http://host:8686 | Music | | Lidarr | http://host:8686 | Music |
| SABnzbd | http://host:8085 | Usenet NZB downloads |
| **Forgejo** | http://host:3002 | Self-hosted Git (SSH :2222) | | **Forgejo** | http://host:3002 | Self-hosted Git (SSH :2222) |
| **Open WebUI** | http://host:3000 | Chat UI, RAG, tools, code interpreter | | **Open WebUI** | http://host:3000 | Chat UI, RAG, tools, code interpreter |
| **Bonsai API** | http://host:8081/v1 | OpenAI-compatible llama-server | | **Bonsai API** | http://host:8081/v1 | OpenAI-compatible llama-server |

View file

@ -185,6 +185,7 @@ ufw_lan_tcp_ports:
- { port: 3001, comment: "OpenHands (optional)" } - { port: 3001, comment: "OpenHands (optional)" }
- { port: 3002, comment: "Forgejo HTTP" } - { port: 3002, comment: "Forgejo HTTP" }
- { port: 2222, comment: "Forgejo Git SSH" } - { port: 2222, comment: "Forgejo Git SSH" }
- { port: 8085, comment: "SABnzbd" }
# WireGuard peer hostname for gluetun-endpoint-watch # WireGuard peer hostname for gluetun-endpoint-watch
wireguard_endpoint_host: las-183-wg.whiskergalaxy.com wireguard_endpoint_host: las-183-wg.whiskergalaxy.com
@ -231,3 +232,7 @@ media_subdirs:
- torrents/movies - torrents/movies
- torrents/tv - torrents/tv
- torrents/music - torrents/music
- usenet/incomplete
- usenet/complete/movies
- usenet/complete/tv
- usenet/complete/music

View file

@ -4,7 +4,8 @@ name: arr-stack
# Arr stack # Arr stack
# gluetun WireGuard VPN client + firewall killswitch # gluetun WireGuard VPN client + firewall killswitch
# qbittorrent torrent client, lives INSIDE gluetun's network namespace # qbittorrent torrent client, lives INSIDE gluetun's network namespace
# prowlarr indexer manager # prowlarr indexer manager (torrents + NZB/Usenet indexers)
# sabnzbd Usenet NZB download client (Newshosting, Tweaknews, …)
# sonarr TV radarr movies bazarr subtitles # sonarr TV radarr movies bazarr subtitles
# jellyfin media server # jellyfin media server
# #
@ -80,6 +81,21 @@ services:
- ${CONFIG_DIR}/qbittorrent:/config - ${CONFIG_DIR}/qbittorrent:/config
- ${DATA_DIR}:/data # single shared tree -> hardlinks - ${DATA_DIR}:/data # single shared tree -> hardlinks
# ---------------------------------------------------------- usenet / NZB #
# SABnzbd talks to Usenet providers over SSL (no VPN required).
# Incomplete + complete under /data/usenet so Sonarr/Radarr can hardlink.
sabnzbd:
image: lscr.io/linuxserver/sabnzbd:latest
container_name: sabnzbd
restart: unless-stopped
networks: [arr-net]
ports:
- "${SABNZBD_PORT:-8085}:8080"
environment: *arr-env
volumes:
- ${CONFIG_DIR}/sabnzbd:/config
- ${DATA_DIR}:/data
# -------------------------------------------------------------- indexers # # -------------------------------------------------------------- indexers #
prowlarr: prowlarr:
image: lscr.io/linuxserver/prowlarr:latest image: lscr.io/linuxserver/prowlarr:latest

View file

@ -68,6 +68,7 @@ LAN URLs (UFW allows **`192.168.8.0/24`**). Replace host if needed.
| Radarr | http://192.168.8.123:7878 | | Radarr | http://192.168.8.123:7878 |
| Bazarr | http://192.168.8.123:6767 | | Bazarr | http://192.168.8.123:6767 |
| Lidarr | http://192.168.8.123:8686 | | Lidarr | http://192.168.8.123:8686 |
| SABnzbd | http://192.168.8.123:8085 |
| Open WebUI | http://192.168.8.123:3000 | | Open WebUI | http://192.168.8.123:3000 |
| Bonsai API | http://192.168.8.123:8081/v1 | | Bonsai API | http://192.168.8.123:8081/v1 |
| OpenHands | http://192.168.8.123:3001 | | OpenHands | http://192.168.8.123:3001 |

167
scripts/configure-usenet.sh Executable file
View file

@ -0,0 +1,167 @@
#!/usr/bin/env bash
# Bootstrap SABnzbd folders + Usenet servers from secrets/.env
# Run on the stack host after: docker compose up -d sabnzbd
#
# ./scripts/configure-usenet.sh
# SECRETS_FILE=./secrets/.env SAB_URL=http://127.0.0.1:8085 ./scripts/configure-usenet.sh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
STACK_DIR="${STACK_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
SECRETS_FILE="${SECRETS_FILE:-$STACK_DIR/secrets/.env}"
SAB_URL="${SAB_URL:-http://127.0.0.1:8085}"
log() { echo "[configure-usenet] $*"; }
die() { log "ERROR: $*"; exit 1; }
[[ -f "$SECRETS_FILE" ]] || die "missing $SECRETS_FILE (see secrets/.env.example)"
# shellcheck disable=SC1090
set -a
# shellcheck disable=SC1091
source "$SECRETS_FILE"
set +a
# Accept both clean names and the keys already in secrets/.env
NZBGEEK_API_KEY="${NZBGEEK_API_KEY:-${NZBKEEPKEY:-}}"
NZBGEEK_URL="${NZBGEEK_URL:-${NZBGEEKAPI:-https://api.nzbgeek.info/}}"
NEWSHOSTING_HOST="${NEWSHOSTING_HOST:-${NEWSHOSTINGAPI:-news.newshosting.com}}"
NEWSHOSTING_PORT="${NEWSHOSTING_PORT:-${NEWSHOSTINGSSLPORT:-563}}"
[[ "$NEWSHOSTING_PORT" == "653" ]] && NEWSHOSTING_PORT=563 # common typo
NEWSHOSTING_USER="${NEWSHOSTING_USER:-${NEWSHOSTINGUSER:-}}"
NEWSHOSTING_PASS="${NEWSHOSTING_PASS:-${HNEWSHOSTINGPASS:-${NEWSHOSTINGPASS:-}}}"
NEWSHOSTING_CONNECTIONS="${NEWSHOSTING_CONNECTIONS:-20}"
TWEAKNEWS_HOST="${TWEAKNEWS_HOST:-news.tweaknews.eu}"
TWEAKNEWS_PORT="${TWEAKNEWS_PORT:-563}"
TWEAKNEWS_USER="${TWEAKNEWS_USER:-${TWEAKNEWSUSER:-}}"
TWEAKNEWS_PASS="${TWEAKNEWS_PASS:-${TWEAKNEWSPASS:-}}"
TWEAKNEWS_CONNECTIONS="${TWEAKNEWS_CONNECTIONS:-10}"
find_api_key() {
local f
for f in \
"${CONFIG_DIR:-$STACK_DIR/config}/sabnzbd/sabnzbd.ini" \
"$STACK_DIR/config/sabnzbd/sabnzbd.ini" \
"/opt/stack/config/sabnzbd/sabnzbd.ini" \
; do
if [[ -f "$f" ]]; then
grep -E '^api_key[[:space:]]*=' "$f" | head -1 | cut -d= -f2- | tr -d '[:space:]'
return 0
fi
done
# docker exec fallback
if command -v docker >/dev/null 2>&1; then
docker exec sabnzbd sh -c 'grep -E "^api_key" /config/sabnzbd.ini 2>/dev/null | head -1' \
| cut -d= -f2- | tr -d '[:space:]' || true
fi
}
log "Waiting for SABnzbd at $SAB_URL ..."
for i in $(seq 1 90); do
if curl -fsS "$SAB_URL/api?mode=version&output=json" >/dev/null 2>&1; then
break
fi
sleep 2
[[ $i -eq 90 ]] && die "SABnzbd not reachable — is the container up? (docker compose up -d sabnzbd)"
done
api_key="$(find_api_key)"
[[ -n "$api_key" ]] || die "no api_key yet — open $SAB_URL once to finish first-run, then re-run this script"
sab_set() {
# set_config: section + keyword + value OR servers with name= + fields
curl -fsS -G "${SAB_URL}/api" \
--data-urlencode "mode=set_config" \
--data-urlencode "apikey=${api_key}" \
--data-urlencode "output=json" \
"$@"
}
log "Folders: incomplete + complete under /data/usenet"
sab_set --data-urlencode "section=misc" --data-urlencode "keyword=download_dir" \
--data-urlencode "value=/data/usenet/incomplete" >/dev/null
sab_set --data-urlencode "section=misc" --data-urlencode "keyword=complete_dir" \
--data-urlencode "value=/data/usenet/complete" >/dev/null
# Relative category folders under complete_dir
add_category() {
local name="$1"
sab_set \
--data-urlencode "section=categories" \
--data-urlencode "name=${name}" \
--data-urlencode "dir=${name}" \
--data-urlencode "pp=3" \
>/dev/null || true
}
add_category movies
add_category tv
add_category music
add_server() {
local name="$1" host="$2" port="$3" user="$4" pass="$5" conns="$6"
if [[ -z "$user" || -z "$pass" ]]; then
log "skip server '$name' (missing user/password in secrets)"
return 0
fi
log "Usenet server: $name$host:$port (ssl, ${conns} conn)"
# https://sabnzbd.org/wiki/configuration/5.0/api — servers via set_config + name=
sab_set \
--data-urlencode "section=servers" \
--data-urlencode "name=${name}" \
--data-urlencode "displayline=${name}" \
--data-urlencode "host=${host}" \
--data-urlencode "port=${port}" \
--data-urlencode "username=${user}" \
--data-urlencode "password=${pass}" \
--data-urlencode "connections=${conns}" \
--data-urlencode "ssl=1" \
--data-urlencode "enable=1" \
--data-urlencode "priority=0" \
--data-urlencode "timeout=60" \
>/dev/null
}
add_server "Newshosting" \
"$NEWSHOSTING_HOST" "$NEWSHOSTING_PORT" \
"$NEWSHOSTING_USER" "$NEWSHOSTING_PASS" \
"$NEWSHOSTING_CONNECTIONS"
add_server "Tweaknews" \
"$TWEAKNEWS_HOST" "$TWEAKNEWS_PORT" \
"$TWEAKNEWS_USER" "$TWEAKNEWS_PASS" \
"$TWEAKNEWS_CONNECTIONS"
log "Restart SABnzbd to pick up servers"
if command -v docker >/dev/null 2>&1 && docker ps --format '{{.Names}}' | grep -qx sabnzbd; then
docker restart sabnzbd >/dev/null
sleep 5
fi
log ""
log "=== SABnzbd ready ==="
log "UI: $SAB_URL"
log "API: (Config → General) — use this key in Prowlarr/*arr"
log ""
log "=== Prowlarr (NZBgeek) ==="
log "Indexers → Add Indexer → NZBgeek"
log " URL: ${NZBGEEK_URL}"
if [[ -n "$NZBGEEK_API_KEY" ]]; then
log " API Key: ${NZBGEEK_API_KEY}"
else
log " API Key: (set NZBKEEPKEY or NZBGEEK_API_KEY in secrets/.env)"
fi
log ""
log "Download Clients → Add → SABnzbd"
log " Host: sabnzbd Port: 8080 API Key: (from SABnzbd)"
log " (sync to apps so Sonarr/Radarr get SABnzbd too, or add per-app)"
log ""
log "=== Sonarr / Radarr / Lidarr ==="
log "Download Clients → SABnzbd → Host sabnzbd Port 8080"
log " Category: tv | movies | music"
log "Root folders still /data/media/{tv,movies,music} (hardlinks on same /data)"
log ""
if [[ -z "$TWEAKNEWS_USER" || -z "$TWEAKNEWS_PASS" ]]; then
log "NOTE: Tweaknews user/pass not in secrets — add TWEAKNEWS_USER / TWEAKNEWS_PASS and re-run."
fi

27
secrets/.env.example Normal file
View file

@ -0,0 +1,27 @@
# Copy to secrets/.env and fill in. NEVER commit secrets/.env
# Used by scripts/configure-usenet.sh and as a personal password vault.
# --- NZBgeek (indexer — wire in Prowlarr) ---
NZBGEEK_USERNAME=
NZBGEEK_EMAIL=
# API key from nzbgeek.info account (not the base URL)
NZBGEEK_API_KEY=
# Optional base URL (default https://api.nzbgeek.info/)
NZBGEEK_URL=https://api.nzbgeek.info/
# --- Newshosting (Usenet provider — SABnzbd server) ---
NEWSHOSTING_HOST=news.newshosting.com
NEWSHOSTING_PORT=563
NEWSHOSTING_SSL=1
NEWSHOSTING_USER=
NEWSHOSTING_PASS=
# Connections: start ~20 (plan-dependent)
NEWSHOSTING_CONNECTIONS=20
# --- Tweaknews (Usenet provider / fill-in block) ---
TWEAKNEWS_HOST=news.tweaknews.eu
TWEAKNEWS_PORT=563
TWEAKNEWS_SSL=1
TWEAKNEWS_USER=
TWEAKNEWS_PASS=
TWEAKNEWS_CONNECTIONS=10