Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 96 additions & 2 deletions install-zabbix-agent-linux-tactical-rmm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,52 @@ send_discord() {
service_active() { systemctl is-active --quiet "$1" 2>/dev/null; }
package_installed() { dpkg -s "$1" &>/dev/null; }

# --- Defer service start during package installs -----------------------------
# The zabbix-agent2 postinst starts the service, whose ExecStartPre runs
# `zabbix_agent2 -T` (config test). Any loadable plugin that cannot initialise
# makes that test exit 1, which fails the postinst and leaves dpkg
# half-configured — the package is unpacked but never configured, and every
# later apt call complains until it is cleared.
#
# A temporary policy-rc.d telling dpkg not to start services during the install
# removes that failure mode entirely: we configure the agent and disable the
# plugins we do not need first, then start it ourselves at the end. It also lets
# a re-run recover a host already stuck in that half-configured state.
POLICY_RC_D="/usr/sbin/policy-rc.d"
POLICY_RC_D_SAVED=""
POLICY_RC_D_OURS=0

defer_service_start() {
if [[ -e "$POLICY_RC_D" ]]; then
POLICY_RC_D_SAVED="${POLICY_RC_D}.zabbix-install-$$"
mv "$POLICY_RC_D" "$POLICY_RC_D_SAVED" || { warn "Could not set aside existing policy-rc.d"; return 1; }
fi
if printf '#!/bin/sh\nexit 101\n' > "$POLICY_RC_D" && chmod 0755 "$POLICY_RC_D"; then
POLICY_RC_D_OURS=1
else
warn "Could not install policy-rc.d — the package may try to start the agent during install"
# Put back whatever was there before rather than leaving the host bare.
[[ -n "$POLICY_RC_D_SAVED" && -e "$POLICY_RC_D_SAVED" ]] && mv "$POLICY_RC_D_SAVED" "$POLICY_RC_D"
POLICY_RC_D_SAVED=""
fi
}

# Only ever removes the policy-rc.d we created — a pre-existing one belongs to
# the host and must survive, including when this runs from the EXIT trap after
# a path that never deferred anything.
restore_service_policy() {
[[ "$POLICY_RC_D_OURS" -eq 1 ]] || return 0
rm -f "$POLICY_RC_D"
POLICY_RC_D_OURS=0
if [[ -n "$POLICY_RC_D_SAVED" && -e "$POLICY_RC_D_SAVED" ]]; then
mv "$POLICY_RC_D_SAVED" "$POLICY_RC_D" || warn "Could not restore the original policy-rc.d"
POLICY_RC_D_SAVED=""
fi
}

# Never leave the host with service starts globally disabled, whatever happens.
trap restore_service_policy EXIT

# --- Gather system info ------------------------------------------------------
SYS_HOSTNAME=$(hostname -f)
IP_ADDRESS=$(hostname -I | awk '{print $1}')
Expand Down Expand Up @@ -132,9 +178,15 @@ ACTION=""

if package_installed zabbix-agent2; then
PREV_VERSION=$(dpkg -s zabbix-agent2 | grep '^Version:' | awk '{print $2}')
# Zabbix's Debian/Ubuntu packages carry an epoch, e.g. "1:7.4.13-1+debian13".
# Comparing that raw against "7.4" never matches, so the "already on target
# version" short-circuit below could never fire and every run — including
# every scheduled TacticalRMM run — re-entered the full install path.
# Strip the epoch before comparing.
PREV_VERSION_CMP="${PREV_VERSION#*:}"
log "Installed version: $PREV_VERSION"

if [[ "$PREV_VERSION" == ${ZABBIX_VERSION}* ]]; then
if [[ "$PREV_VERSION_CMP" == ${ZABBIX_VERSION}.* || "$PREV_VERSION_CMP" == "${ZABBIX_VERSION}" ]]; then
if [[ "$FORCE_RUN" == "force" ]]; then
log "Already on version $ZABBIX_VERSION — force flag set, reconfiguring."
ACTION="Reconfigured"
Expand Down Expand Up @@ -190,8 +242,25 @@ fi

apt-get update -qq

# Hold off service starts for the rest of the run; we start the agent ourselves
# once its config is written and the unneeded plugins are disabled.
defer_service_start

# Clear any half-configured state left by a previous failed attempt. With starts
# deferred this can now succeed where it previously failed on the config test.
if [[ -n "$(dpkg --audit 2>/dev/null)" ]]; then
warn "dpkg has packages in a broken state — reconfiguring before install..."
dpkg --configure -a >/dev/null 2>&1 || warn "dpkg --configure -a did not fully succeed"
fi

log "Installing zabbix-agent2..."
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq -o Dpkg::Options::="--force-confold" zabbix-agent2 zabbix-agent2-plugin-*
# Install the agent ONLY. Loadable plugin packages are installed further down,
# per detected service. Installing them all (the old `zabbix-agent2-plugin-*`
# wildcard) breaks the install on any host lacking the matching hardware or
# libraries: the NVIDIA plugin aborts the agent's own config test with
# "NVML Shared Library couldn't be found or loaded" on every machine without an
# NVIDIA driver, which is nearly all of them.
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq -o Dpkg::Options::="--force-confold" zabbix-agent2

fi # end skip-on-reconfigure

Expand Down Expand Up @@ -338,6 +407,15 @@ fi

if service_active postgresql; then
log " [FOUND] PostgreSQL"; DETECTED_SERVICES+=("PostgreSQL")
# Loadable plugin — previously pulled in by the plugin wildcard, now installed
# only where PostgreSQL is actually running.
if ! package_installed zabbix-agent2-plugin-postgresql; then
log " Installing zabbix-agent2-plugin-postgresql..."
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq -o Dpkg::Options::="--force-confold" zabbix-agent2-plugin-postgresql 2>/dev/null \
|| warn " Could not install zabbix-agent2-plugin-postgresql"
else
log " zabbix-agent2-plugin-postgresql already installed"
fi
cat > "${AGENT_CONF_D}/postgresql.conf" <<'EOF'
# PostgreSQL - Zabbix Agent 2 Plugin
# CREATE USER zabbix WITH PASSWORD 'StrongPassword!';
Expand Down Expand Up @@ -538,12 +616,28 @@ else
fi

# --- Enable & restart agent --------------------------------------------------
# Config is written and unneeded plugins are disabled, so it is safe to let
# services start again before we bring the agent up.
restore_service_policy

# Validate the config before starting, so a failure reports the agent's own
# message rather than a bare systemd exit code.
log "Validating configuration..."
if ! CONFIG_TEST=$(zabbix_agent2 -T -c "$AGENT_CONF" 2>&1); then
echo "$CONFIG_TEST" | sed 's/^/ /'
send_discord "❌ Zabbix Agent Config Invalid" \
"**Host:** \`$SYS_HOSTNAME\`\n**IP:** \`$IP_ADDRESS\`\n**Reason:** zabbix_agent2 -T failed — see script output" 15158332
echo "ERROR: zabbix_agent2 configuration test failed — not starting the agent"; exit 1
fi
log "Configuration valid"

log "Restarting zabbix-agent2..."
systemctl enable zabbix-agent2 --quiet
systemctl restart zabbix-agent2
sleep 2

if ! systemctl is-active --quiet zabbix-agent2; then
systemctl status zabbix-agent2 --no-pager 2>&1 | tail -15 | sed 's/^/ /'
send_discord "❌ Zabbix Agent Failed to Start" \
"**Host:** \`$SYS_HOSTNAME\`\n**IP:** \`$IP_ADDRESS\`\n**Version:** \`$NEW_VERSION\`\n**Proxy:** \`$ZABBIX_PROXY\`" 15158332
echo "ERROR: zabbix-agent2 failed to start"; exit 1
Expand Down