Files
infoscreen/server/init_defaults.py
Olaf 03e3c11e90 feat: crash recovery, service_failed monitoring, broker health fields, command expiry sweep
- Add GET /api/clients/crashed endpoint (process_status=crashed or stale heartbeat)
- Add restart_app command action with same lifecycle + lockout as reboot_host
- Scheduler: crash auto-recovery loop (CRASH_RECOVERY_ENABLED flag, lockout, MQTT publish)
- Scheduler: unconditional command expiry sweep per poll cycle (sweep_expired_commands)
- Listener: subscribe to infoscreen/+/service_failed; persist service_failed_at + unit
- Listener: extract broker_connection block from health payload; persist reconnect_count + last_disconnect_at
- DB migration b1c2d3e4f5a6: service_failed_at, service_failed_unit, mqtt_reconnect_count, mqtt_last_disconnect_at on clients
- Add GET /api/clients/service_failed and POST /api/clients/<uuid>/clear_service_failed
- Monitoring overview API: include mqtt_reconnect_count + mqtt_last_disconnect_at per client
- Frontend: orange service-failed alert panel (hidden when empty, auto-refresh, quittieren action)
- Frontend: MQTT reconnect count + last disconnect in client detail panel
- MQTT auth hardening: listener/scheduler/server use env credentials; broker enforces allow_anonymous false
- Client command lifecycle foundation: ClientCommand model, reboot_host/shutdown_host, full ACK lifecycle
- Docs: TECH-CHANGELOG, DEV-CHANGELOG, MQTT_EVENT_PAYLOAD_GUIDE, copilot-instructions updated
- Add implementation-plans/, RESTART_VALIDATION_CHECKLIST.md, TODO.md
2026-04-05 10:17:56 +00:00

89 lines
4.3 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from sqlalchemy import create_engine, textmosquitto.conf
import os
from dotenv import load_dotenv
import bcrypt
# .env laden (nur in Dev)
if os.getenv("ENV", "development") == "development":
load_dotenv()
# Use same logic as database.py: prefer DB_CONN, fallback to individual vars
DB_URL = os.getenv("DB_CONN")
if not DB_URL:
DB_USER = os.getenv("DB_USER", "infoscreen_admin")
DB_PASSWORD = os.getenv("DB_PASSWORD")
# In Docker Compose: DB_HOST will be 'db' from env
# In dev container: will be 'localhost' from .env
DB_HOST = os.getenv("DB_HOST", "db") # Default to 'db' for Docker Compose
DB_NAME = os.getenv("DB_NAME", "infoscreen_by_taa")
DB_URL = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:3306/{DB_NAME}"
print(f"init_defaults.py connecting to: {DB_URL.split('@')[1] if '@' in DB_URL else DB_URL}")
engine = create_engine(DB_URL, isolation_level="AUTOCOMMIT")
with engine.connect() as conn:
# Default-Gruppe mit id=1 anlegen, falls nicht vorhanden
result = conn.execute(
text("SELECT COUNT(*) FROM client_groups WHERE id=1"))
if result.scalar() == 0:
conn.execute(
text(
"INSERT INTO client_groups (id, name, is_active) VALUES (1, 'Nicht zugeordnet', 1)")
)
print("✅ Default-Gruppe mit id=1 angelegt.")
# Superadmin-Benutzer anlegen, falls nicht vorhanden
admin_user = os.getenv("DEFAULT_SUPERADMIN_USERNAME", "superadmin")
admin_pw = os.getenv("DEFAULT_SUPERADMIN_PASSWORD")
if not admin_pw:
print("⚠️ DEFAULT_SUPERADMIN_PASSWORD nicht gesetzt. Superadmin wird nicht erstellt.")
else:
# Passwort hashen mit bcrypt
hashed_pw = bcrypt.hashpw(admin_pw.encode(
'utf-8'), bcrypt.gensalt()).decode('utf-8')
# Prüfen, ob User existiert
result = conn.execute(text(
"SELECT COUNT(*) FROM users WHERE username=:username"), {"username": admin_user})
if result.scalar() == 0:
# Rolle: 'superadmin' gemäß UserRole enum
conn.execute(
text("INSERT INTO users (username, password_hash, role, is_active) VALUES (:username, :password_hash, 'superadmin', 1)"),
{"username": admin_user, "password_hash": hashed_pw}
)
print(f"✅ Superadmin-Benutzer '{admin_user}' angelegt.")
else:
print(f" Superadmin-Benutzer '{admin_user}' existiert bereits.")
# Default System Settings anlegen
default_settings = [
('supplement_table_url', '', 'URL für Vertretungsplan / WebUntis (Stundenplan-Änderungstabelle)'),
('supplement_table_enabled', 'false', 'Ob Vertretungsplan aktiviert ist'),
('presentation_interval', '10', 'Standard Intervall für Präsentationen (Sekunden)'),
('presentation_page_progress', 'true', 'Seitenfortschrift anzeigen (Page-Progress) für Präsentationen'),
('presentation_auto_progress', 'true', 'Automatischer Fortschritt (Auto-Progress) für Präsentationen'),
('video_autoplay', 'true', 'Autoplay (automatisches Abspielen) für Videos'),
('video_loop', 'true', 'Loop (Wiederholung) für Videos'),
('video_volume', '0.8', 'Standard Lautstärke für Videos (0.0 - 1.0)'),
('holiday_banner_enabled', 'true', 'Ferienstatus-Banner auf Dashboard anzeigen'),
('organization_name', '', 'Name der Organisation (wird im Header angezeigt)'),
('refresh_seconds', '0', 'Scheduler Republish-Intervall (Sekunden; 0 deaktiviert)'),
('group_order', '[]', 'Benutzerdefinierte Reihenfolge der Raumgruppen (JSON-Array mit Group-IDs)'),
]
for key, value, description in default_settings:
result = conn.execute(
text("SELECT COUNT(*) FROM system_settings WHERE `key`=:key"),
{"key": key}
)
if result.scalar() == 0:
conn.execute(
text("INSERT INTO system_settings (`key`, value, description) VALUES (:key, :value, :description)"),
{"key": key, "value": value, "description": description}
)
print(f"✅ System-Einstellung '{key}' angelegt.")
else:
print(f" System-Einstellung '{key}' existiert bereits.")
print("✅ Initialisierung abgeschlossen.")