diff --git a/backends/mint-image/backend.sh b/backends/mint-image/backend.sh index 64d339b..1060c5d 100644 --- a/backends/mint-image/backend.sh +++ b/backends/mint-image/backend.sh @@ -22,6 +22,7 @@ REPO_DIR="$(cd "${BACKEND_DIR}/../.." && pwd)" POSTINSTALL_SCRIPT="${BACKEND_DIR}/postinstall.sh" IMAGE_DEPLOY_LIB="${REPO_DIR}/scripts/lib/image_deploy.sh" BOOT_MEDIUM_LIB="${REPO_DIR}/scripts/lib/boot_medium.sh" +REPORTING_LIB="${REPO_DIR}/scripts/lib/reporting.sh" # Vom Netzwerkmodul geschuetzt abgelegtes WLAN-/Ethernet-Verbindungsprofil # (siehe export_connection_profile() in 05_network.sh) - existiert nur, @@ -108,6 +109,11 @@ backend_init() { # shellcheck source=../../scripts/lib/boot_medium.sh source "${BOOT_MEDIUM_LIB}" + [[ -r "${REPORTING_LIB}" ]] || + { backend_fatal "Reporting-Bibliothek nicht gefunden: ${REPORTING_LIB}"; return 1; } + # shellcheck source=../../scripts/lib/reporting.sh + source "${REPORTING_LIB}" + [[ -r "${POSTINSTALL_SCRIPT}" ]] || { backend_fatal "Postinstall-Skript nicht gefunden: ${POSTINSTALL_SCRIPT}"; return 1; } @@ -334,6 +340,8 @@ backend_postinstall() { umount --recursive "${TARGET_DIR}" || { backend_fatal "${TARGET_DIR} konnte nicht ausgehängt werden."; return 1; } + tuxflotte_report_installation_event "installation_completed" + # Nutzer-Feedback (01.09.2026, echter Hardware-Test): akustischer Hinweis # + klare Ansage, dass der USB-Stick jetzt gefahrlos gezogen werden kann # (ab hier wird nur noch von der Zielplatte gebootet) - Bestaetigung mit diff --git a/boot-medium/config/includes.chroot/opt/tuxflotte/backends/mint-image/backend.sh b/boot-medium/config/includes.chroot/opt/tuxflotte/backends/mint-image/backend.sh index 64d339b..1060c5d 100644 --- a/boot-medium/config/includes.chroot/opt/tuxflotte/backends/mint-image/backend.sh +++ b/boot-medium/config/includes.chroot/opt/tuxflotte/backends/mint-image/backend.sh @@ -22,6 +22,7 @@ REPO_DIR="$(cd "${BACKEND_DIR}/../.." && pwd)" POSTINSTALL_SCRIPT="${BACKEND_DIR}/postinstall.sh" IMAGE_DEPLOY_LIB="${REPO_DIR}/scripts/lib/image_deploy.sh" BOOT_MEDIUM_LIB="${REPO_DIR}/scripts/lib/boot_medium.sh" +REPORTING_LIB="${REPO_DIR}/scripts/lib/reporting.sh" # Vom Netzwerkmodul geschuetzt abgelegtes WLAN-/Ethernet-Verbindungsprofil # (siehe export_connection_profile() in 05_network.sh) - existiert nur, @@ -108,6 +109,11 @@ backend_init() { # shellcheck source=../../scripts/lib/boot_medium.sh source "${BOOT_MEDIUM_LIB}" + [[ -r "${REPORTING_LIB}" ]] || + { backend_fatal "Reporting-Bibliothek nicht gefunden: ${REPORTING_LIB}"; return 1; } + # shellcheck source=../../scripts/lib/reporting.sh + source "${REPORTING_LIB}" + [[ -r "${POSTINSTALL_SCRIPT}" ]] || { backend_fatal "Postinstall-Skript nicht gefunden: ${POSTINSTALL_SCRIPT}"; return 1; } @@ -334,6 +340,8 @@ backend_postinstall() { umount --recursive "${TARGET_DIR}" || { backend_fatal "${TARGET_DIR} konnte nicht ausgehängt werden."; return 1; } + tuxflotte_report_installation_event "installation_completed" + # Nutzer-Feedback (01.09.2026, echter Hardware-Test): akustischer Hinweis # + klare Ansage, dass der USB-Stick jetzt gefahrlos gezogen werden kann # (ab hier wird nur noch von der Zielplatte gebootet) - Bestaetigung mit diff --git a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/boot-autostart.sh b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/boot-autostart.sh index 3870e9f..78e942c 100755 --- a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/boot-autostart.sh +++ b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/boot-autostart.sh @@ -44,6 +44,19 @@ export TUXFLOTTE_LOG_FILE="${LOG_FILE}" /opt/tuxflotte/scripts/installer.sh 2>&1 | tee -a "${LOG_FILE}" STATUS="${PIPESTATUS[0]}" +# Nutzer-Wunsch (01.09.2026): Fehlschlaege sollen sich melden, ohne dass am +# Bildschirm mitgeschrieben werden muss ("Gold wert" fuer die eigene +# Fehlersuche). Nur bei echtem Fehlschlag (STATUS != 0) - ein kontrollierter +# Abbruch an einem Commit-Gate (Nutzer sagt "N") beendet installer.sh +# ebenfalls, aber mit "exit 0", das ist kein Fehler. Detail = die letzten +# Logzeilen (enthalten bereits die eigentliche [FEHLER]-Meldung des +# gescheiterten Moduls) - best effort, siehe reporting.sh. +if [[ "${STATUS}" -ne 0 ]]; then + # shellcheck source=lib/reporting.sh + source /opt/tuxflotte/scripts/lib/reporting.sh + tuxflotte_report_installation_event "installation_failed" "$(tail -n 40 "${LOG_FILE}" 2>/dev/null)" +fi + # Live gefunden (31.08.2026): ein ExecStopPost, der beim Dienstende # getty@tty1.service neu startet, ist unzuverlaessig (Race mit dem eigenen # TTY-Teardown dieses Dienstes - der Job wurde angestossen, blieb aber diff --git a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/installer.sh b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/installer.sh index e4ed7d7..1b6d590 100755 --- a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/installer.sh +++ b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/installer.sh @@ -8,6 +8,7 @@ source "$SCRIPT_DIR/lib/logging.sh" source "$SCRIPT_DIR/lib/errors.sh" source "$SCRIPT_DIR/lib/utils.sh" source "$SCRIPT_DIR/lib/checks.sh" +source "$SCRIPT_DIR/lib/reporting.sh" DRY_RUN=false @@ -65,6 +66,7 @@ source "$INSTALLATION_STATE_FILE" case "${TUXFLOTTE_INSTALLATION_CONFIRMED:-}" in true) log_info "Commit Point bestätigt. Installationsphase wird fortgesetzt." + tuxflotte_report_installation_event "installation_started" ;; false) log_info "Provisionierung wurde durch den Benutzer kontrolliert beendet." diff --git a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/lib/reporting.sh b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/lib/reporting.sh new file mode 100644 index 0000000..7b7e700 --- /dev/null +++ b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/lib/reporting.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +# Meldet Installations-Meilensteine/Fehlschlaege an anode (device_installation_events, +# siehe Migration 0025 in provisioning-server) - ausschliesslich fuer die eigene +# Fehlersuche/Betriebsentwicklung gedacht ("Gold wert" fuer den Support, Nutzer-Wunsch +# 01.09.2026 nach dem ersten echten Hardware-Test), noch NICHT zwangslaeufig auf der +# Kunden-Oberflaeche sichtbar - das ist eine separate, noch offene Entscheidung. +# +# Rein melde-basiert und best effort: das Geraet hat waehrend der Installation noch +# kein agent_secret (das entsteht erst ganz am Ende in backend_postinstall()), deshalb +# device_fingerprint als Nachweis statt eines Service-Tokens (siehe +# InstallationEventRequest/device_fingerprint_matches() serverseitig - gleiches +# Vertrauensniveau wie /api/v1/activate selbst). Ein Fehlschlag beim Melden selbst +# (Netzwerk gerade weg, Server nicht erreichbar, Geraet noch nicht aktiviert) darf die +# eigentliche Installation NIE aufhalten - deshalb "|| true" ueberall, kein "set -e"- +# Abbruch moeglich, keine Rueckgabewert-Pruefung durch die Aufrufer noetig. +tuxflotte_report_installation_event() { + local event_type="$1" + local detail="${2:-}" + + local response_file="/run/tuxflotte/server/response.json" + local hardware_file="/run/tuxflotte/hardware/hardware.json" + local network_state="/run/tuxflotte/network/state.env" + + [[ -r "${response_file}" && -r "${hardware_file}" && -r "${network_state}" ]] || return 0 + + local device_id device_fingerprint server_url payload + device_id="$(jq --raw-output '.device.id // empty' "${response_file}" 2>/dev/null)" + device_fingerprint="$(jq --raw-output '.identity.device_fingerprint // empty' "${hardware_file}" 2>/dev/null)" + + [[ -n "${device_id}" && -n "${device_fingerprint}" ]] || return 0 + + # shellcheck disable=SC1090 + source "${network_state}" 2>/dev/null || return 0 + server_url="${TUXFLOTTE_SERVER_URL%/health}" + [[ -n "${server_url}" ]] || return 0 + + payload="$( + jq --null-input \ + --arg device_fingerprint "${device_fingerprint}" \ + --arg event_type "${event_type}" \ + --arg detail "${detail}" \ + '{ + device_fingerprint: $device_fingerprint, + event_type: $event_type, + detail: (if $detail == "" then null else $detail end) + }' 2>/dev/null + )" + [[ -n "${payload}" ]] || return 0 + + curl \ + --silent \ + --show-error \ + --fail \ + --location \ + --max-time 5 \ + --header 'Content-Type: application/json' \ + --data-binary "${payload}" \ + "${server_url}/api/v1/devices/${device_id}/installation-events" \ + >/dev/null 2>&1 || true +} diff --git a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/modules/15_server_handshake.sh b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/modules/15_server_handshake.sh index ed8bbd0..00a54fe 100755 --- a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/modules/15_server_handshake.sh +++ b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/modules/15_server_handshake.sh @@ -4,6 +4,11 @@ set -Eeuo pipefail readonly SCRIPT_NAME="${0##*/}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly SCRIPT_DIR +# shellcheck source=../lib/reporting.sh +source "${SCRIPT_DIR}/../lib/reporting.sh" + readonly NETWORK_STATE="/run/tuxflotte/network/state.env" readonly HARDWARE_FILE="/run/tuxflotte/hardware/hardware.json" @@ -121,6 +126,7 @@ send_activation_request() { fatal "Provisioning-Server hat die Aktivierung abgelehnt." log "Provisioning-Handshake erfolgreich abgeschlossen." + tuxflotte_report_installation_event "activation_confirmed" } main() { diff --git a/scripts/boot-autostart.sh b/scripts/boot-autostart.sh index 3870e9f..78e942c 100755 --- a/scripts/boot-autostart.sh +++ b/scripts/boot-autostart.sh @@ -44,6 +44,19 @@ export TUXFLOTTE_LOG_FILE="${LOG_FILE}" /opt/tuxflotte/scripts/installer.sh 2>&1 | tee -a "${LOG_FILE}" STATUS="${PIPESTATUS[0]}" +# Nutzer-Wunsch (01.09.2026): Fehlschlaege sollen sich melden, ohne dass am +# Bildschirm mitgeschrieben werden muss ("Gold wert" fuer die eigene +# Fehlersuche). Nur bei echtem Fehlschlag (STATUS != 0) - ein kontrollierter +# Abbruch an einem Commit-Gate (Nutzer sagt "N") beendet installer.sh +# ebenfalls, aber mit "exit 0", das ist kein Fehler. Detail = die letzten +# Logzeilen (enthalten bereits die eigentliche [FEHLER]-Meldung des +# gescheiterten Moduls) - best effort, siehe reporting.sh. +if [[ "${STATUS}" -ne 0 ]]; then + # shellcheck source=lib/reporting.sh + source /opt/tuxflotte/scripts/lib/reporting.sh + tuxflotte_report_installation_event "installation_failed" "$(tail -n 40 "${LOG_FILE}" 2>/dev/null)" +fi + # Live gefunden (31.08.2026): ein ExecStopPost, der beim Dienstende # getty@tty1.service neu startet, ist unzuverlaessig (Race mit dem eigenen # TTY-Teardown dieses Dienstes - der Job wurde angestossen, blieb aber diff --git a/scripts/installer.sh b/scripts/installer.sh index e4ed7d7..1b6d590 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -8,6 +8,7 @@ source "$SCRIPT_DIR/lib/logging.sh" source "$SCRIPT_DIR/lib/errors.sh" source "$SCRIPT_DIR/lib/utils.sh" source "$SCRIPT_DIR/lib/checks.sh" +source "$SCRIPT_DIR/lib/reporting.sh" DRY_RUN=false @@ -65,6 +66,7 @@ source "$INSTALLATION_STATE_FILE" case "${TUXFLOTTE_INSTALLATION_CONFIRMED:-}" in true) log_info "Commit Point bestätigt. Installationsphase wird fortgesetzt." + tuxflotte_report_installation_event "installation_started" ;; false) log_info "Provisionierung wurde durch den Benutzer kontrolliert beendet." diff --git a/scripts/lib/reporting.sh b/scripts/lib/reporting.sh new file mode 100644 index 0000000..7b7e700 --- /dev/null +++ b/scripts/lib/reporting.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +# Meldet Installations-Meilensteine/Fehlschlaege an anode (device_installation_events, +# siehe Migration 0025 in provisioning-server) - ausschliesslich fuer die eigene +# Fehlersuche/Betriebsentwicklung gedacht ("Gold wert" fuer den Support, Nutzer-Wunsch +# 01.09.2026 nach dem ersten echten Hardware-Test), noch NICHT zwangslaeufig auf der +# Kunden-Oberflaeche sichtbar - das ist eine separate, noch offene Entscheidung. +# +# Rein melde-basiert und best effort: das Geraet hat waehrend der Installation noch +# kein agent_secret (das entsteht erst ganz am Ende in backend_postinstall()), deshalb +# device_fingerprint als Nachweis statt eines Service-Tokens (siehe +# InstallationEventRequest/device_fingerprint_matches() serverseitig - gleiches +# Vertrauensniveau wie /api/v1/activate selbst). Ein Fehlschlag beim Melden selbst +# (Netzwerk gerade weg, Server nicht erreichbar, Geraet noch nicht aktiviert) darf die +# eigentliche Installation NIE aufhalten - deshalb "|| true" ueberall, kein "set -e"- +# Abbruch moeglich, keine Rueckgabewert-Pruefung durch die Aufrufer noetig. +tuxflotte_report_installation_event() { + local event_type="$1" + local detail="${2:-}" + + local response_file="/run/tuxflotte/server/response.json" + local hardware_file="/run/tuxflotte/hardware/hardware.json" + local network_state="/run/tuxflotte/network/state.env" + + [[ -r "${response_file}" && -r "${hardware_file}" && -r "${network_state}" ]] || return 0 + + local device_id device_fingerprint server_url payload + device_id="$(jq --raw-output '.device.id // empty' "${response_file}" 2>/dev/null)" + device_fingerprint="$(jq --raw-output '.identity.device_fingerprint // empty' "${hardware_file}" 2>/dev/null)" + + [[ -n "${device_id}" && -n "${device_fingerprint}" ]] || return 0 + + # shellcheck disable=SC1090 + source "${network_state}" 2>/dev/null || return 0 + server_url="${TUXFLOTTE_SERVER_URL%/health}" + [[ -n "${server_url}" ]] || return 0 + + payload="$( + jq --null-input \ + --arg device_fingerprint "${device_fingerprint}" \ + --arg event_type "${event_type}" \ + --arg detail "${detail}" \ + '{ + device_fingerprint: $device_fingerprint, + event_type: $event_type, + detail: (if $detail == "" then null else $detail end) + }' 2>/dev/null + )" + [[ -n "${payload}" ]] || return 0 + + curl \ + --silent \ + --show-error \ + --fail \ + --location \ + --max-time 5 \ + --header 'Content-Type: application/json' \ + --data-binary "${payload}" \ + "${server_url}/api/v1/devices/${device_id}/installation-events" \ + >/dev/null 2>&1 || true +} diff --git a/scripts/modules/15_server_handshake.sh b/scripts/modules/15_server_handshake.sh index ed8bbd0..00a54fe 100755 --- a/scripts/modules/15_server_handshake.sh +++ b/scripts/modules/15_server_handshake.sh @@ -4,6 +4,11 @@ set -Eeuo pipefail readonly SCRIPT_NAME="${0##*/}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly SCRIPT_DIR +# shellcheck source=../lib/reporting.sh +source "${SCRIPT_DIR}/../lib/reporting.sh" + readonly NETWORK_STATE="/run/tuxflotte/network/state.env" readonly HARDWARE_FILE="/run/tuxflotte/hardware/hardware.json" @@ -121,6 +126,7 @@ send_activation_request() { fatal "Provisioning-Server hat die Aktivierung abgelehnt." log "Provisioning-Handshake erfolgreich abgeschlossen." + tuxflotte_report_installation_event "activation_confirmed" } main() {