621e8895c9
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
172 lines
8.3 KiB
Bash
172 lines
8.3 KiB
Bash
#!/usr/bin/env bash
|
|
# wg_hub_setup — Configura el host como hub WireGuard (servidor central).
|
|
# Crea /etc/wireguard/wg0.conf con [Interface] block, abre UDP en firewall,
|
|
# habilita ip_forward persistente, arranca y verifica wg-quick@wg0.
|
|
# Idempotente: si el conf existe con la misma PrivateKey -> no-op.
|
|
# Emite JSON a stdout. Logs a stderr con prefijo [wg_hub_setup].
|
|
# Exit 0 = éxito, 1 = fallo.
|
|
|
|
wg_hub_setup() {
|
|
local private_key="${1:-}"
|
|
local subnet_cidr="${2:-10.42.0.1/24}"
|
|
local listen_port="${3:-51820}"
|
|
|
|
_wg_hub_log() { echo "[wg_hub_setup] $*" >&2; }
|
|
|
|
# ── Validación de entradas ──────────────────────────────────────────────
|
|
|
|
# private_key: base64 estándar de 44 caracteres (32 bytes)
|
|
if [[ -z "${private_key}" ]]; then
|
|
_wg_hub_log "ERROR: private_key requerida (base64 44 chars, generada por wg genkey)"
|
|
return 1
|
|
fi
|
|
if ! [[ "${private_key}" =~ ^[A-Za-z0-9+/]{43}=$ ]]; then
|
|
_wg_hub_log "ERROR: private_key no parece base64 válida (se esperan 44 chars terminando en '=')"
|
|
return 1
|
|
fi
|
|
|
|
# subnet_cidr: 10.x.x.x/nn
|
|
if ! [[ "${subnet_cidr}" =~ ^10\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]]; then
|
|
_wg_hub_log "ERROR: subnet_cidr debe ser 10.x.x.x/nn, recibido: '${subnet_cidr}'"
|
|
return 1
|
|
fi
|
|
|
|
# listen_port: 1024-65535
|
|
if ! [[ "${listen_port}" =~ ^[0-9]+$ ]] || (( listen_port < 1024 || listen_port > 65535 )); then
|
|
_wg_hub_log "ERROR: listen_port debe ser un entero entre 1024 y 65535, recibido: '${listen_port}'"
|
|
return 1
|
|
fi
|
|
|
|
# ── Verificar que wireguard-tools esté instalado ────────────────────────
|
|
if ! command -v wg &>/dev/null; then
|
|
_wg_hub_log "ERROR: 'wg' no encontrado. Ejecuta wg_install primero."
|
|
return 1
|
|
fi
|
|
|
|
if ! command -v wg-quick &>/dev/null; then
|
|
_wg_hub_log "ERROR: 'wg-quick' no encontrado. Instala wireguard-tools."
|
|
return 1
|
|
fi
|
|
|
|
# ── Extraer hub_ip (parte sin CIDR prefix) y determinar config_path ────
|
|
local hub_ip="${subnet_cidr%%/*}"
|
|
local config_path="/etc/wireguard/wg0.conf"
|
|
local interface="wg0"
|
|
local action_status=""
|
|
|
|
# ── Idempotencia: comparar PrivateKey existente ─────────────────────────
|
|
if [[ -f "${config_path}" ]]; then
|
|
local existing_key
|
|
existing_key=$(sudo grep -E '^\s*PrivateKey\s*=' "${config_path}" 2>/dev/null \
|
|
| head -n1 | sed 's/.*=\s*//')
|
|
if [[ "${existing_key}" == "${private_key}" ]]; then
|
|
_wg_hub_log "Config existente con misma PrivateKey — no-op (status=already-configured)"
|
|
printf '{"status":"already-configured","config_path":"%s","interface":"%s","hub_ip":"%s"}\n' \
|
|
"${config_path}" "${interface}" "${hub_ip}"
|
|
return 0
|
|
else
|
|
_wg_hub_log "Config existente con PrivateKey DIFERENTE — haciendo backup y reescribiendo"
|
|
local backup_path="${config_path}.bak.$(date +%Y%m%d%H%M%S)"
|
|
sudo cp "${config_path}" "${backup_path}" \
|
|
|| { _wg_hub_log "ERROR: no se pudo hacer backup en ${backup_path}"; return 1; }
|
|
_wg_hub_log "Backup guardado en ${backup_path}"
|
|
action_status="reconfigured"
|
|
fi
|
|
else
|
|
action_status="configured"
|
|
fi
|
|
|
|
# ── Asegurar que /etc/wireguard existe con permisos correctos ───────────
|
|
if [[ ! -d /etc/wireguard ]]; then
|
|
sudo mkdir -p /etc/wireguard \
|
|
|| { _wg_hub_log "ERROR: no se pudo crear /etc/wireguard"; return 1; }
|
|
sudo chmod 700 /etc/wireguard
|
|
_wg_hub_log "Directorio /etc/wireguard creado"
|
|
fi
|
|
|
|
# ── Escribir /etc/wireguard/wg0.conf ────────────────────────────────────
|
|
_wg_hub_log "Escribiendo ${config_path} (Address=${subnet_cidr}, ListenPort=${listen_port})"
|
|
sudo tee "${config_path}" > /dev/null <<EOF
|
|
[Interface]
|
|
Address = ${subnet_cidr}
|
|
ListenPort = ${listen_port}
|
|
PrivateKey = ${private_key}
|
|
SaveConfig = false
|
|
|
|
# NAT: permite que los peers accedan a internet via este hub (opcional, comentar si no se desea)
|
|
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
|
|
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
|
|
EOF
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
_wg_hub_log "ERROR: no se pudo escribir ${config_path}"
|
|
return 1
|
|
fi
|
|
|
|
sudo chmod 600 "${config_path}" \
|
|
|| { _wg_hub_log "ERROR: chmod 600 ${config_path} falló"; return 1; }
|
|
_wg_hub_log "Permisos 600 aplicados a ${config_path}"
|
|
|
|
# ── Habilitar ip_forward persistente ────────────────────────────────────
|
|
local sysctl_file="/etc/sysctl.d/99-wireguard.conf"
|
|
if [[ ! -f "${sysctl_file}" ]] || ! grep -q "net.ipv4.ip_forward" "${sysctl_file}" 2>/dev/null; then
|
|
_wg_hub_log "Habilitando ip_forward en ${sysctl_file}"
|
|
echo "net.ipv4.ip_forward = 1" | sudo tee "${sysctl_file}" > /dev/null \
|
|
|| { _wg_hub_log "ERROR: no se pudo escribir ${sysctl_file}"; return 1; }
|
|
fi
|
|
sudo sysctl -p "${sysctl_file}" >&2 \
|
|
|| _wg_hub_log "WARN: sysctl -p falló (puede ignorarse si el kernel ya tiene ip_forward=1)"
|
|
|
|
# ── Abrir puerto en firewall ─────────────────────────────────────────────
|
|
if command -v ufw &>/dev/null && sudo ufw status 2>/dev/null | grep -q "Status: active"; then
|
|
_wg_hub_log "ufw activo — abriendo UDP/${listen_port}"
|
|
sudo ufw allow "${listen_port}/udp" >&2 \
|
|
|| _wg_hub_log "WARN: ufw allow ${listen_port}/udp falló (verificar manualmente)"
|
|
elif command -v iptables &>/dev/null; then
|
|
_wg_hub_log "ufw inactivo — usando iptables para abrir UDP/${listen_port}"
|
|
sudo iptables -C INPUT -p udp --dport "${listen_port}" -j ACCEPT 2>/dev/null \
|
|
|| sudo iptables -A INPUT -p udp --dport "${listen_port}" -j ACCEPT >&2 \
|
|
|| _wg_hub_log "WARN: iptables INPUT rule falló (verificar manualmente)"
|
|
else
|
|
_wg_hub_log "WARN: ni ufw ni iptables disponibles — abre el puerto ${listen_port}/udp manualmente"
|
|
fi
|
|
|
|
# ── Detener interfaz si estaba corriendo (para aplicar nueva config) ────
|
|
if sudo wg show "${interface}" &>/dev/null 2>&1; then
|
|
_wg_hub_log "Interfaz ${interface} activa — deteniendo antes de reconfigurar"
|
|
sudo systemctl stop "wg-quick@${interface}" 2>/dev/null \
|
|
|| sudo wg-quick down "${interface}" 2>/dev/null \
|
|
|| _wg_hub_log "WARN: no se pudo detener ${interface} (puede que no estuviera activa)"
|
|
fi
|
|
|
|
# ── Habilitar y arrancar wg-quick@wg0 ────────────────────────────────────
|
|
_wg_hub_log "Habilitando systemd unit wg-quick@${interface}"
|
|
sudo systemctl enable "wg-quick@${interface}" >&2 \
|
|
|| { _wg_hub_log "ERROR: systemctl enable wg-quick@${interface} falló"; return 1; }
|
|
|
|
_wg_hub_log "Arrancando wg-quick@${interface}"
|
|
sudo systemctl start "wg-quick@${interface}" >&2 \
|
|
|| { _wg_hub_log "ERROR: systemctl start wg-quick@${interface} falló"; return 1; }
|
|
|
|
# ── Verificar que la interfaz está UP ────────────────────────────────────
|
|
local retries=5
|
|
local up=0
|
|
for (( i=0; i<retries; i++ )); do
|
|
if sudo wg show "${interface}" &>/dev/null 2>&1; then
|
|
up=1
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
if [[ "${up}" -eq 0 ]]; then
|
|
_wg_hub_log "ERROR: 'wg show ${interface}' falló tras ${retries}s — la interfaz no arrancó"
|
|
return 1
|
|
fi
|
|
|
|
_wg_hub_log "Interfaz ${interface} UP (status=${action_status})"
|
|
printf '{"status":"%s","config_path":"%s","interface":"%s","hub_ip":"%s"}\n' \
|
|
"${action_status}" "${config_path}" "${interface}" "${hub_ip}"
|
|
return 0
|
|
}
|