diff --git a/app.py b/app.py index f9fe57c..e940390 100644 --- a/app.py +++ b/app.py @@ -87,6 +87,19 @@ class ResolveRequest(BaseModel): device_id: str +class InstallationEventRequest(BaseModel): + """ + Meilenstein/Fehlschlag-Meldung vom Boot-Medium waehrend der Installation + (siehe device_installation_events, Migration 0025) - das Geraet hat zu + diesem Zeitpunkt noch kein agent_secret, deshalb device_fingerprint als + Nachweis statt eines Service-Tokens (gleiches Vertrauensniveau wie + /api/v1/activate selbst). + """ + device_fingerprint: str + event_type: str + detail: str | None = None + + class AgentBootstrapRequest(BaseModel): device_id: str @@ -1617,6 +1630,55 @@ def fetch_device_events(device_id: str): for occurred_at, merkmal_key, merkmal_name, event_type, detail in cur.fetchall() ] +def device_fingerprint_matches(device_id: str, device_fingerprint: str) -> bool: + with get_database_connection() as conn: + with conn.cursor() as cur: + cur.execute( + "SELECT device_fingerprint FROM devices WHERE id = %s", + (device_id,), + ) + row = cur.fetchone() + + return row is not None and row[0] == device_fingerprint + +def record_installation_event(device_id: str, event_type: str, detail: str | None) -> None: + with get_database_connection() as conn: + with conn.cursor() as cur: + cur.execute( + """ + INSERT INTO device_installation_events (id, device_id, event_type, detail) + VALUES (%s, %s, %s, %s) + """, + (uuid4(), device_id, event_type, detail), + ) + +def fetch_installation_events(device_id: str): + """ + Meilensteine/Fehlschlaege waehrend der Installation (siehe + device_installation_events, Migration 0025) - separat von + fetch_device_events() (Merkmal-Anwendungen), neueste zuerst. + """ + + with get_database_connection() as conn: + with conn.cursor() as cur: + cur.execute( + """ + SELECT occurred_at, event_type, detail + FROM device_installation_events + WHERE device_id = %s + ORDER BY occurred_at DESC + """, + (device_id,), + ) + return [ + { + "occurred_at": occurred_at.isoformat(), + "event_type": event_type, + "detail": detail, + } + for occurred_at, event_type, detail in cur.fetchall() + ] + def deprovision_device(device_id: str) -> bool: """ Serverseitiger Entzug (siehe Kundenportal-Geräteaktionen-Plan): macht das @@ -2866,6 +2928,43 @@ def get_device_events(device_id: str, authorization: str | None = Header(default return {"success": True, "events": fetch_device_events(device_id)} +@app.post("/api/v1/devices/{device_id}/installation-events") +def post_installation_event(device_id: str, payload: InstallationEventRequest): + """ + Vom Boot-Medium selbst waehrend der Installation aufgerufen (siehe + scripts/lib/reporting.sh in tuxflotte-installer) - das Geraet hat zu + diesem Zeitpunkt noch kein agent_secret, deshalb kein Service-Token- + Zwang wie bei den uebrigen Endpunkten. device_fingerprint dient als + Nachweis, gleiches Vertrauensniveau wie /api/v1/activate selbst - + bewusst kein neues Token-System fuer einen reinen Diagnose-Kanal. + Rein melde-basiert (best effort auf Aufruferseite) - ein Fehlschlag + hier darf die eigentliche Installation nie aufhalten. + """ + + if not device_fingerprint_matches(device_id, payload.device_fingerprint): + return { + "success": False, + "error": "unauthorized", + "message": "Geräte-Kennung/Fingerprint stimmen nicht überein.", + } + + record_installation_event(device_id, payload.event_type, payload.detail) + + return {"success": True} + + +@app.get("/api/v1/devices/{device_id}/installation-events") +def get_installation_events(device_id: str, authorization: str | None = Header(default=None)): + if not require_service_token(authorization): + return { + "success": False, + "error": "unauthorized", + "message": "Fehlendes oder ungültiges Service-Token.", + } + + return {"success": True, "events": fetch_installation_events(device_id)} + + @app.post("/api/v1/devices/{device_id}/deprovision") def deprovision(device_id: str, authorization: str | None = Header(default=None)): if not require_service_token(authorization): diff --git a/migrations/0025_device_installation_events.sql b/migrations/0025_device_installation_events.sql new file mode 100644 index 0000000..2b769f6 --- /dev/null +++ b/migrations/0025_device_installation_events.sql @@ -0,0 +1,25 @@ +BEGIN; + +-- Installations-Meilensteine/Fehlschlaege vom Boot-Medium selbst gemeldet - +-- andere Lebensphase als device_merkmal_events (laeuft schon waehrend der +-- Installation, bevor das Geraet ein agent_secret hat) und andere Datenform +-- (kein merkmal_id-Bezug), deshalb eigene Tabelle statt Wiederverwendung. +-- Nutzer-Wunsch (01.09.2026, nach dem ersten echten Hardware-Test): +-- Fehlschlaege waehrend der Installation sichtbar machen, ohne am +-- Bildschirm mitschreiben/-fotografieren zu muessen. Bewusst zunaechst nur +-- admin-/operatorseitig ausgewertet (siehe kundenplattform) - ob und wie +-- umfangreich das dem Kunden selbst gezeigt wird, ist noch offen. +CREATE TABLE device_installation_events ( + id UUID PRIMARY KEY, + device_id UUID NOT NULL + REFERENCES devices(id) + ON DELETE CASCADE, + event_type TEXT NOT NULL, + detail TEXT, + occurred_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_device_installation_events_device_id + ON device_installation_events(device_id); + +COMMIT;