global
	log /dev/log	local0
	log /dev/log	local1 notice
	
	# песочница
	# chroot /var/lib/haproxy
	
	stats socket /run/haproxy/admin.sock mode 660 level admin
	stats timeout 30s
	
	user haproxy
	group haproxy
	daemon

	# Пути к сертификатам по умолчанию
	ca-base /etc/ssl/certs
	crt-base /etc/ssl/private

	# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
	ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
	ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
	ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

    # ВАЖНО: сохраняем TLS ClientHello для вычисления JA3/JA4/GREASE
    tune.ssl.capture-buffer-size 16384
	
	# Корректная передача boolean-значений из HAProxy в Lua
	tune.lua.bool-sample-conversion normal
	
	# Скрипты
	
	# База по странам
	lua-load /etc/haproxy/lua/geoip_new2.lua
	
	# База по ASN
	lua-load /etc/haproxy/lua/asn.lua
	
	lua-load /etc/haproxy/lua/ja3n.lua
	lua-load /etc/haproxy/lua/ja4.lua
	lua-load /etc/haproxy/lua/grease_detect.lua

defaults
	log	global
	
	mode	http
	option	httplog
	option	dontlognull
	
    timeout http-request 10s
    timeout connect 5s
    timeout client 50s
    timeout server 50s
		
	errorfile 400 /etc/haproxy/errors/400.http
	errorfile 403 /etc/haproxy/errors/403.http
	errorfile 408 /etc/haproxy/errors/408.http
	errorfile 500 /etc/haproxy/errors/500.http
	errorfile 502 /etc/haproxy/errors/502.http
	errorfile 503 /etc/haproxy/errors/503.http
	errorfile 504 /etc/haproxy/errors/504.http
	
backend coraza-spoa
    mode tcp
    server s1 127.0.0.1:9000

# --- ФРОНТЕНД (Прием трафика) ---
frontend my_frontend
	mode http

	# Слушаем обычный HTTP
	bind 185.137.233.123:80
	
    # Слушаем HTTPS и указываем правильный путь к склеенному сертификату
	# Достаточно положить в папку crt /var/www/httpd-cert/haproxy/ .pem - слитый .crt и key
	bind 185.137.233.123:443 ssl crt /var/www/httpd-cert/haproxy/ alpn h2,http/1.1
	
	# Исключаем Let's Encrypt. Чтобы было можно создавать SSL сертификаты
	acl is_letsencrypt path_beg /.well-known/acme-challenge/
	
	# Перенаправление HTTP на HTTPS
    # Перенаправляем на https всех, кто пришел НЕ по защищенному соединению
    # http-request redirect scheme https unless { ssl_fc }
	http-request redirect scheme https if !{ ssl_fc } !is_letsencrypt

    # Разрешаем доступ только если запросили именно наш домен
	acl is_valid_domain hdr(host) -i test.bratstvopera.ru kupidonia.ru www.kupidonia.ru passs.kupidonia.ru loba.kupidonia.ru nobobo.kupidonia.ru tishka.kupidonia.ru
	#acl is_valid_domain hdr(host) -i kupidonia.ru passs.kupidonia.ru loba.kupidonia.ru nobobo.kupidonia.ru
	
	# Если домен чужой (или обращение по IP) - обрываем соединение без ответа
	http-request silent-drop if !is_valid_domain
	
	# === ДОБАВЛЯЕМ РЕДИРЕКТ С WWW НА БЕЗ-WWW ===
	acl is_www hdr(host) -i www.kupidonia.ru
	http-request redirect prefix https://kupidonia.ru code 301 if is_www

    # Разрешаем только стандартные методы
    acl allowed_methods method GET POST HEAD OPTIONS PUT DELETE
    http-request deny if !allowed_methods
    
    # Или можно сделать точечный запрет на CONNECT и TRACE (используется для дебага)
    #http-request deny if { method CONNECT }
    #http-request deny if { method TRACE }

    # Удаляем потенциально поддельные заголовки от клиента
    http-request del-header X-Forwarded-Proto

	# Говорим Nginx'у, что клиент пришел по HTTPS (чтобы Nginx не делал лишних редиректов)
    http-request add-header X-Forwarded-Proto https if { ssl_fc }

    # --- БЕЛЫЙ СПИСОК НАШИХ IP ---

    # Загружаем список доверенных IP
    acl is_whitelisted_ip src -f /etc/haproxy/whitelists/whitelist-our-ips.txt
    
    # Если IP в белом списке — пропускаем все проверки
    # Команда allow немедленно прекращает обработку правил для этого запроса и отправляет его дальше в бэкенд. 
    http-request allow if is_whitelisted_ip
	
	# --- БЛОКИРОВКА ПО СТРАНЕ ---
	
    # 1. Вызываем lua-скрипт и сохраняем код страны посетителя
    http-request set-var(txn.country) lua.geoip_country
	
	# 2. Создаем список стран для блокировки (через пробел)
    acl is_blocked_country var(txn.country) -m str -i CN IN VN SG KR ID HK TW TH MY PH PK TW BR NG EG MX AR SG ZA IR UY CL
	
    # 3. Блокируем, если страна совпала со списком
    http-request deny if is_blocked_country !is_letsencrypt
	
    # 4. Передаем эту переменную в заголовок X-Country для PHP
    http-request set-header X-Country %[var(txn.country)]

	# --- БЛОКИРОВКА ПО ASN ---
	
	# 1. Получаем номер ASN
	http-request set-var(txn.asn) lua.geoip_asn
	
	# 2. Список плохих русских ASN (точное совпадение строк)
	acl is_blocked_asn var(txn.asn) -m str 9123 12555 50214 61178 214574 208969 35048 205090 213220
	# 2. Список плохих зарубежных ASN (точное совпадение строк)
	#acl is_blocked_asn var(txn.asn) -m str 9123 12555 50214 61178 214574 208969 35048 205090 213220
	
	# 3. Блокируем, если совпало
	http-request deny if is_blocked_asn !is_letsencrypt
	
	# 4. Передаем в PHP для проверки и статистики
	http-request set-header X-ASN %[var(txn.asn)]
	
	# --- БЛОКИРОВКА ПО ИМЕНИ ПРОВАЙДЕРА (Хостинги и Дата-центры) ---
	
	# 1. Получаем имя провайдера из базы
	http-request set-var(txn.asn_name) lua.geoip_asn_name
	
	# 2. Проверяем имя на вхождение нежелательных подстрок (без учета регистра)
	# Флаг -m sub -i ищет подстроку, поэтому поймает "Amazon.com", "Hetzner Online GmbH" и т.д.
	acl is_blocked_provider var(txn.asn_name) -m sub -i Biterika WINDSTREAM HOSTING DigitalOcean Hetzner Amazon Linode Contabo EGIHOSTING
	
	# 3. Блокируем, если провайдер из черного списка
	http-request deny if is_blocked_provider !is_letsencrypt
	
	# 4. Передаем имя провайдера в PHP (для логов и проверки)
	http-request set-header X-ASN-Name %[var(txn.asn_name)]
	
	# --- ПЕРЕДАЧА URL КУДА ИДЕТ ЧЕЛОВЕК --------------------
	
	# Удаляем возможный поддельный заголовок, который мог прислать сам клиент
	http-request del-header X-Original-URL
	
	# Кладём в заголовок реальный URL, по которому обратился пользователь
	http-request set-header X-Original-URL %[url]
	
	# == ОТПРАВКА НА КАПЧУ ПО СТРАНЕ, REFERER И USER-AGENT ====================
	
	# 1. Указываем, что правило работает ТОЛЬКО для kupidonia.ru (с www и без)
	acl is_main_kupidonia hdr(host) -i kupidonia.ru www.kupidonia.ru
	
	# 2. Проверяем наличие заголовка Referer (true, если он есть)
	acl has_referer req.hdr(referer) -m found
	
	# 3. Указываем саму страницу капчи, чтобы исключить её из правила (иначе будет цикл!)
	acl is_captcha_page path_beg /sorry
	
	# 4. Исключаем статические файлы (картинки, стили, скрипты)
	acl is_static path_end -i .css .js .jpg .jpeg .png .gif .svg .ico .woff2 .webp .pdf
	
	# 5. Ищем старые маки (Mac OS X 10.15 или 10_15)
	acl is_old_mac req.hdr(user-agent) -m sub -i "Mac OS X 10"
	
	# 5. Ищем старые Android
	acl is_old_android req.hdr(user-agent) -m sub -i "Android 10"
	
	# 6 Проверяем, что это Россия, Казахстан или Беларусь
	acl is_cis_country var(txn.country) -m str -i RU BY KZ
	
	# 7. Исключаем запросы с параметром ysclid (метка Яндекса)
	# Проверяем, что ysclid= имеет непустое значение (хотя бы 1 символ после =)
	acl has_ysclid url -m reg -i "ysclid=[^&]+"
	
	# 8. Исключаем поисковых роботов по белому списку
	acl is_whitelisted_bot src -f /etc/haproxy/whitelists/whitelist-bots.txt
	
	# 9 ДЕЛАЕМ РЕДИРЕКТ (302 Временное перенаправление)
	# Правило 1: Старые Mac (всегда отправляем на капчу, кроме тех, у кого есть ysclid)
	http-request redirect location https://kupidonia.ru/sorry/pardon if is_main_kupidonia !has_referer !is_captcha_page !is_static !is_whitelisted_bot !has_ysclid is_old_mac
	
	# Правило 2: Старые Android ТОЛЬКО если это НЕ Россия/Казахстан/Беларусь и НЕТ ysclid
	http-request redirect location https://kupidonia.ru/sorry/pardon if is_main_kupidonia !has_referer !is_captcha_page !is_static !is_whitelisted_bot !has_ysclid is_old_android !is_cis_country
	
	# ------------------------------------------------------
	
    # Удаляем информацию о веб-сервере и языке программирования
    http-response del-header Server
    http-response del-header X-Powered-By
    http-response del-header X-AspNet-Version
	
    # Запрещаем встраивать сайт во фреймы на чужих доменах (защита от Clickjacking)
    #http-response set-header X-Frame-Options SAMEORIGIN
	
    # Запрещаем браузеру пытаться "угадывать" тип файлов (защита от XSS через картинки)
    http-response set-header X-Content-Type-Options nosniff
	
    # Принудительно заставляем браузеры всегда использовать HTTPS (HSTS), для поддоменов тоже
    #http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
	# или строгий HTTPS только на основном домене (без поддоменов)
	#http-response set-header Strict-Transport-Security "max-age=86400"
	
    # Жесткий контроль синтаксиса заголовков
    #option http-restrict-req-hdr-names preserve
	
    # Вычисляем JA3N, JA4 и GREASE.
    # Данные ClientHello доступны благодаря
    # tune.ssl.capture-buffer-size в секции global.
    http-request lua.fingerprint_ja3n if { ssl_fc }
    http-request lua.ja4 if { ssl_fc }
    http-request set-var(txn.has_grease) lua.has_grease if { ssl_fc }
	
    # Удаляем потенциально поддельные заголовки от клиента
    http-request del-header X-JA3-Hash
    http-request del-header X-JA3-String
    http-request del-header X-JA4-Fingerprint
    http-request del-header X-Has-Grease
	
	# == ПРОВЕРКА REFERER И РЕДИРЕКТ НА КАПЧУ ====================
	
	# 1. Указываем, что правило работает ТОЛЬКО для kupidonia.ru (с www и без)
	#acl is_main_kupidonia hdr(host) -i kupidonia.ru www.kupidonia.ru
	
	# 2. Проверяем наличие заголовка Referer (true, если он есть)
	#acl has_referer req.hdr(referer) -m found
	
	# 3. Указываем саму страницу капчи, чтобы исключить её из правила
	#acl is_captcha_page path_beg /sorry
	
	# 4. Исключаем статические файлы
	#acl is_static path_end -i .css .js .jpg .jpeg .png .gif .svg .ico .woff2 .webp .pdf
	
	# 5. ДЕЛАЕМ РЕДИРЕКТ (302 Временное перенаправление)
	#http-request redirect location /sorry if is_main_kupidonia !has_referer !is_captcha_page !is_static
	
	# == ЗАЩИТА ГИТА =============================
	
	# Ищем попытки скачать файл конфигурации .env (например: /.env или /api/.env)
	acl is_env_scan path_end .env
	
	# Ищем директорию .git в любом месте пути (например: /.git/config или /assets/.git/)
	acl is_git_scan path_dir .git
	
	# Молча сбрасываем (рвём TCP-соединение), если сработало хоть одно из условий
	http-request silent-drop if is_git_scan
	http-request silent-drop if is_env_scan
	
	# ===============================
	
    # Передаем вычисленные отпечатки
    http-request set-header X-JA3-Hash %[var(txn.fingerprint_ja3n)] if { ssl_fc }
    http-request set-header X-JA3-String %[var(txn.fingerprint_ja3n_raw)] if { ssl_fc }
    http-request set-header X-JA4-Fingerprint %[var(txn.ja4)] if { ssl_fc }
    http-request set-header X-Has-Grease %[var(txn.has_grease)] if { ssl_fc }
	
    # Временный захват значений для журнала HAProxy.
    # После проверки эти четыре строки можно удалить.
    #http-request capture var(txn.fingerprint_ja3n) len 32 if { ssl_fc }
    #http-request capture var(txn.fingerprint_ja3n_raw) len 512 if { ssl_fc }
    #http-request capture var(txn.ja4) len 64 if { ssl_fc }
    #http-request capture var(txn.has_grease) len 1 if { ssl_fc }
	
	# Подключение Coraza
	# Указываем имя приложения (как в name в config.yaml)
	http-request set-var(txn.coraza.app) str(sample_app)
	
	# Отправляем запрос на проверку в SPOA
	filter spoe engine coraza config /etc/haproxy/coraza.cfg
	
	# Блокируем запрос (выдаем 403), если Coraza обнаружила атаку
	http-request deny deny_status 403 if { var(txn.coraza.action) -m str deny } !is_letsencrypt
	
# == CAP ====================

    # Ловим домен
    acl is_cap_host hdr(host) -i passs.kupidonia.ru kupidonia.ru nobobo.kupidonia.ru
	
    # Ловим путь
    acl is_cap_path path_beg /cap/
    
    # Если совпало и то, и другое — шлем в бэкенд капчи
    use_backend captcha_backend if is_cap_host is_cap_path
	
# == gitea ====================

    # Ловим домен Gitea
    acl is_tishka_host hdr(host) -i tishka.kupidonia.ru
    
    # Если это Let's Encrypt — отправляем в Nginx (где FastPanel создает проверочный файл)
    use_backend nginx_backend if is_tishka_host is_letsencrypt
    
    # Весь остальной трафик Gitea отправляем в её бэкенд
    use_backend tishka_backend if is_tishka_host !is_letsencrypt
	
# ============================
	
    # Отправляем в Nginx
    default_backend nginx_backend
	
# --- (Передача в Nginx) ---
backend nginx_backend

    # Если в очереди люди ждут дольше 10 секунд — отдаем им ошибку 503 (Сервер перегружен), чтобы не висели вечно
    timeout queue 60s

    # Добавляем заголовок X-Forwarded-For с реальным IP клиента
    option forwardfor
    
    # Указываем адрес Nginx (вы выбрали порт 8081) Пускаем на сайт не больше 400 одновременных активных запросов
    server nginx1 127.0.0.1:8081 check maxconn 400

# --- (Передача в Cap) ---

backend captcha_backend
    mode http
	
    # Отрезаем /cap/ из пути, чтобы на порт 3000 ушел чистый запрос
    http-request replace-path /cap/(.*) /\1
	
    # Передаем реальный IP
    option forwardfor
    http-request set-header X-Real-IP %[src]
	
    # Отправляем на сервер с капчей
    server cap_node 127.0.0.1:3000 check

# --- (Передача в gitea) ---
backend tishka_backend

    mode http
	
    # Передаем реальный IP
    option forwardfor
    http-request set-header X-Real-IP %[src]
	
    # Отправляем на сервер с капчей
    server cap_node 127.0.0.1:3001 check

