#!/usr/bin/env bash # wg_status — Parsea `wg show dump` a JSON estructurado con peers, # handshake age, status (online/stale/never), bytes rx/tx. # Resuelve device_id desde comentarios # DeviceID: en wg0.conf. # # Usage: # wg_status [interface_name] # default: wg0 # # Env: # WG_FAKE_DUMP= # lee dump de archivo en vez de llamar wg show (para tests) wg_status() { local iface="${1:-wg0}" local conf="${WG_FAKE_CONF:-/etc/wireguard/${iface}.conf}" local now now=$(date +%s) # --- obtener dump (real o fake) --- local dump if [[ -n "${WG_FAKE_DUMP:-}" ]]; then if [[ ! -f "$WG_FAKE_DUMP" ]]; then printf '{"error":"WG_FAKE_DUMP file not found: %s"}\n' "$WG_FAKE_DUMP" return 1 fi dump=$(cat "$WG_FAKE_DUMP") else if ! command -v wg &>/dev/null; then printf '{"error":"wg command not found"}\n' return 1 fi if ! dump=$(wg show "$iface" dump 2>&1); then if echo "$dump" | grep -qi "no such device\|does not exist\|unable to access interface"; then printf '{"error":"interface not found"}\n' return 1 fi printf '{"error":"%s"}\n' "$(echo "$dump" | head -n1 | sed 's/"/\\"/g')" return 1 fi fi # --- primera linea: info de la propia interface --- # formato: \t\t\t local iface_line iface_line=$(echo "$dump" | head -n1) local iface_pubkey iface_port iface_pubkey=$(echo "$iface_line" | awk -F'\t' '{print $2}') iface_port=$(echo "$iface_line" | awk -F'\t' '{print $3}') # --- leer DeviceID map desde wg0.conf --- # Busca patron: # # DeviceID: # [Peer] # PublicKey = # Producimos pares "pk\tdevice_id" en un archivo temporal para lookup via awk local device_map device_map=$(awk ' /^#[[:space:]]*DeviceID:/ { split($0, a, "DeviceID:") did = a[2] gsub(/^[[:space:]]+|[[:space:]]+$/, "", did) pending_did = did } /^\[Peer\]/ { in_peer = 1 } in_peer && /^PublicKey[[:space:]]*=/ { pk = $0 sub(/^PublicKey[[:space:]]*=[[:space:]]*/, "", pk) gsub(/^[[:space:]]+|[[:space:]]+$/, "", pk) if (pending_did != "") { print pk "\t" pending_did pending_did = "" } in_peer = 0 } ' "$conf" 2>/dev/null) # --- parsear peers (lineas 2..N del dump) --- # formato peer: \t\t\t\t\t\t\t local peers_json peers_json=$(echo "$dump" | tail -n +2 | awk -v now="$now" -v dmap="$device_map" ' BEGIN { # construir lookup device_id n = split(dmap, lines, "\n") for (i = 1; i <= n; i++) { if (lines[i] != "") { split(lines[i], parts, "\t") pk_to_did[parts[1]] = parts[2] } } first = 1 printf "[" } NF >= 7 { pk = $1 endpoint = $3 allowed = $4 hs = $5 + 0 rx = $6 + 0 tx = $7 + 0 ka = $8 # device_id lookup did = (pk in pk_to_did) ? pk_to_did[pk] : "" # handshake age y status if (hs == 0) { ago = 0 status = "never" } else { ago = now - hs if (ago < 180) status = "online" else if (ago < 86400) status = "stale" else status = "stale" } # persistent_keepalive ka_val = (ka == "off" || ka == "") ? 0 : ka + 0 # endpoint null si "(none)" ep_val = (endpoint == "(none)") ? "null" : "\"" endpoint "\"" # allowed_ips array n_ips = split(allowed, ips_arr, ",") ips_json = "[" for (j = 1; j <= n_ips; j++) { gsub(/^[[:space:]]+|[[:space:]]+$/, "", ips_arr[j]) ips_json = ips_json "\"" ips_arr[j] "\"" if (j < n_ips) ips_json = ips_json "," } ips_json = ips_json "]" if (!first) printf "," first = 0 printf "{" printf "\"public_key\":\"%s\"", pk printf ",\"device_id\":\"%s\"", did printf ",\"endpoint\":%s", ep_val printf ",\"allowed_ips\":%s", ips_json printf ",\"latest_handshake_unix\":%d", hs printf ",\"latest_handshake_ago_s\":%d",ago printf ",\"rx_bytes\":%d", rx printf ",\"tx_bytes\":%d", tx printf ",\"persistent_keepalive\":%d", ka_val printf ",\"status\":\"%s\"", status printf "}" } END { printf "]" } ' FS='\t') # --- output final --- printf '{"interface":"%s","public_key":"%s","listen_port":%s,"peers":%s}\n' \ "$iface" "$iface_pubkey" "$iface_port" "$peers_json" } # Permitir invocacion directa: bash wg_status.sh [iface] if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then wg_status "$@" fi