Ad Widget

Collapse

Zabbix Upgrade Script

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • linuxgurugamer
    Member
    • Oct 2010
    • 68

    #1

    Zabbix Upgrade Script

    I was upgrading my Zabbix install to the latest, and decided to write an upgrade script to automate all the suggested steps. With a bit of help from ChatGPT, I came up with the following. It worked well, and should work for newer versions of Zabbix. The only thing which needs to be set would be the ZABBIX_MAJOR version, if it's different than 7.4

    HTML Code:
    #!/usr/bin/env bash
    set -euo pipefail
    
    # Zabbix upgrade helper for RHEL 8/9:
    # - Role detection (agent2/server/proxy/web)
    # - Stop server/proxy before DB backup (then ALWAYS restart after backup)
    # - Back up DB + configs + web/PHP + binaries
    # - Upgrade detected roles
    # - DRY-RUN mode
    # - backup-only / upgrade-only modes
    #
    # Usage:
    #   sudo ./upgrade-zabbix.sh
    #   sudo ./upgrade-zabbix.sh --dry-run
    #   sudo ./upgrade-zabbix.sh --backup-only
    #   sudo ./upgrade-zabbix.sh --upgrade-only
    #
    # Env overrides:
    #   ZABBIX_MAJOR=6.0|6.4|7.0|7.4
    #   BACKUP_DIR=/var/backups/zabbix
    #   SKIP_DB_BACKUP=1
    #   DRY_RUN=1
    
    ZABBIX_MAJOR="${ZABBIX_MAJOR:-7.4}"
    BACKUP_DIR="${BACKUP_DIR:-/var/backups/zabbix}"
    SKIP_DB_BACKUP="${SKIP_DB_BACKUP:-0}"
    DRY_RUN="${DRY_RUN:-0}"
    
    DO_BACKUP=1
    DO_UPGRADE=1
    
    PKG_MANAGER=""
    RHEL_MAJOR=""
    
    HAS_AGENT2=0
    HAS_SERVER=0
    HAS_PROXY=0
    HAS_WEB=0
    
    # Track whether we stopped things during backup
    STOPPED_SERVER=0
    STOPPED_PROXY=0
    
    log() { printf "\n[%s] %s\n" "$(date +'%F %T')" "$*"; }
    die() { printf "\nERROR: %s\n" "$*" >&2; exit 1; }
    have_cmd() { command -v "$1" >/dev/null 2>&1; }
    ts_now() { date +'%Y%m%d-%H%M%S'; }
    
    run() {
      if [[ "$DRY_RUN" == "1" ]]; then
        printf "[DRY-RUN] %q" "$1"
        shift
        for a in "$@"; do printf " %q" "$a"; done
        printf "\n"
        return 0
      fi
      "$@"
    }
    
    run_sh() {
      local cmd="$1"
      if [[ "$DRY_RUN" == "1" ]]; then
        printf "[DRY-RUN] %s\n" "$cmd"
        return 0
      fi
      bash -c "$cmd"
    }
    
    require_root() { [[ "${EUID}" -eq 0 ]] || die "Run as root (use sudo)."; }
    
    detect_rhel() {
      [[ -r /etc/os-release ]] || die "/etc/os-release not found"
      # shellcheck disable=SC1091
      . /etc/os-release
    
      RHEL_MAJOR="$(rpm -E %rhel 2>/dev/null || true)"
      [[ "$RHEL_MAJOR" =~ ^[0-9]+$ ]] || die "Unable to detect RHEL major version."
      [[ "$RHEL_MAJOR" == "8" || "$RHEL_MAJOR" == "9" ]] || die "Supported: RHEL 8 or 9. Detected: RHEL ${RHEL_MAJOR}"
    
      if command -v dnf >/dev/null 2>&1; then
        PKG_MANAGER="dnf"
      elif command -v yum >/dev/null 2>&1; then
        PKG_MANAGER="yum"
      else
        die "Neither dnf nor yum found."
      fi
    }
    
    # -------------------------
    # Args
    # -------------------------
    parse_args() {
      while [[ "${1:-}" != "" ]]; do
        case "$1" in
          -n|--dry-run) DRY_RUN=1 ;;
          --backup-only) DO_BACKUP=1; DO_UPGRADE=0 ;;
          --upgrade-only) DO_BACKUP=0; DO_UPGRADE=1 ;;
          -h|--help)
            cat <<'EOF'
    Usage: sudo ./upgrade-zabbix.sh [options]
    
    Options:
      -n, --dry-run      Print actions; do not modify the system
      --backup-only      Perform backups only (no upgrade)
      --upgrade-only     Perform upgrade only (no backups)
      -h, --help         Show this help
    
    Environment overrides:
      ZABBIX_MAJOR=6.0|6.4|7.0   (default: 6.0)
      BACKUP_DIR=/var/backups/zabbix
      SKIP_DB_BACKUP=1
      DRY_RUN=1
    EOF
            exit 0
            ;;
          *) die "Unknown argument: $1" ;;
        esac
        shift
      done
    }
    
    # -------------------------
    # Role detection
    # -------------------------
    is_pkg_installed() { rpm -q "$1" >/dev/null 2>&1; }
    
    detect_roles() {
      if is_pkg_installed zabbix-agent2; then HAS_AGENT2=1; fi
      if is_pkg_installed zabbix-server-mysql || is_pkg_installed zabbix-server-pgsql; then HAS_SERVER=1; fi
      if is_pkg_installed zabbix-proxy-mysql || is_pkg_installed zabbix-proxy-pgsql || is_pkg_installed zabbix-proxy-sqlite3; then HAS_PROXY=1; fi
      if is_pkg_installed zabbix-web || is_pkg_installed zabbix-web-mysql || is_pkg_installed zabbix-web-pgsql; then HAS_WEB=1; fi
    
      log "Detected roles: agent2=${HAS_AGENT2} server=${HAS_SERVER} proxy=${HAS_PROXY} web=${HAS_WEB}"
    }
    
    # -------------------------
    # Service stop/start
    # -------------------------
    svc_exists() { systemctl list-unit-files --type=service 2>/dev/null | awk '{print $1}' | grep -qx "$1"; }
    
    stop_server_proxy_for_backup() {
      STOPPED_SERVER=0
      STOPPED_PROXY=0
    
      if [[ "$HAS_SERVER" == "1" ]] && svc_exists zabbix-server.service; then
        if systemctl is-active --quiet zabbix-server 2>/dev/null; then
          log "Stopping zabbix-server for backup"
          run systemctl stop zabbix-server
          STOPPED_SERVER=1
        else
          log "zabbix-server already stopped"
        fi
      fi
    
      if [[ "$HAS_PROXY" == "1" ]] && svc_exists zabbix-proxy.service; then
        if systemctl is-active --quiet zabbix-proxy 2>/dev/null; then
          log "Stopping zabbix-proxy for backup"
          run systemctl stop zabbix-proxy
          STOPPED_PROXY=1
        else
          log "zabbix-proxy already stopped"
        fi
      fi
    
      if [[ "$STOPPED_SERVER" == "1" || "$STOPPED_PROXY" == "1" ]]; then
        log "Server/proxy stopped for backup."
      else
        log "No running server/proxy services to stop."
      fi
    }
    
    restart_server_proxy_after_backup() {
      # Only restart services we actually stopped (avoids changing admin intent)
      if [[ "$STOPPED_PROXY" == "1" ]] && svc_exists zabbix-proxy.service; then
        log "Restarting zabbix-proxy after backup"
        run systemctl start zabbix-proxy
      fi
    
      if [[ "$STOPPED_SERVER" == "1" ]] && svc_exists zabbix-server.service; then
        log "Restarting zabbix-server after backup"
        run systemctl start zabbix-server
      fi
    }
    
    start_server_proxy_after_upgrade() {
      if [[ "$HAS_PROXY" == "1" ]] && svc_exists zabbix-proxy.service; then
        log "Enabling/starting zabbix-proxy"
        run systemctl enable --now zabbix-proxy || true
        run systemctl restart zabbix-proxy || true
      fi
    
      if [[ "$HAS_SERVER" == "1" ]] && svc_exists zabbix-server.service; then
        log "Enabling/starting zabbix-server"
        run systemctl enable --now zabbix-server || true
        run systemctl restart zabbix-server || true
      fi
    }
    
    # -------------------------
    # Backup helpers
    # -------------------------
    read_kv_from_conf() {
      local file="$1" key="$2"
      [[ -r "$file" ]] || return 1
      awk -F= -v k="$key" '
        $0 ~ "^[[:space:]]*#" {next}
        $1 ~ "^[[:space:]]*"k"[[:space:]]*$" {
          sub(/^[[:space:]]+|[[:space:]]+$/, "", $2);
          print $2; exit
        }' "$file"
    }
    
    detect_db_type() {
      if systemctl is-active --quiet mariadb 2>/dev/null || systemctl is-active --quiet mysqld 2>/dev/null; then
        echo "mysql"; return 0
      fi
      if systemctl is-active --quiet postgresql 2>/dev/null; then
        echo "pgsql"; return 0
      fi
      if have_cmd mysqldump; then echo "mysql"; return 0; fi
      if have_cmd pg_dump; then echo "pgsql"; return 0; fi
      return 1
    }
    
    backup_db_mysql() {
      local out_sql="$1"
      local zconf="/etc/zabbix/zabbix_server.conf"
      if [[ "$HAS_SERVER" != "1" && -r /etc/zabbix/zabbix_proxy.conf ]]; then
        zconf="/etc/zabbix/zabbix_proxy.conf"
      fi
    
      local dbhost dbname dbuser dbpass
      dbhost="$(read_kv_from_conf "$zconf" "DBHost" || true)"
      dbname="$(read_kv_from_conf "$zconf" "DBName" || true)"
      dbuser="$(read_kv_from_conf "$zconf" "DBUser" || true)"
      dbpass="$(read_kv_from_conf "$zconf" "DBPassword" || true)"
    
      dbhost="${dbhost:-localhost}"
      dbname="${dbname:-zabbix}"
      dbuser="${dbuser:-zabbix}"
    
      log "DB backup (MySQL/MariaDB): host=${dbhost} db=${dbname} user=${dbuser} (from ${zconf})"
      have_cmd mysqldump || die "mysqldump not found. Install mariadb/mysql client utilities or set SKIP_DB_BACKUP=1."
    
      local extra_opts=( "--host=${dbhost}" "--user=${dbuser}" "--single-transaction" "--routines" "--triggers" "--events" )
      if [[ -n "${dbpass}" ]]; then
        extra_opts+=( "--password=${dbpass}" )
      fi
    
      run_sh "mysqldump $(printf '%q ' "${extra_opts[@]}") $(printf '%q' "${dbname}") | gzip -9 > $(printf '%q' "${out_sql}.gz")"
    }
    
    backup_db_pgsql() {
      local out_base="$1"
      local zconf="/etc/zabbix/zabbix_server.conf"
      if [[ "$HAS_SERVER" != "1" && -r /etc/zabbix/zabbix_proxy.conf ]]; then
        zconf="/etc/zabbix/zabbix_proxy.conf"
      fi
    
      local dbhost dbname dbuser dbpass
      dbhost="$(read_kv_from_conf "$zconf" "DBHost" || true)"
      dbname="$(read_kv_from_conf "$zconf" "DBName" || true)"
      dbuser="$(read_kv_from_conf "$zconf" "DBUser" || true)"
      dbpass="$(read_kv_from_conf "$zconf" "DBPassword" || true)"
    
      dbhost="${dbhost:-localhost}"
      dbname="${dbname:-zabbix}"
      dbuser="${dbuser:-zabbix}"
    
      log "DB backup (PostgreSQL): host=${dbhost} db=${dbname} user=${dbuser} (from ${zconf})"
      have_cmd pg_dump || die "pg_dump not found. Install postgresql client utilities or set SKIP_DB_BACKUP=1."
    
      if [[ -n "${dbpass}" ]]; then
        run_sh "PGPASSWORD=$(printf '%q' "${dbpass}") pg_dump -h $(printf '%q' "${dbhost}") -U $(printf '%q' "${dbuser}") -Fc $(printf '%q' "${dbname}") > $(printf '%q' "${out_base}.dump")"
      else
        run_sh "pg_dump -h $(printf '%q' "${dbhost}") -U $(printf '%q' "${dbuser}") -Fc $(printf '%q' "${dbname}") > $(printf '%q' "${out_base}.dump")"
      fi
    }
    
    backup_files_tar() {
      local tar_path="$1"; shift
      local -a paths=("$@")
    
      local -a existing=()
      for p in "${paths[@]}"; do
        [[ -e "$p" ]] && existing+=("$p")
      done
    
      if [[ "${#existing[@]}" -eq 0 ]]; then
        log "No matching files/dirs found for tar: ${tar_path}"
        return 0
      fi
    
      log "Creating tar: ${tar_path}"
      run tar -cpf "$tar_path" --xattrs --acls --selinux "${existing[@]}" 2>/dev/null || run tar -cpf "$tar_path" "${existing[@]}"
    }
    
    backup_binaries() {
      local out_dir="$1"
    
      log "Backing up package inventory (RPM lists)"
      if [[ "$DRY_RUN" == "1" ]]; then
        echo "[DRY-RUN] rpm -qa | sort > ${out_dir}/rpm_all.txt"
        echo "[DRY-RUN] rpm -qa | grep -i '^zabbix' | sort > ${out_dir}/rpm_zabbix.txt"
        echo "[DRY-RUN] (would run rpm -ql for each installed zabbix RPM)"
      else
        rpm -qa | sort > "${out_dir}/rpm_all.txt"
        rpm -qa | grep -i '^zabbix' | sort > "${out_dir}/rpm_zabbix.txt" || true
        if [[ -s "${out_dir}/rpm_zabbix.txt" ]]; then
          while IFS= read -r pkg; do
            rpm -ql "$pkg" > "${out_dir}/rpm_ql_${pkg}.txt" || true
          done < "${out_dir}/rpm_zabbix.txt"
        fi
      fi
    
      log "Backing up common Zabbix binary locations (best-effort)"
      run mkdir -p "${out_dir}/bin"
    
      local -a bin_paths=(
        /usr/sbin/zabbix_server
        /usr/sbin/zabbix_proxy
        /usr/sbin/zabbix_agentd
        /usr/sbin/zabbix_agent2
        /usr/bin/zabbix_get
        /usr/bin/zabbix_sender
        /usr/bin/zabbix_js
      )
    
      for b in "${bin_paths[@]}"; do
        [[ -x "$b" ]] && run cp -a "$b" "${out_dir}/bin/" || true
      done
    }
    
    backup_all() {
      local ts backup_root final
      ts="$(ts_now)"
      backup_root="${BACKUP_DIR}/zabbix-backup-${ts}"
      final="${BACKUP_DIR}/zabbix-full-backup-${ts}.tar.gz"
    
      log "Creating backup set at: ${backup_root}"
      run mkdir -p "${backup_root}"
    
      stop_server_proxy_for_backup
    
      # Ensure we restart anything we stopped, even if the backup fails mid-way
      trap 'rc=$?; log "Backup phase exiting (rc=$rc) - attempting to restart server/proxy if we stopped them"; restart_server_proxy_after_backup || true; exit $rc' EXIT
    
      if [[ "${SKIP_DB_BACKUP}" == "1" ]]; then
        log "Skipping DB backup (SKIP_DB_BACKUP=1)"
      else
        local dbtype outbase
        dbtype="$(detect_db_type || true)"
        outbase="${backup_root}/db/zabbix-db-${ts}"
        run mkdir -p "${backup_root}/db"
    
        case "${dbtype:-}" in
          mysql) backup_db_mysql "${outbase}.sql" ;;
          pgsql) backup_db_pgsql "${outbase}" ;;
          *)
            log "Could not confidently detect DB type; trying MySQL first if mysqldump exists, else PostgreSQL."
            if have_cmd mysqldump; then
              backup_db_mysql "${outbase}.sql"
            elif have_cmd pg_dump; then
              backup_db_pgsql "${outbase}"
            else
              die "No DB dump tools found (mysqldump/pg_dump). Install client tools or set SKIP_DB_BACKUP=1."
            fi
            ;;
        esac
      fi
    
      log "Backing up Zabbix/service config files"
      backup_files_tar "${backup_root}/configs-${ts}.tar" \
        /etc/zabbix \
        /etc/httpd/conf.d/zabbix.conf \
        /etc/httpd/conf.d/zabbix*.conf \
        /etc/nginx/conf.d/zabbix*.conf \
        /etc/php-fpm.d \
        /etc/php.d \
        /etc/php.ini \
        /etc/opt/rh/rh-php*/php-fpm.d \
        /etc/opt/rh/rh-php*/php.d \
        /etc/opt/rh/rh-php*/php.ini \
        /etc/systemd/system/zabbix*.service \
        /usr/lib/systemd/system/zabbix*.service
    
      log "Backing up Zabbix web/PHP files"
      backup_files_tar "${backup_root}/web-${ts}.tar" \
        /usr/share/zabbix \
        /etc/zabbix/web \
        /var/lib/zabbix \
        /var/log/zabbix
    
      backup_binaries "${backup_root}"
    
      log "Creating consolidated archive: ${final}"
      run tar -C "${BACKUP_DIR}" -czf "${final}" "$(basename "${backup_root}")"
    
      log "Backup complete: ${final}"
    
      # Restart whatever we stopped, then remove the EXIT trap for the caller
      restart_server_proxy_after_backup
      trap - EXIT
    }
    
    # -------------------------
    # Upgrade steps
    # -------------------------
    install_zabbix_repo() {
      local repo_rpm="https://repo.zabbix.com/zabbix/${ZABBIX_MAJOR}/rhel/${RHEL_MAJOR}/x86_64/zabbix-release-latest-${ZABBIX_MAJOR}.el${RHEL_MAJOR}.noarch.rpm"
      log "Installing/updating Zabbix repo package: ${repo_rpm}"
      run rpm -Uvh --quiet "$repo_rpm"
    }
    
    refresh_metadata() {
      log "Refreshing package metadata"
      run "$PKG_MANAGER" -y clean all
      run "$PKG_MANAGER" -y makecache
    }
    
    upgrade_detected_roles() {
      log "Upgrading/installing detected Zabbix packages"
      run "$PKG_MANAGER" -y upgrade zabbix-release || true
    
      if [[ "$HAS_AGENT2" == "1" ]]; then
        run "$PKG_MANAGER" -y install zabbix-agent2 'zabbix-agent2-plugin-*' || true
        run "$PKG_MANAGER" -y upgrade  zabbix-agent2 'zabbix-agent2-plugin-*' zabbix-selinux-policy zabbix-get zabbix-sender || true
      fi
    
      if [[ "$HAS_SERVER" == "1" ]]; then
        run "$PKG_MANAGER" -y upgrade 'zabbix-server-*' zabbix-selinux-policy || true
      fi
    
      if [[ "$HAS_PROXY" == "1" ]]; then
        run "$PKG_MANAGER" -y upgrade 'zabbix-proxy-*' zabbix-selinux-policy || true
      fi
    
      if [[ "$HAS_WEB" == "1" ]]; then
        run "$PKG_MANAGER" -y upgrade 'zabbix-web*' || true
      fi
    }
    
    restart_services_after_upgrade() {
      log "Restarting detected services (post-upgrade)"
      run systemctl daemon-reload || true
    
      if [[ "$HAS_AGENT2" == "1" ]] && svc_exists zabbix-agent2.service; then
        run systemctl enable --now zabbix-agent2 || true
        run systemctl restart zabbix-agent2 || true
      fi
    
      start_server_proxy_after_upgrade
    }
    
    verify() {
      log "Verifying status (best-effort)"
      if [[ "$HAS_SERVER" == "1" ]] && svc_exists zabbix-server.service; then
        run systemctl --no-pager --full status zabbix-server || true
      fi
      if [[ "$HAS_PROXY" == "1" ]] && svc_exists zabbix-proxy.service; then
        run systemctl --no-pager --full status zabbix-proxy || true
      fi
      if [[ "$HAS_AGENT2" == "1" ]] && svc_exists zabbix-agent2.service; then
        run systemctl --no-pager --full status zabbix-agent2 || true
      fi
    
      log "Installed Zabbix RPMs:"
      if [[ "$DRY_RUN" == "1" ]]; then
        echo "[DRY-RUN] rpm -qa | grep -i '^zabbix' | sort"
      else
        rpm -qa | grep -i '^zabbix' | sort || true
      fi
    }
    
    main() {
      parse_args "$@"
      require_root
      detect_rhel
      run mkdir -p "${BACKUP_DIR}"
    
      detect_roles
    
      if [[ "$DO_BACKUP" == "1" ]]; then
        backup_all
      else
        log "Skipping backup phase (--upgrade-only selected)"
      fi
    
      if [[ "$DO_UPGRADE" == "1" ]]; then
        install_zabbix_repo
        refresh_metadata
        upgrade_detected_roles
        restart_services_after_upgrade
        verify
      else
        log "Skipping upgrade phase (--backup-only selected)"
      fi
    
      log "Done."
      [[ "$DRY_RUN" == "1" ]] && log "DRY-RUN mode: no changes were made."
    }
    
    main "$@"
Working...