#!/usr/bin/env bash # # NAME # zabbix-mysql-dump - Configuration Backup for Zabbix with MySQL # # SYNOPSIS # This is a MySQL configuration backup script for Zabbix 1.x, 2.x and 3.0.x. # It does a full backup of all configuration tables, but only a schema # backup of large data tables. # # The script is based on a script by Ricardo Santos # (http://zabbixzone.com/zabbix/backuping-only-the-zabbix-configuration/) # # CONTRIBUTORS # - Ricardo Santos # - Jens Berthold (maxhq) # - Oleksiy Zagorskyi (zalex) # - Petr Jendrejovsky # - Jonathan Bayer # - Andreas Niedermann (dre-) # - Mișu Moldovan (dumol) # - Daniel Schneller (dschneller) # - Ruslan Ohitin (ruslan-ohitin) # # HISTORY # 0.8.1 (2016-07-11) # ENH: Added Zabbix 3.0.x tables to list (added & tested by Ruslan Ohitin) # # 0.8.0 (2016-01-22) # FIX: Only invoke `dig` if available # ENH: Option -c to use a MySQL config ("options") file (suggested by Daniel Schneller) # ENH: Option -r to rotate backup files (Daniel Schneller) # ENH: Add database version to filename if available # ENH: Add quiet mode. IP reverse lookup optional (Daniel Schneller) # ENH: Bash related fixes (Misu Moldovan) # CHG: Default output directory is now $PWD instead of script dir # # 0.7.1 (2015-01-27) # NEW: Parsing of commandline arguments implemented # ENH: Try reverse lookup of IPs and include hostname/IP in filename # REV: Stop if database password is wrong # # 0.7.0 (2014-10-02) # ENH: Complete overhaul to make script work with lots of Zabbix versions # # 0.6.0 (2014-09-15) # REV: Updated the table list for use with zabbix v2.2.3 # # 0.5.0 (2013-05-13) # NEW: Added table list comparison between database and script # # 0.4.0 (2012-03-02) # REV: Incorporated mysqldump options (suggested by Jonathan Bayer) # # 0.3.0 (2012-02-06) # ENH: Backup of Zabbix 1.9.x / 2.0.0, removed unnecessary use of # variables (DATEBIN etc) for commands that use to be in $PATH # # 0.2.0 (2011-11-05) # # AUTHOR # Jens Berthold (maxhq), 2016 # # LICENSE # This script is released under the MIT License (see LICENSE.txt) # # DEFAULT VALUES # # DO NOT EDIT THESE VALUES! # Instead, use command line parameters or a config file to specify options. # DUMPDIR="$PWD" DBHOST="127.0.0.1" DBNAME="zabbix" DBUSER="zabbix" DBPASS="" QUIET="no" REVERSELOOKUP="yes" GENERATIONSTOKEEP=0 # # SHOW HELP # if [ -z "$1" ]; then cat <<EOF USAGE $(basename $BASH_SOURCE) [options] OPTIONS -h HOST Hostname/IP of MySQL server. Default: $DBHOST -d DATABASE Zabbix database name. Default: $DBNAME -u USER MySQL user to access Zabbix database. Default: $DBUSER -p PASSWORD MySQL user password (specify "-" for a prompt). Default: no password -o DIR Save Zabbix MySQL dumps to DIR. Default: $DUMPDIR -c FILE Use FILE for MySQL options (passed via --defaults-extra-file). PLEASE NOTE: mysqldump needs the database to be specified via command line. So the first "database" options found in the config file is used for mysqldump. -r NUM Rotate backups while keeping up to NUM generations. Uses filename to match. Default: keep all backups -n Skip reverse lookup of IP address for host. -q Quiet mode: no output except for errors (for batch/crontab use). EXAMPLES $(basename $BASH_SOURCE) -h 1.2.3.4 -d zabbixdb -u zabbix -p test $(basename $BASH_SOURCE) -u zabbix -p - -o /tmp $(basename $BASH_SOURCE) -c /etc/mysql/mysql.cnf $(basename $BASH_SOURCE) -c /etc/mysql/mysql.cnf -d zabbixdb EOF exit 1 fi # # PARSE COMMAND LINE ARGUMENTS # DB_GIVEN=0 while getopts ":h:d:u:p:o:r:c:qn" opt; do case $opt in h) DBHOST="$OPTARG" ;; d) DBNAME="$OPTARG"; DB_GIVEN=1 ;; u) DBUSER="$OPTARG" ;; p) DBPASS="$OPTARG" ;; c) CNFFILE="$OPTARG" ;; o) DUMPDIR="$OPTARG" ;; r) GENERATIONSTOKEEP=$(printf '%.0f' "$OPTARG") ;; n) REVERSELOOKUP="no" ;; q) QUIET="yes" ;; \?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;; :) echo "Option -$OPTARG requires an argument" >&2; exit 1 ;; esac done # Password prompt if [ "$DBPASS" = "-" ]; then read -s -p "Enter MySQL password for user '$DBUSER' (input will be hidden): " DBPASS echo "" fi # Config file validations if [ ! -z "$CNFFILE" ]; then if [ ! -r "$CNFFILE" ]; then echo "ERROR: Cannot read configuration file $CNFFILE" >&2 exit 1 fi # Database name needs special treatment: # For mysqldump it has to be specified on the command line! # Therefore we need to get it from the config file if [ $DB_GIVEN -eq 0 ]; then DBNAME=$(grep -m 1 ^database= "$CNFFILE" | cut -d= -f2) fi fi # # CONSTANTS # MYSQL_OPTS=() [ ! -z "$CNFFILE" ] && MYSQL_OPTS=("${MYSQL_OPTS[@]}" --defaults-extra-file="$CNFFILE") [ ! -z "$DBHOST" ] && MYSQL_OPTS=("${MYSQL_OPTS[@]}" -h $DBHOST) [ ! -z "$DBUSER" ] && MYSQL_OPTS=("${MYSQL_OPTS[@]}" -u $DBUSER) [ ! -z "$DBPASS" ] && MYSQL_OPTS=("${MYSQL_OPTS[@]}" -p"$DBPASS") MYSQL_OPTS_BATCH=("${MYSQL_OPTS[@]}" --batch --silent) [ ! -z "$DBNAME" ] && MYSQL_OPTS_BATCH=("${MYSQL_OPTS_BATCH[@]}" -D $DBNAME) # Host name: try reverse lookup if IP is given DBHOSTNAME="$DBHOST" command -v dig >/dev/null 2>&1 FIND_DIG=$? if [ "$REVERSELOOKUP" == "yes" -a $FIND_DIG -eq 0 ]; then # Try resolving a given host ip newHostname=$(dig +noall +answer -x $DBHOST | sed -r 's/((\S+)\s+)+([^\.]+)\..*/\3/') test \! -z "$newHostname" && DBHOSTNAME="$newHostname" fi # # CONFIG DUMP # if [ "$QUIET" == "no" ]; then cat <<-EOF Configuration: - host: $DBHOST ($DBHOSTNAME) - database: $DBNAME - user: $DBUSER - output: $DUMPDIR EOF fi # # FUNCTIONS # # Returns TRUE if argument 1 is part of the given array (remaining arguments) elementIn () { local e for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done return 1 } # # CHECKS # if [ ! -x /usr/bin/mysqldump ]; then echo "mysqldump not found." >&2 echo "(with Debian, \"apt-get install mysql-client\" will help)" >&2 exit 1 fi # # READ TABLE LIST from __DATA__ section at the end of this script # (http://stackoverflow.com/a/3477269/2983301) # DATA_TABLES=() while read line; do table=$(echo "$line" | cut -d" " -f1) echo "$line" | cut -d" " -f5 | grep -qi "DATA" test $? -eq 0 && DATA_TABLES+=($table) done < <(sed '0,/^__DATA__$/d' "$BASH_SOURCE" | tr -s " ") # paranoid check if [ ${#DATA_TABLES[@]} -lt 5 ]; then echo "ERROR: The number of large data tables configured in this script is less than 5." >&2 exit 1 fi # # BACKUP # # Read table list from database [ "$QUIET" == "no" ] && echo "Fetching list of existing tables..." DB_TABLES=$(mysql "${MYSQL_OPTS_BATCH[@]}" -e "SELECT table_name FROM information_schema.tables WHERE table_schema = '$DBNAME'" 2>&1) if [ $? -ne 0 ]; then echo -e "ERROR while trying to access database:\n$DB_TABLES" 2>&1; exit 1; fi DB_TABLES=$(echo "$DB_TABLES" | sort) DB_TABLE_NUM=$(echo "$DB_TABLES" | wc -l) # Query Zabbix database version VERSION="" DB_VER=$(mysql "${MYSQL_OPTS_BATCH[@]}" -N -e "select optional from dbversion;" 2>/dev/null) if [ $? -eq 0 ]; then # version string is like: 02030015 re='(.*)([0-9]{2})([0-9]{4})' if [[ $DB_VER =~ $re ]]; then VERSION="_db-${BASH_REMATCH[1]}.$(( ${BASH_REMATCH[2]} + 0 )).$(( ${BASH_REMATCH[3]} + 0 ))" fi fi # Assemble file name DUMPFILENAME_PREFIX="zabbix_cfg_${DBHOSTNAME}" DUMPFILEBASE="${DUMPFILENAME_PREFIX}_$(date +%Y%m%d-%H%M)${VERSION}.sql" DUMPFILE="$DUMPDIR/$DUMPFILEBASE" PROCESSED_DATA_TABLES=() i=0 mkdir -p "${DUMPDIR}" [ "$QUIET" == "no" ] && echo "Starting table backups..." while read table; do # large data tables: only store schema if elementIn "$table" "${DATA_TABLES[@]}"; then dump_opt="--no-data" PROCESSED_DATA_TABLES+=($table) # configuration tables: full dump else dump_opt="--extended-insert=FALSE" fi mysqldump "${MYSQL_OPTS[@]}" \ --routines --opt --single-transaction --skip-lock-tables \ $dump_opt \ $DBNAME --tables ${table} >> "$DUMPFILE" if [ "$QUIET" == "no" ]; then # show percentage i=$((i+1)); i_percent=$(($i * 100 / $DB_TABLE_NUM)) if [ $(($i_percent % 12)) -eq 0 ]; then echo -n "${i_percent}%" else if [ $(($i_percent % 2)) -eq 0 ]; then echo -n "."; fi fi fi done <<<"$DB_TABLES" # # COMPRESS BACKUP # if [ "$QUIET" == "no" ]; then echo -e "\n" echo "For the following large tables only the schema (without data) was stored:" for table in "${PROCESSED_DATA_TABLES[@]}"; do echo " - $table"; done echo echo "Compressing backup file..." fi gzip -f "$DUMPFILE" if [ $? -ne 0 ]; then echo -e "\nERROR: Could not compress backup file, see previous messages" >&2 exit 1 fi [ "$QUIET" == "no" ] && echo -e "\nBackup Completed:\n${DUMPFILE}.gz" # # ROTATE OLD BACKUPS # if [ $GENERATIONSTOKEEP -gt 0 ]; then [ "$QUIET" == "no" ] && echo "Removing old backups, keeping up to $GENERATIONSTOKEEP" REMOVE_OLD_CMD="cd \"$DUMPDIR\" && ls -t \"${DUMPFILENAME_PREFIX}\"* | /usr/bin/awk \"NR>${GENERATIONSTOKEEP}\" | xargs rm -f " eval ${REMOVE_OLD_CMD} if [ $? -ne 0 ]; then echo "ERROR: Could not rotate old backups" >&2 exit 1 fi fi exit 0 ################################################################################ # List of all known table names and a flag indicating data (=large) tables # __DATA__ acknowledges 1.3.1 - 3.0.3 DATA actions 1.3.1 - 3.0.3 alerts 1.3.1 - 3.0.3 DATA application_discovery 2.5.0 - 3.0.3 application_prototype 2.5.0 - 3.0.3 application_template 2.1.0 - 3.0.3 applications 1.3.1 - 3.0.3 auditlog 1.3.1 - 3.0.3 DATA auditlog_details 1.7 - 3.0.3 DATA autoreg 1.3.1 - 1.3.4 autoreg_host 1.7 - 3.0.3 conditions 1.3.1 - 3.0.3 config 1.3.1 - 3.0.3 dbversion 2.1.0 - 3.0.3 dchecks 1.3.4 - 3.0.3 dhosts 1.3.4 - 3.0.3 drules 1.3.4 - 3.0.3 dservices 1.3.4 - 3.0.3 escalations 1.5.3 - 3.0.3 events 1.3.1 - 3.0.3 DATA expressions 1.7 - 3.0.3 functions 1.3.1 - 3.0.3 globalmacro 1.7 - 3.0.3 globalvars 1.9.6 - 3.0.3 graph_discovery 1.9.0 - 3.0.3 graph_theme 1.7 - 3.0.3 graphs 1.3.1 - 3.0.3 graphs_items 1.3.1 - 3.0.3 group_discovery 2.1.4 - 3.0.3 group_prototype 2.1.4 - 3.0.3 groups 1.3.1 - 3.0.3 help_items 1.3.1 - 2.1.8 history 1.3.1 - 3.0.3 DATA history_log 1.3.1 - 3.0.3 DATA history_str 1.3.1 - 3.0.3 DATA history_str_sync 1.3.1 - 2.2.13 DATA history_sync 1.3.1 - 2.2.13 DATA history_text 1.3.1 - 3.0.3 DATA history_uint 1.3.1 - 3.0.3 DATA history_uint_sync 1.3.1 - 2.2.13 DATA host_discovery 2.1.4 - 3.0.3 host_inventory 1.9.6 - 3.0.3 host_profile 1.9.3 - 1.9.5 hostmacro 1.7 - 3.0.3 hosts 1.3.1 - 3.0.3 hosts_groups 1.3.1 - 3.0.3 hosts_profiles 1.3.1 - 1.9.2 hosts_profiles_ext 1.6 - 1.9.2 hosts_templates 1.3.1 - 3.0.3 housekeeper 1.3.1 - 3.0.3 httpstep 1.3.3 - 3.0.3 httpstepitem 1.3.3 - 3.0.3 httptest 1.3.3 - 3.0.3 httptestitem 1.3.3 - 3.0.3 icon_map 1.9.6 - 3.0.3 icon_mapping 1.9.6 - 3.0.3 ids 1.3.3 - 3.0.3 images 1.3.1 - 3.0.3 interface 1.9.1 - 3.0.3 interface_discovery 2.1.4 - 3.0.3 item_application_prototype 2.5.0 - 3.0.3 item_condition 2.3.0 - 3.0.3 item_discovery 1.9.0 - 3.0.3 items 1.3.1 - 3.0.3 items_applications 1.3.1 - 3.0.3 maintenances 1.7 - 3.0.3 maintenances_groups 1.7 - 3.0.3 maintenances_hosts 1.7 - 3.0.3 maintenances_windows 1.7 - 3.0.3 mappings 1.3.1 - 3.0.3 media 1.3.1 - 3.0.3 media_type 1.3.1 - 3.0.3 node_cksum 1.3.1 - 2.2.13 node_configlog 1.3.1 - 1.4.7 nodes 1.3.1 - 2.2.13 opcommand 1.9.4 - 3.0.3 opcommand_grp 1.9.2 - 3.0.3 opcommand_hst 1.9.2 - 3.0.3 opconditions 1.5.3 - 3.0.3 operations 1.3.4 - 3.0.3 opgroup 1.9.2 - 3.0.3 opinventory 3.0.0 - 3.0.3 opmediatypes 1.7 - 1.8.22 opmessage 1.9.2 - 3.0.3 opmessage_grp 1.9.2 - 3.0.3 opmessage_usr 1.9.2 - 3.0.3 optemplate 1.9.2 - 3.0.3 profiles 1.3.1 - 3.0.3 proxy_autoreg_host 1.7 - 3.0.3 proxy_dhistory 1.5 - 3.0.3 proxy_history 1.5.1 - 3.0.3 regexps 1.7 - 3.0.3 rights 1.3.1 - 3.0.3 screen_user 3.0.0 - 3.0.3 screen_usrgrp 3.0.0 - 3.0.3 screens 1.3.1 - 3.0.3 screens_items 1.3.1 - 3.0.3 scripts 1.5 - 3.0.3 service_alarms 1.3.1 - 3.0.3 services 1.3.1 - 3.0.3 services_links 1.3.1 - 3.0.3 services_times 1.3.1 - 3.0.3 sessions 1.3.1 - 3.0.3 slides 1.3.4 - 3.0.3 slideshow_user 3.0.0 - 3.0.3 slideshow_usrgrp 3.0.0 - 3.0.3 slideshows 1.3.4 - 3.0.3 sysmap_element_url 1.9.0 - 3.0.3 sysmap_url 1.9.0 - 3.0.3 sysmap_user 3.0.0 - 3.0.3 sysmap_usrgrp 3.0.0 - 3.0.3 sysmaps 1.3.1 - 3.0.3 sysmaps_elements 1.3.1 - 3.0.3 sysmaps_link_triggers 1.5 - 3.0.3 sysmaps_links 1.3.1 - 3.0.3 timeperiods 1.7 - 3.0.3 trends 1.3.1 - 3.0.3 DATA trends_uint 1.5 - 3.0.3 DATA trigger_depends 1.3.1 - 3.0.3 trigger_discovery 1.9.0 - 3.0.3 triggers 1.3.1 - 3.0.3 user_history 1.7 - 2.4.8 users 1.3.1 - 3.0.3 users_groups 1.3.1 - 3.0.3 usrgrp 1.3.1 - 3.0.3 valuemaps 1.3.1 - 3.0.3