#!/bin/bash
# fix-ssh-keys.sh -- Recuperacion de acceso SSH de la flota Clusteres.
#
# Causa: /opt/ssh-keys/auth.sh autenticaba contra api.github.com con un PAT que
# expiro. Al fallar, jq -r '.content' devolvia "null", base64 -d producia basura
# y sshd se quedaba sin ninguna llave -> "Permission denied (publickey)".
# El repo Clusteres/ssh-keys es PUBLICO: el token nunca fue necesario.
#
# auth.sh pasa a leer una cache local; el refresco contra GitHub lo hace cron
# cada 5 minutos. Funciona detras del firewall del robot de Hetzner que solo
# abre los rangos de GitHub.
#
# Uso (como root, por consola del nodo o del servidor):
#   curl -sfL https://dropshare.zhconsultores.com/fix-ssh-keys.sh | bash
#
# En un nodo Proxmox repara el nodo Y todos sus contenedores encendidos.

set -u
P=/tmp/ssh-keys-fix-payload.sh

cat >"$P" <<'PAYLOAD_EOF'
#!/bin/bash
# Se ejecuta EN el host destino. Reemplaza el auth.sh que depende de un PAT de
# GitHub (expirado) por una version que lee una cache local, y programa el
# refresco de esa cache contra GitHub.
# Idempotente y seguro: valida el auth.sh nuevo ANTES de ponerlo en su lugar.

D=/opt/ssh-keys
HUELLA="SHA256:dHC/ENvMw9/VDnUUoNgVpRi2qUiRin584vDaabMI38Y"

[ -d "$D" ] || { echo "SKIP sin-opt-ssh-keys"; exit 0; }

# --- 1. Refrescador de cache (lo corre root por cron) -----------------------
# Prueba raw primero (sin limite de cuota) y cae a api.github.com, que es el
# unico alcanzable desde los nodos Clusteres: su firewall en el robot de Hetzner
# solo abre 140.82.96.0/19, donde vive api.github.com pero NO raw (185.199.108.0/22).
cat >"$D/refresh-cache.sh" <<'SCRIPT'
#!/bin/bash
# Refresca /opt/ssh-keys/root.pub.cache, la unica fuente que lee auth.sh.
# Nunca sobreescribe la cache con contenido invalido.

CACHE="/opt/ssh-keys/root.pub.cache"

valido() { grep -Eq '^(ssh-|ecdsa-|sk-)' <<<"$1"; }

for url in \
	"https://raw.githubusercontent.com/Clusteres/ssh-keys/main/root.pub" \
	"https://api.github.com/repos/Clusteres/ssh-keys/contents/root.pub?ref=main"; do
	keys=$(curl -sf --max-time 10 -H "Accept: application/vnd.github.raw" "$url" 2>/dev/null)
	if valido "$keys"; then
		printf '%s\n' "$keys" >"$CACHE.tmp" || exit 0
		chmod 644 "$CACHE.tmp"
		mv -f "$CACHE.tmp" "$CACHE"
		exit 0
	fi
done
exit 0
SCRIPT
chown root:root "$D/refresh-cache.sh"
chmod 755 "$D/refresh-cache.sh"

# --- 2. Sembrar la cache ANTES de tocar auth.sh -----------------------------
"$D/refresh-cache.sh"
if ! ssh-keygen -lf "$D/root.pub.cache" 2>/dev/null | grep -qF "$HUELLA"; then
	echo "FAIL cache-sin-llave-codex (revisar salida a GitHub)"
	exit 1
fi

# --- 3. auth.sh nuevo en staging -------------------------------------------
cat >"$D/auth.sh.new" <<'SCRIPT'
#!/bin/bash
# AuthorizedKeysCommand para sshd. Corre como el usuario 'nobody' en CADA login.
#
# Lee solo la cache local que mantiene /opt/ssh-keys/refresh-cache.sh por cron.
# Sin red en el camino critico: login rapido, inmune a cortes hacia GitHub y al
# limite de 60 peticiones/hora de la API anonima (los CTs de un nodo comparten
# la IP publica del nodo, asi que consultar en cada login agotaria la cuota).
#
# Historia: la version anterior consultaba api.github.com con un PAT guardado en
# /opt/ssh-keys/.github_token. Al expirar el PAT, curl devolvia JSON de error,
# jq -r '.content' devolvia "null" y base64 -d producia basura: sshd se quedaba
# sin llaves y toda la flota Linux quedo inaccesible. Ahora se valida siempre.

CACHE="/opt/ssh-keys/root.pub.cache"

valido() { grep -Eq '^(ssh-|ecdsa-|sk-)' <<<"$1"; }

if [ -r "$CACHE" ]; then
	keys=$(cat "$CACHE")
	valido "$keys" && { printf '%s\n' "$keys"; exit 0; }
fi

# Ultimo recurso: cache ausente o corrupta (host recien creado, disco lleno).
# Se paga el costo de red aqui porque la alternativa es quedarse sin acceso.
for url in \
	"https://api.github.com/repos/Clusteres/ssh-keys/contents/root.pub?ref=main" \
	"https://raw.githubusercontent.com/Clusteres/ssh-keys/main/root.pub"; do
	keys=$(curl -sf --max-time 4 -H "Accept: application/vnd.github.raw" "$url" 2>/dev/null)
	valido "$keys" && { printf '%s\n' "$keys"; exit 0; }
done

exit 0
SCRIPT
chown root:root "$D/auth.sh.new"
chmod 755 "$D/auth.sh.new"

# --- 4. Validar que entrega la llave codex ANTES de instalarlo --------------
if ! runuser -u nobody -- "$D/auth.sh.new" 2>/dev/null |
	ssh-keygen -lf - 2>/dev/null | grep -qF "$HUELLA"; then
	rm -f "$D/auth.sh.new"
	echo "FAIL validacion-auth.sh"
	exit 1
fi

# --- 5. Programar el refresco cada 5 minutos, con desfase al azar para no ----
#        sincronizar toda la flota en el mismo instante contra GitHub.
#        raw.githubusercontent.com no tiene limite de cuota; api.github.com es el
#        respaldo y con este intervalo gasta 12 peticiones/hora de las 60.
#        Para revocar YA, sin esperar el cron: /usr/local/bin/ssh-keys-propagate
printf '# Refresca la cache de llaves que lee /opt/ssh-keys/auth.sh.\n%s-59/5 * * * * root /opt/ssh-keys/refresh-cache.sh\n' \
	"$((RANDOM % 5))" >/etc/cron.d/ssh-keys-refresh
chmod 644 /etc/cron.d/ssh-keys-refresh

# --- 6. Poner auth.sh en su lugar (con respaldo) ---------------------------
# Solo respalda la primera vez, para no perder el original al reejecutar.
[ -f "$D/auth.sh" ] && [ ! -f "$D/auth.sh.bak-pat-expirado" ] &&
	cp -a "$D/auth.sh" "$D/auth.sh.bak-pat-expirado"
mv -f "$D/auth.sh.new" "$D/auth.sh"

# --- 7. Retirar el PAT muerto (quedaba en 755, legible por cualquiera) -----
if [ -f "$D/.github_token" ]; then
	mv -f "$D/.github_token" "$D/.github_token.RETIRADO"
	chmod 600 "$D/.github_token.RETIRADO"
fi

# --- 8. Verificacion final en caliente -------------------------------------
final=$(runuser -u nobody -- "$D/auth.sh" 2>/dev/null | ssh-keygen -lf - 2>/dev/null | grep -cF "$HUELLA")
[ "$final" = "1" ] && echo "OK auth.sh-reparado" || echo "FAIL verificacion-final"
PAYLOAD_EOF
chmod 755 "$P"

echo "=== nodo local: $(hostname) ==="
bash "$P"

if command -v pct >/dev/null 2>&1; then
	echo
	echo "=== contenedores ==="
	for v in $(pct list 2>/dev/null | awk '$2=="running"{print $1}'); do
		n=$(pct list | awk -v V="$v" '$1==V{print $3}')
		if ! pct push "$v" "$P" /tmp/ssh-keys-fix-payload.sh >/dev/null 2>&1; then
			printf '%-6s %-22s %s\n' "$v" "$n" "FAIL push"
			continue
		fi
		r=$(timeout 90 pct exec "$v" -- bash /tmp/ssh-keys-fix-payload.sh 2>&1 | tail -1)
		pct exec "$v" -- rm -f /tmp/ssh-keys-fix-payload.sh >/dev/null 2>&1
		printf '%-6s %-22s %s\n' "$v" "$n" "$r"
	done
fi

rm -f "$P"
echo
echo "Listo. Probar desde pxsky:  ssh <host>"
