The panel now fronts user domains with HAProxy 3.4 LTS (official performance packages) instead of Traefik: about 100 MB of RSS and 200 MB of disk saved for a feature set that never used more than Host routing on :80 and SNI passthrough on :443. - hpx: publish the proxy configuration as immutable GENERATIONS. Every domain mutation renders the whole set from the DB, validates it with the pinned `haproxy -c`, and only then repoints the `current` symlink, so a rejected candidate leaves the running proxy untouched and a stale domain can never survive into a later generation. Routing is carried by two map files (Host -> backend, SNI -> backend): per-domain frontend rules cannot work from a second file because HAProxy rejects duplicate section names since 3.3 and attaches bare directives to the section opened last. - mgr: republish and reload (SIGUSR2, i.e. `systemctl reload`) after every domain change, so a change takes effect without dropping established HTTP or TLS connections. Rollback semantics (DB first, then publish) are unchanged. - cfg,db: `net.traefik` is renamed to `net.haproxy` with identical values. A pre-rename config.yaml key and the legacy VPSMGR_TRAEFIK env var are still read (new name wins), and schema migration v18 moves the settings mirror from `traefik` to `haproxy`, keeping a newer value when both rows exist. - 30-haproxy.sh: install the pinned branch from the vendor repository and migrate a Traefik host in one run — install and validate HAProxy first, then remove Traefik's binary, unit, service account and dynamic rules (only its config file is kept, inert). No fallback keeps both proxies alive. - upgrade-haproxy.sh: follow the pinned branch to its newest patch, with a configuration check and a graceful reload; `install.sh --update` runs it. - tests: generation publish/rollback, map rendering, config-key and settings migrations, plus real-binary tests (skipped unless VPSMGR_TEST_HAPROXY_BIN is set) that run the generated configuration through `haproxy -c` and start it in a network namespace. Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
98 lines
4.2 KiB
INI
98 lines
4.2 KiB
INI
# Managed by vpsmgr — generated file, do not edit by hand.
|
|
# Changes are overwritten on the next write; use the panel / `vps` CLI instead.
|
|
#
|
|
# THIS IS THE STATIC ENTRY CONFIG, not a generation file. scripts/30-haproxy.sh
|
|
# installs it as /etc/haproxy/haproxy.cfg (it is byte-identical to what
|
|
# internal/hpx.renderMainConfig produces, and
|
|
# TestMainConfigTemplateMatchesBootstrap fails if the two drift).
|
|
#
|
|
# The unit loads it FIRST and the generation in service SECOND:
|
|
#
|
|
# /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg \
|
|
# -f /etc/haproxy/current/domains.cfg
|
|
#
|
|
# Layout of the published side (all written by the panel, atomically):
|
|
#
|
|
# current -> releases/<NNNNNN>/ the generation in service
|
|
# domains.cfg one backend per domain
|
|
# maps/http.map Host -> backend (port 80)
|
|
# maps/sni.map SNI -> backend (port 443)
|
|
# manifest generation, domain count, timestamp
|
|
#
|
|
# Routing uses those maps so that adding or removing a domain never changes a
|
|
# frontend: a map miss yields an empty backend name, which HAProxy treats as
|
|
# "no rule matched" and falls through to default_backend (the reject backend).
|
|
# Keep this file free of anything domain-specific — it is the one part of the
|
|
# layout a reload cannot change.
|
|
|
|
global
|
|
log /dev/log local0
|
|
log /dev/log local1 notice
|
|
maxconn 4096
|
|
# Started as root by systemd (to bind 80/443), then dropped to 'haproxy'.
|
|
user haproxy
|
|
group haproxy
|
|
# Master-worker is selected with -Ws on the command line, which systemd
|
|
# needs for Type=notify; the old `master-worker` directive is deprecated.
|
|
#
|
|
# A reload keeps the old worker alive until its connections finish. This
|
|
# bounds how long a stuck worker may linger (5 minutes) so repeated reloads
|
|
# cannot accumulate processes. It applies to the DRAIN of established
|
|
# connections, not to new ones, so a long-lived SSH/WebSocket tunnel may in
|
|
# principle be cut short — raise it if that ever matters more than memory.
|
|
hard-stop-after 5m
|
|
# Local admin socket: used by `vps` diagnostics and for graceful reloads.
|
|
stats socket /run/haproxy/admin.sock mode 660 level admin
|
|
|
|
defaults
|
|
log global
|
|
option dontlognull
|
|
retries 2
|
|
timeout connect 5s
|
|
# Deliberately generous: SSH tunnels and WebSockets live for hours and the
|
|
# 443 path is a raw TCP passthrough, so there is nothing to time out early.
|
|
timeout client 1h
|
|
timeout server 1h
|
|
timeout tunnel 1h
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# :80 - HTTP host routing to the containers
|
|
# ---------------------------------------------------------------------------
|
|
frontend fe_http
|
|
mode http
|
|
bind :80
|
|
option httplog
|
|
# Normalize before the map lookup: lowercase, then drop a ":port" suffix
|
|
# that a client may have sent.
|
|
http-request set-var(txn.host) req.hdr(host),lower,regsub(:[0-9]+$,)
|
|
http-request set-header X-Forwarded-Proto http
|
|
http-request set-header X-Forwarded-Port 80
|
|
# The client address reaches the container as X-Forwarded-For, added by the
|
|
# backends (option forwardfor). PROXY protocol is never used on :80.
|
|
use_backend %[var(txn.host),map(/etc/haproxy/current/maps/http.map)]
|
|
default_backend be_http_reject
|
|
|
|
backend be_http_reject
|
|
mode http
|
|
http-request deny deny_status 404
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# :443 - TLS SNI routing (PASSTHROUGH: HAProxy never terminates TLS here)
|
|
# ---------------------------------------------------------------------------
|
|
frontend fe_tls
|
|
mode tcp
|
|
bind :443
|
|
option tcplog
|
|
# The SNI is only available once the ClientHello has arrived; without an
|
|
# inspect-delay the routing decision would be taken before it is known.
|
|
tcp-request inspect-delay 2s
|
|
tcp-request content accept if { req.ssl_hello_type 1 }
|
|
use_backend %[req.ssl_sni,lower,map(/etc/haproxy/current/maps/sni.map)]
|
|
default_backend be_tls_reject
|
|
|
|
backend be_tls_reject
|
|
mode tcp
|
|
# No SNI, an unregistered host, or anything that is not a TLS ClientHello:
|
|
# close immediately instead of forwarding.
|
|
tcp-request content reject
|