mirror of
https://github.com/taylanbakircioglu/haproxy-openmanager.git
synced 2026-09-21 10:03:29 +00:00
fix: prevent daemon crash-loop on exit 1 with retry-backoff mechanism
- Replace immediate exit 1 with retry-with-backoff in all daemon mode paths (dependency checks: 5 retries, 30/60/90/120/150s; config file: 5 retries, 15/30/45/60/75s) - Make socat missing non-fatal in daemon mode (agent continues without stats) - Add SystemD StartLimitBurst=5/StartLimitIntervalSec=120 for new installs - Add macOS launchd ThrottleInterval=30 for new installs - Fix Linux daemon fallback to installer mode (would hang on interactive read) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -260,38 +260,58 @@ fi
|
||||
if [[ "$DAEMON_MODE" == "true" ]]; then
|
||||
log "INFO" "DAEMON: Starting agent daemon with existing configuration..."
|
||||
|
||||
# Verify jq is available (required for config parsing in daemon mode)
|
||||
if ! command -v jq &> /dev/null; then
|
||||
log "ERROR" "DAEMON: jq is required but not found. Cannot parse agent configuration."
|
||||
echo "FATAL: jq is not installed. Install jq and restart the agent service." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Verify critical dependencies with retry (prevents rapid restart loops)
|
||||
# SystemD Restart=always would cause fast crash-loop if we exit immediately
|
||||
_dep_retry=0
|
||||
_dep_max=5
|
||||
while ! command -v jq &> /dev/null || ! command -v curl &> /dev/null; do
|
||||
_dep_retry=$((_dep_retry + 1))
|
||||
_missing=""
|
||||
command -v jq &> /dev/null || _missing="jq"
|
||||
command -v curl &> /dev/null || _missing="${_missing:+$_missing }curl"
|
||||
if [[ $_dep_retry -ge $_dep_max ]]; then
|
||||
log "ERROR" "DAEMON: Required tools not found after $_dep_max retries: $_missing"
|
||||
log "ERROR" "DAEMON: Install missing tools and restart: sudo systemctl restart haproxy-agent"
|
||||
exit 1
|
||||
fi
|
||||
_wait=$((_dep_retry * 30))
|
||||
log "WARN" "DAEMON: Required tools missing ($_missing). Retry $_dep_retry/$_dep_max in ${_wait}s..."
|
||||
sleep $_wait
|
||||
done
|
||||
|
||||
# Load existing configuration
|
||||
CONFIG_FILE="/etc/haproxy-agent/config.json"
|
||||
LOG_FILE="/var/log/haproxy-agent/agent.log"
|
||||
PID_FILE="/var/run/haproxy-agent.pid"
|
||||
|
||||
# Load configuration from file
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
MANAGEMENT_URL=$(jq -r '.management.url' "$CONFIG_FILE" 2>/dev/null)
|
||||
AGENT_TOKEN=$(jq -r '.management.token' "$CONFIG_FILE" 2>/dev/null)
|
||||
CLUSTER_ID=$(jq -r '.management.cluster_id' "$CONFIG_FILE" 2>/dev/null)
|
||||
AGENT_NAME=$(jq -r '.agent.name' "$CONFIG_FILE" 2>/dev/null)
|
||||
HOSTNAME=$(jq -r '.agent.hostname' "$CONFIG_FILE" 2>/dev/null)
|
||||
|
||||
log "INFO" "DAEMON: Configuration loaded - Agent: $AGENT_NAME, Cluster: $CLUSTER_ID"
|
||||
|
||||
# FIXED: Jump directly to daemon mode instead of setting SKIP_TO_DAEMON (MacOS style)
|
||||
log "INFO" "DAEMON: Going directly to daemon mode..."
|
||||
|
||||
# Jump to the daemon section at the end of the script
|
||||
SKIP_TO_DAEMON=true
|
||||
else
|
||||
log "ERROR" "DAEMON: Configuration file not found: $CONFIG_FILE"
|
||||
log "INFO" "DAEMON: Falling back to installer mode..."
|
||||
DAEMON_MODE=false
|
||||
fi
|
||||
# Load configuration from file (with retry - config may be temporarily unavailable)
|
||||
_cfg_retry=0
|
||||
_cfg_max=5
|
||||
while [[ ! -f "$CONFIG_FILE" ]]; do
|
||||
_cfg_retry=$((_cfg_retry + 1))
|
||||
if [[ $_cfg_retry -ge $_cfg_max ]]; then
|
||||
log "ERROR" "DAEMON: Configuration file not found after $_cfg_max retries: $CONFIG_FILE"
|
||||
log "ERROR" "DAEMON: Re-install the agent to recreate the configuration."
|
||||
exit 1
|
||||
fi
|
||||
_wait=$((_cfg_retry * 15))
|
||||
log "WARN" "DAEMON: Config not found: $CONFIG_FILE. Retry $_cfg_retry/$_cfg_max in ${_wait}s..."
|
||||
sleep $_wait
|
||||
done
|
||||
|
||||
MANAGEMENT_URL=$(jq -r '.management.url' "$CONFIG_FILE" 2>/dev/null)
|
||||
AGENT_TOKEN=$(jq -r '.management.token' "$CONFIG_FILE" 2>/dev/null)
|
||||
CLUSTER_ID=$(jq -r '.management.cluster_id' "$CONFIG_FILE" 2>/dev/null)
|
||||
AGENT_NAME=$(jq -r '.agent.name' "$CONFIG_FILE" 2>/dev/null)
|
||||
HOSTNAME=$(jq -r '.agent.hostname' "$CONFIG_FILE" 2>/dev/null)
|
||||
|
||||
log "INFO" "DAEMON: Configuration loaded - Agent: $AGENT_NAME, Cluster: $CLUSTER_ID"
|
||||
|
||||
# FIXED: Jump directly to daemon mode instead of setting SKIP_TO_DAEMON (MacOS style)
|
||||
log "INFO" "DAEMON: Going directly to daemon mode..."
|
||||
|
||||
# Jump to the daemon section at the end of the script
|
||||
SKIP_TO_DAEMON=true
|
||||
fi
|
||||
|
||||
# Configuration variables - MODIFY THESE IF NEEDED
|
||||
@@ -922,24 +942,48 @@ find_binary() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# Initialize global binary paths for daemon
|
||||
CURL_BIN=$(find_binary curl)
|
||||
if [[ -z "$CURL_BIN" ]]; then
|
||||
echo "ERROR: curl not found in system" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Initialize global binary paths for daemon (with retry to prevent crash loops)
|
||||
_dep_retry=0
|
||||
_dep_max=5
|
||||
while true; do
|
||||
CURL_BIN=$(find_binary curl)
|
||||
JQ_AVAILABLE=false
|
||||
command -v jq &> /dev/null && JQ_AVAILABLE=true
|
||||
|
||||
if [[ -n "$CURL_BIN" ]] && [[ "$JQ_AVAILABLE" == "true" ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
_dep_retry=$((_dep_retry + 1))
|
||||
_missing=""
|
||||
[[ -z "$CURL_BIN" ]] && _missing="curl"
|
||||
[[ "$JQ_AVAILABLE" != "true" ]] && _missing="${_missing:+$_missing }jq"
|
||||
|
||||
if [[ $_dep_retry -ge $_dep_max ]]; then
|
||||
echo "ERROR: Required tools not found after $_dep_max retries: $_missing" >&2
|
||||
echo "Install missing tools and restart the agent service." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
_wait=$((_dep_retry * 30))
|
||||
echo "WARN: Required tools missing ($_missing). Retry $_dep_retry/$_dep_max in ${_wait}s..." >&2
|
||||
sleep $_wait
|
||||
done
|
||||
|
||||
# Verify jq is available (critical dependency for JSON config parsing)
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "ERROR: jq not found. Install jq and restart the agent service." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Load configuration
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "ERROR: Configuration not found: $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Load configuration (with retry - config may be temporarily unavailable after upgrade)
|
||||
_cfg_retry=0
|
||||
_cfg_max=5
|
||||
while [[ ! -f "$CONFIG_FILE" ]]; do
|
||||
_cfg_retry=$((_cfg_retry + 1))
|
||||
if [[ $_cfg_retry -ge $_cfg_max ]]; then
|
||||
echo "ERROR: Configuration not found after $_cfg_max retries: $CONFIG_FILE" >&2
|
||||
echo "Re-install the agent to recreate the configuration." >&2
|
||||
exit 1
|
||||
fi
|
||||
_wait=$((_cfg_retry * 15))
|
||||
echo "WARN: Config file not found: $CONFIG_FILE. Retry $_cfg_retry/$_cfg_max in ${_wait}s..." >&2
|
||||
sleep $_wait
|
||||
done
|
||||
|
||||
MANAGEMENT_URL=$(jq -r '.management.url' "$CONFIG_FILE")
|
||||
AGENT_TOKEN=$(jq -r '.management.token' "$CONFIG_FILE")
|
||||
@@ -2089,8 +2133,7 @@ run_daemon() {
|
||||
if [[ -n "$SOCAT_BIN" ]]; then
|
||||
log "INFO" "Found socat: $SOCAT_BIN"
|
||||
else
|
||||
log "ERROR" "socat not found in system"
|
||||
exit 1
|
||||
log "WARN" "socat not found - HAProxy stats will be unavailable (agent will still function)"
|
||||
fi
|
||||
|
||||
if [[ -n "$JQ_BIN" ]]; then
|
||||
@@ -2300,6 +2343,10 @@ KillSignal=SIGTERM
|
||||
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
# Prevent rapid restart loops: max 5 restarts within 2 minutes
|
||||
# Allows multiple legitimate upgrades (exit 0 + restart) while catching crash loops
|
||||
StartLimitBurst=5
|
||||
StartLimitIntervalSec=120
|
||||
StandardOutput=append:$LOG_DIR/agent.log
|
||||
StandardError=append:$LOG_DIR/agent.log
|
||||
Environment=SKIP_TO_DAEMON=true
|
||||
|
||||
@@ -109,37 +109,58 @@ log "DEBUG" "Config file exists: $(test -f "/etc/haproxy-agent/config.json" && e
|
||||
if [[ "$1" == "daemon" && -f "/etc/haproxy-agent/config.json" ]] || [[ "$HAPROXY_AGENT_SKIP_SETUP" == "true" ]]; then
|
||||
log "INFO" "Starting agent daemon with existing configuration..."
|
||||
|
||||
# Verify jq is available (required for config parsing in daemon mode)
|
||||
if ! command -v jq &> /dev/null; then
|
||||
log "ERROR" "DAEMON: jq is required but not found. Cannot parse agent configuration."
|
||||
echo "FATAL: jq is not installed. Install jq (brew install jq) and restart the agent service." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Verify critical dependencies with retry (prevents rapid restart loops)
|
||||
# launchd KeepAlive=true would cause fast crash-loop if we exit immediately
|
||||
_dep_retry=0
|
||||
_dep_max=5
|
||||
while ! command -v jq &> /dev/null || ! command -v curl &> /dev/null; do
|
||||
_dep_retry=$((_dep_retry + 1))
|
||||
_missing=""
|
||||
command -v jq &> /dev/null || _missing="jq"
|
||||
command -v curl &> /dev/null || _missing="${_missing:+$_missing }curl"
|
||||
if [[ $_dep_retry -ge $_dep_max ]]; then
|
||||
log "ERROR" "DAEMON: Required tools not found after $_dep_max retries: $_missing"
|
||||
log "ERROR" "DAEMON: Install missing tools: brew install $_missing"
|
||||
exit 1
|
||||
fi
|
||||
_wait=$((_dep_retry * 30))
|
||||
log "WARN" "DAEMON: Required tools missing ($_missing). Retry $_dep_retry/$_dep_max in ${_wait}s..."
|
||||
sleep $_wait
|
||||
done
|
||||
|
||||
# Use provided config file or default
|
||||
CONFIG_FILE="${HAPROXY_AGENT_CONFIG_FILE:-/etc/haproxy-agent/config.json}"
|
||||
LOG_FILE="/var/log/haproxy-agent/agent.log"
|
||||
PID_FILE="/var/run/haproxy-agent.pid"
|
||||
|
||||
# Load configuration from file
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
MANAGEMENT_URL=$(jq -r '.management.url' "$CONFIG_FILE")
|
||||
AGENT_TOKEN=$(jq -r '.management.token' "$CONFIG_FILE")
|
||||
CLUSTER_ID=$(jq -r '.management.cluster_id' "$CONFIG_FILE")
|
||||
AGENT_NAME=$(jq -r '.agent.name' "$CONFIG_FILE")
|
||||
HOSTNAME=$(jq -r '.agent.hostname' "$CONFIG_FILE")
|
||||
|
||||
log "INFO" "AGENT UPGRADE: Configuration loaded - Agent: $AGENT_NAME, Cluster: $CLUSTER_ID"
|
||||
|
||||
# FIXED: Jump directly to daemon mode instead of setting SKIP_TO_DAEMON
|
||||
log "INFO" "AGENT UPGRADE: Going directly to daemon mode..."
|
||||
|
||||
# Jump to the daemon section at the end of the script
|
||||
SKIP_TO_DAEMON=true
|
||||
else
|
||||
log "ERROR" "Configuration file not found: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
# Load configuration from file (with retry - config may be temporarily unavailable)
|
||||
_cfg_retry=0
|
||||
_cfg_max=5
|
||||
while [[ ! -f "$CONFIG_FILE" ]]; do
|
||||
_cfg_retry=$((_cfg_retry + 1))
|
||||
if [[ $_cfg_retry -ge $_cfg_max ]]; then
|
||||
log "ERROR" "DAEMON: Configuration file not found after $_cfg_max retries: $CONFIG_FILE"
|
||||
log "ERROR" "DAEMON: Re-install the agent to recreate the configuration."
|
||||
exit 1
|
||||
fi
|
||||
_wait=$((_cfg_retry * 15))
|
||||
log "WARN" "DAEMON: Config not found: $CONFIG_FILE. Retry $_cfg_retry/$_cfg_max in ${_wait}s..."
|
||||
sleep $_wait
|
||||
done
|
||||
|
||||
MANAGEMENT_URL=$(jq -r '.management.url' "$CONFIG_FILE")
|
||||
AGENT_TOKEN=$(jq -r '.management.token' "$CONFIG_FILE")
|
||||
CLUSTER_ID=$(jq -r '.management.cluster_id' "$CONFIG_FILE")
|
||||
AGENT_NAME=$(jq -r '.agent.name' "$CONFIG_FILE")
|
||||
HOSTNAME=$(jq -r '.agent.hostname' "$CONFIG_FILE")
|
||||
|
||||
log "INFO" "AGENT UPGRADE: Configuration loaded - Agent: $AGENT_NAME, Cluster: $CLUSTER_ID"
|
||||
|
||||
# FIXED: Jump directly to daemon mode instead of setting SKIP_TO_DAEMON
|
||||
log "INFO" "AGENT UPGRADE: Going directly to daemon mode..."
|
||||
|
||||
# Jump to the daemon section at the end of the script
|
||||
SKIP_TO_DAEMON=true
|
||||
fi
|
||||
|
||||
# Configuration variables - MODIFY THESE IF NEEDED
|
||||
@@ -784,24 +805,48 @@ find_binary() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# Initialize global binary paths for daemon
|
||||
CURL_BIN=$(find_binary curl)
|
||||
if [[ -z "$CURL_BIN" ]]; then
|
||||
echo "ERROR: curl not found in system" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Initialize global binary paths for daemon (with retry to prevent crash loops)
|
||||
_dep_retry=0
|
||||
_dep_max=5
|
||||
while true; do
|
||||
CURL_BIN=$(find_binary curl)
|
||||
JQ_AVAILABLE=false
|
||||
command -v jq &> /dev/null && JQ_AVAILABLE=true
|
||||
|
||||
if [[ -n "$CURL_BIN" ]] && [[ "$JQ_AVAILABLE" == "true" ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
_dep_retry=$((_dep_retry + 1))
|
||||
_missing=""
|
||||
[[ -z "$CURL_BIN" ]] && _missing="curl"
|
||||
[[ "$JQ_AVAILABLE" != "true" ]] && _missing="${_missing:+$_missing }jq"
|
||||
|
||||
if [[ $_dep_retry -ge $_dep_max ]]; then
|
||||
echo "ERROR: Required tools not found after $_dep_max retries: $_missing" >&2
|
||||
echo "Install missing tools (brew install $_missing) and restart the agent service." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
_wait=$((_dep_retry * 30))
|
||||
echo "WARN: Required tools missing ($_missing). Retry $_dep_retry/$_dep_max in ${_wait}s..." >&2
|
||||
sleep $_wait
|
||||
done
|
||||
|
||||
# Verify jq is available (critical dependency for JSON config parsing)
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "ERROR: jq not found. Install jq (brew install jq) and restart the agent service." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Load configuration
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "ERROR: Configuration not found: $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Load configuration (with retry - config may be temporarily unavailable after upgrade)
|
||||
_cfg_retry=0
|
||||
_cfg_max=5
|
||||
while [[ ! -f "$CONFIG_FILE" ]]; do
|
||||
_cfg_retry=$((_cfg_retry + 1))
|
||||
if [[ $_cfg_retry -ge $_cfg_max ]]; then
|
||||
echo "ERROR: Configuration not found after $_cfg_max retries: $CONFIG_FILE" >&2
|
||||
echo "Re-install the agent to recreate the configuration." >&2
|
||||
exit 1
|
||||
fi
|
||||
_wait=$((_cfg_retry * 15))
|
||||
echo "WARN: Config file not found: $CONFIG_FILE. Retry $_cfg_retry/$_cfg_max in ${_wait}s..." >&2
|
||||
sleep $_wait
|
||||
done
|
||||
|
||||
MANAGEMENT_URL=$(jq -r '.management.url' "$CONFIG_FILE")
|
||||
AGENT_TOKEN=$(jq -r '.management.token' "$CONFIG_FILE")
|
||||
@@ -1981,8 +2026,7 @@ run_daemon() {
|
||||
if [[ -n "$SOCAT_BIN" ]]; then
|
||||
log "INFO" "Found socat: $SOCAT_BIN"
|
||||
else
|
||||
log "ERROR" "socat not found in system"
|
||||
exit 1
|
||||
log "WARN" "socat not found - HAProxy stats will be unavailable (agent will still function)"
|
||||
fi
|
||||
|
||||
if [[ -n "$JQ_BIN" ]]; then
|
||||
@@ -2181,6 +2225,8 @@ cat > "/Library/LaunchDaemons/com.haproxy.agent.plist" << PLIST_EOF
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>30</integer>
|
||||
<key>StandardOutPath</key>
|
||||
<string>$LOG_DIR/agent.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
|
||||
Reference in New Issue
Block a user