- wgctl.json: commands section with defaults and aliases - command_mixins.sh: flag::exclusive, command::_resolve_conflicts - command::run: two-pass defaults+user with conflict resolution - load_command: _CURRENT_LOADING_CMD for flag::exclusive context - list: flag::exclusive --online --offline --blocked --restricted --allowed - logs: flag::exclusive --ascending --descending - logs: fix --fw --wg together treated as neither (show both) - dispatch: config alias resolution before load_command - wgctl ls/peers/act: aliases via wgctl.json
240 lines
No EOL
8.5 KiB
Bash
240 lines
No EOL
8.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:-eu}"
|
|
}
|
|
|
|
# ============================================
|
|
# Defaults
|
|
# ============================================
|
|
|
|
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}"
|
|
|
|
declare -gA _COMMAND_DEFAULTS=()
|
|
declare -gA _COMMAND_ALIASES=()
|
|
|
|
function config::_init_defaults() {
|
|
_WG_INTERFACE="wg0"
|
|
_WG_DNS="10.0.0.103"
|
|
_WG_DNS_FALLBACK=""
|
|
_WG_LAN="10.0.0.0/24"
|
|
_WG_SUBNET="10.1.0.0/16"
|
|
_WG_PORT="51820"
|
|
_WG_ENDPOINT=""
|
|
_WG_HANDSHAKE_CHECK_TIME_SEC="300"
|
|
_FMT_DATE_FORMAT="eu"
|
|
|
|
# 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"
|
|
}
|
|
|
|
# ============================================
|
|
# Load from wgctl.json
|
|
# Falls back to wgctl.conf for migration period
|
|
# ============================================
|
|
|
|
function config::load() {
|
|
local json_conf
|
|
json_conf="$(ctx::config_file)"
|
|
|
|
if [[ -f "$json_conf" ]]; then
|
|
config::_load_json "$json_conf"
|
|
else
|
|
# Fallback: legacy wgctl.conf
|
|
local legacy_conf
|
|
legacy_conf="$(ctx::wgctl)/wgctl.conf"
|
|
[[ -f "$legacy_conf" ]] && config::_load_legacy "$legacy_conf"
|
|
fi
|
|
|
|
# Recompute derived values after overrides
|
|
_WG_CONFIG="$(ctx::wg)/${_WG_INTERFACE}.conf"
|
|
_WG_TUNNEL_SPLIT="${_WG_SUBNET}, ${_WG_LAN}"
|
|
}
|
|
|
|
function config::_load_json() {
|
|
local file="$1"
|
|
[[ ! -f "$file" ]] && return 0
|
|
|
|
while IFS='=' read -r key value; do
|
|
[[ -z "$key" ]] && continue
|
|
case "$key" in
|
|
WG_INTERFACE) _WG_INTERFACE="$value" ;;
|
|
WG_ENDPOINT) _WG_ENDPOINT="$value" ;;
|
|
WG_DNS) _WG_DNS="$value" ;;
|
|
WG_DNS_FALLBACK) _WG_DNS_FALLBACK="$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" ;;
|
|
DATE_FORMAT)
|
|
_FMT_DATE_FORMAT="$value"
|
|
fmt::set_date_format "$value"
|
|
;;
|
|
ACTIVITY_TOTAL_LOW_BYTES) _ACTIVITY_TOTAL_LOW_BYTES="$value" ;;
|
|
ACTIVITY_TOTAL_MED_BYTES) _ACTIVITY_TOTAL_MED_BYTES="$value" ;;
|
|
ACTIVITY_TOTAL_HIGH_BYTES) _ACTIVITY_TOTAL_HIGH_BYTES="$value" ;;
|
|
ACTIVITY_CURRENT_LOW_BYTES) _ACTIVITY_CURRENT_LOW_BYTES="$value" ;;
|
|
ACTIVITY_CURRENT_MED_BYTES) _ACTIVITY_CURRENT_MED_BYTES="$value" ;;
|
|
ACTIVITY_CURRENT_HIGH_BYTES) _ACTIVITY_CURRENT_HIGH_BYTES="$value" ;;
|
|
CMD_DEFAULT:*)
|
|
local cmd_name="${key#CMD_DEFAULT:}"
|
|
_COMMAND_DEFAULTS["$cmd_name"]="$value"
|
|
;;
|
|
CMD_ALIAS:*)
|
|
local alias_name="${key#CMD_ALIAS:}"
|
|
_COMMAND_ALIASES["$alias_name"]="$value"
|
|
;;
|
|
esac
|
|
done < <(json::config_load "$file" 2>/dev/null)
|
|
}
|
|
|
|
function config::_load_legacy() {
|
|
local conf_file="$1"
|
|
log::wg_warning "Using legacy wgctl.conf — run 'wgctl config migrate' to upgrade"
|
|
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_DNS_FALLBACK) _WG_DNS_FALLBACK="$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" ;;
|
|
DATE_FORMAT)
|
|
_FMT_DATE_FORMAT="$value"
|
|
fmt::set_date_format "$value"
|
|
;;
|
|
esac
|
|
done < "$conf_file"
|
|
}
|
|
|
|
# ============================================
|
|
# Validation (unchanged)
|
|
# ============================================
|
|
|
|
function config::validate() {
|
|
local errors=()
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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 %s to fix these issues.\n\n" "$(ctx::config_file)" >&2
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# ============================================
|
|
# Accessors (unchanged)
|
|
# ============================================
|
|
|
|
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::dns_fallback() { echo "${_WG_DNS_FALLBACK:-}"; }
|
|
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_CURRENT_LOW_BYTES"; }
|
|
function config::activity_current_med() { echo "$_ACTIVITY_CURRENT_MED_BYTES"; }
|
|
function config::activity_current_high() { echo "$_ACTIVITY_CURRENT_HIGH_BYTES"; }
|
|
function config::server_public_key() { cat "$_WG_SERVER_PUBLIC_KEY_FILE"; }
|
|
|
|
function config::allowed_ips_for() {
|
|
local tunnel="${1:-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
|
|
}
|
|
|
|
function config::dns_string() {
|
|
local fallback
|
|
fallback=$(config::dns_fallback)
|
|
if [[ -n "$fallback" ]]; then
|
|
echo "$(config::dns), ${fallback}"
|
|
else
|
|
echo "$(config::dns)"
|
|
fi
|
|
} |