feat(agent): Bash-Agent mit Installer, systemd-Timer und Spool (#005)
Produkterkennung fuer PVE, PBS und PMG, JSON-Erzeugung ohne jq, Retry mit Backoff, Nachsenden aus dem Spool, Update und Uninstall. Das Dashboard liefert install.sh und agent.sh mit eingesetzter Adresse aus. 38 Tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+235
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env bash
|
||||
# ProxLedger-Agent installieren.
|
||||
#
|
||||
# curl -fsSL https://dashboard.example.de/install.sh | bash -s -- --token plx_e_...
|
||||
#
|
||||
# Optionen:
|
||||
# --token <TOKEN> Enrollment-Token aus dem Dashboard (erforderlich)
|
||||
# --url <URL> Adresse des Dashboards (Standard: Herkunft dieses Skripts)
|
||||
# --ca-cert <DATEI> eigenes CA-Zertifikat für TLS
|
||||
# --auto-update Agent aktualisiert sich selbst, wenn das Dashboard eine neuere Version meldet
|
||||
# --uninstall Agent vollständig entfernen
|
||||
# --help diese Hilfe
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
DASHBOARD_URL="${PROXLEDGER_URL:-@@DASHBOARD_URL@@}"
|
||||
TOKEN=""
|
||||
CA_CERT=""
|
||||
AUTO_UPDATE="no"
|
||||
AKTION="install"
|
||||
|
||||
CONFIG_VERZEICHNIS="/etc/proxledger"
|
||||
CONFIG_DATEI="${CONFIG_VERZEICHNIS}/agent.conf"
|
||||
AGENT_PFAD="/usr/local/bin/proxledger-agent"
|
||||
SPOOL_VERZEICHNIS="/var/lib/proxledger/spool"
|
||||
SYSTEMD_VERZEICHNIS="/etc/systemd/system"
|
||||
|
||||
hinweis() { printf '\033[1;36m›\033[0m %s\n' "$*"; }
|
||||
erfolg() { printf '\033[1;32m✓\033[0m %s\n' "$*"; }
|
||||
fehler() {
|
||||
printf '\033[1;31m✗\033[0m %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
hilfe() {
|
||||
sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--token)
|
||||
TOKEN="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--url)
|
||||
DASHBOARD_URL="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--ca-cert)
|
||||
CA_CERT="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--auto-update)
|
||||
AUTO_UPDATE="yes"
|
||||
shift
|
||||
;;
|
||||
--uninstall)
|
||||
AKTION="uninstall"
|
||||
shift
|
||||
;;
|
||||
--help | -h)
|
||||
hilfe
|
||||
exit 0
|
||||
;;
|
||||
*) fehler "Unbekannte Option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || fehler "Bitte als root ausführen."
|
||||
|
||||
deinstallieren() {
|
||||
hinweis "Entferne ProxLedger-Agent …"
|
||||
systemctl disable --now proxledger.timer >/dev/null 2>&1 || true
|
||||
systemctl stop proxledger.service >/dev/null 2>&1 || true
|
||||
rm -f "${SYSTEMD_VERZEICHNIS}/proxledger.service" "${SYSTEMD_VERZEICHNIS}/proxledger.timer"
|
||||
systemctl daemon-reload
|
||||
rm -f "$AGENT_PFAD"
|
||||
rm -rf "$CONFIG_VERZEICHNIS" "$(dirname "$SPOOL_VERZEICHNIS")"
|
||||
erfolg "Agent entfernt. Der Host bleibt im Dashboard erhalten; dort ggf. den Token widerrufen."
|
||||
}
|
||||
|
||||
if [ "$AKTION" = "uninstall" ]; then
|
||||
deinstallieren
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ -n "$TOKEN" ] || fehler "Enrollment-Token fehlt (--token)."
|
||||
[ -n "$DASHBOARD_URL" ] || fehler "Dashboard-Adresse fehlt (--url)."
|
||||
DASHBOARD_URL=${DASHBOARD_URL%/}
|
||||
|
||||
command -v curl >/dev/null 2>&1 || fehler "curl ist nicht installiert."
|
||||
command -v systemctl >/dev/null 2>&1 || fehler "systemd wird benötigt."
|
||||
|
||||
produkt_erkennen() {
|
||||
if command -v pvesubscription >/dev/null 2>&1; then
|
||||
printf 'pve'
|
||||
elif command -v proxmox-backup-manager >/dev/null 2>&1; then
|
||||
printf 'pbs'
|
||||
elif command -v pmgsubscription >/dev/null 2>&1; then
|
||||
printf 'pmg'
|
||||
fi
|
||||
}
|
||||
|
||||
PRODUKT=$(produkt_erkennen)
|
||||
[ -n "$PRODUKT" ] || fehler "Kein Proxmox-Produkt gefunden (VE, Backup Server oder Mail Gateway)."
|
||||
hinweis "Produkt erkannt: ${PRODUKT}"
|
||||
|
||||
# Stabile Agent-Kennung: bleibt auch nach Hardwaretausch gleich, solange das System dasselbe ist.
|
||||
maschinen_id() {
|
||||
local id=""
|
||||
[ -r /etc/machine-id ] && id=$(cat /etc/machine-id)
|
||||
[ -z "$id" ] && [ -r /var/lib/dbus/machine-id ] && id=$(cat /var/lib/dbus/machine-id)
|
||||
[ -z "$id" ] && id=$(hostname -f 2>/dev/null || hostname)
|
||||
printf '%s' "$id"
|
||||
}
|
||||
|
||||
AGENT_ID=$(maschinen_id)
|
||||
HOSTNAME_KURZ=$(hostname -s 2>/dev/null || hostname)
|
||||
FQDN=$(hostname -f 2>/dev/null || printf '')
|
||||
|
||||
server_id() {
|
||||
case "$PRODUKT" in
|
||||
pve) pvesubscription get 2>/dev/null | sed -n 's/^serverid:[[:space:]]*//p' | head -n1 ;;
|
||||
pbs) proxmox-backup-manager subscription get 2>/dev/null | sed -n 's/^serverid:[[:space:]]*//p' | head -n1 ;;
|
||||
pmg) pmgsubscription get 2>/dev/null | sed -n 's/^serverid:[[:space:]]*//p' | head -n1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
SERVER_ID=$(server_id)
|
||||
|
||||
hinweis "Registriere Host beim Dashboard …"
|
||||
ANFRAGE=$(
|
||||
cat <<JSON
|
||||
{"agentId":"${AGENT_ID}","hostname":"${HOSTNAME_KURZ}","fqdn":$([ -n "$FQDN" ] && printf '"%s"' "$FQDN" || printf 'null'),"produkt":"${PRODUKT}","serverId":$([ -n "$SERVER_ID" ] && printf '"%s"' "$SERVER_ID" || printf 'null'),"agentVersion":"@@AGENT_VERSION@@"}
|
||||
JSON
|
||||
)
|
||||
|
||||
ANTWORT=$(curl --silent --show-error --max-time 30 \
|
||||
${CA_CERT:+--cacert "$CA_CERT"} \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Authorization: Bearer ${TOKEN}" \
|
||||
--write-out '\n%{http_code}' \
|
||||
--request POST --data "$ANFRAGE" "${DASHBOARD_URL}/api/agent/v1/enroll") || fehler "Dashboard nicht erreichbar."
|
||||
|
||||
HTTP_CODE=$(printf '%s' "$ANTWORT" | tail -n1)
|
||||
KOERPER=$(printf '%s' "$ANTWORT" | sed '$d')
|
||||
|
||||
case "$HTTP_CODE" in
|
||||
200 | 201) ;;
|
||||
401) fehler "Enrollment-Token wurde abgelehnt (abgelaufen, widerrufen oder aufgebraucht)." ;;
|
||||
429) fehler "Zu viele Registrierungsversuche. Bitte später erneut versuchen." ;;
|
||||
*) fehler "Registrierung fehlgeschlagen (HTTP ${HTTP_CODE}): ${KOERPER}" ;;
|
||||
esac
|
||||
|
||||
HOST_TOKEN=$(printf '%s' "$KOERPER" | sed -n 's/.*"token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)
|
||||
[ -n "$HOST_TOKEN" ] || fehler "Dashboard hat keinen Host-Token geliefert."
|
||||
erfolg "Host registriert."
|
||||
|
||||
hinweis "Installiere Agent nach ${AGENT_PFAD} …"
|
||||
curl --silent --show-error --fail --max-time 60 \
|
||||
${CA_CERT:+--cacert "$CA_CERT"} \
|
||||
"${DASHBOARD_URL}/agent.sh" -o "${AGENT_PFAD}.neu" || fehler "Agent-Skript konnte nicht geladen werden."
|
||||
head -n1 "${AGENT_PFAD}.neu" | grep -q '^#!/usr/bin/env bash' || fehler "Heruntergeladene Datei ist kein Agent-Skript."
|
||||
install -m 755 "${AGENT_PFAD}.neu" "$AGENT_PFAD"
|
||||
rm -f "${AGENT_PFAD}.neu"
|
||||
|
||||
mkdir -p "$CONFIG_VERZEICHNIS" "$SPOOL_VERZEICHNIS"
|
||||
chmod 700 "$CONFIG_VERZEICHNIS"
|
||||
umask 077
|
||||
cat >"$CONFIG_DATEI" <<CONF
|
||||
# ProxLedger-Agent – Konfiguration (von install.sh erzeugt)
|
||||
DASHBOARD_URL="${DASHBOARD_URL}"
|
||||
TOKEN="${HOST_TOKEN}"
|
||||
AGENT_ID="${AGENT_ID}"
|
||||
# Eigenes CA-Zertifikat, falls das Dashboard ein privates Zertifikat nutzt:
|
||||
CA_CERT="${CA_CERT}"
|
||||
# Agent aktualisiert sich selbst, sobald das Dashboard eine neuere Version meldet:
|
||||
AUTO_UPDATE="${AUTO_UPDATE}"
|
||||
# TLS-Prüfung niemals dauerhaft abschalten – nur für Tests:
|
||||
TLS_PRUEFEN="yes"
|
||||
CONF
|
||||
chmod 600 "$CONFIG_DATEI"
|
||||
erfolg "Konfiguration geschrieben (${CONFIG_DATEI}, Rechte 600)."
|
||||
|
||||
cat >"${SYSTEMD_VERZEICHNIS}/proxledger.service" <<'UNIT'
|
||||
[Unit]
|
||||
Description=ProxLedger-Agent – meldet den Subscription-Status an das Dashboard
|
||||
Documentation=https://github.com/proxledger
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/proxledger-agent --report
|
||||
Nice=10
|
||||
# Der Agent liest nur Systeminformationen und braucht keine erweiterten Rechte.
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
UNIT
|
||||
|
||||
cat >"${SYSTEMD_VERZEICHNIS}/proxledger.timer" <<'UNIT'
|
||||
[Unit]
|
||||
Description=Täglicher ProxLedger-Report
|
||||
|
||||
[Timer]
|
||||
OnCalendar=daily
|
||||
# Verteilt die Meldungen aller Hosts über vier Stunden.
|
||||
RandomizedDelaySec=4h
|
||||
Persistent=true
|
||||
Unit=proxledger.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
UNIT
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now proxledger.timer >/dev/null
|
||||
erfolg "systemd-Timer aktiviert (täglich, zufällig um bis zu 4 Stunden versetzt)."
|
||||
|
||||
hinweis "Sende ersten Report …"
|
||||
if "$AGENT_PFAD" --report; then
|
||||
erfolg "Erster Report übertragen."
|
||||
else
|
||||
printf '\033[1;33m!\033[0m Erster Report fehlgeschlagen – er wird beim nächsten Lauf nachgesendet.\n'
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
erfolg "Fertig. Status prüfen mit: systemctl list-timers proxledger.timer"
|
||||
printf ' Log ansehen: journalctl -t proxledger -n 50\n'
|
||||
printf ' Verbindung testen: %s --test\n' "$AGENT_PFAD"
|
||||
printf ' Entfernen: curl -fsSL %s/install.sh | bash -s -- --uninstall\n' "$DASHBOARD_URL"
|
||||
Executable
+451
@@ -0,0 +1,451 @@
|
||||
#!/usr/bin/env bash
|
||||
# ProxLedger-Agent – meldet den Subscription-Status eines Proxmox-Hosts an das Dashboard.
|
||||
# Läuft ohne zusätzliche Abhängigkeiten: nur curl und Bordmittel von Proxmox.
|
||||
#
|
||||
# Aufrufe:
|
||||
# proxledger-agent Report erzeugen und senden (Standard, vom systemd-Timer aufgerufen)
|
||||
# proxledger-agent --dry-run Report nur ausgeben, nichts senden
|
||||
# proxledger-agent --test Verbindung zum Dashboard prüfen
|
||||
# proxledger-agent --update Agent auf die vom Dashboard gemeldete Version aktualisieren
|
||||
# proxledger-agent --version Version ausgeben
|
||||
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
AGENT_VERSION="0.2.0"
|
||||
CONFIG_DATEI="${PROXLEDGER_CONFIG:-/etc/proxledger/agent.conf}"
|
||||
SPOOL_VERZEICHNIS="${PROXLEDGER_SPOOL:-/var/lib/proxledger/spool}"
|
||||
SPOOL_MAXIMUM=30
|
||||
WARTEZEITEN=(30 120 480)
|
||||
|
||||
# --------------------------------------------------------------------- Protokoll
|
||||
|
||||
protokoll() {
|
||||
local stufe="$1"
|
||||
shift
|
||||
if command -v logger >/dev/null 2>&1; then
|
||||
logger -t proxledger -p "daemon.${stufe}" -- "$*" 2>/dev/null || true
|
||||
fi
|
||||
printf '%s [%s] %s\n' "$(datum_jetzt)" "$stufe" "$*" >&2
|
||||
}
|
||||
|
||||
datum_jetzt() {
|
||||
date -u '+%Y-%m-%dT%H:%M:%SZ'
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- JSON-Hilfen
|
||||
|
||||
# Maskiert einen Wert für JSON (ohne jq, das auf Proxmox nicht überall vorhanden ist).
|
||||
json_text() {
|
||||
local wert="$1"
|
||||
if [ -z "$wert" ]; then
|
||||
printf 'null'
|
||||
return
|
||||
fi
|
||||
wert=${wert//\\/\\\\}
|
||||
wert=${wert//\"/\\\"}
|
||||
wert=${wert//$'\t'/\\t}
|
||||
wert=${wert//$'\r'/}
|
||||
wert=${wert//$'\n'/\\n}
|
||||
# Übrige Steuerzeichen entfernen, damit immer gültiges JSON entsteht.
|
||||
wert=$(printf '%s' "$wert" | tr -d '\000-\010\013\014\016-\037')
|
||||
printf '"%s"' "$wert"
|
||||
}
|
||||
|
||||
# Gibt eine Zahl aus oder null, wenn der Wert leer bzw. keine Zahl ist.
|
||||
json_zahl() {
|
||||
local wert="$1"
|
||||
if [[ "$wert" =~ ^-?[0-9]+$ ]]; then
|
||||
printf '%s' "$wert"
|
||||
else
|
||||
printf 'null'
|
||||
fi
|
||||
}
|
||||
|
||||
# Baut aus Zeilen eine JSON-Liste; leere Eingabe ergibt [].
|
||||
json_liste() {
|
||||
local ausgabe="[" erste=1 zeile
|
||||
while IFS= read -r zeile; do
|
||||
[ -z "$zeile" ] && continue
|
||||
if [ $erste -eq 0 ]; then ausgabe+=","; fi
|
||||
ausgabe+=$(json_text "$zeile")
|
||||
erste=0
|
||||
done
|
||||
printf '%s]' "$ausgabe"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- Konfiguration
|
||||
|
||||
konfiguration_laden() {
|
||||
DASHBOARD_URL=""
|
||||
TOKEN=""
|
||||
CA_CERT=""
|
||||
AUTO_UPDATE="no"
|
||||
AGENT_ID=""
|
||||
TLS_PRUEFEN="yes"
|
||||
|
||||
if [ -r "$CONFIG_DATEI" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$CONFIG_DATEI"
|
||||
fi
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- Datensammlung
|
||||
|
||||
produkt_erkennen() {
|
||||
if command -v pvesubscription >/dev/null 2>&1; then
|
||||
printf 'pve'
|
||||
elif command -v proxmox-backup-manager >/dev/null 2>&1; then
|
||||
printf 'pbs'
|
||||
elif command -v pmgsubscription >/dev/null 2>&1; then
|
||||
printf 'pmg'
|
||||
else
|
||||
printf ''
|
||||
fi
|
||||
}
|
||||
|
||||
subscription_ausgabe() {
|
||||
case "$1" in
|
||||
pve) pvesubscription get 2>/dev/null ;;
|
||||
pbs) proxmox-backup-manager subscription get 2>/dev/null ;;
|
||||
pmg) pmgsubscription get 2>/dev/null ;;
|
||||
*) printf '' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Liest ein Feld aus der Subscription-Ausgabe. Unterstützt "feld: wert" und JSON.
|
||||
feld_lesen() {
|
||||
local feld="$1" text="$2" wert
|
||||
wert=$(printf '%s\n' "$text" | sed -n "s/^[[:space:]]*${feld}[[:space:]]*:[[:space:]]*//p" | head -n1)
|
||||
if [ -z "$wert" ]; then
|
||||
wert=$(printf '%s\n' "$text" |
|
||||
sed -n "s/.*\"${feld}\"[[:space:]]*:[[:space:]]*\"\{0,1\}\([^\",}]*\)\"\{0,1\}.*/\1/p" | head -n1)
|
||||
fi
|
||||
# Umschließende Anführungszeichen und Leerzeichen entfernen.
|
||||
wert=${wert%\"}
|
||||
wert=${wert#\"}
|
||||
printf '%s' "$wert" | sed 's/[[:space:]]*$//'
|
||||
}
|
||||
|
||||
produkt_version() {
|
||||
case "$1" in
|
||||
pve) pveversion 2>/dev/null | head -n1 ;;
|
||||
pbs) proxmox-backup-manager version 2>/dev/null | head -n1 ;;
|
||||
pmg) pmgversion 2>/dev/null | head -n1 ;;
|
||||
*) printf '' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
ip_adressen() {
|
||||
if command -v ip >/dev/null 2>&1; then
|
||||
ip -o addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1
|
||||
else
|
||||
hostname -I 2>/dev/null | tr ' ' '\n'
|
||||
fi
|
||||
}
|
||||
|
||||
cluster_name() {
|
||||
if command -v pvecm >/dev/null 2>&1; then
|
||||
pvecm status 2>/dev/null | sed -n 's/^[[:space:]]*Name:[[:space:]]*//p' | head -n1
|
||||
fi
|
||||
}
|
||||
|
||||
node_name() {
|
||||
if command -v pvecm >/dev/null 2>&1 && pvecm status >/dev/null 2>&1; then
|
||||
hostname -s 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
cpu_sockets() {
|
||||
if command -v lscpu >/dev/null 2>&1; then
|
||||
lscpu 2>/dev/null | sed -n 's/^Socket(s):[[:space:]]*//p' | head -n1
|
||||
fi
|
||||
}
|
||||
|
||||
cpu_modell() {
|
||||
if command -v lscpu >/dev/null 2>&1; then
|
||||
lscpu 2>/dev/null | sed -n 's/^Model name:[[:space:]]*//p' | head -n1
|
||||
fi
|
||||
}
|
||||
|
||||
uptime_sekunden() {
|
||||
if [ -r /proc/uptime ]; then
|
||||
cut -d. -f1 /proc/uptime
|
||||
fi
|
||||
}
|
||||
|
||||
paket_updates() {
|
||||
if command -v apt >/dev/null 2>&1; then
|
||||
# Die Kopfzeile "Auflistung..." zählt nicht mit.
|
||||
apt list --upgradable 2>/dev/null | grep -c 'upgradable from' || true
|
||||
fi
|
||||
}
|
||||
|
||||
report_id() {
|
||||
if [ -r /proc/sys/kernel/random/uuid ]; then
|
||||
cat /proc/sys/kernel/random/uuid
|
||||
else
|
||||
printf 'report-%s-%s' "$(date -u '+%Y%m%d%H%M%S')" "$RANDOM$RANDOM"
|
||||
fi
|
||||
}
|
||||
|
||||
# Erzeugt den vollständigen Report als JSON. Fehlende Werte werden zu null.
|
||||
report_erzeugen() {
|
||||
local produkt subscription key status level productname sockets serverid regdate nextduedate checktime
|
||||
produkt=$(produkt_erkennen)
|
||||
subscription=$(subscription_ausgabe "$produkt")
|
||||
|
||||
key=$(feld_lesen "key" "$subscription")
|
||||
status=$(feld_lesen "status" "$subscription")
|
||||
level=$(feld_lesen "level" "$subscription")
|
||||
productname=$(feld_lesen "productname" "$subscription")
|
||||
sockets=$(feld_lesen "sockets" "$subscription")
|
||||
serverid=$(feld_lesen "serverid" "$subscription")
|
||||
regdate=$(feld_lesen "regdate" "$subscription")
|
||||
nextduedate=$(feld_lesen "nextduedate" "$subscription")
|
||||
checktime=$(feld_lesen "checktime" "$subscription")
|
||||
|
||||
# Proxmox liefert Datumsangaben teils mit Uhrzeit; das Dashboard erwartet nur den Tag.
|
||||
regdate=${regdate%% *}
|
||||
nextduedate=${nextduedate%% *}
|
||||
|
||||
cat <<JSON
|
||||
{
|
||||
"reportId": $(json_text "$(report_id)"),
|
||||
"agentVersion": $(json_text "$AGENT_VERSION"),
|
||||
"erhobenAm": $(json_text "$(datum_jetzt)"),
|
||||
"host": {
|
||||
"hostname": $(json_text "$(hostname -s 2>/dev/null)"),
|
||||
"fqdn": $(json_text "$(hostname -f 2>/dev/null)"),
|
||||
"ips": $(ip_adressen | json_liste),
|
||||
"clusterName": $(json_text "$(cluster_name)"),
|
||||
"nodeName": $(json_text "$(node_name)"),
|
||||
"kernel": $(json_text "$(uname -r 2>/dev/null)"),
|
||||
"uptimeSekunden": $(json_zahl "$(uptime_sekunden)"),
|
||||
"cpuModell": $(json_text "$(cpu_modell)"),
|
||||
"sockets": $(json_zahl "$(cpu_sockets)"),
|
||||
"paketUpdates": $(json_zahl "$(paket_updates)")
|
||||
},
|
||||
"produkt": {
|
||||
"typ": $(json_text "$produkt"),
|
||||
"version": $(json_text "$(produkt_version "$produkt")")
|
||||
},
|
||||
"subscription": {
|
||||
"key": $(json_text "$key"),
|
||||
"status": $(json_text "$status"),
|
||||
"level": $(json_text "$level"),
|
||||
"productname": $(json_text "$productname"),
|
||||
"sockets": $(json_zahl "$sockets"),
|
||||
"serverid": $(json_text "$serverid"),
|
||||
"regdate": $(json_text "$regdate"),
|
||||
"nextduedate": $(json_text "$nextduedate"),
|
||||
"checktime": $(json_text "$checktime")
|
||||
}
|
||||
}
|
||||
JSON
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- Versand
|
||||
|
||||
curl_optionen() {
|
||||
local optionen=(--silent --show-error --max-time 30 --header "Content-Type: application/json")
|
||||
optionen+=(--header "Authorization: Bearer ${TOKEN}")
|
||||
optionen+=(--header "User-Agent: proxledger-agent/${AGENT_VERSION}")
|
||||
if [ -n "$CA_CERT" ]; then
|
||||
optionen+=(--cacert "$CA_CERT")
|
||||
fi
|
||||
if [ "$TLS_PRUEFEN" = "no" ]; then
|
||||
# Nur für Testumgebungen; die Konfiguration warnt davor.
|
||||
optionen+=(--insecure)
|
||||
fi
|
||||
printf '%s\n' "${optionen[@]}"
|
||||
}
|
||||
|
||||
# Sendet einen Report; 0 = erfolgreich, 1 = Fehler.
|
||||
report_senden() {
|
||||
local nutzdaten="$1" antwort http_code
|
||||
local -a optionen
|
||||
mapfile -t optionen < <(curl_optionen)
|
||||
|
||||
antwort=$(curl "${optionen[@]}" --write-out '\n%{http_code}' \
|
||||
--request POST --data "$nutzdaten" "${DASHBOARD_URL}/api/agent/v1/report" 2>&1)
|
||||
http_code=$(printf '%s' "$antwort" | tail -n1)
|
||||
|
||||
case "$http_code" in
|
||||
200 | 201)
|
||||
neueste_version_merken "$antwort"
|
||||
return 0
|
||||
;;
|
||||
401 | 403)
|
||||
protokoll err "Dashboard lehnt den Token ab (HTTP $http_code). Agent neu registrieren."
|
||||
return 1
|
||||
;;
|
||||
"")
|
||||
protokoll warning "Dashboard nicht erreichbar."
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
protokoll warning "Dashboard antwortet mit HTTP ${http_code}."
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
neueste_version_merken() {
|
||||
NEUESTE_VERSION=$(printf '%s' "$1" |
|
||||
sed -n 's/.*"agentVersion"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)
|
||||
}
|
||||
|
||||
# Versucht den Versand mehrfach mit wachsenden Wartezeiten.
|
||||
mit_wiederholung_senden() {
|
||||
local nutzdaten="$1" versuch=0
|
||||
|
||||
if report_senden "$nutzdaten"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
for wartezeit in "${WARTEZEITEN[@]}"; do
|
||||
versuch=$((versuch + 1))
|
||||
protokoll info "Neuer Versuch ${versuch} in ${wartezeit} s."
|
||||
sleep "$wartezeit"
|
||||
if report_senden "$nutzdaten"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
spool_ablegen() {
|
||||
local nutzdaten="$1" datei
|
||||
mkdir -p "$SPOOL_VERZEICHNIS" 2>/dev/null || return 1
|
||||
datei="${SPOOL_VERZEICHNIS}/$(date -u '+%Y%m%d%H%M%S')-$$.json"
|
||||
printf '%s\n' "$nutzdaten" >"$datei"
|
||||
chmod 600 "$datei" 2>/dev/null || true
|
||||
protokoll info "Report zwischengespeichert: $datei"
|
||||
spool_begrenzen
|
||||
}
|
||||
|
||||
# Bewahrt nur die neuesten Dateien auf, damit der Spool nicht unbegrenzt wächst.
|
||||
spool_begrenzen() {
|
||||
local anzahl
|
||||
anzahl=$(find "$SPOOL_VERZEICHNIS" -maxdepth 1 -name '*.json' 2>/dev/null | wc -l)
|
||||
if [ "$anzahl" -gt "$SPOOL_MAXIMUM" ]; then
|
||||
find "$SPOOL_VERZEICHNIS" -maxdepth 1 -name '*.json' -printf '%T@ %p\n' 2>/dev/null |
|
||||
sort -n | head -n "$((anzahl - SPOOL_MAXIMUM))" | cut -d' ' -f2- |
|
||||
while IFS= read -r alt; do rm -f "$alt"; done
|
||||
fi
|
||||
}
|
||||
|
||||
# Sendet zwischengespeicherte Reports nach; erfolgreiche werden gelöscht.
|
||||
spool_nachsenden() {
|
||||
[ -d "$SPOOL_VERZEICHNIS" ] || return 0
|
||||
local datei inhalt
|
||||
for datei in "$SPOOL_VERZEICHNIS"/*.json; do
|
||||
[ -e "$datei" ] || continue
|
||||
inhalt=$(cat "$datei")
|
||||
if report_senden "$inhalt"; then
|
||||
rm -f "$datei"
|
||||
protokoll info "Nachgesendeter Report übertragen: $(basename "$datei")"
|
||||
else
|
||||
protokoll info "Nachsenden verschoben, Dashboard nicht erreichbar."
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- Aktionen
|
||||
|
||||
aktion_report() {
|
||||
konfiguration_pruefen
|
||||
spool_nachsenden
|
||||
|
||||
local nutzdaten
|
||||
nutzdaten=$(report_erzeugen)
|
||||
|
||||
if mit_wiederholung_senden "$nutzdaten"; then
|
||||
protokoll info "Report übertragen (Agent ${AGENT_VERSION})."
|
||||
version_pruefen
|
||||
return 0
|
||||
fi
|
||||
|
||||
spool_ablegen "$nutzdaten"
|
||||
return 1
|
||||
}
|
||||
|
||||
aktion_test() {
|
||||
konfiguration_pruefen
|
||||
local -a optionen
|
||||
mapfile -t optionen < <(curl_optionen)
|
||||
local antwort
|
||||
antwort=$(curl "${optionen[@]}" --write-out '\n%{http_code}' "${DASHBOARD_URL}/api/agent/v1/ping")
|
||||
printf '%s\n' "$antwort"
|
||||
[ "$(printf '%s' "$antwort" | tail -n1)" = "200" ]
|
||||
}
|
||||
|
||||
version_pruefen() {
|
||||
if [ -n "${NEUESTE_VERSION:-}" ] && [ "$NEUESTE_VERSION" != "$AGENT_VERSION" ]; then
|
||||
protokoll notice "Neue Agent-Version verfügbar: ${NEUESTE_VERSION} (installiert: ${AGENT_VERSION})."
|
||||
if [ "$AUTO_UPDATE" = "yes" ]; then
|
||||
aktion_update
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
aktion_update() {
|
||||
konfiguration_pruefen
|
||||
local ziel="/usr/local/bin/proxledger-agent" temp
|
||||
temp=$(mktemp)
|
||||
if curl --silent --show-error --fail --max-time 60 \
|
||||
${CA_CERT:+--cacert "$CA_CERT"} \
|
||||
"${DASHBOARD_URL}/agent.sh" -o "$temp"; then
|
||||
if head -n1 "$temp" | grep -q '^#!/usr/bin/env bash'; then
|
||||
install -m 755 "$temp" "$ziel"
|
||||
rm -f "$temp"
|
||||
protokoll notice "Agent aktualisiert auf $("$ziel" --version)."
|
||||
return 0
|
||||
fi
|
||||
protokoll err "Heruntergeladene Datei ist kein Agent-Skript – Update abgebrochen."
|
||||
else
|
||||
protokoll err "Update fehlgeschlagen: Dashboard nicht erreichbar."
|
||||
fi
|
||||
rm -f "$temp"
|
||||
return 1
|
||||
}
|
||||
|
||||
konfiguration_pruefen() {
|
||||
if [ -z "$DASHBOARD_URL" ] || [ -z "$TOKEN" ]; then
|
||||
protokoll err "Konfiguration unvollständig (${CONFIG_DATEI}). Bitte neu installieren."
|
||||
exit 2
|
||||
fi
|
||||
DASHBOARD_URL=${DASHBOARD_URL%/}
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- Einstieg
|
||||
|
||||
konfiguration_laden
|
||||
|
||||
case "${1:---report}" in
|
||||
--report | "")
|
||||
aktion_report
|
||||
;;
|
||||
--dry-run)
|
||||
report_erzeugen
|
||||
;;
|
||||
--test)
|
||||
aktion_test
|
||||
;;
|
||||
--update)
|
||||
aktion_update
|
||||
;;
|
||||
--version)
|
||||
printf '%s\n' "$AGENT_VERSION"
|
||||
;;
|
||||
--help | -h)
|
||||
sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
|
||||
;;
|
||||
*)
|
||||
printf 'Unbekannte Option: %s\n' "$1" >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user