Skip to content

Commit 64d4266

Browse files
fix(agent): stop installer self-kill in pre-installation cleanup (v1.8.4)
The Linux/macOS agent installer could abort during "pre-installation cleanup" (terminal showed `Killing processes matching: haproxy-agent` then `Killed`, returning to the prompt) when the install script's own command line contained "haproxy-agent". The cleanup killed processes via `pgrep -f "$pattern"` starting with the bare string "haproxy-agent", which also matched the running installer's own command line and a sudo/PAM ancestor that the $$/$PPID self-exclusion did not cover, so the installer terminated itself before installing. - linux_install.sh / macos_install.sh: the cleanup kill loop now targets ONLY the installed agent - "$INSTALL_DIR/haproxy-agent" (the daemon binary path) and the agent service/label ("haproxy-agent.service" / "com.haproxy.agent") - never the bare "haproxy-agent" substring. Neither pattern can match the installer's own command line. The redundant bare pattern is dropped (the service is stopped separately, and the binary-path pattern still catches a running daemon). - frontend (AgentManagement.js): name the downloaded scripts install-agent-<platform>.sh / uninstall-agent-<platform>.sh (matching the backend's suggested filename) - defense in depth so this cannot resurface. Installer-only change. The running agent and its privilege model are unchanged (it runs as root for HAProxy reload, config writes, keepalived, and self-upgrade). The cleanup runs only on a full interactive install (gated by SKIP_TO_DAEMON), so daemon mode, self-upgrade, and config/version apply are unaffected. Both agent scripts kept in sync. Scripts parse on bash 4.2-5.2; full backend suite green. Addresses #31.
1 parent 27fbe48 commit 64d4266

8 files changed

Lines changed: 35 additions & 10 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2415,6 +2415,7 @@ Developed with ❤️ for the HAProxy community
24152415

24162416
## Release Notes
24172417

2418+
- **v1.8.4** (2026-06-27) — **Agent installer self-kill fix** (Issue #31): the Linux/macOS agent installer could abort during "pre-installation cleanup" (terminal showed `Killing processes matching: haproxy-agent` then `Killed`) when the install script's own filename contained "haproxy-agent". The cleanup killed processes by matching the bare string "haproxy-agent" against full command lines, which also matched the running installer (and a `sudo`/PAM ancestor the self-exclusion did not cover), so the installer terminated itself. Cleanup now targets only the installed agent (the `$INSTALL_DIR/haproxy-agent` binary and the agent service), never the bare string, and the UI now names the downloaded scripts `install-agent-<platform>.sh` / `uninstall-agent-<platform>.sh`. Installer-only change; the running agent and its privilege model (it runs as root for HAProxy reload, config writes, keepalived, and self-upgrade) are unchanged.
24182419
- **v1.8.3** (2026-06-25) — **Agent heartbeat JSON fix** (Issue #31): a self-hosted agent could fail every heartbeat with `HTTP 400 Invalid JSON: Expecting property name enclosed in double quotes` when the system-info block it collects came back empty on an unusual host, leaving a stray comma in the hand-built heartbeat JSON. The agent script now substitutes a valid placeholder when that block is empty so it can no longer emit a stray comma, and the backend heartbeat endpoint now parses valid payloads as-is and, only when a body fails to parse, tolerates that specific malformed pattern (a leading or doubled comma) so an already-deployed agent recovers on its next heartbeat after this build is deployed. Backend + agent-script only; healthy agents of every version are byte-for-byte unaffected.
24192420
- **v1.8.2** (2026-06-25) — **ACME nonce fix** (Issue #35 follow-up): the ACME client now scopes the anti-replay nonce **per certificate authority** so a nonce issued by one CA is never sent to another. This fixes ZeroSSL/Google account registration failing with `malformed: The Replay Nonce could not be base64url-decoded` (the client previously shared one nonce across CAs and only auto-retried on `badNonce`). Account registration now always uses a fresh nonce from the target CA, and the retry covers this case too. Backend-only; HTTP-01 and Let's Encrypt are unaffected.
24202421
- **v1.8.1** (2026-06-24) — **ACME DNS-01 fixes** (Issue #35 follow-up): Cloudflare API tokens are now sanitized so a pasted token with quotes/spaces no longer fails with "Invalid request headers"; ZeroSSL/Google **External Account Binding (EAB)** can be entered per-account in the register dialog and EAB-required failures show a clear message; and **Apply Management** now categorizes cluster ACME enable/disable changes under their own "ACME Challenge Routing" section and **Apply/Reject All** correctly process them (previously "Rejected 0 HA/VIP change(s)"), consistent with every other entity. Fully backward compatible.

backend/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from datetime import datetime, timedelta
1010

1111
# Build/deploy marker for the v1.8.x (Issue #35, DNS-01) rollout — ensures the pipeline ships this commit's image.
12-
_version_info = {"version": "1.8.3", "releaseName": "Agent heartbeat JSON fix", "releaseDate": "2026-06-25"}
12+
_version_info = {"version": "1.8.4", "releaseName": "Agent installer self-kill fix", "releaseDate": "2026-06-27"}
1313
for _vpath in ["/app/version.json", os.path.join(os.path.dirname(__file__), "..", "version.json")]:
1414
try:
1515
with open(_vpath) as _vf:

backend/tests/test_agent_script_heartbeat_json.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,18 @@ def test_b2_guard_precedes_every_fragment_system_info_use():
4444
for name, script in (("linux", LINUX), ("macos", MACOS)):
4545
assert script.count(" $system_info,") >= 1, f"{name}: fragment heartbeat form unexpectedly gone"
4646
assert script.count(_B2_GUARD) == 2, f"{name}: each fragment call site must carry the guard"
47+
48+
49+
def test_cleanup_does_not_self_kill_via_bare_haproxy_agent_pattern():
50+
# Issue #31 (v1.8.4): the pre-installation cleanup kills processes by pgrep -f "$pattern". A bare
51+
# "haproxy-agent" pattern also matches the installer's OWN path (install-haproxy-agent-*.sh) and a
52+
# sudo/PAM ancestor, so the installer killed itself. The kill loop must target ONLY the installed
53+
# agent (binary path + service/label), never the bare string.
54+
for name, script in (("linux", LINUX), ("macos", MACOS)):
55+
assert 'for pattern in "haproxy-agent"' not in script, (
56+
f"{name}: pre-install cleanup uses the bare 'haproxy-agent' kill pattern -> self-kill (issue #31)"
57+
)
58+
# The narrowed, installer-safe pattern must be present (binary path via $INSTALL_DIR).
59+
assert 'for pattern in "$INSTALL_DIR/haproxy-agent"' in script, (
60+
f"{name}: cleanup must match the installed binary path, not a bare substring"
61+
)

backend/utils/agent_scripts/linux_install.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,12 @@ safe_remove() {
470470
[[ "$QUIET_MODE" != "true" ]] && echo "Terminating existing HAProxy Agent processes..."
471471
KILLED_COUNT=0
472472
INSTALLER_PID=$$
473-
for pattern in "haproxy-agent" "/usr/local/bin/haproxy-agent" "haproxy-agent.service"; do
473+
# issue #31: match ONLY the installed agent (binary path + service), never the bare string
474+
# "haproxy-agent". With pgrep -f, that bare string can also match the installer's OWN command line
475+
# or a sudo/PAM ancestor (which the $$/$PPID guard does not fully cover), making the cleanup kill
476+
# the installer itself ("Killed", install aborts). The systemd service is also stopped below; the
477+
# "$INSTALL_DIR/haproxy-agent" path still catches any running daemon.
478+
for pattern in "$INSTALL_DIR/haproxy-agent" "haproxy-agent.service"; do
474479
PIDS=$(pgrep -f "$pattern" 2>/dev/null || true)
475480
if [[ -n "$PIDS" ]]; then
476481
FILTERED=""

backend/utils/agent_scripts/macos_install.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,11 @@ safe_remove() {
318318
[[ "$QUIET_MODE" != "true" ]] && echo "Terminating existing HAProxy Agent processes..."
319319
KILLED_COUNT=0
320320
INSTALLER_PID=$$
321-
for pattern in "haproxy-agent" "/usr/local/bin/haproxy-agent" "com.haproxy.agent"; do
321+
# issue #31: match ONLY the installed agent (binary path + LaunchDaemon label), never the bare
322+
# string "haproxy-agent". With pgrep -f, that bare string can also match the installer's OWN command
323+
# line or a sudo ancestor (which the $$/$PPID guard does not fully cover), making the cleanup kill
324+
# the installer itself. The "$INSTALL_DIR/haproxy-agent" path still catches any running daemon.
325+
for pattern in "$INSTALL_DIR/haproxy-agent" "com.haproxy.agent"; do
322326
PIDS=$(pgrep -f "$pattern" 2>/dev/null || true)
323327
if [[ -n "$PIDS" ]]; then
324328
FILTERED=""

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "haproxy-openmanager-frontend",
3-
"version": "1.8.3",
3+
"version": "1.8.4",
44
"description": "HAProxy Load Balancer Management UI",
55
"license": "AGPL-3.0-or-later",
66
"dependencies": {

frontend/src/components/AgentManagement.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2378,7 +2378,7 @@ const AgentManagement = () => {
23782378
const element = document.createElement('a');
23792379
const file = new Blob([installScript], { type: 'text/plain' });
23802380
element.href = URL.createObjectURL(file);
2381-
element.download = `install-haproxy-agent-${selectedPlatform}.sh`;
2381+
element.download = `install-agent-${selectedPlatform}.sh`;
23822382
document.body.appendChild(element);
23832383
element.click();
23842384
document.body.removeChild(element);
@@ -2516,7 +2516,7 @@ const AgentManagement = () => {
25162516
const element = document.createElement('a');
25172517
const file = new Blob([uninstallScript], { type: 'text/plain' });
25182518
element.href = URL.createObjectURL(file);
2519-
element.download = `uninstall-haproxy-agent-${selectedPlatform}.sh`;
2519+
element.download = `uninstall-agent-${selectedPlatform}.sh`;
25202520
document.body.appendChild(element);
25212521
element.click();
25222522
document.body.removeChild(element);
@@ -3147,7 +3147,7 @@ const AgentManagement = () => {
31473147
const element = document.createElement('a');
31483148
const file = new Blob([deleteUninstallScript], { type: 'text/plain' });
31493149
element.href = URL.createObjectURL(file);
3150-
element.download = `uninstall-haproxy-agent-${agentToDelete.platform || 'linux'}.sh`;
3150+
element.download = `uninstall-agent-${agentToDelete.platform || 'linux'}.sh`;
31513151
document.body.appendChild(element);
31523152
element.click();
31533153
document.body.removeChild(element);

version.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "1.8.3",
3-
"releaseName": "Agent heartbeat JSON fix",
4-
"releaseDate": "2026-06-25"
2+
"version": "1.8.4",
3+
"releaseName": "Agent installer self-kill fix",
4+
"releaseDate": "2026-06-27"
55
}

0 commit comments

Comments
 (0)