qBittorrent (secubox-app-qbittorrent): - Full-featured BitTorrent client with web UI - Container IP: 192.168.255.42:8090 - qbittorrent-nox from Debian repos - API commands: add, list, status WebTorrent (secubox-app-webtorrent): - Browser-based torrent streaming via WebRTC - Container IP: 192.168.255.43:8095 - Node.js server with webtorrent library - Stream video files directly in browser - Beautiful dark theme web UI Both use Debian LXC containers (no Docker/Podman) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
384 lines
16 KiB
Bash
Executable File
384 lines
16 KiB
Bash
Executable File
#!/bin/sh
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# qBittorrent Controller - BitTorrent Client
|
|
# LXC container with Debian rootfs (no Docker/Podman)
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
CONTAINER_NAME="qbittorrent"
|
|
CONTAINER_DIR="/srv/lxc/$CONTAINER_NAME"
|
|
DATA_DIR="/srv/qbittorrent"
|
|
DOWNLOAD_DIR="/srv/downloads/torrents"
|
|
CONFIG="qbittorrent"
|
|
|
|
# Debian LXC rootfs URL (arm64)
|
|
DEBIAN_LXC_MIRROR="https://images.linuxcontainers.org/images/debian/bookworm/arm64/default"
|
|
|
|
# Logging
|
|
log_info() { logger -t qbittorrent -p user.info "$*"; echo "[INFO] $*"; }
|
|
log_error() { logger -t qbittorrent -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 qBittorrent container (Debian LXC)..."
|
|
|
|
# Create directories
|
|
mkdir -p "$CONTAINER_DIR/rootfs"
|
|
mkdir -p "$DATA_DIR/config"
|
|
mkdir -p "$DOWNLOAD_DIR"/{complete,incomplete,watch}
|
|
|
|
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-qbt.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-qbittorrent.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 qBittorrent $(date) ==="
|
|
|
|
# Install qBittorrent if not present
|
|
if ! command -v qbittorrent-nox >/dev/null 2>&1; then
|
|
echo "Installing qBittorrent..."
|
|
|
|
apt-get update
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
qbittorrent-nox \
|
|
ca-certificates || { echo "apt install failed"; exit 1; }
|
|
|
|
apt-get clean
|
|
rm -rf /var/lib/apt/lists/*
|
|
fi
|
|
|
|
echo "=== Starting qBittorrent server ==="
|
|
mkdir -p /config /downloads
|
|
cd /config
|
|
|
|
# Run qBittorrent-nox
|
|
exec qbittorrent-nox --webui-port=8090 --profile=/config
|
|
STARTEOF
|
|
chmod +x "$rootfs/start-qbittorrent.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-qbittorrent.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.42/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 "qBittorrent container installed"
|
|
log_info "Container IP: 192.168.255.42"
|
|
log_info "Run: qbittorrentctl start"
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
# Start container
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
cmd_start() {
|
|
if ! [ -d "$CONTAINER_DIR/rootfs" ]; then
|
|
log_error "Container not installed. Run: qbittorrentctl install"
|
|
return 1
|
|
fi
|
|
|
|
if lxc-info -n "$CONTAINER_NAME" 2>/dev/null | grep -q "RUNNING"; then
|
|
log_info "qBittorrent already running"
|
|
return 0
|
|
fi
|
|
|
|
log_info "Starting qBittorrent..."
|
|
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 "qBittorrent started on http://192.168.255.42:8090/"
|
|
log_info "Default login: admin / adminadmin"
|
|
else
|
|
log_error "Failed to start qBittorrent"
|
|
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 "qBittorrent not running"
|
|
return 0
|
|
fi
|
|
|
|
log_info "Stopping qBittorrent..."
|
|
lxc-stop -n "$CONTAINER_NAME" -t 30
|
|
log_ok "qBittorrent stopped"
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
# Restart container
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
cmd_restart() {
|
|
cmd_stop
|
|
sleep 2
|
|
cmd_start
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
# Status
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
cmd_status() {
|
|
echo "=== qBittorrent Status ==="
|
|
|
|
# Container state
|
|
if lxc-info -n "$CONTAINER_NAME" 2>/dev/null | grep -q "RUNNING"; then
|
|
echo "Container: RUNNING"
|
|
echo "IP: 192.168.255.42"
|
|
else
|
|
echo "Container: STOPPED"
|
|
return 0
|
|
fi
|
|
|
|
# API status
|
|
local info=$(curl -s "http://192.168.255.42:8090/api/v2/app/version" 2>/dev/null)
|
|
if [ -n "$info" ]; then
|
|
echo "Version: $info"
|
|
fi
|
|
|
|
local transfer=$(curl -s "http://192.168.255.42:8090/api/v2/transfer/info" 2>/dev/null)
|
|
if [ -n "$transfer" ]; then
|
|
local dl_speed=$(echo "$transfer" | jsonfilter -e '@.dl_info_speed' 2>/dev/null)
|
|
local up_speed=$(echo "$transfer" | jsonfilter -e '@.up_info_speed' 2>/dev/null)
|
|
[ -n "$dl_speed" ] && echo "Download: $(($dl_speed / 1024)) KB/s"
|
|
[ -n "$up_speed" ] && echo "Upload: $(($up_speed / 1024)) KB/s"
|
|
fi
|
|
|
|
echo "Web UI: http://192.168.255.42:8090/"
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
# Logs
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
cmd_logs() {
|
|
local lines="${1:-50}"
|
|
if [ -f "$DATA_DIR/config/startup.log" ]; then
|
|
tail -n "$lines" "$DATA_DIR/config/startup.log"
|
|
else
|
|
log_info "No logs yet."
|
|
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
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
# Add torrent
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
cmd_add() {
|
|
local url="$1"
|
|
[ -z "$url" ] && { log_error "Usage: qbittorrentctl add <magnet/url>"; return 1; }
|
|
|
|
local result=$(curl -s -X POST "http://192.168.255.42:8090/api/v2/torrents/add" \
|
|
-d "urls=$url" 2>/dev/null)
|
|
|
|
if [ "$result" = "Ok." ]; then
|
|
log_ok "Torrent added"
|
|
else
|
|
log_error "Failed to add torrent: $result"
|
|
fi
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
# List torrents
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
cmd_list() {
|
|
local torrents=$(curl -s "http://192.168.255.42:8090/api/v2/torrents/info" 2>/dev/null)
|
|
|
|
if [ -n "$torrents" ]; then
|
|
echo "$torrents" | python3 -c "
|
|
import sys, json
|
|
try:
|
|
data = json.load(sys.stdin)
|
|
for i, t in enumerate(data[:20], 1):
|
|
name = t.get('name', 'N/A')[:50]
|
|
progress = t.get('progress', 0) * 100
|
|
state = t.get('state', 'N/A')
|
|
print(f'{i}. [{state}] {name} ({progress:.1f}%)')
|
|
except:
|
|
pass
|
|
" 2>/dev/null
|
|
else
|
|
log_info "No torrents or API not accessible"
|
|
fi
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
# Configure HAProxy exposure
|
|
# ─────────────────────────────────────────────────────────────────────────────────
|
|
cmd_configure_haproxy() {
|
|
local domain=$(uci_get exposure.domain)
|
|
[ -z "$domain" ] && domain="torrent.gk2.secubox.in"
|
|
|
|
log_info "Configuring HAProxy for $domain"
|
|
|
|
# Create backend
|
|
uci set haproxy.qbittorrent_web=backend
|
|
uci set haproxy.qbittorrent_web.name='qbittorrent_web'
|
|
uci set haproxy.qbittorrent_web.mode='http'
|
|
uci set haproxy.qbittorrent_web.server="qbittorrent 192.168.255.42:8090 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=qbittorrent_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.42', 8090]
|
|
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 qBittorrent..."
|
|
|
|
cmd_stop 2>/dev/null
|
|
|
|
rm -rf "$CONTAINER_DIR"
|
|
log_info "Container removed. Data preserved in $DATA_DIR"
|
|
|
|
log_ok "qBittorrent 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) shift; cmd_add "$@" ;;
|
|
list) cmd_list ;;
|
|
configure-haproxy) cmd_configure_haproxy ;;
|
|
uninstall) cmd_uninstall ;;
|
|
*)
|
|
echo "qBittorrent Controller - BitTorrent Client"
|
|
echo ""
|
|
echo "Usage: qbittorrentctl <command>"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " install Install Debian LXC container with qBittorrent"
|
|
echo " start Start qBittorrent"
|
|
echo " stop Stop qBittorrent"
|
|
echo " restart Restart qBittorrent"
|
|
echo " status Show status and transfer info"
|
|
echo " logs [n] Show last n log lines (default 50)"
|
|
echo " shell Interactive shell in container"
|
|
echo " add <url> Add torrent by magnet/URL"
|
|
echo " list List active torrents"
|
|
echo " configure-haproxy Setup HAProxy reverse proxy"
|
|
echo " uninstall Remove container (keeps data)"
|
|
;;
|
|
esac
|