secubox-openwrt/package/secubox/secubox-app-sabnzbd/files/usr/sbin/sabnzbdctl
CyberMind-FR 00da717ea4 fix(newsbin): Use Debian LXC rootfs for SABnzbd and NZBHydra
- SABnzbd/NZBHydra now use Debian LXC containers instead of Docker
- Added PATH/HOME exports to fix startup issues
- Added non-free repo for unrar dependency
- Use container IPs (192.168.255.40/41) instead of localhost
- Fixed cgroup mount compatibility

Container network:
- SABnzbd: 192.168.255.40:8085
- NZBHydra: 192.168.255.41:5076

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-14 17:26:43 +01:00

382 lines
16 KiB
Bash

#!/bin/sh
# ═══════════════════════════════════════════════════════════════════════════════
# SABnzbd Controller - Usenet NZB Downloader
# LXC container with Debian rootfs (no Docker/Podman)
# ═══════════════════════════════════════════════════════════════════════════════
CONTAINER_NAME="sabnzbd"
CONTAINER_DIR="/srv/lxc/$CONTAINER_NAME"
DATA_DIR="/srv/sabnzbd"
DOWNLOAD_DIR="/srv/downloads/usenet"
CONFIG="sabnzbd"
# Debian LXC rootfs URL (arm64) - Linux Containers official
DEBIAN_LXC_MIRROR="https://images.linuxcontainers.org/images/debian/bookworm/arm64/default"
# Logging
log_info() { logger -t sabnzbd -p user.info "$*"; echo "[INFO] $*"; }
log_error() { logger -t sabnzbd -p user.error "$*"; echo "[ERROR] $*" >&2; }
log_ok() { echo "[OK] $*"; }
# UCI helpers
uci_get() { uci -q get "$CONFIG.$1"; }
# ─────────────────────────────────────────────────────────────────────────────────
# Install container with Debian rootfs
# ─────────────────────────────────────────────────────────────────────────────────
cmd_install() {
log_info "Installing SABnzbd container (Debian LXC)..."
# Create directories
mkdir -p "$CONTAINER_DIR/rootfs"
mkdir -p "$DATA_DIR/config"
mkdir -p "$DOWNLOAD_DIR"/{complete,incomplete,nzb}
local rootfs="$CONTAINER_DIR/rootfs"
# Download Debian LXC rootfs if not exists
if [ ! -f "$rootfs/bin/bash" ]; then
log_info "Downloading Debian LXC rootfs..."
# Get latest build date from LXC mirror
local latest=$(curl -sL "$DEBIAN_LXC_MIRROR/" | grep -oE '[0-9]{8}_[0-9]{2}:[0-9]{2}' | sort -r | head -1)
[ -z "$latest" ] && latest="20240301_05:24"
local rootfs_url="${DEBIAN_LXC_MIRROR}/${latest}/rootfs.tar.xz"
local tarball="/tmp/debian-rootfs.tar.xz"
log_info "Fetching: $rootfs_url"
curl -L -o "$tarball" "$rootfs_url" || {
log_error "Failed to download Debian rootfs"
return 1
}
log_info "Extracting rootfs..."
xz -d -c "$tarball" | tar -xf - -C "$rootfs" || {
log_error "Failed to extract rootfs"
rm -f "$tarball"
return 1
}
rm -f "$tarball"
fi
# Configure DNS
echo "nameserver 8.8.8.8" > "$rootfs/etc/resolv.conf"
echo "nameserver 1.1.1.1" >> "$rootfs/etc/resolv.conf"
# Create startup script
cat > "$rootfs/start-sabnzbd.sh" <<'STARTEOF'
#!/bin/bash
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export HOME="/root"
exec > /config/startup.log 2>&1
echo "=== Starting SABnzbd $(date) ==="
# Install SABnzbd if not present
if ! command -v sabnzbdplus >/dev/null 2>&1; then
echo "Installing SABnzbd..."
# Add non-free repo for unrar
cat > /etc/apt/sources.list << EOF
deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware
deb http://deb.debian.org/debian-security/ bookworm-security main contrib non-free non-free-firmware
EOF
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
sabnzbdplus \
unrar \
par2 \
p7zip-full \
ca-certificates
apt-get clean
rm -rf /var/lib/apt/lists/*
fi
echo "=== Starting SABnzbd server ==="
mkdir -p /config
exec sabnzbdplus -s 0.0.0.0:8085 -f /config
STARTEOF
chmod +x "$rootfs/start-sabnzbd.sh"
# Create LXC config
local memory=$(uci_get main.memory)
[ -z "$memory" ] && memory="536870912" # 512MB in bytes
cat > "$CONTAINER_DIR/config" <<EOF
lxc.uts.name = $CONTAINER_NAME
lxc.rootfs.path = dir:$CONTAINER_DIR/rootfs
lxc.init.cmd = /start-sabnzbd.sh
# Network - use veth with bridge
lxc.net.0.type = veth
lxc.net.0.link = br-lan
lxc.net.0.flags = up
lxc.net.0.ipv4.address = 192.168.255.40/24
lxc.net.0.ipv4.gateway = 192.168.255.1
# Mounts
lxc.mount.auto = proc:mixed sys:ro
lxc.mount.entry = $DATA_DIR/config config none bind,create=dir 0 0
lxc.mount.entry = $DOWNLOAD_DIR downloads none bind,create=dir 0 0
lxc.mount.entry = tmpfs tmp tmpfs defaults 0 0
lxc.mount.entry = tmpfs run tmpfs defaults 0 0
# Capabilities
lxc.cap.drop = sys_admin sys_boot sys_module sys_rawio sys_time
# Memory limit
lxc.cgroup2.memory.max = $memory
# TTY
lxc.tty.max = 0
lxc.pty.max = 256
# Seccomp (disabled for compatibility)
lxc.seccomp.profile =
# Auto-start
lxc.start.auto = 1
EOF
log_ok "SABnzbd container installed"
log_info "Container IP: 192.168.255.40"
log_info "Run: sabnzbdctl start"
}
# ─────────────────────────────────────────────────────────────────────────────────
# Start container
# ─────────────────────────────────────────────────────────────────────────────────
cmd_start() {
if ! [ -d "$CONTAINER_DIR/rootfs" ]; then
log_error "Container not installed. Run: sabnzbdctl install"
return 1
fi
if lxc-info -n "$CONTAINER_NAME" 2>/dev/null | grep -q "RUNNING"; then
log_info "SABnzbd already running"
return 0
fi
log_info "Starting SABnzbd..."
lxc-start -n "$CONTAINER_NAME" -f "$CONTAINER_DIR/config"
# Wait for startup
sleep 5
if lxc-info -n "$CONTAINER_NAME" 2>/dev/null | grep -q "RUNNING"; then
log_ok "SABnzbd started on http://192.168.255.40:8085/"
else
log_error "Failed to start SABnzbd"
lxc-info -n "$CONTAINER_NAME" 2>&1
return 1
fi
}
# ─────────────────────────────────────────────────────────────────────────────────
# Stop container
# ─────────────────────────────────────────────────────────────────────────────────
cmd_stop() {
if ! lxc-info -n "$CONTAINER_NAME" 2>/dev/null | grep -q "RUNNING"; then
log_info "SABnzbd not running"
return 0
fi
log_info "Stopping SABnzbd..."
lxc-stop -n "$CONTAINER_NAME" -t 30
log_ok "SABnzbd stopped"
}
# ─────────────────────────────────────────────────────────────────────────────────
# Restart container
# ─────────────────────────────────────────────────────────────────────────────────
cmd_restart() {
cmd_stop
sleep 2
cmd_start
}
# ─────────────────────────────────────────────────────────────────────────────────
# Status
# ─────────────────────────────────────────────────────────────────────────────────
cmd_status() {
echo "=== SABnzbd Status ==="
# Container state
if lxc-info -n "$CONTAINER_NAME" 2>/dev/null | grep -q "RUNNING"; then
echo "Container: RUNNING"
echo "IP: 192.168.255.40"
else
echo "Container: STOPPED"
return 0
fi
# API status
local api_key=$(cat "$DATA_DIR/config/sabnzbd.ini" 2>/dev/null | grep "^api_key" | cut -d'=' -f2 | tr -d ' ')
if [ -n "$api_key" ]; then
local status=$(curl -s "http://192.168.255.40:8085/api?mode=queue&output=json&apikey=$api_key" 2>/dev/null)
if [ -n "$status" ]; then
local speed=$(echo "$status" | jsonfilter -e '@.queue.speed' 2>/dev/null)
local queue_size=$(echo "$status" | jsonfilter -e '@.queue.noofslots' 2>/dev/null)
local disk_free=$(echo "$status" | jsonfilter -e '@.queue.diskspace1' 2>/dev/null)
echo "Speed: ${speed:-0}"
echo "Queue: ${queue_size:-0} items"
echo "Disk Free: ${disk_free:-?} GB"
fi
fi
echo "Web UI: http://192.168.255.40:8085/"
}
# ─────────────────────────────────────────────────────────────────────────────────
# Logs
# ─────────────────────────────────────────────────────────────────────────────────
cmd_logs() {
local lines="${1:-50}"
if [ -f "$DATA_DIR/config/logs/sabnzbd.log" ]; then
tail -n "$lines" "$DATA_DIR/config/logs/sabnzbd.log"
else
log_info "No logs yet. SABnzbd may not have run."
fi
}
# ─────────────────────────────────────────────────────────────────────────────────
# Shell access
# ─────────────────────────────────────────────────────────────────────────────────
cmd_shell() {
if ! lxc-info -n "$CONTAINER_NAME" 2>/dev/null | grep -q "RUNNING"; then
log_error "Container not running"
return 1
fi
lxc-attach -n "$CONTAINER_NAME" -- /bin/bash
}
# ─────────────────────────────────────────────────────────────────────────────────
# Configure NNTP server
# ─────────────────────────────────────────────────────────────────────────────────
cmd_add_server() {
local ini_file="$DATA_DIR/config/sabnzbd.ini"
# Read NNTP config from UCI
local name=$(uci_get eweka.name)
local host=$(uci_get eweka.host)
local port=$(uci_get eweka.port)
local ssl=$(uci_get eweka.ssl)
local username=$(uci_get eweka.username)
local password=$(uci_get eweka.password)
local connections=$(uci_get eweka.connections)
[ -z "$host" ] && { log_error "No NNTP server configured in UCI"; return 1; }
log_info "NNTP server configured: $name ($host)"
log_info "Username: $username"
log_info "Connections: $connections"
log_info "SSL Port: $port"
# If SABnzbd running, use API
if [ -f "$ini_file" ]; then
local api_key=$(grep "^api_key" "$ini_file" 2>/dev/null | cut -d'=' -f2 | tr -d ' ')
if [ -n "$api_key" ] && curl -s "http://192.168.255.40:8085/api?mode=version&apikey=$api_key" >/dev/null 2>&1; then
log_info "Adding server via API..."
# Server config via web UI or API
fi
fi
log_ok "Configure the server in SABnzbd web UI: http://192.168.255.40:8085/config/server/"
}
# ─────────────────────────────────────────────────────────────────────────────────
# Configure HAProxy exposure
# ─────────────────────────────────────────────────────────────────────────────────
cmd_configure_haproxy() {
local domain=$(uci_get exposure.domain)
[ -z "$domain" ] && domain="sabnzbd.gk2.secubox.in"
log_info "Configuring HAProxy for $domain"
# Create backend
uci set haproxy.sabnzbd_web=backend
uci set haproxy.sabnzbd_web.name='sabnzbd_web'
uci set haproxy.sabnzbd_web.mode='http'
uci set haproxy.sabnzbd_web.server="sabnzbd 192.168.255.40:8085 weight 100 check"
# Create vhost
local vhost_id=$(echo "$domain" | tr '.' '_')
uci set "haproxy.$vhost_id=vhost"
uci set "haproxy.$vhost_id.domain=$domain"
uci set "haproxy.$vhost_id.backend=mitmproxy_inspector"
uci set "haproxy.$vhost_id.original_backend=sabnzbd_web"
uci set "haproxy.$vhost_id.ssl=1"
uci set "haproxy.$vhost_id.ssl_redirect=1"
uci set "haproxy.$vhost_id.acme=1"
uci commit haproxy
# Add mitmproxy route
if [ -f /srv/mitmproxy/haproxy-routes.json ]; then
python3 -c "
import json
with open('/srv/mitmproxy/haproxy-routes.json') as f:
routes = json.load(f)
routes['$domain'] = ['192.168.255.40', 8085]
with open('/srv/mitmproxy/haproxy-routes.json', 'w') as f:
json.dump(routes, f, indent=2)
" 2>/dev/null
fi
# Reload
haproxyctl reload 2>/dev/null || true
/etc/init.d/mitmproxy restart 2>/dev/null || true
log_ok "HAProxy configured: https://$domain/"
}
# ─────────────────────────────────────────────────────────────────────────────────
# Uninstall
# ─────────────────────────────────────────────────────────────────────────────────
cmd_uninstall() {
log_info "Uninstalling SABnzbd..."
cmd_stop 2>/dev/null
rm -rf "$CONTAINER_DIR"
log_info "Container removed. Data preserved in $DATA_DIR"
log_ok "SABnzbd uninstalled"
}
# ─────────────────────────────────────────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────────────────────────────────────────
case "$1" in
install) cmd_install ;;
start) cmd_start ;;
stop) cmd_stop ;;
restart) cmd_restart ;;
status) cmd_status ;;
logs) shift; cmd_logs "$@" ;;
shell) cmd_shell ;;
add-server) cmd_add_server ;;
configure-haproxy) cmd_configure_haproxy ;;
uninstall) cmd_uninstall ;;
*)
echo "SABnzbd Controller - Usenet NZB Downloader"
echo ""
echo "Usage: sabnzbdctl <command>"
echo ""
echo "Commands:"
echo " install Install Debian LXC container with SABnzbd"
echo " start Start SABnzbd"
echo " stop Stop SABnzbd"
echo " restart Restart SABnzbd"
echo " status Show status and queue info"
echo " logs [n] Show last n log lines (default 50)"
echo " shell Interactive shell in container"
echo " add-server Show NNTP server config from UCI"
echo " configure-haproxy Setup HAProxy reverse proxy"
echo " uninstall Remove container (keeps data)"
;;
esac