621e8895c9
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
162 lines
5.1 KiB
Bash
162 lines
5.1 KiB
Bash
#!/usr/bin/env bash
|
|
# wg_status — Parsea `wg show <iface> dump` a JSON estructurado con peers,
|
|
# handshake age, status (online/stale/never), bytes rx/tx.
|
|
# Resuelve device_id desde comentarios # DeviceID:<id> en wg0.conf.
|
|
#
|
|
# Usage:
|
|
# wg_status [interface_name] # default: wg0
|
|
#
|
|
# Env:
|
|
# WG_FAKE_DUMP=<path> # 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: <private_key>\t<public_key>\t<listen_port>\t<fwmark>
|
|
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:<id>
|
|
# [Peer]
|
|
# PublicKey = <pk>
|
|
# 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: <public_key>\t<preshared_key>\t<endpoint>\t<allowed_ips>\t<latest_handshake>\t<rx_bytes>\t<tx_bytes>\t<persistent_keepalive>
|
|
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
|