#!/bin/bash
################################################################################
# Script Name: deploy-exporter.sh
# Version: 1.1
# Description: Deployment tool for Prometheus exporters from mylinux.work.
#              Downloads, installs, configures cron jobs, validates output,
#              and manages lifecycle (install, update, remove, status) for
#              any exporter script hosted at mylinux.work/downloads/.
#
# Author: Phil Connor
# Contact: contact@mylinux.work
# Website: https://mylinux.work
# License: MIT
#
# Prerequisites:
#   - wget or curl
#   - root access (for /usr/local/bin/ and /etc/cron.d/)
#
# Usage:
#   deploy-exporter.sh list                                  # list available
#   deploy-exporter.sh install process-metrics-exporter      # install one
#   deploy-exporter.sh install process-metrics-exporter --cron "*/3 * * * *"
#   deploy-exporter.sh install process-metrics-exporter journal-error-exporter
#   deploy-exporter.sh status                                # check installed
#   deploy-exporter.sh remove process-metrics-exporter       # remove one
#   deploy-exporter.sh update                                # update all
#
################################################################################

set -uo pipefail

# ============================================================================
# CONFIGURATION
# ============================================================================

BASE_URL="https://mylinux.work/downloads"
INSTALL_DIR="/usr/local/bin"
CRON_DIR="/etc/cron.d"
TEXTFILE_DIR="/var/lib/node_exporter"

# Detect noexec /tmp — fall back to /usr/local/src for staging
STAGE_DIR="/tmp"
_exec_test=$(mktemp /tmp/.exec_test.XXXXXX 2>/dev/null)
if [[ -n "$_exec_test" ]]; then
    printf '#!/bin/sh\nexit 0\n' > "$_exec_test"
    chmod +x "$_exec_test"
    "$_exec_test" 2>/dev/null || STAGE_DIR="/usr/local/src"
    rm -f "$_exec_test"
else
    STAGE_DIR="/usr/local/src"
fi

# ============================================================================
# AVAILABLE EXPORTERS
# ============================================================================

declare -A EXPORTER_DESC=(
    [alertmanager-exporter]="Alertmanager notification and silence metrics"
    [apache-metrics-exporter]="Apache HTTP server performance metrics"
    [apt-updates-exporter]="Pending apt package updates"
    [artifactory-exporter]="JFrog Artifactory repository metrics"
    [backup-status-exporter]="Backup job status and age metrics"
    [borg-backup-exporter]="Borg backup repository and archive metrics"
    [caprover-exporter]="CapRover app deployment metrics"
    [clickhouse-exporter]="ClickHouse query, memory, merge, and replication metrics"
    [consul-exporter]="HashiCorp Consul service health metrics"
    [container-health-exporter]="Docker container health and resource metrics"
    [coolify-exporter]="Coolify deployment platform metrics"
    [dokku-exporter]="Dokku deployment platform metrics"
    [dokploy-exporter]="Dokploy deployment platform metrics"
    [cron-job-exporter]="Cron job execution status and timing"
    [crowdsec-decisions-exporter]="CrowdSec active decisions and ban metrics"
    [crowdsec-exporter]="CrowdSec intrusion detection metrics"
    [database-backup-exporter]="Database backup status and size metrics"
    [dhcp-lease-exporter]="DHCP lease allocation metrics"
    [directory-size-exporter]="Directory size and file count metrics"
    [disk-io-exporter]="Disk I/O throughput and latency metrics"
    [docker-swarm-exporter]="Docker Swarm node and service metrics"
    [dovecot-metrics-exporter]="Dovecot mail server metrics"
    [duplicati-exporter]="Duplicati backup job metrics"
    [elasticsearch-exporter]="Elasticsearch cluster health and index metrics"
    [fail2ban-exporter]="Fail2ban jail and ban metrics"
    [freeradius-exporter]="FreeRADIUS authentication metrics"
    [game-server-exporter]="Game server player count and status metrics"
    [gitea-exporter]="Gitea repository and user metrics"
    [glpi-exporter]="GLPI ITSM ticket and asset metrics"
    [gitlab-metrics-exporter]="GitLab instance performance metrics"
    [gitlab-migration-exporter]="GitLab migration progress metrics"
    [gpu-exporter]="GPU utilization and temperature metrics"
    [graylog-exporter]="Graylog log management metrics"
    [headscale-metrics-exporter]="Headscale coordination server metrics"
    [ip-intel-exporter]="IP intelligence from nginx access logs"
    [jenkins-exporter]="Jenkins build and queue metrics"
    [incus-metrics-exporter]="Incus storage pool, snapshot, and instance inventory metrics"
    [journal-error-exporter]="Journalctl error and warning metrics"
    [keepalived-exporter]="Keepalived VRRP failover metrics"
    [login-attempt-exporter]="SSH and system login attempt metrics"
    [ufw-blocklist-metrics]="UFW blocklist feed, ipset, and block count metrics"
    [users-logged-in]="User login sessions, terminals, sudo, and failed login metrics"
    [logrotate-check-exporter]="Logrotate configuration health metrics"
    [lynis-metrics-exporter]="Lynis security audit score metrics"
    [mailcow-exporter]="Mailcow mail server metrics"
    [memory-pressure-exporter]="Memory pressure and swap usage metrics"
    [mysql-exporter]="MySQL/MariaDB performance metrics"
    [n8n-exporter]="n8n workflow automation metrics"
    [network-info-exporter]="Network interface and routing metrics"
    [nexus-exporter]="Sonatype Nexus Repository metrics"
    [nextcloud-exporter]="Nextcloud instance health metrics"
    [nfs-exporter]="NFS client mount and performance metrics"
    [nfs-server-exporter]="NFS server export and connection metrics"
    [nginx-metrics-exporter]="Nginx connection and request metrics"
    [ntp-drift-exporter]="NTP clock drift and sync metrics"
    [ollama-exporter]="Ollama LLM model and inference metrics"
    [openvpn-exporter]="OpenVPN tunnel and client metrics"
    [password-expiry-exporter]="System user password expiry metrics"
    [pihole-exporter]="Pi-hole DNS filtering metrics"
    [plex-exporter]="Plex media server activity metrics"
    [podman-container-exporter]="Podman container health and resource metrics"
    [postgresql-exporter]="PostgreSQL database performance metrics"
    [postgresql-ha-exporter]="PostgreSQL HA replication metrics"
    [process-metrics-exporter]="Process CPU/memory/state metrics"
    [textfile-health-exporter]="Textfile collector health monitoring"
    [rabbitmq-exporter]="RabbitMQ queue and connection metrics"
    [redis-metrics-exporter]="Redis server performance metrics"
    [redis-sentinel-exporter]="Redis Sentinel failover metrics"
    [restic-backup-exporter]="Restic backup snapshot and size metrics"
    [rsyslog-metrics-exporter]="Rsyslog message processing metrics"
    [samba-exporter]="Samba file share and session metrics"
    [seo-exporter]="SEO health and crawl metrics"
    [smart-drive-exporter]="SMART disk health and temperature metrics"
    [snipeit-exporter]="Snipe-IT asset management metrics"
    [sonarqube-exporter]="SonarQube code quality metrics"
    [squid-exporter]="Squid proxy cache and request metrics"
    [storage-health-exporter]="Storage pool and volume health metrics"
    [suricata-exporter]="Suricata IDS/IPS alert metrics"
    [syncthing-exporter]="Syncthing folder sync and device metrics"
    [systemd-boot-time-exporter]="Systemd boot and service startup timing"
    [systemd-service-exporter]="Systemd service state and restart metrics"
    [systemd-timer-exporter]="Systemd timer schedule and execution metrics"
    [tailscale-exporter]="Tailscale node and network metrics"
    [trivy-cve-auditor]="Trivy container image vulnerability metrics"
    [vault-exporter]="HashiCorp Vault seal and token metrics"
    [vaultwarden-exporter]="Vaultwarden password manager metrics"
    [wazuh-exporter]="Wazuh SIEM alert and agent metrics"
    [web-traffic-exporter]="Web traffic request and response metrics"
    [webtop-selkies-exporter]="Webtop and Selkies container desktop metrics"
    [wickr-io-exporter]="Wickr.io bot and message metrics"
    [wickr-metrics-exporter]="Wickr messaging platform metrics"
    [wireguard-exporter]="WireGuard tunnel and peer metrics"
    [yum-updates-exporter]="Pending yum/dnf package updates"
)

declare -A EXPORTER_CRON=(
    [alertmanager-exporter]="*/5 * * * *"
    [apache-metrics-exporter]="*/3 * * * *"
    [apt-updates-exporter]="0 0 * * *"
    [artifactory-exporter]="*/5 * * * *"
    [backup-status-exporter]="*/15 * * * *"
    [borg-backup-exporter]="*/15 * * * *"
    [caprover-exporter]="*/5 * * * *"
    [clickhouse-exporter]="*/3 * * * *"
    [consul-exporter]="*/3 * * * *"
    [container-health-exporter]="*/3 * * * *"
    [coolify-exporter]="*/5 * * * *"
    [dokku-exporter]="*/5 * * * *"
    [dokploy-exporter]="*/5 * * * *"
    [cron-job-exporter]="*/5 * * * *"
    [crowdsec-decisions-exporter]="*/5 * * * *"
    [crowdsec-exporter]="*/5 * * * *"
    [database-backup-exporter]="*/15 * * * *"
    [dhcp-lease-exporter]="*/5 * * * *"
    [directory-size-exporter]="*/15 * * * *"
    [disk-io-exporter]="*/3 * * * *"
    [docker-swarm-exporter]="*/3 * * * *"
    [dovecot-metrics-exporter]="*/5 * * * *"
    [duplicati-exporter]="*/15 * * * *"
    [elasticsearch-exporter]="*/3 * * * *"
    [fail2ban-exporter]="*/5 * * * *"
    [freeradius-exporter]="*/5 * * * *"
    [game-server-exporter]="*/3 * * * *"
    [gitea-exporter]="*/5 * * * *"
    [glpi-exporter]="*/5 * * * *"
    [gitlab-metrics-exporter]="*/5 * * * *"
    [gitlab-migration-exporter]="*/5 * * * *"
    [gpu-exporter]="*/3 * * * *"
    [graylog-exporter]="*/5 * * * *"
    [headscale-metrics-exporter]="*/5 * * * *"
    [ip-intel-exporter]="*/5 * * * *"
    [jenkins-exporter]="*/5 * * * *"
    [incus-metrics-exporter]="*/5 * * * *"
    [journal-error-exporter]="*/5 * * * *"
    [keepalived-exporter]="*/5 * * * *"
    [login-attempt-exporter]="*/5 * * * *"
    [ufw-blocklist-metrics]="*/5 * * * *"
    [users-logged-in]="*/3 * * * *"
    [logrotate-check-exporter]="0 */6 * * *"
    [lynis-metrics-exporter]="0 0 * * *"
    [mailcow-exporter]="*/5 * * * *"
    [memory-pressure-exporter]="*/3 * * * *"
    [mysql-exporter]="*/3 * * * *"
    [n8n-exporter]="*/5 * * * *"
    [network-info-exporter]="*/5 * * * *"
    [nexus-exporter]="*/5 * * * *"
    [nextcloud-exporter]="*/5 * * * *"
    [nfs-exporter]="*/5 * * * *"
    [nfs-server-exporter]="*/5 * * * *"
    [nginx-metrics-exporter]="*/3 * * * *"
    [ntp-drift-exporter]="*/5 * * * *"
    [ollama-exporter]="*/5 * * * *"
    [openvpn-exporter]="*/5 * * * *"
    [password-expiry-exporter]="0 0 * * *"
    [pihole-exporter]="*/5 * * * *"
    [plex-exporter]="*/5 * * * *"
    [podman-container-exporter]="*/3 * * * *"
    [postgresql-exporter]="*/3 * * * *"
    [postgresql-ha-exporter]="*/3 * * * *"
    [process-metrics-exporter]="*/3 * * * *"
    [textfile-health-exporter]="*/5 * * * *"
    [rabbitmq-exporter]="*/5 * * * *"
    [redis-metrics-exporter]="*/3 * * * *"
    [redis-sentinel-exporter]="*/5 * * * *"
    [restic-backup-exporter]="*/15 * * * *"
    [rsyslog-metrics-exporter]="*/5 * * * *"
    [samba-exporter]="*/5 * * * *"
    [seo-exporter]="0 */6 * * *"
    [smart-drive-exporter]="*/15 * * * *"
    [snipeit-exporter]="*/5 * * * *"
    [sonarqube-exporter]="*/5 * * * *"
    [squid-exporter]="*/5 * * * *"
    [storage-health-exporter]="*/15 * * * *"
    [suricata-exporter]="*/5 * * * *"
    [syncthing-exporter]="*/5 * * * *"
    [systemd-boot-time-exporter]="*/15 * * * *"
    [systemd-service-exporter]="*/5 * * * *"
    [systemd-timer-exporter]="*/5 * * * *"
    [tailscale-exporter]="*/5 * * * *"
    [trivy-cve-auditor]="*/30 * * * *"
    [vault-exporter]="*/5 * * * *"
    [vaultwarden-exporter]="*/5 * * * *"
    [wazuh-exporter]="*/5 * * * *"
    [web-traffic-exporter]="*/5 * * * *"
    [webtop-selkies-exporter]="*/3 * * * *"
    [wickr-io-exporter]="*/5 * * * *"
    [wickr-metrics-exporter]="*/5 * * * *"
    [wireguard-exporter]="*/5 * * * *"
    [yum-updates-exporter]="0 0 * * *"
)

# ============================================================================
# HELPERS
# ============================================================================

log()  { echo "# $*"; }
warn() { echo "# WARN: $*" >&2; }
err()  { echo "# ERROR: $*" >&2; }
die()  { err "$@"; exit 1; }

check_root() {
    [[ $EUID -eq 0 ]] || die "Must run as root (need write access to ${INSTALL_DIR}/ and ${CRON_DIR}/)"
}

download() {
    local url="$1" dest="$2"
    if command -v wget &>/dev/null; then
        wget -q -O "$dest" "$url"
    elif command -v curl &>/dev/null; then
        curl -fsSL -o "$dest" "$url"
    else
        die "Neither wget nor curl found"
    fi
}

get_script_version() {
    local file="$1"
    grep -m1 '^# Version:' "$file" 2>/dev/null | awk '{print $3}' || echo "unknown"
}

# ============================================================================
# LIST
# ============================================================================

cmd_list() {
    log "Available exporters from mylinux.work (${#EXPORTER_DESC[@]} total)"
    log ""
    printf "# %-40s %-15s %s\n" "EXPORTER" "DEFAULT CRON" "DESCRIPTION"
    printf "# %-40s %-15s %s\n" "--------" "------------" "-----------"

    for name in $(echo "${!EXPORTER_DESC[@]}" | tr ' ' '\n' | sort); do
        local cron="${EXPORTER_CRON[$name]:-*/5 * * * *}"
        local desc="${EXPORTER_DESC[$name]}"
        local installed=""
        [[ -f "${INSTALL_DIR}/${name}.sh" ]] && installed=" [installed]"
        printf "# %-40s %-15s %s%s\n" "$name" "$cron" "$desc" "$installed"
    done
}

# ============================================================================
# INSTALL
# ============================================================================

cmd_install() {
    check_root

    local names=()
    local cron_schedule=""

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --cron)
                cron_schedule="$2"
                shift 2
                ;;
            -*)
                die "Unknown option: $1"
                ;;
            *)
                names+=("$1")
                shift
                ;;
        esac
    done

    [[ ${#names[@]} -gt 0 ]] || die "No exporter name(s) specified"

    for name in "${names[@]}"; do
        install_one "$name" "$cron_schedule"
    done
}

install_one() {
    local name="$1"
    local cron_schedule="$2"
    local url="${BASE_URL}/${name}.sh"
    local dest="${INSTALL_DIR}/${name}.sh"
    local temp_file

    if [[ -z "${EXPORTER_DESC[$name]+x}" ]]; then
        warn "Unknown exporter '${name}' — not in the built-in list, attempting download anyway"
    fi

    log "Installing ${name}..."

    temp_file=$(mktemp "${STAGE_DIR}/${name}.XXXXXX")
    if ! download "$url" "$temp_file"; then
        rm -f "$temp_file"
        err "Failed to download ${url}"
        return 1
    fi

    if [[ ! -s "$temp_file" ]]; then
        rm -f "$temp_file"
        err "Downloaded file is empty: ${url}"
        return 1
    fi

    chmod +x "$temp_file"

    log "Validating ${name}..."
    local test_output
    test_output=$("$temp_file" 2>/dev/null || true)
    local line_count
    line_count=$(echo "$test_output" | wc -l)

    if [[ "$line_count" -lt 3 ]]; then
        rm -f "$temp_file"
        err "Validation failed — ${name} produced only ${line_count} lines of output"
        return 1
    fi

    mv -f "$temp_file" "$dest"
    chmod +x "$dest"
    log "Installed ${dest}"

    if [[ -n "$cron_schedule" ]]; then
        local default_cron="$cron_schedule"
    elif [[ -n "${EXPORTER_CRON[$name]+x}" ]]; then
        local default_cron="${EXPORTER_CRON[$name]}"
        log "No --cron specified, using default: ${default_cron}"
    else
        local default_cron=""
    fi

    if [[ -n "$default_cron" ]]; then
        mkdir -p "$TEXTFILE_DIR"
        local cron_file="${CRON_DIR}/${name}"
        cat > "$cron_file" <<EOF
# Managed by deploy-exporter.sh — do not edit manually
${default_cron} root ${INSTALL_DIR}/${name}.sh --textfile 2>&1
EOF
        log "Created cron job: ${cron_file}"
        log "  Schedule: ${default_cron}"
        log "  Command:  ${INSTALL_DIR}/${name}.sh --textfile"
    fi

    log "${name} installed successfully"
    log ""
}

# ============================================================================
# STATUS
# ============================================================================

cmd_status() {
    local found=0

    log "Installed exporters in ${INSTALL_DIR}/:"
    log ""
    printf "# %-40s %-12s %-10s %s\n" "EXPORTER" "VERSION" "CRON" "LAST .prom UPDATE"
    printf "# %-40s %-12s %-10s %s\n" "--------" "-------" "----" "-----------------"

    for script in "${INSTALL_DIR}"/*-exporter.sh; do
        [[ -f "$script" ]] || continue
        found=1

        local name
        name=$(basename "$script" .sh)
        local version
        version=$(get_script_version "$script")

        local cron_status="none"
        [[ -f "${CRON_DIR}/${name}" ]] && cron_status="active"

        local prom_name
        prom_name=$(echo "$name" | tr '-' '_')
        local prom_file="${TEXTFILE_DIR}/${prom_name}.prom"
        local prom_age="no .prom file"

        if [[ -f "$prom_file" ]]; then
            local mod_time now age_sec
            mod_time=$(stat -c %Y "$prom_file" 2>/dev/null || echo 0)
            now=$(date +%s)
            age_sec=$(( now - mod_time ))

            if [[ $age_sec -lt 60 ]]; then
                prom_age="${age_sec}s ago"
            elif [[ $age_sec -lt 3600 ]]; then
                prom_age="$(( age_sec / 60 ))m ago"
            elif [[ $age_sec -lt 86400 ]]; then
                prom_age="$(( age_sec / 3600 ))h ago"
            else
                prom_age="$(( age_sec / 86400 ))d ago (STALE)"
            fi
        fi

        printf "# %-40s %-12s %-10s %s\n" "$name" "$version" "$cron_status" "$prom_age"
    done

    if [[ $found -eq 0 ]]; then
        log "No exporters installed in ${INSTALL_DIR}/"
    fi
}

# ============================================================================
# REMOVE
# ============================================================================

cmd_remove() {
    check_root
    [[ $# -gt 0 ]] || die "No exporter name specified"

    for name in "$@"; do
        remove_one "$name"
    done
}

remove_one() {
    local name="$1"
    local script="${INSTALL_DIR}/${name}.sh"
    local cron_file="${CRON_DIR}/${name}"
    local prom_name
    prom_name=$(echo "$name" | tr '-' '_')
    local prom_file="${TEXTFILE_DIR}/${prom_name}.prom"

    if [[ ! -f "$script" ]]; then
        warn "${name} is not installed in ${INSTALL_DIR}/"
        return 1
    fi

    rm -f "$script"
    log "Removed ${script}"

    if [[ -f "$cron_file" ]]; then
        rm -f "$cron_file"
        log "Removed cron job: ${cron_file}"
    fi

    if [[ -f "$prom_file" ]]; then
        rm -f "$prom_file"
        log "Removed .prom file: ${prom_file}"
    fi

    log "${name} removed"
    log ""
}

# ============================================================================
# UPDATE
# ============================================================================

cmd_update() {
    check_root

    local found=0

    for script in "${INSTALL_DIR}"/*-exporter.sh; do
        [[ -f "$script" ]] || continue
        found=1

        local name
        name=$(basename "$script" .sh)
        local old_version
        old_version=$(get_script_version "$script")

        log "Updating ${name} (current: v${old_version})..."

        local url="${BASE_URL}/${name}.sh"
        local temp_file
        temp_file=$(mktemp "${STAGE_DIR}/${name}.XXXXXX")

        if ! download "$url" "$temp_file"; then
            rm -f "$temp_file"
            err "Failed to download ${name}, skipping"
            continue
        fi

        if [[ ! -s "$temp_file" ]]; then
            rm -f "$temp_file"
            err "Downloaded file is empty for ${name}, skipping"
            continue
        fi

        local new_version
        new_version=$(get_script_version "$temp_file")

        chmod +x "$temp_file"
        mv -f "$temp_file" "$script"
        chmod +x "$script"

        if [[ "$old_version" == "$new_version" ]]; then
            log "${name}: v${new_version} (unchanged)"
        else
            log "${name}: v${old_version} → v${new_version}"
        fi
    done

    if [[ $found -eq 0 ]]; then
        log "No exporters installed in ${INSTALL_DIR}/"
    fi
}

# ============================================================================
# USAGE
# ============================================================================

show_usage() {
    cat <<EOF
Usage: $(basename "$0") COMMAND [OPTIONS]

Deploy, manage, and update Prometheus exporters from mylinux.work.

COMMANDS:
    list                            List available exporters
    install NAME [NAME...] [OPTS]   Install one or more exporters
    status                          Show installed exporters and their state
    remove NAME [NAME...]           Remove one or more exporters
    update                          Update all installed exporters

INSTALL OPTIONS:
    --cron "SCHEDULE"               Set cron schedule (default: per-exporter)

EXAMPLES:
    $(basename "$0") list
    $(basename "$0") install process-metrics-exporter
    $(basename "$0") install process-metrics-exporter --cron "*/3 * * * *"
    $(basename "$0") install process-metrics-exporter journal-error-exporter
    $(basename "$0") status
    $(basename "$0") remove process-metrics-exporter
    $(basename "$0") update
EOF
    exit 0
}

# ============================================================================
# MAIN
# ============================================================================

[[ $# -gt 0 ]] || show_usage

COMMAND="$1"
shift

case "$COMMAND" in
    list)       cmd_list ;;
    install)    cmd_install "$@" ;;
    status)     cmd_status ;;
    remove)     cmd_remove "$@" ;;
    update)     cmd_update ;;
    -h|--help)  show_usage ;;
    *)          die "Unknown command: ${COMMAND} — run with --help for usage" ;;
esac
