gistfile1.txt
· 7.7 KiB · Text
Sin formato
#!/usr/bin/env bash
# activity.command.sh — WireGuard activity snapshot
# ============================================
# Lifecycle
# ============================================
function cmd::activity::on_load() {
load_module net
flag::register --peer
flag::register --service
flag::register --ip
flag::register --hours
flag::register --type
flag::register --dropped
command::mixin json_output
}
# ============================================
# Help
# ============================================
function cmd::activity::help() {
cat <<EOF
Usage: wgctl activity [options]
Show WireGuard activity — transfer totals and firewall drops per peer.
Data sources: wg show transfer, fw_events.log
Options:
--peer <name> Filter by peer name
--service <name> Filter by service (e.g. truenas, proxmox:web-ui)
--ip <ip> Filter by destination IP
--hours <n> Time window in hours (default: 24, 0 = all time)
--type <type> Filter by device type (combined with --peer)
--dropped Show only peers with at least one drop
Examples:
wgctl activity
wgctl activity --dropped
wgctl activity --peer phone-nuno
wgctl activity --service truenas
wgctl activity --hours 0
wgctl activity --ip 10.0.0.101
EOF
}
# ============================================
# Run
# ============================================
function cmd::activity::run() {
local filter_peer="" filter_service="" filter_ip="" filter_type=""
local hours=24 dropped_only=false
while [[ $# -gt 0 ]]; do
case "$1" in
--peer) filter_peer="$2"; shift 2 ;;
--service) filter_service="$2"; shift 2 ;;
--ip) filter_ip="$2"; shift 2 ;;
--type) filter_type="$2"; shift 2 ;;
--hours) hours="$2"; shift 2 ;;
--dropped) dropped_only=true; shift ;;
--help) cmd::activity::help; return ;;
*)
log::error "Unknown flag: $1"
cmd::activity::help
return 1
;;
esac
done
if command::json; then
cmd::activity::_output_json "$hours"
return 0
fi
# Resolve peer name if type provided
if [[ -n "$filter_peer" && -n "$filter_type" ]]; then
filter_peer=$(peers::resolve_and_require "$filter_peer" "$filter_type") || return 1
fi
# Resolve --service to IP
local service_ip=""
if [[ -n "$filter_service" ]]; then
service_ip=$(net::resolve "$filter_service" 2>/dev/null | head -1 | cut -d: -f1) || true
if [[ -z "$service_ip" ]]; then
log::error "Service not found: ${filter_service}"
return 1
fi
fi
[[ -n "$filter_ip" ]] && service_ip="$filter_ip"
# Fetch aggregated data
local data
data=$(json::activity_aggregate \
"$(ctx::fw_events_log)" \
"$(ctx::events_log)" \
"$(config::interface)" \
"$(ctx::net)" \
"$(ctx::clients)" \
"$(ctx::meta)" \
"$hours" \
"$filter_peer" \
"$service_ip" 2>/dev/null)
if [[ -z "$data" ]]; then
log::wg_warning "No activity data found"
return 0
fi
# Measure column widths
local w_peer=16 w_drops=1
while IFS='|' read -r type rest; do
case "$type" in
peer)
local name drops
name=$(echo "$rest" | cut -d'|' -f1)
drops=$(echo "$rest" | cut -d'|' -f4)
(( ${#name} > w_peer )) && w_peer=${#name}
(( ${#drops} > w_drops )) && w_drops=${#drops}
;;
service)
local count
count=$(echo "$rest" | cut -d'|' -f3)
(( ${#count} > w_drops )) && w_drops=${#count}
;;
esac
done <<< "$data"
(( w_peer += 2 ))
# Compute column where drop count starts on peer row:
# " " (2) + name (w_peer) + " ↓" (3) + rx (10) + " ↑" (3) + tx (10) + " " (2)
# ↓ and ↑ are multi-byte (3 bytes, 1 visible) — 2 extra bytes each
# Visible: 2 + w_peer + 2+1 + 10 + 2+1 + 10 + 2 = w_peer + 30
local drops_col=$(( w_peer + 30 ))
local hours_display="${hours}h"
[[ "$hours" == "0" ]] && hours_display="all time"
log::section "Activity Monitor (last ${hours_display})"
echo ""
if display::is_table "activity"; then
cmd::activity::_render_table "$data"
return 0
fi
local first_peer=true skip_peer=false
while IFS='|' read -r record_type rest; do
case "$record_type" in
peer)
local name rx tx drops
IFS='|' read -r name rx tx drops <<< "$rest"
skip_peer=false
if $dropped_only && [[ "$drops" -eq 0 ]]; then
skip_peer=true
continue
fi
$first_peer || echo ""
first_peer=false
local rx_fmt tx_fmt
rx_fmt=$(fmt::bytes "$rx")
tx_fmt=$(fmt::bytes "$tx")
local name_pad rx_pad tx_pad
name_pad=$(printf "%-${w_peer}s" "$name")
rx_pad=$(printf "%-10s" "$rx_fmt")
tx_pad=$(printf "%-10s" "$tx_fmt")
local drop_word="drops"
[[ "$drops" -eq 1 ]] && drop_word="drop"
ui::activity::peer_row \
"$name_pad" "$rx_pad" "$tx_pad" "$drops" "$drop_word" "$w_drops"
;;
service)
$skip_peer && continue
local peer dest_display drop_count
IFS='|' read -r peer dest_display drop_count <<< "$rest"
local svc_drop_word="drops"
[[ "$drop_count" -eq 1 ]] && svc_drop_word="drop"
ui::activity::service_row \
"$dest_display" "$drop_count" "$svc_drop_word" "$drops_col" "$w_drops"
;;
esac
done <<< "$data"
echo ""
}
function cmd::activity::_render_table() {
local data="${1:-}"
[[ -z "$data" ]] && return 0
ui::activity::header_table
local skip_peer=false
while IFS='|' read -r record_type rest; do
case "$record_type" in
peer)
local name rx tx drops
IFS='|' read -r name rx tx drops <<< "$rest"
skip_peer=false
local rx_fmt tx_fmt
rx_fmt=$(fmt::bytes "$rx")
tx_fmt=$(fmt::bytes "$tx")
ui::activity::peer_row_table "$name" "$rx_fmt" "$tx_fmt" "$drops" ""
;;
service)
$skip_peer && continue
local peer dest count
IFS='|' read -r peer dest count <<< "$rest"
ui::activity::service_row_table "$dest" "$count" "drops"
;;
esac
done <<< "$data"
}
function cmd::activity::_output_json() {
local hours="${1:-24}"
local data
data=$(json::activity_aggregate \
"$(ctx::fw_events_log)" "$(ctx::events_log)" \
"$(config::interface)" "$(ctx::net)" \
"$(ctx::clients)" "$(ctx::meta)" \
"$hours" "" "" 2>/dev/null)
local -a peers=()
local current_peer="" current_services=""
local -a current_svc_list=()
while IFS='|' read -r record_type rest; do
case "$record_type" in
peer)
# Flush previous peer
if [[ -n "$current_peer" ]]; then
local svc_array
svc_array=$(printf '%s\n' "${current_svc_list[@]:-}" | paste -sd ',' -)
peers+=("${current_peer},\"services\":[${svc_array:-}]}")
current_svc_list=()
fi
local name rx tx drops
IFS='|' read -r name rx tx drops <<< "$rest"
current_peer=$(printf '{"name":"%s","rx":%s,"tx":%s,"drops":%s' \
"$name" "$rx" "$tx" "$drops")
;;
service)
local peer dest count
IFS='|' read -r peer dest count <<< "$rest"
current_svc_list+=("$(printf '{"dest":"%s","drops":%s}' "$dest" "$count")")
;;
esac
done <<< "$data"
# Flush last peer
if [[ -n "$current_peer" ]]; then
local svc_array
svc_array=$(printf '%s\n' "${current_svc_list[@]:-}" | paste -sd ',' -)
peers+=("${current_peer},\"services\":[${svc_array:-}]}")
fi
local count=${#peers[@]}
local array
array=$(printf '%s\n' "${peers[@]:-}" | paste -sd ',' -)
printf '{"peers":[%s]}' "${array:-}" | json::envelope "activity" "$count"
}
| 1 | #!/usr/bin/env bash |
| 2 | # activity.command.sh — WireGuard activity snapshot |
| 3 | |
| 4 | # ============================================ |
| 5 | # Lifecycle |
| 6 | # ============================================ |
| 7 | |
| 8 | function cmd::activity::on_load() { |
| 9 | load_module net |
| 10 | |
| 11 | flag::register --peer |
| 12 | flag::register --service |
| 13 | flag::register --ip |
| 14 | flag::register --hours |
| 15 | flag::register --type |
| 16 | flag::register --dropped |
| 17 | |
| 18 | command::mixin json_output |
| 19 | } |
| 20 | |
| 21 | # ============================================ |
| 22 | # Help |
| 23 | # ============================================ |
| 24 | |
| 25 | function cmd::activity::help() { |
| 26 | cat <<EOF |
| 27 | Usage: wgctl activity [options] |
| 28 | |
| 29 | Show WireGuard activity — transfer totals and firewall drops per peer. |
| 30 | Data sources: wg show transfer, fw_events.log |
| 31 | |
| 32 | Options: |
| 33 | --peer <name> Filter by peer name |
| 34 | --service <name> Filter by service (e.g. truenas, proxmox:web-ui) |
| 35 | --ip <ip> Filter by destination IP |
| 36 | --hours <n> Time window in hours (default: 24, 0 = all time) |
| 37 | --type <type> Filter by device type (combined with --peer) |
| 38 | --dropped Show only peers with at least one drop |
| 39 | |
| 40 | Examples: |
| 41 | wgctl activity |
| 42 | wgctl activity --dropped |
| 43 | wgctl activity --peer phone-nuno |
| 44 | wgctl activity --service truenas |
| 45 | wgctl activity --hours 0 |
| 46 | wgctl activity --ip 10.0.0.101 |
| 47 | EOF |
| 48 | } |
| 49 | |
| 50 | # ============================================ |
| 51 | # Run |
| 52 | # ============================================ |
| 53 | |
| 54 | function cmd::activity::run() { |
| 55 | local filter_peer="" filter_service="" filter_ip="" filter_type="" |
| 56 | local hours=24 dropped_only=false |
| 57 | |
| 58 | while [[ $# -gt 0 ]]; do |
| 59 | case "$1" in |
| 60 | --peer) filter_peer="$2"; shift 2 ;; |
| 61 | --service) filter_service="$2"; shift 2 ;; |
| 62 | --ip) filter_ip="$2"; shift 2 ;; |
| 63 | --type) filter_type="$2"; shift 2 ;; |
| 64 | --hours) hours="$2"; shift 2 ;; |
| 65 | --dropped) dropped_only=true; shift ;; |
| 66 | --help) cmd::activity::help; return ;; |
| 67 | *) |
| 68 | log::error "Unknown flag: $1" |
| 69 | cmd::activity::help |
| 70 | return 1 |
| 71 | ;; |
| 72 | esac |
| 73 | done |
| 74 | |
| 75 | if command::json; then |
| 76 | cmd::activity::_output_json "$hours" |
| 77 | return 0 |
| 78 | fi |
| 79 | |
| 80 | # Resolve peer name if type provided |
| 81 | if [[ -n "$filter_peer" && -n "$filter_type" ]]; then |
| 82 | filter_peer=$(peers::resolve_and_require "$filter_peer" "$filter_type") || return 1 |
| 83 | fi |
| 84 | |
| 85 | # Resolve --service to IP |
| 86 | local service_ip="" |
| 87 | if [[ -n "$filter_service" ]]; then |
| 88 | service_ip=$(net::resolve "$filter_service" 2>/dev/null | head -1 | cut -d: -f1) || true |
| 89 | if [[ -z "$service_ip" ]]; then |
| 90 | log::error "Service not found: ${filter_service}" |
| 91 | return 1 |
| 92 | fi |
| 93 | fi |
| 94 | [[ -n "$filter_ip" ]] && service_ip="$filter_ip" |
| 95 | |
| 96 | # Fetch aggregated data |
| 97 | local data |
| 98 | data=$(json::activity_aggregate \ |
| 99 | "$(ctx::fw_events_log)" \ |
| 100 | "$(ctx::events_log)" \ |
| 101 | "$(config::interface)" \ |
| 102 | "$(ctx::net)" \ |
| 103 | "$(ctx::clients)" \ |
| 104 | "$(ctx::meta)" \ |
| 105 | "$hours" \ |
| 106 | "$filter_peer" \ |
| 107 | "$service_ip" 2>/dev/null) |
| 108 | |
| 109 | if [[ -z "$data" ]]; then |
| 110 | log::wg_warning "No activity data found" |
| 111 | return 0 |
| 112 | fi |
| 113 | |
| 114 | # Measure column widths |
| 115 | local w_peer=16 w_drops=1 |
| 116 | while IFS='|' read -r type rest; do |
| 117 | case "$type" in |
| 118 | peer) |
| 119 | local name drops |
| 120 | name=$(echo "$rest" | cut -d'|' -f1) |
| 121 | drops=$(echo "$rest" | cut -d'|' -f4) |
| 122 | (( ${#name} > w_peer )) && w_peer=${#name} |
| 123 | (( ${#drops} > w_drops )) && w_drops=${#drops} |
| 124 | ;; |
| 125 | service) |
| 126 | local count |
| 127 | count=$(echo "$rest" | cut -d'|' -f3) |
| 128 | (( ${#count} > w_drops )) && w_drops=${#count} |
| 129 | ;; |
| 130 | esac |
| 131 | done <<< "$data" |
| 132 | |
| 133 | (( w_peer += 2 )) |
| 134 | |
| 135 | # Compute column where drop count starts on peer row: |
| 136 | # " " (2) + name (w_peer) + " ↓" (3) + rx (10) + " ↑" (3) + tx (10) + " " (2) |
| 137 | # ↓ and ↑ are multi-byte (3 bytes, 1 visible) — 2 extra bytes each |
| 138 | # Visible: 2 + w_peer + 2+1 + 10 + 2+1 + 10 + 2 = w_peer + 30 |
| 139 | local drops_col=$(( w_peer + 30 )) |
| 140 | |
| 141 | local hours_display="${hours}h" |
| 142 | [[ "$hours" == "0" ]] && hours_display="all time" |
| 143 | |
| 144 | log::section "Activity Monitor (last ${hours_display})" |
| 145 | echo "" |
| 146 | |
| 147 | if display::is_table "activity"; then |
| 148 | cmd::activity::_render_table "$data" |
| 149 | return 0 |
| 150 | fi |
| 151 | |
| 152 | local first_peer=true skip_peer=false |
| 153 | |
| 154 | while IFS='|' read -r record_type rest; do |
| 155 | case "$record_type" in |
| 156 | peer) |
| 157 | local name rx tx drops |
| 158 | IFS='|' read -r name rx tx drops <<< "$rest" |
| 159 | |
| 160 | skip_peer=false |
| 161 | if $dropped_only && [[ "$drops" -eq 0 ]]; then |
| 162 | skip_peer=true |
| 163 | continue |
| 164 | fi |
| 165 | |
| 166 | $first_peer || echo "" |
| 167 | first_peer=false |
| 168 | |
| 169 | local rx_fmt tx_fmt |
| 170 | rx_fmt=$(fmt::bytes "$rx") |
| 171 | tx_fmt=$(fmt::bytes "$tx") |
| 172 | |
| 173 | local name_pad rx_pad tx_pad |
| 174 | name_pad=$(printf "%-${w_peer}s" "$name") |
| 175 | rx_pad=$(printf "%-10s" "$rx_fmt") |
| 176 | tx_pad=$(printf "%-10s" "$tx_fmt") |
| 177 | |
| 178 | local drop_word="drops" |
| 179 | [[ "$drops" -eq 1 ]] && drop_word="drop" |
| 180 | |
| 181 | ui::activity::peer_row \ |
| 182 | "$name_pad" "$rx_pad" "$tx_pad" "$drops" "$drop_word" "$w_drops" |
| 183 | ;; |
| 184 | |
| 185 | service) |
| 186 | $skip_peer && continue |
| 187 | |
| 188 | local peer dest_display drop_count |
| 189 | IFS='|' read -r peer dest_display drop_count <<< "$rest" |
| 190 | |
| 191 | local svc_drop_word="drops" |
| 192 | [[ "$drop_count" -eq 1 ]] && svc_drop_word="drop" |
| 193 | |
| 194 | ui::activity::service_row \ |
| 195 | "$dest_display" "$drop_count" "$svc_drop_word" "$drops_col" "$w_drops" |
| 196 | ;; |
| 197 | esac |
| 198 | done <<< "$data" |
| 199 | |
| 200 | echo "" |
| 201 | } |
| 202 | |
| 203 | function cmd::activity::_render_table() { |
| 204 | local data="${1:-}" |
| 205 | [[ -z "$data" ]] && return 0 |
| 206 | |
| 207 | ui::activity::header_table |
| 208 | local skip_peer=false |
| 209 | while IFS='|' read -r record_type rest; do |
| 210 | case "$record_type" in |
| 211 | peer) |
| 212 | local name rx tx drops |
| 213 | IFS='|' read -r name rx tx drops <<< "$rest" |
| 214 | skip_peer=false |
| 215 | local rx_fmt tx_fmt |
| 216 | rx_fmt=$(fmt::bytes "$rx") |
| 217 | tx_fmt=$(fmt::bytes "$tx") |
| 218 | ui::activity::peer_row_table "$name" "$rx_fmt" "$tx_fmt" "$drops" "" |
| 219 | ;; |
| 220 | service) |
| 221 | $skip_peer && continue |
| 222 | local peer dest count |
| 223 | IFS='|' read -r peer dest count <<< "$rest" |
| 224 | ui::activity::service_row_table "$dest" "$count" "drops" |
| 225 | ;; |
| 226 | esac |
| 227 | done <<< "$data" |
| 228 | } |
| 229 | |
| 230 | |
| 231 | function cmd::activity::_output_json() { |
| 232 | local hours="${1:-24}" |
| 233 | local data |
| 234 | data=$(json::activity_aggregate \ |
| 235 | "$(ctx::fw_events_log)" "$(ctx::events_log)" \ |
| 236 | "$(config::interface)" "$(ctx::net)" \ |
| 237 | "$(ctx::clients)" "$(ctx::meta)" \ |
| 238 | "$hours" "" "" 2>/dev/null) |
| 239 | |
| 240 | local -a peers=() |
| 241 | local current_peer="" current_services="" |
| 242 | local -a current_svc_list=() |
| 243 | |
| 244 | while IFS='|' read -r record_type rest; do |
| 245 | case "$record_type" in |
| 246 | peer) |
| 247 | # Flush previous peer |
| 248 | if [[ -n "$current_peer" ]]; then |
| 249 | local svc_array |
| 250 | svc_array=$(printf '%s\n' "${current_svc_list[@]:-}" | paste -sd ',' -) |
| 251 | peers+=("${current_peer},\"services\":[${svc_array:-}]}") |
| 252 | current_svc_list=() |
| 253 | fi |
| 254 | local name rx tx drops |
| 255 | IFS='|' read -r name rx tx drops <<< "$rest" |
| 256 | current_peer=$(printf '{"name":"%s","rx":%s,"tx":%s,"drops":%s' \ |
| 257 | "$name" "$rx" "$tx" "$drops") |
| 258 | ;; |
| 259 | service) |
| 260 | local peer dest count |
| 261 | IFS='|' read -r peer dest count <<< "$rest" |
| 262 | current_svc_list+=("$(printf '{"dest":"%s","drops":%s}' "$dest" "$count")") |
| 263 | ;; |
| 264 | esac |
| 265 | done <<< "$data" |
| 266 | |
| 267 | # Flush last peer |
| 268 | if [[ -n "$current_peer" ]]; then |
| 269 | local svc_array |
| 270 | svc_array=$(printf '%s\n' "${current_svc_list[@]:-}" | paste -sd ',' -) |
| 271 | peers+=("${current_peer},\"services\":[${svc_array:-}]}") |
| 272 | fi |
| 273 | |
| 274 | local count=${#peers[@]} |
| 275 | local array |
| 276 | array=$(printf '%s\n' "${peers[@]:-}" | paste -sd ',' -) |
| 277 | printf '{"peers":[%s]}' "${array:-}" | json::envelope "activity" "$count" |
| 278 | } |
| 279 |