wgctl/modules/config.module.sh
Nuno Duque Nunes 4dcf98b128 feat: tableless logs/watch layout with service annotations
- wgctl logs: tableless layout, fw/wg sections, --merged flag, --raw flag
- wgctl watch: tableless layout, service annotations, colored fw/wg labels
- wgctl rule list: tableless with +N/-N/+all indicators, inline extends
- wgctl activity: transfer totals and firewall drops per peer
- ui/logs.module.sh: fw_row, wg_row, watch rows, table versions kept
- ui/rule.module.sh: list_row, list_group_header, list_base_header
- fmt.sh: FMT_DATETIME_SHORT, updated fmt::set_date_format
- json_helper.py: fw_events with service annotation, wg_events with count
2026-05-23 03:24:20 +00:00

181 lines
No EOL
6.5 KiB
Bash

#!/usr/bin/env bash
# ============================================
# Lifecycle
# ============================================
function config::on_load() {
config::_init_defaults
config::load
config::validate
fmt::set_date_format "${_FMT_DATE_FORMAT:-iso}"
}
# ============================================
# Defaults
# ============================================
# Activity thresholds
declare -g _ACTIVITY_TOTAL_LOW_BYTES="${ACTIVITY_TOTAL_LOW_BYTES:-1000000}"
declare -g _ACTIVITY_TOTAL_MED_BYTES="${ACTIVITY_TOTAL_MED_BYTES:-10000000}"
declare -g _ACTIVITY_TOTAL_HIGH_BYTES="${ACTIVITY_TOTAL_HIGH_BYTES:-100000000}"
declare -g _ACTIVITY_CURRENT_LOW_BYTES="${ACTIVITY_CURRENT_LOW_BYTES:-1000000}"
declare -g _ACTIVITY_CURRENT_MED_BYTES="${ACTIVITY_CURRENT_MED_BYTES:-10000000}"
declare -g _ACTIVITY_CURRENT_HIGH_BYTES="${ACTIVITY_CURRENT_HIGH_BYTES:-100000000}"
function config::_init_defaults() {
_WG_INTERFACE="${WG_INTERFACE:-wg0}"
_WG_DNS="${WG_DNS:-10.0.0.103}"
_WG_LAN="${WG_LAN:-10.0.0.0/24}"
_WG_SUBNET="${WG_SUBNET:-10.1.0.0/16}"
_WG_PORT="${WG_PORT:-51820}"
_WG_ENDPOINT="${WG_ENDPOINT:-}"
_WG_HANDSHAKE_CHECK_TIME_SEC="${WG_HANDSHAKE_CHECK_TIME_SEC:-180}"
# Derived
_WG_CONFIG="$(ctx::wg)/${_WG_INTERFACE}.conf"
_WG_SERVER_PUBLIC_KEY_FILE="$(ctx::wg)/server_public.key"
_WG_SERVER_PRIVATE_KEY_FILE="$(ctx::wg)/server_private.key"
_WG_TUNNEL_SPLIT="${_WG_SUBNET}, ${_WG_LAN}"
_WG_TUNNEL_FULL="0.0.0.0/0, ::/0"
}
# ============================================
# Validation
# ============================================
function config::validate() {
local errors=()
# Server key and config files
if [[ ! -f "$_WG_SERVER_PUBLIC_KEY_FILE" ]]; then
errors+=("Server public key not found: ${_WG_SERVER_PUBLIC_KEY_FILE}")
fi
if [[ ! -f "$_WG_SERVER_PRIVATE_KEY_FILE" ]]; then
errors+=("Server private key not found: ${_WG_SERVER_PRIVATE_KEY_FILE}")
fi
if [[ ! -f "$_WG_CONFIG" ]]; then
errors+=("WireGuard config not found: ${_WG_CONFIG}")
fi
# Required config values
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_PORT is not set")
elif ! [[ "$port" =~ ^[0-9]+$ ]] || (( port < 1 || port > 65535 )); then
errors+=("WG_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
# Warn-only
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
# ============================================
function config::load() {
local conf_file
conf_file="$(ctx::data)/wgctl.conf"
[[ ! -f "$conf_file" ]] && return 0
while IFS='=' read -r key value || [[ -n "$key" ]]; do
[[ "$key" =~ ^[[:space:]]*# ]] && continue
[[ -z "${key// }" ]] && continue
key="${key// /}"
value="${value// /}"
case "$key" in
WG_INTERFACE) _WG_INTERFACE="$value" ;;
WG_ENDPOINT) _WG_ENDPOINT="$value" ;;
WG_DNS) _WG_DNS="$value" ;;
WG_PORT) _WG_PORT="$value" ;;
WG_SUBNET) _WG_SUBNET="$value" ;;
WG_LAN) _WG_LAN="$value" ;;
WG_HANDSHAKE_CHECK_TIME_SEC) _WG_HANDSHAKE_CHECK_TIME_SEC="$value" ;;
ACTIVITY_LOW_BYTES) _ACTIVITY_LOW_BYTES="$value" ;;
ACTIVITY_MED_BYTES) _ACTIVITY_MED_BYTES="$value" ;;
ACTIVITY_HIGH_BYTES) _ACTIVITY_HIGH_BYTES="$value" ;;
DATE_FORMAT)
_FMT_DATE_FORMAT="$value"
fmt::set_date_format "$value"
;;
esac
done < "$conf_file"
# Recompute derived values after overrides
_WG_CONFIG="$(ctx::wg)/${_WG_INTERFACE}.conf"
_WG_TUNNEL_SPLIT="${_WG_SUBNET}, ${_WG_LAN}"
}
# ============================================
# Accessors
# ============================================
function config::interface() { echo "$_WG_INTERFACE"; }
function config::config_file() { echo "$_WG_CONFIG"; }
function config::endpoint() { echo "$_WG_ENDPOINT"; }
function config::dns() { echo "$_WG_DNS"; }
function config::port() { echo "$_WG_PORT"; }
function config::subnet() { echo "$_WG_SUBNET"; }
function config::lan() { echo "$_WG_LAN"; }
function config::tunnel_split() { echo "$_WG_TUNNEL_SPLIT"; }
function config::tunnel_full() { echo "$_WG_TUNNEL_FULL"; }
function config::handshake_time_sec() { echo "$_WG_HANDSHAKE_CHECK_TIME_SEC"; }
function config::activity_total_low() { echo "$_ACTIVITY_TOTAL_LOW_BYTES"; }
function config::activity_total_med() { echo "$_ACTIVITY_TOTAL_MED_BYTES"; }
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::allowed_ips_for() {
local tunnel="${2:-split}"
case "$tunnel" in
full) echo "$_WG_TUNNEL_FULL" ;;
split) echo "$_WG_TUNNEL_SPLIT" ;;
*)
log::error "Unknown tunnel mode: ${tunnel} (use 'split' or 'full')"
return 1
;;
esac
}