237 lines
9.9 KiB
Bash
237 lines
9.9 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export LC_ALL=C
|
|
|
|
################################################################################
|
|
# FAIT Zabbix - Enrolement d un noeud de supervision
|
|
#
|
|
# curl -fsSL https://.../bootstrap.sh | sudo FAIT_CLIENT=acme FAIT_TOKEN=xxx bash
|
|
#
|
|
# La meme ligne vaut pour toutes les machines d un meme client : rien n est a
|
|
# personnaliser par machine.
|
|
#
|
|
# FAIT_TOKEN sur la ligne de commande ci-dessus finit dans l argv de sudo,
|
|
# donc dans /proc/<pid>/cmdline, lisible par tout utilisateur local le temps
|
|
# que la commande tourne. Pour l eviter, ecrire le jeton dans un fichier et
|
|
# passer FAIT_TOKEN_FILE a la place :
|
|
#
|
|
# curl -fsSL https://.../bootstrap.sh | sudo FAIT_CLIENT=acme FAIT_TOKEN_FILE=/root/jeton bash
|
|
#
|
|
# FAIT_TOKEN reste accepte pour compatibilite, mais FAIT_TOKEN_FILE est la
|
|
# forme recommandee des que le jeton n est pas jetable.
|
|
#
|
|
# Le noeud ne clone plus le depot. Il recoit un paquet Debian signe, et se met
|
|
# a jour tout seul ensuite via fait-zabbix-update.timer.
|
|
################################################################################
|
|
|
|
FAIT_CLIENT="${FAIT_CLIENT:-}"
|
|
FAIT_TOKEN="${FAIT_TOKEN:-}"
|
|
FAIT_TOKEN_FILE="${FAIT_TOKEN_FILE:-}"
|
|
|
|
ZABBIX_MAJOR="${ZABBIX_MAJOR:-7.0}"
|
|
ZBX_SERVER="${ZBX_SERVER:-sup.fabout.it}"
|
|
ZBX_SERVER_ACTIVE="${ZBX_SERVER_ACTIVE:-${ZBX_SERVER}}"
|
|
ZBX_HOSTNAME="${ZBX_HOSTNAME:-}"
|
|
ZBX_BIND_IP="${ZBX_BIND_IP:-}"
|
|
|
|
REPO_HOST="${FAIT_REPO_HOST:-repo.fabout.it}"
|
|
AUTH_FILE=/etc/apt/auth.conf.d/fait-zabbix.conf
|
|
KEYRING=/etc/apt/keyrings/fait-supervision.asc
|
|
SOURCE_LIST=/etc/apt/sources.list.d/fait-zabbix.list
|
|
CLIENT_FILE=/etc/fait-zabbix/client
|
|
# Lu par update.sh : si FAIT_REPO_HOST a surcharge le defaut ici, l updater
|
|
# doit interroger le meme hote, sinon le fichier d authentification (dont la
|
|
# ligne "machine" vise REPO_HOST) ne correspond plus a l URL interrogee et
|
|
# les requetes partent sans identifiants.
|
|
REPO_HOST_FILE=/etc/fait-zabbix/repo-host
|
|
|
|
log() { echo "INFO: $*" >&2; }
|
|
warn() { echo "WARN: $*" >&2; }
|
|
err() { echo "ERROR: $*" >&2; exit 1; }
|
|
|
|
# Les parametres passent avant le controle de root : signaler une variable
|
|
# oubliee est plus utile que reclamer un sudo quand les deux manquent, et
|
|
# evite de relancer en root juste pour decouvrir la vraie erreur.
|
|
[[ -n "${FAIT_CLIENT}" ]] || err "FAIT_CLIENT est obligatoire : le nom du client, tel qu il figure dans le manifeste."
|
|
|
|
# FAIT_TOKEN_FILE evite que le jeton apparaisse dans /proc/<pid>/cmdline : on
|
|
# le lit ici, avant le controle d absence, pour que les deux formes soient
|
|
# equivalentes au reste du script.
|
|
if [[ -n "${FAIT_TOKEN_FILE}" ]]; then
|
|
[[ -r "${FAIT_TOKEN_FILE}" ]] || err "FAIT_TOKEN_FILE illisible : ${FAIT_TOKEN_FILE}"
|
|
FAIT_TOKEN="$(tr -d ' \t\r\n' < "${FAIT_TOKEN_FILE}")"
|
|
fi
|
|
[[ -n "${FAIT_TOKEN}" ]] || err "FAIT_TOKEN (ou FAIT_TOKEN_FILE) est obligatoire : le jeton de lecture du client."
|
|
[[ "${EUID}" -eq 0 ]] || err "A lancer en root (sudo)."
|
|
|
|
[[ -r /etc/os-release ]] || err "/etc/os-release introuvable, distribution non supportee."
|
|
# shellcheck disable=SC1091
|
|
. /etc/os-release
|
|
|
|
# --- prerequis ----------------------------------------------------------------
|
|
ensure_prereqs() {
|
|
local manquants=()
|
|
# shellcheck disable=SC2043
|
|
# La boucle ne porte qu un seul outil aujourd hui, mais reste une boucle :
|
|
# c est la ou un futur prerequis obligatoire s ajouterait, sans reprendre
|
|
# toute la fonction.
|
|
for c in curl; do command -v "$c" >/dev/null 2>&1 || manquants+=("$c"); done
|
|
if [[ ${#manquants[@]} -gt 0 ]] || ! dpkg -s ca-certificates >/dev/null 2>&1; then
|
|
log "Installation des prerequis : ${manquants[*]:-} ca-certificates"
|
|
apt-get update -y >/dev/null 2>&1 || true
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
|
${manquants[@]+"${manquants[@]}"} ca-certificates >/dev/null
|
|
fi
|
|
}
|
|
|
|
# --- depot Zabbix officiel ----------------------------------------------------
|
|
# zabbix-agent2 est une dependance stricte du paquet : sans ce depot, apt
|
|
# refuserait la transaction entiere.
|
|
ajouter_depot_zabbix() {
|
|
if command -v zabbix_agent2 >/dev/null 2>&1; then
|
|
log "zabbix-agent2 deja installe."
|
|
return 0
|
|
fi
|
|
local dist relver url tmp_deb
|
|
dist="${ID}"; [[ "${dist}" == "ubuntu" ]] || dist="debian"
|
|
# Zabbix nomme ses artefacts Ubuntu avec la version complete (ubuntu22.04),
|
|
# mais Debian avec le seul majeur (debian12) : un relver tronque a la
|
|
# majeure donnerait ubuntu22, qui n existe pas au depot Zabbix.
|
|
if [[ "${dist}" == "ubuntu" ]]; then
|
|
relver="${VERSION_ID}"
|
|
else
|
|
relver="${VERSION_ID%%.*}"
|
|
fi
|
|
url="https://repo.zabbix.com/zabbix/${ZABBIX_MAJOR}/${dist}/pool/main/z/zabbix-release/zabbix-release_latest+${dist}${relver}_all.deb"
|
|
log "Ajout du depot Zabbix ${ZABBIX_MAJOR} : ${url}"
|
|
# Pas de --remove-on-error : absent avant curl 7.83, donc de Debian 11 et
|
|
# Ubuntu 22.04. On telecharge dans un fichier temporaire et on ne l installe
|
|
# que si le telechargement a reussi ; un transfert tronque ne passe jamais
|
|
# a dpkg.
|
|
tmp_deb="$(mktemp --suffix=.deb)"
|
|
if ! curl -fsSL -o "${tmp_deb}" "${url}"; then
|
|
rm -f "${tmp_deb}"
|
|
err "Telechargement zabbix-release echoue."
|
|
fi
|
|
DEBIAN_FRONTEND=noninteractive dpkg -i "${tmp_deb}" >/dev/null
|
|
rm -f "${tmp_deb}"
|
|
}
|
|
|
|
# --- identifiants du client ---------------------------------------------------
|
|
poser_identifiants() {
|
|
install -d -m 0755 /etc/apt/auth.conf.d
|
|
# Cree en 0600 AVANT toute ecriture : l ecrire puis le restreindre laisserait
|
|
# le jeton lisible par tous dans l intervalle.
|
|
install -m 0600 /dev/null "${AUTH_FILE}"
|
|
cat > "${AUTH_FILE}" <<EOF
|
|
machine ${REPO_HOST}
|
|
login ${FAIT_CLIENT}
|
|
password ${FAIT_TOKEN}
|
|
EOF
|
|
install -d -m 0755 /etc/fait-zabbix
|
|
printf '%s\n' "${FAIT_CLIENT}" > "${CLIENT_FILE}"
|
|
chmod 0644 "${CLIENT_FILE}"
|
|
printf '%s\n' "${REPO_HOST}" > "${REPO_HOST_FILE}"
|
|
chmod 0644 "${REPO_HOST_FILE}"
|
|
log "Identifiants poses pour le client ${FAIT_CLIENT}."
|
|
}
|
|
|
|
# --- cle de signature ---------------------------------------------------------
|
|
# L organisation etant privee, la cle n est pas servie anonymement : elle est
|
|
# recuperee avec les identifiants poses juste avant, jamais passes en argument.
|
|
# Pas de --remove-on-error (absent avant curl 7.83, donc de Debian 11 et
|
|
# Ubuntu 22.04) : on telecharge vers un fichier temporaire et on ne le
|
|
# promeut en cle qu apres coup, ce qui protege pareillement contre un
|
|
# transfert tronque qui passerait quand meme le controle head -1 ci-dessous.
|
|
poser_cle() {
|
|
install -d -m 0755 /etc/apt/keyrings
|
|
local tmp_cle
|
|
tmp_cle="$(mktemp)"
|
|
if ! curl -fsSL --netrc-file "${AUTH_FILE}" \
|
|
"https://${REPO_HOST}/repository.key" -o "${tmp_cle}"; then
|
|
rm -f "${tmp_cle}"
|
|
err "Recuperation de la cle de signature echouee."
|
|
fi
|
|
head -1 "${tmp_cle}" | grep -q "BEGIN PGP PUBLIC KEY BLOCK" || {
|
|
rm -f "${tmp_cle}"
|
|
err "Le fichier recupere n est pas une cle PGP."
|
|
}
|
|
install -m 0644 "${tmp_cle}" "${KEYRING}"
|
|
rm -f "${tmp_cle}"
|
|
log "Cle de signature en place."
|
|
}
|
|
|
|
# arch=all : le registre Debian de Gitea annonce "Architectures: all" et ne
|
|
# publiera jamais d index binary-amd64. Sans cette mention, apt en reclame un a
|
|
# chaque update et affiche un Notice sur la machine du client - inoffensif, mais
|
|
# c est le genre de ligne rouge qui fait ouvrir un ticket. A ne retirer que si
|
|
# le paquet cesse un jour d etre Architecture: all.
|
|
poser_source() {
|
|
cat > "${SOURCE_LIST}" <<EOF
|
|
deb [arch=all signed-by=${KEYRING}] https://${REPO_HOST} stable main
|
|
EOF
|
|
chmod 0644 "${SOURCE_LIST}"
|
|
}
|
|
|
|
# --- conf agent ---------------------------------------------------------------
|
|
render_conf() {
|
|
local dest=/etc/zabbix/zabbix_agent2.conf host bind
|
|
host="${ZBX_HOSTNAME:-$(hostname -f 2>/dev/null || hostname)}"
|
|
bind="${ZBX_BIND_IP:-$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')}"
|
|
[[ -n "${bind}" ]] || bind="0.0.0.0"
|
|
|
|
[[ -f "${dest}" ]] || { warn "Conf agent absente, elle sera celle du paquet zabbix-agent2."; return 0; }
|
|
|
|
local bak; bak="${dest}.bak.$(date +%Y%m%d-%H%M%S 2>/dev/null || echo "$$")"
|
|
# shellcheck disable=SC2015
|
|
# log() est un echo qui ne rend jamais un code non nul : le A && B || C
|
|
# ci-dessous ne peut pas executer C apres un A reussi.
|
|
cp -a "${dest}" "${bak}" \
|
|
&& log "Conf existante sauvegardee : ${bak}" \
|
|
|| warn "Echec de la sauvegarde de ${dest} ; la conf va etre reecrite sans copie."
|
|
|
|
sed -i -e "s|^SourceIP=.*|SourceIP=${bind}|" \
|
|
-e "s|^ListenIP=.*|ListenIP=${bind}|" \
|
|
-e "s|^Hostname=.*|Hostname=${host}|" \
|
|
-e "s|^Server=.*|Server=${ZBX_SERVER}|" \
|
|
-e "s|^ServerActive=.*|ServerActive=${ZBX_SERVER_ACTIVE}|" \
|
|
"${dest}"
|
|
log "Conf agent ajustee (Hostname=${host}, Server=${ZBX_SERVER})."
|
|
}
|
|
|
|
ensure_docker_access() {
|
|
command -v docker >/dev/null 2>&1 || return 0
|
|
getent group docker >/dev/null 2>&1 || return 0
|
|
id zabbix >/dev/null 2>&1 || return 0
|
|
id -nG zabbix 2>/dev/null | tr ' ' '\n' | grep -qx docker && return 0
|
|
log "Ajout de l utilisateur zabbix au groupe docker."
|
|
usermod -aG docker zabbix || warn "Echec de l ajout au groupe docker."
|
|
}
|
|
|
|
main() {
|
|
ensure_prereqs
|
|
ajouter_depot_zabbix
|
|
poser_identifiants
|
|
poser_cle
|
|
poser_source
|
|
log "Installation de fait-zabbix..."
|
|
apt-get update -y >/dev/null
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y fait-zabbix
|
|
render_conf
|
|
ensure_docker_access
|
|
systemctl restart zabbix-agent2 || true
|
|
|
|
# Le paquet vient d etre installe a la version la plus recente du depot ;
|
|
# le manifeste peut en vouloir une autre, ou geler ce client. On converge
|
|
# donc une fois, tout de suite, plutot que d attendre le premier reveil du
|
|
# timer et de laisser tourner une version non assignee pendant dix minutes.
|
|
if [[ -x /usr/lib/fait-zabbix/update.sh ]]; then
|
|
/usr/lib/fait-zabbix/update.sh || warn "La convergence initiale a echoue ; le timer reessaiera."
|
|
fi
|
|
|
|
log "Enrolement termine. La machine converge desormais seule vers le manifeste."
|
|
}
|
|
|
|
main "$@"
|
|
# marqueur temporaire de test du miroir - doit disparaitre au prochain run
|