| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- #!/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 <CTID> -mp0 /mnt/nas/jellyfin,mp=/nas/jellyfin
- # pct restart <CTID>
- #
- # 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@<container-ip>:/root/
- # - pct push <CTID> 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 <CTID>
- 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 <CTID> -mp0 /mnt/nas/jellyfin,mp=${MEDIA_DIR}"
- warn " pct restart <CTID>"
- 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 <<ENVEOF
- YAAR_BASE=${YAAR_DIR}
- YAAR_MEDIA=${MEDIA_DIR}
- YAAR_PORT=${PORT}
- ENVEOF
- success "/etc/yaar.env written"
- # ── 7. OpenRC init script ─────────────────────────────────────────────────────
- info "Installing OpenRC service (/etc/init.d/yaar)..."
- cat > /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 <<CRONEOF
- #!/bin/sh
- ${VENV_DIR}/bin/pip install --quiet --upgrade yt-dlp && \
- printf "[YAAR] yt-dlp updated to \$(${VENV_DIR}/bin/yt-dlp --version)\\n" >> ${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 "<container-ip>")
- 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"
|