setup.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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. # Raw file endpoint on Gogs. Used to fetch app files when they aren't already
  36. # present next to this script (i.e. when installing via the curl | sh one-liner).
  37. RAW="https://gogs.av2x.dev/av2x/yaar/raw/master"
  38. RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[0;33m'; NC='\033[0m'
  39. info() { printf "${CYAN}[YAAR]${NC} %s\n" "$*"; }
  40. success() { printf "${GREEN}[OK]${NC} %s\n" "$*"; }
  41. warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*"; }
  42. die() { printf "${RED}[ERR]${NC} %s\n" "$*"; exit 1; }
  43. # ── 0. Preflight ──────────────────────────────────────────────────────────────
  44. [ "$(id -u)" -eq 0 ] || die "Run as root: sh setup.sh"
  45. # Hard stop if somehow run on the Proxmox host itself.
  46. # pveversion is a binary that only exists on PVE hosts — not in any container.
  47. if command -v pveversion >/dev/null 2>&1; then
  48. printf "\n"
  49. die "This script must be run INSIDE the Alpine container, not on the Proxmox host.
  50. On the host, enter the container first:
  51. pct enter <CTID>
  52. Then re-run setup.sh from inside it."
  53. fi
  54. # Make sure we're on Alpine (or at least warn loudly if not).
  55. # Read the prompt from the terminal, not stdin — stdin may be the piped script
  56. # when installing via `curl ... | sh`.
  57. if [ ! -f /etc/alpine-release ]; then
  58. warn "This does not look like an Alpine Linux system."
  59. warn "The apk commands below will fail on non-Alpine distros."
  60. if [ -e /dev/tty ]; then
  61. printf "${YELLOW}Continue anyway? [y/N] ${NC}"
  62. read -r yn </dev/tty
  63. case "$yn" in
  64. [Yy]*) ;;
  65. *) die "Aborted." ;;
  66. esac
  67. else
  68. die "Not Alpine and no terminal to confirm. Aborting."
  69. fi
  70. fi
  71. # Determine where app files come from. If this script was extracted alongside
  72. # the web/ folder (git clone / tarball), copy from disk. If it was piped in via
  73. # `curl ... | sh`, SCRIPT_DIR won't hold the files — fetch them from RAW instead.
  74. SCRIPT_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd || echo /nonexistent)"
  75. # fetch_or_copy <relative-path> <destination>
  76. # Uses the local file when present, otherwise downloads it from the repo.
  77. fetch_or_copy() {
  78. _rel="$1"; _dest="$2"
  79. if [ -f "${SCRIPT_DIR}/${_rel}" ]; then
  80. cp "${SCRIPT_DIR}/${_rel}" "${_dest}"
  81. else
  82. curl -fsSL "${RAW}/${_rel}" -o "${_dest}" \
  83. || die "Could not fetch ${_rel} from ${RAW}"
  84. fi
  85. }
  86. info "Running inside container as root — Proxmox host will not be touched."
  87. # Ensure curl exists now — fetch_or_copy and the mount/version checks may need it
  88. # before the main package install step runs.
  89. if ! command -v curl >/dev/null 2>&1; then
  90. info "Installing curl (needed to fetch files)..."
  91. apk add --no-cache curl ca-certificates >/dev/null 2>&1 || \
  92. die "Could not install curl. Check network/DNS in the container."
  93. fi
  94. # ── 1. Bind-mount check ───────────────────────────────────────────────────────
  95. info "Checking Jellyfin media mount at ${MEDIA_DIR}..."
  96. MOUNT_READY=1
  97. if [ ! -d "${MEDIA_DIR}" ]; then
  98. warn ""
  99. warn "${MEDIA_DIR} does not exist inside this container."
  100. warn "YAAR will install fine, but no downloads will succeed until the mount is added."
  101. warn ""
  102. warn "To fix this (on the Proxmox host — not here):"
  103. warn " pct set <CTID> -mp0 /mnt/nas/jellyfin,mp=${MEDIA_DIR}"
  104. warn " pct restart <CTID>"
  105. warn ""
  106. warn "Or use the Proxmox web UI:"
  107. warn " Container → Resources → Add → Mount Point"
  108. warn " Host path: /mnt/nas/jellyfin Container path: ${MEDIA_DIR}"
  109. warn ""
  110. MOUNT_READY=0
  111. elif [ ! -w "${MEDIA_DIR}" ]; then
  112. warn "${MEDIA_DIR} exists but this container cannot write to it."
  113. warn "Check the mp0 entry in Proxmox — 'ro=1' makes it read-only."
  114. MOUNT_READY=0
  115. else
  116. success "${MEDIA_DIR} is mounted and writable"
  117. fi
  118. # ── 2. System packages ────────────────────────────────────────────────────────
  119. info "Updating apk and installing system packages..."
  120. apk update --quiet
  121. apk add --quiet --no-progress \
  122. python3 \
  123. py3-pip \
  124. py3-virtualenv \
  125. ffmpeg \
  126. curl \
  127. ca-certificates \
  128. openrc
  129. success "System packages installed"
  130. # ── 3. Application directories ────────────────────────────────────────────────
  131. info "Creating application directories under ${YAAR_DIR}..."
  132. mkdir -p "${WEB_DIR}/templates" "${LOG_DIR}" "${JOB_DIR}"
  133. # MEDIA_DIR is a host bind-mount — never mkdir it from inside the container.
  134. # YAAR will create Movies/, Shows/, YouTube/ inside it on first archive.
  135. success "Directories ready"
  136. # ── 4. Install application files ──────────────────────────────────────────────
  137. info "Installing application files to ${WEB_DIR}..."
  138. fetch_or_copy "web/app.py" "${WEB_DIR}/app.py"
  139. fetch_or_copy "web/templates/index.html" "${WEB_DIR}/templates/index.html"
  140. success "Files installed"
  141. # ── 4b. Install update script + convenience command ───────────────────────────
  142. info "Installing update script to ${YAAR_DIR}/update.sh..."
  143. if fetch_or_copy "update.sh" "${YAAR_DIR}/update.sh" 2>/dev/null; then
  144. chmod +x "${YAAR_DIR}/update.sh"
  145. cat > /usr/local/bin/yaar-update <<'WRAPEOF'
  146. #!/bin/sh
  147. exec sh /opt/yaar/update.sh "$@"
  148. WRAPEOF
  149. chmod +x /usr/local/bin/yaar-update
  150. success "Update script installed — run 'yaar-update' anytime"
  151. else
  152. warn "Could not install update.sh — you can add it later"
  153. fi
  154. # ── 5. Python virtualenv + pip packages ──────────────────────────────────────
  155. info "Creating Python virtualenv at ${VENV_DIR}..."
  156. python3 -m venv "${VENV_DIR}"
  157. "${VENV_DIR}/bin/pip" install --quiet --upgrade pip
  158. "${VENV_DIR}/bin/pip" install --quiet \
  159. "yt-dlp>=2024.1.0" \
  160. "flask>=3.0.0" \
  161. "gunicorn>=21.0.0"
  162. success "Python dependencies installed (yt-dlp, Flask, Gunicorn)"
  163. # ── 6. Environment config ─────────────────────────────────────────────────────
  164. info "Writing /etc/yaar.env..."
  165. cat > /etc/yaar.env <<ENVEOF
  166. YAAR_BASE=${YAAR_DIR}
  167. YAAR_MEDIA=${MEDIA_DIR}
  168. YAAR_PORT=${PORT}
  169. ENVEOF
  170. success "/etc/yaar.env written"
  171. # ── 7. OpenRC init script ─────────────────────────────────────────────────────
  172. info "Installing OpenRC service (/etc/init.d/yaar)..."
  173. cat > /etc/init.d/yaar <<'INITEOF'
  174. #!/sbin/openrc-run
  175. name="yaar"
  176. description="YAAR - YouTube Auto-Archiver and Retagger"
  177. YAAR_DIR="/opt/yaar"
  178. VENV_DIR="${YAAR_DIR}/venv"
  179. WEB_DIR="${YAAR_DIR}/web"
  180. LOG_DIR="${YAAR_DIR}/logs"
  181. command="${VENV_DIR}/bin/gunicorn"
  182. 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"
  183. command_background="yes"
  184. pidfile="/run/yaar.pid"
  185. directory="${WEB_DIR}"
  186. depend() {
  187. need net
  188. after firewall
  189. }
  190. start_pre() {
  191. [ -f /etc/yaar.env ] && export $(grep -v '^#' /etc/yaar.env | xargs) || true
  192. checkpath --directory --owner root:root --mode 0755 "${LOG_DIR}"
  193. }
  194. INITEOF
  195. chmod +x /etc/init.d/yaar
  196. rc-update add yaar default
  197. success "OpenRC service installed and enabled at boot"
  198. # ── 8. Weekly yt-dlp auto-update via Alpine cron ─────────────────────────────
  199. info "Installing weekly yt-dlp auto-update cron..."
  200. cat > /etc/periodic/weekly/yt-dlp-update <<CRONEOF
  201. #!/bin/sh
  202. ${VENV_DIR}/bin/pip install --quiet --upgrade yt-dlp && \
  203. printf "[YAAR] yt-dlp updated to \$(${VENV_DIR}/bin/yt-dlp --version)\\n" >> ${LOG_DIR}/yaar.log
  204. CRONEOF
  205. chmod +x /etc/periodic/weekly/yt-dlp-update
  206. success "Auto-update cron installed (/etc/periodic/weekly/yt-dlp-update)"
  207. # ── 9. Tool verification ──────────────────────────────────────────────────────
  208. info "Verifying ffmpeg..."
  209. ffmpeg -version 2>&1 | head -1
  210. success "ffmpeg OK"
  211. info "Verifying yt-dlp..."
  212. "${VENV_DIR}/bin/yt-dlp" --version
  213. success "yt-dlp OK"
  214. # ── 10. Start service ─────────────────────────────────────────────────────────
  215. info "Starting YAAR service..."
  216. if rc-service yaar start 2>/dev/null; then
  217. success "YAAR service started"
  218. else
  219. warn "Service start returned an error — check: ${LOG_DIR}/gunicorn.log"
  220. warn "You can start it manually with: rc-service yaar start"
  221. fi
  222. # ── Done ──────────────────────────────────────────────────────────────────────
  223. HOST_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '/src/{print $7}' | head -1 || echo "<container-ip>")
  224. printf "\n"
  225. printf "${GREEN}╔══════════════════════════════════════════════════════╗${NC}\n"
  226. printf "${GREEN}║ YAAR installed successfully ║${NC}\n"
  227. printf "${GREEN}╠══════════════════════════════════════════════════════╣${NC}\n"
  228. printf "${GREEN}║ Web UI: http://%-33s║${NC}\n" "${HOST_IP}:${PORT}"
  229. printf "${GREEN}║ Logs: ${LOG_DIR}/yaar.log${NC}\n"
  230. printf "${GREEN}║ Service: rc-service yaar {start|stop|restart} ║${NC}\n"
  231. printf "${GREEN}║ Update: yaar-update (pulls latest, redeploys) ║${NC}\n"
  232. printf "${GREEN}╠══════════════════════════════════════════════════════╣${NC}\n"
  233. if [ "${MOUNT_READY}" = "0" ]; then
  234. printf "${YELLOW}║ ⚠ ${MEDIA_DIR} not ready. ║${NC}\n"
  235. printf "${YELLOW}║ Add mp0 in Proxmox and restart container. ║${NC}\n"
  236. printf "${YELLOW}║ YAAR will work automatically once mounted. ║${NC}\n"
  237. printf "${YELLOW}╚══════════════════════════════════════════════════════╝${NC}\n"
  238. else
  239. printf "${GREEN}║ Media: ${MEDIA_DIR}${NC}\n"
  240. printf "${GREEN}╚══════════════════════════════════════════════════════╝${NC}\n"
  241. fi
  242. printf "\n"