#!/bin/bash # ============================================================================================== # ================================= Mesh Monitor =============================================== # ============================================================================================== # # PURPOSE # ───────────────────────────────────────────────────────────────────────────── # At-a-glance view of everyone in the mesh — who they are, how to reach them, # and what services each node has put up for mutual fallback protection. # Read-only — sources conf data only, no SSH or API calls, no sensitive data. # # ============================================================================================== # OPERATIONAL MODEL # ============================================================================================== # # Reads directly from all three conf files without detect_hosts(). # Iterates all defined HOST* vars — scales automatically as new nodes join. # # Sections: # MEMBERS — server name, owner, contact email for each HOST* # COVERAGE — what each host runs for others when they go down, by tier # PARTNERSHIP — enabled/disabled, current owner, sync interval # # Safe to share with mesh members — contains no API keys, passwords, or SSH keys. # # ============================================================================================== # CONFIGURATION # ============================================================================================== # # host*.conf — per-host identity vars read by this script: # # HOST*_OWNER human-readable owner name # HOST*_OWNER_EMAIL contact email — also used by NPM for Let's Encrypt # # ============================================================================================== # RUNTIME MODES # ============================================================================================== # # mesh_monitor.sh — show full mesh overview # mesh_monitor.sh --log — verbose output # mesh_monitor.sh --status — show config and exit # # ============================================================================================== SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../load_config.sh" parse_args "$@" # ── Helpers ─────────────────────────────────────────────────────────────────────────────────── # Populate ALL_HOST_IDS with every defined HOST* var collect_hosts() { ALL_HOST_IDS=() for h in HOST1 HOST2 HOST3 HOST4 HOST5 HOST6 HOST7 HOST8; do [[ -n "${!h:-}" ]] && ALL_HOST_IDS+=("$h") done } # Read a bash array by computed name — returns space-separated elements get_array() { eval "echo \"\${${1}[*]}\""; } # Convert minutes to human-readable delay string format_delay() { local m="${1:-0}" (( m == 0 )) && { echo "immediate"; return; } (( m >= 1440 )) && { echo "$((m / 1440))d"; return; } (( m >= 60 )) && { echo "$((m / 60))hr"; return; } echo "${m}min" } collect_hosts log "$ICON_HOST Hosts: ${ALL_HOST_IDS[*]} (${#ALL_HOST_IDS[@]} in mesh)" # ============================================================================================== # ━━━ Status ━━━ # ============================================================================================== if [[ "$SHOW_STATUS" == true ]]; then echo "" echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━" echo " Hosts: ${ALL_HOST_IDS[*]}" for h in "${ALL_HOST_IDS[@]}"; do owner_var="${h}_OWNER"; owner="${!owner_var:-unknown}" email_var="${h}_OWNER_EMAIL"; email="${!email_var:-(not set)}" echo " $h: ${!h} / $owner / $email" done echo " Mode: read-only — conf data only, no SSH or API calls" echo "━━━━━━━━━━━━━━━━━━━━━━━" exit 0 fi # ============================================================================================== # ━━━ Mesh Overview ━━━ # ============================================================================================== echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " MESH OVERVIEW" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" # ── Members ─────────────────────────────────────────────────────────────────────────────────── echo "" echo "━━━ $ICON_HOST Members ━━━" echo "" # Dynamic column widths W_HOST=4; W_SERVER=6; W_OWNER=5 for h in "${ALL_HOST_IDS[@]}"; do server="${!h}" owner_var="${h}_OWNER"; owner="${!owner_var:-unknown}" (( ${#h} > W_HOST )) && W_HOST=${#h} (( ${#server} > W_SERVER )) && W_SERVER=${#server} (( ${#owner} > W_OWNER )) && W_OWNER=${#owner} done printf " %-${W_HOST}s %-${W_SERVER}s %-${W_OWNER}s %s\n" \ "HOST" "SERVER" "OWNER" "EMAIL" printf " %-${W_HOST}s %-${W_SERVER}s %-${W_OWNER}s %s\n" \ "$(printf '─%.0s' $(seq 1 $W_HOST))" \ "$(printf '─%.0s' $(seq 1 $W_SERVER))" \ "$(printf '─%.0s' $(seq 1 $W_OWNER))" \ "─────────────────────────" for h in "${ALL_HOST_IDS[@]}"; do server="${!h}" owner_var="${h}_OWNER"; owner="${!owner_var:-unknown}" email_var="${h}_OWNER_EMAIL"; email="${!email_var:-(not set)}" printf " %-${W_HOST}s %-${W_SERVER}s %-${W_OWNER}s %s\n" \ "$h" "$server" "$owner" "$email" log "$h: server=$server owner=$owner email=$email" done # ── Protected Services ──────────────────────────────────────────────────────────────────────── echo "" echo "━━━ $ICON_SHIELD Protected Services ━━━" COVERAGE_FOUND=false # Iterate from the perspective of each protected host — what do they want covered for covered in "${ALL_HOST_IDS[@]}"; do covered_owner_var="${covered}_OWNER"; covered_owner="${!covered_owner_var:-$covered}" covered_email_var="${covered}_OWNER_EMAIL"; covered_email="${!covered_email_var:-}" # Find who covers this host and collect their tiers any_tiers=false declare -A tier_containers=() declare -A tier_delays=() covered_by="" for covering in "${ALL_HOST_IDS[@]}"; do [[ "$covering" == "$covered" ]] && continue for tier in 1 2 3 4; do containers=$(get_array "FALLBACK_${covering}_COVERS_${covered}_TIER${tier}") [[ -z "$containers" ]] && continue any_tiers=true tier_containers[$tier]="$containers" delay_var="${covered}_TIER${tier}_DELAY" tier_delays[$tier]="${!delay_var:-0}" done [[ "$any_tiers" == true ]] && { covering_owner_var="${covering}_OWNER" covering_owner="${!covering_owner_var:-$covering}" covered_by="$covering ($covering_owner)" } done [[ "$any_tiers" == false ]] && continue COVERAGE_FOUND=true echo "" header="$covered $covered_owner" [[ -n "$covered_email" ]] && header+=" — $covered_email" echo " $header" for tier in 1 2 3 4; do [[ -z "${tier_containers[$tier]:-}" ]] && continue if [[ "$tier" -eq 1 ]]; then printf " Tier 1 %-12s %s\n" "(immediate)" "${tier_containers[$tier]// /, }" else delay=$(format_delay "${tier_delays[$tier]:-0}") printf " Tier %s %-12s %s\n" "$tier" "($delay)" "${tier_containers[$tier]// /, }" fi done [[ -n "$covered_by" ]] && echo " Covered by: $covered_by" unset tier_containers tier_delays declare -A tier_containers=() declare -A tier_delays=() done if [[ "$COVERAGE_FOUND" == false ]]; then echo "" echo " No fallback coverage configured" fi # ── Partnership ─────────────────────────────────────────────────────────────────────────────── echo "" echo "━━━ $ICON_NET Partnership ━━━" echo "" if [[ "${PARTNERSHIP_ENABLED:-false}" == true ]]; then owner_host="${PARTNERSHIP_OWNER_HOST:-HOST1}" owner_server="${!owner_host:-unknown}" owner_name_var="${owner_host}_OWNER"; owner_name="${!owner_name_var:-unknown}" echo " Status enabled" echo " Owner $owner_host ($owner_server / $owner_name)" echo " Sync every ${PARTNERSHIP_SYNC_INTERVAL:-15}min" else echo " Status disabled" fi echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" exit 0