Files
bluebluesodaandCommandCodeBot 5c76576ffb fix(haproxy): load the published generation in the unit, not just the entry config
The installed unit started HAProxy with the static entry config alone:

  -Ws -f /etc/haproxy/haproxy.cfg

That file declares the frontends, the reject backends and the timeouts; the
per-domain backends and the routing maps live in the generation the panel
publishes, and the maps' paths point at it. So HAProxy came up healthy, bound
80/443 and answered 404 on :80 and closed every :443 connection — no managed
domain was reachable. `haproxy -c` on the entry config alone passes, which is
why the install-time check did not catch it.

- the unit now passes both files, the generation through "current" so a reload
  picks up whatever the panel published last;
- 30-haproxy.sh validates exactly that command line (entry + generation) and
  requires domains.cfg AND both maps before it considers the bootstrap
  generation in place, so the pre-start check can no longer pass a setup that
  serves nothing;
- tests: TestUnitLoadsBothConfigFiles parses the unit's own ExecStart and fails
  if it does not pass two config files, and TestUnitCommandLineRoutesTraffic
  runs that command line against a real HAProxy and asserts a registered host
  reaches its backend. Both fail against the old unit.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-16 14:17:43 +00:00

53 lines
2.4 KiB
Desktop File

# Managed by vpsmgr — installed file, do not edit by hand.
# Changes are overwritten on the next install.
#
# Installed to /etc/systemd/system/haproxy.service, which takes precedence over
# the unit the distribution's haproxy package ships in /lib/systemd/system.
# Exactly one unit may own 80/443.
[Unit]
Description=HAProxy domain proxy (vpsmgr)
Documentation=https://docs.haproxy.org/3.4/management.html
Wants=network-online.target
After=network-online.target
# The panel publishes generations under /etc/haproxy and reloads the service
# after a successful validation, so start after it if both are enabled.
After=vps.service
[Service]
# -Ws = master-worker mode speaking the systemd notify protocol:
# * the master process stays alive and owns the workers,
# * a reload (SIGUSR2 to the master) starts a new worker with the re-read
# configuration and lets the old one DRAIN established connections, which
# is why a published generation never drops a live HTTP or TLS connection,
# * `systemctl reload` therefore never has to restart the process.
#
# BOTH -f ARGUMENTS ARE REQUIRED. The static entry config only declares the
# frontends, the reject backends and the timeouts; the per-domain backends live
# in the generation the panel publishes, and the routing maps whose paths the
# entry config resolves point at THAT generation. Loading the entry config
# alone starts a perfectly healthy-looking HAProxy that answers 404 on :80 and
# closes every :443 connection, because no domain backend exists. Any later
# change to this command line has to keep both files, and
# TestUnitLoadsBothConfigFiles fails if it does not.
#
# The generation is loaded through "current" so a reload picks up whatever the
# panel published last; scripts/30-haproxy.sh creates the bootstrap generation
# before the unit is ever started.
Type=notify
ExecStart=/usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/current/domains.cfg -p /run/haproxy/haproxy.pid
ExecReload=/bin/kill -USR2 $MAINPID
# Send SIGUSR1 (graceful stop) to the master on `systemctl stop`: established
# connections are allowed to finish; hard-stop-after (see haproxy.cfg) is the
# upper bound. KillMode=mixed keeps systemd from SIGKILLing the workers while
# they drain.
KillSignal=SIGUSR1
KillMode=mixed
Restart=on-failure
RestartSec=3
RuntimeDirectory=haproxy
RuntimeDirectoryMode=0750
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target