#!/usr/bin/env bash
# =============================================================================
#  demo-04 · El borde con HAProxy
# =============================================================================
#  A qué punto del guion sirve: puntos 16, 17, 18 y 19
#    · 16 «HAProxy en bare metal»            → el borde corre en el HOST,
#                                              fuera del contenedor.
#    · 17 «Directo al 443»                   → el puerto privilegiado lo abre
#                                              HAProxy, en el metal.
#    · 18 «Traefik se levanta en el 8443»    → el contenedor solo publica un
#                                              puerto alto.
#    · 19 «Y no necesito reglas especiales»  → sin ip_unprivileged_port_start,
#                                              sin setcap, sin iptables.
#
#  Qué se ve en pantalla:
#    1. `config/haproxy.cfg` entero, con las tres líneas clave señaladas.
#    2. El recorrido: :443 → HAProxy → `send-proxy-v2` → Traefik :8443.
#    3. La validación de sintaxis de verdad, con `haproxy -c` y un control
#       negativo (un fichero roto a propósito) para demostrar que el chequeo
#       no dice «OK» a todo.
#
#  Estado: VERIFICADO en local. La validación se ejecuta con HAProxy 3.4.7
#  dentro de un contenedor EFÍMERO (`--rm`, sin puertos publicados y sin tocar
#  el host). Salida real en `verificacion/demo-04-salida.txt`.
#  En esta máquina NO hay binario `haproxy` ni `docker` instalados: por eso la
#  validación va por contenedor y ese camino es el que está verificado.
#
#  Si no hay ni `haproxy` ni `podman` ni `docker`, el script NO falla: avisa de
#  que la sintaxis queda sin validar y muestra el comando exacto para hacerlo.
# =============================================================================

set -u

AQUI="$(cd "$(dirname "$0")" && pwd)"
CFG="$AQUI/config/haproxy.cfg"
# Imagen del validador. Se mira primero la caché local y, si no hay ninguna, se
# descarga. Son las mismas versiones que el binario oficial de HAProxy:
#   · docker.io/library/haproxy:lts-alpine  → la canónica
#   · mirror.gcr.io/library/haproxy:lts-alpine → espejo público (el Hub devolvió
#     error de autenticación al preparar este material, así que el espejo es lo
#     que acabó funcionando)
#   · docker.io/library/haproxy:latest      → por si ya la tienes por casa
CANDIDATAS=(
    "docker.io/library/haproxy:lts-alpine"
    "mirror.gcr.io/library/haproxy:lts-alpine"
    "docker.io/library/haproxy:latest"
)
IMAGEN="${HAPROXY_IMAGEN:-${CANDIDATAS[0]}}"
TMP="${TMPDIR:-/tmp/opencode}"

resolver_imagen() {  # elige la primera imagen disponible (caché primero, luego descarga)
    local img
    for img in "${CANDIDATAS[@]}"; do
        if podman image exists "$img" >/dev/null 2>&1; then IMAGEN="$img"; return 0; fi
    done
    for img in "${CANDIDATAS[@]}"; do
        printf '   (descargando %s…)\n' "$img"
        if podman pull "$img" >/dev/null 2>&1; then IMAGEN="$img"; return 0; fi
    done
    return 1
}

titulo() { printf '\n\033[1m── %s\033[0m\n' "$1"; }
orden()  { printf '\n\033[1m$ %s\033[0m\n' "$1"; }
nota()   { printf '   ↳ %s\n' "$1"; }
ok()     { printf '   \033[1m✔ %s\033[0m\n' "$1"; }
fallo()  { printf '   \033[1m✘ %s\033[0m\n' "$1"; }

rc_final=0

printf '\033[1mEl borde en bare metal\033[0m  (puntos 16, 17, 18 y 19)\n'

# -----------------------------------------------------------------------------
titulo "1 · El fichero del borde"
# -----------------------------------------------------------------------------
orden "cat config/haproxy.cfg"
cat "$CFG"

# -----------------------------------------------------------------------------
titulo "2 · Las cuatro líneas que sostienen el diseño"
# -----------------------------------------------------------------------------
orden "grep -nE 'mode tcp|bind \\*:443|send-proxy-v2|127.0.0.1:8443' config/haproxy.cfg"
grep -nE 'mode tcp|bind \*:443|send-proxy-v2|127.0.0.1:8443' "$CFG"
nota "'bind *:443' → el puerto privilegiado lo abre el HOST (punto 17)."
nota "'mode tcp'    → no descifra: pasa la conexión tal cual."
nota "'send-proxy-v2' → avisa al siguiente salto de quién llama."
nota "'127.0.0.1:8443' → Traefik, en rootless, en un puerto alto (punto 18)."

# Comprobación de que están de verdad (no solo de que el grep los encuentre).
for patron in 'bind \*:443' 'send-proxy-v2' '127\.0\.0\.1:8443'; do
    if grep -qE "$patron" "$CFG"; then
        ok "presente: $patron"
    else
        fallo "falta: $patron"
        rc_final=1
    fi
done

# -----------------------------------------------------------------------------
titulo "3 · Los tres apaños que NO hacen falta (punto 19)"
# -----------------------------------------------------------------------------
orden "grep -nE 'ip_unprivileged_port_start|setcap|iptables|nftables' config/haproxy.cfg   # solo aparecen en los comentarios"
if grep -nE 'ip_unprivileged_port_start|setcap|iptables|nftables' "$CFG"; then
    nota "aparecen mencionados en los comentarios, para explicar por qué no se usan;"
    nota "ninguno está como directiva activa (en HAProxy no existen como directivas)."
else
    nota "ni siquiera se mencionan."
fi
nota "El motivo, en una frase: el contenedor no abre ningún puerto < 1024,"
nota "así que no hay nada que habilitar dentro del espacio de nombres."

# -----------------------------------------------------------------------------
titulo "4 · Validar la sintaxis con HAProxy de verdad"
# -----------------------------------------------------------------------------
validar() {  # validar <fichero>  → 0 si HAProxy acepta la configuración
    if command -v haproxy >/dev/null 2>&1; then
        haproxy -c -f "$1"
    elif command -v podman >/dev/null 2>&1; then
        podman run --rm -v "$1:/usr/local/etc/haproxy/haproxy.cfg:ro" \
            "$IMAGEN" haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg
    elif command -v docker >/dev/null 2>&1; then
        docker run --rm -v "$1:/usr/local/etc/haproxy/haproxy.cfg:ro" \
            "$IMAGEN" haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg
    else
        return 127
    fi
}

if command -v haproxy >/dev/null 2>&1; then
    orden "haproxy -v"
    haproxy -v | head -2
    VALIDADOR="haproxy (binario del sistema)"
elif command -v podman >/dev/null 2>&1; then
    [ -n "${HAPROXY_IMAGEN:-}" ] || resolver_imagen || true
    if podman run --rm "$IMAGEN" haproxy -v >/dev/null 2>&1; then
        orden "podman run --rm $IMAGEN haproxy -v"
        podman run --rm "$IMAGEN" haproxy -v 2>&1 | head -2
        VALIDADOR="HAProxy en contenedor efímero (podman)"
        nota "El contenedor se borra solo (--rm): no deja nada en el host."
        nota "Imagen usada: $IMAGEN"
    else
        VALIDADOR=""
    fi
elif command -v docker >/dev/null 2>&1; then
    if docker run --rm "$IMAGEN" haproxy -v >/dev/null 2>&1; then
        orden "docker run --rm $IMAGEN haproxy -v"
        docker run --rm "$IMAGEN" haproxy -v 2>&1 | head -2
        VALIDADOR="HAProxy en contenedor efímero (docker)"
    else
        VALIDADOR=""
    fi
else
    VALIDADOR=""
fi

if [ -n "$VALIDADOR" ]; then
    orden "haproxy -c -f config/haproxy.cfg"
    if validar "$CFG"; then
        ok "configuración válida (exit 0). Validador: $VALIDADOR"
    else
        fallo "HAProxy NO acepta la configuración."
        rc_final=1
    fi

    # --- control negativo: si el chequeo dijera «OK» a todo, no valdría nada ---
    orden "control negativo: una copia con 'send-proxy-v9' (no existe)"
    ROTO="$(mktemp "$TMP/haproxy-roto-XXXXXX.cfg")"
    sed 's/send-proxy-v2/send-proxy-v9/' "$CFG" > "$ROTO"
    if validar "$ROTO" >/dev/null 2>&1; then
        fallo "ha aceptado una directiva inventada: la validación no vale."
        rc_final=1
    else
        ok "la copia rota se rechaza → el chequeo es real (exit != 0)."
    fi
    rm -f "$ROTO"
else
    nota "No hay 'haproxy', ni 'podman', ni 'docker' en esta máquina:"
    nota "la sintaxis queda SIN VALIDAR. Para validarla, en cualquier equipo:"
    nota "  podman run --rm -v \"\$PWD/config/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro\" \\"
    nota "      $IMAGEN haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg"
fi

# -----------------------------------------------------------------------------
titulo "5 · El recorrido, para contarlo en voz alta"
# -----------------------------------------------------------------------------
cat <<'TEXTO'
   visitante ──:443──► HAProxy (HOST, servicio del sistema)
                            │  mode tcp: no descifra nada
                            │  send-proxy-v2: antepone quién llama
                            ▼
                       Traefik :8443 (rootless, en su contenedor)
                            │  proxyProtocol.trustedIPs: solo ese borde
                            │  forwardedHeaders: lee la IP de verdad
                            ▼
                       tu web, tus logs y la analítica

   · El 443 no lo pelea el contenedor: lo abre el host. Por eso no hacen falta
     ip_unprivileged_port_start, ni setcap, ni reglas de iptables.
   · Sin certificados duplicados: HAProxy no termina TLS, solo lo atraviesa.
   · Cada cosa en la capa que le toca: el puerto en el metal, el enrutado en el
     contenedor rootless.
TEXTO

printf '\n'
if [ "$rc_final" -eq 0 ]; then
    printf '\033[1mResultado: el diseño del borde está completo y validado.\033[0m\n\n'
else
    printf '\033[1mResultado: hay algo que revisar (ver arriba).\033[0m\n\n'
fi
exit "$rc_final"
