massive update. Master conf split, now modular with a load sceriprt to drive all configs to scripts. with unraid scpecific safeguard tests , and improved standardized ux. including dynamic host detect, who am i who else it there. EVERY SINGLE SCRIPT UPDATED. DEBATING THAT THIS IS ACUALLY V2

This commit is contained in:
2026-05-03 17:16:49 -04:00
parent 2691a35e80
commit ec7de648dc
72 changed files with 25640 additions and 14629 deletions
+129 -61
View File
@@ -1,73 +1,130 @@
#!/bin/bash
# -----------------------------------------------------------------------------------------------
# --------------------------------- Certificate Monitor ----------------------------------------
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ================================= Certificate Monitor ========================================
# ==============================================================================================
# Monitors SSL certificate expiry for all configured domains by connecting directly
# via openssl — no dependency on NPM or any other service. Reads the actual certificate
# the server is presenting to the outside world.
#
# This approach catches real-world cert issues that API-based checks miss:
# - Cert renewed but server not reloaded
# - Wrong cert being served
# - Cert chain issues
# ── WHY DIRECT OPENSSL ────────────────────────────────────────────────────────────────────────
# Catches real-world cert issues that API-based checks miss:
# - Cert renewed in NPM but server not reloaded (old cert still serving)
# - Wrong cert being served to external clients
# - Cert chain issues not visible from the internal network
# - NPM reporting healthy while the world sees an expired cert
#
# Each domain and subdomain is a separate entry — they have independent certs.
# Silent when all certs are healthy. Notifies when any approach warning threshold.
# Notifications batched per severity — one message for warnings, one for criticals.
# ── BEHAVIOUR ─────────────────────────────────────────────────────────────────────────────────
# Each domain is checked independently — they have independent certs.
# Results per domain:
# HEALTHY — > CERT_WARN_DAYS remaining — silent ✅
# WARNING — <= CERT_WARN_DAYS remaining — notifies
# CRITICAL — <= CERT_CRIT_DAYS remaining — notifies with urgency
# FAILED — could not connect or parse cert — notifies
#
# All configuration in Master.conf under Certificate Monitor section.
# Supports --dry-run to check certs and show results without sending notifications.
# -----------------------------------------------------------------------------------------------
# Notifications batched per severity — one message per severity level, not per domain.
# This is a monitor script — SILENT_MODE=false — output is the point.
#
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
# detect_hosts() sets MY_ID and aliases HOST*_CERT_MONITOR_DOMAINS → CERT_MONITOR_DOMAINS.
# Each server monitors its own domains — HOST1 monitors Gmer4Lfe.com etc.
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# acquire_lock — prevents concurrent runs
# detect_hosts() — correct domain list per host via MY_ID aliases
# Empty array guard — warns and exits cleanly if no domains configured
# CERT_TIMEOUT — openssl connects are time-limited per domain
# validate_unraid_cmd — openssl and notify validated before use
# Silent healthy certs — only problems produce visible output
#
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
# HOST*_CERT_MONITOR_DOMAINS — domains checked by this host
# Aliased by detect_hosts() — script uses CERT_MONITOR_DOMAINS
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# CERT_WARN_DAYS — warn when cert expires within this many days (default 30)
# CERT_CRIT_DAYS — critical alert within this many days (default 7)
# CERT_TIMEOUT — seconds per domain before giving up (default 10)
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# cert_monitor.sh — normal run
# cert_monitor.sh --dry-run — check certs and show results, no notifications
# cert_monitor.sh --log — verbose output
# cert_monitor.sh --status — show config and exit
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../Master.conf"
source "$SCRIPT_DIR/../common.sh"
source "$SCRIPT_DIR/../load_config.sh"
# Monitor script — output is the point
SILENT_MODE=false
parse_args "$@"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_GEAR Setup ━━━
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ Setup ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_GEAR Setup ━━━"
if ! command -v openssl >/dev/null 2>&1; then
error "openssl not found — required for certificate checks"
if [[ "$EUID" -ne 0 ]]; then
error "Must be run as root"
exit 1
fi
success "openssl available"
# Validate openssl — required for all cert checks
validate_unraid_cmd \
"$(command -v openssl 2>/dev/null || echo /usr/bin/openssl)" \
"version" "OpenSSL" \
"openssl" || { error "openssl not found — required for certificate checks"; exit 1; }
validate_unraid_cmd \
"/usr/local/emhttp/plugins/dynamix/scripts/notify" \
"" "" \
"unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"
acquire_lock
# detect_hosts() sets MY_ID and aliases HOST*_CERT_MONITOR_DOMAINS
detect_hosts
# Empty array guard
if [[ ${#CERT_MONITOR_DOMAINS[@]} -eq 0 ]]; then
warn "CERT_MONITOR_DOMAINS is empty in Master.conf — add your domains to enable monitoring"
warn "CERT_MONITOR_DOMAINS is empty for $MY_ID"
warn "Check HOST*_CERT_MONITOR_DOMAINS in master_host*.conf"
exit 0
fi
info "$ICON_CERT Domains to check: ${#CERT_MONITOR_DOMAINS[@]}"
log "Domains to check: ${#CERT_MONITOR_DOMAINS[@]}"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Status ━━━
# -----------------------------------------------------------------------------------------------
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — results shown but no notifications sent"
# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
echo ""
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
echo "$ICON_CERT Domains: ${CERT_MONITOR_DOMAINS[*]}"
echo "$ICON_WARN Warn at: ${CERT_WARN_DAYS} days remaining"
echo "$ICON_ERROR Crit at: ${CERT_CRIT_DAYS} days remaining"
echo "$ICON_TIME Timeout: ${CERT_TIMEOUT}s per domain"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_CERT Domains: ${CERT_MONITOR_DOMAINS[*]}"
echo "$ICON_WARN Warn at: ${CERT_WARN_DAYS} days remaining"
echo "$ICON_ERROR Crit at: ${CERT_CRIT_DAYS} days remaining"
echo "$ICON_TIME Timeout: ${CERT_TIMEOUT}s per domain"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — results shown but no notifications sent"
# -----------------------------------------------------------------------------------------------
# CERT CHECK FUNCTION
# ==============================================================================================
# ── CERT CHECK FUNCTION ───────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Connects to domain:443 via openssl, extracts expiry date, calculates days remaining.
# Returns 0=healthy 1=warning 2=critical 3=failed
# -----------------------------------------------------------------------------------------------
# Returns:
# 0 = healthy (> CERT_WARN_DAYS)
# 1 = warning (<= CERT_WARN_DAYS)
# 2 = critical (<= CERT_CRIT_DAYS)
# 3 = failed (could not connect or parse)
check_cert() {
local domain="$1"
local port="${2:-443}"
@@ -79,7 +136,7 @@ check_cert() {
2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [[ -z "$expiry_str" ]]; then
error "$ICON_CERT $domain — could not retrieve certificate"
error "$ICON_CERT $domain — could not retrieve certificate (unreachable or no TLS)"
return 3
fi
@@ -103,18 +160,19 @@ check_cert() {
warn "$ICON_CERT $domain — WARNING: ${days_remaining} days remaining (expires $expiry_display)"
return 1
else
success "$ICON_CERT $domain — OK: ${days_remaining} days remaining (expires $expiry_display)"
log "$ICON_CERT $domain — OK: ${days_remaining} days remaining (expires $expiry_display)"
return 0
fi
}
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_CERT Certificate Monitor ━━━
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ Certificate Monitor ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_CERT Certificate Monitor — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
echo "$ICON_WARN Warn threshold: ${CERT_WARN_DAYS} days"
echo "$ICON_ERROR Crit threshold: ${CERT_CRIT_DAYS} days"
echo "$ICON_HOST $MY_ID ($LOCAL_SERVER_NAME)"
log "Warn threshold: ${CERT_WARN_DAYS} days"
log "Crit threshold: ${CERT_CRIT_DAYS} days"
echo ""
START=$(date +%s)
@@ -126,7 +184,6 @@ declare -A DOMAIN_STATUS
for domain in "${CERT_MONITOR_DOMAINS[@]}"; do
[[ -z "$domain" ]] && continue
echo "━━━ $ICON_CERT $domain ━━━"
check_cert "$domain"
result=$?
case $result in
@@ -135,46 +192,57 @@ for domain in "${CERT_MONITOR_DOMAINS[@]}"; do
2) CRITICAL+=("$domain"); DOMAIN_STATUS["$domain"]="CRIT" ;;
3) FAILED+=("$domain"); DOMAIN_STATUS["$domain"]="FAIL" ;;
esac
echo ""
done
END=$(date +%s)
# ── Send notifications — batched per severity ─────────────────────────────────────────────────
if [[ "$DRY_RUN" == false ]]; then
[[ ${#CRITICAL[@]} -gt 0 ]] && \
notify "Certificate CRITICAL on $(hostname) — expiring within ${CERT_CRIT_DAYS} days: ${CRITICAL[*]}" "Certificate Monitor" "warning"
notify "Certificate CRITICAL on $(hostname) — expiring within ${CERT_CRIT_DAYS} days: ${CRITICAL[*]}" \
"Certificate Monitor" "warning"
[[ ${#WARNING[@]} -gt 0 ]] && \
notify "Certificate WARNING on $(hostname) — expiring within ${CERT_WARN_DAYS} days: ${WARNING[*]}" "Certificate Monitor" "warning"
notify "Certificate WARNING on $(hostname) — expiring within ${CERT_WARN_DAYS} days: ${WARNING[*]}" \
"Certificate Monitor" "warning"
[[ ${#FAILED[@]} -gt 0 ]] && \
notify "Certificate check FAILED on $(hostname) — could not reach: ${FAILED[*]}" "Certificate Monitor" "warning"
notify "Certificate check FAILED on $(hostname) — could not reach: ${FAILED[*]}" \
"Certificate Monitor" "warning"
fi
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Summary ━━━
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY CERTIFICATE MONITOR SUMMARY ━━━━━"
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_TIME Duration: $(format_duration $(( END - START )))"
echo ""
echo " $ICON_SUCCESS Healthy: ${#HEALTHY[@]} $ICON_WARN Warning: ${#WARNING[@]} $ICON_ERROR Critical: ${#CRITICAL[@]} Failed: ${#FAILED[@]}"
echo " $ICON_SUCCESS Healthy: ${#HEALTHY[@]}"
[[ ${#WARNING[@]} -gt 0 ]] && warn "Warning: ${#WARNING[@]} — renewal recommended"
[[ ${#CRITICAL[@]} -gt 0 ]] && echo "$ICON_ERROR Critical: ${#CRITICAL[@]} — ACTION REQUIRED"
[[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed: ${#FAILED[@]} — unreachable"
echo ""
# Per-domain results — only show problems, healthy ones stay in log()
for domain in "${CERT_MONITOR_DOMAINS[@]}"; do
[[ -z "$domain" ]] && continue
case "${DOMAIN_STATUS[$domain]:-UNKN}" in
OK) echo " $ICON_SUCCESS $domain" ;;
WARN) echo " $ICON_WARN $domain" ;;
CRIT) echo " $ICON_ERROR $domain" ;;
FAIL) echo " $ICON_ERROR $domain (unreachable)" ;;
OK) log " $ICON_SUCCESS $domain — healthy" ;;
WARN) warn " $ICON_WARN $domain — warning" ;;
CRIT) echo " $ICON_ERROR $domain — CRITICAL" ;;
FAIL) echo " $ICON_ERROR $domain unreachable" ;;
esac
done
echo ""
if [[ "$DRY_RUN" == true ]]; then
echo "$ICON_WARN Status: DRY RUN — no notifications sent"
warn "DRY RUN — no notifications sent"
elif [[ ${#CRITICAL[@]} -gt 0 || ${#FAILED[@]} -gt 0 ]]; then
echo "$ICON_ERROR Status: ACTION REQUIRED"
elif [[ ${#WARNING[@]} -gt 0 ]]; then
echo "$ICON_WARN Status: WARNINGS — renewal recommended"
warn "Status: WARNINGS — renewal recommended"
else
echo "$ICON_DONE Status: $ICON_SUCCESS ALL CERTS HEALTHY"
log "$ICON_DONE Status: all ${#HEALTHY[@]} certs healthy ✅"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"