Última atividade 1 month ago

Revisão 7624d7eb67cc26dd475febf351c6ca3be5e145fb

gistfile1.txt Bruto
1#!/usr/bin/env bash
2
3function cmd::inspect::on_load() {
4 flag::register --name
5 flag::register --type
6 flag::register --config
7 flag::register --qr
8}
9
10function cmd::inspect::help() {
11 cat <<EOF
12Usage: wgctl inspect --name <name> [options]
13 wgctl inspect <full-name>
14
15Show detailed information for a WireGuard client.
16
17Sections shown:
18 Client — IP, type, rule, status, activity
19 Groups — group memberships
20 Rule — firewall rule with inheritance tree and service annotations
21 Peer Blocks — peer-specific restrictions (beyond the assigned rule)
22 Firewall — active iptables rules with ACCEPT/DROP counts
23
24Options:
25 --name <name> Client name (e.g. phone-nuno)
26 --type <type> Device type — combines with --name
27 --config Also show raw WireGuard client config
28 --qr Also show QR code
29
30Examples:
31 wgctl inspect --name phone-nuno
32 wgctl inspect --name nuno --type phone
33 wgctl inspect --name phone-nuno --config
34 wgctl inspect --name phone-nuno --qr
35 wgctl inspect guest-zephyr
36EOF
37}
38
39INSPECT_WIDTH=48 # total visible width of section lines
40INSPECT_LABEL_WIDTH=20
41
42# ============================================
43# Private helpers
44# ============================================
45
46
47function cmd::inspect::_section() {
48 local title="${1:-}" extra="${2:-0}"
49 local width=$(( INSPECT_WIDTH + extra ))
50 local title_len=${#title}
51 # Account for "── " (3) + " " (1) before dashes
52 local dash_count=$(( width - title_len - 4 ))
53 [[ $dash_count -lt 2 ]] && dash_count=2
54 local dashes
55 dashes=$(printf '─%.0s' $(seq 1 $dash_count))
56 printf "\n \033[0;37m── %s %s\033[0m\n" "$title" "$dashes"
57}
58
59function cmd::inspect::_peer_info() {
60 local name="${1:-}"
61
62 local ip type rule public_key allowed_ips
63 ip=$(peers::get_ip "$name")
64 type=$(peers::get_type "$name")
65 rule=$(peers::get_meta "$name" "rule")
66 public_key=$(keys::public "$name" 2>/dev/null || echo "")
67 allowed_ips=$(grep "^AllowedIPs" "$(ctx::clients)/${name}.conf" \
68 2>/dev/null | cut -d'=' -f2- | xargs)
69
70 # Status
71 local handshake_ts is_blocked last_ts
72 handshake_ts=$(monitor::get_handshake_ts "$public_key")
73 peers::is_blocked "$name" && is_blocked="true" || is_blocked="false"
74 last_ts=$(monitor::last_attempt "$name")
75
76 local is_restricted="false"
77 block::has_specific_rules "$name" 2>/dev/null && is_restricted="true"
78
79 local status last_seen endpoint
80 status=$(peers::format_status_verbose "$name" "$public_key" \
81 "$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts")
82 last_seen=$(peers::format_last_seen "$name" "$public_key" \
83 "$is_blocked" "$last_ts" "" "$handshake_ts")
84 endpoint=$(monitor::get_cached_endpoint "$name")
85
86 local activity_total
87 activity_total=$(peers::format_activity_total "$public_key")
88
89 local activity_current
90 activity_current=$(peers::format_activity_current "$public_key")
91
92 local rule_file=""
93 local rule_extends=""
94 if [[ -n "$rule" ]]; then
95 rule_file="$(rule::path "$rule" 2>/dev/null)" || true
96 if [[ -n "$rule_file" ]]; then
97 local ext=()
98 mapfile -t ext < <(json::get "$rule_file" "extends" 2>/dev/null || true)
99 if [[ ${#ext[@]} -gt 0 && -n "${ext[0]:-}" ]]; then
100 rule_extends=" (↳ ${ext[*]})"
101 fi
102 fi
103 fi
104
105 # Rule formatting
106 local rule_display="${rule:-—}"
107 if [[ -n "$rule_file" && ${#ext[@]} -gt 0 && -n "${ext[0]:-}" ]]; then
108 local extends_str
109 extends_str=$(printf '%s, ' "${ext[@]}" | sed 's/, $//')
110 rule_display="${rule} ↳ (${extends_str})"
111 fi
112
113 cmd::inspect::_section "Client"
114 printf "\n"
115 ui::row "Name" "$name" "${INSPECT_LABEL_WIDTH}"
116 ui::row "IP" "$ip" "${INSPECT_LABEL_WIDTH}"
117 ui::row "Type" "$(peers::display_type "$type")" "${INSPECT_LABEL_WIDTH}"
118 ui::row "Rule" "$rule_display" "${INSPECT_LABEL_WIDTH}"
119 ui::row "Status" "$(echo -e "$status")" "${INSPECT_LABEL_WIDTH}"
120 ui::row "Endpoint" "${endpoint:-—}" "${INSPECT_LABEL_WIDTH}"
121 ui::row "Last seen" "$last_seen" "${INSPECT_LABEL_WIDTH}"
122 ui::row "AllowedIPs" "$allowed_ips" "${INSPECT_LABEL_WIDTH}"
123 ui::row "Public key" "${public_key:-—}" "${INSPECT_LABEL_WIDTH}"
124 ui::row "Activity (total)" "$activity_total" "${INSPECT_LABEL_WIDTH}"
125 ui::row "Activity (current)" "$activity_current" "${INSPECT_LABEL_WIDTH}"
126
127 return 0
128}
129
130# function cmd::inspect::_rule_info() {
131# local name="${1:-}"
132# local rule
133# rule=$(peers::get_meta "$name" "rule")
134# [[ -z "$rule" ]] && return 0
135# rule::exists "$rule" || return 0
136
137# cmd::inspect::_section "Rule: ${rule}"
138
139# if ui::rule::tree "$rule"; then
140# # printf "\n"
141# : # no-op
142# else
143# # No inheritance — flat view
144# rule::render_flat "$rule"
145# fi
146# return 0
147# }
148
149function cmd::inspect::_rule_info() {
150 local name="${1:-}"
151 local rule
152 rule=$(peers::get_meta "$name" "rule")
153
154 # Get identity name and its rules
155 local identity_name identity_rules strict
156 identity_name=$(identity::get_name "$name")
157 if [[ -n "$identity_name" ]]; then
158 identity_rules=$(identity::rules "$identity_name")
159 strict=$(identity::rule_flags "$identity_name" "strict_rule")
160 fi
161
162 # Build section header
163 local header="Rules"
164 [[ -n "$rule" ]] && header="${header}: ${rule}"
165 [[ -n "$identity_name" && -n "$identity_rules" ]] && \
166 header="${header} · identity:${identity_name}"
167
168 # Skip section entirely if nothing to show
169 [[ -z "$rule" && -z "$identity_rules" ]] && return 0
170
171 cmd::inspect::_section "$header"
172
173 # Identity block first (applied first, base access level)
174 if [[ -n "$identity_name" && -n "$identity_rules" ]]; then
175 ui::rule::identity_block "$identity_name" "$strict"
176 fi
177
178 # Peer rule tree (if set and not suppressed by strict)
179 if [[ -n "$rule" && "$strict" != "true" ]]; then
180 rule::exists "$rule" || return 0
181 if rule::render_extends_tree "$rule"; then
182 printf "\n"
183 else
184 rule::render_flat "$rule"
185 fi
186 elif [[ -n "$rule" && "$strict" == "true" ]]; then
187 printf "\n \033[2mpeer rule '%s' suppressed by strict policy\033[0m\n" "$rule"
188 fi
189
190 return 0
191}
192
193function cmd::inspect::_blocks_info() {
194 local name="${1:-}"
195 block::has_file "$name" || return 0
196
197 local blocked_direct
198 blocked_direct=$(block::is_blocked_direct "$name")
199
200 local blocked_groups
201 blocked_groups=$(block::get_groups "$name")
202
203 local rules_output
204 rules_output=$(block::get_rules "$name")
205
206 # Skip if truly empty
207 if [[ "$blocked_direct" != "true" ]] && \
208 ui::empty "$blocked_groups" && \
209 ui::empty "$rules_output"; then
210 block::cleanup "$name" # clean up stale empty file
211 return 0
212 fi
213
214 # Count rules for header
215 local rule_count=0
216 while IFS= read -r line; do
217 [[ -n "$line" ]] && (( rule_count++ )) || true
218 done <<< "$rules_output"
219
220 # Build header like firewall: Blocks (+N)
221 local header_counts=""
222 [[ "$rule_count" -gt 0 ]] && header_counts=" (${rule_count})"
223 [[ "$blocked_direct" == "true" || -n "$blocked_groups" ]] && \
224 header_counts="${header_counts} 🚫"
225
226 cmd::inspect::_section "Blocks${header_counts}"
227 printf "\n"
228
229 [[ "$blocked_direct" == "true" ]] && \
230 printf " \033[1;31m🚫\033[0m blocked directly\n"
231 [[ -n "$blocked_groups" ]] && \
232 printf " \033[1;31m🚫\033[0m blocked by groups: %s\n" "$blocked_groups"
233
234 block::format_rules "$name"
235 return 0
236}
237
238function cmd::inspect::_group_info() {
239 local name="$1"
240
241 local groups=()
242 mapfile -t groups < <(json::peer_groups "$(ctx::groups)" "$name")
243
244 ui::empty "${groups[*]}" && return 0
245
246 local count=${#groups[@]}
247 cmd::inspect::_section "Groups (${count})"
248 printf "\n"
249
250 for g in "${groups[@]}"; do
251 [[ -z "$g" ]] && continue
252 local peer_count
253 local main_marker=""
254 peer_count=$(json::count "$(group::path "$g")" "peers")
255 [[ "$g" == "$(peers::get_main_group "$name")" ]] && \
256 main_marker=" \033[0;33m★\033[0m"
257 printf " \033[0;37m·\033[0m %-20s \033[0;37m%s peers\033[0m%b\n" \
258 "$g" "$peer_count" "$main_marker"
259 done
260
261 return 0
262}
263
264function cmd::inspect::_firewall_info() {
265 local name="${1:-}"
266 local ip
267 ip=$(peers::get_ip "$name")
268
269 local total=0 accepts=0 drops=0
270 local rules_output=()
271 while IFS= read -r line; do
272 [[ -z "$line" ]] && continue
273 (( total++ )) || true
274 [[ "$line" =~ ACCEPT ]] && (( accepts++ )) || true
275 [[ "$line" =~ DROP ]] && (( drops++ )) || true
276 rules_output+=("$line")
277 done < <(fw::forward_rules_for_ip "$ip" | grep -v NFLOG)
278
279 ui::empty "${rules_output[*]}" && return 0
280
281 printf "\n \033[0;37m── Firewall (%s %s) \033[0m%s\n\n" \
282 "$(color::green "+${accepts}")" \
283 "$(color::red "-${drops}")" \
284 "$(printf '\033[0;37m─%.0s' {1..28})"
285
286 fw::list_peer_rules "$ip" false
287
288 return 0
289}
290
291function cmd::inspect::_config() {
292 local name="$1"
293 cmd::inspect::_section "Config"
294 printf "\n"
295 cat "$(ctx::clients)/${name}.conf"
296 printf "\n"
297
298 return 0
299}
300
301# ============================================
302# Run
303# ============================================
304
305function cmd::inspect::run() {
306 local name="" type="" show_config=false show_qr=false
307
308 if [[ $# -gt 0 && "$1" != "--"* ]]; then
309 name="$1"
310 shift
311 fi
312
313 while [[ $# -gt 0 ]]; do
314 case "$1" in
315 --name) name="$2"; shift 2 ;;
316 --type) type="$2"; shift 2 ;;
317 --config) show_config=true; shift ;;
318 --qr) show_qr=true; shift ;;
319 --help) cmd::inspect::help; return ;;
320 *)
321 log::error "Unknown flag: $1"
322 cmd::inspect::help
323 return 1
324 ;;
325 esac
326 done
327
328 if [[ -z "$name" ]]; then
329 log::error "Missing required flag: --name"
330 cmd::inspect::help
331 return 1
332 fi
333
334 name=$(peers::resolve_and_require "$name" "$type") || return 1
335
336 load_command list
337
338 log::section "Inspect: ${name}"
339
340 cmd::inspect::_peer_info "$name"
341 cmd::inspect::_group_info "$name"
342 cmd::inspect::_rule_info "$name"
343 cmd::inspect::_blocks_info "$name"
344 cmd::inspect::_firewall_info "$name"
345
346 if $show_config; then
347 cmd::inspect::_config "$name"
348 fi
349
350 if $show_qr; then
351 cmd::inspect::_section "QR Code"
352 printf "\n"
353 load_command qr
354 cmd::qr::run --name "$name"
355 fi
356
357 printf "\n"
358}