Utoljára aktív 1 month ago

nuno gist felülvizsgálása 1 month ago. Revízióhoz ugrás

1 file changed, 767 insertions

gistfile1.txt(fájl létrehozva)

@@ -0,0 +1,767 @@
1 + #!/usr/bin/env bash
2 +
3 + # ============================================
4 + # Lifecycle
5 + # ============================================
6 +
7 + function cmd::rule::on_load() {
8 + flag::register --name
9 + flag::register --desc
10 + flag::register --block-ip
11 + flag::register --allow-ip
12 + flag::register --block-port
13 + flag::register --allow-port
14 + flag::register --remove-block-ip
15 + flag::register --remove-allow-ip
16 + flag::register --remove-block-port
17 + flag::register --remove-allow-port
18 + flag::register --peer
19 + flag::register --peers
20 + flag::register --dns-redirect
21 + flag::register --color
22 + }
23 +
24 + # ============================================
25 + # Help
26 + # ============================================
27 +
28 + function cmd::rule::help() {
29 + cat <<EOF
30 + Usage: wgctl rule <subcommand> [options]
31 +
32 + Manage firewall rules for peers.
33 +
34 + Subcommands:
35 + list, ls List all rules
36 + show Show rule details and assigned peers
37 + add, new, create Create a new rule
38 + update, edit Update a rule and re-apply to all peers
39 + remove, rm, del Remove a rule
40 + assign Assign a rule to a peer
41 + unassign Remove rule from a peer
42 + migrate Apply default rules to all unassigned peers
43 +
44 + Options for add/update:
45 + --name <name> Rule name (e.g. guest, user, dev-01)
46 + --desc <description> Human readable description
47 + --allow-ip <ip/cidr> Allow IP or subnet (repeatable)
48 + --allow-port <ip:port:proto> Allow specific port (repeatable)
49 + --block-ip <ip/cidr> Block IP or subnet (repeatable)
50 + --block-port <ip:port:proto> Block specific port (repeatable)
51 + --dns-redirect Force DNS through Pi-hole
52 + --remove-allow-ip <ip> Remove allow IP entry (update only)
53 + --remove-allow-port <entry> Remove allow port entry (update only)
54 + --remove-block-ip <ip> Remove block IP entry (update only)
55 + --remove-block-port <entry> Remove block port entry (update only)
56 +
57 + Options for assign/unassign:
58 + --name <rule> Rule name
59 + --peer <peer> Peer name (e.g. phone-nuno)
60 + --type <type> Peer device type (optional)
61 +
62 + Examples:
63 + wgctl rule list
64 + wgctl rule show --name guest
65 + wgctl rule add --name dev-01 --desc "Dev VM only" --allow-ip 10.0.0.50 --block-ip 10.0.0.0/24
66 + wgctl rule update --name user --block-port 10.0.0.100:8006:tcp
67 + wgctl rule update --name user --remove-block-ip 10.0.0.99
68 + wgctl rule assign --name dev-01 --peer laptop-nuno
69 + wgctl rule unassign --peer laptop-nuno --type laptop
70 + wgctl rule migrate
71 + EOF
72 + }
73 +
74 + # ============================================
75 + # Run
76 + # ============================================
77 +
78 + function cmd::rule::run() {
79 + local subcmd="${1:-help}"
80 + shift || true
81 +
82 + case "$subcmd" in
83 + list|ls) cmd::rule::list "$@" ;;
84 + show) cmd::rule::show "$@" ;;
85 + add|new|create) cmd::rule::add "$@" ;;
86 + update|edit) cmd::rule::update "$@" ;;
87 + remove|rm|del|delete) cmd::rule::remove "$@" ;;
88 + assign) cmd::rule::assign "$@" ;;
89 + unassign) cmd::rule::unassign "$@" ;;
90 + migrate) cmd::rule::migrate "$@" ;;
91 + reapply) cmd::rule::reapply "$@" ;;
92 + inspect) cmd::rule::inspect "$@" ;;
93 + help) cmd::rule::help ;;
94 + *)
95 + log::error "Unknown subcommand: '${subcmd}'"
96 + cmd::rule::help
97 + return 1
98 + ;;
99 + esac
100 + }
101 +
102 + # ============================================
103 + # List
104 + # ============================================
105 +
106 + function cmd::rule::_pad() {
107 + local text="$1" width="$2"
108 + local visible
109 + visible=$(printf "%s" "$text" | sed 's/\x1b\[[0-9;]*m//g')
110 + local visible_len=${#visible}
111 + local byte_len=${#text}
112 + local extra=$(( byte_len - visible_len ))
113 + printf "%-$(( width + extra ))s" "$text"
114 + }
115 +
116 + function cmd::rule::list() {
117 + local rules_dir
118 + rules_dir="$(ctx::rules)"
119 +
120 + local rules=("${rules_dir}"/*.rule)
121 + if [[ ! -f "${rules[0]}" ]]; then
122 + log::wg "No rules configured"
123 + return 0
124 + fi
125 +
126 + log::section "Firewall Rules"
127 + printf "\n %-20s %-40s %-8s %-8s %s\n" \
128 + "NAME" "DESCRIPTION" \
129 + "$(ui::center "ALLOWS" 8)" \
130 + "$(ui::center "BLOCKS" 8)" \
131 + "PEERS"
132 + local divider
133 + divider=$(printf '─%.0s' {1..88})
134 + printf " %s\n" "$divider"
135 +
136 + local printing_base=false
137 + local current_group=""
138 +
139 + while IFS="|" read -r name desc n_allows n_blocks peer_count extends is_base group; do
140 + [[ -z "$name" ]] && continue
141 +
142 + # Base rules section header
143 + if [[ "$is_base" == "True" && "$printing_base" == "false" ]]; then
144 + local bdashes
145 + bdashes=$(printf '─%.0s' {1..74})
146 + printf "\n \033[0;37m── Base Rules \033[0m%s\n" "$bdashes"
147 + printing_base=true
148 + current_group="" # reset group tracking for base section
149 + fi
150 +
151 + # Group header — only for non-base rules
152 + if [[ "$is_base" == "False" && "$group" != "$current_group" ]]; then
153 + if [[ -n "$group" ]]; then
154 + printf "\n \033[0;36m▸ %s\033[0m\n" "$group"
155 + elif [[ -n "$current_group" ]]; then
156 + # Switching from grouped back to ungrouped
157 + printf "\n"
158 + fi
159 + current_group="$group"
160 + fi
161 +
162 + local short_desc="${desc:0:35}"
163 + [[ ${#desc} -gt 35 ]] && short_desc="${short_desc}..."
164 +
165 + local desc_col_width=40
166 + [[ "${short_desc:-—}" == "—" ]] && desc_col_width=42
167 +
168 + printf " %-20s %-${desc_col_width}s %-8s %-8s %s\n" \
169 + "$name" "${short_desc:-—}" \
170 + "$(ui::center "$n_allows" 8)" \
171 + "$(ui::center "$n_blocks" 8)" \
172 + "${peer_count} peers"
173 +
174 + # Print extends IMMEDIATELY after the rule row
175 + if [[ -n "$extends" ]]; then
176 + IFS=',' read -ra extend_list <<< "$extends"
177 + for base in "${extend_list[@]}"; do
178 + [[ -z "$base" ]] && continue
179 + printf " \033[0;37m ↳ %s\033[0m\n" "$base"
180 + done
181 + printf "\n" # blank line after rule with extends
182 + fi
183 +
184 + done < <(json::rule_list_data "$rules_dir" "$(ctx::meta)")
185 +
186 + printf "\n"
187 + }
188 +
189 + # ============================================
190 + # Show
191 + # ============================================
192 +
193 + function cmd::rule::show() {
194 + local name="" show_peers=false color=false
195 +
196 + while [[ $# -gt 0 ]]; do
197 + case "$1" in
198 + --name) name="$2"; shift 2 ;;
199 + --peers) show_peers=true; shift ;;
200 + --color) color=true; shift ;;
201 + --help) cmd::rule::help; return ;;
202 + *) log::error "Unknown flag: $1"; return 1 ;;
203 + esac
204 + done
205 +
206 + [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
207 + rule::require_exists "$name" || return 1
208 +
209 + local rule_file
210 + rule_file="$(ctx::rule::path "${name}.rule")"
211 +
212 + # Precompute peers before any operations
213 + local peer_list=()
214 + mapfile -t peer_list < <(peers::with_rule "$name")
215 + local peer_count=${#peer_list[@]}
216 +
217 + log::section "Rule: ${name}"
218 +
219 + local desc dns_redirect
220 + desc=$(json::get "$rule_file" "desc")
221 + dns_redirect=$(json::get "$rule_file" "dns_redirect")
222 +
223 + printf "\n"
224 + ui::row "Description" "${desc:-—}"
225 + ui::row "DNS Redirect" "${dns_redirect:-false}"
226 +
227 + # Load all entries
228 + local allow_ports allow_ips block_ips block_ports
229 + allow_ports=$(json::get "$rule_file" "allow_ports")
230 + allow_ips=$(json::get "$rule_file" "allow_ips")
231 + block_ips=$(json::get "$rule_file" "block_ips")
232 + block_ports=$(json::get "$rule_file" "block_ports")
233 +
234 + # Allow section
235 + if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
236 + cmd::rule::_show_section "Allow" "green" "$color"
237 + cmd::rule::_show_entries "Ports" "+" "$allow_ports" "$color" "green"
238 + cmd::rule::_show_entries "IPs" "+" "$allow_ips" "$color" "green"
239 + fi
240 +
241 + # Block section
242 + if [[ -n "$block_ips" || -n "$block_ports" ]]; then
243 + cmd::rule::_show_section "Block" "red" "$color"
244 + cmd::rule::_show_entries "IPs" "-" "$block_ips" "$color" "red"
245 + cmd::rule::_show_entries "Ports" "-" "$block_ports" "$color" "red"
246 + fi
247 +
248 + if [[ -z "$allow_ports" && -z "$allow_ips" && -z "$block_ips" && -z "$block_ports" ]]; then
249 + printf "\n"
250 + ui::row "Access" "full (no restrictions)"
251 + fi
252 +
253 + # Peers section
254 + cmd::rule::_show_section "Peers" "white" false
255 + ui::row "Assigned" "$peer_count"
256 +
257 + if $show_peers && [[ $peer_count -gt 0 ]]; then
258 + printf "\n"
259 + for peer_name in "${peer_list[@]}"; do
260 + local ip
261 + ip=$(peers::get_ip "$peer_name")
262 + printf " %-28s %s\n" "$peer_name" "$ip"
263 + done
264 + fi
265 +
266 + printf "\n"
267 + }
268 +
269 + # function cmd::rule::show() {
270 + # local name="" show_peers=false
271 +
272 + # while [[ $# -gt 0 ]]; do
273 + # case "$1" in
274 + # --name) name="$2"; shift 2 ;;
275 + # --peers) show_peers=true; shift ;;
276 + # --help) cmd::rule::help; return ;;
277 + # *) log::error "Unknown flag: $1"; return 1 ;;
278 + # esac
279 + # done
280 +
281 + # if [[ -z "$name" ]]; then
282 + # log::error "Missing required flag: --name"
283 + # return 1
284 + # fi
285 +
286 + # rule::require_exists "$name" || return 1
287 +
288 + # local rule_file
289 + # rule_file="$(ctx::rule::path "${name}.rule")"
290 +
291 + # log::section "Rule: ${name}"
292 +
293 + # local desc dns_redirect
294 + # desc=$(json::get "$rule_file" "desc")
295 + # dns_redirect=$(json::get "$rule_file" "dns_redirect")
296 +
297 + # printf "\n"
298 + # ui::row "Description" "${desc:-—}"
299 + # ui::row "DNS Redirect" "${dns_redirect:-false}"
300 +
301 + # # Allow ports
302 + # local allow_ports
303 + # allow_ports=$(json::get "$rule_file" "allow_ports")
304 + # if [[ -n "$allow_ports" ]]; then
305 + # printf "\n Allow Ports:\n"
306 + # ui::print_list "+" "$allow_ports"
307 + # fi
308 +
309 + # # Allow IPs
310 + # local allow_ips
311 + # allow_ips=$(json::get "$rule_file" "allow_ips")
312 + # if [[ -n "$allow_ips" ]]; then
313 + # printf "\n Allow IPs:\n"
314 + # while IFS= read -r ip; do
315 + # printf " + %s\n" "$ip"
316 + # done <<< "$allow_ips"
317 + # fi
318 +
319 + # # Block IPs
320 + # local block_ips
321 + # block_ips=$(json::get "$rule_file" "block_ips")
322 + # if [[ -n "$block_ips" ]]; then
323 + # printf "\n Block IPs:\n"
324 + # while IFS= read -r ip; do
325 + # printf " - %s\n" "$ip"
326 + # done <<< "$block_ips"
327 + # fi
328 +
329 + # # Block ports
330 + # local block_ports
331 + # block_ports=$(json::get "$rule_file" "block_ports")
332 + # if [[ -n "$block_ports" ]]; then
333 + # printf "\n Block Ports:\n"
334 + # while IFS= read -r entry; do
335 + # printf " - %s\n" "$entry"
336 + # done <<< "$block_ports"
337 + # fi
338 +
339 + # # Precompute peers before any other operations
340 + # local peer_list=()
341 + # mapfile -t peer_list < <(peers::with_rule "$name")
342 + # local peer_count=${#peer_list[@]}
343 +
344 + # # Peer count — always shown
345 + # printf "\n %-20s %s\n" "Assigned Peers:" "$peer_count"
346 + # printf " %s\n" "$(printf '─%.0s' {1..40})"
347 +
348 + # # Peer details — only with --peers flag
349 + # if $show_peers && [[ $peer_count -gt 0 ]]; then
350 + # for peer_name in "${peer_list[@]}"; do
351 + # local ip
352 + # ip=$(peers::get_ip "$peer_name")
353 + # printf " %-28s %s\n" "$peer_name" "$ip"
354 + # done
355 + # fi
356 +
357 + # printf "\n"
358 + # }
359 +
360 + # ============================================
361 + # Inspect
362 + # ============================================
363 +
364 + function cmd::rule::inspect() {
365 + local name=""
366 +
367 + while [[ $# -gt 0 ]]; do
368 + case "$1" in
369 + --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
370 + --help) cmd::rule::help; return ;;
371 + *) log::error "Unknown flag: $1"; return 1 ;;
372 + esac
373 + done
374 +
375 + [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
376 + rule::require_exists "$name" || return 1
377 +
378 + log::section "Rule Inspect: ${name}"
379 +
380 + local prev_section=""
381 + while IFS="|" read -r section key value; do
382 + [[ -z "$section" ]] && continue
383 +
384 + # Print section header when section changes
385 + if [[ "$section" != "$prev_section" ]]; then
386 + case "$section" in
387 + own)
388 + cmd::rule::_show_section "Own Rules" ;;
389 + dns)
390 + cmd::rule::_show_section "DNS" ;;
391 + resolved)
392 + cmd::rule::_show_section "Resolved (applied to peers)" ;;
393 + inherited:*)
394 + local base_name="${section#inherited:}"
395 + cmd::rule::_show_section "Inherited: ${base_name}" ;;
396 + esac
397 + prev_section="$section"
398 + fi
399 +
400 + case "$key" in
401 + allow_ip|allow_port)
402 + printf " \033[0;32m+\033[0m %s\n" "$value" ;;
403 + block_ip|block_port)
404 + printf " \033[0;31m-\033[0m %s\n" "$value" ;;
405 + dns_redirect)
406 + printf " Redirect all DNS → %s\n" "$(config::dns)" ;;
407 + esac
408 +
409 + done < <(json::rule_inspect "$(ctx::rules)" "$name")
410 +
411 + printf "\n"
412 + }
413 +
414 + # ============================================
415 + # Add
416 + # ============================================
417 +
418 + function cmd::rule::add() {
419 + local name="" desc=""
420 + local allow_ips=() block_ips=() block_ports=()
421 + local dns_redirect=false
422 +
423 + while [[ $# -gt 0 ]]; do
424 + case "$1" in
425 + --name) name="$2"; shift 2 ;;
426 + --desc) desc="$2"; shift 2 ;;
427 + --allow-ip) allow_ips+=("$2"); shift 2 ;;
428 + --block-ip) block_ips+=("$2"); shift 2 ;;
429 + --block-port) block_ports+=("$2"); shift 2 ;;
430 + --dns-redirect) dns_redirect=true; shift ;;
431 + --help) cmd::rule::help; return ;;
432 + *)
433 + log::error "Unknown flag: $1"
434 + return 1
435 + ;;
436 + esac
437 + done
438 +
439 + if [[ -z "$name" ]]; then
440 + log::error "Missing required flag: --name"
441 + return 1
442 + fi
443 +
444 + if rule::exists "$name"; then
445 + log::error "Rule already exists: ${name}"
446 + return 1
447 + fi
448 +
449 + local rule_file
450 + rule_file="$(ctx::rule::path "${name}.rule")"
451 +
452 + local allow_str block_str port_str
453 +
454 + allow_str=$(IFS=','; echo "${allow_ips[*]}")
455 + block_str=$(IFS=','; echo "${block_ips[*]}")
456 + port_str=$(IFS=','; echo "${block_ports[*]}")
457 +
458 + json::create_rule "$rule_file" "$name" "$desc" \
459 + "$($dns_redirect && echo true || echo false)" \
460 + "$allow_str" "$block_str" "$port_str" || return 1
461 +
462 + log::wg_success "Rule created: ${name}"
463 + }
464 +
465 + # ============================================
466 + # Update
467 + # ============================================
468 +
469 + function cmd::rule::update() {
470 + local name="" desc=""
471 + local allow_ips=() block_ips=() block_ports=()
472 + local allow_ports=()
473 + local rm_allow_ips=() rm_block_ips=() rm_block_ports=() rm_allow_ports=()
474 + local dns_redirect=""
475 +
476 + while [[ $# -gt 0 ]]; do
477 + case "$1" in
478 + --name) name="$2"; shift 2 ;;
479 + --desc) desc="$2"; shift 2 ;;
480 + --allow-ip) allow_ips+=("$2"); shift 2 ;;
481 + --block-ip) block_ips+=("$2"); shift 2 ;;
482 + --block-port) block_ports+=("$2"); shift 2 ;;
483 + --allow-port) allow_ports+=("$2"); shift 2 ;;
484 + --remove-allow-ip) rm_allow_ips+=("$2"); shift 2 ;;
485 + --remove-block-ip) rm_block_ips+=("$2"); shift 2 ;;
486 + --remove-block-port) rm_block_ports+=("$2"); shift 2 ;;
487 + --remove-allow-port) rm_allow_ports+=("$2"); shift 2 ;;
488 + --dns-redirect) dns_redirect=true; shift ;;
489 + --help) cmd::rule::help; return ;;
490 + *)
491 + log::error "Unknown flag: $1"
492 + return 1
493 + ;;
494 + esac
495 + done
496 +
497 + if [[ -z "$name" ]]; then
498 + log::error "Missing required flag: --name"
499 + return 1
500 + fi
501 +
502 + rule::require_exists "$name" || return 1
503 +
504 + local rule_file
505 + rule_file="$(ctx::rule::path "${name}.rule")"
506 +
507 + # Update desc and dns_redirect
508 + [[ -n "$desc" ]] && json::set "$rule_file" "desc" "\"$desc\""
509 + [[ -n "$dns_redirect" ]] && json::set "$rule_file" "dns_redirect" "true"
510 +
511 + # Add entries
512 + for ip in "${allow_ips[@]}"; do json::append "$rule_file" "allow_ips" "$ip"; done
513 + for ip in "${block_ips[@]}"; do json::append "$rule_file" "block_ips" "$ip"; done
514 + for p in "${block_ports[@]}"; do json::append "$rule_file" "block_ports" "$p"; done
515 + for p in "${allow_ports[@]}"; do json::append "$rule_file" "allow_ports" "$p"; done
516 +
517 + # Remove entries
518 + for ip in "${rm_allow_ips[@]}"; do json::remove "$rule_file" "allow_ips" "$ip"; done
519 + for ip in "${rm_block_ips[@]}"; do json::remove "$rule_file" "block_ips" "$ip"; done
520 + for p in "${rm_block_ports[@]}"; do json::remove "$rule_file" "block_ports" "$p"; done
521 + for p in "${rm_allow_ports[@]}"; do json::remove "$rule_file" "allow_ports" "$p"; done
522 +
523 + log::wg_success "Rule updated: ${name}"
524 +
525 + # Re-apply to all assigned peers
526 + rule::reapply_all "$name"
527 + }
528 +
529 + # ============================================
530 + # Remove
531 + # ============================================
532 +
533 + function cmd::rule::remove() {
534 + local name="" force=false
535 +
536 + while [[ $# -gt 0 ]]; do
537 + case "$1" in
538 + --name) name="$2"; shift 2 ;;
539 + --force) force=true; shift ;;
540 + --help) cmd::rule::help; return ;;
541 + *)
542 + log::error "Unknown flag: $1"
543 + return 1
544 + ;;
545 + esac
546 + done
547 +
548 + if [[ -z "$name" ]]; then
549 + log::error "Missing required flag: --name"
550 + return 1
551 + fi
552 +
553 + rule::require_exists "$name" || return 1
554 +
555 + # Check for assigned peers
556 +
557 + local peer_list=()
558 + mapfile -t peer_list < <(peers::with_rule "$name")
559 + local peer_count=${#peer_list[@]}
560 +
561 + if [[ "$peer_count" -gt 0 ]]; then
562 + log::error "Rule '${name}' is assigned to ${peer_count} peer(s) — unassign first or use --force"
563 + $force || return 1
564 +
565 + # Force: unassign from all peers
566 + for peer in "${peer_list[@]}"; do
567 + local ip
568 + ip=$(peers::get_ip "$peer")
569 + rule::unapply "$name" "$ip"
570 + done
571 + fi
572 +
573 + rm -f "$(ctx::rule::path "${name}.rule")"
574 + log::wg_success "Rule removed: ${name}"
575 + }
576 +
577 + # ============================================
578 + # Assign
579 + # ============================================
580 +
581 + function cmd::rule::assign() {
582 + local name="" peer="" type=""
583 + while [[ $# -gt 0 ]]; do
584 + case "$1" in
585 + --name) name="$2"; shift 2 ;;
586 + --peer) peer="$2"; shift 2 ;;
587 + --type) type="$2"; shift 2 ;;
588 + --help) cmd::rule::help; return ;;
589 + *)
590 + log::error "Unknown flag: $1"
591 + return 1
592 + ;;
593 + esac
594 + done
595 +
596 + if [[ -z "$name" || -z "$peer" ]]; then
597 + log::error "Missing required flags: --name and --peer"
598 + return 1
599 + fi
600 +
601 + rule::require_exists "$name" || return 1
602 + rule::require_assignable "$name" || return 1
603 +
604 + # Support --type for peer resolution
605 + peer=$(peers::resolve_and_require "$peer" "$type") || return 1
606 +
607 + # Unapply existing rule first if any
608 + local existing_rule
609 + existing_rule=$(peers::get_meta "$peer" "rule")
610 +
611 + local ip
612 + ip=$(peers::get_ip "$peer")
613 + log::debug "rule::assign: peer=$peer ip=$ip"
614 + [[ -z "$ip" ]] && log::error "Could not resolve IP for: $peer" && return 1
615 +
616 + log::debug "assign: peer=$peer ip=$ip clients=$(ctx::clients)"
617 +
618 + if [[ -n "$existing_rule" && "$existing_rule" != "$name" ]]; then
619 + rule::unapply "$existing_rule" "$ip"
620 + log::wg "Removed existing rule '${existing_rule}' from: ${peer}"
621 + fi
622 +
623 + rule::apply "$name" "$ip"
624 +
625 + log::wg_success "Assigned rule '${name}' to: ${peer}"
626 + }
627 +
628 + # ============================================
629 + # Unassign
630 + # ============================================
631 +
632 + function cmd::rule::unassign() {
633 + local peer="" type=""
634 +
635 + while [[ $# -gt 0 ]]; do
636 + case "$1" in
637 + --peer) peer="$2"; shift 2 ;;
638 + --type) type="$2"; shift 2 ;;
639 + --help) cmd::rule::help; return ;;
640 + *)
641 + log::error "Unknown flag: $1"
642 + return 1
643 + ;;
644 + esac
645 + done
646 +
647 + if [[ -z "$peer" ]]; then
648 + log::error "Missing required flag: --peer"
649 + return 1
650 + fi
651 +
652 + peer=$(peers::resolve_and_require "$peer" "$type") || return 1
653 +
654 + local existing_rule
655 + existing_rule=$(peers::get_meta "$peer" "rule")
656 +
657 + if [[ -z "$existing_rule" ]]; then
658 + log::wg_warning "Peer '${peer}' has no assigned rule"
659 + return 0
660 + fi
661 +
662 + local ip
663 + ip=$(peers::get_ip "$peer")
664 + rule::unapply "$existing_rule" "$ip"
665 +
666 + log::wg_success "Unassigned rule from: ${peer}"
667 + }
668 +
669 + # ============================================
670 + # Migrate Rules
671 + # ============================================
672 +
673 + function cmd::rule::migrate() {
674 + log::section "Migrating peers to default rules"
675 +
676 + # Write migration plan to temp file to avoid fd conflicts
677 + local tmp
678 + tmp=$(mktemp)
679 +
680 + while IFS= read -r peer_name; do
681 + local existing
682 + existing=$(peers::get_meta "$peer_name" "rule")
683 + [[ -n "$existing" ]] && continue
684 + local default_rule
685 + default_rule=$(peers::default_rule "$peer_name")
686 + rule::exists "$default_rule" || continue
687 + local ip
688 + ip=$(peers::get_ip "$peer_name")
689 + echo "${peer_name} ${default_rule} ${ip}" >> "$tmp"
690 + done < <(peers::all)
691 +
692 + local count=0
693 + local lines
694 + mapfile -t lines < "$tmp"
695 +
696 + for line in "${lines[@]}"; do
697 + IFS=" " read -r peer_name default_rule ip <<< "$line"
698 + rule::apply "$default_rule" "$ip" "$peer_name" </dev/null
699 + (( count++ )) || true
700 + done
701 +
702 + cat "$tmp"
703 +
704 + rm -f "$tmp"
705 + log::wg_success "Migrated ${count} peers"
706 + }
707 +
708 + # ============================================
709 + # Reapply Rule
710 + # ============================================
711 +
712 + function cmd::rule::reapply() {
713 + local name=""
714 + while [[ $# -gt 0 ]]; do
715 + case "$1" in
716 + --name) name="$2"; shift 2 ;;
717 + *) log::error "Unknown flag: $1"; return 1 ;;
718 + esac
719 + done
720 + [[ -z "$name" ]] && log::error "Missing --name" && return 1
721 + rule::require_exists "$name" || return 1
722 + rule::reapply_all "$name"
723 + log::wg_success "Rule '${name}' reapplied"
724 + }
725 +
726 + # ============================================
727 + # Show helpers
728 + # ============================================
729 +
730 + function cmd::rule::_show_section() {
731 + local title="${1:-}" color="${2:-white}" use_color="${3:-false}"
732 + local color_code=""
733 + if $use_color; then
734 + case "$color" in
735 + green) color_code="\033[0;32m" ;;
736 + red) color_code="\033[0;31m" ;;
737 + esac
738 + fi
739 + printf "\n ${color_code}── %s ──────────────────────────────────\033[0m\n" "$title"
740 + }
741 +
742 + function cmd::rule::_show_entries() {
743 + local label="${1:-}" prefix="${2:-}" entries="${3:-}"
744 + local use_color="${4:-false}" color="${5:-white}"
745 + [[ -z "$entries" ]] && return 0
746 +
747 + local color_code="" reset=""
748 + if $use_color; then
749 + case "$color" in
750 + green) color_code="\033[0;32m" ;;
751 + red) color_code="\033[0;31m" ;;
752 + esac
753 + reset="\033[0m"
754 + fi
755 +
756 + printf " %-8s" "${label}:"
757 + local first=true
758 + while IFS= read -r entry; do
759 + [[ -z "$entry" ]] && continue
760 + if $first; then
761 + printf "%b%s %s%b\n" "$color_code" "$prefix" "$entry" "$reset"
762 + first=false
763 + else
764 + printf " %b%s %s%b\n" "$color_code" "$prefix" "$entry" "$reset"
765 + fi
766 + done <<< "$entries"
767 + }
Újabb Régebbi