Files
netbird-telegraf-installer/install.sh
T
mm ea5b796012 fix(installer): envsubst-Redirection wiederhergestellt — DAS war der x14-Haenger
Der Trace auf x14 endete exakt hier:
  + envsubst ${NODE_NAME} ... ${APACHE_ERROR_FILES}
  root@x14:/home/mm#

Ohne "< \$2.tmp" liest envsubst STDIN. Bei sh install.sh vom Terminal ist stdin
die Konsole -> der Installer wartet auf Eingabe, nach der letzten Meldung
"Schreibe Konfiguration ...". Genau das Bild: Stillstand, ^C, kein Fehler.
Bei curl | sh waere es schlimmer: envsubst wuerde den Rest des Skripts fressen.

Ein frueherer Patch von mir hat die Redirection verschluckt; alle Laeufe mit
vorhandener telegraf.conf (x22) liefen trotzdem, weil dort nur der Fallback
subst_vars() aktiv war. Erst der Erstinstall (x14) traf envsubst.

Verifiziert: vorher Haenger bei envsubst, jetzt vollstaendiger Durchlauf mit
CONFIG_VERSION-Marker und allen Confs.
2026-09-14 10:42:21 +02:00

1229 lines
56 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env sh
# ============================================================================
# SELBSTSCHUTZ BEI "curl | sh"
#
# Problem (real passiert, x14): Bei `curl … | sh` liest die Shell das Skript aus
# STDIN. Jeder Unterbefehl, der stdin genauso liest, frisst den Rest des
# Skripts — die Shell setzt dann mitten im Quelltext fort und bricht ab.
# Ausloeser ist apt/dpkg: auf Ubuntu laeuft dabei `needrestart`, und das
# meldet sich mit "Scanning processes... / Scanning linux images...".
# Symptom in der Praxis: Der Installer gibt seinen EIGENEN Quelltext aus
# ("; then", "fi", Kommentarzeilen) und bleibt mitten in der Installation stehen.
# Auf Nodes mit bereits installiertem Telegraf tritt das NICHT auf (kein apt).
#
# Loesung: Laeuft das Skript ueber eine Pipe, kopiert es sich EINMAL auf die
# Platte und startet sich von dort neu. Ab da ist stdin frei und apt kann
# nichts mehr verschlucken. Idempotent: der Neustart setzt die Marker-Variable.
# ============================================================================
# Nur bei einer ECHTEN PIPE (curl … | sh) neu starten. Bei `sh install.sh`
# oder `sh install.sh < eingabe` ist stdin eine Datei — dort kann kein
# Unterbefehl das Skript wegfressen, und der Re-Exec waere nur ein
# unnoetiger Download.
if [ -z "${NETBIRD_TG_REEXEC:-}" ] && [ ! -t 0 ] && [ -p /dev/stdin ]; then
# Das Skript wird NICHT per `cat` von stdin kopiert: die Shell liest stdin
# blockweise und hat den Anfang bereits gepuffert — `cat` bekaeme nur den
# Rest und die Kopie waere unvollstaendig. Stattdessen frisch von der Quelle
# laden (dieselbe URL, die der Aufrufer benutzt hat).
_src="${REPO_RAW:-https://gitea.m-m.bio/mm/netbird-telegraf-installer/raw/branch/main}"
_self="$(mktemp /tmp/netbird-telegraf-install.XXXXXX 2>/dev/null || echo /tmp/netbird-tg.$$)"
# WICHTIG: auf einem FRISCHEN System gibt es oft kein curl — genau dort, wo
# der Schutz am noetigsten ist (dort laeuft apt und damit needrestart).
# Deshalb BEIDE Wege versuchen und, wenn keiner geht, das Skript weiter
# ausfuehren wie bisher (dann bleibt das Risiko, aber es laeuft).
_dl=0
if command -v curl >/dev/null 2>&1; then
curl -fsSL -m 30 --connect-timeout 10 "$_src/install.sh" -o "$_self" 2>/dev/null && [ -s "$_self" ] && _dl=1
fi
if [ "$_dl" -eq 0 ] && command -v wget >/dev/null 2>&1; then
timeout 30 wget -q -O "$_self" "$_src/install.sh" 2>/dev/null && [ -s "$_self" ] && _dl=1
fi
if [ "$_dl" -eq 0 ] && command -v busybox >/dev/null 2>&1; then
busybox wget -q -O "$_self" "$_src/install.sh" 2>/dev/null && [ -s "$_self" ] && _dl=1
fi
# Letzter Weg: liegt das Skript schon lokal (z. B. per scp), von dort kopieren.
if [ "$_dl" -eq 0 ] && [ -n "${NETBIRD_TG_SOURCE:-}" ] && [ -s "${NETBIRD_TG_SOURCE:-}" ]; then
cp "${NETBIRD_TG_SOURCE}" "$_self" 2>/dev/null && _dl=1
fi
if [ "$_dl" -eq 1 ]; then
chmod 0700 "$_self"
printf '[netbird-telegraf] Selbstschutz: Skript laeuft jetzt von %s (stdin frei).\n' "$_self"
# stdin ausdruecklich auf /dev/null: der Prozess erbt sonst die Pipe, und
# apt/needrestart koennte weiter daraus lesen.
NETBIRD_TG_REEXEC=1 REPO_RAW="$_src" sh "$_self" "$@" < /dev/null
_rc=$?
rm -f "$_self"
exit "$_rc"
fi
printf '[netbird-telegraf] HINWEIS: kein curl/wget fuer den Selbstschutz — weiter ohne.\n'
rm -f "$_self"
fi
# ============================================================================
# NetBird Observability — Telegraf Node Installer (Linux)
#
# Installiert Telegraf als systemd-Dienst und verbindet die Node fest mit dem
# zentralen Collector auf dem imac (Prometheus Remote Write + Loki) im NetBird.
#
# Unterstützt: Debian/Ubuntu/Proxmox VE (apt), RHEL/Rocky/Alma/Fedora (dnf/yum),
# Alpine (apk, OpenRC), sonstige Linux via statischem Binary.
# Architekturen: amd64 / x86_64, arm64 / aarch64, armv7.
# Windows: install.ps1 verwenden.
#
# Nutzung (als root):
# curl -fsSL https://gitea.m-m.bio/mm/netbird-telegraf-installer/raw/branch/main/install.sh | sh -s -- --name <node-name>
# Optionen:
# --name <n> Node-Name (Default: hostname -s)
# --token <t> Bearer-Token des Reverse-Proxys (Pflicht)
# --collector <ip> Direktweg im NetBird statt Reverse Proxy
# --role <r> Zusatz-Profil: proxmox | apache | docker | none (Default: auto)
# --pve-token <t> Proxmox API-Token 'user@pve!name=secret' (nur --role proxmox)
# --pve-node <n> Name des PVE-Knotens im PVE-Baum (Default: per pvesh erkannt)
# --pve-url <u> PVE-API-URL (Default: https://127.0.0.1:8006/api2/json)
# --apache-log <p> Apache-Access-Log (Default: /var/log/apache2/access.log)
# --uninstall Telegraf + Konfiguration entfernen
# --check Nur Versionsvergleich, nichts aendern (Exit 0 = aktuell)
# --version Installierte und verfuegbare Version ausgeben
# --force-update Confs/Scripts IMMER neu schreiben + Dienst neu starten
# --update-only Nur Confs/Scripts aktualisieren (kein Paket, kein Restart
# ohne Aenderung) — wird vom Auto-Updater-Timer genutzt
# --install-update-timer [daily|hourly|weekly|<N>min]
# systemd-Timer fuer den Auto-Updater einrichten
# --remove-update-timer Timer wieder entfernen
# ============================================================================
set -eu
# Ausgang ueber den NetBird-Reverse-Proxy: funktioniert auch fuer Hosts, die
# KEIN NetBird-Peer sind (Büro-LAN, Kundenlaptop, CGNAT). Token erforderlich.
PROM_URL_DEFAULT="https://imac-prometheus.x05.idx.wf/api/v1/write"
LOKI_URL_DEFAULT="https://imac-loki.x05.idx.wf"
COLLECTOR="${COLLECTOR:-}" # nur setzen fuer den Direktweg im NetBird
TELEGRAF_TOKEN="${TELEGRAF_TOKEN:-}"
NODE_NAME="${NODE_NAME:-$(hostname -s 2>/dev/null || hostname)}"
ROLE="auto"
# Wurden --name/--role auf der Kommandozeile gesetzt? Nur dann darf ein
# Update-Lauf die in /etc/default/telegraf hinterlegten Werte ueberschreiben.
NAME_GIVEN=0
ROLE_GIVEN=0
PVE_TOKEN="${PVE_TOKEN:-}"
PVE_NODE="${PVE_NODE:-}"
PVE_URL="${PVE_URL:-}"
# Apache-Logpfad: Distributionen unterscheiden sich — Debian/Ubuntu/KeyHelp
# nutzen /var/log/apache2/, RHEL/Rocky /var/log/httpd/. Der Installer sucht beim
# ersten Lauf; --apache-log ueberschreibt.
APACHE_LOG=""
TELEGRAF_VERSION="${TELEGRAF_VERSION:-1.40.0}"
# Ueberschreibbar (Tests/Mirror): REPO_RAW=... sh install.sh
REPO_RAW="${REPO_RAW:-https://gitea.m-m.bio/mm/netbird-telegraf-installer/raw/branch/main}"
UNINSTALL=0
CHECK=0
SHOW_VERSION=0
DIAGNOSE=0
FORCE_UPDATE=0
UPDATE_ONLY=0
TIMER_INTERVAL=""
REMOVE_TIMER=0
while [ $# -gt 0 ]; do
case "$1" in
--name) NODE_NAME="$2"; NAME_GIVEN=1; shift 2 ;;
--collector) COLLECTOR="${2:-}"; shift 2 ;;
--token) TELEGRAF_TOKEN="$2"; shift 2 ;;
--role) ROLE="$2"; ROLE_GIVEN=1; shift 2 ;;
--pve-token) PVE_TOKEN="$2"; shift 2 ;;
--pve-node) PVE_NODE="$2"; shift 2 ;;
--pve-url) PVE_URL="$2"; shift 2 ;;
--apache-log) APACHE_LOG="$2"; shift 2 ;;
--uninstall) UNINSTALL=1; shift ;;
--check) CHECK=1; shift ;;
--version) SHOW_VERSION=1; shift ;;
--diagnose) DIAGNOSE=1; shift ;;
--force-update) FORCE_UPDATE=1; shift ;;
--update-only) UPDATE_ONLY=1; shift ;;
--install-update-timer)
TIMER_INTERVAL="${2:-daily}"
case "$TIMER_INTERVAL" in
daily|hourly|weekly|[0-9]*min|[0-9]*) shift 2 ;;
*) TIMER_INTERVAL="daily"; shift ;;
esac ;;
--remove-update-timer) REMOVE_TIMER=1; shift ;;
-h|--help) sed -n '2,24p' "$0"; exit 0 ;;
*) echo "Unbekannte Option: $1" >&2; exit 2 ;;
esac
done
log() { printf '\033[1;34m[netbird-telegraf]\033[0m %s\n' "$*"; }
# --------------------------------------------------------------------------
# Bestehende Installation: Werte aus /etc/default/telegraf nachladen
#
# WARUM: Der Auto-Updater liest Token, NODE_NAME und ROLE aus dieser Datei und
# gibt sie dem Unterlauf mit. Ein MANUELLER `--update-only`-Lauf tat das nicht
# und brach an der Token-Pruefung ab ("Kein Token"), obwohl der Token auf der
# Maschine liegt. Die Datei ist die Quelle der Wahrheit fuer eine bereits
# eingerichtete Node — die Kommandozeile ueberschreibt sie nur, wenn sie
# ausdruecklich etwas anderes sagt.
#
# Zweiter, stiller Fehler derselben Ursache: ohne --name fiel NODE_NAME auf den
# Hostnamen zurueck. Auf einem KeyHelp-Host heisst der Host aber nicht wie die
# Node ('x22' vs 'x22.idx.wf-keyhelp') — ein Update-Lauf haette die Node unter
# falschem Label neu registriert. Ebenso haette ein PVE-Knoten ohne --role seine
# Rolle auf 'auto' verloren (kein pvesh-Drop-in mehr).
# --------------------------------------------------------------------------
_rd_default() {
sed -n "s/^$1=//p" /etc/default/telegraf 2>/dev/null | head -1 \
| sed "s/^['\"]//; s/['\"]$//"
}
if [ -r /etc/default/telegraf ]; then
if [ -z "$TELEGRAF_TOKEN" ]; then
TELEGRAF_TOKEN="$(_rd_default TELEGRAF_TOKEN)"
[ -n "$TELEGRAF_TOKEN" ] && log "Token aus /etc/default/telegraf uebernommen."
fi
[ -z "$COLLECTOR" ] && COLLECTOR="$(_rd_default COLLECTOR)"
if [ "$NAME_GIVEN" -eq 0 ]; then
_n="$(_rd_default NODE_NAME)"
if [ -n "$_n" ] && [ "$_n" != "$NODE_NAME" ]; then
log "Node-Name aus /etc/default/telegraf uebernommen: $_n"
NODE_NAME="$_n"
fi
fi
if [ "$ROLE_GIVEN" -eq 0 ]; then
_r="$(_rd_default ROLE)"
if [ -n "$_r" ] && [ "$_r" != "none" ]; then
log "Rolle aus /etc/default/telegraf uebernommen: $_r"
ROLE="$_r"
fi
fi
[ -z "$PVE_NODE" ] && PVE_NODE="$(_rd_default PVE_NODE)"
[ -z "$PVE_URL" ] && PVE_URL="$(_rd_default PVE_URL)"
# APACHE_LOG kommt als gequotete Liste — die Quotes entfernt _rd_default.
fi
# /etc/default/telegraf robust einlesen. Ein direktes "." bricht die ganze
# Installation ab, wenn die Datei einen unquoted Wert mit Leerzeichen oder
# Sonderzeichen enthaelt (alte APACHE_LOG-Zeile mit zwei Pfaden:
# "sh: /etc/default/telegraf: /pfad/zwei.log: Permission denied" — genau das
# passierte auf x22 und blockierte jedes weitere Update).
# Deshalb: nur syntaktisch heile Dateien auswerten, sonst sichern und warnen.
load_defaults() {
[ -r /etc/default/telegraf ] || return 0
if sh -n /etc/default/telegraf >/dev/null 2>&1; then
set -a; . /etc/default/telegraf; set +a
return 0
fi
printf '\033[1;33m[netbird-telegraf] WARNUNG:\033[0m /etc/default/telegraf ist nicht auswertbar — wird neu geschrieben.\n'
_bad="/etc/default/telegraf.unlesbar.$(date +%s)"
cp -a /etc/default/telegraf "$_bad" 2>/dev/null || true
printf '\033[1;33m[netbird-telegraf]\033[0m Sicherung: %s\n' "$_bad"
return 0
}
die() { printf '\033[1;31m[netbird-telegraf] FEHLER:\033[0m %s\n' "$*" >&2; exit 1; }
[ "$(id -u)" -eq 0 ] || die "Bitte als root ausführen (sudo)."
# --------------------------------------------------------------------------
# --diagnose: prueft in EINEM Aufruf, warum ein Input nichts liefert.
# Prueft genau die Stellen, die schon still versagt haben:
# Rechte des Dienst-Benutzers, leere Dateien, Offset-Zustand, Parser-Fehler.
# --------------------------------------------------------------------------
if [ "$DIAGNOSE" -eq 1 ]; then
printf '=== Telegraf-Installer Diagnose ===\n'
TG="$(command -v telegraf || echo /usr/local/bin/telegraf)"
printf '\n-- Dienst --\n'
if command -v systemctl >/dev/null 2>&1; then
printf 'Status: %s\n' "$(systemctl is-active telegraf 2>/dev/null || echo unbekannt)"
printf 'Benutzer: %s\n' "$(systemctl show telegraf -p User --value 2>/dev/null || echo unbekannt)"
printf 'Gruppen: %s\n' "$(systemctl show telegraf -p SupplementaryGroups --value 2>/dev/null || echo -)"
fi
DBU="$(systemctl show telegraf -p User --value 2>/dev/null || echo telegraf)"
[ -n "$DBU" ] || DBU=telegraf
printf '\n-- Logpfade aus der aktiven Conf --\n'
CF="/etc/telegraf/telegraf.d/apache.conf"
[ -f "$CF" ] || CF="/etc/telegraf/telegraf.conf"
# Beide files-Zeilen (journal + apache_access) lesen, aber entdoppeln —
# sonst erscheint jeder Pfad zweimal in der Ausgabe.
_files="$(sed -n 's/^[[:space:]]*files = \[\(.*\)\].*/\1/p' "$CF" | tr -d '"' | tr ',' '\n' | sed 's/^ *//; s/ *$//' | grep -v '^$' | sort -u | tr '\n' ' ')"
[ -n "$_files" ] || { printf ' (keine files-Liste gefunden)\n'; }
_n=0; _ok=0; _empty=0; _noread=0
for _f in $_files; do
_n=$((_n+1))
if [ ! -e "$_f" ]; then printf ' FEHLT %s\n' "$_f"; continue; fi
_size="$(stat -c %s "$_f" 2>/dev/null || echo 0)"
if [ "$_size" -eq 0 ]; then printf ' LEER (0 B) %s\n' "$_f"; _empty=$((_empty+1)); continue; fi
# Lesbarkeit aus Sicht des Dienst-Benutzers — das war die stille Fehlerquelle.
if [ "$(id -u)" -eq 0 ] && command -v su >/dev/null 2>&1 && id "$DBU" >/dev/null 2>&1; then
# Dateiname fuer die Shell quoten (Pfade koennen Sonderzeichen haben)
_q="$(printf '%s' "$_f" | sed "s/'/'\\\\''/g")"
if su -s /bin/sh "$DBU" -c "head -c 1 '$_q' >/dev/null 2>&1"; then
printf ' OK %s (%s Bytes)\n' "$_f" "$_size"; _ok=$((_ok+1))
else
printf ' NICHT LESBAR %s (Benutzer %s) ← URSACHE\n' "$_f" "$DBU"; _noread=$((_noread+1))
fi
else
printf ' vorhanden %s (%s Bytes)\n' "$_f" "$_size"
fi
done
printf ' -> %s Pfade, %s lesbar, %s leer, %s nicht lesbar\n' "$_n" "$_ok" "$_empty" "$_noread"
printf '\n-- Parser-Test (ohne Offset-Zwang) --\n'
_out="$("$TG" --config /etc/telegraf/telegraf.conf --config-directory /etc/telegraf/telegraf.d --test --test-wait 2 2>&1)"
printf '%s\n' "$_out" | grep -c 'E!' | sed 's/^/ Fehlerzeilen: /'
printf '%s\n' "$_out" | grep -E 'E!' | head -3 | sed 's/^/ /'
printf '%s\n' "$_out" | grep -cE '^[>] ' | sed 's/^/ erzeugte Messungen: /'
printf '\n-- Offset-Zustand (erste Messung je Datei erst nach neuem Zugriff) --\n'
ls -la /var/lib/telegraf/ 2>/dev/null | tail -5 || printf ' (kein State-Verzeichnis)\n'
printf '\nHinweis: initial_read_offset = "saved-or-end" bedeutet, dass beim ERSTEN\n'
printf 'Lauf nur NEUES gelesen wird. Bestehende Zeilen kommen erst mit neuen\n'
printf 'Zugriffen. Fuer einen sofortigen Test: eine Datei anfordern und warten.\n'
exit 0
fi
# Reine Abfragen brauchen keinen Token und kein root (Timer-Verwaltung schon).
if [ "$SHOW_VERSION" -eq 1 ] || [ "$CHECK" -eq 1 ]; then
LOCAL_V="$(cat /etc/telegraf/CONFIG_VERSION 2>/dev/null || echo 'nicht installiert')"
REMOTE_V="$(curl -fsS -m 15 "$REPO_RAW/VERSION" 2>/dev/null | tr -d ' \r\n')"
[ -n "$REMOTE_V" ] || REMOTE_V="nicht erreichbar"
printf 'installiert: %s\nverfuegbar: %s\n' "$LOCAL_V" "$REMOTE_V"
[ "$CHECK" -eq 1 ] || exit 0
if [ "$LOCAL_V" = "$REMOTE_V" ] && [ -n "$LOCAL_V" ] && [ "$LOCAL_V" != "nicht installiert" ]; then
echo "Status: aktuell"; exit 0
fi
echo "Status: Update verfuegbar"; exit 10
fi
[ -n "$TELEGRAF_TOKEN" ] || die "Kein Token: --token <Bearer-Token> angeben (siehe README)."
if [ -n "${COLLECTOR:-}" ]; then
PROM_URL="http://$COLLECTOR:9090/api/v1/write"; LOKI_URL="http://$COLLECTOR:3100"
log "Direktweg im NetBird: Collector $COLLECTOR"
else
PROM_URL="$PROM_URL_DEFAULT"; LOKI_URL="$LOKI_URL_DEFAULT"
fi
# --------------------------------------------------------------------------
# Uninstall
# --------------------------------------------------------------------------
if [ "$UNINSTALL" -eq 1 ]; then
log "Entferne Telegraf …"
systemctl disable --now telegraf 2>/dev/null || rc-service telegraf stop 2>/dev/null || true
if command -v apt-get >/dev/null; then apt-get remove -y telegraf >/dev/null 2>&1 || true
elif command -v dnf >/dev/null; then dnf remove -y telegraf >/dev/null 2>&1 || true
elif command -v yum >/dev/null; then yum remove -y telegraf >/dev/null 2>&1 || true
elif command -v apk >/dev/null; then apk del telegraf >/dev/null 2>&1 || true
fi
rm -rf /etc/telegraf /usr/local/bin/telegraf /etc/systemd/system/telegraf.service
systemctl daemon-reload 2>/dev/null || true
log "Fertig."
exit 0
fi
# --------------------------------------------------------------------------
# Erkennung: OS, Arch, Rolle
# --------------------------------------------------------------------------
OS_ID="unknown"; [ -r /etc/os-release ] && . /etc/os-release && OS_ID="${ID:-unknown}"
ARCH_RAW="$(uname -m)"
case "$ARCH_RAW" in
x86_64|amd64) ARCH="amd64" ;;
aarch64|arm64) ARCH="arm64" ;;
armv7l|armhf) ARCH="armhf" ;;
*) die "Architektur $ARCH_RAW wird nicht unterstützt." ;;
esac
IS_PVE=0; command -v pveversion >/dev/null 2>&1 && IS_PVE=1
HAS_DOCKER=0; [ -S /var/run/docker.sock ] && HAS_DOCKER=1
GPU="none"
if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then
GPU="nvidia"
elif command -v rocm-smi >/dev/null 2>&1; then
GPU="amd"
elif [ -e /dev/dri/renderD128 ]; then
GPU="intel"
fi
HAS_APACHE=0; { command -v apache2ctl >/dev/null 2>&1 || command -v httpd >/dev/null 2>&1; } && HAS_APACHE=1
if [ "$HAS_APACHE" -eq 1 ] && [ -z "$APACHE_LOG" ]; then
# ---------------------------------------------------------------------------
# Apache-Logpfade ermitteln — MUSTER, nicht expandierte Einzelpfade.
#
# Warum Muster: KeyHelp/Plesk legen pro Domain eigene Logs an, und es kommen
# laufend neue dazu. Eine Conf mit 43 festen Pfaden verliert jede neue Domain,
# bis der Installer erneut laeuft. Ein Glob deckt sie sofort mit ab.
#
# Verifizierte Strukturen (auf x22 real vorgefunden):
# KeyHelp : /home/users/<user>/logs/<domain>/access.log (zweistufig!)
# /var/log/apache2/<vhost>/access.log (z.B. keyhelp/)
# Plesk : /var/www/vhosts/<domain>/logs/access_log
# cPanel : /usr/local/apache/domlogs/<domain>
# klassisch: /var/log/apache2/access.log, /var/log/httpd/access_log
# ---------------------------------------------------------------------------
_pats=""
for _pat in \
"/var/log/apache2/*/access.log" \
"/home/users/*/logs/*/access.log" \
"/home/*/logs/*/access.log" \
"/var/www/vhosts/*/logs/access_log" \
"/usr/local/apache/domlogs/*" \
"/var/log/httpd/*access*.log" \
"/var/log/apache2/*/error.log" \
"/home/users/*/logs/*/error.log" \
"/home/*/logs/*/error.log" \
"/var/log/apache2/error.log" \
"/var/log/httpd/*error*.log"; do
# shellcheck disable=SC2086
if ls $_pat >/dev/null 2>&1; then
_pats="$_pats $_pat"
fi
done
# Klassische Einzeldateien nur, wenn sie NICHT leer sind: auf KeyHelp ist
# /var/log/apache2/access.log vorhanden, aber 0 Bytes — ein Input darauf
# liefert still nichts.
for _p in /var/log/apache2/access.log /var/log/httpd/access_log \
/var/log/apache2/other_vhosts_access.log \
/var/log/apache2/error.log /var/log/httpd/error_log; do
[ -s "$_p" ] && _pats="$_pats $_p"
done
# Nichts gefunden? Dann die CustomLog-Direktive befragen (verlaesslichste
# Quelle bei exotischen Setups) — ${APACHE_LOG_DIR} wird aufgeloest.
if [ -z "$_pats" ]; then
_cl_dir="$(grep -rhoE '^[[:space:]]*CustomLog[[:space:]]+(\S+|"[^"]+")' \
/etc/apache2/ /etc/httpd/ /etc/apache2/sites-enabled/ 2>/dev/null \
| sed -E 's/^[[:space:]]*CustomLog[[:space:]]+//; s/^"//; s/"$//' \
| grep -v '^|' | grep -v '^/dev/' | sort -u)"
for _p in $_cl_dir; do
_p="$(printf '%s' "$_p" | sed "s#\${APACHE_LOG_DIR}#/var/log/apache2#g")"
[ -r "$_p" ] && _pats="$_pats $_p"
done
fi
# Letzter Ausweg: breiter Scan der ueblichen Wurzelverzeichnisse.
if [ -z "$_pats" ]; then
_pats="$(find /var/log/apache2 /var/log/httpd /home -maxdepth 4 \
\( -name 'access.log' -o -name 'access_log' \
-o -name 'error.log' -o -name 'error_log' \) 2>/dev/null \
| head -200 | tr '\n' ' ')"
fi
[ -n "$_pats" ] && APACHE_LOG="$(printf '%s' "$_pats" | sed 's/^ *//; s/ *$//')"
[ -n "$APACHE_LOG" ] || APACHE_LOG="/var/log/apache2/access.log"
fi
# APACHE_LOG kann mehrere, leerzeichengetrennte Pfade enthalten -> als
# TOML-Liste rendern. Achtung: Pfade mit Leerzeichen sind in Apache-Logs
# ungewoehnlich; wir quoten jeden Eintrag einzeln.
# APACHE_FILES IMMER berechnen, NICHT nur bei erkanntem Apache.
# Realer Fehler: bei ROLE=apache ohne Apache-Erkennung (z. B. httpd auf RHEL,
# oder Apache wurde entfernt) blieb die Variable leer. Die Conf rendert dann zu
# "files = " — ungueltiges TOML, Telegraf startet nicht mehr, ALLE Metriken aus.
# Der Guard war die Ursache; die Berechnung selbst ist harmlos.
APACHE_FILES=""
if [ "$HAS_APACHE" -eq 1 ] || [ "$ROLE" = "apache" ]; then
# set -f: Pfadnamen-Expansion abschalten! Sonst ersetzt die Shell die
# Glob-Muster durch die aktuell vorhandenen Dateien — die Conf enthielte
# feste Einzelpfade und jede NEUE Domain fiele heraus, bis der Installer
# erneut laeuft. Wir wollen die Muster in der Conf, Telegraf loest sie bei
# jedem Lauf selbst auf.
# TRENNUNG access/error — wichtig fuer die Metriken:
# Der Grok-Parser versteht nur das ACCESS-Format
# IP ident auth [HTTPDATE] "VERB REQ" CODE BYTES
# Eine Error-Zeile sieht anders aus:
# [Mon Sep 14 00:10:01.123456 2026] [core:error] [pid 9] AH00124: ...
# Laeuft sie durch den Access-Parser, entstehen keine Metriken (Muster passt
# nicht) — schlimmer: der Aggregator zaehlt sie in Gruppen mit, die es nicht
# gibt. Deshalb bekommt der Access-Input nur Access-Pfade, und die Error-Logs
# gehen ueber einen eigenen Input roh nach Loki.
set -f
for _p in $APACHE_LOG; do
case "$_p" in
*error*|*Error*)
APACHE_ERROR_FILES="${APACHE_ERROR_FILES:+$APACHE_ERROR_FILES, }\"$_p\"" ;;
*)
APACHE_FILES="${APACHE_FILES:+$APACHE_FILES, }\"$_p\"" ;;
esac
done
set +f
[ -n "$APACHE_FILES" ] || APACHE_FILES='"/var/log/apache2/access.log"'
APACHE_ERROR_FILES="[${APACHE_ERROR_FILES:-\"/var/log/apache2/error.log\"}]"
export APACHE_ERROR_FILES
APACHE_FILES="[$APACHE_FILES]"
fi
# Sicherheitsnetz: die Variable MUSS nicht-leer sein, bevor apache.conf gerendert
# wird. Ein leerer Wert erzeugt ungueltiges TOML und kostet die ganze Node.
[ -n "${APACHE_FILES:-}" ] || APACHE_FILES='["/var/log/apache2/access.log"]'
export APACHE_FILES
if [ "$ROLE" = "auto" ]; then
ROLE="none"
[ "$IS_PVE" -eq 1 ] && ROLE="proxmox"
fi
log "Node=$NODE_NAME OS=$OS_ID Arch=$ARCH Rolle=$ROLE Docker=$HAS_DOCKER Apache=$HAS_APACHE GPU=$GPU Collector=$COLLECTOR"
# --------------------------------------------------------------------------
# Erreichbarkeit des Collectors (NetBird Lazy Connection: erst anklopfen)
# --------------------------------------------------------------------------
if command -v curl >/dev/null; then
HOST_OF_URL="$(printf '%s' "$PROM_URL" | sed -E 's#^https?://([^/:]+).*#\1#')"
ping -c1 -W2 "$HOST_OF_URL" >/dev/null 2>&1 || true # NetBird Lazy Connection wecken
if curl -fsS -m 10 -H "Authorization: Bearer $TELEGRAF_TOKEN" "${PROM_URL%/api/v1/write}/-/ready" >/dev/null 2>&1; then
log "Collector erreichbar (auth ok): $PROM_URL"
else
log "WARNUNG: $PROM_URL nicht erreichbar oder Token falsch — Installation läuft weiter, Telegraf puffert und retried."
fi
fi
# --------------------------------------------------------------------------
# Update-Only: nur Konfiguration und Hilfsscripts aktualisieren.
# Kein Paket installieren, /etc/default/telegraf unangetastet lassen (Token!),
# Dienst nur neu starten, wenn sich wirklich etwas geaendert hat.
# --------------------------------------------------------------------------
SNAPSHOT_BEFORE=""
if [ "$UPDATE_ONLY" -eq 1 ]; then
SNAPSHOT_BEFORE="$(mktemp)"
cat /etc/telegraf/telegraf.d/*.conf /etc/telegraf/telegraf.conf 2>/dev/null > "$SNAPSHOT_BEFORE" || true
for s in journal-tail syslog-tail node-info proxmox-ve gpu-rocm; do
[ -f "/etc/telegraf/$s.sh" ] && cat "/etc/telegraf/$s.sh" >> "$SNAPSHOT_BEFORE" 2>/dev/null
done
fi
# --------------------------------------------------------------------------
# Installation
# --------------------------------------------------------------------------
install_apt() {
# ZWEITE SCHUTZSCHICHT gegen den stdin-Fraß:
# `needrestart` (Ubuntu-Standard) liest stdin und meldet
# "Scanning processes... / Scanning linux images...". Mit NEEDRESTART_MODE=a
# laeuft es nicht-interaktiv, und die apt-Optionen halten dpkg davon ab,
# Prompts zu stellen. Der Re-Exec oben schuetzt die Shell — diese Zeile
# schuetzt zusaetzlich den Installer-Ablauf selbst.
export NEEDRESTART_MODE=a
export DEBIAN_FRONTEND=noninteractive
APT_OPTS="-y -qq -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold"
command -v gpg >/dev/null || { apt-get update -qq; apt-get install $APT_OPTS gnupg >/dev/null; }
command -v curl >/dev/null || apt-get install $APT_OPTS curl >/dev/null
mkdir -p /etc/apt/keyrings
curl -fsSL -m 30 --connect-timeout 10 https://repos.influxdata.com/influxdata-archive.key | gpg --dearmor --yes -o /etc/apt/keyrings/influxdata-archive.gpg
echo "deb [signed-by=/etc/apt/keyrings/influxdata-archive.gpg] https://repos.influxdata.com/debian stable main" > /etc/apt/sources.list.d/influxdata.list
apt-get update -qq
apt-get install $APT_OPTS telegraf >/dev/null
}
install_rpm() {
cat > /etc/yum.repos.d/influxdata.repo <<'EOF'
[influxdata]
name = InfluxData Repository - Stable
baseurl = https://repos.influxdata.com/stable/$basearch/main
enabled = 1
gpgcheck = 1
gpgkey = https://repos.influxdata.com/influxdata-archive.key
EOF
# dnf/yum lesen ebenfalls stdin (Prompts, Plugin-Rueckfragen) -> hier mit
# -y und ohne Assumeyes-Rueckfrage, damit nichts aus der Pipe greift.
if command -v dnf >/dev/null; then
dnf install -y -q telegraf < /dev/null
else
yum install -y -q telegraf < /dev/null
fi
}
install_static() {
command -v curl >/dev/null || die "curl fehlt."
TB="telegraf-${TELEGRAF_VERSION}_linux_${ARCH}.tar.gz"
log "Statisches Binary $TB …"
TMP="$(mktemp -d)"
curl -fsSL -m 120 --connect-timeout 10 "https://dl.influxdata.com/telegraf/releases/$TB" -o "$TMP/t.tgz"
tar -xzf "$TMP/t.tgz" -C "$TMP"
install -m 0755 "$TMP"/telegraf-*/usr/bin/telegraf /usr/local/bin/telegraf
rm -rf "$TMP"
id telegraf >/dev/null 2>&1 || useradd -r -s /usr/sbin/nologin -d /etc/telegraf telegraf 2>/dev/null || adduser -S -D -H -s /sbin/nologin telegraf
if command -v rc-update >/dev/null; then
cat > /etc/init.d/telegraf <<'EOF'
#!/sbin/openrc-run
description="Telegraf"
command=/usr/local/bin/telegraf
command_args="--config /etc/telegraf/telegraf.conf --config-directory /etc/telegraf/telegraf.d"
command_user=telegraf
command_background=true
pidfile=/run/telegraf.pid
start_pre() {
# Nur auswerten, wenn die Datei syntaktisch heil ist — sonst startet der
# Dienst gar nicht (unquoted Werte mit Leerzeichen brechen das Sourcing).
if [ -r /etc/default/telegraf ] && sh -n /etc/default/telegraf 2>/dev/null; then
set -a; . /etc/default/telegraf; set +a
fi
}
depend() { need net; }
EOF
chmod +x /etc/init.d/telegraf
return
fi
mkdir -p /etc/systemd/system
cat > /etc/systemd/system/telegraf.service <<'EOF'
[Unit]
Description=Telegraf
After=network-online.target
Wants=network-online.target
[Service]
User=telegraf
EnvironmentFile=-/etc/default/telegraf
ExecStart=/usr/local/bin/telegraf --config /etc/telegraf/telegraf.conf --config-directory /etc/telegraf/telegraf.d
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
EOF
}
if command -v telegraf >/dev/null 2>&1 || [ -x /usr/local/bin/telegraf ]; then
log "Telegraf bereits installiert ($(telegraf --version 2>/dev/null || /usr/local/bin/telegraf --version))."
[ "$UPDATE_ONLY" -eq 1 ] && log "Update-Only: Paketinstallation uebersprungen."
else
case "$OS_ID" in
debian|ubuntu|raspbian|linuxmint|pop) install_apt ;;
rhel|centos|rocky|almalinux|fedora|ol) install_rpm ;;
alpine) apk add --no-cache -q curl ca-certificates < /dev/null; install_static ;;
*) if command -v apt-get >/dev/null; then install_apt
elif command -v dnf >/dev/null || command -v yum >/dev/null; then install_rpm
else install_static; fi ;;
esac
fi
# --------------------------------------------------------------------------
# Log-Leserechte fuer den Dienst-Benutzer
# --------------------------------------------------------------------------
# Zwei verschiedene Rechte-Welten bei Apache-Logs:
# /var/log/apache2/... root:adm 0640 -> usermod -aG adm genuegt
# /home/users/<u>/logs/... <u>:<u> 0640 -> adm hilft NICHT
# KeyHelp/Plesk legen die VHost-Logs in den User-Homes ab (auf x22: 64 von 84
# Pfaden waren fuer 'telegraf' unlesbar). inputs.tail meldet das NICHT — es
# liefert einfach nichts, und der Node sieht "aktiv" aus.
# Loesung: ein ACL fuer den Dienst-Benutzer, rekursiv und vererbbar. Damit
# bleibt der Dienst im eingeschraenkten Konto (kein root noetig) und neue
# Domain-Ordner erben das Recht automatisch.
grant_apache_log_access() {
[ "$HAS_APACHE" -eq 1 ] || return 0
# Fortschritt melden: ohne diese Zeile sah der Lauf wie ein Haenger aus,
# weil zwischen "Telegraf bereits installiert" und der naechsten Meldung
# ein apt-Aufruf mit Netzwerkzugriff liegt (kann bis zu 3 Minuten dauern).
log "Log-Leserechte: pruefe/ergaenze ACL fuer 'telegraf' (kann etwas dauern) …"
_u="telegraf"
id "$_u" >/dev/null 2>&1 || return 0
if ! command -v setfacl >/dev/null 2>&1; then
# acl-Paket nachziehen, sonst ist die Rechtevergabe nicht moeglich.
# Alle Paketmanager mit </dev/null: sie duerfen nichts aus stdin lesen.
# `timeout` ist PFLICHT: ohne Paketlisten kann apt in eine Rueckfrage laufen
# und unbegrenzt warten — der Installer haengt dann nach der letzten
# Erfolgsmeldung, ohne Fehler. Real passiert (x14): Abbruch per ^C.
if command -v apt-get >/dev/null; then
apt-get update -qq </dev/null >/dev/null 2>&1 || true
timeout 180 env NEEDRESTART_MODE=a DEBIAN_FRONTEND=noninteractive \
apt-get install -y -qq acl </dev/null >/dev/null 2>&1 || true
elif command -v dnf >/dev/null; then
timeout 180 dnf install -y -q acl </dev/null >/dev/null 2>&1 || true
elif command -v yum >/dev/null; then
timeout 180 yum install -y -q acl </dev/null >/dev/null 2>&1 || true
elif command -v apk >/dev/null; then
timeout 180 apk add --no-cache -q acl </dev/null >/dev/null 2>&1 || true
fi
fi
if ! command -v setfacl >/dev/null 2>&1; then
log "HINWEIS: setfacl fehlt — Apache-Logs in User-Homes bleiben evtl. unlesbar."
log " Pruefen mit: sh install.sh --diagnose"
return 0
fi
# 1) Verzeichnisse aus der aktiven Conf ermitteln.
#
# REIHENFOLGE-BUG (real): diese Funktion liest
# /etc/telegraf/telegraf.d/apache.conf
# die erst SPAETER im Ablauf geschrieben wird. Beim ERSTEN Install ist sie
# nicht da -> _roots bleibt leer -> der Fallback unten greift auf die groben
# Panel-Wurzeln (/home/users, /var/www/vhosts) zu, also auf zehntausende
# Dateien inklusive Mails und Webinhalten. Beim zweiten Lauf (x22) existierte
# die Conf und lieferte die praezisen Domain-Ordner (83 Wu­rzeln).
# Deshalb: fehlt die Conf, wird sie hier aus den erkannten Pfaden erzeugt,
# BEVOR die ACL gesetzt wird.
# QUELLE DER PFADE, richtig herum:
# 1. die aktive apache.conf (Folge-Laeufe — dort stehen bereits die Muster)
# 2. sonst APACHE_LOG (in DIESEM Lauf erkannt; beim Erstinstall ist die Conf
# noch nicht geschrieben, und genau dann griff vorher der grobe Fallback)
_pfadquelle=""
if [ -f /etc/telegraf/telegraf.d/apache.conf ]; then
_pfadquelle="$(sed -n 's/^[[:space:]]*files = \[\(.*\)\].*/\1/p' \
/etc/telegraf/telegraf.d/apache.conf 2>/dev/null | tr -d '"' | tr ',' ' ')"
fi
[ -n "$_pfadquelle" ] || _pfadquelle="${APACHE_LOG:-}"
_roots=""
for _f in $_pfadquelle; do
case "$_f" in
*/\**) _f="${_f%%/\**}" ;; # /a/b/** -> /a/b
*/*) _f="${_f%/*}" ;; # /a/b/*/x -> /a/b/*
esac
case "$_f" in
*/\*|*/\*/*) _f="${_f%%\**}" ;; # Glob-Rest abschneiden
esac
_f="${_f%/}"
[ -d "$_f" ] && _roots="$_roots $_f"
done
# Panel-typische Wurzeln direkt mitnehmen (auch wenn die Conf sie globt)
for _d in /home/users /home/*/logs /var/www/vhosts /usr/local/apache/domlogs; do
[ -d "$_d" ] && _roots="$_roots $_d"
done
[ -n "$_roots" ] || return 0
_n=0
# Timeout pro Wurzel: setfacl laeuft rekursiv. Auf einem Panel-Host kann eine
# Wurzel sehr gross sein; ohne Grenze sieht ein langsamer Lauf wie ein Haenger
# aus. 120 s pro Wurzel sind grosszuegig und begrenzen die Gesamtdauer.
for _r in $_roots; do
# Nur die LOG-Verzeichnisse, nicht den ganzen User-Baum: /home/users
# enthaelt auch Mails, Webinhalte und Backups — ein rekursives Recht darauf
# waere wirkungslos fuer die Logs, aber teuer und semantisch falsch.
_targets=""
case "$_r" in
/home/*|/var/www*)
# Untergeordnete logs/-Verzeichnisse einsammeln (eine Ebene tief genug)
_targets="$(find "$_r" -maxdepth 3 -type d -name 'logs' 2>/dev/null | head -500 | tr '\n' ' ')"
[ -n "$_targets" ] || _targets="$_r" ;;
*) _targets="$_r" ;;
esac
for _t in $_targets; do
[ -d "$_t" ] || continue
# DURCHGANGSRECHT auf allen ELTERNVERZEICHNISSEN bis zur Wurzel.
# Realer Fehler: /home/users/<user> ist oft chmod 700. Ein setfacl -R auf
# .../<user>/logs setzt nichts auf <user> selbst — telegraf kommt dann
# nicht bis zur Datei, obwohl die Meldung "ACL gesetzt" erscheint.
_p="$(dirname "$_t")"
while [ -n "$_p" ] && [ "$_p" != "/" ] && [ "$_p" != "." ]; do
timeout 30 setfacl -m "u:$_u:x" "$_p" 2>/dev/null || true
# Default-ACL auf Zwischenverzeichnissen, damit neue Ordner erben
case "$_p" in
/home|/home/users|/var|/var/www|/usr|/usr/local) ;;
*) timeout 30 setfacl -d -m "u:$_u:x" "$_p" 2>/dev/null || true ;;
esac
_p="$(dirname "$_p")"
done
# Leserecht (r) auf die Dateien, Durchgang (x) auf die Ordner:
# rX vergibt x nur fuer Verzeichnisse.
timeout 120 setfacl -R -m "u:$_u:rX" "$_t" 2>/dev/null && _n=$((_n+1))
# Neu angelegte Dateien/Ordner erben das Recht (default-ACL nur fuer dirs).
timeout 120 setfacl -R -d -m "u:$_u:rX" "$_t" 2>/dev/null || true
done
done
[ "$_n" -gt 0 ] && log "Log-Leserechte gesetzt (ACL fuer '$_u') auf $_n Wurzel(n)."
# 2) Gegentest: JEDEN Logpfad als Dienst-Benutzer pruefen — nicht nur den ersten.
#
# Realer Fehler: der Test brach beim ersten Treffer ab. Auf einem KeyHelp-Host
# war das /var/log/apache2/keyhelp/access.log (root:adm, immer lesbar) — die
# Meldung lautete "Gegentest OK", waehrend ALLE Domain-Logs in den User-Homes
# unlesbar blieben. Ein Test, der beim ersten Erfolg abbricht, verdeckt genau
# die Pfade, fuer die er gedacht ist.
_tested=0; _fail=0; _firstfail=""
for _f in $_pfadquelle; do
# shellcheck disable=SC2086
for _cand in $(ls $_f 2>/dev/null); do
[ -s "$_cand" ] || continue
_tested=$((_tested+1))
if ! su -s /bin/sh "$_u" -c "head -c 1 \"$_cand\" >/dev/null 2>&1"; then
_fail=$((_fail+1))
[ -n "$_firstfail" ] || _firstfail="$_cand"
fi
done
done
if [ "$_tested" -eq 0 ]; then
log "HINWEIS: keine gefuellte Logdatei gefunden — Gegentest nicht moeglich."
elif [ "$_fail" -eq 0 ]; then
log "Gegentest: '$_u' kann alle $_tested Logdateien lesen."
else
log "WARNUNG: $_fail von $_tested Logdateien sind fuer '$_u' NICHT lesbar."
log " Beispiel: $_firstfail"
log " Pruefen mit: sh install.sh --diagnose"
fi
}
# --------------------------------------------------------------------------
# Konfiguration
# --------------------------------------------------------------------------
# Fortschritt melden: ab hier folgen mehrere Netzwerk-Downloads (base.conf,
# profil-conf, Hilfsscripts). Ohne diese Zeile sieht ein langsamer Download
# wie ein Haenger nach der letzten Erfolgsmeldung aus.
log "Schreibe Konfiguration (laedt Vorlagen vom Repo) …"
# Proxmox-Parameter VOR dem Schreiben der Konfiguration ermitteln
if [ "$ROLE" = "proxmox" ]; then
[ -n "$PVE_NODE" ] || PVE_NODE="$(pvesh get /nodes --output-format json 2>/dev/null | sed -n 's/.*"node":"\([^"]*\)".*/\1/p' | head -1)"
[ -n "$PVE_NODE" ] || PVE_NODE="$NODE_NAME"
[ -n "$PVE_URL" ] || PVE_URL="https://127.0.0.1:8006/api2/json"
log "Proxmox: node_name=$PVE_NODE base_url=$PVE_URL"
fi
mkdir -p /etc/telegraf/telegraf.d /etc/default
umask 077
cat > /etc/default/telegraf <<EOF
NODE_NAME=$NODE_NAME
# Die InfluxData-Unit nutzt \$TELEGRAF_OPTS in ExecStart. Ohne diesen Wert
# warnt systemd bei jedem Start "Referenced but unset environment variable".
# Explizit leer setzen = Warnung weg, Verhalten unveraendert.
TELEGRAF_OPTS=
# ROLE/COLLECTOR/PVE_* braucht der Auto-Updater, um das Profil zu erhalten
# (sonst verliert ein PVE-Knoten beim Update seine Rolle).
ROLE=$ROLE
COLLECTOR=${COLLECTOR:-}
PROM_URL=$PROM_URL
LOKI_URL=$LOKI_URL
TELEGRAF_TOKEN=$TELEGRAF_TOKEN
PVE_TOKEN=$PVE_TOKEN
PVE_NODE=$PVE_NODE
PVE_URL=$PVE_URL
# WICHTIG: Diese Datei wird per ". /etc/default/telegraf" GESOURCT. Werte ohne
# Anfuehrungszeichen brechen bei Leerzeichen oder Sonderzeichen: bei mehreren
# Logpfaden versucht die Shell, den zweiten Pfad als Befehl auszufuehren
# ("...access.log: Permission denied"). Eckige Klammern (TOML-Liste) sind im
# Sourcing sogar Syntaxmuell. Deshalb alles quoten — die Werte werden in der
# Config nur als String gebraucht, nicht als Liste.
APACHE_LOG='$APACHE_LOG'
EOF
umask 022
# Jeder Download MIT Timeout: ohne -m wartet curl unbegrenzt, wenn das Netz
# haengt (NetBird-Tunnel, DNS). Der Installer bliebe dann nach der letzten
# Erfolgsmeldung stehen — ohne Fehler, ohne Ende. Genau das passierte auf x14.
fetch() {
_tries=3
while [ "$_tries" -gt 0 ]; do
if curl -fsSL -m 30 --connect-timeout 10 "$REPO_RAW/$1" -o "$2" 2>/dev/null; then
return 0
fi
_tries=$((_tries - 1))
[ "$_tries" -gt 0 ] && sleep 2
done
die "Download $1 fehlgeschlagen nach 3 Versuchen ($REPO_RAW/$1)."
}
# Ersatz fuer envsubst, falls gettext fehlt (Minimal-Container, schlanke
# Alpine-/Rocky-Installationen). Ersetzt nur die uebergebenen Variablennamen —
# gleiche Semantik wie envsubst mit expliziter Liste.
subst_vars() { # $1 = Quelldatei, $2 = Zieldatei, rest = Variablennamen
_src="$1"; _dst="$2"; shift 2
cp "$_src" "$_dst"
for _v in "$@"; do
eval "_val=\${$_v-}"
# Wert mit | als Trenner, Sonderzeichen via awk ersetzen (kein sed-Gebastel)
awk -v name="$_v" -v val="$_val" '{
gsub("\$\{" name "\}", val); print
}' "$_dst" > "$_dst.tmp" && mv "$_dst.tmp" "$_dst"
done
}
# Holt eine Conf und ersetzt ${...}-Platzhalter aus /etc/default/telegraf.
fetch_conf() {
fetch "$1" "$2.tmp"
load_defaults
if command -v envsubst >/dev/null; then
# PFLICHT: Redirection. Ohne "< $2.tmp" liest envsubst STDIN.
#
# Realer Ausfall (x14): hier fehlte die Redirection. `envsubst '${…}'` ohne
# Eingabeumleitung wartet auf stdin — bei `sh install.sh` vom Terminal ist
# das die Konsole, der Installer blieb also NACH "Schreibe Konfiguration …"
# stehen und wartete auf Eingabe. Der Trace endete exakt hier.
# Bei `curl … | sh` waere es noch schlimmer: envsubst wuerde den REST des
# Skripts als Eingabe verbrauchen.
envsubst '${NODE_NAME} ${PROM_URL} ${LOKI_URL} ${TELEGRAF_TOKEN} ${PVE_TOKEN} ${PVE_NODE} ${PVE_URL} ${APACHE_LOG} ${APACHE_FILES} ${APACHE_ERROR_FILES}' < "$2.tmp" > "$2"
else
subst_vars "$2.tmp" "$2" NODE_NAME PROM_URL LOKI_URL TELEGRAF_TOKEN PVE_TOKEN PVE_NODE PVE_URL APACHE_LOG APACHE_FILES APACHE_ERROR_FILES
fi
if grep -q '\${' "$2"; then
log "WARNUNG: $2 enthaelt noch unaufgeloeste Platzhalter:"
grep -o '\${[A-Z_]*}' "$2" | sort -u >&2
fi
rm -f "$2.tmp"
}
fetch_conf telegraf/base.conf /etc/telegraf/telegraf.conf
# Nur Confs entfernen, die DIESER Installer verwaltet — nicht pauschal alles.
# Sonst reisst ein Auto-Update fremde oder manuell ergaenzte Confs mit, die
# jemand bewusst nach /etc/telegraf/telegraf.d/ gelegt hat.
for _c in docker proxmox apache gpu journal nodeinfo syslog; do
rm -f "/etc/telegraf/telegraf.d/$_c.conf"
done
[ "$HAS_DOCKER" -eq 1 ] && { fetch telegraf/docker.conf /etc/telegraf/telegraf.d/docker.conf; usermod -aG docker telegraf 2>/dev/null || true; }
case "$ROLE" in
proxmox) : # pvesh-Weg braucht keinen Token (siehe unten)
fetch scripts/proxmox-ve.sh /etc/telegraf/proxmox-ve.sh
chmod 0755 /etc/telegraf/proxmox-ve.sh
# pvesh-Weg: braucht keinen Token und keine ACL (laeuft als root)
cat > /etc/telegraf/telegraf.d/proxmox.conf <<'EOF'
[[inputs.exec]]
interval = "30s"
timeout = "25s"
data_format = "prometheus"
commands = ["/etc/telegraf/proxmox-ve.sh"]
EOF
log "Proxmox: pvesh-Input aktiv (VM/CT-Metriken, kein API-Token noetig)." ;;
apache) fetch_conf telegraf/apache.conf /etc/telegraf/telegraf.d/apache.conf
usermod -aG adm telegraf 2>/dev/null || true
grant_apache_log_access ;;
docker|none) ;;
*) die "Unbekannte Rolle: $ROLE" ;;
esac
[ "$HAS_APACHE" -eq 1 ] && [ "$ROLE" != "apache" ] && {
fetch_conf telegraf/apache.conf /etc/telegraf/telegraf.d/apache.conf
usermod -aG adm telegraf 2>/dev/null || true
grant_apache_log_access
}
# --- System-Logs (journald) → Loki — nur auf systemd-Systemen ---
if command -v journalctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
fetch scripts/journal-tail.sh /etc/telegraf/journal-tail.sh
chmod 0755 /etc/telegraf/journal-tail.sh
fetch_conf telegraf/journal.conf /etc/telegraf/telegraf.d/journal.conf
# Zustandsverzeichnis fuer den Journal-Cursor
mkdir -p /var/lib/telegraf-journal
if [ "$ROLE" = "proxmox" ]; then
: # laeuft als root (Drop-in) — voller Journal-Zugriff
else
chown telegraf:telegraf /var/lib/telegraf-journal 2>/dev/null || true
usermod -aG systemd-journal telegraf 2>/dev/null || true
fi
log "Journald-Logs aktiv (Warnungen+, inkrementell ueber Cursor)."
fi
# --- Node-Info (LAN-IP, NetBird-IP/FQDN, PVE-URL) — auf jeder Node ---
fetch scripts/node-info.sh /etc/telegraf/node-info.sh
chmod 0755 /etc/telegraf/node-info.sh
cat > /etc/telegraf/telegraf.d/nodeinfo.conf <<'EOF'
[[inputs.exec]]
interval = "300s"
timeout = "25s"
data_format = "prometheus"
commands = ["/etc/telegraf/node-info.sh"]
EOF
log "Node-Info aktiv (LAN-IP, NetBird-IP/FQDN, PVE-URL)."
# --- GPU (nur wenn erkannt) ---
# Jeder Block wird NUR geschrieben, wenn das jeweilige Werkzeug wirklich da ist.
# Ein Input ohne Binary laesst Telegraf beim Start hart scheitern -> alle Metriken aus.
case "$GPU" in
nvidia)
cat > /etc/telegraf/telegraf.d/gpu.conf <<'EOF'
[[inputs.nvidia_smi]]
timeout = "10s"
[inputs.nvidia_smi.tagdrop]
compute_mode = ["*"]
EOF
log "NVIDIA-GPU erkannt: inputs.nvidia_smi aktiv."
;;
amd)
fetch scripts/gpu-rocm.sh /etc/telegraf/gpu-rocm.sh
chmod 0755 /etc/telegraf/gpu-rocm.sh
# KEIN name_override: das Script liefert die Metriknamen selbst
# (gpu_utilization_gpu, gpu_temperature_edge, ...). name_override wuerde
# alle Werte zu "gpu" kollabieren -> in Prometheus nur noch gpu_value.
# data_format = prometheus: mit "influx" haengt Telegraf "_value" an den Namen.
cat > /etc/telegraf/telegraf.d/gpu.conf <<'EOF'
[[inputs.exec]]
interval = "30s"
timeout = "20s"
data_format = "prometheus"
commands = ["/etc/telegraf/gpu-rocm.sh"]
EOF
log "AMD-GPU erkannt: rocm-smi-exec-Input aktiv."
;;
intel)
# Bewusst KEIN GPU-Input: intel_gpu_top liefert kein Prometheus/Influx-Format
# und ein nvidia_smi-Block ohne nvidia-smi wuerde Telegraf killen.
log "Intel-GPU (/dev/dri) erkannt: kein GPU-Input aktiviert (intel_gpu_top wird nicht erfasst)."
;;
*) : ;;
esac
chown -R telegraf:telegraf /etc/telegraf 2>/dev/null || true
# --------------------------------------------------------------------------
# Force-Update: Confs und Hilfsscripts erzwungen neu schreiben.
# Laeuft VOR dem Konfig-Test, damit ein Rollback unten greifen kann.
# --------------------------------------------------------------------------
write_conf_backups() { # Sicherung fuer den Rollback
BK="/var/backups/telegraf-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BK"
cp -a /etc/telegraf/telegraf.d "$BK/" 2>/dev/null || true
cp -a /etc/telegraf/telegraf.conf "$BK/" 2>/dev/null || true
printf '%s' "$BK" > /var/lib/telegraf-last-backup
}
rollback_conf() { # letzte Sicherung zurueckspielen
BK="$(cat /var/lib/telegraf-last-backup 2>/dev/null || true)"
[ -n "$BK" ] && [ -d "$BK/telegraf.d" ] || return 1
rm -rf /etc/telegraf/telegraf.d
cp -a "$BK/telegraf.d" /etc/telegraf/telegraf.d
[ -f "$BK/telegraf.conf" ] && cp -a "$BK/telegraf.conf" /etc/telegraf/telegraf.conf
log "Rollback aus $BK"
}
if [ "$FORCE_UPDATE" -eq 1 ]; then
write_conf_backups
log "Force-Update: Confs und Scripts werden neu geschrieben."
fi
# --------------------------------------------------------------------------
# Konfig testen
# --------------------------------------------------------------------------
# WICHTIG: NICHT mit --input-filter pruefen! Telegraf validiert dann nur die
# gefilterten Plugins und ueberspringt die uebrigen Konfigdateien. Ein Fehler in
# telegraf.d/apache.conf (z.B. ein ungueltiges Feld) lief damit durch, der
# Installer meldete "OK", und erst der Dienststart scheiterte:
# Job for telegraf.service failed
# Ohne Filter werden ALLE Dateien geparst — genau wie beim Dienststart. Das ist
# der einzige Test, der den Startfehler wirklich verhindert.
TG="$(command -v telegraf || echo /usr/local/bin/telegraf)"
load_defaults
conf_test() {
_out="$("$TG" --config /etc/telegraf/telegraf.conf \
--config-directory /etc/telegraf/telegraf.d --test --test-wait 1 2>&1)"
_rc=$?
# Nur echte Konfigurationsfehler zaehlen. Ein fehlendes Input-Binary
# (nvidia-smi) erscheint erst beim Start als E!, nicht beim Parsen — und
# Sammel-Warnungen wie die DeprecationNotice sind harmlos.
if [ "$_rc" -ne 0 ] || printf '%s' "$_out" | grep -q '^.*E!.*loading config file'; then
printf '%s\n' "$_out" | grep -E 'E!' | head -5 >&2
return 1
fi
return 0
}
if ! conf_test; then
# Bei Force-/Auto-Update zuerst zurueckrollen, dann erst aufgeben.
if [ "$FORCE_UPDATE" -eq 1 ] || [ "$UPDATE_ONLY" -eq 1 ]; then
log "Konfiguration ungültig — Rollback auf die letzte Sicherung."
rollback_conf || true
conf_test || die "Konfiguration auch nach Rollback ungültig — telegraf --test prüfen."
log "Rollback erfolgreich, alte Konfiguration aktiv."
else
die "Konfiguration ungültig — siehe Meldung oben. Einzelne Datei pruefen mit: telegraf --config /etc/telegraf/telegraf.conf --config-directory /etc/telegraf/telegraf.d --test"
fi
fi
# --------------------------------------------------------------------------
# Dienst
# --------------------------------------------------------------------------
# Proxmox-Rolle: pvesh verlangt root. Der InfluxData-Dienst laeuft als User
# "telegraf" -> das Script liefert dann still nichts. Drop-in auf root.
if [ "$ROLE" = "proxmox" ] && [ -d /etc/systemd/system ]; then
mkdir -p /etc/systemd/system/telegraf.service.d
cat > /etc/systemd/system/telegraf.service.d/10-proxmox-root.conf <<'EOF'
# pvesh (VM/CT-Metriken) erfordert root-Rechte.
[Service]
User=root
Group=root
EOF
log "Proxmox: systemd-Drop-in gesetzt — Dienst laeuft als root (pvesh braucht root)."
fi
CHANGED=1
if [ "$UPDATE_ONLY" -eq 1 ] && [ -n "$SNAPSHOT_BEFORE" ]; then
SNAPSHOT_AFTER="$(mktemp)"
cat /etc/telegraf/telegraf.d/*.conf /etc/telegraf/telegraf.conf 2>/dev/null > "$SNAPSHOT_AFTER" || true
for s in journal-tail syslog-tail node-info proxmox-ve gpu-rocm; do
[ -f "/etc/telegraf/$s.sh" ] && cat "/etc/telegraf/$s.sh" >> "$SNAPSHOT_AFTER" 2>/dev/null
done
if cmp -s "$SNAPSHOT_BEFORE" "$SNAPSHOT_AFTER"; then CHANGED=0; fi
rm -f "$SNAPSHOT_BEFORE" "$SNAPSHOT_AFTER"
if [ "$CHANGED" -eq 0 ]; then
log "Update-Only: keine Aenderung an Confs/Scripts — kein Neustart noetig."
# Versionsmarker TROTZDEM auf den Repo-Stand ziehen: sonst meldet der
# naechste Timer-Lauf erneut ein Update und laeuft endlos im Kreis.
_RV="$(curl -fsS -m 15 "$REPO_RAW/VERSION" 2>/dev/null | tr -d ' \r\n' || true)"
[ -n "$_RV" ] && printf '%s\n' "$_RV" > /etc/telegraf/CONFIG_VERSION
_RS="$(curl -fsS -m 20 "$REPO_RAW/install.sh" 2>/dev/null | { command -v sha256sum >/dev/null && sha256sum || shasum -a 256; } | cut -d' ' -f1 || true)"
[ -n "$_RS" ] && printf '%s\n' "$_RS" > /etc/telegraf/INSTALLER_SHA
printf '%s\n' "$REPO_RAW" > /etc/telegraf/REPO_RAW
exit 0
fi
log "Update-Only: Aenderungen erkannt — Dienst wird neu gestartet."
fi
if command -v systemctl >/dev/null && [ -d /run/systemd/system ]; then
systemctl daemon-reload
if [ "$UPDATE_ONLY" -eq 1 ]; then
systemctl restart telegraf
else
systemctl enable --now telegraf >/dev/null
systemctl restart telegraf
fi
sleep 3
if ! systemctl is-active --quiet telegraf; then
journalctl -u telegraf -n 20 --no-pager
# Haeufigste Ursache: ein aktivierter Input, dessen Binary fehlt
# (z.B. nvidia_smi ohne nvidia-smi). GPU-Conf verwerfen und erneut versuchen.
if [ -f /etc/telegraf/telegraf.d/gpu.conf ]; then
log "Start fehlgeschlagen — entferne GPU-Conf und versuche erneut."
rm -f /etc/telegraf/telegraf.d/gpu.conf
systemctl reset-failed telegraf 2>/dev/null || true
systemctl restart telegraf
sleep 3
fi
systemctl is-active --quiet telegraf || die "telegraf läuft nicht. journalctl -u telegraf -n 30 prüfen."
log "WARNUNG: Telegraf läuft, aber ohne GPU-Metriken (Input-Binary fehlte)."
fi
elif command -v systemctl >/dev/null; then
log "HINWEIS: systemd nicht aktiv (Container/Chroot) — Dienst wird beim nächsten Boot gestartet (enabled)."
systemctl enable telegraf >/dev/null 2>&1 || true
elif command -v rc-update >/dev/null; then
rc-update add telegraf default >/dev/null 2>&1 || true
rc-service telegraf restart >/dev/null 2>&1 || log "HINWEIS: OpenRC nicht aktiv (Container?) — Dienst beim nächsten Boot."
fi
PROM_BASE="$(printf '%s' "$PROM_URL" | sed -E 's#/api/v1/write/?$##')"
[ "$GPU" != "none" ] && log "GPU=$GPU erfasst. Hinweis: im Docker-Betrieb muss der GPU-Treiber im Container verfuegbar sein (nvidia-container-toolkit bzw. /dev/dri-Mount); im systemd-Betrieb ist das automatisch der Fall."
# --------------------------------------------------------------------------
# Versionsmarker fuer den Auto-Updater
# --------------------------------------------------------------------------
REMOTE_V="$(curl -fsS -m 15 "$REPO_RAW/VERSION" 2>/dev/null | tr -d ' \r\n' || true)"
printf '%s\n' "${REMOTE_V:-unbekannt}" > /etc/telegraf/CONFIG_VERSION
# Aktive Repo-Quelle festhalten, damit der Auto-Updater denselben Weg nutzt.
printf '%s\n' "$REPO_RAW" > /etc/telegraf/REPO_RAW
# Hash der Installer-Logik: erkennt auch Aenderungen ohne Versionssprung
# (z.B. eine Korrektur an journal-tail.sh).
if curl -fsS -m 20 "$REPO_RAW/install.sh" -o /tmp/.tg-installer.sha 2>/dev/null; then
if command -v sha256sum >/dev/null; then sha256sum /tmp/.tg-installer.sha | cut -d' ' -f1 > /etc/telegraf/INSTALLER_SHA
elif command -v shasum >/dev/null; then shasum -a 256 /tmp/.tg-installer.sha | cut -d' ' -f1 > /etc/telegraf/INSTALLER_SHA
fi
rm -f /tmp/.tg-installer.sha
fi
chmod 0644 /etc/telegraf/CONFIG_VERSION /etc/telegraf/INSTALLER_SHA 2>/dev/null || true
# --------------------------------------------------------------------------
# Auto-Updater-Timer
# --------------------------------------------------------------------------
install_update_timer() {
IV="${1:-daily}"
case "$IV" in
daily) ONCAL="*-*-* 04:30:00" ;;
hourly) ONCAL="*-*-* *:30:00" ;;
weekly) ONCAL="Mon *-*-* 04:30:00" ;;
*min) ONCAL="" ;; # OnUnitActiveSec statt OnCalendar
*) ONCAL="*-*-* 04:30:00" ;;
esac
# Der Updater holt sich das Script frisch und ruft sich selbst mit
# --update-only auf. Token kommt aus /etc/default/telegraf (nicht im Repo).
cat > /usr/local/bin/telegraf-installer-update <<'UPD'
#!/bin/sh
# Auto-Updater: nur bei echter Aenderung etwas tun.
set -u
# Repo-Quelle: aus der Umgebung, sonst aus der beim Install hinterlegten
# Datei, sonst Default. So funktioniert der Updater auch mit Mirror/Testserver.
REPO_RAW="${REPO_RAW:-$(cat /etc/telegraf/REPO_RAW 2>/dev/null || echo https://gitea.m-m.bio/mm/netbird-telegraf-installer/raw/branch/main)}"
LOG="/var/log/telegraf-installer-update.log"
say() { printf '%s %s\n' "$(date -Is)" "$*" >> "$LOG"; }
LOCAL_V="$(cat /etc/telegraf/CONFIG_VERSION 2>/dev/null || echo '')"
REMOTE_V="$(curl -fsS -m 20 "$REPO_RAW/VERSION" 2>/dev/null | tr -d ' \r\n' || true)"
[ -n "$REMOTE_V" ] || { say "Repo nicht erreichbar (NetBird down?) — kein Update."; exit 0; }
NEED=0
[ "$LOCAL_V" != "$REMOTE_V" ] && NEED=1
# Hash-Vergleich: erkennt Script-Fixes ohne Versionssprung
if [ "$NEED" -eq 0 ] && command -v sha256sum >/dev/null; then
TMP="$(mktemp)"; curl -fsS -m 25 "$REPO_RAW/install.sh" -o "$TMP" 2>/dev/null || true
if [ -s "$TMP" ]; then
R_SHA="$(sha256sum "$TMP" | cut -d' ' -f1)"
L_SHA="$(cat /etc/telegraf/INSTALLER_SHA 2>/dev/null || echo '')"
[ "$R_SHA" != "$L_SHA" ] && NEED=1
fi
rm -f "$TMP"
fi
if [ "$NEED" -eq 0 ]; then
say "aktuell ($LOCAL_V) — nichts zu tun."
exit 0
fi
say "Update $LOCAL_V -> $REMOTE_V"
TMPI="$(mktemp)"
if ! curl -fsS -m 60 "$REPO_RAW/install.sh" -o "$TMPI" 2>/dev/null; then
say "Download fehlgeschlagen."; rm -f "$TMPI"; exit 1
fi
# --update-only: nur Confs/Scripts, kein Paket, kein Tokenwechsel.
# WICHTIG: REPO_RAW und die Node-Identitaet muessen mitgegeben werden, sonst
# faellt NODE_NAME auf den Hostnamen der AUFRUFENDEN Maschine zurueck und der
# Unterlauf holt die Version von der falschen Quelle.
UPD_NAME="$(sed -n 's/^NODE_NAME=//p' /etc/default/telegraf 2>/dev/null | head -1)"
UPD_ROLE="$(sed -n 's/^ROLE=//p' /etc/default/telegraf 2>/dev/null | head -1)"
UPD_TOKEN="$(sed -n 's/^TELEGRAF_TOKEN=//p' /etc/default/telegraf 2>/dev/null | head -1)"
UPD_COLL="$(sed -n 's/^COLLECTOR=//p' /etc/default/telegraf 2>/dev/null | head -1)"
[ -n "$UPD_NAME" ] || UPD_NAME="$(hostname -s)"
set -- --update-only --name "$UPD_NAME" --token "$UPD_TOKEN"
[ -n "$UPD_ROLE" ] && set -- "$@" --role "$UPD_ROLE"
[ -n "$UPD_COLL" ] && set -- "$@" --collector "$UPD_COLL"
if REPO_RAW="$REPO_RAW" sh "$TMPI" "$@" >> "$LOG" 2>&1; then
say "Update erfolgreich."
else
say "Update FEHLGESCHLAGEN — siehe oben. Alte Konfiguration bleibt aktiv."
fi
rm -f "$TMPI"
UPD
chmod 0755 /usr/local/bin/telegraf-installer-update
if [ -d /etc/systemd/system ]; then
cat > /etc/systemd/system/telegraf-installer-update.service <<'EOF'
[Unit]
Description=Telegraf-Installer Auto-Update (nur bei Aenderung)
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/telegraf-installer-update
EOF
if [ -n "$ONCAL" ]; then
cat > /etc/systemd/system/telegraf-installer-update.timer <<EOF
[Unit]
Description=Taeglicher Telegraf-Installer Auto-Update
[Timer]
OnCalendar=$ONCAL
Persistent=true
RandomizedDelaySec=15m
[Install]
WantedBy=timers.target
EOF
else
cat > /etc/systemd/system/telegraf-installer-update.timer <<EOF
[Unit]
Description=Telegraf-Installer Auto-Update ($IV)
[Timer]
OnBootSec=$IV
OnUnitActiveSec=$IV
Persistent=true
[Install]
WantedBy=timers.target
EOF
fi
systemctl daemon-reload
systemctl enable --now telegraf-installer-update.timer >/dev/null 2>&1 \
&& log "Auto-Updater aktiv ($IV) — Log: /var/log/telegraf-installer-update.log" \
|| log "WARNUNG: Timer konnte nicht aktiviert werden (systemd nicht aktiv?)."
else
log "HINWEIS: kein systemd — Auto-Updater nur manuell (telegraf-installer-update)."
fi
}
remove_update_timer() {
command -v systemctl >/dev/null 2>&1 || { log "Auto-Updater: systemctl fehlt — Dateien werden entfernt."; rm -f /etc/systemd/system/telegraf-installer-update.timer /etc/systemd/system/telegraf-installer-update.service /usr/local/bin/telegraf-installer-update; return 0; }
systemctl disable --now telegraf-installer-update.timer >/dev/null 2>&1 || true
rm -f /etc/systemd/system/telegraf-installer-update.timer \
/etc/systemd/system/telegraf-installer-update.service \
/usr/local/bin/telegraf-installer-update
systemctl daemon-reload 2>/dev/null || true
log "Auto-Updater entfernt."
}
if [ "$REMOVE_TIMER" -eq 1 ]; then
if command -v systemctl >/dev/null 2>&1; then remove_update_timer
else log "Auto-Updater: kein systemd — nichts zu entfernen."; fi
fi
if [ -n "$TIMER_INTERVAL" ]; then install_update_timer "$TIMER_INTERVAL"; fi
log "OK — Node '$NODE_NAME' liefert an $PROM_URL (Metriken) und $LOKI_URL (Logs)."
log "Prüfen: curl -s -H 'Authorization: Bearer <TOKEN>' '$PROM_BASE/api/v1/query?query=system_uptime{node=\"$NODE_NAME\"}'"
log "Grafana: http://100.83.134.46:3000 (nur im NetBird)"