#!/bin/bash ################################################################################ # Script Name: deploy-exporter.sh # Version: 1.0 # 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" # ============================================================================ # 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 "/tmp/${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" <&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 "/tmp/${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 <