396 lines
No EOL
11 KiB
Bash
396 lines
No EOL
11 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
function cmd::inspect::on_load() {
|
|
flag::register --name
|
|
flag::register --type
|
|
flag::register --config
|
|
flag::register --qr
|
|
}
|
|
|
|
function cmd::inspect::help() {
|
|
cat <<EOF
|
|
Usage: wgctl inspect --name <name> [options]
|
|
wgctl inspect <full-name>
|
|
|
|
Show detailed information for a WireGuard client including
|
|
status, rule with inheritance, groups, firewall rules, and activity.
|
|
|
|
Options:
|
|
--name <name> Client name (e.g. phone-nuno)
|
|
--type <type> Device type — combines with --name (e.g. --name nuno --type phone)
|
|
--config Also show raw WireGuard client config
|
|
--qr Also show QR code
|
|
|
|
Examples:
|
|
wgctl inspect --name phone-nuno
|
|
wgctl inspect --name nuno --type phone
|
|
wgctl inspect --name phone-nuno --config
|
|
wgctl inspect --name phone-nuno --qr
|
|
wgctl inspect guest-zephyr
|
|
EOF
|
|
}
|
|
|
|
# ============================================
|
|
# Private helpers
|
|
# ============================================
|
|
|
|
function cmd::inspect::_section() {
|
|
local title="$1"
|
|
printf "\n \033[0;37m── %s ──────────────────────────────────\033[0m\n" "$title"
|
|
}
|
|
|
|
function cmd::inspect::_peer_info() {
|
|
local name="${1:-}"
|
|
|
|
local ip type rule public_key allowed_ips
|
|
ip=$(peers::get_ip "$name")
|
|
type=$(peers::get_type "$name")
|
|
rule=$(peers::get_meta "$name" "rule")
|
|
public_key=$(keys::public "$name" 2>/dev/null || echo "")
|
|
allowed_ips=$(grep "^AllowedIPs" "$(ctx::clients)/${name}.conf" \
|
|
2>/dev/null | cut -d'=' -f2- | xargs)
|
|
|
|
# Status
|
|
local handshake_ts is_blocked last_ts
|
|
handshake_ts=$(monitor::get_handshake_ts "$public_key")
|
|
peers::is_blocked "$name" && is_blocked="true" || is_blocked="false"
|
|
last_ts=$(monitor::last_attempt "$name")
|
|
|
|
local status last_seen endpoint
|
|
status=$(peers::format_status "$name" "$public_key" \
|
|
"$is_blocked" "false" "$handshake_ts" "$last_ts")
|
|
last_seen=$(peers::format_last_seen "$name" "$public_key" \
|
|
"$is_blocked" "$last_ts" "" "$handshake_ts")
|
|
endpoint=$(monitor::get_cached_endpoint "$name")
|
|
|
|
local activity_total
|
|
activity_total=$(peers::format_activity_total "$public_key")
|
|
|
|
local activity_current
|
|
activity_current=$(peers::format_activity_current "$public_key")
|
|
|
|
local subtype
|
|
subtype=$(peers::get_meta "$name" "subtype")
|
|
|
|
local rule_extends=""
|
|
if [[ -n "$rule" ]]; then
|
|
local rule_file
|
|
rule_file="$(rule::path "$rule" 2>/dev/null)" || true
|
|
if [[ -n "$rule_file" ]]; then
|
|
local ext=()
|
|
mapfile -t ext < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
|
if [[ ${#ext[@]} -gt 0 && -n "${ext[0]:-}" ]]; then
|
|
rule_extends=" (↳ ${ext[*]})"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Rule formatting
|
|
local rule_display="${rule:-—}"
|
|
if [[ -n "$rule_file" && ${#ext[@]} -gt 0 && -n "${ext[0]:-}" ]]; then
|
|
local extends_str
|
|
extends_str=$(printf '%s, ' "${ext[@]}" | sed 's/, $//')
|
|
rule_display="${rule} ↳ (${extends_str})"
|
|
fi
|
|
|
|
cmd::inspect::_section "Client"
|
|
ui::row "Name" "$name"
|
|
ui::row "IP" "$ip"
|
|
ui::row "Type" "$(peers::display_type "$type" "$subtype")"
|
|
ui::row "Rule" "$rule_display"
|
|
ui::row "Status" "$(echo -e "$status")"
|
|
ui::row "Endpoint" "${endpoint:-—}"
|
|
ui::row "Last seen" "$last_seen"
|
|
ui::row "AllowedIPs" "$allowed_ips"
|
|
ui::row "Public key" "${public_key:-—}"
|
|
ui::row "Activity (total)" "$activity_total"
|
|
ui::row "Activity (current)" "$activity_current"
|
|
|
|
return 0
|
|
}
|
|
|
|
function cmd::inspect::_rule_info() {
|
|
local name="${1:-}"
|
|
local rule
|
|
rule=$(peers::get_meta "$name" "rule")
|
|
[[ -z "$rule" ]] && return 0
|
|
rule::exists "$rule" || return 0
|
|
|
|
cmd::inspect::_section "Rule: ${rule}"
|
|
|
|
local rule_file
|
|
rule_file="$(rule::path "$rule")"
|
|
|
|
# Check for inheritance
|
|
local extends_raw=()
|
|
mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
|
|
|
if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]:-}" ]]; then
|
|
# Show inheritance tree
|
|
for base_name in "${extends_raw[@]}"; do
|
|
[[ -z "$base_name" ]] && continue
|
|
printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
|
|
|
|
local base_allows base_blocks
|
|
base_allows=$(rule::get "$base_name" "allow_ports")$'\n'$(rule::get "$base_name" "allow_ips")
|
|
base_blocks=$(rule::get "$base_name" "block_ips")$'\n'$(rule::get "$base_name" "block_ports")
|
|
|
|
while IFS= read -r e; do
|
|
[[ -z "$e" ]] && continue
|
|
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
done <<< "$base_allows"
|
|
while IFS= read -r e; do
|
|
[[ -z "$e" ]] && continue
|
|
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
done <<< "$base_blocks"
|
|
|
|
local base_dns
|
|
base_dns=$(rule::get_own "$base_name" "dns_redirect")
|
|
[[ "${base_dns,,}" == "true" ]] && \
|
|
printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
|
done
|
|
|
|
# Own rules
|
|
local own_allows own_blocks
|
|
own_allows=$(json::get "$rule_file" "allow_ports" 2>/dev/null)$'\n'$(json::get "$rule_file" "allow_ips" 2>/dev/null)
|
|
own_blocks=$(json::get "$rule_file" "block_ips" 2>/dev/null)$'\n'$(json::get "$rule_file" "block_ports" 2>/dev/null)
|
|
local has_own=false
|
|
|
|
while IFS= read -r e; do [[ -n "$e" ]] && has_own=true && break; done <<< "$own_allows$own_blocks"
|
|
|
|
if $has_own; then
|
|
printf "\n \033[0;37mOwn:\033[0m\n"
|
|
while IFS= read -r e; do
|
|
[[ -z "$e" ]] && continue
|
|
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
done <<< "$own_allows"
|
|
while IFS= read -r e; do
|
|
[[ -z "$e" ]] && continue
|
|
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
done <<< "$own_blocks"
|
|
fi
|
|
|
|
else
|
|
# No inheritance — flat view
|
|
local allow_ports allow_ips block_ips block_ports
|
|
allow_ports=$(rule::get "$rule" "allow_ports")
|
|
allow_ips=$(rule::get "$rule" "allow_ips")
|
|
block_ips=$(rule::get "$rule" "block_ips")
|
|
block_ports=$(rule::get "$rule" "block_ports")
|
|
|
|
if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
|
|
printf "\n"
|
|
while IFS= read -r e; do
|
|
[[ -z "$e" ]] && continue
|
|
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
done <<< "$allow_ports"$'\n'"$allow_ips"
|
|
fi
|
|
|
|
if [[ -n "$block_ips" || -n "$block_ports" ]]; then
|
|
printf "\n"
|
|
while IFS= read -r e; do
|
|
[[ -z "$e" ]] && continue
|
|
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
done <<< "$block_ips"$'\n'"$block_ports"
|
|
fi
|
|
|
|
if [[ -z "$allow_ports" && -z "$allow_ips" && \
|
|
-z "$block_ips" && -z "$block_ports" ]]; then
|
|
printf "\n full access (no restrictions)\n"
|
|
fi
|
|
|
|
local dns_redirect
|
|
dns_redirect=$(rule::get_own "$rule" "dns_redirect")
|
|
[[ "${dns_redirect,,}" == "true" ]] && \
|
|
printf "\n \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
function cmd::inspect::_blocks_info() {
|
|
local name="${1:-}"
|
|
block::has_file "$name" || return 0
|
|
|
|
cmd::inspect::_section "Peer Blocks"
|
|
|
|
local blocked_direct
|
|
blocked_direct=$(block::is_blocked_direct "$name")
|
|
[[ "$blocked_direct" == "true" ]] && \
|
|
printf " \033[1;31m🚫\033[0m blocked directly\n"
|
|
|
|
local blocked_groups
|
|
blocked_groups=$(block::get_groups "$name")
|
|
[[ -n "$blocked_groups" ]] && \
|
|
printf " \033[1;31m🚫\033[0m blocked by groups: %s\n" "$blocked_groups"
|
|
|
|
block::format_rules "$name"
|
|
|
|
return 0
|
|
}
|
|
|
|
# function cmd::inspect::_rule_info() {
|
|
# local name="$1"
|
|
# local rule
|
|
# rule=$(peers::get_meta "$name" "rule")
|
|
# [[ -z "$rule" ]] && return 0
|
|
|
|
# rule::exists "$rule" || return 0
|
|
|
|
# local rule_file
|
|
# rule_file="$(ctx::rule::path "${rule}.rule")"
|
|
|
|
# ui::section "Rule: ${rule}"
|
|
|
|
# local desc dns_redirect
|
|
# desc=$(json::get "$rule_file" "desc")
|
|
# dns_redirect=$(json::get "$rule_file" "dns_redirect")
|
|
# ui::row "Description" "${desc:-—}"
|
|
# ui::row "DNS Redirect" "${dns_redirect:-false}"
|
|
|
|
# local allow_ports allow_ips block_ips block_ports
|
|
# allow_ports=$(json::get "$rule_file" "allow_ports")
|
|
# allow_ips=$(json::get "$rule_file" "allow_ips")
|
|
# block_ips=$(json::get "$rule_file" "block_ips")
|
|
# block_ports=$(json::get "$rule_file" "block_ports")
|
|
|
|
# if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
|
|
# printf " %-20s\n" "Allows:"
|
|
# ui::print_list "+" "$allow_ports"
|
|
# ui::print_list "+" "$allow_ips"
|
|
# else
|
|
# ui::row "Allows" "—"
|
|
# fi
|
|
|
|
# if [[ -n "$block_ips" || -n "$block_ports" ]]; then
|
|
# printf " %-20s\n" "Blocks:"
|
|
# ui::print_list "-" "$block_ips"
|
|
# ui::print_list "-" "$block_ports"
|
|
# else
|
|
# ui::row "Blocks" "—"
|
|
# fi
|
|
# }
|
|
|
|
function cmd::inspect::_group_info() {
|
|
local name="$1"
|
|
|
|
ui::section "Groups"
|
|
|
|
local groups=()
|
|
mapfile -t groups < <(json::peer_groups "$(ctx::groups)" "$name")
|
|
|
|
if [[ ${#groups[@]} -eq 0 ]] || [[ -z "${groups[0]:-}" ]]; then
|
|
printf " —\n"
|
|
return 0
|
|
fi
|
|
|
|
for g in "${groups[@]}"; do
|
|
[[ -z "$g" ]] && continue
|
|
local count
|
|
count=$(json::count "$(group::path "$g")" "peers")
|
|
printf " %-20s %s peers\n" "$g" "$count"
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
function cmd::inspect::_firewall_info() {
|
|
local name="${1:-}"
|
|
local ip
|
|
ip=$(peers::get_ip "$name")
|
|
|
|
local total=0 accepts=0 drops=0
|
|
local rules_output=()
|
|
while IFS= read -r line; do
|
|
[[ -z "$line" ]] && continue
|
|
(( total++ )) || true
|
|
[[ "$line" =~ ACCEPT ]] && (( accepts++ )) || true
|
|
[[ "$line" =~ DROP ]] && (( drops++ )) || true
|
|
rules_output+=("$line")
|
|
done < <(fw::forward_rules_for_ip "$ip" | grep -v NFLOG)
|
|
|
|
# printf "\n \033[0;37m── Firewall (\033[0;32m+%d\033[0m \033[0;31m-%d\033[0m) \033[0m%s\n" \
|
|
# "$accepts" "$drops" "$(printf '─%.0s' {1..28})"
|
|
|
|
printf "\n \033[0;37m── Firewall (%s %s) \033[0m%s\n\n" \
|
|
"$(color::green "+${accepts}")" \
|
|
"$(color::red "-${drops}")" \
|
|
"$(printf '─%.0s' {1..28})"
|
|
|
|
if [[ ${#rules_output[@]} -gt 0 ]]; then
|
|
for line in "${rules_output[@]}"; do
|
|
fw::format_rule "$line"
|
|
done
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
function cmd::inspect::_config() {
|
|
local name="$1"
|
|
cmd::inspect::_section "Config"
|
|
printf "\n"
|
|
cat "$(ctx::clients)/${name}.conf"
|
|
printf "\n"
|
|
|
|
return 0
|
|
}
|
|
|
|
# ============================================
|
|
# Run
|
|
# ============================================
|
|
|
|
function cmd::inspect::run() {
|
|
local name="" type="" show_config=false show_qr=false
|
|
|
|
if [[ $# -gt 0 && "$1" != "--"* ]]; then
|
|
name="$1"
|
|
shift
|
|
fi
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--type) type="$2"; shift 2 ;;
|
|
--config) show_config=true; shift ;;
|
|
--qr) show_qr=true; shift ;;
|
|
--help) cmd::inspect::help; return ;;
|
|
*)
|
|
log::error "Unknown flag: $1"
|
|
cmd::inspect::help
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$name" ]]; then
|
|
log::error "Missing required flag: --name"
|
|
cmd::inspect::help
|
|
return 1
|
|
fi
|
|
|
|
name=$(peers::resolve_and_require "$name" "$type") || return 1
|
|
|
|
load_command list
|
|
|
|
log::section "Inspect: ${name}"
|
|
|
|
cmd::inspect::_peer_info "$name"
|
|
cmd::inspect::_rule_info "$name"
|
|
cmd::inspect::_group_info "$name"
|
|
cmd::inspect::_firewall_info "$name"
|
|
cmd::inspect::_blocks_info "$name"
|
|
|
|
if $show_config; then
|
|
cmd::inspect::_config "$name"
|
|
fi
|
|
|
|
if $show_qr; then
|
|
cmd::inspect::_section "QR Code"
|
|
printf "\n"
|
|
load_command qr
|
|
cmd::qr::run --name "$name"
|
|
fi
|
|
|
|
printf "\n"
|
|
} |