setup.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. #!/bin/sh
  2. # YAAR — YouTube Auto-Archiver and Retagger
  3. # ─────────────────────────────────────────
  4. # Run this script INSIDE your existing Alpine Linux LXC container as root.
  5. # It does not touch the Proxmox host in any way.
  6. #
  7. # BEFORE running this, do one thing in Proxmox to attach your Jellyfin media:
  8. # Proxmox web UI → your container → Resources → Add → Mount Point
  9. # Host path: /mnt/nas/jellyfin (wherever your NAS is mounted on the host)
  10. # Container path: /nas/jellyfin
  11. # Read-only: No
  12. # Then restart the container.
  13. #
  14. # Or via the Proxmox shell (not this container — the host shell):
  15. # pct set <CTID> -mp0 /mnt/nas/jellyfin,mp=/nas/jellyfin
  16. # pct restart <CTID>
  17. #
  18. # After that, get this script into the container any way you like:
  19. # - Copy from the Proxmox web UI "Upload" button
  20. # - scp yaar.tar.gz root@<container-ip>:/root/
  21. # - pct push <CTID> yaar.tar.gz /root/yaar.tar.gz (from Proxmox host only)
  22. #
  23. # Then inside the container:
  24. # tar xzf /root/yaar.tar.gz -C /root/
  25. # sh /root/yaar/setup.sh
  26. # ─────────────────────────────────────────────────────────────────────────────
  27. set -e
  28. YAAR_DIR="/opt/yaar"
  29. MEDIA_DIR="/nas/jellyfin" # Must match the mp0 mount point set in Proxmox
  30. WEB_DIR="${YAAR_DIR}/web"
  31. VENV_DIR="${YAAR_DIR}/venv"
  32. LOG_DIR="${YAAR_DIR}/logs"
  33. JOB_DIR="${YAAR_DIR}/jobs"
  34. PORT=7474
  35. RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[0;33m'; NC='\033[0m'
  36. info() { printf "${CYAN}[YAAR]${NC} %s\n" "$*"; }
  37. success() { printf "${GREEN}[OK]${NC} %s\n" "$*"; }
  38. warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*"; }
  39. die() { printf "${RED}[ERR]${NC} %s\n" "$*"; exit 1; }
  40. # ── 0. Preflight ──────────────────────────────────────────────────────────────
  41. [ "$(id -u)" -eq 0 ] || die "Run as root: sh setup.sh"
  42. # Hard stop if somehow run on the Proxmox host itself.
  43. # pveversion is a binary that only exists on PVE hosts — not in any container.
  44. if command -v pveversion >/dev/null 2>&1; then
  45. printf "\n"
  46. die "This script must be run INSIDE the Alpine container, not on the Proxmox host.
  47. On the host, enter the container first:
  48. pct enter <CTID>
  49. Then re-run setup.sh from inside it."
  50. fi
  51. # Make sure we're on Alpine (or at least warn loudly if not).
  52. if [ ! -f /etc/alpine-release ]; then
  53. warn "This does not look like an Alpine Linux system."
  54. warn "The apk commands below will fail on non-Alpine distros."
  55. printf "${YELLOW}Continue anyway? [y/N] ${NC}"
  56. read -r yn
  57. case "$yn" in
  58. [Yy]*) ;;
  59. *) die "Aborted." ;;
  60. esac
  61. fi
  62. # Confirm the source files are next to this script.
  63. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  64. [ -f "${SCRIPT_DIR}/web/app.py" ] || \
  65. die "Cannot find web/app.py relative to setup.sh (expected at ${SCRIPT_DIR}/web/app.py).
  66. Make sure you extracted the full YAAR archive before running this."
  67. [ -f "${SCRIPT_DIR}/web/templates/index.html" ] || \
  68. die "Cannot find web/templates/index.html relative to setup.sh."
  69. info "Running inside container as root — Proxmox host will not be touched."
  70. # ── 1. Bind-mount check ───────────────────────────────────────────────────────
  71. info "Checking Jellyfin media mount at ${MEDIA_DIR}..."
  72. MOUNT_READY=1
  73. if [ ! -d "${MEDIA_DIR}" ]; then
  74. warn ""
  75. warn "${MEDIA_DIR} does not exist inside this container."
  76. warn "YAAR will install fine, but no downloads will succeed until the mount is added."
  77. warn ""
  78. warn "To fix this (on the Proxmox host — not here):"
  79. warn " pct set <CTID> -mp0 /mnt/nas/jellyfin,mp=${MEDIA_DIR}"
  80. warn " pct restart <CTID>"
  81. warn ""
  82. warn "Or use the Proxmox web UI:"
  83. warn " Container → Resources → Add → Mount Point"
  84. warn " Host path: /mnt/nas/jellyfin Container path: ${MEDIA_DIR}"
  85. warn ""
  86. MOUNT_READY=0
  87. elif [ ! -w "${MEDIA_DIR}" ]; then
  88. warn "${MEDIA_DIR} exists but this container cannot write to it."
  89. warn "Check the mp0 entry in Proxmox — 'ro=1' makes it read-only."
  90. MOUNT_READY=0
  91. else
  92. success "${MEDIA_DIR} is mounted and writable"
  93. fi
  94. # ── 2. System packages ────────────────────────────────────────────────────────
  95. info "Updating apk and installing system packages..."
  96. apk update --quiet
  97. apk add --quiet --no-progress \
  98. python3 \
  99. py3-pip \
  100. py3-virtualenv \
  101. ffmpeg \
  102. curl \
  103. ca-certificates \
  104. openrc
  105. success "System packages installed"
  106. # ── 3. Application directories ────────────────────────────────────────────────
  107. info "Creating application directories under ${YAAR_DIR}..."
  108. mkdir -p "${WEB_DIR}/templates" "${LOG_DIR}" "${JOB_DIR}"
  109. # MEDIA_DIR is a host bind-mount — never mkdir it from inside the container.
  110. # YAAR will create Movies/, Shows/, YouTube/ inside it on first archive.
  111. success "Directories ready"
  112. # ── 4. Copy application files ─────────────────────────────────────────────────
  113. info "Installing application files to ${WEB_DIR}..."
  114. cp "${SCRIPT_DIR}/web/app.py" "${WEB_DIR}/app.py"
  115. cp "${SCRIPT_DIR}/web/templates/index.html" "${WEB_DIR}/templates/index.html"
  116. success "Files installed"
  117. # ── 4b. Install update script + convenience command ───────────────────────────
  118. if [ -f "${SCRIPT_DIR}/update.sh" ]; then
  119. info "Installing update script to ${YAAR_DIR}/update.sh..."
  120. cp "${SCRIPT_DIR}/update.sh" "${YAAR_DIR}/update.sh"
  121. chmod +x "${YAAR_DIR}/update.sh"
  122. # Convenience command: run `yaar-update` from anywhere.
  123. cat > /usr/local/bin/yaar-update <<'WRAPEOF'
  124. #!/bin/sh
  125. exec sh /opt/yaar/update.sh "$@"
  126. WRAPEOF
  127. chmod +x /usr/local/bin/yaar-update
  128. success "Update script installed — run 'yaar-update' anytime"
  129. else
  130. warn "update.sh not found in archive — skipping (you can add it later)"
  131. fi
  132. # ── 5. Python virtualenv + pip packages ──────────────────────────────────────
  133. info "Creating Python virtualenv at ${VENV_DIR}..."
  134. python3 -m venv "${VENV_DIR}"
  135. "${VENV_DIR}/bin/pip" install --quiet --upgrade pip
  136. "${VENV_DIR}/bin/pip" install --quiet \
  137. "yt-dlp>=2024.1.0" \
  138. "flask>=3.0.0" \
  139. "gunicorn>=21.0.0"
  140. success "Python dependencies installed (yt-dlp, Flask, Gunicorn)"
  141. # ── 6. Environment config ─────────────────────────────────────────────────────
  142. info "Writing /etc/yaar.env..."
  143. cat > /etc/yaar.env <<ENVEOF
  144. YAAR_BASE=${YAAR_DIR}
  145. YAAR_MEDIA=${MEDIA_DIR}
  146. YAAR_PORT=${PORT}
  147. ENVEOF
  148. success "/etc/yaar.env written"
  149. # ── 7. OpenRC init script ─────────────────────────────────────────────────────
  150. info "Installing OpenRC service (/etc/init.d/yaar)..."
  151. cat > /etc/init.d/yaar <<'INITEOF'
  152. #!/sbin/openrc-run
  153. name="yaar"
  154. description="YAAR - YouTube Auto-Archiver and Retagger"
  155. YAAR_DIR="/opt/yaar"
  156. VENV_DIR="${YAAR_DIR}/venv"
  157. WEB_DIR="${YAAR_DIR}/web"
  158. LOG_DIR="${YAAR_DIR}/logs"
  159. command="${VENV_DIR}/bin/gunicorn"
  160. 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"
  161. command_background="yes"
  162. pidfile="/run/yaar.pid"
  163. directory="${WEB_DIR}"
  164. depend() {
  165. need net
  166. after firewall
  167. }
  168. start_pre() {
  169. [ -f /etc/yaar.env ] && export $(grep -v '^#' /etc/yaar.env | xargs) || true
  170. checkpath --directory --owner root:root --mode 0755 "${LOG_DIR}"
  171. }
  172. INITEOF
  173. chmod +x /etc/init.d/yaar
  174. rc-update add yaar default
  175. success "OpenRC service installed and enabled at boot"
  176. # ── 8. Weekly yt-dlp auto-update via Alpine cron ─────────────────────────────
  177. info "Installing weekly yt-dlp auto-update cron..."
  178. cat > /etc/periodic/weekly/yt-dlp-update <<CRONEOF
  179. #!/bin/sh
  180. ${VENV_DIR}/bin/pip install --quiet --upgrade yt-dlp && \
  181. printf "[YAAR] yt-dlp updated to \$(${VENV_DIR}/bin/yt-dlp --version)\\n" >> ${LOG_DIR}/yaar.log
  182. CRONEOF
  183. chmod +x /etc/periodic/weekly/yt-dlp-update
  184. success "Auto-update cron installed (/etc/periodic/weekly/yt-dlp-update)"
  185. # ── 9. Tool verification ──────────────────────────────────────────────────────
  186. info "Verifying ffmpeg..."
  187. ffmpeg -version 2>&1 | head -1
  188. success "ffmpeg OK"
  189. info "Verifying yt-dlp..."
  190. "${VENV_DIR}/bin/yt-dlp" --version
  191. success "yt-dlp OK"
  192. # ── 10. Start service ─────────────────────────────────────────────────────────
  193. info "Starting YAAR service..."
  194. if rc-service yaar start 2>/dev/null; then
  195. success "YAAR service started"
  196. else
  197. warn "Service start returned an error — check: ${LOG_DIR}/gunicorn.log"
  198. warn "You can start it manually with: rc-service yaar start"
  199. fi
  200. # ── Done ──────────────────────────────────────────────────────────────────────
  201. HOST_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '/src/{print $7}' | head -1 || echo "<container-ip>")
  202. printf "\n"
  203. printf "${GREEN}╔══════════════════════════════════════════════════════╗${NC}\n"
  204. printf "${GREEN}║ YAAR installed successfully ║${NC}\n"
  205. printf "${GREEN}╠══════════════════════════════════════════════════════╣${NC}\n"
  206. printf "${GREEN}║ Web UI: http://%-33s║${NC}\n" "${HOST_IP}:${PORT}"
  207. printf "${GREEN}║ Logs: ${LOG_DIR}/yaar.log${NC}\n"
  208. printf "${GREEN}║ Service: rc-service yaar {start|stop|restart} ║${NC}\n"
  209. printf "${GREEN}║ Update: yaar-update (pulls latest, redeploys) ║${NC}\n"
  210. printf "${GREEN}╠══════════════════════════════════════════════════════╣${NC}\n"
  211. if [ "${MOUNT_READY}" = "0" ]; then
  212. printf "${YELLOW}║ ⚠ ${MEDIA_DIR} not ready. ║${NC}\n"
  213. printf "${YELLOW}║ Add mp0 in Proxmox and restart container. ║${NC}\n"
  214. printf "${YELLOW}║ YAAR will work automatically once mounted. ║${NC}\n"
  215. printf "${YELLOW}╚══════════════════════════════════════════════════════╝${NC}\n"
  216. else
  217. printf "${GREEN}║ Media: ${MEDIA_DIR}${NC}\n"
  218. printf "${GREEN}╚══════════════════════════════════════════════════════╝${NC}\n"
  219. fi
  220. printf "\n"