#!/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 # Raw file endpoint on Gogs. Used to fetch app files when they aren't already # present next to this script (i.e. when installing via the curl | sh one-liner). RAW="https://gogs.av2x.dev/av2x/yaar/raw/master" 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). # Read the prompt from the terminal, not stdin — stdin may be the piped script # when installing via `curl ... | sh`. 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." if [ -e /dev/tty ]; then printf "${YELLOW}Continue anyway? [y/N] ${NC}" read -r yn /dev/null && pwd || echo /nonexistent)" # fetch_or_copy # Uses the local file when present, otherwise downloads it from the repo. fetch_or_copy() { _rel="$1"; _dest="$2" if [ -f "${SCRIPT_DIR}/${_rel}" ]; then cp "${SCRIPT_DIR}/${_rel}" "${_dest}" else curl -fsSL "${RAW}/${_rel}" -o "${_dest}" \ || die "Could not fetch ${_rel} from ${RAW}" fi } info "Running inside container as root — Proxmox host will not be touched." # Ensure curl exists now — fetch_or_copy and the mount/version checks may need it # before the main package install step runs. if ! command -v curl >/dev/null 2>&1; then info "Installing curl (needed to fetch files)..." apk add --no-cache curl ca-certificates >/dev/null 2>&1 || \ die "Could not install curl. Check network/DNS in the container." fi # ── 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. Install application files ────────────────────────────────────────────── info "Installing application files to ${WEB_DIR}..." fetch_or_copy "web/app.py" "${WEB_DIR}/app.py" fetch_or_copy "web/templates/index.html" "${WEB_DIR}/templates/index.html" success "Files installed" # ── 4b. Install update script + convenience command ─────────────────────────── info "Installing update script to ${YAAR_DIR}/update.sh..." if fetch_or_copy "update.sh" "${YAAR_DIR}/update.sh" 2>/dev/null; then chmod +x "${YAAR_DIR}/update.sh" cat > /usr/local/bin/yaar-update <<'WRAPEOF' #!/bin/sh exec sh /opt/yaar/update.sh "$@" WRAPEOF chmod +x /usr/local/bin/yaar-update success "Update script installed — run 'yaar-update' anytime" else warn "Could not install update.sh — you can add it later" fi # ── 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}║ Update: yaar-update (pulls latest, redeploys) ║${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"