#!/bin/sh
# ============================================================
#  vexor_selfupdate - Vexor agent plugin self-update (Linux)
#
#  Keeps /usr/lib64/nagios/plugins in sync with the plugin versions
#  published by the Vexor master, so plugin fixes reach every agent
#  automatically without re-running the bootstrap installer.
#
#  Modes:
#    --apply    Download the manifest, replace any changed plugin,
#               write a status file. Must run as root (the plugins dir
#               is root-owned and the nrpe user cannot write it), so it
#               is driven by the vexor-selfupdate systemd timer.
#    --report   Read the status file written by the last --apply and
#               print a Nagios line (read-only; runs fine as the nrpe
#               user via NRPE for GUI visibility).  This is the default.
#
#  Configuration: /etc/vexor/agent.conf (KEY=VALUE lines)
#    VEXOR_URL=https://vexor.example.com
#    VEXOR_IGNORE_CERT=1
# ============================================================
set -eu

CONF=/etc/vexor/agent.conf
# Dedicated world-readable dir so the nrpe user can read the status for the
# --report NRPE check (the vexor-api dir /var/lib/vexor is private, 0750).
STATUS=/var/lib/vexor-agent/selfupdate.status
PLUGDIR=/usr/lib64/nagios/plugins

# Plugins this updater is allowed to manage (mirrors AGENT_PLUGINS on the
# server). We only ever touch names on this whitelist.
MANAGED="check_mem check_uptime check_cpu_stats.sh check_file_count.sh check_sockets.sh vexor_check_systemd_unit vexor_check_systemd_mem vexor_check_cert_file vexor_check_updates vexor_check_deps vexor_selfupdate"

MODE=report
for a in "$@"; do
    case "$a" in
        --apply)  MODE=apply ;;
        --report) MODE=report ;;
    esac
done

sha_of() {
    # print sha256 hex of $1, or empty if missing
    [ -f "$1" ] || { echo ""; return; }
    sha256sum "$1" 2>/dev/null | awk '{print $1}'
}

write_status() {
    # write_status <state> <text> <updated-csv>
    _dir=$(dirname "$STATUS")
    mkdir -p "$_dir" 2>/dev/null || true
    chmod 0755 "$_dir" 2>/dev/null || true
    {
        echo "ts=$(date '+%Y-%m-%d %H:%M:%S')"
        echo "state=$1"
        echo "updated=$3"
        echo "text=$2"
    } > "$STATUS" 2>/dev/null || true
    chmod 0644 "$STATUS" 2>/dev/null || true
}

do_report() {
    if [ ! -f "$STATUS" ]; then
        echo "SELFUPDATE UNKNOWN - self-update has not run yet (no status file)"
        exit 3
    fi
    ts=$(sed -n 's/^ts=//p' "$STATUS")
    state=$(sed -n 's/^state=//p' "$STATUS")
    text=$(sed -n 's/^text=//p' "$STATUS")
    updated=$(sed -n 's/^updated=//p' "$STATUS")
    n=0
    if [ -n "$updated" ]; then
        n=$(echo "$updated" | awk -F, '{print NF}')
    fi
    if [ "$state" = "error" ]; then
        echo "SELFUPDATE WARNING - last self-update failed at $ts: $text"
        exit 1
    fi
    if [ "$n" -gt 0 ]; then
        echo "SELFUPDATE OK - last run $ts updated $n plugin(s): $updated|updated=$n"
    else
        echo "SELFUPDATE OK - all plugins up to date (last checked $ts)|updated=0"
    fi
    exit 0
}

is_managed() {
    for m in $MANAGED; do
        [ "$m" = "$1" ] && return 0
    done
    return 1
}

do_apply() {
    [ -r "$CONF" ] && . "$CONF" 2>/dev/null || true
    : "${VEXOR_URL:=}"
    : "${VEXOR_IGNORE_CERT:=0}"
    if [ -z "$VEXOR_URL" ]; then
        write_status error "VEXOR_URL not configured" ""
        echo "SELFUPDATE UNKNOWN - VEXOR_URL not configured ($CONF)"
        exit 3
    fi
    base=$(echo "$VEXOR_URL" | sed 's#/*$##')
    CURL="curl -fsS --max-time 60"
    [ "$VEXOR_IGNORE_CERT" = "1" ] && CURL="$CURL -k"

    tmpdir=$(mktemp -d /tmp/vexor-su.XXXXXX)
    trap 'rm -rf "$tmpdir"' EXIT

    if ! $CURL "$base/api/v1/nrpe/manifest" -o "$tmpdir/manifest" 2>/dev/null; then
        write_status error "cannot fetch manifest from $base" ""
        echo "SELFUPDATE WARNING - cannot fetch manifest from $base/api/v1/nrpe/manifest"
        exit 1
    fi

    mkdir -p "$PLUGDIR" 2>/dev/null || true
    updated=""
    errors=""
    # manifest lines: "<sha256hex>  <name>"
    while IFS= read -r line; do
        case "$line" in ""|\#*) continue ;; esac
        want=$(echo "$line" | awk '{print $1}' | tr 'A-Z' 'a-z')
        name=$(echo "$line" | awk '{print $2}')
        [ -n "$name" ] || continue
        is_managed "$name" || continue
        have=$(sha_of "$PLUGDIR/$name" | tr 'A-Z' 'a-z')
        [ "$have" = "$want" ] && continue

        if ! $CURL "$base/api/v1/nrpe/plugins/$name" -o "$tmpdir/$name" 2>/dev/null; then
            errors="$errors $name:download"
            continue
        fi
        got=$(sha_of "$tmpdir/$name" | tr 'A-Z' 'a-z')
        if [ "$got" != "$want" ]; then
            errors="$errors $name:checksum"
            continue
        fi
        chmod 0755 "$tmpdir/$name" 2>/dev/null || true
        # atomic replace on the same filesystem
        if cp "$tmpdir/$name" "$PLUGDIR/$name.new" 2>/dev/null && mv -f "$PLUGDIR/$name.new" "$PLUGDIR/$name" 2>/dev/null; then
            updated="$updated,$name"
        else
            errors="$errors $name:write"
            rm -f "$PLUGDIR/$name.new" 2>/dev/null || true
        fi
    done < "$tmpdir/manifest"

    updated=$(echo "$updated" | sed 's/^,//')
    command -v restorecon >/dev/null 2>&1 && restorecon -RF "$PLUGDIR" 2>/dev/null || true

    # ---- Sync the NRPE command definitions (vexor.cfg) ----
    # The self-updater keeps plugin *files* current, but the command
    # definitions in /etc/nrpe.d/vexor.cfg only shipped at bootstrap time,
    # so command-def fixes never reached existing hosts. Pull the current
    # vexor.cfg from the master and hot-reload nrpe when it changed.
    NRPE_D=""
    for _d in /etc/nrpe.d /etc/nagios/nrpe.d; do
        [ -f "$_d/vexor.cfg" ] && { NRPE_D="$_d"; break; }
    done
    [ -z "$NRPE_D" ] && [ -d /etc/nrpe.d ] && NRPE_D=/etc/nrpe.d
    if [ -n "$NRPE_D" ] && $CURL "$base/api/v1/nrpe/vexor.cfg" -o "$tmpdir/vexor.cfg" 2>/dev/null; then
        # Only accept a plausible cfg (has at least one command[...] line) so
        # a login/error page can never clobber the working config.
        if grep -q '^command\[' "$tmpdir/vexor.cfg" 2>/dev/null; then
            have=$(sha_of "$NRPE_D/vexor.cfg")
            want=$(sha_of "$tmpdir/vexor.cfg")
            if [ "$have" != "$want" ]; then
                if cp "$tmpdir/vexor.cfg" "$NRPE_D/vexor.cfg.new" 2>/dev/null && \
                   mv -f "$NRPE_D/vexor.cfg.new" "$NRPE_D/vexor.cfg" 2>/dev/null; then
                    command -v restorecon >/dev/null 2>&1 && restorecon -F "$NRPE_D/vexor.cfg" 2>/dev/null || true
                    updated="${updated:+$updated,}vexor.cfg"
                    # reload so the new command defs take effect immediately
                    _svc=nrpe
                    systemctl list-unit-files 2>/dev/null | grep -q '^nagios-nrpe-server' && _svc=nagios-nrpe-server
                    systemctl reload "$_svc" 2>/dev/null || systemctl restart "$_svc" 2>/dev/null || true
                else
                    errors="$errors vexor.cfg:write"
                    rm -f "$NRPE_D/vexor.cfg.new" 2>/dev/null || true
                fi
            fi
        fi
    fi

    if [ -n "$errors" ]; then
        write_status error "$(echo "$errors" | sed 's/^ //')" "$updated"
        echo "SELFUPDATE WARNING -$errors"
        exit 1
    fi
    if [ -n "$updated" ]; then
        ncount=$(echo "$updated" | awk -F, '{print NF}')
        write_status updated "updated $ncount plugin(s)" "$updated"
        echo "SELFUPDATE OK - updated $ncount plugin(s): $updated|updated=$ncount"
    else
        write_status ok "all plugins up to date" ""
        echo "SELFUPDATE OK - all plugins up to date|updated=0"
    fi
    exit 0
}

if [ "$MODE" = "apply" ]; then
    do_apply
else
    do_report
fi
