#!/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 # ============================================ 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:-}" # 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 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" ;; # Add debug temporarily to config::load: DATE_FORMAT) log::debug "config: setting date format to $value" _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}" } # ============================================ # Device Type → Subnet Mapping # ============================================ declare -gA DEVICE_SUBNETS=( [desktop]="10.1.1" [laptop]="10.1.2" [phone]="10.1.3" [tablet]="10.1.4" [guest]="10.1.100" [guest-desktop]="10.1.101" [guest-laptop]="10.1.102" [guest-phone]="10.1.103" [guest-tablet]="10.1.104" ) # ============================================ # Tunnel Modes # ============================================ declare -gA DEVICE_TUNNEL_MODE=( [desktop]="split" [laptop]="split" [phone]="split" [tablet]="split" [guest]="split" [guest-desktop]="split" [guest-laptop]="split" [guest-phone]="split" [guest-tablet]="split" ) # ============================================ # 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::server_public_key() { cat "$_WG_SERVER_PUBLIC_KEY_FILE" } function config::device_types() { local types { set +u; types="${!DEVICE_SUBNETS[@]}"; set -u; } echo "$types" } function config::is_valid_type() { local type="$1" local subnet subnet=$(config::subnet_for "$type") [[ -n "$subnet" ]] } function config::is_guest_type() { local type="$1" [[ "$type" == "guest" || "$type" == guest-* ]] } function config::subnet_for() { local type="$1" local result { set +u; result="${DEVICE_SUBNETS[$type]:-}"; set -u; } echo "$result" } function config::default_tunnel_for() { local type="$1" local result { set +u; result="${DEVICE_TUNNEL_MODE[$type]:-split}"; set -u; } echo "$result" } function config::allowed_ips_for() { local type="$1" local tunnel="${2:-}" if [[ -z "$tunnel" ]]; then tunnel=$(config::default_tunnel_for "$type") fi 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 } # ============================================ # Validation # ============================================ function config::validate() { if [[ ! -f "$_WG_SERVER_PUBLIC_KEY_FILE" ]]; then log::error "Server public key not found: ${_WG_SERVER_PUBLIC_KEY_FILE}" exit 1 fi if [[ ! -f "$_WG_SERVER_PRIVATE_KEY_FILE" ]]; then log::error "Server private key not found: ${_WG_SERVER_PRIVATE_KEY_FILE}" exit 1 fi if [[ ! -f "$_WG_CONFIG" ]]; then log::error "WireGuard config not found: ${_WG_CONFIG}" exit 1 fi }