feat: migrate device registry to PostgreSQL

This commit is contained in:
Thomas Stallinger 2026-07-12 15:22:22 +02:00
parent 8d70236ef5
commit 2683b65ffc
3 changed files with 112 additions and 23 deletions

122
app.py
View File

@ -1,6 +1,10 @@
from pathlib import Path
from datetime import datetime, timezone
import json
import os
from uuid import uuid4
import psycopg
from fastapi import FastAPI, Request
from pydantic import BaseModel
@ -9,7 +13,7 @@ from fastapi.responses import FileResponse
CONFIG_PATH = Path("config.json")
LOG_PATH = Path("activation.log")
DATABASE_URL = os.environ.get("TUXFLOTTE_DATABASE_URL")
app = FastAPI(title="Provisioning Activation Server", version="0.1.0")
@ -30,9 +34,17 @@ def write_log(entry: dict) -> None:
with LOG_PATH.open("a", encoding="utf-8") as f:
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
DEVICES_DIR = Path("data/devices")
def get_database_connection():
if not DATABASE_URL:
raise RuntimeError("TUXFLOTTE_DATABASE_URL ist nicht gesetzt.")
def load_or_create_device(device_fingerprint: str) -> tuple[dict, bool]:
return psycopg.connect(DATABASE_URL)
def load_or_create_device(
organization_id,
device_fingerprint: str,
hostname: str | None,
) -> tuple[dict, bool]:
"""
Lädt ein bekanntes Gerät oder legt es neu an.
@ -40,33 +52,90 @@ def load_or_create_device(device_fingerprint: str) -> tuple[dict, bool]:
(device, created)
"""
DEVICES_DIR.mkdir(parents=True, exist_ok=True)
with get_database_connection() as conn:
with conn.cursor() as cur:
cur.execute(
"""
SELECT
id,
organization_id,
device_fingerprint,
hostname,
created_at,
last_seen
FROM devices
WHERE device_fingerprint = %s
""",
(device_fingerprint,),
)
device_file = DEVICES_DIR / f"{device_fingerprint}.json"
row = cur.fetchone()
if device_file.exists():
with device_file.open("r", encoding="utf-8") as f:
device = json.load(f)
if row is not None:
if str(row[1]) != str(organization_id):
raise RuntimeError(
"Das Gerät ist bereits einer anderen Organization zugeordnet."
)
cur.execute(
"""
UPDATE devices
SET
hostname = %s,
last_seen = CURRENT_TIMESTAMP
WHERE id = %s
RETURNING
id,
organization_id,
device_fingerprint,
hostname,
created_at,
last_seen
""",
(hostname, row[0]),
)
device["last_seen"] = datetime.now(timezone.utc).isoformat()
device["activation_count"] = device.get("activation_count", 0) + 1
row = cur.fetchone()
created = False
else:
device_id = uuid4()
with device_file.open("w", encoding="utf-8") as f:
json.dump(device, f, indent=2)
cur.execute(
"""
INSERT INTO devices (
id,
organization_id,
device_fingerprint,
hostname
)
VALUES (%s, %s, %s, %s)
RETURNING
id,
device_fingerprint,
hostname,
created_at,
last_seen
""",
(
device_id,
organization_id,
device_fingerprint,
hostname,
),
)
return device, False
row = cur.fetchone()
created = True
device = {
"device_fingerprint": device_fingerprint,
"created_at": datetime.now(timezone.utc).isoformat(),
"last_seen": datetime.now(timezone.utc).isoformat(),
"activation_count": 1
"id": str(row[0]),
"organization_id": str(row[1]),
"device_fingerprint": row[1],
"hostname": row[2],
"created_at": row[3].isoformat(),
"last_seen": row[4].isoformat(),
}
with device_file.open("w", encoding="utf-8") as f:
json.dump(device, f, indent=2)
return device, True
return device, created
@app.get("/health")
def health():
@ -87,8 +156,12 @@ def activate(payload: ActivationRequest, request: Request):
device_created = False
if success:
organization_id = activation_entry["customer"]["organization_id"]
device, device_created = load_or_create_device(
payload.device_fingerprint
organization_id,
payload.device_fingerprint,
payload.hostname,
)
write_log({
@ -120,7 +193,10 @@ def activate(payload: ActivationRequest, request: Request):
"success": True,
"device": {
"fingerprint": device["device_fingerprint"],
"id": device["id"],
"fingerprint": device["device_fingerprint"],
"hostname": device["hostname"],
"created": device_created,
},
"customer": activation_entry["customer"],

View File

@ -3,6 +3,7 @@
"LAB-2026-START": {
"customer": {
"id": "default",
"organization_id": "11111111-1111-4111-8111-111111111111",
"name": "Default Lab"
},
"profile_ids": [

View File

@ -0,0 +1,12 @@
BEGIN;
INSERT INTO organizations (
id,
name
)
VALUES (
'11111111-1111-4111-8111-111111111111',
'Default Lab'
);
COMMIT;