最後活躍 1 month ago

nuno 已修改 1 month ago. 還原成這個修訂版本

1 file changed, 560 insertions

gistfile1.txt(檔案已創建)

@@ -0,0 +1,560 @@
1 + #!/usr/bin/env bash
2 +
3 + # ============================================
4 + # Client Config
5 + # ============================================
6 +
7 + function peers::create_client_config() {
8 + local name="$1"
9 + local type="$2"
10 + local ip="$3"
11 + local allowed_ips="${4:-$(config::allowed_ips_for "split")}"
12 +
13 + local conf
14 + conf="$(ctx::clients)/${name}.conf"
15 +
16 + if [[ -f "$conf" ]]; then
17 + log::wg_warning "Client config already exists: ${name}"
18 + return 1
19 + fi
20 +
21 + local private_key
22 + private_key=$(keys::private "$name")
23 +
24 + local server_public_key
25 + server_public_key=$(config::server_public_key)
26 +
27 + cat > "$conf" <<EOF
28 + [Interface]
29 + PrivateKey = ${private_key}
30 + Address = ${ip}/32
31 + DNS = $(config::dns)
32 +
33 + [Peer]
34 + PublicKey = ${server_public_key}
35 + Endpoint = $(config::endpoint)
36 + AllowedIPs = ${allowed_ips}
37 + PersistentKeepalive = 25
38 + EOF
39 +
40 + chmod 600 "$conf"
41 + log::debug "Created client config: ${name} (${ip})"
42 + }
43 +
44 + function peers::remove_client_config() {
45 + local name="$1"
46 + local conf
47 + conf="$(ctx::clients)/${name}.conf"
48 +
49 + if [[ ! -f "$conf" ]]; then
50 + log::wg_warning "Client config not found: ${name}"
51 + return 1
52 + fi
53 +
54 + rm -f "$conf"
55 + log::debug "Removed client config: ${name}"
56 + }
57 +
58 + # ============================================
59 + # Server Config (wg0.conf) Peer Management
60 + # ============================================
61 +
62 + function peers::cleanup_config() {
63 + json::cleanup_config "$(config::config_file)"
64 + }
65 +
66 +
67 + function peers::add_to_server() {
68 + local name="${1:?name required}"
69 + local public_key="${2:?public_key required}"
70 + local ip="${3:?ip required}"
71 +
72 + local config
73 + config=$(config::config_file)
74 +
75 + cat >> "$config" <<EOF
76 +
77 + [Peer]
78 + # ${name}
79 + PublicKey = ${public_key}
80 + AllowedIPs = ${ip}/32
81 + EOF
82 +
83 + log::debug "Added peer to server config: ${name}"
84 + }
85 +
86 + function peers::remove_block() {
87 + local name="${1:?name required}"
88 + json::remove_peer_block "$(config::config_file)" "$name"
89 + }
90 +
91 + function peers::remove_from_server() {
92 + local name="${1:?name required}"
93 + peers::remove_block "$name"
94 + peers::cleanup_config
95 + log::debug "Removed peer from server config: ${name}"
96 + }
97 +
98 + # ============================================
99 + # Listing
100 + # ============================================
101 +
102 + function peers::list() {
103 + local dir
104 + dir="$(ctx::clients)"
105 +
106 + if [[ -z "$(ls -A "$dir"/*.conf 2>/dev/null)" ]]; then
107 + log::wg_list "No clients configured"
108 + return 0
109 + fi
110 +
111 + for conf in "${dir}"/*.conf; do
112 + local client_name
113 + client_name=$(basename "$conf" .conf)
114 +
115 + local ip
116 + ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
117 +
118 + local public_key
119 + public_key=$(keys::public "$client_name" 2>/dev/null || echo "unknown")
120 +
121 + local type
122 + type=$(peers::get_meta "$client_name" "type" 2>/dev/null)
123 + [[ -z "$type" ]] && type=$(peers::get_type_from_ip "$ip")
124 +
125 + printf " %-30s %-15s %-10s %s\n" \
126 + "$client_name" "$ip" "$type" "$public_key"
127 + done
128 + }
129 +
130 + function peers::list_by_type() {
131 + local filter_type="$1"
132 + local dir
133 + dir="$(ctx::clients)"
134 +
135 + for conf in "${dir}"/*.conf; do
136 + local client_name
137 + client_name=$(basename "$conf" .conf)
138 +
139 + local ip
140 + ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
141 +
142 + local type
143 + type=$(peers::get_meta "$client_name" "type" 2>/dev/null)
144 + [[ -z "$type" ]] && type=$(peers::get_type_from_ip "$ip")
145 +
146 + [[ "$type" == "$filter_type" ]] && \
147 + printf " %-30s %-15s\n" "$client_name" "$ip"
148 + done
149 + }
150 +
151 + function peers::exists_in_server() {
152 + local name="$1"
153 + grep -q "^# ${name}$" "$(config::config_file)"
154 + }
155 +
156 + function peers::is_blocked() {
157 + local name="${1:-}"
158 + block::is_blocked "$name"
159 + }
160 +
161 + function peers::is_restricted() {
162 + local name="${1:-}"
163 + block::has_specific_rules "$name" 2>/dev/null
164 + }
165 +
166 + # ============================================
167 + # Default Rule
168 + # ============================================
169 +
170 + function peers::get_type() {
171 + local name="$1"
172 + local ip
173 + ip=$(peers::get_ip "$name")
174 + [[ -z "$ip" ]] && echo "unknown" && return 0
175 + peers::get_type_from_ip "$ip"
176 + }
177 +
178 + function peers::default_rule() {
179 + echo "user"
180 + }
181 +
182 + function peers::effective_rule() {
183 + local name="$1"
184 + local rule
185 + rule=$(peers::get_meta "$name" "rule")
186 + echo "${rule:---}"
187 + }
188 +
189 + # ============================================
190 + # Query
191 + # ============================================
192 +
193 + function peers::all() {
194 + local dir
195 + dir="$(ctx::clients)"
196 + for conf in "${dir}"/*.conf; do
197 + [[ -f "$conf" ]] || continue
198 + basename "$conf" .conf
199 + done
200 + }
201 +
202 + function peers::with_rule() {
203 + local rule="$1"
204 + while IFS= read -r name; do
205 + local effective
206 + effective=$(peers::effective_rule "$name")
207 + [[ "$effective" == "$rule" ]] && echo "$name"
208 + done < <(peers::all)
209 + }
210 +
211 + function peers::get_ip() {
212 + local name="$1"
213 + grep "^Address" "$(ctx::clients)/${name}.conf" 2>/dev/null \
214 + | awk '{print $3}' | cut -d'/' -f1 || true
215 + }
216 +
217 + function peers::find_by_ip() {
218 + local target_ip="$1"
219 + while IFS= read -r name; do
220 + local ip
221 + ip=$(peers::get_ip "$name")
222 + [[ "$ip" == "$target_ip" ]] && echo "$name" && return 0
223 + done < <(peers::all)
224 + }
225 +
226 + function peers::is_connected() {
227 + local handshake_ts="${1:-0}"
228 + local now diff threshold
229 + now=$(date +%s)
230 + threshold=$(config::handshake_time_sec)
231 + [[ "$handshake_ts" == "0" || -z "$handshake_ts" ]] && return 1
232 + diff=$(( now - handshake_ts ))
233 + (( diff < threshold ))
234 + }
235 +
236 + function peers::is_attempting() {
237 + local last_ts="${1:-}"
238 + [[ -z "$last_ts" ]] && return 1
239 + local now attempt_ts diff threshold
240 + now=$(date +%s)
241 + threshold=$(config::handshake_time_sec)
242 + attempt_ts=$(json::iso_to_ts "$last_ts")
243 + [[ -z "$attempt_ts" || "$attempt_ts" == "0" ]] && return 1
244 + diff=$(( now - attempt_ts ))
245 + (( diff < threshold ))
246 + }
247 +
248 + function peers::is_online() {
249 + local name="${1:-}" handshake_ts="${2:-0}" last_ts="${3:-}"
250 + local is_blocked
251 + peers::is_blocked "$name" && is_blocked="true" || is_blocked="false"
252 + if [[ "$is_blocked" == "true" ]]; then
253 + peers::is_attempting "$last_ts"
254 + else
255 + peers::is_connected "$handshake_ts"
256 + fi
257 + }
258 +
259 + function peers::is_offline() {
260 + local name="${1:-}" handshake_ts="${2:-0}" last_ts="${3:-}"
261 + peers::is_online "$name" "$handshake_ts" "$last_ts" && return 1 || return 0
262 + }
263 +
264 + function peers::get_main_group() {
265 + local name="${1:?}"
266 + peers::get_meta "$name" "main_group"
267 + }
268 +
269 + function peers::set_main_group() {
270 + local name="${1:?}" group="${2:?}"
271 + peers::set_meta "$name" "main_group" "$group"
272 + }
273 +
274 + # ============================================
275 + # Name + Type Parsing
276 + # ============================================
277 +
278 + function peers::resolve_name() {
279 + local name="$1"
280 + local type="${2:-}"
281 +
282 + if [[ -n "$type" ]]; then
283 + if ! subnet::exists "$type"; then
284 + log::error "Invalid device type: ${type}"
285 + return 1
286 + fi
287 + echo "${type}-${name}"
288 + else
289 + echo "$name"
290 + fi
291 + }
292 +
293 + function peers::require_exists() {
294 + local name="$1"
295 + if [[ ! -f "$(ctx::clients)/${name}.conf" ]]; then
296 + log::error "Client not found: ${name}"
297 + return 1
298 + fi
299 + }
300 +
301 + function peers::resolve_and_require() {
302 + local name="$1"
303 + local type="${2:-}"
304 +
305 + local resolved
306 + resolved=$(peers::resolve_name "$name" "$type") || return 1
307 + peers::require_exists "$resolved" || return 1
308 + echo "$resolved"
309 + }
310 +
311 + function peers::rename_meta() {
312 + local name="${1:-}" new_name="${2:-}"
313 + local old_meta new_meta
314 + old_meta=$(peers::meta_path "$name")
315 + new_meta=$(peers::meta_path "$new_name")
316 + [[ -f "$old_meta" ]] && mv "$old_meta" "$new_meta"
317 + return 0
318 + }
319 +
320 + # ============================================
321 + # Cleanup
322 + # ============================================
323 +
324 + function peers::purge() {
325 + local name="${1:-}" client_ip="${2:-}" was_blocked="${3:-false}"
326 +
327 + [[ -n "$client_ip" ]] && fw::flush_peer "$client_ip"
328 + peers::remove_from_server "$name" || return 1
329 + peers::remove_client_config "$name" || return 1
330 + keys::remove "$name" || return 1
331 + group::remove_peer_from_all "$name" || return 1
332 +
333 + if [[ -n "$client_ip" ]] && $was_blocked; then
334 + fw::unblock_all "$client_ip"
335 + fi
336 +
337 + block::remove_file "$name" 2>/dev/null || true
338 + peers::remove_meta "$name" 2>/dev/null || true
339 + peers::reload || return 1
340 + }
341 +
342 +
343 + # ============================================
344 + # Display / Formatting
345 + # ============================================
346 +
347 + function peers::format_last_seen() {
348 + local name="${1:-}" pubkey="${2:-}" is_blocked="${3:-false}"
349 + local last_ts="${4:-}" last_evt="${5:-}" handshake_ts="${6:-0}"
350 +
351 + local data
352 + data=$(peers::last_seen_data "$is_blocked" "$last_ts" "$handshake_ts")
353 +
354 + local ts type
355 + IFS="|" read -r ts type <<< "$data"
356 +
357 + case "$type" in
358 + none) echo "—" ;;
359 + dropped) echo "$(fmt::datetime_iso "$ts") (dropped)" ;;
360 + handshake) echo "$(fmt::datetime "$ts") (handshake)" ;;
361 + esac
362 + }
363 +
364 + function peers::format_status() {
365 + local name="${1:-}" public_key="${2:-}" is_blocked="${3:-false}"
366 + local is_restricted="${4:-false}" handshake_ts="${5:-0}" last_ts="${6:-}"
367 +
368 + local state
369 + state=$(peers::connection_state "$is_blocked" "$is_restricted" \
370 + "$handshake_ts" "$last_ts")
371 +
372 + local conn_str modifier color
373 + IFS="|" read -r conn_str modifier color <<< "$state"
374 +
375 + # Color based on state — modifier overrides base connection color
376 + if [[ "$is_blocked" == "true" ]]; then
377 + color="\033[1;31m" # red — blocked
378 + elif [[ "$is_restricted" == "true" ]]; then
379 + color="\033[1;33m" # yellow — restricted
380 + elif [[ "$conn_str" == "online" ]]; then
381 + color="\033[1;32m" # green — online
382 + else
383 + color="\033[0;37m" # gray — offline
384 + fi
385 +
386 + local conn_str_padded
387 + conn_str_padded=$(printf "%-7s" "$conn_str")
388 + echo -e "${color}${conn_str_padded}\033[0m"
389 + }
390 +
391 + # Inspect — verbose, color + descriptive text
392 + function peers::format_status_verbose() {
393 + local name="${1:-}" public_key="${2:-}" is_blocked="${3:-false}"
394 + local is_restricted="${4:-false}" handshake_ts="${5:-0}" last_ts="${6:-}"
395 +
396 + local conn_str
397 + local state
398 + state=$(peers::connection_state "$is_blocked" "$is_restricted" \
399 + "$handshake_ts" "$last_ts")
400 + IFS="|" read -r conn_str _ _ <<< "$state"
401 +
402 + local color suffix=""
403 + if [[ "$is_blocked" == "true" ]]; then
404 + color="\033[1;31m"
405 + suffix=" (blocked)"
406 + elif [[ "$is_restricted" == "true" ]]; then
407 + color="\033[1;33m"
408 + suffix=" (restricted)"
409 + elif [[ "$conn_str" == "online" ]]; then
410 + color="\033[1;32m"
411 + else
412 + color="\033[0;37m"
413 + fi
414 +
415 + echo -e "${color}${conn_str}${suffix}\033[0m"
416 + }
417 +
418 + function peers::display_type() {
419 + local type="${1:-}" _subtype="${2:-}"
420 + echo "${type:-unknown}"
421 + }
422 +
423 + # ============================================
424 + # Connection data
425 + # ============================================
426 +
427 + # Data functions — return raw values
428 + function peers::connection_state() {
429 + # Returns: connected|modifier|color_code
430 + local is_blocked="${1:-false}" is_restricted="${2:-false}"
431 + local handshake_ts="${3:-0}" last_ts="${4:-}"
432 + local threshold
433 + threshold=$(config::handshake_time_sec)
434 + local now
435 + now=$(date +%s)
436 + local connected=false modifier="" color
437 +
438 + if [[ "$is_blocked" == "true" ]]; then
439 + local attempt_ts diff
440 + attempt_ts=$(json::iso_to_ts "${last_ts:-0}")
441 + diff=$(( now - attempt_ts ))
442 + (( diff < threshold )) && connected=true
443 + modifier="blocked"
444 + color="\033[1;31m"
445 + elif [[ "$is_restricted" == "true" ]]; then
446 + local diff=$(( now - handshake_ts ))
447 + (( diff < threshold )) && connected=true
448 + modifier="restricted"
449 + color="\033[1;33m"
450 + else
451 + local diff=$(( now - handshake_ts ))
452 + (( diff < threshold )) && connected=true
453 + $connected && color="\033[1;32m" || color="\033[0;37m"
454 + fi
455 +
456 + $connected && echo "online|${modifier}|${color}" || echo "offline|${modifier}|${color}"
457 + }
458 +
459 + function peers::last_seen_data() {
460 + # Returns: timestamp|type (dropped|handshake|none)
461 + local is_blocked="${1:-false}" last_ts="${2:-}" handshake_ts="${3:-0}"
462 +
463 + if [[ "$is_blocked" == "true" ]]; then
464 + if [[ -n "$last_ts" && "$last_ts" != "0" && "$last_ts" != "null" ]]; then
465 + echo "${last_ts}|dropped"
466 + else
467 + echo "|none"
468 + fi
469 + else
470 + if [[ -z "$handshake_ts" || "$handshake_ts" == "0" ]]; then
471 + echo "|none"
472 + else
473 + echo "${handshake_ts}|handshake"
474 + fi
475 + fi
476 + }
477 +
478 + function peers::get_type_from_ip() {
479 + local ip="${1:-}"
480 + [[ -z "$ip" ]] && echo "unknown" && return 0
481 + subnet::type_from_ip "$ip"
482 + }
483 +
484 +
485 + # ============================================
486 + # Activity
487 + # ============================================
488 +
489 + # Returns: level|rx|tx
490 + function peers::activity_total() {
491 + local pubkey="${1:-}"
492 + json::peer_transfer "$(config::interface)" | grep "^${pubkey}" | head -1 | cut -d'|' -f2-
493 + }
494 +
495 + # Returns: level|rx_rate|tx_rate
496 + function peers::activity_current() {
497 + local pubkey="${1:-}"
498 + json::peer_transfer_delta "$(config::interface)" \
499 + "$(ctx::daemon)/transfer_cache.json" \
500 + | grep "^${pubkey}" | head -1 | cut -d'|' -f2-
501 + }
502 +
503 + function peers::format_activity_total() {
504 + local pubkey="${1:-}"
505 + local data
506 + data=$(peers::activity_total "$pubkey")
507 + [[ -z "$data" ]] && echo "—" && return 0
508 + local level rx tx rx_hr tx_hr
509 + IFS="|" read -r rx tx level <<< "$data"
510 + rx_hr=$(numfmt --to=iec "${rx:-0}" 2>/dev/null || echo "0B")
511 + tx_hr=$(numfmt --to=iec "${tx:-0}" 2>/dev/null || echo "0B")
512 + echo "${level:-none} (↓${rx_hr} ↑${tx_hr})"
513 + }
514 +
515 + function peers::format_activity_current() {
516 + local pubkey="${1:-}"
517 + local data
518 + data=$(peers::activity_current "$pubkey")
519 + [[ -z "$data" ]] && echo "—" && return 0
520 + local level rx_rate tx_rate rx_hr tx_hr
521 + IFS="|" read -r rx_rate tx_rate level <<< "$data"
522 + [[ "$level" == "unknown" ]] && echo "sampling..." && return 0
523 + local rx_hr tx_hr
524 + rx_hr=$(numfmt --to=iec "${rx_rate:-0}" 2>/dev/null || echo "${rx_rate:-0}")
525 + tx_hr=$(numfmt --to=iec "${tx_rate:-0}" 2>/dev/null || echo "${tx_rate:-0}")
526 + echo "${level} (↓${rx_hr}B/s ↑${tx_hr}B/s)"
527 + }
528 +
529 + # ============================================
530 + # Helpers - Meta File
531 + # ============================================
532 +
533 + function peers::meta_path() {
534 + local name="$1"
535 + echo "$(ctx::meta)/${name}.meta"
536 + }
537 +
538 + function peers::get_meta() {
539 + local name="$1" key="$2"
540 + json::get "$(peers::meta_path "$name")" "$key"
541 + }
542 +
543 + function peers::set_meta() {
544 + local name="$1" key="$2" value="$3"
545 + json::set "$(peers::meta_path "$name")" "$key" "$value"
546 + }
547 +
548 + function peers::remove_meta() {
549 + local name="$1"
550 + rm -f "$(peers::meta_path "$name")"
551 + }
552 +
553 + # ============================================
554 + # Live Reload
555 + # ============================================
556 +
557 + function peers::reload() {
558 + wg syncconf "$(config::interface)" <(wg-quick strip "$(config::interface)")
559 + log::debug "WireGuard config reloaded"
560 + }
上一頁 下一頁