wgctl/modules/peers.module.sh

309 lines
6.8 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::debug "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::debug "Removed client config: ${name}"
}
# ============================================
# Server Config (wg0.conf) Peer Management
# ============================================
function peers::cleanup_config() {
json::cleanup_config "$(config::config_file)"
}
function peers::add_to_server() {
local name="${1:?name required}"
local public_key="${2:?public_key required}"
local ip="${3:?ip required}"
local config
config=$(config::config_file)
cat >> "$config" <<EOF
[Peer]
# ${name}
PublicKey = ${public_key}
AllowedIPs = ${ip}/32
EOF
log::debug "Added peer to server config: ${name}"
}
function peers::remove_block() {
local name="${1:?name required}"
json::remove_peer_block "$(config::config_file)" "$name"
}
function peers::remove_from_server() {
local name="${1:?name required}"
peers::remove_block "$name"
peers::cleanup_config
log::debug "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" && return 1 || return 0
}
# ============================================
# Default Rule
# ============================================
function peers::get_type() {
local name="$1"
local ip
ip=$(peers::get_ip "$name")
[[ -z "$ip" ]] && echo "unknown" && return
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
echo "$type"
}
function peers::default_rule() {
local name="$1"
local type
type=$(peers::get_type "$name")
config::is_guest_type "$type" && echo "guest" || echo "user"
}
function peers::effective_rule() {
local name="$1"
local rule
rule=$(peers::get_meta "$name" "rule")
echo "${rule:---}"
}
# ============================================
# Query
# ============================================
function peers::all() {
local dir
dir="$(ctx::clients)"
for conf in "${dir}"/*.conf; do
[[ -f "$conf" ]] || continue
basename "$conf" .conf
done
}
function peers::with_rule() {
local rule="$1"
while IFS= read -r name; do
local effective
effective=$(peers::effective_rule "$name")
[[ "$effective" == "$rule" ]] && echo "$name"
done < <(peers::all)
}
function peers::get_ip() {
local name="$1"
grep "^Address" "$(ctx::clients)/${name}.conf" 2>/dev/null \
| awk '{print $3}' | cut -d'/' -f1 || true
}
function peers::find_by_ip() {
local target_ip="$1"
while IFS= read -r name; do
local ip
ip=$(peers::get_ip "$name")
[[ "$ip" == "$target_ip" ]] && echo "$name" && return 0
done < <(peers::all)
}
# ============================================
# 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"
}
# ============================================
# Helpers - Meta File
# ============================================
function peers::meta_path() {
local name="$1"
echo "$(ctx::meta)/${name}.meta"
}
function peers::get_meta() {
local name="$1" key="$2"
json::get "$(peers::meta_path "$name")" "$key"
}
function peers::set_meta() {
local name="$1" key="$2" value="$3"
json::set "$(peers::meta_path "$name")" "$key" "$value"
}
function peers::remove_meta() {
local name="$1"
rm -f "$(peers::meta_path "$name")"
}
# ============================================
# Live Reload
# ============================================
function peers::reload() {
wg syncconf "$(config::interface)" <(wg-quick strip "$(config::interface)")
log::debug "WireGuard config reloaded"
}