#!/bin/sh -efu
#
# Copyright (C) 2010  Paul Wolneykien <manowar@altlinux.org>
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#

. shell-error
. shell-quote
. shell-args
. shell-config
. alterator-service-functions
. alterator-php-functions

show_help()
{
	cat <<EOF
Usage: $PROG [options] command configuration-archive.tar.gz

$PROG applies a Zabbix node configuration from an archive produced by the grow-zabbix-node program.


* Commands *

  The program is responsible for the following set of commands.

    apply

    By this command the new configuration is applied completely. The target database is dropped if it exists, so ALL OF THE DATA IS MISSING (overwritten). All configuration files are overwritten.

    switch

    By this command first an attempt is made to use an existing database. If the database exists it is left untouched. Otherwise the \`apply' command is executed. All configuration files are overwritten anyway.


* Main options *

  The root directory to extract files to:

  -r PATH, --root-dir=PATH

  Defaults to \`/'.

  The name and password of the database administrator user:

  -u ADMIN, --user=ADMIN
  -p [PASSWD], --password[=PASSWD]

  The default user name is \`root'. If the PASSWD argument is omitted then it is expected at the process standard input.

* Auxiliary options *

  -c CSNAME, --charset=CSNAME		the name of the charset to use loading the dump.


* Configuration of the core services *

  The core monitoring services (Zabbix server and agent) can be enabled systemwide with the following option:

  --enable-monitoring

  If this option is specified then the supporting services (i.e. a database service) are enabled too.


* Configuration of environmental services *

  The following options control possible adjustments of environmental programs and services.

  Adjust the PHP Apache2 module to satisfy:

  --adjust-php-min	the minimal Zabbix requirements;
  --adjust-php		the recommended Zabbix requirements.

  Basic adjustments for the database server can be made with the following option:

  --adjust-db-service


  The firewall (IP filter) is adjusted in the presense of the following option:

  --adjust-firewall


* Usage options *

  The following options control the program execution:

  -h, --help		print this help information;
  --usage		print short program usage information;
  -V, --version		print the program version and license terms;
  -v, --verbose		print information messages while processing the input;
  -q, --quiet		do not print any information messages.


  Please, report bugs to http://bugs.altlinux.ru/

EOF
	exit
}

print_version()
{
	cat <<EOF
$PROG version 1.0
Written by Paul Wolneykien <manowar@altlinux.org>

Copyright (C) 2010 Paul Wolneykien <manowar@altlinux.org>
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.
EOF
	exit
}

OPTS=`getopt -n $PROG \
              -o r:,u:,p::,c:h,V,v,q \
	      -l root-dir:,user:,password::,charset:,enable-monitoring,adjust-php-min,adjust-php,adjust-db-service,adjust-firewall,help,usage,version,verbose,quiet -- "$@"` \
	|| show_usage
eval set -- "$OPTS"

ROOT_DIR=;USER=;PASSWORD_PRESENT=;PASSWORD=;CSNAME=;ENABLE_MONITORING=;ADJUST_PHP_MIN=;ADJUST_PHP=;ADJUST_DB_SERVICE=;ADJUST_FIREWALL=;VERBOSE=;QUIET=

ROOT_DIR="/"

while :; do
	case "$1" in
		-r|--root-dir) ROOT_DIR="$2"; shift;;
		-u|--user) USER="$2"; shift;;
		-p|--password) PASSWORD_PRESENT="$1"; PASSWORD="$2"; shift;;
		-c|--charset) CSNAME="$2"; shift;;
		--enable-monitoring) ENABLE_MONITORING="$1";;
		--adjust-php-min) ADJUST_PHP_MIN="$1";;
		--adjust-php) ADJUST_PHP="$1";;
		--adjust-db-service) ADJUST_DB_SERVICE="$1";;
		--adjust-firewall) ADJUST_FIREWALL="$1";;
		-h|--help) show_help;;
		--usage) show_usage;;
		-V|--version) print_version;;
		-v|--verbose) VERBOSE="$1";;
		-q|--quiet) QUIET="$1";;
		--) shift; break;;
		*) fatal "unrecognized option: $1";;
	esac
	shift
done

[ -n "$QUIET" ] || verbose=$VERBOSE

[ -n "${1:-}" ] || fatal "Specify a command and a .tar.gz archive"
COMMAND="$1"
[ "$COMMAND" = "apply" ] || [ "$COMMAND" = "switch" ] || fatal "Unrecognized command"

[ -n "${2:-}" ] || fatal "Specify a .tar.gz archive with the node configuration"
CONFIGA="$2"
[ -r "$CONFIGA" ] || fatal "Configuration archive is not readable"

cleanup_handler()
{
	[ -n "${workdir:-}" ] && rm -rf "$workdir"
}
trap cleanup_handler EXIT
trap cleanup_handler HUP PIPE INT QUIT TERM

workdir="$(mktemp -dt "$PROG.XXXXXXXXXX")"
[ -d "$workdir" ] || fatal "Unable to create the work directory"

if [ -n "$PASSWORD_PRESENT" ] && [ -z "$PASSWORD" ]; then
	read -s -p "Password:" PASSWORD
fi

[ -n "$CSNAME" ] || CSNAME="utf8"

# Reads a metadata value.
# args: name
read_metadata() {
	tar xzOf "$CONFIGA" etc/zabbix/NODE \
	| sed -n -e "s/^$1: //p"
}

# Set file owner group and mode.
# args: filename user group mode
chogm() {
	chown "$2" "$1"
	chgrp "$3" "$1"
	chmod "$4" "$1"
	[ "$4" = "0755" ] && chmod g-s "$1" || :
	verbose "Set $4 $2 $3 for $1"
}

# Sets the specified owner group and mode of a directory creating it
# optionally.
# args: dir user group mode
mmkfdir() {
	[ -d "$1" ] || mkdir "$1"
	chogm "$@"
}

# Creates the directory if it doesn't exist and sets its owner group
# and mode.
# args: dir user group mode
mmkdir() {
	if [ ! -d "$1" ]; then
		mkdir "$1"
		chogm "$@"
	fi
}

# Sets the specified owner group and mode of a file creating it
# optionally.
# args: filename user group mode
mmkffile() {
	[ -f "$1" ] || touch "$1"
	chogm "$@"
}

# Creates a file if it doesn't exist and sets its owner group
# and mode.
# args: filename user group mode
mmkfile() {
	if [ ! -f "$1" ]; then
		touch "$1"
		chogm "$@"
	fi
}

# Extracts the contents of the archive to the specified
# directory.
# args: target-dir
extract_to() {
	local target="$1"
	tar tzf "$CONFIGA" \
	| while read fn; do
		tfn="${target%/}/${fn%/}"
		case "$fn" in
			etc/zabbix/) mmkfdir "$tfn" root root 0755;;
			etc/zabbix/*) mmkffile "$tfn" root zabbix 0640;;
			var/www/) mmkfdir "$tfn" root webmaster 0755;;
			var/www/webapps/) mmkfdir "$tfn" root webmaster 2771;;
			var/www/webapps/zabbix/) mmkfdir "$tfn" apache2 root 0755;;
			var/www/webapps/zabbix/*/) mmkfdir "$tfn" apache2 root 0755;;
			var/www/webapps/zabbix/*) mmkffile "$tfn" apache2 apache2 0640;;
			var/lib/mysql/) mmkfdir "$tfn" root mysql 3771;;
			var/lib/mysql/tmp/) mmkfdir "$tfn" root mysql 7770;;
			var/lib/mysql/tmp/*) mmkffile "$tfn" mysql mysql 0644;;
			*/) mmkdir "$tfn" root root 0755;;
			*) mmkfile "$tfn" root root 0640;;
		esac
		if [ -f "$tfn" ]; then
			verbose "Output $tfn"
			tar xzOf "$CONFIGA" "$fn" > "$tfn"
		fi
	done
}

extract_to "$ROOT_DIR"

get_zbx_server() {
	echo "zabbix_$(read_metadata DBTYPE | tr [[:upper:]] [[:lower:]])"
}

zbx_server() {
	local srvbin="$(which "$(get_zbx_server)")" || fatal "Zabbix server executable ($(read_metadata DBTYPE) version) not found"
	"$srvbin" "$@"
}

# Gets the full path to an SQL script with the given name.
# args: script-base-name
get_script() {
	local base="${1:-}"
	[ -n "$base" ] && base="-$base"
	local name="$(read_metadata NAME)"
	[ -n "$name" ] || name="$(read_metadata DBNAME)"
	local script="${name}${base}-$(read_metadata DBTYPE | tr [[:upper:]] [[:lower:]]).sql"
	echo "${ROOT_DIR%/}/$(tar tzf "$CONFIGA" | grep "\\(.*/\\)\\?$script$")"
}

dbtype="$(read_metadata DBTYPE | tr [[:upper:]] [[:lower:]])"
# Load helper functions for the target DB type
. alterator-${dbtype}-functions

# Runs the specified SQL-script.
# args: sql-file [db-name]
run_sql_script() {
	verbose "Running SQL-script $1"
	${dbtype}_run_sql_script "$workdir" "$PASSWORD" "$1" "$CSNAME" ${2:-} \
	|| fatal "Error running SQL-script"
}

apply_conf() {
	run_sql_script "$(get_script)"
	local nodeid="$(read_metadata ID)"
	if [ -n "$nodeid" ] && [ "$nodeid" -ne 0 ]; then
		verbose "Recalculate DB for Id = $nodeid"
		zbx_server -n "$nodeid" -c "/etc/zabbix/zabbix_server.conf" || [ $? -eq 255 ]
	fi
	run_sql_script "$(get_script 'update')"
}

# Runs the specified SQL-query.
# args: sql-query [db-name]
run_sql_cmd() {
	${dbtype}_run_sql_cmd "$workdir" "$PASSWORD" "$1" "$CSNAME" ${2:-} \
	|| fatal "Error running SQL-query"
}

query_node_id() {
	run_sql_cmd "select nodeid from nodes where name='$(read_metadata NAME)' and ip='$(read_metadata DOWNLINK)' or ip='127.0.0.1';" \
		    "$(read_metadata DBNAME)" \
	| sed -n -e '/^[0-9]\+[[:space:]]*$/ {s/^\([0-9]\+\)[[:space:]]*$/\1/p; q}'
}

# Checks if the specified database exists.
# args: dbname
db_exists() {
	${dbtype}_db_exists "$workdir" "$PASSWORD" "$1"
}

switch_to_conf() {
	local nodeid=
	# Try not to touch the DB
	verbose "Check if DB $(read_metadata DBNAME) exists"
	if db_exists "$(read_metadata DBNAME)"; then
		verbose "Existing database found"
		# Check the node Id
		[ -n "$(read_metadata ID)" ] || return 0
		verbose "Compare DB and configured node IDs"
		nodeid="$(query_node_id)"
		[ "$nodeid" = "$(read_metadata ID)" ] || fatal "Node IDs do not match (DB: $nodeid, conf: $(read_metadata ID))"
		verbose "Node IDs match -- node is ready"
	else
		verbose "Database $(read_metadata DBNAME) not found"
		apply_conf || fatal "Error while applying configuration"
	fi
}

server_status=off
if service_exists "$(get_zbx_server)"; then
	if service_control "$(get_zbx_server)" is-active; then
		server_status=on
		verbose "Stopping the Zabbix server"
		service_control "$(get_zbx_server)" stop || fatal "Unable to stop the Zabbix server"
	fi
	[ -n "$ENABLE_MONITORING" ] && server_status=on
else
	echo "Warning: Zabbix server service is not installed" 1>&2
fi

# Remember the original database service status
dbservice_status="$(${dbtype}_service_status)"

# Start the database service
verbose "Start the $dbtype service"
${dbtype}_service_start || fatal "Unable to start the $dbtype service"

case "$COMMAND" in
	apply) apply_conf || fatal "Error while applying configuration";;
	switch) switch_to_conf || fatal "Unable to switch to the configuration $(read_metadata NAME)";;
	*) echo "Unrecognized command $COMMAND" 1>&2;;
esac

if [ -n "$ADJUST_PHP_MIN" ] && [ -z "$ADJUST_PHP" ]; then
	verbose "Adjust PHP for minimal Zabbix requirements"
	write_apache2_mod_php_ini 'max_execution_time' '300'
	write_apache2_mod_php_ini 'max_input_time' '300'
	write_apache2_mod_php_ini 'memory_limit' '128M'
	write_apache2_mod_php_ini 'post_max_size' '16M'
	write_apache2_mod_php_ini 'upload_max_filesize' '2M'
fi

if [ -z "$ADJUST_PHP_MIN" ] && [ -n "$ADJUST_PHP" ]; then
	verbose "Adjust PHP for the recommended Zabbix requirements"
	write_apache2_mod_php_ini 'max_execution_time' '600'
	write_apache2_mod_php_ini 'max_input_time' '600'
	write_apache2_mod_php_ini 'memory_limit' '256M'
	write_apache2_mod_php_ini 'post_max_size' '32M'
	write_apache2_mod_php_ini 'upload_max_filesize' '16M'
fi

get_system_tzone() {
	local tzone="$(shell_config_get /etc/sysconfig/clock 'ZONE')"
	[ -n "$tzone" ] || fatal "Please select a time zone in /etc/sysconfig/clock"
	echo "$tzone"
}

if [ -n "$ADJUST_PHP_MIN" ] || [ -n "$ADJUST_PHP" ]; then
	write_apache2_mod_php_ini 'safe_mode' 'Off'
	write_apache2_mod_php_ini 'date.timezone' "$(get_system_tzone)"
	verbose "Apply the PHP configuration"
	apache2_mod_php_apply || fatal "Unable to apply the PHP configuration"
fi

if [ -n "$ADJUST_DB_SERVICE" ]; then
	verbose "Allow $dbtype network access"
	${dbtype}_allow_network_access
fi

if [ "$server_status" = "on" ] && [ "$dbservice_status" = "off" ]; then
	verbose "Starting the $dbtype service (for the Zabbix server)"
	${dbtype}_service_start
else
	verbose "Restore the $dbtype service original state ($dbservice_status)"
	${dbtype}_service_control "$dbservice_status"
fi
if [ "$server_status" = "on" ]; then
	verbose "Starting the Zabbix server"
	service_control "$(get_zbx_server)" start || echo "Warning: unable to start the Zabbix server" 1>&2
	if [ -n "$ENABLE_MONITORING" ]; then
		verbose "Enable the Zabbix server service for the default runlevels"
		service_control "$(get_zbx_server)" enable || fatal "Unable to enable the Zabbix server service"
		verbose "Enable the database service for the default runlevels"
		${dbtype}_service_startup_control 'on'
	fi
fi
if service_exists "zabbix_agentd"; then
	if service_control "zabbix_agentd" is-active; then
		verbose "Reloading the Zabbix agent daemon"
		service_control "zabbix_agentd" reload || echo  "Warning: unable to reload the Zabbix agent" 1>&2
	else
		if [ -n "$ENABLE_MONITORING" ]; then
			verbose "Starting the Zabbix agent daemon"
			service_control "zabbix_agentd" start || fatal "Unable to start the Zabbix agent daemon"
		fi
	fi
	if [ -n "$ENABLE_MONITORING" ]; then
		verbose "Enable the Zabbix agent service for the default runlevels"
		service_control "zabbix_agentd" enable || fatal "Unable to enable the Zabbix agent service"
	fi
else
	echo "Warning: Zabbix agent daemon is not installed" 1>&2
fi

. zabbix-sh-functions
ZABBIX_SERVER_PORT="$(zbxsrv_get 'ListenPort')"
[ -n "$ZABBIX_SERVER_PORT" ] || ZABBIX_SERVER_PORT=10051
if [ -n "$ADJUST_FIREWALL" ]; then
	verbose "Adjust firewall rules"
	alterator-net-iptables write -c on -t "+$ZABBIX_SERVER_PORT" 1>/dev/null 2>/dev/null || fatal "Error open the $ZABBIX_SERVER_PORT port"
fi

