gistfile1.txt
· 8.3 KiB · Text
Sin formato
#!/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=()
# 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
# ============================================
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}"
}
# ============================================
# 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::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::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
}
| 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | # ============================================ |
| 4 | # Lifecycle |
| 5 | # ============================================ |
| 6 | |
| 7 | function config::on_load() { |
| 8 | config::_init_defaults |
| 9 | config::load |
| 10 | config::validate |
| 11 | fmt::set_date_format "${_FMT_DATE_FORMAT:-iso}" |
| 12 | } |
| 13 | |
| 14 | # ============================================ |
| 15 | # Defaults |
| 16 | # ============================================ |
| 17 | |
| 18 | # Activity thresholds |
| 19 | declare -g _ACTIVITY_TOTAL_LOW_BYTES="${ACTIVITY_TOTAL_LOW_BYTES:-1000000}" |
| 20 | declare -g _ACTIVITY_TOTAL_MED_BYTES="${ACTIVITY_TOTAL_MED_BYTES:-10000000}" |
| 21 | declare -g _ACTIVITY_TOTAL_HIGH_BYTES="${ACTIVITY_TOTAL_HIGH_BYTES:-100000000}" |
| 22 | |
| 23 | declare -g _ACTIVITY_CURRENT_LOW_BYTES="${ACTIVITY_CURRENT_LOW_BYTES:-1000000}" |
| 24 | declare -g _ACTIVITY_CURRENT_MED_BYTES="${ACTIVITY_CURRENT_MED_BYTES:-10000000}" |
| 25 | declare -g _ACTIVITY_CURRENT_HIGH_BYTES="${ACTIVITY_CURRENT_HIGH_BYTES:-100000000}" |
| 26 | |
| 27 | function config::_init_defaults() { |
| 28 | _WG_INTERFACE="${WG_INTERFACE:-wg0}" |
| 29 | _WG_DNS="${WG_DNS:-10.0.0.103}" |
| 30 | _WG_LAN="${WG_LAN:-10.0.0.0/24}" |
| 31 | _WG_SUBNET="${WG_SUBNET:-10.1.0.0/16}" |
| 32 | _WG_PORT="${WG_PORT:-51820}" |
| 33 | _WG_ENDPOINT="${WG_ENDPOINT:-}" |
| 34 | _WG_HANDSHAKE_CHECK_TIME_SEC="${WG_HANDSHAKE_CHECK_TIME_SEC:-180}" |
| 35 | |
| 36 | # Derived |
| 37 | _WG_CONFIG="$(ctx::wg)/${_WG_INTERFACE}.conf" |
| 38 | _WG_SERVER_PUBLIC_KEY_FILE="$(ctx::wg)/server_public.key" |
| 39 | _WG_SERVER_PRIVATE_KEY_FILE="$(ctx::wg)/server_private.key" |
| 40 | _WG_TUNNEL_SPLIT="${_WG_SUBNET}, ${_WG_LAN}" |
| 41 | _WG_TUNNEL_FULL="0.0.0.0/0, ::/0" |
| 42 | } |
| 43 | |
| 44 | # ============================================ |
| 45 | # Validation |
| 46 | # ============================================ |
| 47 | |
| 48 | function config::validate() { |
| 49 | local errors=() |
| 50 | |
| 51 | # Required fields |
| 52 | local endpoint |
| 53 | endpoint=$(config::endpoint) |
| 54 | if [[ -z "$endpoint" ]]; then |
| 55 | errors+=("WG_ENDPOINT is not set — required for client config generation") |
| 56 | elif [[ "$endpoint" != *:* ]]; then |
| 57 | errors+=("WG_ENDPOINT must include port (e.g. wg.example.com:51820)") |
| 58 | fi |
| 59 | |
| 60 | local port |
| 61 | port=$(config::port) |
| 62 | if [[ -z "$port" ]]; then |
| 63 | errors+=("WG_LISTEN_PORT is not set") |
| 64 | elif ! [[ "$port" =~ ^[0-9]+$ ]] || (( port < 1 || port > 65535 )); then |
| 65 | errors+=("WG_LISTEN_PORT must be a valid port number (1-65535)") |
| 66 | fi |
| 67 | |
| 68 | local dns |
| 69 | dns=$(config::dns) |
| 70 | if [[ -z "$dns" ]]; then |
| 71 | errors+=("WG_DNS is not set — required for client configs") |
| 72 | elif ! ip::is_valid "$dns"; then |
| 73 | errors+=("WG_DNS must be a valid IP address") |
| 74 | fi |
| 75 | |
| 76 | local subnet |
| 77 | subnet=$(config::subnet) |
| 78 | if [[ -z "$subnet" ]]; then |
| 79 | errors+=("WG_SUBNET is not set — required for IP allocation") |
| 80 | fi |
| 81 | |
| 82 | local interface |
| 83 | interface=$(config::interface) |
| 84 | if [[ -z "$interface" ]]; then |
| 85 | errors+=("WG_INTERFACE is not set, defaulting to wg0") |
| 86 | fi |
| 87 | |
| 88 | # Warn-only fields |
| 89 | local lan |
| 90 | lan=$(config::lan) |
| 91 | if [[ -z "$lan" ]]; then |
| 92 | log::wg_warning "WG_LAN is not set — some rule features may not work correctly" |
| 93 | fi |
| 94 | |
| 95 | if [[ ${#errors[@]} -gt 0 ]]; then |
| 96 | log::error "wgctl configuration errors:" |
| 97 | for err in "${errors[@]}"; do |
| 98 | printf " ✗ %s\n" "$err" >&2 |
| 99 | done |
| 100 | printf "\n Edit /etc/wireguard/.wgctl/wgctl.conf to fix these issues.\n\n" >&2 |
| 101 | return 1 |
| 102 | fi |
| 103 | |
| 104 | return 0 |
| 105 | } |
| 106 | |
| 107 | # ============================================ |
| 108 | # Load overrides from .wgctl/wgctl.conf |
| 109 | # ============================================ |
| 110 | |
| 111 | function config::load() { |
| 112 | local conf_file |
| 113 | conf_file="$(ctx::data)/wgctl.conf" |
| 114 | [[ ! -f "$conf_file" ]] && return 0 |
| 115 | while IFS='=' read -r key value || [[ -n "$key" ]]; do |
| 116 | [[ "$key" =~ ^[[:space:]]*# ]] && continue |
| 117 | [[ -z "${key// }" ]] && continue |
| 118 | key="${key// /}" |
| 119 | value="${value// /}" |
| 120 | case "$key" in |
| 121 | WG_INTERFACE) _WG_INTERFACE="$value" ;; |
| 122 | WG_ENDPOINT) _WG_ENDPOINT="$value" ;; |
| 123 | WG_DNS) _WG_DNS="$value" ;; |
| 124 | WG_PORT) _WG_PORT="$value" ;; |
| 125 | WG_SUBNET) _WG_SUBNET="$value" ;; |
| 126 | WG_LAN) _WG_LAN="$value" ;; |
| 127 | WG_HANDSHAKE_CHECK_TIME_SEC) _WG_HANDSHAKE_CHECK_TIME_SEC="$value" ;; |
| 128 | ACTIVITY_LOW_BYTES) _ACTIVITY_LOW_BYTES="$value" ;; |
| 129 | ACTIVITY_MED_BYTES) _ACTIVITY_MED_BYTES="$value" ;; |
| 130 | ACTIVITY_HIGH_BYTES) _ACTIVITY_HIGH_BYTES="$value" ;; |
| 131 | DATE_FORMAT) |
| 132 | _FMT_DATE_FORMAT="$value" |
| 133 | fmt::set_date_format "$value" |
| 134 | ;; |
| 135 | esac |
| 136 | done < "$conf_file" |
| 137 | |
| 138 | # Recompute derived values after overrides |
| 139 | _WG_CONFIG="$(ctx::wg)/${_WG_INTERFACE}.conf" |
| 140 | _WG_TUNNEL_SPLIT="${_WG_SUBNET}, ${_WG_LAN}" |
| 141 | } |
| 142 | |
| 143 | # ============================================ |
| 144 | # Device Type → Subnet Mapping |
| 145 | # ============================================ |
| 146 | |
| 147 | declare -gA DEVICE_SUBNETS=( |
| 148 | [desktop]="10.1.1" |
| 149 | [laptop]="10.1.2" |
| 150 | [phone]="10.1.3" |
| 151 | [tablet]="10.1.4" |
| 152 | [guest]="10.1.100" |
| 153 | [guest-desktop]="10.1.101" |
| 154 | [guest-laptop]="10.1.102" |
| 155 | [guest-phone]="10.1.103" |
| 156 | [guest-tablet]="10.1.104" |
| 157 | ) |
| 158 | |
| 159 | # ============================================ |
| 160 | # Tunnel Modes |
| 161 | # ============================================ |
| 162 | |
| 163 | declare -gA DEVICE_TUNNEL_MODE=( |
| 164 | [desktop]="split" |
| 165 | [laptop]="split" |
| 166 | [phone]="split" |
| 167 | [tablet]="split" |
| 168 | [guest]="split" |
| 169 | [guest-desktop]="split" |
| 170 | [guest-laptop]="split" |
| 171 | [guest-phone]="split" |
| 172 | [guest-tablet]="split" |
| 173 | ) |
| 174 | |
| 175 | # ============================================ |
| 176 | # Accessors |
| 177 | # ============================================ |
| 178 | |
| 179 | function config::interface() { echo "$_WG_INTERFACE"; } |
| 180 | function config::config_file() { echo "$_WG_CONFIG"; } |
| 181 | function config::endpoint() { echo "$_WG_ENDPOINT"; } |
| 182 | function config::dns() { echo "$_WG_DNS"; } |
| 183 | function config::port() { echo "$_WG_PORT"; } |
| 184 | function config::subnet() { echo "$_WG_SUBNET"; } |
| 185 | function config::lan() { echo "$_WG_LAN"; } |
| 186 | function config::tunnel_split() { echo "$_WG_TUNNEL_SPLIT"; } |
| 187 | function config::tunnel_full() { echo "$_WG_TUNNEL_FULL"; } |
| 188 | function config::handshake_time_sec() { echo "$_WG_HANDSHAKE_CHECK_TIME_SEC"; } |
| 189 | function config::activity_total_low() { echo "$_ACTIVITY_TOTAL_LOW_BYTES"; } |
| 190 | function config::activity_total_med() { echo "$_ACTIVITY_TOTAL_MED_BYTES"; } |
| 191 | function config::activity_total_high() { echo "$_ACTIVITY_TOTAL_HIGH_BYTES"; } |
| 192 | function config::activity_current_low() { echo "$_ACTIVITY_TOTAL_LOW_BYTES"; } |
| 193 | function config::activity_current_med() { echo "$_ACTIVITY_TOTAL_MED_BYTES"; } |
| 194 | function config::activity_current_high() { echo "$_ACTIVITY_TOTAL_HIGH_BYTES"; } |
| 195 | function config::server_public_key() { cat "$_WG_SERVER_PUBLIC_KEY_FILE"; } |
| 196 | |
| 197 | function config::device_types() { |
| 198 | local types |
| 199 | { set +u; types="${!DEVICE_SUBNETS[@]}"; set -u; } |
| 200 | echo "$types" |
| 201 | } |
| 202 | |
| 203 | function config::is_valid_type() { |
| 204 | local type="$1" |
| 205 | local subnet |
| 206 | subnet=$(config::subnet_for "$type") |
| 207 | [[ -n "$subnet" ]] |
| 208 | } |
| 209 | |
| 210 | function config::is_guest_type() { |
| 211 | local type="$1" |
| 212 | [[ "$type" == "guest" || "$type" == guest-* ]] |
| 213 | } |
| 214 | |
| 215 | function config::subnet_for() { |
| 216 | local type="$1" |
| 217 | local result |
| 218 | { set +u; result="${DEVICE_SUBNETS[$type]:-}"; set -u; } |
| 219 | echo "$result" |
| 220 | } |
| 221 | |
| 222 | function config::default_tunnel_for() { |
| 223 | local type="$1" |
| 224 | local result |
| 225 | { set +u; result="${DEVICE_TUNNEL_MODE[$type]:-split}"; set -u; } |
| 226 | echo "$result" |
| 227 | } |
| 228 | |
| 229 | function config::allowed_ips_for() { |
| 230 | local type="$1" |
| 231 | local tunnel="${2:-}" |
| 232 | |
| 233 | if [[ -z "$tunnel" ]]; then |
| 234 | tunnel=$(config::default_tunnel_for "$type") |
| 235 | fi |
| 236 | |
| 237 | case "$tunnel" in |
| 238 | full) echo "$_WG_TUNNEL_FULL" ;; |
| 239 | split) echo "$_WG_TUNNEL_SPLIT" ;; |
| 240 | *) |
| 241 | log::error "Unknown tunnel mode: ${tunnel} (use 'split' or 'full')" |
| 242 | return 1 |
| 243 | ;; |
| 244 | esac |
| 245 | } |
| 246 | |
| 247 | # ============================================ |
| 248 | # Validation |
| 249 | # ============================================ |
| 250 | |
| 251 | function config::validate() { |
| 252 | if [[ ! -f "$_WG_SERVER_PUBLIC_KEY_FILE" ]]; then |
| 253 | log::error "Server public key not found: ${_WG_SERVER_PUBLIC_KEY_FILE}" |
| 254 | exit 1 |
| 255 | fi |
| 256 | |
| 257 | if [[ ! -f "$_WG_SERVER_PRIVATE_KEY_FILE" ]]; then |
| 258 | log::error "Server private key not found: ${_WG_SERVER_PRIVATE_KEY_FILE}" |
| 259 | exit 1 |
| 260 | fi |
| 261 | |
| 262 | if [[ ! -f "$_WG_CONFIG" ]]; then |
| 263 | log::error "WireGuard config not found: ${_WG_CONFIG}" |
| 264 | exit 1 |
| 265 | fi |
| 266 | } |
| 267 |