#!/usr/bin/env bash # wg_client_install — Device-side: instala wg0.conf en /etc/wireguard/, habilita # systemd wg-quick@, verifica handshake con hub. Idempotente. # Acepta config por path o stdin ("-"). # Exit 0 = éxito (installed o already-configured), 1 = error fatal. wg_client_install() { local config_src="${1:--}" local iface="${2:-wg0}" local conf_dest="/etc/wireguard/${iface}.conf" local config_content="" hub_endpoint="" handshake_seen="false" _wg_ci_log() { echo "[wg_client_install] $*" >&2; } # ── Prereq: wg debe estar instalado ────────────────────────────────────── if ! command -v wg &>/dev/null; then _wg_ci_log "ERROR: 'wg' no encontrado. Ejecuta wg_install primero." return 1 fi # ── Leer contenido del .conf ────────────────────────────────────────────── if [[ "${config_src}" == "-" ]]; then _wg_ci_log "Leyendo config desde stdin" config_content=$(cat) || { _wg_ci_log "ERROR: fallo al leer stdin"; return 1; } elif [[ -f "${config_src}" ]]; then _wg_ci_log "Leyendo config desde ${config_src}" config_content=$(cat "${config_src}") || { _wg_ci_log "ERROR: fallo al leer ${config_src}"; return 1; } else _wg_ci_log "ERROR: '${config_src}' no es un path existente ni '-' (stdin)" return 1 fi if [[ -z "${config_content}" ]]; then _wg_ci_log "ERROR: contenido de config vacío" return 1 fi # ── Extraer endpoint del hub para incluirlo en el JSON de salida ────────── hub_endpoint=$(printf '%s\n' "${config_content}" | grep -m1 '^Endpoint\s*=' | sed 's/.*=\s*//' | tr -d '[:space:]' || true) # ── Idempotencia: comparar con conf existente ───────────────────────────── if [[ -f "${conf_dest}" ]]; then local existing_content existing_content=$(sudo cat "${conf_dest}" 2>/dev/null || cat "${conf_dest}" 2>/dev/null || true) if [[ "${existing_content}" == "${config_content}" ]]; then _wg_ci_log "Configuración idéntica ya presente en ${conf_dest}; nada que hacer" printf '{"status":"already-configured","interface":"%s","hub_endpoint":"%s","handshake_seen":false}\n' \ "${iface}" "${hub_endpoint}" return 0 fi # Contenido difiere → backup + rewrite local backup="${conf_dest}.bak.$(date +%Y%m%d%H%M%S)" _wg_ci_log "Configuración existente difiere; backup → ${backup}" sudo cp "${conf_dest}" "${backup}" \ || { _wg_ci_log "ERROR: no se pudo hacer backup de ${conf_dest}"; return 1; } fi # ── Crear directorio y escribir conf ───────────────────────────────────── sudo mkdir -p "/etc/wireguard" \ || { _wg_ci_log "ERROR: no se pudo crear /etc/wireguard"; return 1; } printf '%s\n' "${config_content}" | sudo tee "${conf_dest}" >/dev/null \ || { _wg_ci_log "ERROR: no se pudo escribir ${conf_dest}"; return 1; } sudo chmod 600 "${conf_dest}" \ || { _wg_ci_log "WARN: no se pudo chmod 600 ${conf_dest}"; } _wg_ci_log "Config escrita en ${conf_dest} (chmod 600)" # ── Habilitar + arrancar systemd unit ───────────────────────────────────── if ! command -v systemctl &>/dev/null; then _wg_ci_log "WARN: systemctl no disponible." _wg_ci_log " En WSL2 sin systemd: ejecuta 'sudo wg-quick up ${iface}' manualmente." _wg_ci_log " Para autostart en WSL2: añade 'sudo wg-quick up ${iface}' a ~/.bashrc o usa WSL2 con systemd habilitado." printf '{"status":"installed-no-systemd","interface":"%s","hub_endpoint":"%s","handshake_seen":false}\n' \ "${iface}" "${hub_endpoint}" return 0 fi _wg_ci_log "Habilitando y arrancando wg-quick@${iface}" if ! sudo systemctl enable --now "wg-quick@${iface}" 2>&1 | tee /dev/stderr >&2; then _wg_ci_log "ERROR: systemctl enable --now wg-quick@${iface} falló." _wg_ci_log " En WSL2: asegúrate de tener kernel >= 5.6 y systemd habilitado (/etc/wsl.conf: [boot] systemd=true)." _wg_ci_log " Si NetworkManager gestiona ${iface}: añade 'unmanaged-devices=interface-name:${iface}' a /etc/NetworkManager/conf.d/99-wg.conf" return 1 fi _wg_ci_log "wg-quick@${iface} habilitado y activo" # ── Esperar handshake (hasta 10 s) ──────────────────────────────────────── local deadline=$(( $(date +%s) + 10 )) _wg_ci_log "Esperando handshake en ${iface} (timeout 10s)..." while [[ $(date +%s) -lt ${deadline} ]]; do local hs_output hs_output=$(sudo wg show "${iface}" latest-handshakes 2>/dev/null || true) # latest-handshakes devuelve " "; ts > 0 = handshake visto if printf '%s\n' "${hs_output}" | awk '{print $2}' | grep -qE '^[1-9][0-9]+$'; then handshake_seen="true" _wg_ci_log "Handshake confirmado en ${iface}" break fi sleep 1 done if [[ "${handshake_seen}" == "false" ]]; then _wg_ci_log "WARN: timeout esperando handshake en ${iface}. La interfaz está activa pero el hub no ha respondido aún." _wg_ci_log " Verifica: endpoint accesible, hub corriendo, claves correctas." printf '{"status":"installed-no-handshake","interface":"%s","hub_endpoint":"%s","handshake_seen":false}\n' \ "${iface}" "${hub_endpoint}" return 0 fi printf '{"status":"installed","interface":"%s","hub_endpoint":"%s","handshake_seen":true}\n' \ "${iface}" "${hub_endpoint}" return 0 }