最終更新 1 month ago

修正履歴 bb88674334a7825091b930da898daaf6de12dc73

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