331 lines
7.1 KiB
Bash
331 lines
7.1 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() {
|
|
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::debug "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::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"
|
|
}
|
|
|
|
# ============================================
|
|
# 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
|
|
}
|
|
|
|
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"
|
|
}
|