#!/usr/bin/env bash
# ============================================================================
# deploy-onserver.sh — À LANCER SUR LE SERVEUR (pas depuis le Mac).
# Auto-contenu : embarque les 6 fichiers de conf, déploie Nginx PUIS fail2ban,
# avec backups horodatés, gates (nginx -t / fail2ban -t) et rollback par étage.
#
#   Transfert (1 seule connexion, OK) :  scp deploy-onserver.sh SRV:/tmp/
#   Puis sur le serveur :                chmod +x /tmp/deploy-onserver.sh && /tmp/deploy-onserver.sh
# ============================================================================
set -uo pipefail

command -v sudo >/dev/null || { echo "sudo requis" >&2; exit 1; }
ts=$(date +%Y%m%d-%H%M%S)
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT

say() { printf '\n\033[1m### %s ###\033[0m\n' "$1"; }

# ------------------------------------------------------------------ fichiers embarqués
cat > "$TMP/nginx.conf" <<'NGINXCONF_EOF'
user www-data;
worker_processes auto;
worker_cpu_affinity auto;
# Plafond de descripteurs par worker (défaut système ~1024, bien trop bas).
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;

events {
	# 768 (défaut Ubuntu) -> 4096 : marge confortable pour un reverse proxy.
	worker_connections 4096;
	# Accepte toutes les connexions en attente d'un coup au lieu d'une par une.
	multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	types_hash_max_size 2048;
	keepalive_timeout 65;

	# build -> off : ne pas divulguer "nginx/1.28.3 (Ubuntu)" dans les erreurs/headers.
	server_tokens off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# SSL Settings
	##

	ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3 (POODLE), TLS 1.0, 1.1
	ssl_prefer_server_ciphers off; # Don't force server cipher order.
	# Cache de session TLS partagé -> évite les handshakes complets répétés.
	ssl_session_cache shared:SSL:10m;
	ssl_session_timeout 1d;
	ssl_session_tickets off;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;

	##
	# Gzip Settings
	##

	gzip on;
	gzip_vary on;
	gzip_proxied any;
	gzip_comp_level 5;
	gzip_min_length 1024;
	gzip_types text/plain text/css application/json application/javascript
	           text/xml application/xml application/xml+rss text/javascript
	           image/svg+xml application/manifest+json;

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}
NGINXCONF_EOF

cat > "$TMP/10-ratelimit.conf" <<'RATELIMIT_EOF'
# /etc/nginx/conf.d/10-ratelimit.conf
# Zones de limitation (contexte http). Utilisées par le vhost dash via limit_req/limit_conn.
# $binary_remote_addr = vraie IP client (pas de CDN) -> ~16k IP par Mo.

# Débit : 15 req/s par IP en régime établi (large pour un dashboard).
limit_req_zone  $binary_remote_addr  zone=app:10m  rate=15r/s;

# Connexions simultanées : max 20 par IP.
limit_conn_zone $binary_remote_addr  zone=addr:10m;

# Code renvoyé quand la limite est franchie (429 = Too Many Requests).
limit_req_status  429;
limit_conn_status 429;
RATELIMIT_EOF

cat > "$TMP/10-upgrade-map.conf" <<'UPGRADE_EOF'
# /etc/nginx/conf.d/10-upgrade-map.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    # Hors handshake WebSocket -> Connection: "" (vide), et NON "close".
    # "" autorise le keepalive vers l'upstream Node (connexions réutilisées).
    ''      "";
}
UPGRADE_EOF

cat > "$TMP/dash.seoinsight.app" <<'VHOST_EOF'
# ===========================================================================
# dash.seoinsight.app — reverse proxy vers Next.js (127.0.0.1:3000)
# Version optimisée : HTTP/2, HSTS, keepalive upstream, rate-limit.
# ===========================================================================

upstream nextjs_app {
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl ipv6only=on;
    http2 on;

    server_name dash.seoinsight.app;
    server_tokens off;

    ssl_certificate     /etc/letsencrypt/live/dash.seoinsight.app/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/dash.seoinsight.app/privkey.pem;   # managed by Certbot
    include             /etc/letsencrypt/options-ssl-nginx.conf;                 # managed by Certbot
    ssl_dhparam         /etc/letsencrypt/ssl-dhparams.pem;                       # managed by Certbot

    # HSTS : force HTTPS côté navigateur pendant 1 an. PAS de "preload".
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    client_max_body_size 60M;
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;

    gzip on;
    gzip_proxied any;
    gzip_comp_level 5;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/javascript application/json
               image/svg+xml application/xml application/manifest+json;

    location /_next/static/ {
        alias /opt/seo-insight/.next/static/;
        access_log off;
        add_header Cache-Control "public, max-age=31536000, immutable" always;
    }

    location / {
        limit_req  zone=app burst=30 nodelay;
        limit_conn addr 20;

        proxy_pass http://nextjs_app;
        proxy_http_version 1.1;

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host  $host;

        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_cache_bypass $http_upgrade;
    }
}

server {
    listen 80;
    listen [::]:80;
    server_name dash.seoinsight.app;
    return 301 https://dash.seoinsight.app$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name dashboard.seoinsight.app;
    server_tokens off;

    ssl_certificate     /etc/letsencrypt/live/dash.seoinsight.app/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/dash.seoinsight.app/privkey.pem;
    include             /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam         /etc/letsencrypt/ssl-dhparams.pem;

    return 301 https://dash.seoinsight.app$request_uri;
}

server {
    listen 80;
    listen [::]:80;
    server_name dashboard.seoinsight.app;
    return 301 https://dash.seoinsight.app$request_uri;
}
VHOST_EOF

cat > "$TMP/jail.local" <<'JAIL_EOF'
# /etc/fail2ban/jail.local — config durcie SEO Insight
# Ubuntu 26.04 · nftables · SSH systemd · Nginx (dash.seoinsight.app) · sans CDN

[DEFAULT]
# Pas d'IP fixe -> anti-lockout via SSH par clé, pas d'ignoreip perso.
# Option DDNS : ajouter un nom d'hôte DynDNS en fin de ligne (fail2ban le résout).
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16

bantime  = 1w
findtime = 1d
maxretry = 3

bantime.increment = true
bantime.factor    = 2
bantime.maxtime   = 5w

# Firewall nftables (natif 26.04, cohabite avec UFW). Fallback : commenter ces 2 lignes.
banaction          = nftables
banaction_allports = nftables[type=allports]

[sshd]
enabled  = true
backend  = systemd
mode     = normal
maxretry = 3

[nginx-botsearch]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/access.log
maxretry = 1

[nginx-badscan]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/access.log
filter   = nginx-badscan
maxretry = 1

[nginx-http-auth]
enabled = false

[nginx-limit-req]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log
maxretry = 5

[recidive]
enabled  = true
logpath  = /var/log/fail2ban.log
bantime  = 8w
findtime = 1d
maxretry = 3
JAIL_EOF

cat > "$TMP/nginx-badscan.conf" <<'BADSCAN_EOF'
# /etc/fail2ban/filter.d/nginx-badscan.conf
# Bannit les scans de fichiers sensibles / secrets dans l'access.log Nginx.
[Definition]

failregex = ^<HOST> \- \S+ \[[^\]]*\] "(?:GET|POST|HEAD|PUT|PATCH|DELETE) [^"]*(?:\.env|\.git/|\.aws/|\.ssh/|\.htpasswd|\.htaccess|/config\.(?:php|json|ya?ml|js)|/settings\.py|/credentials|\.pem|\.sql|/dump|/backup|\.bak|/\.vscode|/\.idea|/_ignition|/telescope|/actuator|/boaform|/server-status|/wp-config)[^"]*" \d+

ignoreregex =

datepattern = {^LN-BEG}%%ExY(?P<_sep>[-/.])%%m(?P=_sep)%%d[T ]%%H:%%M:%%S(?:[.,]%%f)?(?:\s*%%z)?
              ^[^\[]*\[({DATE})
              {^LN-BEG}
BADSCAN_EOF

echo "==> 6 fichiers générés dans $TMP :"
ls -1 "$TMP"

# ============================ ÉTAPE 1 : NGINX ============================
say "NGINX — backup & install"
sudo cp -a /etc/nginx/nginx.conf                          /etc/nginx/nginx.conf.bak.$ts
sudo cp -a /etc/nginx/conf.d/10-upgrade-map.conf          /etc/nginx/conf.d/10-upgrade-map.conf.bak.$ts 2>/dev/null || true
sudo cp -a /etc/nginx/sites-available/dash.seoinsight.app /etc/nginx/sites-available/dash.seoinsight.app.bak.$ts

sudo install -m 644 -o root -g root "$TMP/nginx.conf"          /etc/nginx/nginx.conf
sudo install -m 644 -o root -g root "$TMP/10-ratelimit.conf"   /etc/nginx/conf.d/10-ratelimit.conf
sudo install -m 644 -o root -g root "$TMP/10-upgrade-map.conf" /etc/nginx/conf.d/10-upgrade-map.conf
sudo install -m 644 -o root -g root "$TMP/dash.seoinsight.app" /etc/nginx/sites-available/dash.seoinsight.app

say "NGINX — test"
if sudo nginx -t; then
  sudo systemctl reload nginx && echo ">> NGINX déployé."
else
  echo ">> nginx -t ÉCHOUE : rollback Nginx, fail2ban NON touché." >&2
  sudo cp -a /etc/nginx/nginx.conf.bak.$ts                          /etc/nginx/nginx.conf
  sudo cp -a /etc/nginx/conf.d/10-upgrade-map.conf.bak.$ts          /etc/nginx/conf.d/10-upgrade-map.conf 2>/dev/null || true
  sudo cp -a /etc/nginx/sites-available/dash.seoinsight.app.bak.$ts /etc/nginx/sites-available/dash.seoinsight.app
  sudo rm -f /etc/nginx/conf.d/10-ratelimit.conf
  exit 10
fi

# ============================ ÉTAPE 2 : FAIL2BAN =========================
say "FAIL2BAN — backup & install"
sudo cp -a /etc/fail2ban/jail.local /etc/fail2ban/jail.local.bak.$ts
sudo install -m 644 -o root -g root "$TMP/jail.local"         /etc/fail2ban/jail.local
sudo install -m 644 -o root -g root "$TMP/nginx-badscan.conf" /etc/fail2ban/filter.d/nginx-badscan.conf

say "FAIL2BAN — test filtre nginx-badscan (info)"
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-badscan.conf \
  | grep -E "Failregex: [0-9]|lines" || true

say "FAIL2BAN — test config"
if sudo fail2ban-client -t; then
  sudo systemctl restart fail2ban && sleep 2 && echo ">> FAIL2BAN déployé."
else
  echo ">> fail2ban -t ÉCHOUE : rollback jail.local (Nginx reste déployé)." >&2
  sudo cp -a /etc/fail2ban/jail.local.bak.$ts /etc/fail2ban/jail.local
  sudo rm -f /etc/fail2ban/filter.d/nginx-badscan.conf
  exit 20
fi

# ============================ VÉRIFICATIONS ============================
say "VÉRIF — HTTP/2 + HSTS + server tokens"
curl -sI https://dash.seoinsight.app | grep -iE "^HTTP|strict-transport|^server:" || true

say "VÉRIF — jails fail2ban"
sudo fail2ban-client status
for j in sshd nginx-botsearch nginx-badscan nginx-limit-req recidive; do
  echo "----- $j -----"; sudo fail2ban-client status "$j" 2>/dev/null | grep -E "Currently|Total banned|Banned IP" || echo "(inactif)"
done

say "VÉRIF — table nftables f2b"
sudo nft list tables | grep -i f2b || echo "(pas de table f2b — vérifier banaction)"

printf '\n\033[1;32m==> Déploiement terminé (Nginx + fail2ban). Sauvegardes .bak.%s\033[0m\n' "$ts"
