#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

# ═══════════════════════════════════════════════════════════════
#  ZERO TRUST NKH — Script d'installation v3 (final)
#  Testé et validé le 2026-04-16
#
#  Architecture :
#   - Caddy (network_mode: host) — reverse proxy TLS ZeroSSL
#   - Authelia — 2FA TOTP
#   - WireGuard (wg-easy) — VPN
#   - AdGuard — DNS
#   - Portainer — gestion Docker
#   - Grafana + Prometheus + Loki + Promtail — monitoring
#   - Uptime Kuma — monitoring uptime
#   - Redis — session Authelia
#   - Watchtower — mises à jour auto
# ═══════════════════════════════════════════════════════════════

DOMAIN="nkhproxy.duckdns.org"
EMAIL="nkh@ik.me"

# -------------------------------------------------------
# HACHAGES DU MOT DE PASSE
#
#   Algorithme   : bcrypt cost=12  (wg-easy)
#   Plain        : Nihilisme1158#@
#   Hash bcrypt  : $2b$12$vCMZR3rDO4HS4.Vb.nG2IOE7EELn.exnaKbmIA2eQeBX00vDkJbiu
#
#   Algorithme   : argon2id m=65536,t=3,p=4  (authelia)
#   Plain        : Nihilisme1158#@
#   Hash argon2  : $argon2id$v=19$m=65536,t=3,p=4$Sh3obhPJbYwhBBCxMl3iGQ$/MJcZZpOjCB6+a3DKsyO4Dipudqylpyv21yGjsymPZM
# -------------------------------------------------------

BCRYPT_HASH='$2b$12$vCMZR3rDO4HS4.Vb.nG2IOE7EELn.exnaKbmIA2eQeBX00vDkJbiu'
ARGON2_HASH='$argon2id$v=19$m=65536,t=3,p=4$Sh3obhPJbYwhBBCxMl3iGQ$/MJcZZpOjCB6+a3DKsyO4Dipudqylpyv21yGjsymPZM'
JWT_SECRET=$(openssl rand -hex 32)
REDIS_PASS=$(openssl rand -hex 24)

# Mot de passe Grafana lu depuis stdin
if [[ -z "${GRAFANA_PASS:-}" ]]; then
    read -rsp "🔐 Mot de passe Grafana (plaintext) : " GRAFANA_PASS
    echo
fi
[[ -z "$GRAFANA_PASS" ]] && { echo "❌ Mot de passe Grafana requis."; exit 1; }


# ═══════════════════════════════════════════════════════════════
# SECTION 1 — NETTOYAGE DOCKER COMPLET
# ═══════════════════════════════════════════════════════════════
echo ""
echo "🧹 NETTOYAGE DE L'INSTALLATION DOCKER EXISTANTE..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

if docker ps -aq 2>/dev/null | grep -q .; then
    docker stop $(docker ps -aq) 2>/dev/null || true
    docker rm -f $(docker ps -aq) 2>/dev/null || true
fi
docker images -q 2>/dev/null | grep -q . && docker rmi -f $(docker images -q) 2>/dev/null || true
docker volume ls -q 2>/dev/null | grep -q . && docker volume rm $(docker volume ls -q) 2>/dev/null || true
docker network prune -f 2>/dev/null || true
[[ -d ~/homelab ]] && rm -rf ~/homelab

echo "  ✅ Nettoyage terminé."


# ═══════════════════════════════════════════════════════════════
# SECTION 2 — INSTALLATION DES DÉPENDANCES
# ═══════════════════════════════════════════════════════════════
echo ""
echo "📦 INSTALLATION DES DÉPENDANCES..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

apt update -y

# Purge des résidus Docker CE (conflits avec docker.io)
apt purge -y \
    docker-ce docker-ce-cli docker-ce-rootless-extras \
    docker-buildx-plugin docker-compose-plugin \
    containerd.io 2>/dev/null || true
apt autoremove -y

apt install -y docker.io docker-compose fail2ban curl chrony
systemctl enable docker && systemctl start docker
systemctl enable fail2ban && systemctl start fail2ban

# Synchronisation NTP
chronyc makestep 2>/dev/null || true
sysctl vm.overcommit_memory=1
echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf

# systemd-resolved : désactiver stub listener si actif
if systemctl is-active --quiet systemd-resolved 2>/dev/null; then
    mkdir -p /etc/systemd/resolved.conf.d
    printf '[Resolve]\nDNSStubListener=no\n' > /etc/systemd/resolved.conf.d/nostub.conf
    systemctl restart systemd-resolved
    ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
else
    : # systemd-resolved absent/masqué
fi
grep -q "^nameserver" /etc/resolv.conf 2>/dev/null || echo "nameserver 1.1.1.1" > /etc/resolv.conf


# ═══════════════════════════════════════════════════════════════
# SECTION 3 — STRUCTURE DES FICHIERS
# ═══════════════════════════════════════════════════════════════
echo ""
echo "📁 CRÉATION DE LA STRUCTURE..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

mkdir -p ~/homelab && cd ~/homelab
mkdir -p authelia adguard secrets caddy-build

HOST_IP=$(ip route | awk '/default/ { print $3 }')
echo "  → IP hôte détectée : ${HOST_IP}"


# ═══════════════════════════════════════════════════════════════
# SECTION 4 — SECRETS & .ENV
# ═══════════════════════════════════════════════════════════════

echo -n "${GRAFANA_PASS}" > secrets/grafana_pass.txt
chmod 600 secrets/grafana_pass.txt

cat > .env <<EOF
WG_HOST=${DOMAIN}
USERNAME=NKH
PASSWORD_HASH=${BCRYPT_HASH}
AUTHELIA_SESSION_SECRET=$(openssl rand -hex 32)
AUTHELIA_STORAGE_ENCRYPTION_KEY=$(openssl rand -hex 32)
REDIS_PASS=${REDIS_PASS}
EOF
chmod 600 .env


# ═══════════════════════════════════════════════════════════════
# SECTION 5 — CADDY BUILD (plugin coraza via xcaddy)
# ═══════════════════════════════════════════════════════════════

cat > caddy-build/Dockerfile <<'EOF'
FROM caddy:2-builder AS builder
RUN xcaddy build \
    --with github.com/corazawaf/coraza-caddy/v2

FROM caddy:2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
EOF


# ═══════════════════════════════════════════════════════════════
# SECTION 6 — DOCKER COMPOSE
# ═══════════════════════════════════════════════════════════════

cat > docker-compose.yml <<EOF
services:

  caddy:
    build: ./caddy-build
    network_mode: host
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    restart: unless-stopped

  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:14
    environment:
      - WG_HOST=\${WG_HOST}
      - WG_DEFAULT_DNS=172.20.0.3
      - USERNAME=\${USERNAME}
      - PASSWORD_HASH=${BCRYPT_HASH}
    ports:
      - "51820:51820/udp"
    expose:
      - "51821"
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
    networks:
      homelab:
        ipv4_address: 172.20.0.2
    restart: unless-stopped
    labels:
      - "com.centurylinklabs.watchtower.enable=true"

  adguard:
    image: adguard/adguardhome:latest
    volumes:
      - ./adguard:/opt/adguardhome/work
    ports:
      - "127.0.0.1:53:53/tcp"
      - "127.0.0.1:53:53/udp"
    expose:
      - "3000"
    networks:
      homelab:
        ipv4_address: 172.20.0.3
    restart: unless-stopped

  portainer:
    image: portainer/portainer-ce:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    expose:
      - "9000"
    networks:
      homelab:
        ipv4_address: 172.20.0.4
    restart: unless-stopped

  authelia:
    image: authelia/authelia:latest
    volumes:
      - ./authelia:/config
    environment:
      - AUTHELIA_SESSION_SECRET=\${AUTHELIA_SESSION_SECRET}
      - AUTHELIA_STORAGE_ENCRYPTION_KEY=\${AUTHELIA_STORAGE_ENCRYPTION_KEY}
      - REDIS_PASS=\${REDIS_PASS}
    expose:
      - "9091"
    networks:
      homelab:
        ipv4_address: 172.20.0.5
    restart: unless-stopped
    depends_on:
      redis:
        condition: service_healthy

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass \${REDIS_PASS}
    expose:
      - "6379"
    networks:
      homelab:
        ipv4_address: 172.20.0.6
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "\${REDIS_PASS}", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10

  grafana:
    image: grafana/grafana:latest
    environment:
      - GF_SECURITY_ADMIN_USER=NKH
      - GF_SECURITY_ADMIN_PASSWORD__FILE=/run/secrets/grafana_pass
    secrets:
      - grafana_pass
    expose:
      - "3000"
    networks:
      homelab:
        ipv4_address: 172.20.0.7
    restart: unless-stopped
    labels:
      - "com.centurylinklabs.watchtower.enable=true"

  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    expose:
      - "9090"
    networks:
      homelab:
        ipv4_address: 172.20.0.8
    restart: unless-stopped

  loki:
    image: grafana/loki:latest
    command: -config.file=/etc/loki/local-config.yaml
    expose:
      - "3100"
    networks:
      homelab:
        ipv4_address: 172.20.0.9
    restart: unless-stopped

  promtail:
    image: grafana/promtail:latest
    volumes:
      - /var/log:/var/log:ro
      - ./promtail.yml:/etc/promtail/config.yml
    networks:
      homelab:
        ipv4_address: 172.20.0.10
    restart: unless-stopped
    depends_on: [loki]

  uptime-kuma:
    image: louislam/uptime-kuma:latest
    volumes:
      - uptime_kuma_data:/app/data
    expose:
      - "3001"
    networks:
      homelab:
        ipv4_address: 172.20.0.11
    restart: unless-stopped

  watchtower:
    image: containrrr/watchtower:latest
    environment:
      - WATCHTOWER_POLL_INTERVAL=86400
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_LABEL_ENABLE=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      homelab:
        ipv4_address: 172.20.0.12
    restart: unless-stopped

  node-exporter:
    image: prom/node-exporter:latest
    pid: host
    network_mode: host
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
    restart: unless-stopped

secrets:
  grafana_pass:
    file: ./secrets/grafana_pass.txt

volumes:
  portainer_data:
  uptime_kuma_data:
  caddy_data:
  caddy_config:

networks:
  homelab:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16
          gateway: 172.20.0.1
EOF


# ═══════════════════════════════════════════════════════════════
# SECTION 7 — CADDYFILE
# ═══════════════════════════════════════════════════════════════

cat > Caddyfile <<EOF
{
    email ${EMAIL}
    acme_ca https://acme.zerossl.com/v2/DV90
}

(auth) {
    forward_auth 172.20.0.5:9091 {
        uri /api/authz/forward-auth
        copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
    }
}

vpn.${DOMAIN} {
    reverse_proxy 172.20.0.2:51821
}

auth.${DOMAIN} {
    reverse_proxy 172.20.0.5:9091
}

dns.${DOMAIN} {
    import auth
    reverse_proxy 172.20.0.3:3000
}

grafana.${DOMAIN} {
    import auth
    reverse_proxy 172.20.0.7:3000
}

portainer.${DOMAIN} {
    import auth
    reverse_proxy 172.20.0.4:9000
}

uptime.${DOMAIN} {
    import auth
    reverse_proxy 172.20.0.11:3001
}
EOF


# ═══════════════════════════════════════════════════════════════
# SECTION 8 — PROMETHEUS
# ═══════════════════════════════════════════════════════════════

cat > prometheus.yml <<EOF
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  - job_name: "caddy"
    static_configs:
      - targets: ["localhost:2019"]

  - job_name: "node"
    static_configs:
      - targets: ["${HOST_IP}:9100"]
EOF


# ═══════════════════════════════════════════════════════════════
# SECTION 9 — PROMTAIL
# ═══════════════════════════════════════════════════════════════

cat > promtail.yml <<'EOF'
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://172.20.0.9:3100/loki/api/v1/push

scrape_configs:
  - job_name: system
    static_configs:
      - targets: [localhost]
        labels:
          job: varlogs
          __path__: /var/log/*log

  - job_name: docker
    static_configs:
      - targets: [localhost]
        labels:
          job: docker
          __path__: /var/log/docker*
EOF


# ═══════════════════════════════════════════════════════════════
# SECTION 10 — AUTHELIA
# ═══════════════════════════════════════════════════════════════

cat > authelia/configuration.yml <<EOF
server:
  address: tcp://0.0.0.0:9091

log:
  level: info

access_control:
  default_policy: deny
  rules:
    - domain: "auth.${DOMAIN}"
      policy: bypass
    - domain: "*.${DOMAIN}"
      policy: two_factor

session:
  secret: "$(openssl rand -hex 32)"
  cookies:
    - domain: ${DOMAIN}
      authelia_url: https://auth.${DOMAIN}
      default_redirection_url: https://uptime.${DOMAIN}
  redis:
    host: 172.20.0.6
    port: 6379
    password: "${REDIS_PASS}"

storage:
  encryption_key: "$(openssl rand -hex 32)"
  local:
    path: /config/db.sqlite3

notifier:
  filesystem:
    filename: /config/notification.txt

totp:
  issuer: ${DOMAIN}
  period: 30
  skew: 1

identity_validation:
  reset_password:
    jwt_secret: "${JWT_SECRET}"

authentication_backend:
  file:
    path: /config/users_database.yml
    password:
      algorithm: argon2id

ntp:
  address: "time.cloudflare.com:123"
  disable_startup_check: true
EOF

# users_database en single-quote heredoc pour éviter l'interprétation des $
cat > authelia/users_database.yml <<'USERSEOF'
users:
  NKH:
    displayname: "NKH"
    password: "ARGON2_PLACEHOLDER"
    email: EMAIL_PLACEHOLDER
    groups:
      - admins
USERSEOF

sed -i "s|ARGON2_PLACEHOLDER|${ARGON2_HASH}|g" authelia/users_database.yml
sed -i "s|EMAIL_PLACEHOLDER|${EMAIL}|g" authelia/users_database.yml
chmod 600 authelia/users_database.yml


# ═══════════════════════════════════════════════════════════════
# SECTION 11 — FAIL2BAN
# ═══════════════════════════════════════════════════════════════

cat > /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
bantime  = 3600
findtime = 600
maxretry = 5

[sshd]
enabled = true

[recidive]
enabled  = true
bantime  = 86400
findtime = 86400
maxretry = 5
EOF
systemctl restart fail2ban


# ═══════════════════════════════════════════════════════════════
# SECTION 12 — DÉMARRAGE
# ═══════════════════════════════════════════════════════════════
echo ""
echo "🚀 DÉMARRAGE DES CONTAINERS..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

docker compose --env-file .env up -d

echo ""
echo "✅ INSTALLATION ZERO TRUST TERMINÉE"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "👤 Utilisateur  : NKH"
echo "🔐 Mot de passe : Nihilisme1158#@"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🌐 VPN          : https://vpn.${DOMAIN}"
echo "📊 Grafana      : https://grafana.${DOMAIN}  (2FA)"
echo "🐳 Portainer    : https://portainer.${DOMAIN} (2FA)"
echo "🛡️  DNS          : https://dns.${DOMAIN}      (2FA)"
echo "📡 Uptime Kuma  : https://uptime.${DOMAIN}   (2FA)"
echo "🔐 Authelia     : https://auth.${DOMAIN}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📝 Enregistrer le TOTP Authelia au premier login :"
echo "   docker exec homelab-authelia-1 authelia storage user totp generate NKH --config /config/configuration.yml"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📝 node-exporter scrape target : ${HOST_IP}:9100"
