Files
fait-zabbix-public/bootstrap.sh
T
hcloez e0c40eaada Ajoute bootstrap.sh, miroir du depot prive
Ce depot est public pour que la ligne d enrolement tienne en une commande.
Le script ne contient aucun secret: le jeton client arrive par
l environnement.
2026-08-25 14:26:36 +02:00

231 lines
9.4 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."
}
poser_source() {
cat > "${SOURCE_LIST}" <<EOF
deb [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 "$@"