#!/usr/bin/bash

set -f
export PATH="$PATH:${0%/*}"
PROGNAME=${0##*/}
DRY_RUN=
OVERRIDE=

INSTALL_FILE_DEFAULTS=( -m 0644 -o root -g root )
INSTALL_DIR_DEFAULTS=( -m 0755 -o root -g root )

KOMPOT_INSTALL_VAR_LOG_FILE=/opt/kompot/share/install.var-log
KOMPOT_PERSIST_VERSION_FILE=/etc/kompot/.version
{ KOMPOT_PERSIST_VERSION=$(< "$KOMPOT_PERSIST_VERSION_FILE"); } 2>/dev/null
KOMPOT_VERSION=$(rpm -q kompot --qf '%{version}-%{release}')

[[ -t 0 ]] && INTERACTIVE=1 || INTERACTIVE=

declare -A C=()
if [[ -t 1 ]]; then
    C+=(
        [info]=$'\x1b[1m'
        [warning]=$'\x1b[7;33m'
        [err]=$'\x1b[1;91m'
        [crit]=$'\x1b[7;91m'
        [_]=$'\x1b[m'
    )
fi

function exit_usage() {
    local status=${1:-0}
    [[ "$status" != "0" ]] && exec >&2
    echo "\
Usage: $PROGNAME [OPTION...]
Setup kompot for the system

Available options:
  -n, --dry-run     Dry-run mode, do not change anything.
  -o, --override    Reinstall files even if they exist.
  -h, --help        Display this help."
    exit "$status"
}

function _log() {
    local now=$(date +%Y-%m-%dT%H:%M:%S.%3N%:z)
    local severity=$1; shift
    local label=$1; shift
    [[ -n $INTERACTIVE ]] || logger -t "$PROGNAME[$$]" -p "$severity" -- "${DRY_RUN:+DRY-RUN ** }$*"
    echo "$now ${C[$severity]}${label}${C[_]} $PROGNAME: ${DRY_RUN:+DRY-RUN ** }$*"
}

function info() { _log info INFO "$@"; }
function warning() { _log warning WARNING "$@"; }
function error() { _log err ERROR "$@"; }
function fatal() { _log crit FATAL "$@"; exit 2; }

# $1: file to test
function file_is_flagged_kompot() {
    local flag=$({ grep -I . "$1" || true; } |sed -n '1{p;q}')
    [[ $flag == [#\;]kompot ]]
}

# $1: file to rename with a backup suffix
function mv_dot_increment() {
    local inc=
    while :; do
        REPLY="$1.bk${inc:+.$inc}"
        [[ ! -e $REPLY ]] && break
        (( inc++ ))
    done
    mv "$1" "$REPLY"
}

function has_journald() {
    [[ -S /dev/log ]] && pgrep systemd-journal >/dev/null
}

function is_docker() {
    local initcmd
    initcmd=$(ps -q 1 --no-headers -o comm)
    # always assume normal install if init is systemd => return FALSE
    [[ $initcmd == systemd ]] && return 1
    # docker / podman => return TRUE
    [[ -f /.dockerenv || -f /run/.containerenv ]] && return 0
    # default FALSE
    return 1
}

function rpm_verify_file() {
    local file=$1; shift
    rpm --verify --nomtime --nodeps -f "$file" |
        awk -v "file=$file" '$NF == file && $1 ~ /[S5]/ {exit 1}';
}

function explain() {
    [[ -n $DRY_RUN ]] && info "RUN: ${*@Q}" && return 0
    REPLY=
    "$@"; local ret=$?
    case "$ret" in
        0) info "RUN: ${*@Q} => OK${REPLY:+, $REPLY}" ;;
        *) error "RUN: ${*@Q} => FAILED ($ret)${REPLY:+, $REPLY}" ;;
    esac
    return "$ret"
}

# Usage: persist-symlink [INSTALL_OPTS...] ORIGINAL-SOURCE MOVE-TO-DESTINATION
# $1: (original) file, the source that will become a symlink
# $2: persistent target, the new destiination where $1 is moved to
#
# Examples:
# persist-symlink /etc/nagios /etc/kompot/nagios
#     /etc/nagios becomes a symlink to /etc/kompot/nagios
# persist-symlink /etc/rsyslog.conf /etc/kompot/rsyslog/rsyslog.conf
#     /etc/rsyslog.conf becomes a symlink to /etc/kompot/rsyslog/rsyslog.conf
function persist-symlink() {
    REPLY=
    local file=$1; shift
    local target=$1; shift
    [[ ${file: -1} == / ]] && { error "$FUNCNAME: File must not end with a /"; return 1; }
    [[ ${target: -1} == / ]] && target+=${file##*/}
    if [[ -L $file && $(readlink "$file") == "$target" ]]; then
        REPLY=skip
        return 0
    fi
    [[ -L $file ]] && { error "$FUNCNAME: File is already a symlink, operation unsupported"; return 1; }
    if [[ -e $file ]]; then
        mv_dot_increment "$file" || return 1 # new file in $REPLY
	    mkdir -p "${target%/*}" || return 1
	    cp -aTn "$REPLY" "$target" || return 1
	    REPLY=
    # else there will be a dead link because there is nothing to copy
    # but file might (will) come later
    fi
    mkdir -p "${file%/*}" || return 1
    ln -s "$target" "$file"
}

# Usage: install-dir [INSTALL_OPTS...] DESTINATION
# $@: install -d options
function install-dir() {
    REPLY=
    # abstract persist symlinks
    local dst=$(readlink -m "${*: -1}")
    install -d "${INSTALL_DIR_DEFAULTS[@]}" "${@:1:$#-1}" "$dst"
}

# Usage: install-file [INSTALL_OPTS...] [SOURCE] DESTINATION
# If file exists, do not copy, unless source is flagged #kompot and
# destination does not have the flag.
function install-file() {
    REPLY=
    local install_opts=() allow_disable=
    while [[ $1 == -* ]]; do
        case "$1" in
            --allow-disable) allow_disable=1; shift ;;
            *) install_opts+=( "$1" "$2" ); shift 2 ;;
        esac
    done
    (( $# == 1 )) && { create-file "${install_opts[@]}" "$1"; return $?; }
    local src=$1; shift
    local dst=$1; shift
    [[ -f $src ]] || { error "$FUNCNAME: Source must be a file"; return 1; }
    [[ ${dst: -1} == / ]] && dst+=${src##*/}

    # abstract persist symlinks
    dst=$(readlink -m "$dst")

    # handle --allow-disable
    if [[ -n $allow_disable ]]; then
        if [[ $src == *.disabled ]]; then
            # default (src) is disabled, file has been enabled
            [[ -e ${dst%.disabled} ]] && dst=${dst%.disabled}
        else
            # default (src) is enabled, file has been disabled
            [[ -e $dst.disabled ]] && dst=$dst.disabled
        fi
        if [[ -e ${dst%.disabled} && -e ${dst%.disabled}.disabled ]]; then
            warning "Both ${dst%.disabled}{,.disabled} exists, only one should be there, please cleanup!"
        fi
    fi

    if [[ ! -e $dst || -n $OVERRIDE ]] || { file_is_flagged_kompot "$src" &&
                                          ! file_is_flagged_kompot "$dst"; }; then
        if [[ -e $dst ]]; then
            mv_dot_increment "$dst" || return 1
            REPLY=override
        fi
        install -D "${INSTALL_FILE_DEFAULTS[@]}" "${install_opts[@]}" "$src" "$dst" || return 1
    else
        REPLY=skip
    fi
    return 0
}

# Usage: create-file [INSTALL_OPTS...] SOURCE DESTINATION
# Touch the file is it does not exist.
# Apply install-like options if given, -m -o, -g supported.
function create-file() {
    REPLY=
    local mode= user= group=
    while [[ $1 == -* ]]; do
        case "$1" in
            -m) mode=$2; shift 2 ;;
            -o) user=$2; shift 2 ;;
            -g) group=$2; shift 2 ;;
        esac
    done

    # abstract persist symlinks
    dst=$(readlink -m "$1")

    [[ -e $dst ]] && { REPLY=skip; return 0; }
    mkdir -p "${dst%/*}" || return 1
    touch "$dst" || return 1
    [[ -n $mode ]] && { chmod "$mode" "$dst" || return 1; }
    [[ -n $user ]] && { chown "$user" "$dst" || return 1; }
    [[ -n $group ]] && { chgrp "$group" "$dst" || return 1; }
    return 0
}

# Usage: sed-file -re 's,x,y,' file
function sed-file() {
    # abstract persist symlinks
    file=$(readlink -m "${*: -1}")
    sed -i "${@:1:$#-1}" "$file"
}

function enable-file() {
    REPLY=
    [[ -f $1 ]] && { REPLY=skip; return 0; }
    [[ -f $1.disabled ]] || return 1
    mv "$1"{.disabled,}
}

# Usage: systemctl-enable-if-disabled SERVICE...
# Service(s) won't be enabled if masked.
function systemctl-enable-if-disabled() {
    local i is_enabled ret=0
    for i in "$@"; do
        is_enabled=$(systemctl is-enabled "$i")
        [[ $is_enabled == enabled ]] && continue
        systemctl enable "$i" || ret=1
    done
    return "$ret"
}

function is_version_greater() {
    local vleft=$1; shift
    local vright=$1; shift
    [[ $vleft == $vright ]] && return 1
    local vgreater=$(printf '%s\n%s\n' "$vleft" "$vright" |sort -V |tail -n 1)
    [[ $vgreater == $vleft ]] && return 0
    return 1
}

# done when sourced
(return 0 2>/dev/null) && return 0

while (( $# > 0 )); do
    case "$1" in
        -n|--dry-run) DRY_RUN=1 ;;
        -o|--override) OVERRIDE=1 ;;
        -h|--help) exit_usage ;;
        --) shift; break ;;
        -*) exit_usage 1 ;;
        *) break ;;
    esac
    shift
done

# /bin/env me?
if (( $# > 0 )); then
    ret=0
    for i in "$@"; do
        (PROGNAME=${i##*/} source "$i") || ret=$?
    done
    exit "$ret"
fi

if [[ -z $KOMPOT_VERSION ]]; then
    fatal 'Cannot find installed version, abort!'
fi
if [[ -n $KOMPOT_PERSIST_VERSION ]] &&
   is_version_greater "$KOMPOT_PERSIST_VERSION" "$KOMPOT_VERSION"; then
    fatal 'Persistence version greater than installed version, abort!'
fi

# DOCKER PERSISTANCE

if is_docker; then
    # centreon
    explain persist-symlink /etc/centreon /etc/kompot/centreon

    # cron
    explain persist-symlink /etc/cron.d/kompot.local /etc/kompot/cron/kompot.local

    # grafana
    explain persist-symlink /etc/grafana /etc/kompot/grafana
    explain persist-symlink /var/lib/grafana /var/lib/kompot/grafana

    # influxdb
    explain persist-symlink /etc/influxdb /etc/kompot/influxdb
    explain persist-symlink /var/lib/influxdb /var/lib/kompot/influxdb

    # logrotate
    explain persist-symlink /var/lib/logrotate /var/lib/kompot/logrotate

    # nagflux
    explain persist-symlink /etc/nagflux /etc/kompot/nagflux

    # nagios
    explain persist-symlink /etc/nagios /etc/kompot/nagios
    explain persist-symlink /var/spool/nagios /var/lib/kompot/nagios

    # snmp
    explain persist-symlink /etc/snmp /etc/kompot/snmp

    # ssh
    for i in rsa ecdsa ed25519; do
        if [[ ! -f /etc/kompot/ssh/ssh_host_${i}_key ]]; then
            # service will trigger sshd-keygen from opensshd package and that script
            # removes the symlinks, so let's make sure host keys are ready before
            # switching to /etc/kompot/ssh symlinks
            explain /usr/libexec/openssh/sshd-keygen "$i"
        fi
        explain persist-symlink "/etc/ssh/ssh_host_${i}_key" /etc/kompot/ssh/
        explain persist-symlink "/etc/ssh/ssh_host_${i}_key.pub" /etc/kompot/ssh/
    done

    # restore /var/log dirs in case it is a volume
    while read -r u g m d; do
        explain install-dir -o "$u" -g "$g" -m "$m" "$d"
    done < "$KOMPOT_INSTALL_VAR_LOG_FILE"
fi

# INSTALL FILES

# apache
explain install-file /opt/kompot/share/configs/apache/kompot.httpd.conf /etc/httpd/conf.d/50-kompot.conf
explain install-file /opt/kompot/share/configs/apache/defines.conf /etc/kompot/httpd/
explain install-file /opt/kompot/share/configs/apache/kompot.conf /etc/kompot/httpd/
explain install-dir /etc/kompot/httpd/conf.d

# bin
explain install-dir /etc/kompot/bin

# bash
explain install-file /opt/kompot/share/configs/bash/kompot-alias.sh.disabled /etc/profile.d/
explain install-file /opt/kompot/share/configs/bash/kompot-path.sh /etc/profile.d/

# cron
explain install-file /opt/kompot/share/configs/cron/kompot /etc/cron.d/
explain install-file /etc/cron.d/kompot.local

# drawio
explain install-dir -o apache /etc/kompot/diagrams
explain install-file -o apache -g apache --allow-disable /opt/kompot/share/samples/diagrams/BAIE.xml /etc/kompot/diagrams/

# grafana
explain install-file -m 640 -g grafana /opt/kompot/share/configs/grafana/grafana.ini /etc/grafana/

# influxdb
explain install-file /opt/kompot/share/configs/influxdb/influxdb.conf /etc/influxdb/

# logmatch
explain install-file /etc/kompot/logmatch/logmatch.conf
explain install-file /etc/kompot/logmatch/aliases.conf

# logrotate
explain install-file /opt/kompot/share/configs/logrotate/kompot.logrotate.conf /etc/logrotate.d/00-kompot.conf
explain install-file /opt/kompot/share/configs/logrotate/kompot.conf /etc/kompot/logrotate/
explain install-dir /etc/kompot/logrotate/conf.d

# menus
explain install-dir /etc/kompot/menus
explain install-file --allow-disable /opt/kompot/share/samples/menus/200-drawio-rack.json /etc/kompot/menus/
explain install-file --allow-disable /opt/kompot/share/samples/menus/title.txt /etc/kompot/menus/

# nagflux
explain install-file /opt/kompot/share/configs/nagflux/config.gcfg /etc/nagflux/

# nagios
explain install-file /opt/kompot/share/configs/nagios/cgi.cfg /etc/nagios/
explain install-file /opt/kompot/share/configs/nagios/nagios.cfg /etc/nagios/
explain install-file /opt/kompot/share/configs/nagios/resource.cfg /etc/nagios/
explain install-dir /etc/kompot/nagios/credentials
explain install-dir /etc/kompot/nagios/objects
explain install-file --allow-disable /opt/kompot/share/samples/nagios/credentials-default.cfg /etc/kompot/nagios/credentials/default.cfg
explain install-dir /etc/kompot/plugins
explain install-dir -o nagios -g nagios /var/log/nagios/archives
explain install-dir -o nagios -g nagios /var/spool/nagios/nagfluxperfdata
explain install-dir -o nagios -g nagios /var/spool/nagios/plugins-cache

# nagzen
explain install-dir /etc/kompot/nagzen
explain install-file --allow-disable /opt/kompot/share/samples/nagzen/100_inventory.cfg /etc/kompot/nagzen/

# rsyslog
explain install-file /opt/kompot/share/configs/rsyslog/rsyslog.env /etc/kompot/rsyslog/
explain install-file /opt/kompot/share/configs/rsyslog/kompot.conf /etc/kompot/rsyslog/
explain install-file --allow-disable /opt/kompot/share/configs/rsyslog/kompot.rsyslogd.conf /etc/rsyslog.d/50-kompot.conf
explain install-dir /etc/kompot/rsyslog/conf.d
if ! is_docker; then
    explain install-file --allow-disable /opt/kompot/share/configs/rsyslog/rsyslog.systemd /etc/systemd/system/rsyslog.service.d/kompot.conf
    [[ $REPLY == skip ]] || explain systemctl daemon-reload
fi

# nagios-history
if ! is_docker; then
    explain install-file --allow-disable /opt/kompot/share/configs/systemd/nagios-history.service /etc/systemd/system/nagios-history.service
    [[ $REPLY == skip ]] || explain systemctl daemon-reload
fi

# snmp
explain install-file /opt/kompot/share/configs/snmp/snmpd.local.conf /etc/snmp/
explain install-file /opt/kompot/share/configs/snmp/snmptrapd.local.conf /etc/snmp/

# ssh
if is_docker; then
    explain install-file /opt/kompot/share/configs/ssh/kompot.sshd.conf /etc/ssh/sshd_config.d/60-kompot.conf
    explain install-file -m 600 -g root -g root /etc/kompot/ssh/authorized_keys.root
fi

# sudo
explain install-file -m 400 /opt/kompot/share/configs/sudoers/nagios-handlers /etc/sudoers.d/

# PREPARE SYSTEM AND SERVICES

# rpms
if is_docker && [[ -d /etc/kompot/system ]]; then
    rpms=( $(find /etc/kompot/system/ -mindepth 1 -maxdepth 1 -name '*.rpm' |
                awk -v OFS="\t" '{x="rpm -qp --qf \x27%{nevra}\t%{name}\x27 "$0; (x|getline $2); close(x);} {print}' |
                sort -k2Vr |
                awk -F "\t" 'seen[$3]==1 {next;} { if($3)seen[$3]=1; print $1; }') )
    if [[ -n $rpms ]]; then
        explain rpm -Uvh "${rpms[@]}"
    fi
fi

# vsphere perl modules
if is_docker && [[ -d /etc/kompot/system && -x /opt/centreon-vmware/share/install-vmware-perl-modules ]]; then
    vsphere_tarball=$(find /etc/kompot/system/ -mindepth 1 -maxdepth 1 -name 'VMware-vSphere-Perl-SDK*.tar.gz' |sort -rV |head -n 1)
    vsan_zip=$(find /etc/kompot/system/ -mindepth 1 -maxdepth 1 -name 'vsan-sdk-perl*.zip' |sort -rV |head -n 1)
    if [[ -n $vsphere_tarball && -n $vsan_zip ]]; then
        explain /opt/centreon-vmware/share/install-vmware-perl-modules --vsphere "$vsphere_tarball" --vsan "$vsan_zip"
    fi
fi

# timezone
if is_docker; then
    # configuration file /etc/kompot/system/timezone wins
    tz=$KOMPOT_TZ # try from environment
    [[ -f /etc/kompot/system/timezone ]] && tz=$(< /etc/kompot/system/timezone)
    if [[ -n $tz ]]; then
        if [[ -f /usr/share/zoneinfo/$tz ]]; then
            explain ln -snf "../usr/share/zoneinfo/$tz" /etc/localtime
        else
            warning 'Invalid value in /etc/kompot/system/timezone'
        fi
    fi
fi

# apache
if is_docker; then
    explain sed-file -re 's,^#(Define KOMPOT_REDIRECT_SLASH) .*,\1 1,' /etc/kompot/httpd/defines.conf
fi
explain usermod -aG nagios apache

# bash
if is_docker; then
    explain enable-file /etc/profile.d/kompot-alias.sh
fi

# rsyslog
explain sed-file -re 's,^\s*module\(load="builtin:omfile" Template=,#\0,' /etc/rsyslog.conf
if ! has_journald; then
    explain sed-file -re 's/^(module\(load="imjournal") [^c]/\1 config.enabled="off"/' /etc/rsyslog.conf
    explain sed-file -re 's,^#?\s*(KOMPOT_RSYSLOG_LISTEN_DEV_LOG).*,\1=on,' /etc/kompot/rsyslog/rsyslog.env
fi
[[ -d /var/lib/kompot/configs/logmatch/current ]] ||
    explain sh -c 'update-passive build && APPLY_NO_RESTART=1 update-passive apply'

# logrotate
if is_docker; then
    explain sed-file -re 's!systemctl.* -s ([^ ]+).* rsyslog.service!killall -\1 rsyslogd!' /etc/logrotate.d/rsyslog
    explain sed-file -re 's![^ ]+/systemctl reload httpd.service!/usr/sbin/httpd -k graceful!' /etc/logrotate.d/httpd
fi

# nagios
[[ -d /var/lib/kompot/configs/nagios/current ]] ||
    explain sh -c 'update-nagios build /etc/kompot/nagzen/* && APPLY_NO_RESTART=1 update-nagios apply'

# snmpd
if is_docker || rpm_verify_file /etc/snmp/snmpd.conf; then
    explain sed-file -re 's,^(\s*com2sec\s+notConfigUser\s+default\s+public\>),#\1,' /etc/snmp/snmpd.conf
fi

# snmptrapd
[[ -d /var/lib/kompot/configs/snmptrapd/current ]] ||
    explain sh -c 'update-snmptrapd build && APPLY_NO_RESTART=1 update-snmptrapd apply'

# menus
[[ -d /var/lib/kompot/configs/menus/current ]] ||
    explain sh -c 'update-menus build && update-menus apply'

# grafana
explain sh -c 'CHECK_QUIET=1 /opt/kompot/share/configs/grafana/check-init-grafana'

# enable services at boot time
if ! is_docker; then
    explain systemctl-enable-if-disabled centreon_vmware crond grafana-server httpd influxdb \
        nagflux nagios nagios-history rsyslog snmpd snmptrapd sshd
fi

# MIGRATION

info "Persistence version: ${KOMPOT_PERSIST_VERSION:-<none>}"
info "Installed version: $KOMPOT_VERSION"

function persist-version() {
    echo "$KOMPOT_VERSION" > "$KOMPOT_PERSIST_VERSION_FILE"
}

[[ -z $KOMPOT_PERSIST_VERSION ]] && { persist-version; exit 0; }

migration_scripts=($({
        printf '2\tpersist\t%s\n' "$KOMPOT_PERSIST_VERSION"
        printf '3\tcurrent\t%s\n' "$KOMPOT_VERSION"
        if [[ -d /opt/kompot/share/migration ]]; then
            find /opt/kompot/share/migration -executable -name 'to-kompot-*.sh' -printf '%p\n' |
                sed -nre 's,^.*\/to-kompot-(.+)\.sh$,1\tscript\t\1\t\0,p'
        fi
    } |
    sort -t $'\t' -k 3V,3 -k 1n,1 |
    awk -F $'\t' '$2=="persist"{p=1;next} $2=="current"{exit} p==1{print $4}'))

if [[ -n $migration_scripts ]]; then
    echo
    echo '######################################'
    echo '## /!\ KOMPOT MIGRATION SCRIPTS /!\ ##'
    echo '######################################'
    echo
fi

migration_missing=()
for i in "${migration_scripts[@]}"; do
    if [[ -n $migration_missing ]] || ! explain "$i"; then
        migration_missing+=( "$i" )
    fi
done

if [[ -n $migration_missing ]]; then
    error 'Migration failed, run missing scripts manually:'
    for (( i = 0; i < ${#migration_missing[@]}; i++ )); do
        error "[$((i+1))] ${migration_missing[i]}"
    done
    exit 3
fi

persist-version
