create_db.py/create_admin.py: frisches Setup auf den vollständig migrierten Schema-Stand bringen

Ein wirklich frisches Setup (create_db.py auf einer neuen Installation)
erzeugte bislang nur den ALLERERSTEN Schema-Stand aus der Projektgeschichte
-- alle seither per _ensure_schema()-Migration nachgerüsteten Spalten und
Tabellen fehlten komplett, bis diese Migration beim ersten App-Start
erfolgreich durchläuft. Live auf einem frisch installierten Produktivsystem
ist genau das nicht passiert: "sqlite3.OperationalError: no such column:
deleted_at" beim Dashboard-Aufruf, weil devices/switches/credentials/
users/groups ohne die Papierkorb-Spalte angelegt wurden. Fehlten außerdem
komplett: dhcp_subnets, dhcp_manual_reservations, service_accounts,
ldap_group_mappings (LDAP/DHCP-Funktionen wären auf einer solchen frischen
Installation gar nicht nutzbar gewesen) sowie dhcp_option_defs.is_standard.

- create_db.py jetzt deckungsgleich mit dem tatsächlichen, vollständig
  migrierten Schema (per .schema-Dump von einer produktiv laufenden Instanz
  verifiziert) -- category/deleted_at auf credentials, deleted_at auf
  switches, credential_id/ssh_port/deleted_at auf devices, deleted_at auf
  users/groups, is_standard auf dhcp_option_defs, plus die vier fehlenden
  Tabellen.
- POE_DB_PATH → TESM_DB_PATH in create_db.py UND create_admin.py (weiteres
  übersehenes Namensrelikt aus der TESM-Umbenennung), Docstring von
  create_admin.py aktualisiert.
- Live verifiziert: komplett frische DB erzeugt, App auf separatem
  Testport dagegen gestartet, alle wesentlichen Routen (Dashboard, Geräte,
  Switche, Zugangsdaten, Wartung, Papierkorb, Benutzer, Gruppen, alle
  Einstellungsseiten, Logs, Konto) liefern 200 ohne einen einzigen Fehler
  im App-Log -- eine frische Installation hängt jetzt an keiner Stelle mehr
  von der Laufzeit-Migration ab, um überhaupt benutzbar zu sein.
This commit is contained in:
2026-08-13 20:53:31 +02:00
parent 89682618f7
commit 8fc9ce7b27
2 changed files with 76 additions and 7 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
#!/usr/bin/env python3
"""Legt einen Admin-Benutzer im PoE Manager an."""
"""Legt einen Admin-Benutzer in TESM an."""
import os
import sqlite3
from getpass import getpass
from flask_bcrypt import Bcrypt
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.environ.get("POE_DB_PATH", os.path.join(BASE_DIR, "sqlite.db"))
DB_PATH = os.environ.get("TESM_DB_PATH", os.path.join(BASE_DIR, "sqlite.db"))
bcrypt = Bcrypt()
+74 -5
View File
@@ -4,7 +4,7 @@ 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"))
DB_PATH = os.environ.get("TESM_DB_PATH", os.path.join(BASE_DIR, "sqlite.db"))
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
@@ -16,7 +16,9 @@ CREATE TABLE IF NOT EXISTS credentials (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL
password TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'switch',
deleted_at TEXT
);
""")
@@ -30,6 +32,7 @@ CREATE TABLE IF NOT EXISTS switches (
credential_id INTEGER,
last_modified_by TEXT,
last_modified_at TEXT,
deleted_at TEXT,
FOREIGN KEY (credential_id) REFERENCES credentials(id)
);
""")
@@ -45,6 +48,9 @@ CREATE TABLE IF NOT EXISTS devices (
is_active INTEGER DEFAULT 1,
last_modified_by TEXT,
last_modified_at TEXT,
credential_id INTEGER,
ssh_port INTEGER,
deleted_at TEXT,
FOREIGN KEY (switch_hostname) REFERENCES switches(hostname)
);
""")
@@ -61,7 +67,8 @@ CREATE TABLE IF NOT EXISTS users (
avatar_filename TEXT,
auth_source TEXT NOT NULL DEFAULT 'local',
email TEXT,
is_locked INTEGER NOT NULL DEFAULT 0
is_locked INTEGER NOT NULL DEFAULT 0,
deleted_at TEXT
);
""")
@@ -73,7 +80,8 @@ 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
is_system INTEGER DEFAULT 0,
deleted_at TEXT
);
""")
@@ -109,13 +117,17 @@ CREATE TABLE IF NOT EXISTS settings (
# DHCP: eigene Options-Definitionen (analog "Vordefinierte Optionen und
# Werte" bei einem Windows-DHCP-Server) — Code/Name/Typ/Beschreibung.
# is_standard markiert die vorbefüllten Standard-Optionen (Router/DNS/…) --
# die können vom Admin nicht gelöscht werden, nur ihr Wert geleert (die
# tatsächliche Vorbefüllung übernimmt app.py: _ensure_schema()).
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
description TEXT,
is_standard INTEGER NOT NULL DEFAULT 0
);
""")
@@ -136,6 +148,63 @@ CREATE TABLE IF NOT EXISTS dhcp_option_values (
);
""")
# DHCP: konfigurierte Subnetze (Interface + Range + optionales Gateway/DNS)
# -- ein Host kann mehrere Subnetze gleichzeitig bedienen, je eines pro
# Interface/Netz. sort_order steuert die Anzeige-/Verarbeitungsreihenfolge,
# enabled erlaubt ein Subnetz vorübergehend aus der geschriebenen
# Konfiguration auszuschließen, ohne es zu löschen.
c.execute("""
CREATE TABLE IF NOT EXISTS dhcp_subnets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
interface TEXT NOT NULL,
range_start TEXT NOT NULL,
range_end TEXT NOT NULL,
gateway TEXT,
dns TEXT,
sort_order INTEGER NOT NULL DEFAULT 0,
enabled INTEGER NOT NULL DEFAULT 1
);
""")
# DHCP: manuelle Reservierungen für Geräte OHNE eigenen Client-Datensatz
# (Geräte mit MAC+IP in der devices-Tabelle bekommen ihre Reservierung
# automatisch, siehe _dhcp_reservation_candidates in app.py).
c.execute("""
CREATE TABLE IF NOT EXISTS dhcp_manual_reservations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mac TEXT NOT NULL UNIQUE,
ip TEXT NOT NULL,
name TEXT NOT NULL
);
""")
# Service-Accounts (aktuell nur das LDAP-Bind-Konto) -- bewusst NICHT in der
# users-Tabelle (kein echter, einloggbarer App-Benutzer) und NICHT in der
# credentials-Tabelle (die ist für Switch-/Geräte-Zugangsdaten, ein anderer
# Anwendungsfall) -- eine eigene, kleine Tabelle. "purpose" ist UNIQUE, ein
# neu gespeichertes Konto für denselben Zweck ersetzt das alte.
c.execute("""
CREATE TABLE IF NOT EXISTS service_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
purpose TEXT UNIQUE NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL
);
""")
# AD-Gruppe -> App-Rechtegruppe: mehrere AD-Gruppen können jeweils einer
# App-Gruppe zugeordnet werden (additiv, siehe _ldap_resolve_app_groups in
# app.py) -- passt keine dieser Gruppen, greift die Standardgruppe.
c.execute("""
CREATE TABLE IF NOT EXISTS ldap_group_mappings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ad_group_dn TEXT UNIQUE NOT NULL,
ad_group_name TEXT NOT NULL,
app_group_id INTEGER NOT NULL,
FOREIGN KEY (app_group_id) REFERENCES groups(id)
);
""")
# Änderungslog (Audit-Log) — wer hat wann was geändert. Bewusst OHNE
# PoE-Neustarts (die stehen im Live-Log von poe.sh).
c.execute("""