852 lines
25 KiB
Bash
852 lines
25 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# ============================================
|
|
# Lifecycle
|
|
# ============================================
|
|
|
|
function cmd::group::on_load() {
|
|
flag::register --name
|
|
flag::register --desc
|
|
flag::register --peer
|
|
flag::register --type
|
|
flag::register --rule
|
|
flag::register --new-name
|
|
flag::register --force
|
|
}
|
|
|
|
# ============================================
|
|
# Help
|
|
# ============================================
|
|
|
|
function cmd::group::help() {
|
|
cat <<EOF
|
|
Usage: wgctl group <subcommand> [options]
|
|
|
|
Manage peer groups.
|
|
|
|
Subcommands:
|
|
list, ls List all groups
|
|
show Show group details and members
|
|
add, new, create Create a new group
|
|
remove, rm, del Remove a group definition
|
|
rename Rename a group
|
|
peer add Add a peer to a group
|
|
peer remove, peer rm Remove a peer from a group
|
|
rm-peers Remove all peers from WireGuard server
|
|
block Block all peers in group
|
|
unblock Unblock all peers in group
|
|
rule assign Assign a rule to all peers in group
|
|
audit Audit all peers in group
|
|
logs Show logs for all peers in group
|
|
watch Live monitor for all peers in group
|
|
|
|
Options:
|
|
--name <name> Group name
|
|
--desc <description> Group description
|
|
--peer <peer> Peer name
|
|
--type <type> Peer device type (for peer resolution)
|
|
--rule <rule> Rule name (for rule assign)
|
|
--new-name <name> New name (for rename)
|
|
--force Skip confirmation prompts
|
|
|
|
Examples:
|
|
wgctl group add --name family --desc "Family devices"
|
|
wgctl group peer add --name family --peer phone-nuno
|
|
wgctl group block --name family
|
|
wgctl group rule assign --name family --rule user
|
|
wgctl group audit --name family
|
|
EOF
|
|
}
|
|
|
|
# ============================================
|
|
# Run
|
|
# ============================================
|
|
|
|
function cmd::group::run() {
|
|
local subcmd="${1:-help}"
|
|
shift || true
|
|
|
|
case "$subcmd" in
|
|
list|ls) cmd::group::list "$@" ;;
|
|
show) cmd::group::show "$@" ;;
|
|
add|new|create) cmd::group::add "$@" ;;
|
|
remove|rm|del|delete) cmd::group::remove "$@" ;;
|
|
rename) cmd::group::rename "$@" ;;
|
|
peer) cmd::group::peer "$@" ;;
|
|
rm-peers) cmd::group::rm_peers "$@" ;;
|
|
block) cmd::group::block "$@" ;;
|
|
unblock) cmd::group::unblock "$@" ;;
|
|
rule) cmd::group::rule "$@" ;;
|
|
audit) cmd::group::audit "$@" ;;
|
|
logs) cmd::group::logs "$@" ;;
|
|
watch) cmd::group::watch "$@" ;;
|
|
help) cmd::group::help ;;
|
|
*)
|
|
log::error "Unknown subcommand: '${subcmd}'"
|
|
cmd::group::help
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ============================================
|
|
# List
|
|
# ============================================
|
|
|
|
function cmd::group::list() {
|
|
local groups_dir
|
|
groups_dir="$(ctx::groups)"
|
|
|
|
local groups=("${groups_dir}"/*.group)
|
|
if [[ ! -f "${groups[0]}" ]]; then
|
|
log::wg "No groups configured"
|
|
return 0
|
|
fi
|
|
|
|
log::section "Groups"
|
|
printf "\n %-20s %-35s %-8s %s\n" "NAME" "DESCRIPTION" "PEERS" "STATUS"
|
|
printf " %s\n" "$(printf '─%.0s' {1..75})"
|
|
|
|
while IFS="|" read -r name desc total blocked; do
|
|
[[ -z "$name" ]] && continue
|
|
|
|
local status_color="" status_str="active"
|
|
if [[ "$total" -gt 0 ]]; then
|
|
if [[ "$blocked" -eq "$total" ]]; then
|
|
status_color="\033[1;31m"
|
|
status_str="blocked"
|
|
elif [[ "$blocked" -gt 0 ]]; then
|
|
status_color="\033[1;33m"
|
|
status_str="blocked (${blocked}/${total})"
|
|
else
|
|
status_color="\033[1;32m"
|
|
status_str="active"
|
|
fi
|
|
fi
|
|
|
|
local short_desc="${desc:0:33}"
|
|
[[ ${#desc} -gt 33 ]] && short_desc="${short_desc}..."
|
|
|
|
local desc_col_width=35
|
|
[[ "$desc" == "—" || -z "$desc" ]] && desc_col_width=37
|
|
|
|
printf " %-20s %-${desc_col_width}s %-8s %b\n" \
|
|
"$name" "${short_desc:-—}" "$total" \
|
|
"${status_color}${status_str}\033[0m"
|
|
|
|
done < <(json::group_list_data "$groups_dir" "$(ctx::blocks)")
|
|
|
|
printf "\n"
|
|
}
|
|
|
|
# function cmd::group::list() {
|
|
# local groups_dir
|
|
# groups_dir="$(ctx::groups)"
|
|
|
|
# local groups=("${groups_dir}"/*.group)
|
|
# if [[ ! -f "${groups[0]}" ]]; then
|
|
# log::wg "No groups configured"
|
|
# return 0
|
|
# fi
|
|
|
|
# log::section "Groups"
|
|
|
|
# printf "\n %-20s %-35s %-8s %s\n" "NAME" "DESCRIPTION" "PEERS" "STATUS"
|
|
# printf " %s\n" "$(printf '─%.0s' {1..75})"
|
|
|
|
# for group_file in "${groups_dir}"/*.group; do
|
|
# [[ -f "$group_file" ]] || continue
|
|
|
|
# local name desc
|
|
# name=$(json::get "$group_file" "name")
|
|
# desc=$(json::get "$group_file" "desc")
|
|
|
|
# # Count peers
|
|
# local peers_list=()
|
|
# mapfile -t peers_list < <(json::get "$group_file" "peers")
|
|
# # Filter empty entries
|
|
# local filtered=()
|
|
# for p in "${peers_list[@]:-}"; do
|
|
# [[ -n "$p" ]] && filtered+=("$p")
|
|
# done
|
|
# peers_list=("${filtered[@]:-}")
|
|
# local peer_count=${#peers_list[@]}
|
|
|
|
# [[ -z "${peers_list[0]}" ]] && peer_count=0
|
|
|
|
# # Check block status
|
|
# local blocked=0 total=0
|
|
# for peer_name in "${peers_list[@]}"; do
|
|
# [[ -z "$peer_name" ]] && continue
|
|
# (( total++ )) || true
|
|
# peers::is_blocked "$peer_name" && (( blocked++ )) || true
|
|
# done
|
|
|
|
# local status_color=""
|
|
# local status_str="active"
|
|
# if [[ "$total" -gt 0 ]]; then
|
|
# if [[ "$blocked" -eq "$total" ]]; then
|
|
# status_color="\033[1;31m"
|
|
# status_str="blocked"
|
|
# elif [[ "$blocked" -gt 0 ]]; then
|
|
# status_color="\033[1;33m"
|
|
# status_str="blocked (${blocked}/${total})"
|
|
# # status_color="\033[1;33m"
|
|
# # status_str="${blocked}/${total} blocked"
|
|
# else
|
|
# status_color="\033[1;32m"
|
|
# status_str="active"
|
|
# fi
|
|
# fi
|
|
|
|
# local short_desc="${desc:0:33}"
|
|
# [[ ${#desc} -gt 33 ]] && short_desc="${short_desc}..."
|
|
|
|
# printf " %-20s %-35s %-8s %b\n" \
|
|
# "$name" \
|
|
# "${short_desc:-—}" \
|
|
# "$peer_count" \
|
|
# "${status_color}${status_str}\033[0m"
|
|
# done
|
|
|
|
# printf "\n"
|
|
# }
|
|
|
|
# ============================================
|
|
# Show
|
|
# ============================================
|
|
|
|
function cmd::group::show() {
|
|
local name=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
group::require_exists "$name" || return 1
|
|
|
|
local group_file
|
|
group_file="$(group::path "$name")"
|
|
|
|
log::section "Group: ${name}"
|
|
|
|
local desc
|
|
desc=$(json::get "$group_file" "desc")
|
|
printf "\n %-20s %s\n" "Description:" "${desc:-—}"
|
|
|
|
# Load peers
|
|
local peers_list=()
|
|
mapfile -t peers_list < <(json::get "$group_file" "peers")
|
|
# Filter empty entries
|
|
local filtered=()
|
|
for p in "${peers_list[@]:-}"; do
|
|
[[ -n "$p" ]] && filtered+=("$p")
|
|
done
|
|
peers_list=("${filtered[@]:-}")
|
|
local peer_count=${#peers_list[@]}
|
|
|
|
[[ -z "${peers_list[0]}" ]] && peer_count=0
|
|
|
|
printf " %-20s %s\n" "Peers:" "$peer_count"
|
|
printf " %s\n" "$(printf '─%.0s' {1..50})"
|
|
|
|
if [[ "$peer_count" -gt 0 ]]; then
|
|
printf "\n %-28s %-15s %-12s %s\n" "NAME" "IP" "RULE" "STATUS"
|
|
printf " %s\n" "$(printf '─%.0s' {1..65})"
|
|
for peer_name in "${peers_list[@]}"; do
|
|
[[ -z "$peer_name" ]] && continue
|
|
|
|
# Skip if peer no longer exists
|
|
if ! peers::require_exists "$peer_name" > /dev/null 2>&1; then
|
|
log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
|
|
continue
|
|
fi
|
|
|
|
local ip rule status_str status_color
|
|
ip=$(peers::get_ip "$peer_name")
|
|
rule=$(peers::get_meta "$peer_name" "rule")
|
|
rule="${rule:-—}"
|
|
|
|
if peers::is_blocked "$peer_name" 2>/dev/null; then
|
|
status_color="\033[1;31m"
|
|
status_str="blocked"
|
|
else
|
|
status_color="\033[1;32m"
|
|
status_str="active"
|
|
fi
|
|
|
|
printf " %-28s %-15s %-12s %b\n" \
|
|
"$peer_name" "0" "$rule" \
|
|
"${status_str}\033[0m"
|
|
done
|
|
else
|
|
printf " —\n"
|
|
fi
|
|
|
|
printf "\n"
|
|
return 0
|
|
}
|
|
|
|
# ============================================
|
|
# Add
|
|
# ============================================
|
|
|
|
function cmd::group::add() {
|
|
local name="" desc=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--desc) util::require_flag "--desc" "${2:-}" || return 1; desc="$2"; shift 2 ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
|
|
if group::exists "$name"; then
|
|
log::error "Group already exists: ${name}"
|
|
return 1
|
|
fi
|
|
|
|
json::create_group "$(group::path "$name")" "$name" "$desc"
|
|
|
|
log::wg_success "Group created: ${name}"
|
|
}
|
|
|
|
# ============================================
|
|
# Remove
|
|
# ============================================
|
|
|
|
function cmd::group::remove() {
|
|
local name="" force=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--force) force=true; shift ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
group::require_exists "$name" || return 1
|
|
|
|
if ! $force; then
|
|
read -r -p "Remove group '${name}'? This only removes the group definition, not the peers. [y/N] " confirm
|
|
case "$confirm" in
|
|
[yY][eE][sS]|[yY]) ;;
|
|
*) log::info "Aborted"; return 0 ;;
|
|
esac
|
|
fi
|
|
|
|
rm -f "$(group::path "$name")"
|
|
log::wg_success "Group removed: ${name}"
|
|
}
|
|
|
|
# ============================================
|
|
# Rename
|
|
# ============================================
|
|
|
|
function cmd::group::rename() {
|
|
local name="" new_name=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--new-name) util::require_flag "--new-name" "${2:-}" || return 1; new_name="$2"; shift 2 ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
[[ -z "$new_name" ]] && log::error "Missing required flag: --new-name" && return 1
|
|
group::require_exists "$name" || return 1
|
|
|
|
if group::exists "$new_name"; then
|
|
log::error "Group already exists: ${new_name}"
|
|
return 1
|
|
fi
|
|
|
|
local old_file new_file
|
|
old_file="$(group::path "$name")"
|
|
new_file="$(group::path "$new_name")"
|
|
|
|
# Update name field in file
|
|
json::set "$old_file" "name" "\"$new_name\""
|
|
mv "$old_file" "$new_file"
|
|
|
|
log::wg_success "Group renamed: ${name} → ${new_name}"
|
|
}
|
|
|
|
# ============================================
|
|
# Peer subcommand
|
|
# ============================================
|
|
|
|
function cmd::group::peer() {
|
|
local subcmd="${1:-help}"
|
|
shift || true
|
|
|
|
case "$subcmd" in
|
|
add) cmd::group::peer_add "$@" ;;
|
|
remove|rm|del) cmd::group::peer_remove "$@" ;;
|
|
*)
|
|
log::error "Unknown peer subcommand: '${subcmd}'"
|
|
cmd::group::help
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
function cmd::group::peer_add() {
|
|
local name="" peer="" type=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--peer) util::require_flag "--peer" "${2:-}" || return 1; peer="$2"; shift 2 ;;
|
|
--type) util::require_flag "--type" "${2:-}" || return 1; type="$2"; shift 2 ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
[[ -z "$peer" ]] && log::error "Missing required flag: --peer" && return 1
|
|
group::require_exists "$name" || return 1
|
|
|
|
peer=$(peers::resolve_and_require "$peer" "$type") || return 1
|
|
|
|
# Check if already in group
|
|
if group::peers "$name" | grep -qF "$peer"; then
|
|
log::wg_warning "'${peer}' is already in group '${name}'"
|
|
return 0
|
|
fi
|
|
|
|
group::add_peer "$name" "$peer"
|
|
log::wg_success "Added '${peer}' to group '${name}'"
|
|
}
|
|
|
|
function cmd::group::peer_remove() {
|
|
local name="" peer="" type=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--peer) util::require_flag "--peer" "${2:-}" || return 1; peer="$2"; shift 2 ;;
|
|
--type) util::require_flag "--type" "${2:-}" || return 1; type="$2"; shift 2 ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
[[ -z "$peer" ]] && log::error "Missing required flag: --peer" && return 1
|
|
group::require_exists "$name" || return 1
|
|
|
|
peer=$(peers::resolve_and_require "$peer" "$type") || return 1
|
|
group::remove_peer "$name" "$peer"
|
|
log::wg_success "Removed '${peer}' from group '${name}'"
|
|
}
|
|
|
|
# ============================================
|
|
# Remove peers from WireGuard
|
|
# ============================================
|
|
|
|
function cmd::group::rm_peers() {
|
|
local name="" force=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--force) force=true; shift ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
group::require_exists "$name" || return 1
|
|
|
|
local peers_list=()
|
|
mapfile -t peers_list < <(group::peers "$name")
|
|
local peer_count=${#peers_list[@]}
|
|
[[ -z "${peers_list[0]:-}" ]] && peer_count=0
|
|
|
|
if [[ "$peer_count" -eq 0 ]]; then
|
|
log::wg_warning "Group '${name}' has no peers"
|
|
return 0
|
|
fi
|
|
|
|
if ! $force; then
|
|
read -r -p "Remove all ${peer_count} peers in group '${name}' from WireGuard? [y/N] " confirm
|
|
case "$confirm" in
|
|
[yY][eE][sS]|[yY]) ;;
|
|
*) log::info "Aborted"; return 0 ;;
|
|
esac
|
|
fi
|
|
|
|
load_command remove
|
|
group::each_peer "$name" cmd::group::_rm_peer_cb
|
|
log::wg_success "Removed peers from group '${name}' (definition kept)"
|
|
}
|
|
|
|
function cmd::group::_rm_peer_cb() {
|
|
local peer_name="${1:-}"
|
|
if ! group::_peer_exists_check "$peer_name"; then
|
|
log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
|
|
return 0
|
|
fi
|
|
cmd::remove::run --name "$peer_name" --force
|
|
}
|
|
|
|
# function cmd::group::rm_peers() {
|
|
# local name="" force=false
|
|
|
|
# while [[ $# -gt 0 ]]; do
|
|
# case "$1" in
|
|
# --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
# --force) force=true; shift ;;
|
|
# --help) cmd::group::help; return ;;
|
|
# *) log::error "Unknown flag: $1"; return 1 ;;
|
|
# esac
|
|
# done
|
|
|
|
# [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
# group::require_exists "$name" || return 1
|
|
|
|
# local peers_list=()
|
|
# mapfile -t peers_list < <(group::peers "$name")
|
|
# local peer_count=${#peers_list[@]}
|
|
# [[ -z "${peers_list[0]}" ]] && peer_count=0
|
|
|
|
# if [[ "$peer_count" -eq 0 ]]; then
|
|
# log::wg_warning "Group '${name}' has no peers"
|
|
# return 0
|
|
# fi
|
|
|
|
# if ! $force; then
|
|
# read -r -p "Remove all ${peer_count} peers in group '${name}' from WireGuard? [y/N] " confirm
|
|
# case "$confirm" in
|
|
# [yY][eE][sS]|[yY]) ;;
|
|
# *) log::info "Aborted"; return 0 ;;
|
|
# esac
|
|
# fi
|
|
|
|
# local count=0
|
|
# for peer_name in "${peers_list[@]}"; do
|
|
# [[ -z "$peer_name" ]] && continue
|
|
|
|
# # Skip if peer no longer exists
|
|
# if ! peers::require_exists "$peer_name" > /dev/null 2>&1; then
|
|
# log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
|
|
# continue
|
|
# fi
|
|
|
|
# cmd::remove::run --name "$peer_name" --force
|
|
# (( count++ )) || true
|
|
# done
|
|
|
|
# log::wg_success "Removed ${count} peers from WireGuard (group '${name}' definition kept)"
|
|
# }
|
|
|
|
# ============================================
|
|
# Block / Unblock
|
|
# ============================================
|
|
|
|
function cmd::group::block() {
|
|
local name="" force=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--force) force=true; shift ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
group::require_exists "$name" || return 1
|
|
|
|
local peers_list=()
|
|
mapfile -t peers_list < <(group::peers "$name")
|
|
|
|
if [[ ${#peers_list[@]} -eq 0 ]] || [[ -z "${peers_list[0]:-}" ]]; then
|
|
log::wg_warning "Group '${name}' has no peers"
|
|
return 0
|
|
fi
|
|
|
|
# log::section "Blocking group: ${name}"
|
|
|
|
local count=0 blocked_names=()
|
|
for peer_name in "${peers_list[@]}"; do
|
|
[[ -z "$peer_name" ]] && continue
|
|
# Skip if peer no longer exists
|
|
if ! peers::require_exists "$peer_name" > /dev/null 2>&1; then
|
|
log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
|
|
continue
|
|
fi
|
|
|
|
if peers::is_blocked "$peer_name"; then
|
|
log::wg_warning "${peer_name} — already blocked"
|
|
continue
|
|
fi
|
|
( core::set_quiet; load_command block; cmd::block::run --name "$peer_name" --force )
|
|
blocked_names+=("$peer_name")
|
|
(( count++ )) || true
|
|
done
|
|
|
|
[[ "$count" -eq 0 ]] && return 0
|
|
|
|
# if [[ "$count" -gt 0 ]]; then
|
|
# printf "\n"
|
|
# for n in "${blocked_names[@]}"; do
|
|
# log::wg " Blocked: ${n}"
|
|
# done
|
|
# fi
|
|
|
|
# log::wg_block "Blocked ${count} peers in group '${name}'"
|
|
log::wg_block "All peers from ${name} have been blocked (${count} peers)."
|
|
}
|
|
|
|
function cmd::group::unblock() {
|
|
local name="" force=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--force) force=true; shift ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
group::require_exists "$name" || return 1
|
|
|
|
local peers_list=()
|
|
mapfile -t peers_list < <(group::peers "$name")
|
|
[[ -z "${peers_list[0]}" ]] && log::wg_warning "Group '${name}' has no peers" && return 0
|
|
|
|
# log::section "Unblocking group: ${name}"
|
|
|
|
local count=0 unblocked_names=()
|
|
for peer_name in "${peers_list[@]}"; do
|
|
[[ -z "$peer_name" ]] && continue
|
|
# Skip if peer no longer exists
|
|
if ! peers::require_exists "$peer_name" > /dev/null 2>&1; then
|
|
log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
|
|
continue
|
|
fi
|
|
|
|
if ! peers::is_blocked "$peer_name"; then
|
|
log::wg_warning "${peer_name} — not blocked"
|
|
continue
|
|
fi
|
|
( core::set_quiet; load_command unblock; cmd::unblock::run --name "$peer_name" --force )
|
|
unblocked_names+=("$peer_name")
|
|
(( count++ )) || true
|
|
done
|
|
|
|
[[ "$count" -eq 0 ]] && return 0
|
|
|
|
# if [[ "$count" -gt 0 ]]; then
|
|
# printf "\n"
|
|
# for n in "${blocked_names[@]}"; do
|
|
# log::wg " Unblocked: ${n}"
|
|
# done
|
|
# fi
|
|
|
|
log::wg_unblock "All peers from ${name} have been unblocked (${count} peers)."
|
|
}
|
|
|
|
# ============================================
|
|
# Rule assign
|
|
# ============================================
|
|
|
|
function cmd::group::rule() {
|
|
local subcmd="${1:-help}"
|
|
shift || true
|
|
case "$subcmd" in
|
|
assign) cmd::group::rule_assign "$@" ;;
|
|
*)
|
|
log::error "Unknown rule subcommand: '${subcmd}'"
|
|
cmd::group::help
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
function cmd::group::rule_assign() {
|
|
local name="" rule=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--rule) util::require_flag "--rule" "${2:-}" || return 1; rule="$2"; shift 2 ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
[[ -z "$rule" ]] && log::error "Missing required flag: --rule" && return 1
|
|
group::require_exists "$name" || return 1
|
|
rule::require_exists "$rule" || return 1
|
|
|
|
local peers_list=()
|
|
mapfile -t peers_list < <(group::peers "$name")
|
|
[[ -z "${peers_list[0]:-}" ]] && log::wg_warning "Group '${name}' has no peers" && return 0
|
|
|
|
group::each_peer "$name" cmd::group::_rule_assign_cb "$rule"
|
|
log::wg_success "Assigned rule '${rule}' to group '${name}'"
|
|
}
|
|
|
|
function cmd::group::_rule_assign_cb() {
|
|
local peer_name="${1:-}" rule="${2:-}"
|
|
if ! group::_peer_exists_check "$peer_name"; then
|
|
log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
|
|
return 0
|
|
fi
|
|
load_command rule
|
|
cmd::rule::assign --name "$rule" --peer "$peer_name"
|
|
}
|
|
|
|
# ============================================
|
|
# Audit
|
|
# ============================================
|
|
|
|
function cmd::group::audit() {
|
|
local name=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
group::require_exists "$name" || return 1
|
|
|
|
local peers_list=()
|
|
mapfile -t peers_list < <(group::peers "$name")
|
|
[[ -z "${peers_list[0]}" ]] && log::wg_warning "Group '${name}' has no peers" && return 0
|
|
|
|
# Run audit filtered to group peers
|
|
load_command audit
|
|
# Pass first peer as filter — audit handles the rest per-peer
|
|
# Actually run full audit and filter output
|
|
local peer_args=()
|
|
for peer_name in "${peers_list[@]}"; do
|
|
[[ -z "$peer_name" ]] && continue
|
|
peer_args+=("$peer_name")
|
|
done
|
|
|
|
test::reset
|
|
log::section "Audit: Group '${name}'"
|
|
|
|
# Precompute fw counts
|
|
declare -A peer_fw_counts
|
|
while IFS=":" read -r pname count; do
|
|
[[ -n "$pname" ]] && peer_fw_counts["$pname"]="$count"
|
|
done < <(json::audit_fw_counts "$(ctx::clients)")
|
|
|
|
test::section "Peer Rules"
|
|
for peer_name in "${peer_args[@]}"; do
|
|
# Skip if peer no longer exists
|
|
if ! peers::require_exists "$peer_name" > /dev/null 2>&1; then
|
|
log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
|
|
continue
|
|
fi
|
|
cmd::audit::check_peer "$peer_name" false "${peer_fw_counts[$peer_name]:-0}"
|
|
done
|
|
|
|
test::summary
|
|
}
|
|
|
|
# ============================================
|
|
# Logs
|
|
# ============================================
|
|
|
|
function cmd::group::logs() {
|
|
local name="" limit=50
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--limit) util::require_flag "--limit" "${2:-}" || return 1; limit="$2"; shift 2 ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
group::require_exists "$name" || return 1
|
|
|
|
local peers_list=()
|
|
mapfile -t peers_list < <(group::peers "$name")
|
|
[[ -z "${peers_list[0]}" ]] && log::wg_warning "Group '${name}' has no peers" && return 0
|
|
|
|
log::section "Logs: Group '${name}'"
|
|
|
|
for peer_name in "${peers_list[@]}"; do
|
|
[[ -z "$peer_name" ]] && continue
|
|
# Skip if peer no longer exists
|
|
if ! peers::require_exists "$peer_name" > /dev/null 2>&1; then
|
|
log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
|
|
continue
|
|
fi
|
|
printf "\n \033[1;37m── %s ──\033[0m\n" "$peer_name"
|
|
load_command logs
|
|
cmd::logs::show --name "$peer_name" --limit "$limit"
|
|
done
|
|
}
|
|
|
|
# ============================================
|
|
# Watch
|
|
# ============================================
|
|
|
|
function cmd::group::watch() {
|
|
local name=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
|
|
--help) cmd::group::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
|
group::require_exists "$name" || return 1
|
|
|
|
local peers_list=()
|
|
mapfile -t peers_list < <(group::peers "$name")
|
|
[[ -z "${peers_list[0]}" ]] && log::wg_warning "Group '${name}' has no peers" && return 0
|
|
|
|
local peer_filter
|
|
peer_filter=$(IFS=','; echo "${peers_list[*]}")
|
|
|
|
# Build comma-separated peer list for watch filter
|
|
# Watch already supports --type filter but not multiple peers
|
|
# For now, use follow mode filtered to group peers one at a time
|
|
# or just run watch with no filter (shows all, user sees group context)
|
|
log::section "Live Monitor: Group '${name}'"
|
|
printf " Monitoring: %s\n\n" "${peers_list[*]}"
|
|
|
|
load_command logs
|
|
# Use follow mode — it shows all peers but user knows context
|
|
# Future: add --peers flag to follow for multi-peer filter
|
|
cmd::logs::follow "" "" "" false false "$peer_filter"
|
|
}
|