Последняя активность 1 month ago

nuno ревизий этого фрагмента 1 month ago. К ревизии

1 file changed, 276 insertions

gistfile1.txt(файл создан)

@@ -0,0 +1,276 @@
1 + #!/usr/bin/env bash
2 +
3 + # ============================================
4 + # Lifecycle
5 + # ============================================
6 +
7 + function cmd::watch::on_load() {
8 + flag::register --type
9 + flag::register --name
10 + flag::register --blocked
11 + flag::register --restricted
12 + flag::register --allowed
13 + }
14 +
15 + # ============================================
16 + # Help
17 + # ============================================
18 +
19 + function cmd::watch::help() {
20 + cat <<EOF
21 + Usage: wgctl watch [options]
22 +
23 + Live monitor of WireGuard activity.
24 + Shows handshakes from active clients and connection attempts from blocked clients.
25 +
26 + Options:
27 + --type <type> Filter by device type
28 + --name <name> Filter by client name
29 + --allowed Show only allowed client handshakes
30 + --restricted Show only restricted client events
31 + --blocked Show only blocked client attempts
32 +
33 + Examples:
34 + wgctl watch
35 + wgctl watch --blocked
36 + wgctl watch --allowed
37 + wgctl watch --type phone
38 + wgctl watch --name phone-nuno
39 + EOF
40 + }
41 +
42 + # ============================================
43 + # Helpers
44 + # ============================================
45 +
46 + function cmd::watch::format_event() {
47 + local ts="$1"
48 + local client="$2"
49 + local endpoint="$3"
50 + local event="$4"
51 + local status="$5"
52 +
53 + local event_color
54 + case "$event" in
55 + attempt) event_color="\033[1;31m" ;; # red
56 + handshake) event_color="\033[1;32m" ;; # green
57 + *) event_color="\033[0;37m" ;; # grey
58 + esac
59 +
60 + local status_str=""
61 + if [[ -n "$status" ]]; then
62 + case "$status" in
63 + blocked) status_str=" \033[1;31mblocked\033[0m" ;;
64 + allowed) status_str=" \033[1;32mallowed\033[0m" ;;
65 + *) status_str="" ;;
66 + esac
67 + fi
68 +
69 + printf " %-20s %-25s %-20s ${event_color}%-12s\033[0m%b\n" \
70 + "$ts" "$client" "${endpoint:-—}" "$event" "$status_str"
71 + }
72 +
73 + function cmd::watch::header() {
74 + log::section "wgctl — Live Monitor (Ctrl+C to stop)"
75 + printf "\n %-20s %-25s %-20s %-12s %s\n" \
76 + "TIME" "CLIENT" "ENDPOINT" "EVENT" "STATUS"
77 + printf " %s\n\n" "$(printf '─%.0s' {1..85})"
78 + }
79 +
80 + # ============================================
81 + # Handshake Poller
82 + # ============================================
83 +
84 + declare -A _WATCH_LAST_HANDSHAKES=()
85 +
86 + function cmd::watch::poll_handshakes() {
87 + local filter_name="$1"
88 + local filter_type="$2"
89 + local allowed_only="$3"
90 +
91 + while IFS= read -r line; do
92 + local public_key ts
93 + public_key=$(echo "$line" | awk '{print $1}')
94 + ts=$(echo "$line" | awk '{print $2}')
95 +
96 + [[ -z "$ts" || "$ts" == "0" ]] && continue
97 +
98 + # Find client by public key
99 + local client_name=""
100 + for conf in "$(ctx::clients)"/*.conf; do
101 + [[ -f "$conf" ]] || continue
102 + local name
103 + name=$(basename "$conf" .conf)
104 + local key
105 + key=$(keys::public "$name" 2>/dev/null || echo "")
106 + if [[ "$key" == "$public_key" ]]; then
107 + client_name="$name"
108 + break
109 + fi
110 + done
111 +
112 + [[ -z "$client_name" ]] && continue
113 +
114 + # Apply filters
115 + [[ -n "$filter_name" && "$client_name" != "$filter_name" ]] && continue
116 +
117 + if [[ -n "$filter_type" ]]; then
118 + local ip
119 + ip=$(grep "^Address" "$(ctx::clients)/${client_name}.conf" | awk '{print $3}' | cut -d'/' -f1)
120 + local subnet
121 + subnet=$(config::subnet_for "$filter_type")
122 + string::starts_with "$ip" "$subnet" || continue
123 + fi
124 +
125 + # Only emit if handshake is new
126 + local safe_key
127 + safe_key=$(echo "$public_key" | md5sum | cut -d' ' -f1)
128 + local prev_ts_file="/tmp/wgctl_hs_${safe_key}"
129 + local prev_ts="0"
130 + [[ -f "$prev_ts_file" ]] && prev_ts=$(cat "$prev_ts_file")
131 + if [[ "$ts" != "$prev_ts" ]]; then
132 + echo "$ts" > "$prev_ts_file"
133 +
134 + local formatted_ts
135 + formatted_ts=$(fmt::datetime "$ts")
136 +
137 + local endpoint
138 + endpoint=$(monitor::endpoint_for_key "$public_key")
139 +
140 + cmd::watch::format_event \
141 + "$formatted_ts" "$client_name" "${endpoint:-—}" "handshake" "allowed"
142 + fi
143 +
144 + done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null)
145 + }
146 +
147 + # ============================================
148 + # Event Tailer
149 + # ============================================
150 +
151 + function cmd::watch::tail_events() {
152 + local filter_name="$1"
153 + local filter_type="$2"
154 + local blocked_only="$3"
155 + local restricted_only="$4"
156 + local allowed_only="$5"
157 +
158 + declare -A _WATCH_LAST_ATTEMPT=()
159 +
160 + tail -f "$(ctx::root)/daemon/events.log" 2>/dev/null | while IFS= read -r line; do
161 + [[ -z "$line" ]] && continue
162 +
163 + local event_data
164 + event_data=$(json::parse_event "$line")
165 +
166 + [[ -z "$event_data" ]] && continue
167 +
168 + local ts client endpoint event
169 + IFS="|" read -r ts client endpoint event <<< "$event_data"
170 +
171 + if $restricted_only; then
172 + local conf
173 + conf="$(ctx::clients)/${client}.conf"
174 + [[ -f "$conf" ]] || continue
175 + cmd::list::is_restricted "$client" || continue
176 + fi
177 +
178 + # Apply filters
179 + [[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
180 +
181 + if [[ -n "$filter_type" ]]; then
182 + local conf
183 + conf="$(ctx::clients)/${client}.conf"
184 + [[ -f "$conf" ]] || continue
185 + local ip
186 + ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
187 + local subnet
188 + subnet=$(config::subnet_for "$filter_type")
189 + string::starts_with "$ip" "$subnet" || continue
190 + fi
191 +
192 + # Filter by status
193 + if $allowed_only && [[ "$event" != "handshake" ]]; then
194 + continue
195 + fi
196 +
197 + if $restricted_only; then
198 + local conf
199 + conf="$(ctx::clients)/${client}.conf"
200 + [[ -f "$conf" ]] || continue
201 + cmd::list::is_restricted "$client" || continue
202 + fi
203 +
204 + local formatted_ts
205 + formatted_ts=$(fmt::datetime_iso "$ts")
206 +
207 + # Before printing the event
208 + local now
209 + now=$(date +%s)
210 + local safe_client="${client//[-.]/_}"
211 + local last="${_WATCH_LAST_ATTEMPT[$safe_client]:-0}"
212 + local diff=$(( now - last ))
213 + if (( diff < 30 )); then
214 + continue
215 + fi
216 + _WATCH_LAST_ATTEMPT[$safe_client]="$now"
217 +
218 + cmd::watch::format_event \
219 + "$formatted_ts" "$client" "${endpoint:-—}" "$event" "blocked"
220 + done
221 + }
222 +
223 + # ============================================
224 + # Run
225 + # ============================================
226 +
227 + function cmd::watch::run() {
228 + local filter_name=""
229 + local filter_type=""
230 + local blocked_only=false
231 + local allowed_only=false
232 + local restricted_only=false
233 +
234 + # Clean up any stale temp files from previous runs
235 + rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_* 2>/dev/null || true
236 +
237 + while [[ $# -gt 0 ]]; do
238 + case "$1" in
239 + --name) filter_name="$2"; shift 2 ;;
240 + --type) filter_type="$2"; shift 2 ;;
241 + --blocked) blocked_only=true; shift ;;
242 + --allowed) allowed_only=true; shift ;;
243 + --restricted) restricted_only=true; shift ;;
244 + --help) cmd::watch::help; return ;;
245 + *)
246 + log::error "Unknown flag: $1"
247 + cmd::watch::help
248 + return 1
249 + ;;
250 + esac
251 + done
252 +
253 + cmd::watch::header
254 +
255 + # Start event tailer in background unless --blocked not set
256 + if ! $blocked_only && ! $restricted_only; then
257 + # Poll handshakes every 5 seconds in background
258 + (
259 + while true; do
260 + cmd::watch::poll_handshakes "$filter_name" "$filter_type" "$allowed_only"
261 + sleep 5
262 + done
263 + ) &
264 + local poller_pid=$!
265 + fi
266 +
267 + # Tail events log for blocked attempts
268 + cmd::watch::tail_events "$filter_name" "$filter_type" "$blocked_only" "$restricted_only" "$allowed_only" &
269 + local tailer_pid=$!
270 +
271 + # Trap Ctrl+C to clean up background processes
272 + trap "kill $tailer_pid ${poller_pid:-} 2>/dev/null; rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_*; echo ''; exit 0" INT TERM
273 +
274 + # Keep main process alive
275 + wait
276 + }
Новее Позже