Ostatnio aktywny 1 month ago

gistfile1.txt Surowy
1#!/usr/bin/env bash
2# activity.command.sh — WireGuard activity snapshot
3
4# ============================================
5# Lifecycle
6# ============================================
7
8function 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
25function cmd::activity::help() {
26 cat <<EOF
27Usage: wgctl activity [options]
28
29Show WireGuard activity — transfer totals and firewall drops per peer.
30Data sources: wg show transfer, fw_events.log
31
32Options:
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
40Examples:
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
47EOF
48}
49
50# ============================================
51# Run
52# ============================================
53
54function 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
203function 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
231function 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