wgctl/modules/peers.module.sh
2026-05-06 23:02:12 +00:00

234 lines
5 KiB
Bash

#!/usr/bin/env bash
# ============================================
# Client Config
# ============================================
function peers::create_client_config() {
local name="$1"
local type="$2"
local ip="$3"
local allowed_ips="${4:-$(config::allowed_ips_for "$type" "$(config::default_tunnel_for "$type")")}"
local conf
conf="$(ctx::clients)/${name}.conf"
if [[ -f "$conf" ]]; then
log::wg_warning "Client config already exists: ${name}"
return 1
fi
local private_key
private_key=$(keys::private "$name")
local server_public_key
server_public_key=$(config::server_public_key)
cat > "$conf" <<EOF
[Interface]
PrivateKey = ${private_key}
Address = ${ip}/32
DNS = $(config::dns)
[Peer]
PublicKey = ${server_public_key}
Endpoint = $(config::endpoint)
AllowedIPs = ${allowed_ips}
PersistentKeepalive = 25
EOF
chmod 600 "$conf"
log::wg_add "Created client config: ${name} (${ip})"
}
function peers::remove_client_config() {
local name="$1"
local conf
conf="$(ctx::clients)/${name}.conf"
if [[ ! -f "$conf" ]]; then
log::wg_warning "Client config not found: ${name}"
return 1
fi
rm -f "$conf"
log::wg_remove "Removed client config: ${name}"
}
# ============================================
# Server Config (wg0.conf) Peer Management
# ============================================
function peers::cleanup_config() {
local config
config=$(config::config_file)
python3 -c "
import re
config = open('${config}').read()
# Normalize multiple blank lines to single blank line
config = re.sub(r'\n{3,}', '\n\n', config)
# Ensure file ends with single newline
config = config.rstrip('\n') + '\n'
open('${config}', 'w').write(config)
"
}
function peers::add_to_server() {
local name="$1"
local public_key="$2"
local ip="$3"
local config
config=$(config::config_file)
cat >> "$config" <<EOF
[Peer]
# ${name}
PublicKey = ${public_key}
AllowedIPs = ${ip}/32
EOF
log::wg_add "Added peer to server config: ${name}"
}
function peers::remove_block() {
local name="$1"
local config
config=$(config::config_file)
python3 -c "
import re
config = open('${config}').read()
pattern = r'\n\[Peer\]\n# ${name}\n[^\n]+\n[^\n]+\n'
result = re.sub(pattern, '\n', config)
open('${config}', 'w').write(result)
"
}
function peers::remove_from_server() {
local name="$1"
peers::remove_block "$name"
peers::cleanup_config
log::wg_remove "Removed peer from server config: ${name}"
}
# ============================================
# Listing
# ============================================
function peers::list() {
local dir
dir="$(ctx::clients)"
if [[ -z "$(ls -A "$dir"/*.conf 2>/dev/null)" ]]; then
log::wg_list "No clients configured"
return 0
fi
for conf in "${dir}"/*.conf; do
local client_name
client_name=$(basename "$conf" .conf)
local ip
ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
local public_key
public_key=$(keys::public "$client_name" 2>/dev/null || echo "unknown")
# Determine type from IP
local type="unknown"
for t in $(config::device_types); do
local subnet
subnet=$(config::subnet_for "$t")
if string::starts_with "$ip" "$subnet"; then
type="$t"
break
fi
done
printf " %-30s %-15s %-10s %s\n" \
"$client_name" "$ip" "$type" "$public_key"
done
}
function peers::list_by_type() {
local filter_type="$1"
local dir
dir="$(ctx::clients)"
for conf in "${dir}"/*.conf; do
local client_name
client_name=$(basename "$conf" .conf)
local ip
ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
local subnet
subnet=$(config::subnet_for "$filter_type")
if string::starts_with "$ip" "$subnet"; then
printf " %-30s %-15s\n" "$client_name" "$ip"
fi
done
}
function peers::exists_in_server() {
local name="$1"
grep -q "^# ${name}$" "$(config::config_file)"
}
function peers::is_blocked() {
local name="$1"
! peers::exists_in_server "$name"
}
# ============================================
# Name + Type Parsing
# ============================================
function peers::resolve_name() {
local name="$1"
local type="${2:-}"
if [[ -n "$type" ]]; then
if ! config::is_valid_type "$type"; then
log::error "Invalid device type: ${type}"
return 1
fi
echo "${type}-${name}"
else
echo "$name"
fi
}
function peers::require_exists() {
local name="$1"
if [[ ! -f "$(ctx::clients)/${name}.conf" ]]; then
log::error "Client not found: ${name}"
return 1
fi
}
function peers::resolve_and_require() {
local name="$1"
local type="${2:-}"
local resolved
resolved=$(peers::resolve_name "$name" "$type") || return 1
peers::require_exists "$resolved" || return 1
echo "$resolved"
}
# ============================================
# Live Reload
# ============================================
function peers::reload() {
wg syncconf "$(config::interface)" <(wg-quick strip "$(config::interface)")
log::wg_success "WireGuard config reloaded"
}