#!/usr/bin/env bash # install-zabbix-agent2-ubuntu.sh — one-shot Zabbix agent2 (ACTIVE checks) # installer for Ubuntu. Public-safe: nothing about any specific environment # is baked in; the server address comes from the required --server flag. # # Idempotent — safe to re-run. What it does: # 1. Installs Zabbix agent2 from the official Zabbix apt repo # 2. Writes an ACTIVE-check config (agent connects OUT to the server; # no inbound ports, no firewall changes needed — works behind NAT): # ServerActive= # Server=127.0.0.1, # Hostname=<--hostname, else the short system hostname> # 3. Enables + restarts the service and sanity-checks the connection. # # Usage: # sudo ./install-zabbix-agent2-ubuntu.sh --server 203.0.113.10 # sudo ./install-zabbix-agent2-ubuntu.sh --server 203.0.113.10:10051 --hostname web01 # sudo ./install-zabbix-agent2-ubuntu.sh web01 --server 203.0.113.10 # positional hostname # sudo ./install-zabbix-agent2-ubuntu.sh --server 203.0.113.10 --docker # + docker monitoring # # One-liner (no local copy needed): # curl -fsSL /install-zabbix-agent2-ubuntu.sh | sudo bash -s -- --server 203.0.113.10 # # After it succeeds, the host must exist on the Zabbix server with the EXACT # same technical name (an active-agent template, e.g. "Linux by Zabbix agent # active"). The script warns if the agent cannot fetch its config yet — that # warning clears by itself within ~a minute of the host being created. set -euo pipefail ZABBIX_MAJOR="7.4" CONF="/etc/zabbix/zabbix_agent2.conf" LOG="/var/log/zabbix/zabbix_agent2.log" say() { echo "[zabbix-agent2] $*"; } die() { echo "[zabbix-agent2] ERROR: $*" >&2; exit 1; } usage() { cat < SA_HOST / SA_PORT (plain IPv4 or DNS name) case "$SERVER_RAW" in *:*) SA_HOST="${SERVER_RAW%%:*}"; SA_PORT="${SERVER_RAW##*:}" ;; *) SA_HOST="$SERVER_RAW"; SA_PORT="10051" ;; esac [[ "$SA_PORT" =~ ^[0-9]+$ ]] || die "bad port in --server '${SERVER_RAW}' (IPv6 literals: give a DNS name instead)" [ -n "$SA_HOST" ] && [ "$SA_HOST" != "$SA_PORT" ] || die "bad --server '${SERVER_RAW}'" SA_ADDR="$SA_HOST"; [ "$SA_PORT" = "10051" ] || SA_ADDR="${SA_HOST}:${SA_PORT}" ZBX_HOSTNAME="${HOSTNAME_ARG:-$(hostname -s)}" case "$ZBX_HOSTNAME" in *[!A-Za-z0-9._-]*) die "hostname '${ZBX_HOSTNAME}' has odd chars — pass --hostname " ;; esac [ "$(id -u)" -eq 0 ] || die "run as root (sudo)" # ---- platform ---------------------------------------------------------------- . /etc/os-release [ "${ID:-}" = "ubuntu" ] || die "Ubuntu only (found '${ID:-unknown}'); for Debian, swap 'ubuntu' for 'debian' in the repo URL below" say "Ubuntu ${VERSION_ID} (${PRETTY_NAME:-ubuntu}) → server ${SA_ADDR}, hostname '${ZBX_HOSTNAME}'" # ---- 1. repo + package --------------------------------------------------------- export DEBIAN_FRONTEND=noninteractive APT_OPTS="-y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold" apt-get update -qq apt-get install -qq $APT_OPTS ca-certificates curl >/dev/null DEB_URL="https://repo.zabbix.com/zabbix/${ZABBIX_MAJOR}/release/ubuntu/pool/main/z/zabbix-release/zabbix-release_latest_${ZABBIX_MAJOR}+ubuntu${VERSION_ID}_all.deb" curl -fsSI -o /dev/null "$DEB_URL" || die "no zabbix-release for Ubuntu ${VERSION_ID} (check --major); looked at ${DEB_URL}" curl -fsS -o /tmp/zabbix-release.deb "$DEB_URL" dpkg -i /tmp/zabbix-release.deb || apt-get -f install -qq $APT_OPTS rm -f /tmp/zabbix-release.deb apt-get update -qq apt-get install -qq $APT_OPTS zabbix-agent2 say "installed: $(zabbix_agent2 -V | head -1)" # ---- 2. config (replace-or-append: one authoritative line per key) ------------- if [ -f "$CONF" ] && [ ! -f "${CONF}.pre-install.bak" ]; then cp -a "$CONF" "${CONF}.pre-install.bak" say "backed up stock config to ${CONF}.pre-install.bak" fi set_conf() { # file key value — drop every existing (commented) line, append ours sed -i -E "/^[[:space:]]*#?[[:space:]]*${2}=/d" "$1" echo "${2}=${3}" >> "$1" } [ -f "$CONF" ] || die "expected ${CONF} to exist after install" set_conf "$CONF" ServerActive "$SA_ADDR" set_conf "$CONF" Server "127.0.0.1,${SA_HOST}" set_conf "$CONF" Hostname "$ZBX_HOSTNAME" # ---- 2b. optional: docker monitoring (agent2 docker plugin) ------------------- DOCKER_STATUS="not requested" if [ "$DOCKER" = "1" ]; then # The plugin talks to the docker UNIX socket; the agent runs as 'zabbix'. # Socket access = membership in the 'docker' group (docker's own group). getent group docker >/dev/null || die "--docker: no 'docker' group — Docker is not installed; install Docker first, then re-run" usermod -aG docker zabbix DOCKER_STATUS="enabled" say "docker monitoring: 'zabbix' user added to 'docker' group" if [ -S /var/run/docker.sock ]; then if runuser -u zabbix -- test -r /var/run/docker.sock; then say "docker monitoring: socket readable by 'zabbix'" else say "WARNING: zabbix still cannot read /var/run/docker.sock — check its group/permissions (should be root:docker 0660)" fi else say "WARNING: /var/run/docker.sock does not exist — is the docker daemon running? Items will fail until it is" fi # Local end-to-end test of the plugin as the agent user (after the service # restart below, the agent picks up the new group and can do the same). PING="$(runuser -u zabbix -- zabbix_agent2 -t docker.ping 2>/dev/null || true)" case "$PING" in *1*) say "docker monitoring: docker.ping OK as 'zabbix'" ;; *) say "note: docker.ping did not answer locally yet (daemon down? access?) — see warning above" ;; esac fi # ---- 3. service + sanity ------------------------------------------------------- systemctl enable zabbix-agent2 >/dev/null systemctl restart zabbix-agent2 sleep 3 systemctl is-active --quiet zabbix-agent2 || { journalctl -u zabbix-agent2 -n 20 --no-pager; die "service not active"; } zabbix_agent2 -t agent.version >/dev/null 2>&1 || die "agent fails to parse config / answer local test" # Active-check reachability: give the agent ~20s to fetch its first config. loggrep() { # pattern — agent log file, else the journal if [ -r "$LOG" ]; then grep -Eqi "$1" "$LOG"; else journalctl -u zabbix-agent2 --since '-2 min' | grep -Eqi "$1"; fi } WARN="" for _ in $(seq 1 10); do if loggrep 'started to fail|cannot obtain|no active checks|host not found'; then WARN=1; break; fi sleep 2 done echo say "done. config:" grep -E '^(Server|ServerActive|Hostname)=' "$CONF" | sed 's/^/ /' say "docker monitoring: ${DOCKER_STATUS}" if [ -n "$WARN" ]; then say "WARNING: agent cannot fetch its config yet:" if [ -r "$LOG" ]; then tail -n 4 "$LOG" | sed 's/^/ /' else journalctl -u zabbix-agent2 -n 4 --no-pager | sed 's/^/ /'; fi say "expected if the host doesn't exist on the Zabbix server yet — create it" say "with the EXACT technical name '${ZBX_HOSTNAME}' and an active-agent" say "template; the warning clears by itself within a minute." else say "active checks OK (no connection errors in the agent log)." fi