f71c0c8ed6
treefmt / nix fmt (pull_request) Successful in 10s
pytest / pytest (pull_request) Failing after 11m10s
build_systems / build-rhapsody-in-green (pull_request) Failing after 16m12s
build_systems / build-leviathan (pull_request) Failing after 16m12s
build_systems / build-jeeves (pull_request) Failing after 16m12s
build_systems / build-brain (pull_request) Failing after 16m12s
build_systems / build-bob (pull_request) Failing after 16m13s
137 lines
5.5 KiB
INI
137 lines
5.5 KiB
INI
global
|
|
log stdout format raw local0
|
|
# stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
|
|
stats timeout 30s
|
|
|
|
defaults
|
|
log global
|
|
mode http
|
|
option httplog
|
|
retries 3
|
|
maxconn 2000
|
|
timeout connect 5s
|
|
timeout client 50s
|
|
timeout server 50s
|
|
timeout http-request 10s
|
|
timeout http-keep-alive 2s
|
|
timeout queue 5s
|
|
timeout tunnel 2m
|
|
timeout client-fin 1s
|
|
timeout server-fin 1s
|
|
|
|
|
|
#Application Setup
|
|
frontend ContentSwitching
|
|
bind *:80 v4v6
|
|
bind *:443 v4v6 ssl crt /var/lib/acme/audiobookshelf.tmmworkshop.com/full.pem crt /var/lib/acme/cache.tmmworkshop.com/full.pem crt /var/lib/acme/jellyfin.tmmworkshop.com/full.pem crt /var/lib/acme/share.tmmworkshop.com/full.pem crt /var/lib/acme/gitea.tmmworkshop.com/full.pem crt /var/lib/acme/www.norn-sight.com/full.pem
|
|
mode http
|
|
|
|
# ACME challenge routing (must be first)
|
|
acl is_acme path_beg /.well-known/acme-challenge/
|
|
|
|
# Host ACLs (defined early so rate-limiting can scope to a single vhost)
|
|
acl host_audiobookshelf hdr(host) -i audiobookshelf.tmmworkshop.com
|
|
acl host_cache hdr(host) -i cache.tmmworkshop.com
|
|
acl host_jellyfin hdr(host) -i jellyfin.tmmworkshop.com
|
|
acl host_share hdr(host) -i share.tmmworkshop.com
|
|
acl host_gitea hdr(host) -i gitea.tmmworkshop.com
|
|
acl host_norn_sight hdr(host) -i www.norn-sight.com
|
|
|
|
# --- Rate limiting (Gitea only, per source IP) ---
|
|
# Trusted devices exempt from rate limiting (add one line per IP/CIDR).
|
|
# Internal / reserved-for-private-use ranges:
|
|
# IPv4: RFC 1918 private, loopback, link-local
|
|
acl rate_limit_allowlist src 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 127.0.0.0/8 169.254.0.0/16
|
|
# IPv6: loopback, unique local (ULA), link-local
|
|
acl rate_limit_allowlist src ::1/128 fc00::/7 fe80::/10
|
|
# Add specific public devices below as needed:
|
|
# acl rate_limit_allowlist src 192.0.2.50
|
|
|
|
# Logged-in Gitea sessions bypass the rate limits. Gitea sets the
|
|
# `i_like_gitea` session cookie on login, and it is only sent to the Gitea
|
|
# vhost, so this only affects Gitea traffic. Note: this matches cookie
|
|
# PRESENCE, not validity, so it filters anonymous crawlers (which carry no
|
|
# cookie) rather than acting as a hard security boundary.
|
|
acl gitea_logged_in req.cook(i_like_gitea) -m found
|
|
|
|
# Track HTTP request rate per client IP over a 10s sliding window. Only Gitea
|
|
# is rate-limited; all other vhosts are left alone.
|
|
# ipv6 table type also covers IPv4 (mapped), so it works for both binds.
|
|
stick-table type ipv6 size 100k expire 30s store http_req_rate(10s)
|
|
http-request track-sc0 src if host_gitea !is_acme !rate_limit_allowlist !gitea_logged_in
|
|
# Threshold: deny (429) when a client exceeds this many requests per 10s.
|
|
acl over_rate_limit sc_http_req_rate(0) gt 10
|
|
http-request deny deny_status 429 if over_rate_limit host_gitea !is_acme !rate_limit_allowlist !gitea_logged_in
|
|
|
|
# --- Request logging ---
|
|
# Capture the Host header and User-Agent so the httplog shows who is
|
|
# requesting what. They appear in the log's {captured|headers} field,
|
|
# in this order: {host|user-agent}. Client IP is already logged by httplog.
|
|
http-request capture req.hdr(Host) len 100
|
|
http-request capture req.hdr(User-Agent) len 128
|
|
|
|
# --- robots.txt ---
|
|
# Serve a single global robots.txt for every vhost (asks crawlers to wait
|
|
# 10s between requests via Crawl-delay). Returned for both HTTP and HTTPS.
|
|
# File is deployed to /etc/haproxy/robots.txt by haproxy.nix.
|
|
acl is_robots path /robots.txt
|
|
http-request return status 200 content-type "text/plain" file /etc/haproxy/robots.txt if is_robots
|
|
|
|
# --- Per-endpoint limit: Gitea compare/diff is expensive; cap at 1 req / 5 min / IP ---
|
|
# Tracked in a separate 5-minute table (st_compare) since a proxy has only one
|
|
# inline stick-table. Allow-listed (internal) IPs are exempt.
|
|
acl is_gitea_compare path_beg /Richie/dotfiles/compare
|
|
http-request track-sc1 src table st_compare if host_gitea is_gitea_compare !rate_limit_allowlist !gitea_logged_in
|
|
http-request deny deny_status 429 if host_gitea is_gitea_compare !rate_limit_allowlist !gitea_logged_in { sc_http_req_rate(1,st_compare) gt 1 }
|
|
|
|
# Hosts allowed to serve plain HTTP (add entries to skip the HTTPS redirect)
|
|
acl allow_http hdr(host) -i __none__
|
|
# acl allow_http hdr(host) -i example.tmmworkshop.com
|
|
|
|
# Redirect all HTTP to HTTPS unless on the allow list or ACME challenge
|
|
http-request redirect scheme https code 301 if !{ ssl_fc } !allow_http !is_acme
|
|
|
|
use_backend acme_challenge if is_acme
|
|
use_backend audiobookshelf_nodes if host_audiobookshelf
|
|
use_backend cache_nodes if host_cache
|
|
use_backend jellyfin if host_jellyfin
|
|
use_backend share_nodes if host_share
|
|
use_backend gitea if host_gitea
|
|
use_backend norn_sight if host_norn_sight
|
|
|
|
# Stick-table only (no servers): tracks per-IP request rate to Gitea's compare
|
|
# endpoint over a 5-minute window so the frontend can cap it at 1 per 5 min.
|
|
backend st_compare
|
|
stick-table type ipv6 size 100k expire 600s store http_req_rate(300s)
|
|
|
|
backend acme_challenge
|
|
mode http
|
|
server acme 127.0.0.1:8402
|
|
|
|
backend audiobookshelf_nodes
|
|
mode http
|
|
server server 127.0.0.1:8000
|
|
|
|
backend cache_nodes
|
|
mode http
|
|
server server 127.0.0.1:5000
|
|
|
|
backend jellyfin
|
|
option httpchk
|
|
option forwardfor
|
|
http-check send meth GET uri /health
|
|
http-check expect string Healthy
|
|
server jellyfin 127.0.0.1:8096
|
|
|
|
backend share_nodes
|
|
mode http
|
|
server server 127.0.0.1:8091
|
|
|
|
backend gitea
|
|
mode http
|
|
server server 127.0.0.1:6443
|
|
|
|
backend norn_sight
|
|
mode http
|
|
server server 127.0.0.1:8001
|