Files
wireguard-vpn/wg-sync-policy.sh
T
2026-04-29 07:52:29 +07:00

133 lines
3.8 KiB
Bash

#!/bin/bash
# wg-sync-policy.sh — Reads wg0.conf, validates, generates policy.json atomically
# Fixed: IP validation, atomic write, proper locking, error handling
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/wg-policy-lib.sh"
# ============================================================
# MAIN
# ============================================================
main() {
log_info "Starting policy sync..."
# Validate prerequisites
if [[ ! -f "$WG_CONF" ]]; then
log_error "WireGuard config not found: $WG_CONF"
exit 1
fi
if ! command -v jq &>/dev/null; then
log_error "jq is required but not installed"
exit 1
fi
# Acquire lock
acquire_lock
trap 'release_lock' EXIT
# Backup current policy
backup_policy
# Temporary file for atomic write
local tmp_policy
tmp_policy="$(mktemp /tmp/wg-policy.XXXXXX)"
trap 'rm -f "$tmp_policy" 2>/dev/null; release_lock' EXIT
echo '{"clients":{}}' > "$tmp_policy"
# Parse peers from wg0.conf
# AWK extracts IP and #Access comment per [Peer] block
local parse_errors=0
awk '
BEGIN { RS="\n\\[Peer\\]\n"; FS="\n" }
NR>1 {
ip=""; access=""
for(i=1;i<=NF;i++){
if($i ~ /^AllowedIPs/) {
split($i,a," = ")
gsub(/ /,"",a[2])
split(a[2],b,",")
split(b[1],c,"/")
ip=c[1]
}
if($i ~ /^#Access/) {
sub(/^#Access[ \t]+/, "", $i)
access=$i
}
}
if(ip!="" && ip!="0.0.0.0" && ip!="::") {
printf "%s|%s\n", ip, access
}
}
' "$WG_CONF" | while IFS="|" read -r ip access_string; do
# === VALIDATE CLIENT IP ===
if ! validate_cidr "$ip"; then
log_warn "Invalid client IP skipped: '$ip'"
(( parse_errors++ )) || true
continue
fi
# === PARSE AND VALIDATE ACCESS TARGETS ===
local ACCESS_JSON="[]"
if [[ -n "$access_string" ]]; then
# Split by ; and , then validate each entry
local valid_targets=()
local IFS_OLD="$IFS"
IFS=';,'
read -ra targets <<< "$access_string"
IFS="$IFS_OLD"
for target in "${targets[@]}"; do
# Trim whitespace
target="$(echo "$target" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
[[ -z "$target" ]] && continue
if validate_cidr "$target"; then
valid_targets+=("$target")
else
log_warn "Invalid access target skipped for $ip: '$target'"
(( parse_errors++ )) || true
fi
done
if (( ${#valid_targets[@]} > 0 )); then
ACCESS_JSON=$(printf '%s\n' "${valid_targets[@]}" | jq -R . | jq -s .)
fi
fi
# Write to temp policy
jq --arg ip "$ip" --argjson access "$ACCESS_JSON" \
'.clients[$ip] = {"name": $ip, "access": $access}' \
"$tmp_policy" > "${tmp_policy}.tmp" && mv "${tmp_policy}.tmp" "$tmp_policy"
done
# Validate JSON before atomic move
if ! jq empty "$tmp_policy" 2>/dev/null; then
log_error "Generated JSON is invalid, aborting. Check $tmp_policy"
exit 1
fi
# Atomic move (same filesystem = atomic rename)
mv -f "$tmp_policy" "$POLICY_FILE"
log_info "policy.json updated successfully"
if (( parse_errors > 0 )); then
log_warn "$parse_errors validation errors encountered (see warnings above)"
fi
local client_count
client_count=$(jq '(.clients // {}) | length' "$POLICY_FILE")
log_info "Total clients in policy: $client_count"
}
main "$@"