#!/bin/sh # SecuBox PeerTube Manager - LXC Debian container with PostgreSQL/Redis/Node.js CONFIG="peertube" LXC_NAME="peertube" LXC_PATH="/srv/lxc" LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs" LXC_CONF="$LXC_PATH/$LXC_NAME/config" DATA_PATH_DEFAULT="/srv/peertube" PEERTUBE_VERSION="6.3.2" OPKG_UPDATED=0 usage() { cat <<'USAGE' Usage: peertubectl Installation: install Create LXC container with PeerTube stack uninstall Remove container (preserves data) update Update PeerTube to latest version check Run prerequisite checks Service: start Start PeerTube (via init) stop Stop PeerTube restart Restart PeerTube status Show container and service status logs [N] Show last N lines of logs (default: 50) shell Open interactive shell in container Administration: admin create-user --username --email [--password

] admin reset-password --username admin list-users List all users Live Streaming: live enable Enable RTMP live streaming live disable Disable RTMP live streaming live status Show live streaming status Exposure: configure-haproxy Setup HAProxy vhost with WebSocket support emancipate Full exposure (HAProxy + ACME + firewall) Backup: backup [path] Backup database and config restore Restore from backup Internal: service-run Run container via procd service-stop Stop container USAGE } # ---------- helpers ---------- require_root() { [ "$(id -u)" -eq 0 ]; } uci_get() { local key="$1" local section="${2:-main}" uci -q get ${CONFIG}.${section}.$key } uci_set() { local key="$1" local value="$2" local section="${3:-main}" uci set ${CONFIG}.${section}.$key="$value" } log_info() { echo "[INFO] $*"; logger -t peertubectl "$*"; } log_warn() { echo "[WARN] $*"; logger -t peertubectl -p warning "$*"; } log_error() { echo "[ERROR] $*" >&2; logger -t peertubectl -p err "$*"; } ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; } ensure_packages() { for pkg in "$@"; do if ! opkg status "$pkg" 2>/dev/null | grep -q "Status:.*installed"; then if [ "$OPKG_UPDATED" -eq 0 ]; then opkg update || return 1 OPKG_UPDATED=1 fi opkg install "$pkg" || return 1 fi done } defaults() { data_path="$(uci_get data_path || echo $DATA_PATH_DEFAULT)" videos_path="$(uci_get videos_path || echo $DATA_PATH_DEFAULT/videos)" memory_limit="$(uci_get memory_limit || echo 2048)" port="$(uci_get port server || echo 9000)" hostname="$(uci_get hostname server || echo peertube.local)" timezone="$(uci_get timezone || echo Europe/Paris)" } detect_arch() { case "$(uname -m)" in aarch64) echo "aarch64" ;; armv7l) echo "armv7" ;; x86_64) echo "x86_64" ;; *) echo "x86_64" ;; esac } generate_password() { head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 24 } # ---------- LXC helpers ---------- lxc_running() { lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING" } lxc_exists() { [ -f "$LXC_CONF" ] && [ -d "$LXC_ROOTFS" ] } lxc_exec() { lxc-attach -n "$LXC_NAME" -- "$@" } lxc_stop() { if lxc_running; then lxc-stop -n "$LXC_NAME" -k 2>/dev/null || true sleep 2 fi } # ---------- rootfs creation ---------- lxc_create_rootfs() { local arch=$(detect_arch) # Map to Debian architecture names local debian_arch case "$arch" in aarch64) debian_arch="arm64" ;; armv7) debian_arch="armhf" ;; x86_64) debian_arch="amd64" ;; *) debian_arch="amd64" ;; esac ensure_dir "$LXC_ROOTFS" # Minimal Debian rootfs via tarball from LXC image server local rootfs_url="https://images.linuxcontainers.org/images/debian/bookworm/${debian_arch}/default/" log_info "Downloading Debian bookworm rootfs for ${debian_arch}..." # Get latest build directory local latest_path latest_path=$(wget -q -O - "$rootfs_url" 2>/dev/null | grep -oE '[0-9]{8}_[0-9]{2}:[0-9]{2}' | tail -1) if [ -z "$latest_path" ]; then log_error "Failed to find latest Debian rootfs build" return 1 fi local tarball="/tmp/debian-peertube.tar.xz" local tarball_url="${rootfs_url}${latest_path}/rootfs.tar.xz" wget -q -O "$tarball" "$tarball_url" || { log_error "Failed to download Debian rootfs from $tarball_url" return 1 } tar -xJf "$tarball" -C "$LXC_ROOTFS" || { log_error "Failed to extract Debian rootfs" return 1 } rm -f "$tarball" # DNS cp /etc/resolv.conf "$LXC_ROOTFS/etc/resolv.conf" 2>/dev/null || \ echo "nameserver 8.8.8.8" > "$LXC_ROOTFS/etc/resolv.conf" # Create minimal /dev for chroot operations mkdir -p "$LXC_ROOTFS/dev" [ -c "$LXC_ROOTFS/dev/null" ] || mknod -m 666 "$LXC_ROOTFS/dev/null" c 1 3 2>/dev/null [ -c "$LXC_ROOTFS/dev/zero" ] || mknod -m 666 "$LXC_ROOTFS/dev/zero" c 1 5 2>/dev/null [ -c "$LXC_ROOTFS/dev/random" ] || mknod -m 666 "$LXC_ROOTFS/dev/random" c 1 8 2>/dev/null [ -c "$LXC_ROOTFS/dev/urandom" ] || mknod -m 666 "$LXC_ROOTFS/dev/urandom" c 1 9 2>/dev/null # Configure apt sources cat > "$LXC_ROOTFS/etc/apt/sources.list" <<'SOURCES' 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://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware SOURCES # Install PeerTube stack log_info "Installing PeerTube dependencies (this takes a while)..." chroot "$LXC_ROOTFS" /bin/sh -c " export DEBIAN_FRONTEND=noninteractive apt-get update && \ apt-get install -y --no-install-recommends \ curl wget ca-certificates gnupg \ postgresql postgresql-contrib \ redis-server \ ffmpeg \ python3 \ g++ make \ openssl \ procps " || { log_error "Failed to install base dependencies" return 1 } # Add NodeSource repository for Node.js 18 LTS log_info "Installing Node.js 18 LTS..." chroot "$LXC_ROOTFS" /bin/sh -c " export DEBIAN_FRONTEND=noninteractive curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \ apt-get install -y nodejs && \ npm install -g yarn " || { log_error "Failed to install Node.js" return 1 } # Create peertube user chroot "$LXC_ROOTFS" /bin/sh -c " useradd -r -s /bin/bash -d /opt/peertube -m peertube " # Download and install PeerTube log_info "Downloading PeerTube v${PEERTUBE_VERSION}..." local pt_url="https://github.com/Chocobozzz/PeerTube/releases/download/v${PEERTUBE_VERSION}/peertube-v${PEERTUBE_VERSION}.zip" wget -q -O "/tmp/peertube.zip" "$pt_url" || { log_error "Failed to download PeerTube" return 1 } chroot "$LXC_ROOTFS" /bin/sh -c " apt-get install -y --no-install-recommends unzip cd /opt/peertube unzip /tmp/peertube.zip mv peertube-v${PEERTUBE_VERSION} peertube-latest cd peertube-latest yarn install --production --pure-lockfile chown -R peertube:peertube /opt/peertube " || { log_error "Failed to install PeerTube" return 1 } rm -f /tmp/peertube.zip # Create directories mkdir -p "$LXC_ROOTFS/opt/peertube/config" mkdir -p "$LXC_ROOTFS/opt/peertube/storage" # Create startup script create_startup_script # Clean up apt cache chroot "$LXC_ROOTFS" /bin/sh -c " apt-get clean rm -rf /var/lib/apt/lists/* " log_info "Rootfs created successfully" } create_startup_script() { cat > "$LXC_ROOTFS/opt/start-peertube.sh" <<'STARTUP' #!/bin/bash set -e export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin # Start PostgreSQL echo "[PEERTUBE] Starting PostgreSQL..." service postgresql start sleep 3 # Start Redis echo "[PEERTUBE] Starting Redis..." service redis-server start sleep 2 # Initialize database on first run if [ ! -f /opt/peertube/.initialized ]; then echo "[PEERTUBE] First run - initializing database..." # Generate database password DB_PASSWORD=$(head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 24) # Create database and user su - postgres -c "psql -c \"CREATE USER peertube WITH PASSWORD '${DB_PASSWORD}';\"" su - postgres -c "psql -c \"CREATE DATABASE peertube OWNER peertube;\"" su - postgres -c "psql -c \"GRANT ALL PRIVILEGES ON DATABASE peertube TO peertube;\"" # Create extensions su - postgres -c "psql -d peertube -c 'CREATE EXTENSION IF NOT EXISTS pg_trgm;'" su - postgres -c "psql -d peertube -c 'CREATE EXTENSION IF NOT EXISTS unaccent;'" # Save password for config generation echo "$DB_PASSWORD" > /opt/peertube/.db_password chmod 600 /opt/peertube/.db_password touch /opt/peertube/.initialized echo "[PEERTUBE] Database initialized" fi # Generate PeerTube config if not exists if [ ! -f /opt/peertube/config/production.yaml ]; then echo "[PEERTUBE] Generating production config..." DB_PASSWORD=$(cat /opt/peertube/.db_password 2>/dev/null || echo "peertube") SECRET=$(head -c 64 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 32) cat > /opt/peertube/config/production.yaml < "$LXC_CONF" </dev/null /etc/init.d/peertube disable 2>/dev/null lxc_stop # Remove container but keep data rm -rf "$LXC_ROOTFS" "$LXC_CONF" uci_set enabled '0' uci commit "$CONFIG" defaults log_info "Container removed. Data preserved in $data_path" } cmd_update() { require_root || { log_error "Must run as root"; return 1; } log_info "Updating PeerTube..." lxc_stop # Get current and new version local new_version new_version=$(wget -q -O - "https://api.github.com/repos/Chocobozzz/PeerTube/releases/latest" 2>/dev/null | \ jsonfilter -e '@.tag_name' | sed 's/^v//') if [ -z "$new_version" ]; then log_error "Failed to get latest version" return 1 fi log_info "Updating to version $new_version..." # Download new version local pt_url="https://github.com/Chocobozzz/PeerTube/releases/download/v${new_version}/peertube-v${new_version}.zip" wget -q -O "/tmp/peertube.zip" "$pt_url" || { log_error "Failed to download PeerTube" return 1 } # Extract and install chroot "$LXC_ROOTFS" /bin/sh -c " cd /opt/peertube unzip -o /tmp/peertube.zip mv peertube-v${new_version} peertube-latest-new cd peertube-latest-new yarn install --production --pure-lockfile cd .. rm -rf peertube-latest-old mv peertube-latest peertube-latest-old mv peertube-latest-new peertube-latest chown -R peertube:peertube /opt/peertube " rm -f /tmp/peertube.zip /etc/init.d/peertube start log_info "Update complete (version $new_version)" } cmd_check() { echo "PeerTube Prerequisites Check" echo "=============================" # LXC if command -v lxc-start >/dev/null 2>&1; then echo "[OK] LXC installed" else echo "[FAIL] LXC not installed" fi # Container exists if lxc_exists; then echo "[OK] Container exists" else echo "[--] Container not created" fi # Container running if lxc_running; then echo "[OK] Container running" else echo "[--] Container not running" fi # PeerTube port defaults if netstat -tln 2>/dev/null | grep -q ":${port} " || \ grep -q ":$(printf '%04X' $port) " /proc/net/tcp 2>/dev/null; then echo "[OK] PeerTube listening on port $port" else echo "[--] PeerTube not listening" fi # Services in container if lxc_running; then if lxc_exec pgrep postgres >/dev/null 2>&1; then echo "[OK] PostgreSQL running" else echo "[FAIL] PostgreSQL not running" fi if lxc_exec pgrep redis-server >/dev/null 2>&1; then echo "[OK] Redis running" else echo "[FAIL] Redis not running" fi if lxc_exec pgrep -f "node dist/server" >/dev/null 2>&1; then echo "[OK] PeerTube process running" else echo "[FAIL] PeerTube process not running" fi fi } cmd_status() { defaults # JSON output for RPCD if [ "$1" = "--json" ]; then local running=0 local postgres=0 local redis=0 local peertube_proc=0 lxc_running && running=1 if [ "$running" = "1" ]; then lxc_exec pgrep postgres >/dev/null 2>&1 && postgres=1 lxc_exec pgrep redis-server >/dev/null 2>&1 && redis=1 lxc_exec pgrep -f "node dist/server" >/dev/null 2>&1 && peertube_proc=1 fi cat </dev/null 2>&1 && echo " PostgreSQL: UP" || echo " PostgreSQL: DOWN" lxc_exec pgrep redis-server >/dev/null 2>&1 && echo " Redis: UP" || echo " Redis: DOWN" lxc_exec pgrep -f "node dist/server" >/dev/null 2>&1 && echo " PeerTube: UP" || echo " PeerTube: DOWN" else echo "State: STOPPED" fi echo "" local lan_ip=$(uci -q get network.lan.ipaddr || echo '192.168.255.1') echo "Access URL: http://${lan_ip}:${port}" } cmd_logs() { local lines="${1:-50}" if lxc_running; then echo "=== PeerTube logs ===" lxc_exec tail -n "$lines" /opt/peertube/storage/logs/peertube.log 2>/dev/null || \ echo "No PeerTube logs found" else echo "Container not running" fi } cmd_shell() { if lxc_running; then lxc_exec /bin/bash || lxc_exec /bin/sh else log_error "Container not running" return 1 fi } cmd_start() { require_root || { log_error "Must run as root"; return 1; } /etc/init.d/peertube start } cmd_stop() { require_root || { log_error "Must run as root"; return 1; } /etc/init.d/peertube stop } cmd_restart() { require_root || { log_error "Must run as root"; return 1; } /etc/init.d/peertube restart } # ---------- admin commands ---------- cmd_admin() { local subcmd="$1" shift case "$subcmd" in create-user) cmd_admin_create_user "$@" ;; reset-password) cmd_admin_reset_password "$@" ;; list-users) cmd_admin_list_users ;; *) echo "Usage: peertubectl admin " return 1 ;; esac } cmd_admin_create_user() { local username="" email="" password="" admin="" while [ $# -gt 0 ]; do case "$1" in --username) username="$2"; shift 2 ;; --email) email="$2"; shift 2 ;; --password) password="$2"; shift 2 ;; --admin) admin="--role 0"; shift ;; *) shift ;; esac done [ -z "$username" ] || [ -z "$email" ] && { echo "Usage: peertubectl admin create-user --username --email [--password

] [--admin]" return 1 } [ -z "$password" ] && password=$(generate_password) lxc_running || { log_error "Container not running"; return 1; } lxc_exec su - peertube -c "cd /opt/peertube/peertube-latest && \ NODE_ENV=production NODE_CONFIG_DIR=/opt/peertube/config \ npm run create-user -- \ --url https://localhost \ --username '$username' \ --email '$email' \ --password '$password' \ $admin" log_info "User created: $username" log_info "Password: $password" } cmd_admin_reset_password() { local username="" while [ $# -gt 0 ]; do case "$1" in --username) username="$2"; shift 2 ;; *) shift ;; esac done [ -z "$username" ] && { echo "Usage: peertubectl admin reset-password --username " return 1 } local password=$(generate_password) lxc_running || { log_error "Container not running"; return 1; } lxc_exec su - peertube -c "cd /opt/peertube/peertube-latest && \ NODE_ENV=production NODE_CONFIG_DIR=/opt/peertube/config \ npm run reset-password -- \ --username '$username'" log_info "Password reset initiated for: $username" } cmd_admin_list_users() { lxc_running || { log_error "Container not running"; return 1; } lxc_exec su - postgres -c "psql -d peertube -c 'SELECT id, username, email, \"createdAt\" FROM \"user\" ORDER BY id;'" } # ---------- live streaming ---------- cmd_live() { local subcmd="$1" case "$subcmd" in enable) uci_set enabled '1' live uci commit "$CONFIG" # Update PeerTube config if lxc_running; then lxc_exec sed -i 's/enabled: false/enabled: true/' /opt/peertube/config/production.yaml # Open RTMP port local rtmp_port=$(uci_get rtmp_port live || echo 1935) uci add firewall rule uci set firewall.@rule[-1].name='PeerTube-RTMP' uci set firewall.@rule[-1].src='wan' uci set firewall.@rule[-1].dest_port="$rtmp_port" uci set firewall.@rule[-1].proto='tcp' uci set firewall.@rule[-1].target='ACCEPT' uci commit firewall /etc/init.d/firewall reload fi log_info "Live streaming enabled (RTMP port: $rtmp_port)" ;; disable) uci_set enabled '0' live uci commit "$CONFIG" if lxc_running; then lxc_exec sed -i 's/enabled: true/enabled: false/' /opt/peertube/config/production.yaml fi log_info "Live streaming disabled" ;; status) local enabled=$(uci_get enabled live || echo 0) local rtmp_port=$(uci_get rtmp_port live || echo 1935) echo "Live Streaming: $([ "$enabled" = "1" ] && echo "ENABLED" || echo "DISABLED")" echo "RTMP Port: $rtmp_port" ;; *) echo "Usage: peertubectl live " return 1 ;; esac } # ---------- HAProxy integration ---------- cmd_configure_haproxy() { require_root || { log_error "Must run as root"; return 1; } defaults local domain=$(uci_get domain network) [ -z "$domain" ] && domain="$hostname" # Create backend with extended timeouts for streaming/WebSocket local backend_name="peertube_web" uci set haproxy.${backend_name}=backend uci set haproxy.${backend_name}.name="$backend_name" uci set haproxy.${backend_name}.mode='http' uci set haproxy.${backend_name}.balance='roundrobin' uci set haproxy.${backend_name}.enabled='1' uci set haproxy.${backend_name}.timeout_server='3600s' uci set haproxy.${backend_name}.timeout_tunnel='3600s' uci set haproxy.${backend_name}.server="peertube 127.0.0.1:${port} check" # Create vhost local vhost_name=$(echo "$domain" | tr '.-' '_') uci set haproxy.${vhost_name}=vhost uci set haproxy.${vhost_name}.domain="$domain" uci set haproxy.${vhost_name}.backend="$backend_name" uci set haproxy.${vhost_name}.ssl='1' uci set haproxy.${vhost_name}.ssl_redirect='1' uci set haproxy.${vhost_name}.acme='1' uci set haproxy.${vhost_name}.enabled='1' uci commit haproxy # Update network config uci_set haproxy '1' network uci_set domain "$domain" network uci commit "$CONFIG" # Regenerate and reload if command -v haproxyctl >/dev/null 2>&1; then haproxyctl generate /etc/init.d/haproxy reload fi log_info "HAProxy configured for $domain" log_info "SSL certificate will be requested via ACME" } cmd_emancipate() { local domain="$1" [ -z "$domain" ] && { echo "Usage: peertubectl emancipate " return 1 } require_root || { log_error "Must run as root"; return 1; } log_info "Emancipating PeerTube at $domain..." # Update hostname uci_set hostname "$domain" server uci_set domain "$domain" network uci commit "$CONFIG" # Update PeerTube config if lxc_running; then lxc_exec sed -i "s/hostname: '.*'/hostname: '$domain'/" /opt/peertube/config/production.yaml fi # Configure HAProxy cmd_configure_haproxy # Register route in mitmproxy local port=$(uci_get port server || echo 9000) local routes_file="/srv/mitmproxy-in/haproxy-routes.json" # Direct JSON update - most reliable method if [ -f "$routes_file" ] && command -v python3 >/dev/null 2>&1; then python3 -c " import json try: with open('$routes_file', 'r') as f: routes = json.load(f) routes['$domain'] = ['192.168.255.1', $port] with open('$routes_file', 'w') as f: json.dump(routes, f, indent=2) except: pass " 2>/dev/null && log_info "Route registered: $domain -> 192.168.255.1:$port" elif command -v secubox-route >/dev/null 2>&1; then secubox-route add "$domain" "192.168.255.1" "$port" "peertube" 2>/dev/null log_info "Route registered via secubox-route" elif command -v mitmproxyctl >/dev/null 2>&1; then mitmproxyctl sync-routes 2>/dev/null fi # Open firewall if needed local wan_open=$(uci_get firewall_wan network) if [ "$wan_open" = "1" ]; then uci add firewall rule uci set firewall.@rule[-1].name='PeerTube-HTTPS' uci set firewall.@rule[-1].src='wan' uci set firewall.@rule[-1].dest_port='443' uci set firewall.@rule[-1].proto='tcp' uci set firewall.@rule[-1].target='ACCEPT' uci commit firewall /etc/init.d/firewall reload fi log_info "" log_info "==============================================" log_info " PeerTube Emancipated!" log_info "==============================================" log_info "" log_info " URL: https://$domain" log_info " SSL: ACME certificate requested" log_info "" log_info " Verify: curl -v https://$domain" log_info "" } # ---------- backup/restore ---------- cmd_backup() { local backup_path="${1:-/srv/peertube/backup}" require_root || { log_error "Must run as root"; return 1; } lxc_running || { log_error "Container must be running"; return 1; } ensure_dir "$backup_path" local timestamp=$(date +%Y%m%d_%H%M%S) local backup_file="$backup_path/peertube_${timestamp}.tar.gz" log_info "Creating backup..." # Dump PostgreSQL lxc_exec su - postgres -c "pg_dump peertube" > "$backup_path/peertube_db_${timestamp}.sql" # Create tarball with config and database dump defaults tar -czf "$backup_file" \ -C "$data_path" config \ -C "$backup_path" "peertube_db_${timestamp}.sql" rm -f "$backup_path/peertube_db_${timestamp}.sql" log_info "Backup created: $backup_file" } cmd_restore() { local backup_file="$1" [ -z "$backup_file" ] || [ ! -f "$backup_file" ] && { echo "Usage: peertubectl restore " return 1 } require_root || { log_error "Must run as root"; return 1; } lxc_running || { log_error "Container must be running"; return 1; } log_info "Restoring from $backup_file..." local tmp_dir="/tmp/peertube_restore_$$" mkdir -p "$tmp_dir" tar -xzf "$backup_file" -C "$tmp_dir" # Restore config defaults cp -a "$tmp_dir/config/"* "$data_path/config/" # Restore database local sql_file=$(ls "$tmp_dir"/peertube_db_*.sql 2>/dev/null | head -1) if [ -n "$sql_file" ]; then lxc_exec su - postgres -c "dropdb --if-exists peertube" lxc_exec su - postgres -c "createdb -O peertube peertube" cat "$sql_file" | lxc_exec su - postgres -c "psql peertube" fi rm -rf "$tmp_dir" log_info "Restore complete. Restart PeerTube to apply changes." } # ---------- service management ---------- cmd_service_run() { require_root || exit 1 defaults # Verify container exists lxc_exists || { log_error "Container not found. Run: peertubectl install"; exit 1; } log_info "Starting PeerTube container..." # Start container in foreground exec lxc-start -n "$LXC_NAME" -F -f "$LXC_CONF" } cmd_service_stop() { log_info "Stopping PeerTube container..." lxc_stop } # ---------- main ---------- case "$1" in install) cmd_install ;; uninstall) cmd_uninstall ;; update) cmd_update ;; check) cmd_check ;; start) cmd_start ;; stop) cmd_stop ;; restart) cmd_restart ;; status) shift; cmd_status "$@" ;; logs) shift; cmd_logs "$@" ;; shell) cmd_shell ;; admin) shift; cmd_admin "$@" ;; live) shift; cmd_live "$@" ;; configure-haproxy) cmd_configure_haproxy ;; emancipate) shift; cmd_emancipate "$@" ;; backup) shift; cmd_backup "$@" ;; restore) shift; cmd_restore "$@" ;; service-run) cmd_service_run ;; service-stop) cmd_service_stop ;; *) usage; exit 1 ;; esac