update.sh 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. #!/bin/sh
  2. # YAAR — Update script
  3. # ─────────────────────────────────────────────────────────────────────────────
  4. # Pulls the two app files directly from master, redeploys, and restarts the
  5. # service. Runs INSIDE the Alpine LXC container as root. Never touches the
  6. # Proxmox host.
  7. #
  8. # Usage (inside the container):
  9. # sh /opt/yaar/update.sh # update code + restart service
  10. # sh /opt/yaar/update.sh --deps # also upgrade yt-dlp / Flask / Gunicorn
  11. # sh /opt/yaar/update.sh --reboot # reboot the whole container after update
  12. # yaar-update # convenience alias (installed by setup.sh)
  13. #
  14. # One-liner (always fetches the newest update logic too):
  15. # curl -fsSL https://gogs.av2x.dev/av2x/yaar/raw/master/update.sh | sh
  16. #
  17. # Safety: current files are backed up before overwriting. New files are fetched
  18. # to a temp dir first and only moved into place if BOTH downloads succeed, so a
  19. # failed pull can't leave a half-updated deploy. If the service fails its health
  20. # check on the new code, the previous version is restored automatically.
  21. # ─────────────────────────────────────────────────────────────────────────────
  22. set -e
  23. RAW="https://gogs.av2x.dev/av2x/yaar/raw/master"
  24. YAAR_DIR="/opt/yaar"
  25. WEB_DIR="${YAAR_DIR}/web"
  26. VENV_DIR="${YAAR_DIR}/venv"
  27. LOG_DIR="${YAAR_DIR}/logs"
  28. BACKUP_DIR="${YAAR_DIR}/backups"
  29. PORT=7474
  30. RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[0;33m'; NC='\033[0m'
  31. info() { printf "${CYAN}[YAAR]${NC} %s\n" "$*"; }
  32. success() { printf "${GREEN}[OK]${NC} %s\n" "$*"; }
  33. warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*"; }
  34. die() { printf "${RED}[ERR]${NC} %s\n" "$*"; exit 1; }
  35. # ── Flags ─────────────────────────────────────────────────────────────────────
  36. DO_DEPS=0
  37. DO_REBOOT=0
  38. for arg in "$@"; do
  39. case "$arg" in
  40. --deps) DO_DEPS=1 ;;
  41. --reboot) DO_REBOOT=1 ;;
  42. -h|--help)
  43. grep '^#' "$0" | sed 's/^# \{0,1\}//'
  44. exit 0 ;;
  45. *) warn "Unknown flag: $arg (ignored)" ;;
  46. esac
  47. done
  48. # ── Preflight ─────────────────────────────────────────────────────────────────
  49. [ "$(id -u)" -eq 0 ] || die "Run as root: sh /opt/yaar/update.sh"
  50. command -v pveversion >/dev/null 2>&1 && \
  51. die "Run this INSIDE the container, not on the Proxmox host."
  52. [ -d "${WEB_DIR}" ] || die "${WEB_DIR} not found — is YAAR installed? Run setup.sh first."
  53. command -v curl >/dev/null 2>&1 || { info "Installing curl..."; apk add --no-cache curl >/dev/null; }
  54. # ── 1. Fetch new files to a temp dir ──────────────────────────────────────────
  55. # Download both first; only deploy if BOTH succeed, so we never half-update.
  56. TMP_DIR="$(mktemp -d /tmp/yaar-update.XXXXXX)"
  57. trap 'rm -rf "${TMP_DIR}"' EXIT
  58. mkdir -p "${TMP_DIR}/templates"
  59. info "Fetching app.py..."
  60. curl -fsSL "${RAW}/web/app.py" -o "${TMP_DIR}/app.py" \
  61. || die "Failed to download app.py — nothing changed."
  62. info "Fetching index.html..."
  63. curl -fsSL "${RAW}/web/templates/index.html" -o "${TMP_DIR}/templates/index.html" \
  64. || die "Failed to download index.html — nothing changed."
  65. # Sanity: make sure we got real files, not an error page.
  66. [ -s "${TMP_DIR}/app.py" ] || die "Downloaded app.py is empty."
  67. head -c 64 "${TMP_DIR}/app.py" | grep -q "python\|import\|#" \
  68. || warn "app.py doesn't look like Python — continuing, health check will catch it."
  69. success "Both files downloaded"
  70. # ── 2. Back up current files ──────────────────────────────────────────────────
  71. STAMP="$(date -u +%Y-%m-%d_%H-%M-%S)"
  72. BACKUP="${BACKUP_DIR}/${STAMP}"
  73. mkdir -p "${BACKUP}/templates"
  74. info "Backing up current files to ${BACKUP}..."
  75. [ -f "${WEB_DIR}/app.py" ] && cp "${WEB_DIR}/app.py" "${BACKUP}/app.py"
  76. [ -f "${WEB_DIR}/templates/index.html" ] && cp "${WEB_DIR}/templates/index.html" "${BACKUP}/templates/index.html"
  77. success "Backup saved"
  78. # Keep only the 5 most recent backups.
  79. ls -1dt "${BACKUP_DIR}"/*/ 2>/dev/null | tail -n +6 | while read -r old; do rm -rf "${old}"; done
  80. # ── 3. Deploy (atomic move into place) ────────────────────────────────────────
  81. info "Deploying new files..."
  82. mv "${TMP_DIR}/app.py" "${WEB_DIR}/app.py"
  83. mv "${TMP_DIR}/templates/index.html" "${WEB_DIR}/templates/index.html"
  84. success "New files deployed"
  85. # Refresh this update script too, so the next run uses the newest logic.
  86. curl -fsSL "${RAW}/update.sh" -o "${YAAR_DIR}/update.sh" 2>/dev/null \
  87. && chmod +x "${YAAR_DIR}/update.sh" || true
  88. # ── 4. Optional dependency upgrade ────────────────────────────────────────────
  89. if [ "${DO_DEPS}" = "1" ]; then
  90. info "Upgrading Python dependencies (yt-dlp, Flask, Gunicorn)..."
  91. "${VENV_DIR}/bin/pip" install --quiet --upgrade yt-dlp flask gunicorn
  92. success "Dependencies upgraded — yt-dlp $(${VENV_DIR}/bin/yt-dlp --version)"
  93. fi
  94. # ── 5. Restart with health check + auto-rollback ──────────────────────────────
  95. info "Restarting YAAR service..."
  96. rc-service yaar restart >/dev/null 2>&1 || true
  97. sleep 2
  98. healthy=0
  99. rc-service yaar status 2>/dev/null | grep -q "started" && healthy=1
  100. if command -v curl >/dev/null 2>&1; then
  101. curl -fsS -o /dev/null --max-time 5 "http://127.0.0.1:${PORT}/" 2>/dev/null && healthy=1
  102. fi
  103. if [ "${healthy}" = "1" ]; then
  104. success "Service restarted and healthy on port ${PORT}"
  105. else
  106. warn "Service did not come up cleanly — rolling back..."
  107. [ -f "${BACKUP}/app.py" ] && cp "${BACKUP}/app.py" "${WEB_DIR}/app.py"
  108. [ -f "${BACKUP}/templates/index.html" ] && cp "${BACKUP}/templates/index.html" "${WEB_DIR}/templates/index.html"
  109. rc-service yaar restart >/dev/null 2>&1 || true
  110. sleep 2
  111. if rc-service yaar status 2>/dev/null | grep -q "started"; then
  112. warn "Rolled back — YAAR is running on the PREVIOUS version."
  113. warn "Check ${LOG_DIR}/gunicorn.log for why the new code failed."
  114. exit 1
  115. else
  116. die "Rollback also failed to start. Inspect ${LOG_DIR}/gunicorn.log manually."
  117. fi
  118. fi
  119. # ── 6. Optional container reboot ──────────────────────────────────────────────
  120. if [ "${DO_REBOOT}" = "1" ]; then
  121. warn "Rebooting the container in 3 seconds (Ctrl+C to cancel)..."
  122. sleep 3
  123. reboot
  124. exit 0
  125. fi
  126. # ── Done ──────────────────────────────────────────────────────────────────────
  127. HOST_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '/src/{print $7}' | head -1 || echo "<container-ip>")
  128. printf "\n"
  129. printf "${GREEN}╔══════════════════════════════════════════════════════╗${NC}\n"
  130. printf "${GREEN}║ YAAR updated successfully ║${NC}\n"
  131. printf "${GREEN}╠══════════════════════════════════════════════════════╣${NC}\n"
  132. printf "${GREEN}║ Web UI: http://%-34s║${NC}\n" "${HOST_IP}:${PORT}"
  133. printf "${GREEN}║ Backup: %-42s║${NC}\n" "${BACKUP}"
  134. printf "${GREEN}╚══════════════════════════════════════════════════════╝${NC}\n"
  135. printf "\n"
  136. info "Hard-refresh your browser (Ctrl+Shift+R) to load the new UI."