#!/bin/sh # YAAR — YouTube Auto-Archiver and Retagger # ───────────────────────────────────────── # Run this script INSIDE your existing Alpine Linux LXC container as root. # It does not touch the Proxmox host in any way. # # BEFORE running this, do one thing in Proxmox to attach your Jellyfin media: # Proxmox web UI → your container → Resources → Add → Mount Point # Host path: /mnt/nas/jellyfin (wherever your NAS is mounted on the host) # Container path: /nas/jellyfin # Read-only: No # Then restart the container. # # Or via the Proxmox shell (not this container — the host shell): # pct set -mp0 /mnt/nas/jellyfin,mp=/nas/jellyfin # pct restart # # After that, get this script into the container any way you like: # - Copy from the Proxmox web UI "Upload" button # - scp yaar.tar.gz root@:/root/ # - pct push yaar.tar.gz /root/yaar.tar.gz (from Proxmox host only) # # Then inside the container: # tar xzf /root/yaar.tar.gz -C /root/ # sh /root/yaar/setup.sh # ───────────────────────────────────────────────────────────────────────────── set -e YAAR_DIR="/opt/yaar" MEDIA_DIR="/nas/jellyfin" # Must match the mp0 mount point set in Proxmox WEB_DIR="${YAAR_DIR}/web" VENV_DIR="${YAAR_DIR}/venv" LOG_DIR="${YAAR_DIR}/logs" JOB_DIR="${YAAR_DIR}/jobs" PORT=7474 RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[0;33m'; NC='\033[0m' info() { printf "${CYAN}[YAAR]${NC} %s\n" "$*"; } success() { printf "${GREEN}[OK]${NC} %s\n" "$*"; } warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*"; } die() { printf "${RED}[ERR]${NC} %s\n" "$*"; exit 1; } # ── 0. Preflight ────────────────────────────────────────────────────────────── [ "$(id -u)" -eq 0 ] || die "Run as root: sh setup.sh" # Hard stop if somehow run on the Proxmox host itself. # pveversion is a binary that only exists on PVE hosts — not in any container. if command -v pveversion >/dev/null 2>&1; then printf "\n" die "This script must be run INSIDE the Alpine container, not on the Proxmox host. On the host, enter the container first: pct enter Then re-run setup.sh from inside it." fi # Make sure we're on Alpine (or at least warn loudly if not). if [ ! -f /etc/alpine-release ]; then warn "This does not look like an Alpine Linux system." warn "The apk commands below will fail on non-Alpine distros." printf "${YELLOW}Continue anyway? [y/N] ${NC}" read -r yn case "$yn" in [Yy]*) ;; *) die "Aborted." ;; esac fi # Confirm the source files are next to this script. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" [ -f "${SCRIPT_DIR}/web/app.py" ] || \ die "Cannot find web/app.py relative to setup.sh (expected at ${SCRIPT_DIR}/web/app.py). Make sure you extracted the full YAAR archive before running this." [ -f "${SCRIPT_DIR}/web/templates/index.html" ] || \ die "Cannot find web/templates/index.html relative to setup.sh." info "Running inside container as root — Proxmox host will not be touched." # ── 1. Bind-mount check ─────────────────────────────────────────────────────── info "Checking Jellyfin media mount at ${MEDIA_DIR}..." MOUNT_READY=1 if [ ! -d "${MEDIA_DIR}" ]; then warn "" warn "${MEDIA_DIR} does not exist inside this container." warn "YAAR will install fine, but no downloads will succeed until the mount is added." warn "" warn "To fix this (on the Proxmox host — not here):" warn " pct set -mp0 /mnt/nas/jellyfin,mp=${MEDIA_DIR}" warn " pct restart " warn "" warn "Or use the Proxmox web UI:" warn " Container → Resources → Add → Mount Point" warn " Host path: /mnt/nas/jellyfin Container path: ${MEDIA_DIR}" warn "" MOUNT_READY=0 elif [ ! -w "${MEDIA_DIR}" ]; then warn "${MEDIA_DIR} exists but this container cannot write to it." warn "Check the mp0 entry in Proxmox — 'ro=1' makes it read-only." MOUNT_READY=0 else success "${MEDIA_DIR} is mounted and writable" fi # ── 2. System packages ──────────────────────────────────────────────────────── info "Updating apk and installing system packages..." apk update --quiet apk add --quiet --no-progress \ python3 \ py3-pip \ py3-virtualenv \ ffmpeg \ curl \ ca-certificates \ openrc success "System packages installed" # ── 3. Application directories ──────────────────────────────────────────────── info "Creating application directories under ${YAAR_DIR}..." mkdir -p "${WEB_DIR}/templates" "${LOG_DIR}" "${JOB_DIR}" # MEDIA_DIR is a host bind-mount — never mkdir it from inside the container. # YAAR will create Movies/, Shows/, YouTube/ inside it on first archive. success "Directories ready" # ── 4. Copy application files ───────────────────────────────────────────────── info "Installing application files to ${WEB_DIR}..." cp "${SCRIPT_DIR}/web/app.py" "${WEB_DIR}/app.py" cp "${SCRIPT_DIR}/web/templates/index.html" "${WEB_DIR}/templates/index.html" success "Files installed" # ── 5. Python virtualenv + pip packages ────────────────────────────────────── info "Creating Python virtualenv at ${VENV_DIR}..." python3 -m venv "${VENV_DIR}" "${VENV_DIR}/bin/pip" install --quiet --upgrade pip "${VENV_DIR}/bin/pip" install --quiet \ "yt-dlp>=2024.1.0" \ "flask>=3.0.0" \ "gunicorn>=21.0.0" success "Python dependencies installed (yt-dlp, Flask, Gunicorn)" # ── 6. Environment config ───────────────────────────────────────────────────── info "Writing /etc/yaar.env..." cat > /etc/yaar.env < /etc/init.d/yaar <<'INITEOF' #!/sbin/openrc-run name="yaar" description="YAAR - YouTube Auto-Archiver and Retagger" YAAR_DIR="/opt/yaar" VENV_DIR="${YAAR_DIR}/venv" WEB_DIR="${YAAR_DIR}/web" LOG_DIR="${YAAR_DIR}/logs" command="${VENV_DIR}/bin/gunicorn" command_args="--workers 2 --bind 0.0.0.0:7474 --timeout 0 --log-file ${LOG_DIR}/gunicorn.log --access-logfile ${LOG_DIR}/access.log app:app" command_background="yes" pidfile="/run/yaar.pid" directory="${WEB_DIR}" depend() { need net after firewall } start_pre() { [ -f /etc/yaar.env ] && export $(grep -v '^#' /etc/yaar.env | xargs) || true checkpath --directory --owner root:root --mode 0755 "${LOG_DIR}" } INITEOF chmod +x /etc/init.d/yaar rc-update add yaar default success "OpenRC service installed and enabled at boot" # ── 8. Weekly yt-dlp auto-update via Alpine cron ───────────────────────────── info "Installing weekly yt-dlp auto-update cron..." cat > /etc/periodic/weekly/yt-dlp-update <> ${LOG_DIR}/yaar.log CRONEOF chmod +x /etc/periodic/weekly/yt-dlp-update success "Auto-update cron installed (/etc/periodic/weekly/yt-dlp-update)" # ── 9. Tool verification ────────────────────────────────────────────────────── info "Verifying ffmpeg..." ffmpeg -version 2>&1 | head -1 success "ffmpeg OK" info "Verifying yt-dlp..." "${VENV_DIR}/bin/yt-dlp" --version success "yt-dlp OK" # ── 10. Start service ───────────────────────────────────────────────────────── info "Starting YAAR service..." if rc-service yaar start 2>/dev/null; then success "YAAR service started" else warn "Service start returned an error — check: ${LOG_DIR}/gunicorn.log" warn "You can start it manually with: rc-service yaar start" fi # ── Done ────────────────────────────────────────────────────────────────────── HOST_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '/src/{print $7}' | head -1 || echo "") printf "\n" printf "${GREEN}╔══════════════════════════════════════════════════════╗${NC}\n" printf "${GREEN}║ YAAR installed successfully ║${NC}\n" printf "${GREEN}╠══════════════════════════════════════════════════════╣${NC}\n" printf "${GREEN}║ Web UI: http://%-33s║${NC}\n" "${HOST_IP}:${PORT}" printf "${GREEN}║ Logs: ${LOG_DIR}/yaar.log${NC}\n" printf "${GREEN}║ Service: rc-service yaar {start|stop|restart} ║${NC}\n" printf "${GREEN}╠══════════════════════════════════════════════════════╣${NC}\n" if [ "${MOUNT_READY}" = "0" ]; then printf "${YELLOW}║ ⚠ ${MEDIA_DIR} not ready. ║${NC}\n" printf "${YELLOW}║ Add mp0 in Proxmox and restart container. ║${NC}\n" printf "${YELLOW}║ YAAR will work automatically once mounted. ║${NC}\n" printf "${YELLOW}╚══════════════════════════════════════════════════════╝${NC}\n" else printf "${GREEN}║ Media: ${MEDIA_DIR}${NC}\n" printf "${GREEN}╚══════════════════════════════════════════════════════╝${NC}\n" fi printf "\n"