update.sh 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. #!/bin/sh
  2. # YAAR — Update script
  3. # ─────────────────────────────────────────────────────────────────────────────
  4. # Pulls the latest code from master, redeploys the app, and restarts the
  5. # service. Runs INSIDE the Alpine LXC container as root. Does not touch 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: the current app.py and index.html are backed up before overwriting.
  18. # If the service fails to start on the new code, the previous version is
  19. # automatically restored and the service restarted, so YAAR is never left down.
  20. # ─────────────────────────────────────────────────────────────────────────────
  21. set -e
  22. GOGS_ARCHIVE="https://gogs.av2x.dev/av2x/yaar/archive/master.tar.gz"
  23. YAAR_DIR="/opt/yaar"
  24. WEB_DIR="${YAAR_DIR}/web"
  25. VENV_DIR="${YAAR_DIR}/venv"
  26. LOG_DIR="${YAAR_DIR}/logs"
  27. BACKUP_DIR="${YAAR_DIR}/backups"
  28. PORT=7474
  29. RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[0;33m'; NC='\033[0m'
  30. info() { printf "${CYAN}[YAAR]${NC} %s\n" "$*"; }
  31. success() { printf "${GREEN}[OK]${NC} %s\n" "$*"; }
  32. warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*"; }
  33. die() { printf "${RED}[ERR]${NC} %s\n" "$*"; exit 1; }
  34. # ── Flags ─────────────────────────────────────────────────────────────────────
  35. DO_DEPS=0
  36. DO_REBOOT=0
  37. for arg in "$@"; do
  38. case "$arg" in
  39. --deps) DO_DEPS=1 ;;
  40. --reboot) DO_REBOOT=1 ;;
  41. -h|--help)
  42. grep '^#' "$0" | sed 's/^# \{0,1\}//'
  43. exit 0 ;;
  44. *) warn "Unknown flag: $arg (ignored)" ;;
  45. esac
  46. done
  47. # ── Preflight ─────────────────────────────────────────────────────────────────
  48. [ "$(id -u)" -eq 0 ] || die "Run as root: sh /opt/yaar/update.sh"
  49. command -v pveversion >/dev/null 2>&1 && \
  50. die "Run this INSIDE the container, not on the Proxmox host."
  51. [ -d "${YAAR_DIR}" ] || die "${YAAR_DIR} not found — is YAAR installed? Run setup.sh first."
  52. command -v curl >/dev/null 2>&1 || { info "Installing curl..."; apk add --no-cache curl >/dev/null; }
  53. # ── 1. Download latest ────────────────────────────────────────────────────────
  54. TMP_DIR="$(mktemp -d /tmp/yaar-update.XXXXXX)"
  55. trap 'rm -rf "${TMP_DIR}"' EXIT
  56. info "Downloading latest code from master..."
  57. curl -fsSL "${GOGS_ARCHIVE}" -o "${TMP_DIR}/yaar.tar.gz" \
  58. || die "Download failed. Check the URL and that the repo is reachable."
  59. tar -xzf "${TMP_DIR}/yaar.tar.gz" -C "${TMP_DIR}"
  60. # Locate the extracted source root (the folder containing web/app.py).
  61. SRC="$(dirname "$(find "${TMP_DIR}" -type f -path '*/web/app.py' | head -1)")/.."
  62. SRC="$(cd "${SRC}" 2>/dev/null && pwd || true)"
  63. [ -n "${SRC}" ] && [ -f "${SRC}/web/app.py" ] \
  64. || die "Could not find web/app.py in the downloaded archive."
  65. [ -f "${SRC}/web/templates/index.html" ] \
  66. || die "Could not find web/templates/index.html in the downloaded archive."
  67. success "Downloaded and extracted to ${SRC}"
  68. # ── 2. Back up current code ───────────────────────────────────────────────────
  69. STAMP="$(date -u +%Y-%m-%d_%H-%M-%S)"
  70. BACKUP="${BACKUP_DIR}/${STAMP}"
  71. mkdir -p "${BACKUP}/web/templates"
  72. info "Backing up current code to ${BACKUP}..."
  73. [ -f "${WEB_DIR}/app.py" ] && cp "${WEB_DIR}/app.py" "${BACKUP}/web/app.py"
  74. [ -f "${WEB_DIR}/templates/index.html" ] && cp "${WEB_DIR}/templates/index.html" "${BACKUP}/web/templates/index.html"
  75. success "Backup saved"
  76. # Keep only the 5 most recent backups.
  77. if [ -d "${BACKUP_DIR}" ]; then
  78. ls -1dt "${BACKUP_DIR}"/*/ 2>/dev/null | tail -n +6 | while read -r old; do
  79. rm -rf "${old}"
  80. done
  81. fi
  82. # ── 3. Deploy new code ────────────────────────────────────────────────────────
  83. info "Deploying new code..."
  84. cp "${SRC}/web/app.py" "${WEB_DIR}/app.py"
  85. cp "${SRC}/web/templates/index.html" "${WEB_DIR}/templates/index.html"
  86. # Refresh the update script itself if it changed (so next run uses newest logic).
  87. [ -f "${SRC}/update.sh" ] && cp "${SRC}/update.sh" "${YAAR_DIR}/update.sh" && chmod +x "${YAAR_DIR}/update.sh"
  88. success "New code deployed"
  89. # ── 4. Optional dependency upgrade ────────────────────────────────────────────
  90. if [ "${DO_DEPS}" = "1" ]; then
  91. info "Upgrading Python dependencies (yt-dlp, Flask, Gunicorn)..."
  92. "${VENV_DIR}/bin/pip" install --quiet --upgrade yt-dlp flask gunicorn
  93. success "Dependencies upgraded — yt-dlp $(${VENV_DIR}/bin/yt-dlp --version)"
  94. fi
  95. # ── 5. Restart service with auto-rollback ─────────────────────────────────────
  96. info "Restarting YAAR service..."
  97. rc-service yaar restart >/dev/null 2>&1 || true
  98. sleep 2
  99. # Health check — is the service actually listening on the port?
  100. healthy=0
  101. if rc-service yaar status 2>/dev/null | grep -q "started"; then
  102. healthy=1
  103. fi
  104. # Secondary check: HTTP response on the port
  105. if command -v curl >/dev/null 2>&1; then
  106. if curl -fsS -o /dev/null --max-time 5 "http://127.0.0.1:${PORT}/" 2>/dev/null; then
  107. healthy=1
  108. fi
  109. fi
  110. if [ "${healthy}" = "1" ]; then
  111. success "Service restarted and healthy on port ${PORT}"
  112. else
  113. warn "Service did not come up cleanly — rolling back to previous version..."
  114. [ -f "${BACKUP}/web/app.py" ] && cp "${BACKUP}/web/app.py" "${WEB_DIR}/app.py"
  115. [ -f "${BACKUP}/web/templates/index.html" ] && cp "${BACKUP}/web/templates/index.html" "${WEB_DIR}/templates/index.html"
  116. rc-service yaar restart >/dev/null 2>&1 || true
  117. sleep 2
  118. if rc-service yaar status 2>/dev/null | grep -q "started"; then
  119. warn "Rolled back to previous version. YAAR is running on the OLD code."
  120. warn "Check ${LOG_DIR}/gunicorn.log for why the new code failed."
  121. exit 1
  122. else
  123. die "Rollback also failed to start. Inspect ${LOG_DIR}/gunicorn.log manually."
  124. fi
  125. fi
  126. # ── 6. Optional container reboot ──────────────────────────────────────────────
  127. if [ "${DO_REBOOT}" = "1" ]; then
  128. warn "Rebooting the container in 3 seconds (Ctrl+C to cancel)..."
  129. sleep 3
  130. # Inside an LXC this signals Proxmox to restart the container.
  131. reboot
  132. exit 0
  133. fi
  134. # ── Done ──────────────────────────────────────────────────────────────────────
  135. HOST_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '/src/{print $7}' | head -1 || echo "<container-ip>")
  136. printf "\n"
  137. printf "${GREEN}╔══════════════════════════════════════════════════════╗${NC}\n"
  138. printf "${GREEN}║ YAAR updated successfully ║${NC}\n"
  139. printf "${GREEN}╠══════════════════════════════════════════════════════╣${NC}\n"
  140. printf "${GREEN}║ Web UI: http://%-34s║${NC}\n" "${HOST_IP}:${PORT}"
  141. printf "${GREEN}║ Backup: %-42s║${NC}\n" "${BACKUP}"
  142. printf "${GREEN}║ Rollback: cp ${BACKUP}/web/* back, then restart ║${NC}\n"
  143. printf "${GREEN}╚══════════════════════════════════════════════════════╝${NC}\n"
  144. printf "\n"
  145. info "Hard-refresh your browser (Ctrl+Shift+R) to load the new UI."