#!/usr/bin/env bash

# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
#
# This script manages a local cron job for automatic LXC container OS updates.
# The update script is downloaded once, displayed for review, and installed
# locally. Cron runs the local copy — no remote code execution at runtime.
#
# bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/cron-update-lxcs.sh)"

set -euo pipefail

REPO_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
SCRIPT_URL="${REPO_URL}/tools/pve/update-lxcs-cron.sh"
LOCAL_SCRIPT="/usr/local/bin/update-lxcs.sh"
CONF_FILE="/etc/update-lxcs.conf"
LOG_FILE="/var/log/update-lxcs-cron.log"
CRON_ENTRY="0 0 * * 0 ${LOCAL_SCRIPT} >>${LOG_FILE} 2>&1"

clear
cat <<"EOF"
   ______                    __  __          __      __          __   _  ________
  / ____/________  ____     / / / /___  ____/ /___ _/ /____     / /  | |/ / ____/____
 / /   / ___/ __ \/ __ \   / / / / __ \/ __  / __ `/ __/ _ \   / /   |   / /   / ___/
/ /___/ /  / /_/ / / / /  / /_/ / /_/ / /_/ / /_/ / /_/  __/  / /___/   / /___(__  )
\____/_/   \____/_/ /_/   \____/ .___/\__,_/\__,_/\__/\___/  /_____/_/|_\____/____/
                              /_/
EOF

info() { echo -e "\n \e[36m[Info]\e[0m $1"; }
ok() { echo -e " \e[32m[OK]\e[0m $1"; }
err() { echo -e " \e[31m[Error]\e[0m $1" >&2; }

confirm() {
  local prompt="${1:-Proceed?}"
  while true; do
    read -rp " ${prompt} (y/n): " yn
    case $yn in
    [Yy]*) return 0 ;;
    [Nn]*) return 1 ;;
    *) echo "  Please answer yes or no." ;;
    esac
  done
}

download_script() {
  local tmp
  tmp=$(mktemp)
  if ! curl -fsSL -o "$tmp" "$SCRIPT_URL"; then
    err "Failed to download script from:\n  ${SCRIPT_URL}"
    rm -f "$tmp"
    return 1
  fi
  echo "$tmp"
}

review_script() {
  local file="$1"
  local hash
  hash=$(sha256sum "$file" | awk '{print $1}')
  echo ""
  echo -e " \e[1;33m─── Script Content ───────────────────────────────────────────\e[0m"
  cat "$file"
  echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m"
  echo -e " \e[36mSHA256:\e[0m ${hash}"
  echo -e " \e[36mSource:\e[0m ${SCRIPT_URL}"
  echo ""
}

remove_legacy_cron() {
  if crontab -l -u root 2>/dev/null | grep -q "update-lxcs-cron.sh"; then
    (crontab -l -u root 2>/dev/null | grep -v "update-lxcs-cron.sh") | crontab -u root -
    ok "Removed legacy curl-based cron entry"
  fi
}

add() {
  info "Downloading update script..."
  local tmp
  tmp=$(download_script) || exit 1

  local hash
  hash=$(sha256sum "$tmp" | awk '{print $1}')
  echo ""
  echo -e " \e[1;33m─── Installation Summary ─────────────────────────────────────\e[0m"
  echo -e " \e[36mSource:\e[0m       ${SCRIPT_URL}"
  echo -e " \e[36mSHA256:\e[0m       ${hash}"
  echo -e " \e[36mInstall to:\e[0m   ${LOCAL_SCRIPT}"
  echo -e " \e[36mConfig:\e[0m       ${CONF_FILE}"
  echo -e " \e[36mLog file:\e[0m     ${LOG_FILE}"
  echo -e " \e[36mCron schedule:\e[0m Every Sunday at midnight (0 0 * * 0)"
  echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m"
  echo ""

  if confirm "Review script content before installing?"; then
    review_script "$tmp"
  fi

  if ! confirm "Install this script and activate cron schedule?"; then
    rm -f "$tmp"
    echo " Aborted."
    exit 0
  fi

  remove_legacy_cron

  install -m 0755 "$tmp" "$LOCAL_SCRIPT"
  rm -f "$tmp"
  ok "Installed script to ${LOCAL_SCRIPT}"

  if [[ ! -f "$CONF_FILE" ]]; then
    cat >"$CONF_FILE" <<'CONF'
# Configuration for automatic LXC container OS updates.
# Add container IDs to exclude from updates (comma-separated):
# EXCLUDE=100,101,102
EXCLUDE=
CONF
    ok "Created config ${CONF_FILE}"
  fi

  (
    crontab -l -u root 2>/dev/null | grep -v "${LOCAL_SCRIPT}" || true
    echo "${CRON_ENTRY}"
  ) | crontab -u root -
  ok "Added cron schedule: Every Sunday at midnight"
  echo ""
  echo -e " \e[36mLocal script:\e[0m ${LOCAL_SCRIPT}"
  echo -e " \e[36mConfig:\e[0m      ${CONF_FILE}"
  echo -e " \e[36mLog file:\e[0m    ${LOG_FILE}"
  echo ""
}

remove() {
  if crontab -l -u root 2>/dev/null | grep -q "${LOCAL_SCRIPT}"; then
    (crontab -l -u root 2>/dev/null | grep -v "${LOCAL_SCRIPT}") | crontab -u root -
    ok "Removed cron schedule"
  fi
  remove_legacy_cron
  [[ -f "$LOCAL_SCRIPT" ]] && rm -f "$LOCAL_SCRIPT" && ok "Removed ${LOCAL_SCRIPT}"
  [[ -f "$LOG_FILE" ]] && rm -f "$LOG_FILE" && ok "Removed ${LOG_FILE}"
  echo -e "\n Cron Update LXCs has been fully removed."
  echo -e " \e[90mNote: ${CONF_FILE} was kept (remove manually if desired).\e[0m"
}

update_script() {
  if [[ ! -f "$LOCAL_SCRIPT" ]]; then
    err "No local script found at ${LOCAL_SCRIPT}. Use 'Add' first."
    exit 1
  fi

  info "Downloading latest version..."
  local tmp
  tmp=$(download_script) || exit 1

  if command -v diff &>/dev/null; then
    local changes
    changes=$(diff --color=auto "$LOCAL_SCRIPT" "$tmp" 2>/dev/null || true)
    if [[ -z "$changes" ]]; then
      ok "Script is already up-to-date (no changes)."
      rm -f "$tmp"
      return
    fi
    echo ""
    echo -e " \e[1;33m─── Changes ──────────────────────────────────────────────────\e[0m"
    echo "$changes"
    echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m"
  else
    review_script "$tmp"
  fi

  local new_hash old_hash
  new_hash=$(sha256sum "$tmp" | awk '{print $1}')
  old_hash=$(sha256sum "$LOCAL_SCRIPT" | awk '{print $1}')
  echo -e " \e[36mCurrent SHA256:\e[0m ${old_hash}"
  echo -e " \e[36mNew SHA256:\e[0m     ${new_hash}"
  echo ""

  if ! confirm "Apply update?"; then
    rm -f "$tmp"
    echo " Aborted."
    return
  fi

  install -m 0755 "$tmp" "$LOCAL_SCRIPT"
  rm -f "$tmp"
  ok "Updated ${LOCAL_SCRIPT}"
}

view_script() {
  if [[ ! -f "$LOCAL_SCRIPT" ]]; then
    err "No local script found at ${LOCAL_SCRIPT}. Use 'Add' first."
    exit 1
  fi

  local view_choice
  view_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "View Script" --menu "What do you want to view?" 12 60 3 \
    "Worker" "Installed update script (${LOCAL_SCRIPT##*/})" \
    "Cron" "Cron schedule & configuration" \
    "Both" "Show everything" \
    3>&1 1>&2 2>&3) || return 0

  case "$view_choice" in
  "Worker") view_worker_script ;;
  "Cron") view_cron_config ;;
  "Both") view_cron_config && echo "" && view_worker_script ;;
  esac
}

view_worker_script() {
  local hash
  hash=$(sha256sum "$LOCAL_SCRIPT" | awk '{print $1}')
  echo ""
  echo -e " \e[1;33m─── ${LOCAL_SCRIPT} ───\e[0m"
  cat "$LOCAL_SCRIPT"
  echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m"
  echo -e " \e[36mSHA256:\e[0m    ${hash}"
  echo -e " \e[36mInstalled:\e[0m $(stat -c '%y' "$LOCAL_SCRIPT" 2>/dev/null | cut -d. -f1)"
  echo ""
}

view_cron_config() {
  echo ""
  echo -e " \e[1;33m─── Cron Configuration ───────────────────────────────────────\e[0m"
  if crontab -l -u root 2>/dev/null | grep -q "${LOCAL_SCRIPT}"; then
    local entry
    entry=$(crontab -l -u root 2>/dev/null | grep "${LOCAL_SCRIPT}")
    echo -e " \e[36mCron entry:\e[0m  ${entry}"
    local schedule
    schedule=$(echo "$entry" | awk '{print $1,$2,$3,$4,$5}')
    echo -e " \e[36mSchedule:\e[0m    ${schedule} ($(cron_to_human "$schedule"))"
  else
    echo -e " \e[31mCron:\e[0m        Not configured"
  fi
  if [[ -f "$CONF_FILE" ]]; then
    echo -e " \e[36mConfig file:\e[0m ${CONF_FILE}"
    local excludes
    excludes=$(grep -oP '^\s*EXCLUDE\s*=\s*\K.*' "$CONF_FILE" 2>/dev/null || true)
    echo -e " \e[36mExcluded:\e[0m    ${excludes:-(none)}"
    echo ""
    echo -e " \e[90m--- ${CONF_FILE} ---\e[0m"
    cat "$CONF_FILE"
  else
    echo -e " \e[36mConfig file:\e[0m (not created yet)"
  fi
  if [[ -f "$LOG_FILE" ]]; then
    local log_size
    log_size=$(du -h "$LOG_FILE" | awk '{print $1}')
    echo -e " \e[36mLog file:\e[0m    ${LOG_FILE} (${log_size})"
  fi
  echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m"
  echo ""
}

cron_to_human() {
  local schedule="$1"
  case "$schedule" in
  "0 0 * * 0") echo "Every Sunday at midnight" ;;
  "0 0 * * *") echo "Daily at midnight" ;;
  "0 * * * *") echo "Every hour" ;;
  *) echo "Custom schedule" ;;
  esac
}

show_status() {
  echo ""
  if [[ -f "$LOCAL_SCRIPT" ]]; then
    local hash
    hash=$(sha256sum "$LOCAL_SCRIPT" | awk '{print $1}')
    ok "Script installed: ${LOCAL_SCRIPT}"
    echo -e "   \e[36mSHA256:\e[0m    ${hash}"
    echo -e "   \e[36mInstalled:\e[0m $(stat -c '%y' "$LOCAL_SCRIPT" 2>/dev/null | cut -d. -f1)"
  else
    err "Script not installed"
  fi

  if crontab -l -u root 2>/dev/null | grep -q "${LOCAL_SCRIPT}"; then
    local schedule
    schedule=$(crontab -l -u root 2>/dev/null | grep "${LOCAL_SCRIPT}" | awk '{print $1,$2,$3,$4,$5}')
    ok "Cron active: ${schedule}"
  else
    err "Cron not configured"
  fi

  if [[ -f "$CONF_FILE" ]]; then
    local excludes
    excludes=$(grep -oP '^\s*EXCLUDE\s*=\s*\K.*' "$CONF_FILE" 2>/dev/null || echo "(none)")
    echo -e "   \e[36mExcluded:\e[0m  ${excludes:-"(none)"}"
  fi

  if [[ -f "$LOG_FILE" ]]; then
    local log_size last_run
    log_size=$(du -h "$LOG_FILE" | awk '{print $1}')
    last_run=$(grep -oP '^\s+\K\w.*' "$LOG_FILE" | tail -1)
    echo -e "   \e[36mLog file:\e[0m  ${LOG_FILE} (${log_size})"
    [[ -n "${last_run:-}" ]] && echo -e "   \e[36mLast run:\e[0m  ${last_run}"
  else
    echo -e "   \e[36mLog file:\e[0m  (no runs yet)"
  fi
  echo ""
}

run_now() {
  if [[ ! -f "$LOCAL_SCRIPT" ]]; then
    err "No local script found at ${LOCAL_SCRIPT}. Use 'Add' first."
    exit 1
  fi
  info "Running update script now..."
  bash "$LOCAL_SCRIPT" | tee -a "$LOG_FILE"
  ok "Run completed. Log appended to ${LOG_FILE}"
}

rotate_log() {
  if [[ ! -f "$LOG_FILE" ]]; then
    info "No log file to rotate."
    return
  fi
  local log_size
  log_size=$(stat -c '%s' "$LOG_FILE" 2>/dev/null || echo 0)
  local log_size_h
  log_size_h=$(du -h "$LOG_FILE" | awk '{print $1}')
  if confirm "Rotate log file? (current size: ${log_size_h})"; then
    mv "$LOG_FILE" "${LOG_FILE}.old"
    ok "Rotated: ${LOG_FILE} → ${LOG_FILE}.old"
  fi
}

OPTIONS=(
  Add "Download, review & install cron schedule"
  Remove "Remove cron schedule & local script"
  Update "Update local script from repository"
  Status "Show installation status & last run"
  Run "Run update script now (manual trigger)"
  View "View cron config & installed script"
  Rotate "Rotate log file"
)

CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Cron Update LXCs" --menu "Select an option:" 16 68 7 \
  "${OPTIONS[@]}" 3>&1 1>&2 2>&3) || exit 0

case $CHOICE in
"Add") add ;;
"Remove") remove ;;
"Update") update_script ;;
"Status") show_status ;;
"Run") run_now ;;
"View") view_script ;;
"Rotate") rotate_log ;;
esac
