#!/bin/sh -efu
#
# Copyright (C) 2020  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.
#

CONFDIR=/etc/nagios/domain-discovery
CONFIG="$CONFDIR/main.conf"
METHODSDIR=/usr/lib/nagios-domain-discovery/methods
DEFAULT_OBJECTDIR=/etc/nagios/objects
DEFAULT_TEMPLATE=linux-server

PROG="${0##*/}"
PROG_VERSION=0.1.0

show_help()
{
	cat <<EOF
Usage: $PROG [options]

$PROG is a tool to create and update the configuration of hosts
under Nagios monitoring based on the configuration of a domain.
It is configured with $CONFIG and uses methods under
$METHODSDIR.

Options:

  -c FILE, --main=FILE      use configuration file FILE instead of
                            $CONFIG;

  -C DIR, --confdir=DIR     use configuration directory DIR instead of
                            $CONFDIR;

  -n, --dry-run             print out the changes without actually
                            applying them;

  -q, --quiet               do not print any info while running;

  -d, --debug               do not delete the working directory
                            (and print its path);

  -V,--version              print program version and exit;
  -h,--help                 show this text and exit.


Report bugs to http://bugs.altlinux.ru/

EOF
}

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

Copyright (C) 2020 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
}

show_usage()
{
	cat <<EOF
Usage: $PROG [options]

Run \`$PROG -h\` to see the help page.
EOF
}


OPTS=`getopt -n $PROG -o c:,C:,n,q,d,V,h \
             -l main:,confdir:,dry-run,quiet,debug,version,help -- "$@"` ||
	show_usage
eval set -- "$OPTS"

dry_run=
quiet=
debug=

while :; do
	case "$1" in
		-c|--main)
            shift
            CONFIG="$1"
            ;;
		-C|--confdir)
            shift
            CONFDIR="$1"
            ;;
        -n|--dry-run)
            dry_run=1
            ;;
        -q|--quiet)
            quiet=1
            ;;
        -d|--debug)
            debug=1
            ;;
		-V|--version)
            print_version
            exit 0
            ;;
		-h|--help)
            show_help
            exit 0
            ;;
        --)
            shift
            break
            ;;
		*)
            echo "Unrecognized option: $1" >&2
            exit 1
            ;;
	esac
	shift
done

print_error() {
    local fmt="${1:-}"; shift
    printf "$fmt\\n" "$@" >&2
}

print_info() {
    [ -n "$quiet" ] || print_error "$@"
}

print_dry_info() {
    [ -n "$quiet" -a -z "$dry_run" ] || print_error "$@"
}

## Configuration

if [ -z "$CONFIG" ]; then
    print_error "ERROR: No configuration specified!"
    exit 1
fi

METHODS=
DOMAIN=
TEMPLATE=
HOSTGROUPS=
KEEPLIST=
GONE_HOSTS=
RELOAD_SERVICES=
RESTART_SERVICES=
OBJECTDIR=

if [ -e "$CONFIG" ]; then
    . "$CONFIG"
else
    print_error "ERROR: Configuration file %s not found!" "$CONFIG"
    exit 2
fi

[ -n "$OBJECTDIR" ] || OBJECTDIR="$DEFAULT_OBJECTDIR"

export CONFDIR
export METHODS
export DOMAIN
export HOSTGROUPS
export QUIET="$quiet"
export DEBUG="$debug"

if [ -z "$METHODS" ]; then
    print_info "No methods specified --- nothing to do!"
    exit 0
fi


## Method execution

workdir="$(mktemp -d --tmpdir $PROG.XXXX)"
if [ -z "$workdir" ]; then
    print_error "ERROR: Unable to create a working directory!"
    exit 3
fi
if [ -z "$debug" ]; then
    trap "rm -rf $workdir" EXIT
else
    print_info "Workdir: %s" "$workdir"
fi

export WORKDIR="$workdir"

(
    IFS=,
    for m in $METHODS; do
        if [ ! -e "$METHODSDIR/$m" ]; then
            print_error "ERROR: Method %s not found in %s!" "$m" "$METHODSDIR"
            exit 4
        fi
        if [ ! -x "$METHODSDIR/$m" ]; then
            print_error "ERROR: Method %s is not an executable file!" \
                        "$METHODSDIR/$m"
            exit 4
        fi

        "$METHODSDIR/$m" >"$workdir/hostlist.$m"
    done
) || exit $?

sort -u "$(IFS=,; for m in $METHODS; do echo "$workdir/hostlist.$m"; done)" \
     >"$workdir/hostlist"

update_host_config() {
    local hostname="$1"
    local address="$2"
    local hostgroups="$3"
    local ret=

    hostgroups="$(echo "$hostgroups" | tr ',' '\n' | sort -u | tr '\n' ',' | sed -e 's/,$//')"

    if [ -e "$OBJECTDIR/$hostname.cfg" ]; then
        ret=0
        sed -n -e '
/^[[:space:]]*define[[:space:]]\+host[[:space:]]*{/,/}/ {
    /}/ {
        g;
        s/^\n//;
        p;
        q0
    }
    /^[[:space:]]*host_name[[:space:]]/ {
        s/^[[:space:]]*host_name[[:space:]]\+\([^#]*\).*$/host_name=\1/;
        s/[[:space:]]*$//;
        H;
    }
    /^[[:space:]]*address[[:space:]]/ {
        s/^[[:space:]]*address[[:space:]]\+\([^#]*\).*$/address=\1/;
        s/[[:space:]]*$//;
        H;
    }
    /^[[:space:]]*hostgroups[[:space:]]/ {
        s/^[[:space:]]*hostgroups[[:space:]]\+\([^#]*\).*$/hostgroups=\1/;
        s/[[:space:]]*$//;
        H;
    }
    /^[[:space:]]*register[[:space:]]/ {
        s/^[[:space:]]*register[[:space:]]\+\([^#]*\).*$/register=\1/;
        s/[[:space:]]*$//;
        H;
    }
}
$ q1' "$OBJECTDIR/$hostname.cfg" >"$workdir/$hostname.keyval" || ret=$?
        if [ $ret -ne 0 ]; then
            print_error "ERROR: Unable to parse %s" "$OBJECTDIR/$hostname.cfg"
            exit 5
        fi

        local _hostname=
        local _address=
        local _hostgroups=
        local _register=
        while read kv; do
            case "$kv" in
                host_name=*)
                    _hostname="${kv#*=}"
                    ;;
                address=*)
                    _address="${kv#*=}"
                    ;;
                hostgroups=*)
                    _hostgroups="${kv#*=}"
                    ;;
                register=*)
                    _register="${kv#*=}"
                    ;;
            esac
        done <"$workdir/$hostname.keyval"

        if [ "$_hostname" != "$hostname" ]; then
            print_error "ERROR: Host %s defined in %s" \
                        "$_hostname" "$OBJECTDIR/$hostname.cfg"
            exit 6
        fi

        if [ "$_register" = "0" ]; then
            echo "$hostname" >>"$workdir/modified"
            print_dry_info "Enable host %s" "$hostname"
        fi

        cat <<EOF >"$workdir/$hostname.sed"
1 {
    x;
EOF
        if [ "$_address" != "$address" ]; then
            echo "$hostname" >>"$workdir/modified"
            print_dry_info "Address changed for host %s: %s -> %s" \
                       "$hostname" "$_address" "$address"
            cat <<EOF >>"$workdir/$hostname.sed"
    s/^.*\$/:address:&/;
EOF
        fi
        if [ "$_hostgroups" != "$hostgroups" ]; then
            echo "$hostname" >>"$workdir/modified"
            print_dry_info "Hostgroups changed for host %s: %s -> %s" \
                       "$hostname" "$_hostgroups" "$hostgroups"
            cat <<EOF >>"$workdir/$hostname.sed"
    s/^.*\$/:hostgroups:&/;
EOF
        fi

        cat <<EOF >>"$workdir/$hostname.sed"
    x;
}
/^[[:space:]]*define[[:space:]]\\+host[[:space:]]*{/,/}/ {
    /}/ {
EOF

        if [ -n "$address" ]; then
            cat <<EOF >>"$workdir/$hostname.sed"
        x;
        /:address:/ {
            x;
            s/^.*\$/\\taddress\\t$address\\n&/;
            x;
        }
EOF
        fi

        if [ -n "$hostgroups" ]; then
            cat <<EOF >>"$workdir/$hostname.sed"
        /:hostgroups:/ {
            x;
            s/^.*\$/\\thostgroups\\t$hostgroups\\n&/;
            x;
        }
EOF
        fi

        cat <<EOF >>"$workdir/$hostname.sed"
        x;
        p; d;
    }
    /^[[:space:]]*address[[:space:]]/ {
        x;
        /:address:/ {
            s/:address://;
            x;
EOF

        if [ -n "$address" ]; then
            cat <<EOF >>"$workdir/$hostname.sed"
            s/^\\([[:space:]]*address[[:space:]]\\+\\).*\$/\\1$address/;
EOF
        else
            cat <<EOF >>"$workdir/$hostname.sed"
            d;
EOF
        fi

        cat <<EOF >>"$workdir/$hostname.sed"
            x;
        }
        x;
    }
    /^[[:space:]]*hostgroups[[:space:]]/ {
        x;
        /:hostgroups:/ {
            s/:hostgroups://;
            x;
EOF

        if [ -n "$hostgroups" ]; then
            cat <<EOF >>"$workdir/$hostname.sed"
            s/^\\([[:space:]]*hostgroups[[:space:]]\\+\\).*\$/\\1$hostgroups/;
EOF
        else
            cat <<EOF >>"$workdir/$hostname.sed"
            d;
EOF
        fi

        cat <<EOF >>"$workdir/$hostname.sed"
            x;
        }
        x;
    }
    /^[[:space:]]*register[[:space:]]\\+0/ d;
}
EOF
        if [ -z "$dry_run" ]; then
            sed -i -f "$workdir/$hostname.sed" "$OBJECTDIR/$hostname.cfg"
        fi
    else
        echo "$hostname" >>"$workdir/new"
        print_dry_info "New host %s [%s]" "$hostname" "$address" "$hostgroups"
        if [ -n "$hostgroups" ]; then
            print_dry_info "Host %s added to groups: %s" "$hostname" "$hostgroups"
        fi
        if [ -z "$dry_run" ]; then
            cat <<EOF >"$OBJECTDIR/$hostname.cfg"
define host{
	host_name	$hostname
	use		${TEMPLATE:-$DEFAULT_TEMPLATE}
	alias	$hostname
	address	$address
	hostgroups	$hostgroups
}
EOF
        fi
    fi
}

(
    IFS="$(printf '\t')"
    hostname=
    address=
    hostgroups=
    while read _name _addr _groups; do
        if [ -n "$hostname" -a "$_name" = "$hostname" ]; then
            address="$_addr" # no accumulation here
            hostgroups="$hostgroups,$_groups"
        else
            [ -z "$hostname" ] || \
                update_host_config "$hostname" "$address" "$hostgroups"
            hostname="$_name"
            address="$_addr"
            hostgroups="$_groups"
        fi
    done <"$workdir/hostlist"

    if [ -n "$hostname" ]; then
        update_host_config "$hostname" "$address" "$hostgroups"
    fi
) || exit $?

case "$GONE_HOSTS" in
    unregister)
        find "$OBJECTDIR" -maxdepth 1 -name '*.cfg' | \
            while read cfg; do
                ret=0
                _name="${cfg##*/}"
                _name="${_name%.*cfg}"
                _register="$(sed -n -e '
/^[[:space:]]*define[[:space:]]\+host[[:space:]]*{/,/}/ {
    /}/ {
        g; s/^\n//; p; q0;
    }
    /^[[:space:]]*register[[:space:]]/ {
        s/^[[:space:]]*register[[:space:]]\+\([^#]\+\).*$/\1/;
        s/[[:space:]]*$//;
        h;
    }
}
$ q1' "$cfg")" || ret=$?
                if [ $ret -ne 0 ]; then
                    print_error "WARNING: Unable to parse the configuration file %s" "$cfg"
                    continue;
                fi
                if [ "$_register" != "0" ]; then
                    echo "$_name"
                fi
            done
        ;;
    *)
        find "$OBJECTDIR" -maxdepth 1 -name '*.cfg' | \
            sed -e 's,^.*/\([^/]\+\)\.cfg$,\1,'
        ;;
esac | sort -u >"$workdir/objectlist.old"
sed -e 's/\t.*$//' "$workdir/hostlist" | sort -u >"$workdir/objlist.new"
(
    IFS=,
    for n in $KEEPLIST; do
        echo "$n"
    done
) | sort -u >"$workdir/keeplist"
comm -23 "$workdir/objectlist.old" "$workdir/objlist.new" | \
    comm -23 - "$workdir/keeplist" >"$workdir/absentlist"

while read hostname; do
    case "$GONE_HOSTS" in
        unregister)
            echo "$hostname" >>"$workdir/gone"
            print_dry_info "Disable host %s" "$hostname"
            if [ -z "$dry_run" ]; then
                sed -i -e '
1 {
    x; s/^.*$/:register:&/; x;
}
/^[[:space:]]*define[[:space:]]\+host[[:space:]]*{/,/}/ {
    /}/ {
        x;
        /:register:/ {
            x; s/^.*$/\tregister\t0\n&/; x;
        }
        x;
        p; d;
    }
    /^[[:space:]]*register[[:space:]]/ {
        s/^\([[:space:]]*register[[:space:]]\+\).*$/\10/;
        x; s/:register://; x;
    }
}' "$OBJECTDIR/$hostname.cfg"
            fi
            ;;
        delete|remove)
            echo "$hostname" >>"$workdir/gone"
            print_dry_info "Delete host %s" "$hostname"
            if [ -z "$dry_run" ]; then
                rm "$OBJECTDIR/$hostname.cfg"
            fi
            ;;
        none)
            ;;
        *)
            print_error "ERROR: Unknown GONE_HOSTS value: %s" "$GONE_HOSTS"
            exit 1
            ;;
    esac
done <"$workdir/absentlist"


## Service reload

touch_service() {
    local srvname="$1"
    local systemd_cond_op="$2"
    local systemd_force_op="$3"
    local sysv_cond_op="${4:-$systemd_cond_op}"
    local sysv_force_op="${5:-$systemd_force_op}"

    if /sbin/sd_booted; then
        case "$srvname" in
            *\!)
                print_dry_info "Force %s-ing the systemd service %s..." \
                               "$systemd_force_op" "${srvname%\!}"
                if [ -z "$dry_run" ]; then
                    /sbin/systemctl "$systemd_force_op" "${srvname%\!}"
                fi
                ;;
            *)
                print_dry_info "Soft %s-ing the systemd service %s..." \
                               "$systemd_cond_op" "${srvname%\!}"
                if [ -z "$dry_run" ]; then
                    /sbin/systemctl "$systemd_cond_op" "$srvname"
                fi
                ;;
        esac
    else
        case "$srvname" in
            *\!)
                print_dry_info "Force %s-ing the SysV service %s..." \
                               "$sysv_force_op" "${srvname%\!}"
                if [ -z "$dry_run" ]; then
                    /sbin/service "${srvname%\!}" "$sysv_force_op"
                fi
                ;;
            *)
                print_dry_info "Soft %s-ing the SysV service %s..." \
                               "$sysv_cond_op" "${srvname%\!}"
                if [ -z "$dry_run" ]; then
                    /sbin/service "$srvname" "$sysv_cond_op"
                fi
                ;;
        esac
    fi
}

if [ -s "$workdir/new" -o \
     -s "$workdir/modified" -o \
     -s "$workdir/gone" ]
then
    if [ -n "$RESTART_SERVICES" ]; then
        (
            IFS=,
            for s in $RESTART_SERVICES; do
                touch_service "$s" 'condrestart' 'restart' || \
                    print_error "ERROR: Something goes wrong trying to restart %s" \
                                "$s"
            done
        )
    fi

    if [ -n "$RELOAD_SERVICES" ]; then
        (
            IFS=,
            for s in $RELOAD_SERVICES; do
                touch_service "$s" 'condreload' 'reload' || \
                    print_error "ERROR: Something goes wrong trying to reload %s" \
                                "$s"
            done
        )
    fi
fi
