Files
tesm/srv/poe_manager/create_db.py
T
alientimandClaude Sonnet 5 c3d69a9870 DHCP: Backend auf Kea umgestellt, eigene Options (global + pro Client), echte Install-/Dienststeuerung
- Backend-Wechsel von isc-dhcp-server (seit 2022 EOL) auf Kea DHCP
  (kea-dhcp4-server) — aktiv weiterentwickelt, bildet "globaler Wert, pro
  Client überschreibbar" nativ über Kea-Reservation-Option-Data ab.
- Neue Tabellen dhcp_option_defs/dhcp_option_values: eigene/herstellerspezifische
  Options mit Code/Name/Typ/Beschreibung, Wert global sowie optional pro
  Client (device_mac="" für global, siehe Kommentar zur SQLite-NULL-
  UNIQUE-Falle). UI dafür: Optionen-Tabelle + "Neue Option"-Modal +
  Pro-Client-Overrides-Modal je Reservierung.
- Kea-JSON-Generator (_render_kea_config): option-def für jede eigene
  Option, globale Werte im Top-Level option-data, Client-Overrides im
  option-data der jeweiligen Reservierung.
- Reservierungen werden jetzt zusätzlich auf Zugehörigkeit zum
  konfigurierten Subnet gefiltert (_dhcp_reservation_candidates) — beim
  Live-Test gegen echtes Kea gefunden: Kea lehnt Reservierungen außerhalb
  ihres Subnets als Konfigurationsfehler ab, das muss also schon bei der
  Generierung berücksichtigt werden statt erst beim Laden zu crashen.
- Neue, einzeln bestätigte Aktionen: "kea-dhcp4-server installieren"
  (apt-get), "Aktivieren & (neu) starten" sowie "Stoppen"
  (systemctl) — bewusst getrennt von "Konfiguration speichern"/"In
  Datei schreiben".
- Live gegen eine echte, frisch installierte Kea-3.0.3-Instanz verifiziert:
  Installation erfolgreich, generierte Config besteht "kea-dhcp4 -t",
  Dienst übernimmt sie beim Neustart fehlerfrei (DHCP4_CONFIG_COMPLETE),
  globaler Options-Wert und Client-Override erscheinen korrekt getrennt.
  Danach wieder gestoppt; Paket bleibt installiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 21:47:54 +02:00

174 lines
5.4 KiB
Python

#!/usr/bin/env python3
"""Initialisiert die SQLite-Datenbank für den PoE Manager."""
import os
import sqlite3
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.environ.get("POE_DB_PATH", os.path.join(BASE_DIR, "sqlite.db"))
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
# Zugangsdaten (wiederverwendbare SSH-Logins, mehrere Switche können sich
# dieselben teilen; Passwort wird verschlüsselt gespeichert)
c.execute("""
CREATE TABLE IF NOT EXISTS credentials (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL
);
""")
# Switches (Aruba-Switche) — referenzieren ihre Zugangsdaten statt sie
# direkt zu speichern
c.execute("""
CREATE TABLE IF NOT EXISTS switches (
hostname TEXT PRIMARY KEY,
ip TEXT NOT NULL,
ssh_port INTEGER,
credential_id INTEGER,
last_modified_by TEXT,
last_modified_at TEXT,
FOREIGN KEY (credential_id) REFERENCES credentials(id)
);
""")
# Devices
c.execute("""
CREATE TABLE IF NOT EXISTS devices (
mac TEXT PRIMARY KEY,
rpi_ip TEXT NOT NULL,
switch_hostname TEXT,
port TEXT,
name TEXT NOT NULL,
is_active INTEGER DEFAULT 1,
last_modified_by TEXT,
last_modified_at TEXT,
FOREIGN KEY (switch_hostname) REFERENCES switches(hostname)
);
""")
# Benutzer
c.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
is_admin INTEGER DEFAULT 0,
first_name TEXT,
last_name TEXT,
avatar_filename TEXT
);
""")
# Gruppen (Rechtesystem) — Admins besitzen ohnehin alle Rechte; Gruppen
# dienen dazu, normalen Benutzern gezielt einzelne Verwaltungsrechte für
# Devices/Switches zu geben, ohne sie zu Admins zu machen.
c.execute("""
CREATE TABLE IF NOT EXISTS groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
is_default INTEGER DEFAULT 0,
is_system INTEGER DEFAULT 0
);
""")
# Welche Berechtigungen eine Gruppe gewährt (siehe app.py: PERMISSIONS)
c.execute("""
CREATE TABLE IF NOT EXISTS group_permissions (
group_id INTEGER NOT NULL,
permission TEXT NOT NULL,
PRIMARY KEY (group_id, permission),
FOREIGN KEY (group_id) REFERENCES groups(id)
);
""")
# Gruppenmitgliedschaft (ein Benutzer kann in mehreren Gruppen sein;
# effektive Rechte = Vereinigung aller Gruppen)
c.execute("""
CREATE TABLE IF NOT EXISTS user_groups (
user_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
PRIMARY KEY (user_id, group_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (group_id) REFERENCES groups(id)
);
""")
# Settings (z.B. Prüfintervall)
c.execute("""
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
""")
# DHCP: eigene Options-Definitionen (analog "Vordefinierte Optionen und
# Werte" bei einem Windows-DHCP-Server) — Code/Name/Typ/Beschreibung.
c.execute("""
CREATE TABLE IF NOT EXISTS dhcp_option_defs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code INTEGER NOT NULL UNIQUE,
name TEXT NOT NULL UNIQUE,
type TEXT NOT NULL,
description TEXT
);
""")
# DHCP: Werte je Option — device_mac='' bedeutet global (gilt für den
# ganzen Scope), ein gesetzter device_mac überschreibt den globalen Wert
# für genau dieses Gerät. Bewusst '' statt NULL für den globalen Fall,
# da SQLite NULL in UNIQUE-Constraints nicht als gleich behandelt (zwei
# NULL-Zeilen wären sonst kein Konflikt) — mit '' funktioniert ON CONFLICT
# zuverlässig für den Upsert des globalen Werts.
c.execute("""
CREATE TABLE IF NOT EXISTS dhcp_option_values (
id INTEGER PRIMARY KEY AUTOINCREMENT,
option_def_id INTEGER NOT NULL,
device_mac TEXT NOT NULL DEFAULT '',
value TEXT NOT NULL,
FOREIGN KEY (option_def_id) REFERENCES dhcp_option_defs(id),
UNIQUE (option_def_id, device_mac)
);
""")
# Änderungslog (Audit-Log) — wer hat wann was geändert. Bewusst OHNE
# PoE-Neustarts (die stehen im Live-Log von poe.sh).
c.execute("""
CREATE TABLE IF NOT EXISTS audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL,
username TEXT NOT NULL,
action TEXT NOT NULL,
target TEXT,
details TEXT
);
""")
# Standard-Setting: Prüfintervall 5 Minuten (interval = Minuten, check_interval = Sekunden)
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("interval", "5"))
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("check_interval", "300"))
# Standardgruppe 'Benutzer' — Systemgruppe (is_system), Name und Rechte
# sind über die UI fest und nicht änderbar. Jeder neu angelegte (nicht-admin)
# Benutzer landet automatisch darin (siehe app.py: users()). Der genaue
# Rechtesatz muss mit DEFAULT_GROUP_PERMISSIONS in app.py übereinstimmen.
cur = c.execute("INSERT OR IGNORE INTO groups (name, is_default, is_system) VALUES (?, 1, 1)", ("Benutzer",))
if cur.rowcount > 0:
default_group_id = cur.lastrowid
c.executemany(
"INSERT OR IGNORE INTO group_permissions (group_id, permission) VALUES (?, ?)",
[
(default_group_id, "devices_group.view"),
(default_group_id, "devices.view"),
(default_group_id, "switches.view"),
(default_group_id, "logs_group.view"),
(default_group_id, "logs_live.view"),
],
)
conn.commit()
conn.close()
print(f"Datenbank '{DB_PATH}' wurde initialisiert inklusive Settings.")