feat: main group display, group::has_peer, config validation, full block cleanup on unblock, ui::empty helper, blocks header count
This commit is contained in:
parent
87f6c770ef
commit
7323bf20f1
14 changed files with 536 additions and 197 deletions
|
|
@ -94,7 +94,7 @@ function cmd::block::run() {
|
|||
# Full block if no specific targets
|
||||
if [[ ${#ips[@]} -eq 0 && ${#ports[@]} -eq 0 && \
|
||||
${#subnets[@]} -eq 0 && ${#services[@]} -eq 0 ]]; then
|
||||
if peers::is_blocked "$name" || block::has_file "$name"; then
|
||||
if peers::is_blocked "$name"; then
|
||||
log::wg_warning "Client is already blocked: ${name}"
|
||||
return 0
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ function cmd::group::on_load() {
|
|||
flag::register --type
|
||||
flag::register --rule
|
||||
flag::register --new-name
|
||||
flag::register --main
|
||||
flag::register --force
|
||||
}
|
||||
|
||||
|
|
@ -83,6 +84,7 @@ function cmd::group::run() {
|
|||
rename) cmd::group::rename "$@" ;;
|
||||
peer) cmd::group::peer "$@" ;;
|
||||
rm-peers) cmd::group::rm_peers "$@" ;;
|
||||
set-main) cmd::group::set_main "$@" ;;
|
||||
block) cmd::group::block "$@" ;;
|
||||
unblock) cmd::group::unblock "$@" ;;
|
||||
rule) cmd::group::rule "$@" ;;
|
||||
|
|
@ -343,13 +345,14 @@ function cmd::group::peer() {
|
|||
}
|
||||
|
||||
function cmd::group::peer_add() {
|
||||
local name="" peer="" type=""
|
||||
local name="" peer="" type="" set_main=false
|
||||
|
||||
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 ;;
|
||||
--main) util::require_flag "--main" "${2:-}" || return 1; set_main=true; shift ;;
|
||||
--help) cmd::group::help; return ;;
|
||||
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||
esac
|
||||
|
|
@ -369,6 +372,11 @@ function cmd::group::peer_add() {
|
|||
|
||||
group::add_peer "$name" "$peer"
|
||||
log::wg_success "Added '${peer}' to group '${name}'"
|
||||
|
||||
if $set_main; then
|
||||
peers::set_main_group "$peer_name" "$group_name"
|
||||
log::wg_success "Set '${group_name}' as main group for ${peer_name}"
|
||||
fi
|
||||
}
|
||||
|
||||
function cmd::group::peer_remove() {
|
||||
|
|
@ -393,6 +401,34 @@ function cmd::group::peer_remove() {
|
|||
log::wg_success "Removed '${peer}' from group '${name}'"
|
||||
}
|
||||
|
||||
function cmd::group::set_main() {
|
||||
local group_name="" peer_name="" type=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--name) group_name="$2"; shift 2 ;;
|
||||
--peer) peer_name="$2"; shift 2 ;;
|
||||
--type) type="$2"; shift 2 ;;
|
||||
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$group_name" ]] && log::error "Missing --name" && return 1
|
||||
[[ -z "$peer_name" ]] && log::error "Missing --peer" && return 1
|
||||
|
||||
# Resolve peer name
|
||||
peer_name=$(peers::resolve_and_require "$peer_name" "$type") || return 1
|
||||
|
||||
# Verify peer is in the group
|
||||
if ! group::has_peer "$group_name" "$peer_name"; then
|
||||
log::error "Peer '${peer_name}' is not in group '${group_name}'"
|
||||
log::info "Add them first: wgctl group peer add --name ${group_name} --peer ${peer_name}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
peers::set_main_group "$peer_name" "$group_name"
|
||||
log::wg_success "Main group for '${peer_name}' set to '${group_name}'"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Remove peers from WireGuard
|
||||
# ============================================
|
||||
|
|
@ -444,56 +480,6 @@ function cmd::group::_rm_peer_cb() {
|
|||
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
|
||||
# ============================================
|
||||
|
|
|
|||
|
|
@ -36,13 +36,24 @@ Examples:
|
|||
EOF
|
||||
}
|
||||
|
||||
INSPECT_WIDTH=48 # total visible width of section lines
|
||||
INSPECT_LABEL_WIDTH=20
|
||||
|
||||
# ============================================
|
||||
# Private helpers
|
||||
# ============================================
|
||||
|
||||
|
||||
function cmd::inspect::_section() {
|
||||
local title="$1"
|
||||
printf "\n \033[0;37m── %s ──────────────────────────────────\033[0m\n" "$title"
|
||||
local title="${1:-}" extra="${2:-0}"
|
||||
local width=$(( INSPECT_WIDTH + extra ))
|
||||
local title_len=${#title}
|
||||
# Account for "── " (3) + " " (1) before dashes
|
||||
local dash_count=$(( width - title_len - 4 ))
|
||||
[[ $dash_count -lt 2 ]] && dash_count=2
|
||||
local dashes
|
||||
dashes=$(printf '─%.0s' $(seq 1 $dash_count))
|
||||
printf "\n \033[0;37m── %s %s\033[0m\n" "$title" "$dashes"
|
||||
}
|
||||
|
||||
function cmd::inspect::_peer_info() {
|
||||
|
|
@ -66,7 +77,7 @@ function cmd::inspect::_peer_info() {
|
|||
block::has_specific_rules "$name" 2>/dev/null && is_restricted="true"
|
||||
|
||||
local status last_seen endpoint
|
||||
status=$(peers::format_status "$name" "$public_key" \
|
||||
status=$(peers::format_status_verbose "$name" "$public_key" \
|
||||
"$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts")
|
||||
last_seen=$(peers::format_last_seen "$name" "$public_key" \
|
||||
"$is_blocked" "$last_ts" "" "$handshake_ts")
|
||||
|
|
@ -103,17 +114,18 @@ function cmd::inspect::_peer_info() {
|
|||
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"
|
||||
printf "\n"
|
||||
ui::row "Name" "$name" "${INSPECT_LABEL_WIDTH}"
|
||||
ui::row "IP" "$ip" "${INSPECT_LABEL_WIDTH}"
|
||||
ui::row "Type" "$(peers::display_type "$type" "$subtype")" "${INSPECT_LABEL_WIDTH}"
|
||||
ui::row "Rule" "$rule_display" "${INSPECT_LABEL_WIDTH}"
|
||||
ui::row "Status" "$(echo -e "$status")" "${INSPECT_LABEL_WIDTH}"
|
||||
ui::row "Endpoint" "${endpoint:-—}" "${INSPECT_LABEL_WIDTH}"
|
||||
ui::row "Last seen" "$last_seen" "${INSPECT_LABEL_WIDTH}"
|
||||
ui::row "AllowedIPs" "$allowed_ips" "${INSPECT_LABEL_WIDTH}"
|
||||
ui::row "Public key" "${public_key:-—}" "${INSPECT_LABEL_WIDTH}"
|
||||
ui::row "Activity (total)" "$activity_total" "${INSPECT_LABEL_WIDTH}"
|
||||
ui::row "Activity (current)" "$activity_current" "${INSPECT_LABEL_WIDTH}"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
@ -128,7 +140,8 @@ function cmd::inspect::_rule_info() {
|
|||
cmd::inspect::_section "Rule: ${rule}"
|
||||
|
||||
if rule::render_extends_tree "$rule"; then
|
||||
printf "\n"
|
||||
# printf "\n"
|
||||
: # no-op
|
||||
else
|
||||
# No inheritance — flat view
|
||||
rule::render_flat "$rule"
|
||||
|
|
@ -140,20 +153,44 @@ function cmd::inspect::_blocks_info() {
|
|||
local name="${1:-}"
|
||||
block::has_file "$name" || return 0
|
||||
|
||||
cmd::inspect::_section "Peer Blocks"
|
||||
|
||||
local blocked_direct
|
||||
local blocked_direct
|
||||
blocked_direct=$(block::is_blocked_direct "$name")
|
||||
|
||||
local blocked_groups
|
||||
blocked_groups=$(block::get_groups "$name")
|
||||
|
||||
local rules_output
|
||||
rules_output=$(block::get_rules "$name")
|
||||
|
||||
# Skip if truly empty
|
||||
if [[ "$blocked_direct" != "true" ]] && \
|
||||
ui::empty "$blocked_groups" && \
|
||||
ui::empty "$rules_output"; then
|
||||
block::cleanup "$name" # clean up stale empty file
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Count rules for header
|
||||
local rule_count=0
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] && (( rule_count++ )) || true
|
||||
done <<< "$rules_output"
|
||||
|
||||
# Build header like firewall: Blocks (+N)
|
||||
local header_counts=""
|
||||
[[ "$rule_count" -gt 0 ]] && header_counts=" (${rule_count})"
|
||||
[[ "$blocked_direct" == "true" || -n "$blocked_groups" ]] && \
|
||||
header_counts="${header_counts} 🚫"
|
||||
|
||||
cmd::inspect::_section "Blocks${header_counts}"
|
||||
printf "\n"
|
||||
|
||||
[[ "$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
|
||||
}
|
||||
|
||||
|
|
@ -162,20 +199,22 @@ function cmd::inspect::_group_info() {
|
|||
|
||||
local groups=()
|
||||
mapfile -t groups < <(json::peer_groups "$(ctx::groups)" "$name")
|
||||
[[ ${#groups[@]} -eq 0 || -z "${groups[0]:-}" ]] && return 0
|
||||
|
||||
ui::section "Groups"
|
||||
ui::empty "${groups[*]}" && return 0
|
||||
|
||||
if [[ ${#groups[@]} -eq 0 ]] || [[ -z "${groups[0]:-}" ]]; then
|
||||
printf " —\n"
|
||||
return 0
|
||||
fi
|
||||
local count=${#groups[@]}
|
||||
cmd::inspect::_section "Groups (${count})"
|
||||
printf "\n"
|
||||
|
||||
for g in "${groups[@]}"; do
|
||||
[[ -z "$g" ]] && continue
|
||||
local count
|
||||
count=$(json::count "$(group::path "$g")" "peers")
|
||||
printf " %-20s %s peers\n" "$g" "$count"
|
||||
local peer_count
|
||||
local main_marker=""
|
||||
peer_count=$(json::count "$(group::path "$g")" "peers")
|
||||
[[ "$g" == "$(peers::get_main_group "$name")" ]] && \
|
||||
main_marker=" \033[0;33m★\033[0m"
|
||||
printf " \033[0;37m·\033[0m %-20s \033[0;37m%s peers\033[0m%b\n" \
|
||||
"$g" "$peer_count" "$main_marker"
|
||||
done
|
||||
|
||||
return 0
|
||||
|
|
@ -196,24 +235,15 @@ function cmd::inspect::_firewall_info() {
|
|||
rules_output+=("$line")
|
||||
done < <(fw::forward_rules_for_ip "$ip" | grep -v NFLOG)
|
||||
|
||||
[[ ${#rules_output[@]} -eq 0 || -z "${rules_output[0]:-}" ]] && return 0
|
||||
|
||||
# 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})"
|
||||
ui::empty "${rules_output[*]}" && return 0
|
||||
|
||||
printf "\n \033[0;37m── Firewall (%s %s) \033[0m%s\n\n" \
|
||||
"$(color::green "+${accepts}")" \
|
||||
"$(color::red "-${drops}")" \
|
||||
"$(printf '─%.0s' {1..28})"
|
||||
"$(printf '\033[0;37m─%.0s' {1..28})"
|
||||
|
||||
fw::list_peer_rules "$ip" false
|
||||
|
||||
# if [[ ${#rules_output[@]} -gt 0 ]]; then
|
||||
# for line in "${rules_output[@]}"; do
|
||||
# fw::format_rule "$line"
|
||||
# done
|
||||
# fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -289,6 +289,76 @@ function cmd::list::_iter_confs() {
|
|||
done
|
||||
}
|
||||
|
||||
# function cmd::list::_render_row() {
|
||||
# local client_name="$1" ip="$2" type="$3"
|
||||
|
||||
# local pubkey="${p_pubkeys[$client_name]:-}"
|
||||
# local handshake_ts="${wg_handshakes[$pubkey]:-0}"
|
||||
# local is_blocked="${p_blocked[$client_name]:-false}"
|
||||
# local is_restricted="${p_restricted[$client_name]:-false}"
|
||||
# local last_ts="${p_last_ts[$client_name]:-}"
|
||||
|
||||
# # Apply status filters
|
||||
# if $online_only; then peers::is_online "$client_name" "$handshake_ts" "$last_ts" || return 0; fi
|
||||
# if $offline_only; then peers::is_offline "$client_name" "$handshake_ts" "$last_ts" || return 0; fi
|
||||
# if $restricted_only && [[ "$is_restricted" != "true" ]]; then return 0; fi
|
||||
# if $blocked_only && [[ "$is_blocked" != "true" ]]; then return 0; fi
|
||||
# if $allowed_only && { [[ "$is_blocked" == "true" ]] || \
|
||||
# [[ "$is_restricted" == "true" ]]; }; then return 0; fi
|
||||
|
||||
# if [[ -n "$filter_group" ]]; then
|
||||
# local peer_group="${peer_group_map[$client_name]:-}"
|
||||
# [[ "$peer_group" != "$filter_group" ]] && return 0
|
||||
# fi
|
||||
|
||||
# # Format display values
|
||||
# local status last_seen display_type rule group_display
|
||||
# status=$(peers::format_status "$client_name" "$pubkey" \
|
||||
# "$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts")
|
||||
# last_seen=$(peers::format_last_seen "$client_name" "$pubkey" \
|
||||
# "$is_blocked" "$last_ts" "" "$handshake_ts")
|
||||
# display_type=$(peers::display_type "$type" "${p_subtypes[$client_name]:-}")
|
||||
# rule="${p_rules[$client_name]:-—}"
|
||||
|
||||
# if [[ -n "$filter_rule" && "$rule" != "$filter_rule" ]]; then return 0; fi
|
||||
|
||||
# # Print header on first match
|
||||
# if [[ "${_list_header_printed:-false}" == "false" ]]; then
|
||||
# log::section "WireGuard Clients"
|
||||
# cmd::list::_render_header $has_groups
|
||||
# _list_header_printed=true
|
||||
# fi
|
||||
|
||||
# # Update rule counts for summary (outer scope array)
|
||||
# rule_counts["$rule"]=$(( ${rule_counts[$rule]:-0} + 1 )) || true
|
||||
|
||||
# # Pad status
|
||||
# local padded_status
|
||||
# padded_status=$(ui::pad_status "$status" 25)
|
||||
|
||||
# # Render row
|
||||
# if $has_groups; then
|
||||
# group_display="${peer_group_map[$client_name]:-—}"
|
||||
|
||||
# if [[ -n "${peer_group_map[$client_name]:-}" ]]; then
|
||||
# group_counts["$group_display"]=$(( ${group_counts[$group_display]:-0} + 1 )) || true
|
||||
# fi
|
||||
|
||||
# local rule_col_width=12 group_col_width=12
|
||||
# [[ "$rule" == "—" ]] && rule_col_width=14
|
||||
# [[ "$group_display" == "—" ]] && group_col_width=14
|
||||
# printf " %-28s %-15s %-13s %-${rule_col_width}s %-${group_col_width}s %s %s\n" \
|
||||
# "$client_name" "$ip" "$display_type" "$rule" \
|
||||
# "$group_display" "$padded_status" "$last_seen"
|
||||
# else
|
||||
# local rule_col_width=12
|
||||
# [[ "$rule" == "—" ]] && rule_col_width=14
|
||||
# printf " %-28s %-15s %-13s %-${rule_col_width}s %s %s\n" \
|
||||
# "$client_name" "$ip" "$display_type" "$rule" \
|
||||
# "$padded_status" "$last_seen"
|
||||
# fi
|
||||
# }
|
||||
|
||||
function cmd::list::_render_row() {
|
||||
local client_name="$1" ip="$2" type="$3"
|
||||
|
||||
|
|
@ -307,8 +377,8 @@ function cmd::list::_render_row() {
|
|||
[[ "$is_restricted" == "true" ]]; }; then return 0; fi
|
||||
|
||||
if [[ -n "$filter_group" ]]; then
|
||||
local peer_group="${peer_group_map[$client_name]:-}"
|
||||
[[ "$peer_group" != "$filter_group" ]] && return 0
|
||||
local all_groups="${peer_group_map[$client_name]:-}"
|
||||
[[ "$all_groups" != *"$filter_group"* ]] && return 0
|
||||
fi
|
||||
|
||||
# Format display values
|
||||
|
|
@ -329,23 +399,26 @@ function cmd::list::_render_row() {
|
|||
_list_header_printed=true
|
||||
fi
|
||||
|
||||
# Update rule counts for summary (outer scope array)
|
||||
rule_counts["$rule"]=$(( ${rule_counts[$rule]:-0} + 1 )) || true
|
||||
|
||||
# Pad status
|
||||
local padded_status
|
||||
padded_status=$(ui::pad_status "$status" 25)
|
||||
|
||||
# Render row
|
||||
if $has_groups; then
|
||||
group_display="${peer_group_map[$client_name]:-—}"
|
||||
# Use main group for display, fall back to first group, then —
|
||||
local main_group="${p_main_groups[$client_name]:-}"
|
||||
if [[ -n "$main_group" ]]; then
|
||||
group_display="$main_group"
|
||||
else
|
||||
group_display="${peer_group_map[$client_name]:-—}"
|
||||
fi
|
||||
|
||||
if [[ -n "${peer_group_map[$client_name]:-}" ]]; then
|
||||
group_counts["$group_display"]=$(( ${group_counts[$group_display]:-0} + 1 )) || true
|
||||
fi
|
||||
|
||||
local rule_col_width=12 group_col_width=12
|
||||
[[ "$rule" == "—" ]] && rule_col_width=14
|
||||
[[ "$rule" == "—" ]] && rule_col_width=14
|
||||
[[ "$group_display" == "—" ]] && group_col_width=14
|
||||
printf " %-28s %-15s %-13s %-${rule_col_width}s %-${group_col_width}s %s %s\n" \
|
||||
"$client_name" "$ip" "$display_type" "$rule" \
|
||||
|
|
@ -365,14 +438,15 @@ function cmd::list::_render_row() {
|
|||
|
||||
function cmd::list::_precompute_all() {
|
||||
# Peer data
|
||||
declare -gA p_ips=() p_rules=() p_subtypes=() p_last_ts=() p_last_evt=()
|
||||
while IFS="|" read -r name ip rule subtype last_ts last_evt; do
|
||||
declare -gA p_ips=() p_rules=() p_subtypes=() p_last_ts=() p_last_evt=() p_main_groups=()
|
||||
while IFS="|" read -r name ip rule subtype last_ts last_evt main_group; do
|
||||
[[ -z "$name" ]] && continue
|
||||
p_ips["$name"]="$ip"
|
||||
p_rules["$name"]="${rule:-—}"
|
||||
p_subtypes["$name"]="$subtype"
|
||||
p_last_ts["$name"]="$last_ts"
|
||||
p_last_evt["$name"]="$last_evt"
|
||||
p_main_groups["$name"]="${main_group:-}"
|
||||
done < <(json::peer_data "$(ctx::clients)" "$(ctx::meta)" "$(ctx::events_log)")
|
||||
|
||||
# WireGuard handshakes + endpoints
|
||||
|
|
@ -399,7 +473,7 @@ function cmd::list::_precompute_all() {
|
|||
p_pubkeys["$kname"]=$(cat "$kf" 2>/dev/null || echo "")
|
||||
done
|
||||
|
||||
# Groups
|
||||
# Groups + main group
|
||||
has_groups=false
|
||||
declare -gA peer_group_map=()
|
||||
local groups_dir
|
||||
|
|
@ -422,6 +496,65 @@ function cmd::list::_precompute_all() {
|
|||
done < <(json::peer_transfer "$(config::interface)")
|
||||
}
|
||||
|
||||
# function cmd::list::_precompute_all() {
|
||||
# # Peer data
|
||||
# declare -gA p_ips=() p_rules=() p_subtypes=() p_last_ts=() p_last_evt=()
|
||||
# while IFS="|" read -r name ip rule subtype last_ts last_evt; do
|
||||
# [[ -z "$name" ]] && continue
|
||||
# p_ips["$name"]="$ip"
|
||||
# p_rules["$name"]="${rule:-—}"
|
||||
# p_subtypes["$name"]="$subtype"
|
||||
# p_last_ts["$name"]="$last_ts"
|
||||
# p_last_evt["$name"]="$last_evt"
|
||||
# done < <(json::peer_data "$(ctx::clients)" "$(ctx::meta)" "$(ctx::events_log)")
|
||||
|
||||
# # WireGuard handshakes + endpoints
|
||||
# declare -gA wg_handshakes=() wg_endpoints=()
|
||||
# while IFS=$'\t' read -r pubkey ts; do
|
||||
# [[ -n "$pubkey" ]] && wg_handshakes["$pubkey"]="$ts"
|
||||
# done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null)
|
||||
# while IFS=$'\t' read -r pubkey endpoint; do
|
||||
# [[ -n "$pubkey" ]] && wg_endpoints["$pubkey"]="$endpoint"
|
||||
# done < <(wg show "$(config::interface)" endpoints 2>/dev/null)
|
||||
|
||||
# # Block/restricted status
|
||||
# declare -gA p_blocked=() p_restricted=()
|
||||
# cmd::list::_precompute_block_status p_blocked p_restricted
|
||||
|
||||
# # Public keys
|
||||
# declare -gA p_pubkeys=()
|
||||
# local dir
|
||||
# dir="$(ctx::clients)"
|
||||
# for kf in "${dir}"/*_public.key; do
|
||||
# [[ -f "$kf" ]] || continue
|
||||
# local kname
|
||||
# kname=$(basename "$kf" _public.key)
|
||||
# p_pubkeys["$kname"]=$(cat "$kf" 2>/dev/null || echo "")
|
||||
# done
|
||||
|
||||
# # Groups
|
||||
# has_groups=false
|
||||
# declare -gA peer_group_map=()
|
||||
# local groups_dir
|
||||
# groups_dir="$(ctx::groups)"
|
||||
# local group_files=("${groups_dir}"/*.group)
|
||||
# if [[ -f "${group_files[0]}" ]]; then
|
||||
# has_groups=true
|
||||
# while IFS=":" read -r peer_name group_name; do
|
||||
# [[ -n "$peer_name" ]] && peer_group_map["$peer_name"]="$group_name"
|
||||
# done < <(json::peer_group_map "$groups_dir")
|
||||
# fi
|
||||
|
||||
# # Transfer/activity data — keyed by pubkey
|
||||
# declare -gA p_rx=() p_tx=() p_activity=()
|
||||
# while IFS="|" read -r pubkey rx tx level; do
|
||||
# [[ -z "$pubkey" ]] && continue
|
||||
# p_rx["$pubkey"]="$rx"
|
||||
# p_tx["$pubkey"]="$tx"
|
||||
# p_activity["$pubkey"]="$level"
|
||||
# done < <(json::peer_transfer "$(config::interface)")
|
||||
# }
|
||||
|
||||
function cmd::list::_precompute_block_status() {
|
||||
local -n _blocked="$1"
|
||||
local -n _restricted="$2"
|
||||
|
|
|
|||
|
|
@ -191,11 +191,10 @@ function cmd::unblock::_unblock_all() {
|
|||
|
||||
# Direct unblock overrides everything — clear all block state
|
||||
block::set_direct "$name" "$client_ip" "false"
|
||||
block::clear_full_block "$name"
|
||||
|
||||
# Force full unblock regardless of group blocks
|
||||
# (direct unblock = admin override)
|
||||
block::restore_peer "$name" "$client_ip"
|
||||
block::remove_file "$name"
|
||||
block::cleanup "$name"
|
||||
|
||||
local rule
|
||||
rule=$(peers::get_meta "$name" "rule")
|
||||
|
|
|
|||
105
core/json.sh
105
core/json.sh
|
|
@ -2,63 +2,64 @@
|
|||
|
||||
JSON_HELPER="${_CTX_ROOT}/core/json_helper.py"
|
||||
|
||||
function json::get() { python3 "$JSON_HELPER" get "$@" </dev/null; }
|
||||
function json::set() { python3 "$JSON_HELPER" set "$@" </dev/null; }
|
||||
function json::delete() { python3 "$JSON_HELPER" delete "$@" </dev/null; }
|
||||
function json::append() { python3 "$JSON_HELPER" append "$@" </dev/null; }
|
||||
function json::remove() { python3 "$JSON_HELPER" remove "$@" </dev/null; }
|
||||
function json::cat() { python3 "$JSON_HELPER" cat "$@" </dev/null; }
|
||||
function json::has_key() { python3 "$JSON_HELPER" has_key "$@" </dev/null; }
|
||||
function json::filter_values() { python3 "$JSON_HELPER" filter_values "$@" </dev/null; }
|
||||
function json::last_event() { python3 "$JSON_HELPER" last_event "$@" </dev/null; }
|
||||
function json::events_for() { python3 "$JSON_HELPER" events_for "$@" </dev/null; }
|
||||
function json::fw_events() { WGCTL_DATETIME_FMT="$FMT_DATETIME" python3 "$JSON_HELPER" fw_events "$@" </dev/null; }
|
||||
function json::wg_events() { WGCTL_DATETIME_FMT="$FMT_DATETIME" python3 "$JSON_HELPER" wg_events "$@" </dev/null; }
|
||||
function json::get() { python3 "$JSON_HELPER" get "$@" </dev/null; }
|
||||
function json::set() { python3 "$JSON_HELPER" set "$@" </dev/null; }
|
||||
function json::delete() { python3 "$JSON_HELPER" delete "$@" </dev/null; }
|
||||
function json::append() { python3 "$JSON_HELPER" append "$@" </dev/null; }
|
||||
function json::remove() { python3 "$JSON_HELPER" remove "$@" </dev/null; }
|
||||
function json::cat() { python3 "$JSON_HELPER" cat "$@" </dev/null; }
|
||||
function json::has_key() { python3 "$JSON_HELPER" has_key "$@" </dev/null; }
|
||||
function json::filter_values() { python3 "$JSON_HELPER" filter_values "$@" </dev/null; }
|
||||
function json::last_event() { python3 "$JSON_HELPER" last_event "$@" </dev/null; }
|
||||
function json::events_for() { python3 "$JSON_HELPER" events_for "$@" </dev/null; }
|
||||
function json::fw_events() { WGCTL_DATETIME_FMT="$FMT_DATETIME" python3 "$JSON_HELPER" fw_events "$@" </dev/null; }
|
||||
function json::wg_events() { WGCTL_DATETIME_FMT="$FMT_DATETIME" python3 "$JSON_HELPER" wg_events "$@" </dev/null; }
|
||||
function json::format_fw_event() { echo "$1" | python3 "$JSON_HELPER" format_fw_event "$2"; }
|
||||
function json::format_wg_event() { echo "$1" | python3 "$JSON_HELPER" format_wg_event; }
|
||||
function json::remove_events() { python3 "$JSON_HELPER" remove_events "$@" </dev/null; }
|
||||
function json::remove_events() { python3 "$JSON_HELPER" remove_events "$@" </dev/null; }
|
||||
function json::follow_logs() { WGCTL_DATETIME_FMT="$FMT_DATETIME" python3 "$JSON_HELPER" follow_logs "$@"; }
|
||||
function json::count() { python3 "$JSON_HELPER" count "$@" </dev/null; }
|
||||
function json::audit_fw_counts() { python3 "$JSON_HELPER" audit_fw_counts "$@" </dev/null; }
|
||||
function json::peer_group_map() { python3 "$JSON_HELPER" peer_group_map "$@" </dev/null; }
|
||||
function json::peer_groups() { python3 "$JSON_HELPER" peer_groups "$@" </dev/null; }
|
||||
function json::peer_data() { WGCTL_DATETIME_FMT="$FMT_DATETIME" python3 "$JSON_HELPER" peer_data "$@" </dev/null; }
|
||||
function json::iso_to_ts() { python3 "$JSON_HELPER" iso_to_ts "$@" </dev/null; }
|
||||
function json::rule_list_data() { python3 "$JSON_HELPER" rule_list_data "$@" </dev/null; }
|
||||
function json::group_list_data() { python3 "$JSON_HELPER" group_list_data "$@" </dev/null; }
|
||||
function json::fmt_datetime() { python3 "$JSON_HELPER" fmt_datetime "$@" </dev/null; }
|
||||
function json::create_rule() { python3 "$JSON_HELPER" create_rule "$@" </dev/null; }
|
||||
function json::cleanup_config() { python3 "$JSON_HELPER" cleanup_config "$@" </dev/null; }
|
||||
function json::remove_peer_block() { python3 "$JSON_HELPER" remove_peer_block "$@" </dev/null; }
|
||||
function json::create_group() { python3 "$JSON_HELPER" create_group "$@" </dev/null; }
|
||||
function json::parse_event() { python3 "$JSON_HELPER" parse_event "$@" </dev/null; }
|
||||
function json::parse_fw_event() { python3 "$JSON_HELPER" parse_fw_event "$@" </dev/null; }
|
||||
function json::count() { python3 "$JSON_HELPER" count "$@" </dev/null; }
|
||||
function json::audit_fw_counts() { python3 "$JSON_HELPER" audit_fw_counts "$@" </dev/null; }
|
||||
function json::peer_group_map() { python3 "$JSON_HELPER" peer_group_map "$@" </dev/null; }
|
||||
function json::peer_groups() { python3 "$JSON_HELPER" peer_groups "$@" </dev/null; }
|
||||
function json::peer_data() { WGCTL_DATETIME_FMT="$FMT_DATETIME" python3 "$JSON_HELPER" peer_data "$@" </dev/null; }
|
||||
function json::iso_to_ts() { python3 "$JSON_HELPER" iso_to_ts "$@" </dev/null; }
|
||||
function json::rule_list_data() { python3 "$JSON_HELPER" rule_list_data "$@" </dev/null; }
|
||||
function json::group_list_data() { python3 "$JSON_HELPER" group_list_data "$@" </dev/null; }
|
||||
function json::fmt_datetime() { python3 "$JSON_HELPER" fmt_datetime "$@" </dev/null; }
|
||||
function json::create_rule() { python3 "$JSON_HELPER" create_rule "$@" </dev/null; }
|
||||
function json::cleanup_config() { python3 "$JSON_HELPER" cleanup_config "$@" </dev/null; }
|
||||
function json::remove_peer_block() { python3 "$JSON_HELPER" remove_peer_block "$@" </dev/null; }
|
||||
function json::create_group() { python3 "$JSON_HELPER" create_group "$@" </dev/null; }
|
||||
function json::parse_event() { python3 "$JSON_HELPER" parse_event "$@" </dev/null; }
|
||||
function json::parse_fw_event() { python3 "$JSON_HELPER" parse_fw_event "$@" </dev/null; }
|
||||
function json::remove_events_filtered() { python3 "$JSON_HELPER" remove_events_filtered "$@" </dev/null; }
|
||||
function json::rule_resolve() { python3 "$JSON_HELPER" rule_resolve "$@" </dev/null; }
|
||||
function json::rule_resolve_field() { python3 "$JSON_HELPER" rule_resolve_field "$@" </dev/null; }
|
||||
function json::rule_inspect() { python3 "$JSON_HELPER" rule_inspect "$@" </dev/null; }
|
||||
function json::find_rule_file() { python3 "$JSON_HELPER" find_rule_file "$@" </dev/null; }
|
||||
function json::get_raw() { python3 "$JSON_HELPER" get_raw "$@" </dev/null; }
|
||||
function json::rule_resolve() { python3 "$JSON_HELPER" rule_resolve "$@" </dev/null; }
|
||||
function json::rule_resolve_field() { python3 "$JSON_HELPER" rule_resolve_field "$@" </dev/null; }
|
||||
function json::rule_inspect() { python3 "$JSON_HELPER" rule_inspect "$@" </dev/null; }
|
||||
function json::find_rule_file() { python3 "$JSON_HELPER" find_rule_file "$@" </dev/null; }
|
||||
function json::get_raw() { python3 "$JSON_HELPER" get_raw "$@" </dev/null; }
|
||||
function json::count_resolved() { python3 "$JSON_HELPER" count_resolved "$(ctx::rules)" "$@" </dev/null; }
|
||||
function json::block_get() { python3 "$JSON_HELPER" block_get "$@" </dev/null; }
|
||||
function json::block_is_blocked() { python3 "$JSON_HELPER" block_is_blocked "$@" </dev/null; }
|
||||
function json::block_set_direct() { python3 "$JSON_HELPER" block_set_direct "$@" </dev/null; }
|
||||
function json::block_add_group() { python3 "$JSON_HELPER" block_add_group "$@" </dev/null; }
|
||||
function json::block_remove_group() { python3 "$JSON_HELPER" block_remove_group "$@" </dev/null; }
|
||||
function json::block_add_rule() { python3 "$JSON_HELPER" block_add_rule "$@" </dev/null; }
|
||||
function json::block_remove_rule() { python3 "$JSON_HELPER" block_remove_rule "$@" </dev/null; }
|
||||
function json::block_get_rules() { python3 "$JSON_HELPER" block_get_rules "$@" </dev/null; }
|
||||
function json::block_get_groups() { python3 "$JSON_HELPER" block_get_groups "$@" </dev/null; }
|
||||
function json::block_get_direct() { python3 "$JSON_HELPER" block_get_direct "$@" </dev/null; }
|
||||
function json::net_list() { python3 "$JSON_HELPER" net_list "$@" </dev/null; }
|
||||
function json::net_show() { python3 "$JSON_HELPER" net_show "$@" </dev/null; }
|
||||
function json::net_exists() { python3 "$JSON_HELPER" net_exists "$@" </dev/null; }
|
||||
function json::net_add_service() { python3 "$JSON_HELPER" net_add_service "$@" </dev/null; }
|
||||
function json::net_add_port() { python3 "$JSON_HELPER" net_add_port "$@" </dev/null; }
|
||||
function json::net_remove() { python3 "$JSON_HELPER" net_remove "$@" </dev/null; }
|
||||
function json::net_resolve() { python3 "$JSON_HELPER" net_resolve "$@" </dev/null; }
|
||||
function json::net_reverse_lookup() { python3 "$JSON_HELPER" net_reverse_lookup "$@" </dev/null; }
|
||||
function json::block_is_empty() { python3 "$JSON_HELPER" block_is_empty "$@" </dev/null; }
|
||||
function json::block_get() { python3 "$JSON_HELPER" block_get "$@" </dev/null; }
|
||||
function json::block_is_blocked() { python3 "$JSON_HELPER" block_is_blocked "$@" </dev/null; }
|
||||
function json::block_set_direct() { python3 "$JSON_HELPER" block_set_direct "$@" </dev/null; }
|
||||
function json::block_add_group() { python3 "$JSON_HELPER" block_add_group "$@" </dev/null; }
|
||||
function json::block_remove_group() { python3 "$JSON_HELPER" block_remove_group "$@" </dev/null; }
|
||||
function json::block_add_rule() { python3 "$JSON_HELPER" block_add_rule "$@" </dev/null; }
|
||||
function json::block_remove_rule() { python3 "$JSON_HELPER" block_remove_rule "$@" </dev/null; }
|
||||
function json::block_get_rules() { python3 "$JSON_HELPER" block_get_rules "$@" </dev/null; }
|
||||
function json::block_get_groups() { python3 "$JSON_HELPER" block_get_groups "$@" </dev/null; }
|
||||
function json::block_get_direct() { python3 "$JSON_HELPER" block_get_direct "$@" </dev/null; }
|
||||
function json::net_list() { python3 "$JSON_HELPER" net_list "$@" </dev/null; }
|
||||
function json::net_show() { python3 "$JSON_HELPER" net_show "$@" </dev/null; }
|
||||
function json::net_exists() { python3 "$JSON_HELPER" net_exists "$@" </dev/null; }
|
||||
function json::net_add_service() { python3 "$JSON_HELPER" net_add_service "$@" </dev/null; }
|
||||
function json::net_add_port() { python3 "$JSON_HELPER" net_add_port "$@" </dev/null; }
|
||||
function json::net_remove() { python3 "$JSON_HELPER" net_remove "$@" </dev/null; }
|
||||
function json::net_resolve() { python3 "$JSON_HELPER" net_resolve "$@" </dev/null; }
|
||||
function json::net_reverse_lookup() { python3 "$JSON_HELPER" net_reverse_lookup "$@" </dev/null; }
|
||||
function json::block_is_empty() { python3 "$JSON_HELPER" block_is_empty "$@" </dev/null; }
|
||||
function json::group_has_peer() { python3 "$JSON_HELPER" group_has_peer "$@" </dev/null; }
|
||||
|
||||
function json::peer_transfer() {
|
||||
ACTIVITY_TOTAL_LOW="$(config::activity_total_low)" \
|
||||
|
|
|
|||
|
|
@ -640,12 +640,13 @@ def peer_data(clients_dir, meta_dir, events_log):
|
|||
m = meta.get(name, {})
|
||||
rule = m.get('rule', '')
|
||||
subtype = m.get('subtype', '')
|
||||
main_group = m.get('main_group', '')
|
||||
|
||||
last_event = last_events.get(name, {})
|
||||
last_ts = last_event.get('timestamp', '') # raw ISO, no formatting
|
||||
last_evt = last_event.get('event', '') # fixed: was last_event
|
||||
|
||||
print(f"{name}|{ip}|{rule}|{subtype}|{last_ts}|{last_evt}")
|
||||
print(f"{name}|{ip}|{rule}|{subtype}|{last_ts}|{last_evt}|{main_group}")
|
||||
|
||||
def iso_to_ts(iso_str):
|
||||
"""Convert ISO timestamp to unix timestamp"""
|
||||
|
|
@ -1270,25 +1271,18 @@ def block_add_rule(file, peer_ip, rule_type, name="", target="",
|
|||
sys.exit(1)
|
||||
|
||||
def block_remove_rule(file, rule_type, target="", port="", proto=""):
|
||||
"""Remove matching block rule entry"""
|
||||
try:
|
||||
data = _block_read(file)
|
||||
if not data:
|
||||
return
|
||||
rules = data.get("rules", [])
|
||||
filtered = []
|
||||
for r in rules:
|
||||
if r.get("type") == rule_type and \
|
||||
r.get("target", "") == target and \
|
||||
r.get("port", "") == port and \
|
||||
r.get("proto", "") == proto:
|
||||
continue # remove this one
|
||||
filtered.append(r)
|
||||
data["rules"] = filtered
|
||||
_block_write(file, data)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
data = _block_read(file)
|
||||
if not data:
|
||||
return
|
||||
rules = data.get("rules", [])
|
||||
filtered = [r for r in rules if not (
|
||||
r.get("type") == rule_type and
|
||||
r.get("target", "") == target and
|
||||
r.get("port", "") == port and
|
||||
r.get("proto", "") == proto
|
||||
)]
|
||||
data["rules"] = filtered
|
||||
_block_write(file, data)
|
||||
|
||||
def block_get_rules(file):
|
||||
"""Print rules as pipe-separated lines: name|type|target|port|proto"""
|
||||
|
|
@ -1483,6 +1477,15 @@ def block_is_empty(file):
|
|||
)
|
||||
print("true" if empty else "false")
|
||||
|
||||
def group_has_peer(file, peer_name):
|
||||
try:
|
||||
with open(file) as f:
|
||||
data = json.load(f)
|
||||
peers = data.get('peers', [])
|
||||
print('true' if peer_name in peers else 'false')
|
||||
except Exception:
|
||||
print('false')
|
||||
|
||||
commands = {
|
||||
'get': lambda args: get(args[0], args[1]),
|
||||
'set': lambda args: set_key(args[0], args[1], args[2]),
|
||||
|
|
@ -1573,6 +1576,7 @@ commands = {
|
|||
args[3] if len(args) > 3 else ''
|
||||
),
|
||||
'block_is_empty': lambda args: block_is_empty(args[0]),
|
||||
'group_has_peer': lambda args: group_has_peer(args[0], args[1]),
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
48
core/ui.sh
48
core/ui.sh
|
|
@ -41,6 +41,30 @@ function ui::pad() {
|
|||
printf "%b%${pad}s" "$text" ""
|
||||
}
|
||||
|
||||
function ui::pad_mb() {
|
||||
local text="$1" width="${2:-20}"
|
||||
local visible
|
||||
visible=$(printf "%b" "$text" | sed 's/\x1b\[[0-9;]*m//g')
|
||||
local vis_len
|
||||
vis_len=$(python3 -c "import sys; print(len(sys.stdin.read().rstrip('\n')))" \
|
||||
<<< "$visible")
|
||||
local pad=$(( width - vis_len ))
|
||||
[[ $pad -lt 0 ]] && pad=0
|
||||
printf "%b%${pad}s" "$text" ""
|
||||
}
|
||||
|
||||
function ui::vis_len_multi() {
|
||||
# Get visible lengths of multiple strings in one Python call
|
||||
# Returns newline-separated integers
|
||||
python3 -c "
|
||||
import sys, re
|
||||
ansi = re.compile(r'\x1b\[[0-9;]*m')
|
||||
for s in sys.argv[1:]:
|
||||
print(len(ansi.sub('', s)))
|
||||
" "$@"
|
||||
}
|
||||
|
||||
|
||||
function ui::pad_status() {
|
||||
ui::pad "${1:-}" "${2:-25}"
|
||||
}
|
||||
|
|
@ -64,4 +88,28 @@ function ui::firewall_rule() {
|
|||
else
|
||||
printf "%s\n" "$rule"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Content Helpers
|
||||
# ============================================
|
||||
|
||||
function ui::has_content() {
|
||||
# Returns 0 (true) if content exists, 1 if empty
|
||||
# Works with strings, arrays, or command output
|
||||
local value="${1:-}"
|
||||
[[ -n "$value" ]]
|
||||
}
|
||||
|
||||
function ui::skip_if_empty() {
|
||||
# Usage: ui::skip_if_empty "$var" || return 0
|
||||
# Or: ui::skip_if_empty "${array[*]}" || return 0
|
||||
local value="${1:-}"
|
||||
[[ -z "${value// }" ]] && return 1 || return 0
|
||||
}
|
||||
|
||||
function ui::empty() {
|
||||
# ui::empty "$var" && return 0
|
||||
# ui::empty "${array[*]}" && return 0
|
||||
[[ -z "${1// }" ]]
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"phone-fred": "94.63.0.129",
|
||||
"phone-helena": "148.69.46.73",
|
||||
"phone-nuno": "148.69.51.201",
|
||||
"phone-nuno": "94.63.0.129",
|
||||
"tablet-nuno": "148.69.202.5",
|
||||
"guest-zephyr": "5.13.82.5",
|
||||
"guest-zephyr": "86.120.152.74",
|
||||
"guest-zephyr-test": "94.63.0.129",
|
||||
"desktop-roboclean": "46.189.215.231",
|
||||
"laptop-nuno": "94.63.0.129"
|
||||
"laptop-nuno": "94.63.0.129",
|
||||
"phone-luis": "176.223.61.15"
|
||||
}
|
||||
|
|
@ -106,6 +106,14 @@ function block::rename() {
|
|||
[[ -f "$old_file" ]] && mv "$old_file" "$new_file"
|
||||
}
|
||||
|
||||
function block::clear_full_block() {
|
||||
local name="${1:?}"
|
||||
local file
|
||||
file=$(block::file "$name")
|
||||
[[ ! -f "$file" ]] && return 0
|
||||
json::block_remove_rule "$file" "full"
|
||||
}
|
||||
|
||||
# ── High level operations ──────────────────
|
||||
|
||||
function block::apply_full() {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,69 @@ function config::_init_defaults() {
|
|||
_WG_TUNNEL_FULL="0.0.0.0/0, ::/0"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Validation
|
||||
# ============================================
|
||||
|
||||
function config::validate() {
|
||||
local errors=()
|
||||
|
||||
# Required fields
|
||||
local endpoint
|
||||
endpoint=$(config::endpoint)
|
||||
if [[ -z "$endpoint" ]]; then
|
||||
errors+=("WG_ENDPOINT is not set — required for client config generation")
|
||||
elif [[ "$endpoint" != *:* ]]; then
|
||||
errors+=("WG_ENDPOINT must include port (e.g. wg.example.com:51820)")
|
||||
fi
|
||||
|
||||
local port
|
||||
port=$(config::port)
|
||||
if [[ -z "$port" ]]; then
|
||||
errors+=("WG_LISTEN_PORT is not set")
|
||||
elif ! [[ "$port" =~ ^[0-9]+$ ]] || (( port < 1 || port > 65535 )); then
|
||||
errors+=("WG_LISTEN_PORT must be a valid port number (1-65535)")
|
||||
fi
|
||||
|
||||
local dns
|
||||
dns=$(config::dns)
|
||||
if [[ -z "$dns" ]]; then
|
||||
errors+=("WG_DNS is not set — required for client configs")
|
||||
elif ! ip::is_valid "$dns"; then
|
||||
errors+=("WG_DNS must be a valid IP address")
|
||||
fi
|
||||
|
||||
local subnet
|
||||
subnet=$(config::subnet)
|
||||
if [[ -z "$subnet" ]]; then
|
||||
errors+=("WG_SUBNET is not set — required for IP allocation")
|
||||
fi
|
||||
|
||||
local interface
|
||||
interface=$(config::interface)
|
||||
if [[ -z "$interface" ]]; then
|
||||
errors+=("WG_INTERFACE is not set, defaulting to wg0")
|
||||
fi
|
||||
|
||||
# Warn-only fields
|
||||
local lan
|
||||
lan=$(config::lan)
|
||||
if [[ -z "$lan" ]]; then
|
||||
log::wg_warning "WG_LAN is not set — some rule features may not work correctly"
|
||||
fi
|
||||
|
||||
if [[ ${#errors[@]} -gt 0 ]]; then
|
||||
log::error "wgctl configuration errors:"
|
||||
for err in "${errors[@]}"; do
|
||||
printf " ✗ %s\n" "$err" >&2
|
||||
done
|
||||
printf "\n Edit /etc/wireguard/.wgctl/wgctl.conf to fix these issues.\n\n" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Load overrides from .wgctl/wgctl.conf
|
||||
# ============================================
|
||||
|
|
@ -129,10 +192,7 @@ function config::activity_total_high() { echo "$_ACTIVITY_TOTAL_HIGH_BYTES";
|
|||
function config::activity_current_low() { echo "$_ACTIVITY_TOTAL_LOW_BYTES"; }
|
||||
function config::activity_current_med() { echo "$_ACTIVITY_TOTAL_MED_BYTES"; }
|
||||
function config::activity_current_high() { echo "$_ACTIVITY_TOTAL_HIGH_BYTES"; }
|
||||
|
||||
function config::server_public_key() {
|
||||
cat "$_WG_SERVER_PUBLIC_KEY_FILE"
|
||||
}
|
||||
function config::server_public_key() { cat "$_WG_SERVER_PUBLIC_KEY_FILE"; }
|
||||
|
||||
function config::device_types() {
|
||||
local types
|
||||
|
|
|
|||
|
|
@ -83,4 +83,15 @@ function group::each_peer() {
|
|||
function group::_peer_exists_check() {
|
||||
local peer_name="${1:-}"
|
||||
peers::require_exists "$peer_name" > /dev/null 2>&1
|
||||
}
|
||||
}
|
||||
|
||||
function group::has_peer() {
|
||||
local group_name="${1:?}" peer_name="${2:?}"
|
||||
local group_file
|
||||
group_file="$(group::path "$group_name")"
|
||||
[[ ! -f "$group_file" ]] && return 1
|
||||
local result
|
||||
result=$(json::group_has_peer "$group_file" "$peer_name")
|
||||
[[ "$result" == "true" ]]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -286,13 +286,15 @@ function peers::is_offline() {
|
|||
peers::is_online "$name" "$handshake_ts" "$last_ts" && return 1 || return 0
|
||||
}
|
||||
|
||||
# function peers::is_offline() {
|
||||
# local name="${1:-}" handshake_ts="${2:-0}" last_ts="${3:-}"
|
||||
# if peers::is_online "$name" "$handshake_ts" "$last_ts"; then
|
||||
# return 1
|
||||
# fi
|
||||
# return 0
|
||||
# }
|
||||
function peers::get_main_group() {
|
||||
local name="${1:?}"
|
||||
peers::get_meta "$name" "main_group"
|
||||
}
|
||||
|
||||
function peers::set_main_group() {
|
||||
local name="${1:?}" group="${2:?}"
|
||||
peers::set_meta "$name" "main_group" "$group"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Name + Type Parsing
|
||||
|
|
@ -352,6 +354,22 @@ function peers::format_last_seen() {
|
|||
esac
|
||||
}
|
||||
|
||||
# function peers::format_status() {
|
||||
# local name="${1:-}" public_key="${2:-}" is_blocked="${3:-false}"
|
||||
# local is_restricted="${4:-false}" handshake_ts="${5:-0}" last_ts="${6:-}"
|
||||
|
||||
# local state
|
||||
# state=$(peers::connection_state "$is_blocked" "$is_restricted" \
|
||||
# "$handshake_ts" "$last_ts")
|
||||
|
||||
# local conn_str modifier color
|
||||
# IFS="|" read -r conn_str modifier color <<< "$state"
|
||||
|
||||
# local display="$conn_str"
|
||||
# [[ -n "$modifier" ]] && display="${conn_str} (${modifier})"
|
||||
# echo -e "${color}${display}\033[0m"
|
||||
# }
|
||||
|
||||
function peers::format_status() {
|
||||
local name="${1:-}" public_key="${2:-}" is_blocked="${3:-false}"
|
||||
local is_restricted="${4:-false}" handshake_ts="${5:-0}" last_ts="${6:-}"
|
||||
|
|
@ -363,9 +381,47 @@ function peers::format_status() {
|
|||
local conn_str modifier color
|
||||
IFS="|" read -r conn_str modifier color <<< "$state"
|
||||
|
||||
local display="$conn_str"
|
||||
[[ -n "$modifier" ]] && display="${conn_str} (${modifier})"
|
||||
echo -e "${color}${display}\033[0m"
|
||||
# Color based on state — modifier overrides base connection color
|
||||
if [[ "$is_blocked" == "true" ]]; then
|
||||
color="\033[1;31m" # red — blocked
|
||||
elif [[ "$is_restricted" == "true" ]]; then
|
||||
color="\033[1;33m" # yellow — restricted
|
||||
elif [[ "$conn_str" == "online" ]]; then
|
||||
color="\033[1;32m" # green — online
|
||||
else
|
||||
color="\033[0;37m" # gray — offline
|
||||
fi
|
||||
|
||||
local conn_str_padded
|
||||
conn_str_padded=$(printf "%-7s" "$conn_str")
|
||||
echo -e "${color}${conn_str_padded}\033[0m"
|
||||
}
|
||||
|
||||
# Inspect — verbose, color + descriptive text
|
||||
function peers::format_status_verbose() {
|
||||
local name="${1:-}" public_key="${2:-}" is_blocked="${3:-false}"
|
||||
local is_restricted="${4:-false}" handshake_ts="${5:-0}" last_ts="${6:-}"
|
||||
|
||||
local conn_str
|
||||
local state
|
||||
state=$(peers::connection_state "$is_blocked" "$is_restricted" \
|
||||
"$handshake_ts" "$last_ts")
|
||||
IFS="|" read -r conn_str _ _ <<< "$state"
|
||||
|
||||
local color suffix=""
|
||||
if [[ "$is_blocked" == "true" ]]; then
|
||||
color="\033[1;31m"
|
||||
suffix=" (blocked)"
|
||||
elif [[ "$is_restricted" == "true" ]]; then
|
||||
color="\033[1;33m"
|
||||
suffix=" (restricted)"
|
||||
elif [[ "$conn_str" == "online" ]]; then
|
||||
color="\033[1;32m"
|
||||
else
|
||||
color="\033[0;37m"
|
||||
fi
|
||||
|
||||
echo -e "${color}${conn_str}${suffix}\033[0m"
|
||||
}
|
||||
|
||||
function peers::display_type() {
|
||||
|
|
|
|||
2
wgctl
2
wgctl
|
|
@ -70,6 +70,8 @@ function wgctl::dispatch() {
|
|||
|
||||
case "$cmd" in
|
||||
help) wgctl::help; return ;;
|
||||
shell) : ;;
|
||||
*) config::validate || exit 1 ;;
|
||||
esac
|
||||
|
||||
# If alias resolved to service, pass original cmd as subcommand
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue