#!/usr/bin/env bash

#########################################################################################
#### nginx-security-hardening.sh — Add rate limiting, bot honeypot, security         ####
#### headers, query string filtering, and hotlink protection to nginx                ####
####                                                                                 ####
#### Supports standalone nginx (default) and HestiaCP/VestaCP                        ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 2.04                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   sudo ./nginx-security-hardening.sh --domain example.com                       ####
####   sudo ./nginx-security-hardening.sh --domain example.com --dry-run             ####
####   sudo ./nginx-security-hardening.sh --domain example.com --user admin          ####
####   sudo ./nginx-security-hardening.sh --domain example.com --skip honeypot       ####
####                                                                                 ####
#### See --help for all options.                                                     ####
#########################################################################################
# v2.03 changes:
#   - Fixed: grep in pipeline crashes under set -euo pipefail when no matches found. Added || true guard
#########################################################################################

set -euo pipefail

# ── Defaults ──────────────────────────────────────────────────────────
DOMAIN=""
USER=""
DRY_RUN=false
SKIP=()
RATE="5r/m"
BURST=10
BANTIME=86400
TRAP_PATH="/trap"
DOWNLOAD_PATH="/downloads/"
BACKUP_EXT=".bak.$(date +%Y%m%d%H%M%S)"

# ── Colors ────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

# ── Usage ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"

usage() {
    cat <<EOF
${SCRIPT_NAME} — Nginx security hardening (standalone or HestiaCP/VestaCP)

USAGE:
  sudo ${SCRIPT_NAME} --domain DOMAIN [OPTIONS]

OPTIONS:
  --domain DOMAIN       Domain name (required)
  --user USER           HestiaCP/VestaCP username (omit for standalone nginx)
  --skip FEATURES       Comma-separated: rate-limit, honeypot, headers, query-filter, hotlink-protection
  --rate RATE           Rate limit (default: 5r/m)
  --burst NUM           Rate limit burst (default: 10)
  --bantime SECS        Honeypot ban duration (default: 86400)
  --trap-path PATH      Honeypot path (default: /trap)
  --download-path PATH  Rate-limited path (default: /downloads/)
  --dry-run             Preview changes without writing files
  --help                Show this help

EXAMPLES:
  ${SCRIPT_NAME} --domain example.com
  ${SCRIPT_NAME} --domain example.com --skip honeypot
  ${SCRIPT_NAME} --domain example.com --user admin
  ${SCRIPT_NAME} --domain example.com --dry-run
EOF
    exit 1
}

# ── Logging ───────────────────────────────────────────────────────────
log_info()  { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn()  { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
log_dry()   { echo -e "${CYAN}[DRY-RUN]${NC} $*"; }

# ── Helpers ───────────────────────────────────────────────────────────
should_skip() {
    local feature="$1"
    for skip in "${SKIP[@]+"${SKIP[@]}"}"; do
        [[ "$skip" == "$feature" ]] && return 0
    done
    return 1
}

backup_file() {
    local file="$1"
    [[ -f "$file" ]] || return 0
    local backup_dir="$(dirname "$file")/.backups"
    local backup_dest="${backup_dir}/$(basename "$file")${BACKUP_EXT}"
    if $DRY_RUN; then
        log_dry "Would back up $file → ${backup_dest}"
    else
        mkdir -p "$backup_dir"
        cp "$file" "$backup_dest"
        log_info "Backed up $file"
    fi
}

write_file() {
    local file="$1"
    local content="$2"
    local desc="$3"

    if $DRY_RUN; then
        log_dry "Would create $file"
        echo -e "${CYAN}--- $desc ---${NC}"
        echo "$content"
        echo ""
    else
        mkdir -p "$(dirname "$file")"
        backup_file "$file"
        echo "$content" > "$file"
        log_info "Created $file"
    fi
}

is_hestia_mode() { [[ -n "$USER" ]]; }

detect_backend() {
    local conf_dir="/home/${USER}/conf/web/${DOMAIN}"
    local http_backend=""
    local https_backend=""

    if [[ -f "${conf_dir}/nginx.conf" ]]; then
        http_backend=$({ grep -m1 'proxy_pass' "${conf_dir}/nginx.conf" || true; } | awk '{print $2}' | tr -d ';')
    fi
    if [[ -f "${conf_dir}/nginx.ssl.conf" ]]; then
        https_backend=$({ grep -m1 'proxy_pass' "${conf_dir}/nginx.ssl.conf" || true; } | awk '{print $2}' | tr -d ';')
    fi

    if [[ -z "$http_backend" || -z "$https_backend" ]]; then
        log_error "Could not detect backend proxy_pass from ${conf_dir}/nginx.conf"
        log_error "Check that the domain exists in HestiaCP/VestaCP"
        exit 1
    fi

    echo "${http_backend}|${https_backend}"
}

# ── Parse Arguments ───────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
    case "$1" in
        --domain)        DOMAIN="$2"; shift 2 ;;
        --user)          USER="$2"; shift 2 ;;
        --dry-run)       DRY_RUN=true; shift ;;
        --skip)          IFS=',' read -ra SKIP <<< "$2"; shift 2 ;;
        --rate)          RATE="$2"; shift 2 ;;
        --burst)         BURST="$2"; shift 2 ;;
        --bantime)       BANTIME="$2"; shift 2 ;;
        --trap-path)     TRAP_PATH="$2"; shift 2 ;;
        --download-path) DOWNLOAD_PATH="$2"; shift 2 ;;
        -h|--help)       usage ;;
        *)               log_error "Unknown option: $1"; usage ;;
    esac
done

# ── Validate ──────────────────────────────────────────────────────────
if [[ -z "$DOMAIN" ]]; then
    log_error "Missing required --domain"
    usage
fi

if [[ "$EUID" -ne 0 && "$DRY_RUN" == false ]]; then
    log_error "Must run as root (or use --dry-run to preview)"
    exit 1
fi

# ── Detect Mode ───────────────────────────────────────────────────────
if is_hestia_mode; then
    CONF_DIR="/home/${USER}/conf/web/${DOMAIN}"
    if [[ ! -d "$CONF_DIR" ]]; then
        log_error "Directory not found: ${CONF_DIR}"
        log_error "Check your --user and --domain values"
        exit 1
    fi
    log_info "Mode: HestiaCP/VestaCP (user: ${USER}, domain: ${DOMAIN})"

    BACKENDS=$(detect_backend)
    HTTP_BACKEND=$(echo "$BACKENDS" | cut -d'|' -f1)
    HTTPS_BACKEND=$(echo "$BACKENDS" | cut -d'|' -f2)
    log_info "Detected HTTP backend:  ${HTTP_BACKEND}"
    log_info "Detected HTTPS backend: ${HTTPS_BACKEND}"

    HTTPS_IS_SSL=false
    if [[ "$HTTPS_BACKEND" == https://* ]]; then
        HTTPS_IS_SSL=true
    fi
else
    CONF_DIR="/etc/nginx"
    log_info "Mode: standalone nginx"
fi

echo ""
log_info "Domain: ${DOMAIN}"
log_info "Features to install:"
should_skip rate-limit   || log_info "  ✓ Rate limiting (${RATE}, burst ${BURST}) on ${DOWNLOAD_PATH}"
should_skip honeypot     || log_info "  ✓ Bot honeypot (${TRAP_PATH}, ban ${BANTIME}s)"
should_skip headers      || log_info "  ✓ Security headers"
should_skip query-filter       || log_info "  ✓ Query string filtering"
should_skip hotlink-protection || log_info "  ✓ Hotlink protection (images)"
for skip in "${SKIP[@]+"${SKIP[@]}"}"; do
    log_warn "  ✗ Skipping: ${skip}"
done
echo ""

# =====================================================================
# 1. RATE LIMITING
# =====================================================================
if ! should_skip rate-limit; then
    log_info "Setting up rate limiting..."

    RATE_ZONE_CONF="# Rate limit zone for download paths
# Installed by nginx-security-hardening.sh
limit_req_zone \$binary_remote_addr zone=downloads_${DOMAIN//./_}:10m rate=${RATE};"

    write_file "/etc/nginx/conf.d/rate-limit-${DOMAIN}.conf" "$RATE_ZONE_CONF" "Rate limit zone (http context)"

    if is_hestia_mode; then
        RATE_HTTP="# Rate limiting on ${DOWNLOAD_PATH}
# Installed by nginx-security-hardening.sh
location ${DOWNLOAD_PATH} {
    limit_req zone=downloads_${DOMAIN//./_} burst=${BURST} nodelay;
    limit_req_status 429;
    proxy_pass ${HTTP_BACKEND};
}"
        write_file "${CONF_DIR}/nginx.conf_rate_limit" "$RATE_HTTP" "Rate limit (HTTP)"

        RATE_HTTPS="# Rate limiting on ${DOWNLOAD_PATH}
# Installed by nginx-security-hardening.sh
location ${DOWNLOAD_PATH} {
    limit_req zone=downloads_${DOMAIN//./_} burst=${BURST} nodelay;
    limit_req_status 429;"
        if $HTTPS_IS_SSL; then
            RATE_HTTPS+="
    proxy_ssl_server_name on;
    proxy_ssl_name \$host;"
        fi
        RATE_HTTPS+="
    proxy_pass ${HTTPS_BACKEND};
}"
        write_file "${CONF_DIR}/nginx.ssl.conf_rate_limit" "$RATE_HTTPS" "Rate limit (HTTPS)"
    else
        RATE_SNIPPET="# Rate limiting on ${DOWNLOAD_PATH}
# Installed by nginx-security-hardening.sh
# Include inside your server block: include snippets/rate-limit-${DOMAIN}.conf;
location ${DOWNLOAD_PATH} {
    limit_req zone=downloads_${DOMAIN//./_} burst=${BURST} nodelay;
    limit_req_status 429;
}"
        write_file "/etc/nginx/snippets/rate-limit-${DOMAIN}.conf" "$RATE_SNIPPET" "Rate limit snippet"
        log_info "Add to your server block: include snippets/rate-limit-${DOMAIN}.conf;"
    fi
fi

# =====================================================================
# 2. BOT HONEYPOT
# =====================================================================
if ! should_skip honeypot; then
    log_info "Setting up bot honeypot..."

    HONEYPOT_LOG="/var/log/nginx/${DOMAIN}.honeypot.log"

    if is_hestia_mode; then
        HONEYPOT_LOG="/var/log/nginx/domains/${DOMAIN}.honeypot.log"

        TRAP_HTTP="# Bot honeypot — any request to ${TRAP_PATH} gets logged and blocked
# Installed by nginx-security-hardening.sh
location ${TRAP_PATH} {
    access_log ${HONEYPOT_LOG} combined;
    return 403;
}"
        write_file "${CONF_DIR}/nginx.conf_honeypot" "$TRAP_HTTP" "Honeypot (HTTP)"

        TRAP_HTTPS="# Bot honeypot — any request to ${TRAP_PATH} gets logged and blocked
# Installed by nginx-security-hardening.sh
location ${TRAP_PATH} {
    access_log ${HONEYPOT_LOG} combined;
    return 403;
}"
        write_file "${CONF_DIR}/nginx.ssl.conf_honeypot" "$TRAP_HTTPS" "Honeypot (HTTPS)"
    else
        TRAP_SNIPPET="# Bot honeypot — any request to ${TRAP_PATH} gets logged and blocked
# Installed by nginx-security-hardening.sh
# Include inside your server block: include snippets/honeypot-${DOMAIN}.conf;
location ${TRAP_PATH} {
    access_log ${HONEYPOT_LOG} combined;
    return 403;
}"
        write_file "/etc/nginx/snippets/honeypot-${DOMAIN}.conf" "$TRAP_SNIPPET" "Honeypot snippet"
        log_info "Add to your server block: include snippets/honeypot-${DOMAIN}.conf;"
    fi

    # Fail2ban filter
    HONEYPOT_FILTER="# Bot honeypot filter
# Installed by nginx-security-hardening.sh
[Definition]
failregex = ^<HOST> .* \"(GET|POST|HEAD) ${TRAP_PATH}
ignoreregex ="

    write_file "/etc/fail2ban/filter.d/nginx-honeypot.conf" "$HONEYPOT_FILTER" "Fail2ban honeypot filter"

    # Fail2ban jail
    HONEYPOT_JAIL="# Bot honeypot jail — ban on first hit
# Installed by nginx-security-hardening.sh
[nginx-honeypot]
enabled  = true
port     = http,https
filter   = nginx-honeypot
logpath  = ${HONEYPOT_LOG}
maxretry = 1
bantime  = ${BANTIME}
findtime = ${BANTIME}"

    write_file "/etc/fail2ban/jail.d/nginx-honeypot.conf" "$HONEYPOT_JAIL" "Fail2ban honeypot jail"

    # Create the log file so fail2ban doesn't complain
    if ! $DRY_RUN; then
        touch "$HONEYPOT_LOG"
        chmod 644 "$HONEYPOT_LOG"
    fi

    log_warn "Add a hidden link to your site template to attract bots:"
    echo -e "  ${CYAN}<a href=\"${TRAP_PATH}/\" style=\"display:none\" aria-hidden=\"true\" tabindex=\"-1\">admin</a>${NC}"
    echo ""
fi

# =====================================================================
# 3. SECURITY HEADERS
# =====================================================================
if ! should_skip headers; then
    log_info "Setting up security headers..."

    HEADERS_CONF="# Security headers
# Installed by nginx-security-hardening.sh

# Prevent MIME type sniffing
add_header X-Content-Type-Options \"nosniff\" always;

# Prevent clickjacking (SAMEORIGIN allows AWStats/panel frames)
add_header X-Frame-Options \"SAMEORIGIN\" always;

# Control referrer information
add_header Referrer-Policy \"strict-origin-when-cross-origin\" always;

# Restrict browser features
add_header Permissions-Policy \"camera=(), microphone=(), geolocation=(), payment=()\" always;

# Prevent XSS in older browsers
add_header X-XSS-Protection \"1; mode=block\" always;"

    if is_hestia_mode; then
        write_file "${CONF_DIR}/nginx.conf_security_headers" "$HEADERS_CONF" "Security headers (HTTP)"
        write_file "${CONF_DIR}/nginx.ssl.conf_security_headers" "$HEADERS_CONF" "Security headers (HTTPS)"
    else
        write_file "/etc/nginx/snippets/security-headers-${DOMAIN}.conf" "$HEADERS_CONF" "Security headers snippet"
        log_info "Add to your server block: include snippets/security-headers-${DOMAIN}.conf;"
    fi
fi

# =====================================================================
# 4. QUERY STRING FILTERING
# =====================================================================
if ! should_skip query-filter; then
    log_info "Setting up query string filtering..."

    QUERY_CONF="# Block malicious query strings (SQL injection, XSS, path traversal)
# Installed by nginx-security-hardening.sh

# SQL injection patterns
if (\$query_string ~* \"(union|select\s.*from|insert\s.*into|delete\s.*from|drop\s+table|update\s.*set|concat\s*\(|exec\s*\()\") {
    return 403;
}

# XSS patterns
if (\$query_string ~* \"(<script|javascript:|on(?:error|load|click|mouseover)=)\") {
    return 403;
}

# Path traversal
if (\$query_string ~* \"(\\.\\./|\\.\\.\\\\/)\") {
    return 403;
}

# Common exploit tools
if (\$query_string ~* \"(base64_decode|eval\s*\\(|php://input)\") {
    return 403;
}"

    if is_hestia_mode; then
        write_file "${CONF_DIR}/nginx.conf_query_filter" "$QUERY_CONF" "Query filter (HTTP)"
        write_file "${CONF_DIR}/nginx.ssl.conf_query_filter" "$QUERY_CONF" "Query filter (HTTPS)"
    else
        write_file "/etc/nginx/snippets/query-filter-${DOMAIN}.conf" "$QUERY_CONF" "Query filter snippet"
        log_info "Add to your server block: include snippets/query-filter-${DOMAIN}.conf;"
    fi
fi

# =====================================================================
# 5. HOTLINK PROTECTION
# =====================================================================
if ! should_skip hotlink-protection; then
    log_info "Setting up hotlink protection..."

    HOTLINK_CONF="# Hotlink protection — block image requests from other domains
# Installed by nginx-security-hardening.sh
location ~* \.(png|jpg|jpeg|gif|webp|svg|ico)$ {
    valid_referers none blocked ${DOMAIN} *.${DOMAIN};
    if (\$invalid_referer) {
        return 403;
    }
}"

    if is_hestia_mode; then
        write_file "${CONF_DIR}/nginx.conf_hotlink_protection" "$HOTLINK_CONF" "Hotlink protection (HTTP)"
        write_file "${CONF_DIR}/nginx.ssl.conf_hotlink_protection" "$HOTLINK_CONF" "Hotlink protection (HTTPS)"
    else
        write_file "/etc/nginx/snippets/hotlink-protection-${DOMAIN}.conf" "$HOTLINK_CONF" "Hotlink protection snippet"
        log_info "Add to your server block: include snippets/hotlink-protection-${DOMAIN}.conf;"
    fi
fi

# =====================================================================
# FINALIZE
# =====================================================================
echo ""

if $DRY_RUN; then
    log_dry "No changes were made (dry-run mode)"
    log_dry "Remove --dry-run to apply changes"
else
    log_info "Testing nginx configuration..."
    if nginx -t 2>&1; then
        log_info "Nginx config test passed"
        log_info "Reloading nginx..."
        systemctl reload nginx
        log_info "Nginx reloaded"
    else
        log_error "Nginx config test FAILED — check the errors above"
        log_error "Your previous config is still active (no reload happened)"
        log_error "Backup files are in .backups/ subdirectories"
        exit 1
    fi

    if ! should_skip honeypot; then
        if systemctl is-active --quiet fail2ban; then
            log_info "Reloading fail2ban..."
            fail2ban-client reload 2>/dev/null || true
            log_info "Fail2ban reloaded"
        else
            log_warn "Fail2ban is not running — start it to enable the honeypot jail"
        fi
    fi

    echo ""
    log_info "Installation complete!"
    echo ""

    should_skip rate-limit          || echo -e "  ${GREEN}✓${NC} Rate limiting:        ${RATE} on ${DOWNLOAD_PATH} (burst ${BURST})"
    should_skip honeypot            || echo -e "  ${GREEN}✓${NC} Bot honeypot:         ${TRAP_PATH} → ban ${BANTIME}s"
    should_skip headers             || echo -e "  ${GREEN}✓${NC} Security headers:     X-Frame-Options, CSP, Referrer-Policy"
    should_skip query-filter        || echo -e "  ${GREEN}✓${NC} Query filter:         SQL injection, XSS, path traversal blocked"
    should_skip hotlink-protection  || echo -e "  ${GREEN}✓${NC} Hotlink protection:   Image hotlinking blocked"

    if ! is_hestia_mode; then
        echo ""
        log_info "Standalone nginx — include the snippets in your server block:"
        should_skip rate-limit          || echo -e "  ${CYAN}include snippets/rate-limit-${DOMAIN}.conf;${NC}"
        should_skip honeypot            || echo -e "  ${CYAN}include snippets/honeypot-${DOMAIN}.conf;${NC}"
        should_skip headers             || echo -e "  ${CYAN}include snippets/security-headers-${DOMAIN}.conf;${NC}"
        should_skip query-filter        || echo -e "  ${CYAN}include snippets/query-filter-${DOMAIN}.conf;${NC}"
        should_skip hotlink-protection  || echo -e "  ${CYAN}include snippets/hotlink-protection-${DOMAIN}.conf;${NC}"
    fi

    if ! should_skip honeypot; then
        echo ""
        log_warn "Don't forget to add the hidden honeypot link to your site template!"
    fi
fi
