devices: Installations-Meilensteine/Fehlschlaege melden (device_installation_events)
Neue Tabelle (Migration 0025) + POST /api/v1/devices/{device_id}/
installation-events (device_fingerprint-authentifiziert statt Service-
Token - das Geraet hat waehrend der Installation noch kein agent_secret,
gleiches Vertrauensniveau wie /api/v1/activate) + GET-Gegenstueck
(Service-Token, fuer die Kundenplattform).
Nutzer-Wunsch (01.09.2026, nach dem ersten echten Hardware-Test):
Fehlschlaege waehrend der Installation sichtbar machen, ohne am
Bildschirm mitschreiben zu muessen ("das ist kein Flow"). Bewusst
getrennt von device_merkmal_events (andere Lebensphase, andere Auth,
kein Merkmal-Bezug) und zunaechst nur admin-/operatorseitig ausgewertet -
ob/wie umfangreich der Kunde das in seiner eigenen GUI sieht, ist noch
offen (Nutzer: "das können wir noch wann anders diskutieren").
This commit is contained in:
parent
fb87a2377a
commit
51fbf0fa0e
99
app.py
99
app.py
@ -87,6 +87,19 @@ class ResolveRequest(BaseModel):
|
|||||||
device_id: str
|
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):
|
class AgentBootstrapRequest(BaseModel):
|
||||||
device_id: str
|
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()
|
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:
|
def deprovision_device(device_id: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Serverseitiger Entzug (siehe Kundenportal-Geräteaktionen-Plan): macht das
|
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)}
|
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")
|
@app.post("/api/v1/devices/{device_id}/deprovision")
|
||||||
def deprovision(device_id: str, authorization: str | None = Header(default=None)):
|
def deprovision(device_id: str, authorization: str | None = Header(default=None)):
|
||||||
if not require_service_token(authorization):
|
if not require_service_token(authorization):
|
||||||
|
|||||||
25
migrations/0025_device_installation_events.sql
Normal file
25
migrations/0025_device_installation_events.sql
Normal file
@ -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;
|
||||||
Loading…
x
Reference in New Issue
Block a user