Bearbeiten | Blame | Historie | Raw

Architektur-Dokumentation – Server-Bibliothek

Diese Datei ist für die technische Übergabe gedacht: sie erklärt, wie das
Projekt aufgebaut ist, welche Entscheidungen warum getroffen wurden, und was
bei einer Anpassung an eine andere Infrastruktur zu beachten ist.

Für die Bedienungsanleitung (Endnutzer-Sicht) siehe README.md.
Für die Entwicklungshistorie (was wurde wann gebaut) siehe CHANGELOG.md.


1. Tech-Stack

  • Python 3.8+, Flask (Web-Framework) – einzige externe Abhängigkeit
    laut requirements.txt
  • SQLite (data/bibliothek.db) – eine einzelne Datei, kein Datenbankserver
  • Server-seitig gerenderte HTML-Templates (Jinja2), kein JavaScript-Framework,
    kein Build-Prozess – nur Vanilla-JS für Interaktivität (Checklisten,
    Formular-Verhalten)
  • Läuft als einfacher Flask-Entwicklungsserver (app.run(host="0.0.0.0", ...))
    nicht produktionsgehärtet, siehe Abschnitt 7 "Bekannte Grenzen"

Bewusste Design-Entscheidung: keine Frameworks/Build-Tools, damit eine Person
ohne Programmiererfahrung (Zielgruppe dieses Projekts) das System mit einem
Doppelklick starten kann, ohne npm install o. Ä.

2. Projektstruktur

server-bibliothek/
├── app.py                          Haupt-Flask-Anwendung, alle Routen
├── updater.py                      Update-Check-Logik (winget/GitHub/Direkt-Link)
├── automatische_pruefung.py        Eigenständiges Skript für geplante Update-Checks
├── requirements.txt                Python-Abhängigkeiten (nur Flask)
├── data/
│   ├── bibliothek.db               SQLite-Datenbank (wird beim ersten Start angelegt)
│   └── doku_bilder/<software_id>/  Hochgeladene Doku-Screenshots
├── installers/                     Standard-Ablageort für Installer-Dateien
│                                   (Nutzer koennen aber JEDEN Pfad eintragen,
│                                   dieser Ordner ist nur ein Vorschlag)
├── static/style.css                Gesamtes Styling (ein File, keine Build-Pipeline)
├── templates/                      Jinja2-HTML-Templates
├── install.bat                     Einmalige Einrichtung (Python-Abhaengigkeiten)
├── start.bat                       Startet den Webserver
├── pruefe_updates.bat              Manueller Update-Check-Lauf
├── automatisierung_einrichten.bat  Richtet taeglichen Update-Check per Windows-Aufgabenplanung ein
├── firewall-freigabe-einrichten.bat Oeffnet Port 5000 in der Windows-Firewall
├── Update-Quellen-Uebersicht.md    Nachschlagehilfe fuer winget-IDs/GitHub-Pfade
├── README.md                       Bedienungsanleitung
├── ARCHITEKTUR.md                  Diese Datei
└── CHANGELOG.md                    Entwicklungshistorie

3. Datenbankschema

Alle Tabellen werden in init_db() (app.py) angelegt. Migrationen (neue
Spalten) laufen automatisch beim Start via ALTER TABLE ... ADD COLUMN,
bestehende Daten bleiben dabei erhalten.

software – der Tool-Katalog

Spalte Typ Bedeutung
id INTEGER PK
name TEXT Anzeigename, wird auch für Dateinamen-Ableitung sanitisiert
kategorie TEXT Freitext, für Gruppierung in Checklisten
version TEXT Bei "direkt"-Update-Quelle: technische Kennung (ETag), keine echte Versionsnummer
installer_pfad TEXT Ordner oder Datei-Pfad, siehe Abschnitt 4 "Ordner-Konvention"
install_parameter TEXT Silent-Install-Flags, z. B. /S oder /quiet /norestart
dokumentation TEXT Freitext-Anleitung
letzte_pruefung TEXT (Datum) Für die Status-Anzeige (Aktuell/Bitte prüfen/Nie geprüft)
erstellt_am TEXT (Zeitstempel)
auto_update INTEGER (0/1) Automatische Aktualisierung aktiv?
update_quelle TEXT winget | github | direkt | leer
update_wert TEXT Paket-ID / besitzer/projekt / URL, je nach Quelle

zuweisungen – einzelne Tool-Zuweisungen (Zeilen eines Protokolls)

Spalte Bedeutung
id, software_id
protokoll_id FK auf protokolle (siehe unten)
mitarbeiter, rechnername Denormalisiert hier UND im Protokoll gespeichert (Absicht: die Installationsverlauf-Ansicht pro Software bleibt dadurch einfach)
installierte_version, installiert_von, installiert_am, notiz

protokolle – ein Eintrag pro Rechner-Einrichtung

Spalte Bedeutung
id, mitarbeiter, rechnername, datum, erstellt_von, notiz, erstellt_zeitpunkt

Ein Protokoll fasst mehrere zuweisungen-Zeilen zusammen (1 Protokoll : n
Zuweisungen), damit bei z. B. 40 Mitarbeitern × 40 Tools keine unübersichtliche
flache Liste entsteht, sondern eine Übersicht pro Person/Rechner.

update_log – Protokoll jedes automatischen Update-Checks

Spalte Bedeutung
id, software_id, zeitpunkt, alte_version, neue_version, erfolg (0/1), meldung

doku_bilder – hochgeladene Screenshots zur Dokumentation

Spalte Bedeutung
id, software_id, dateiname, hochgeladen_am Datei liegt unter data/doku_bilder/<software_id>/<dateiname>

4. Kernkonzept: Ordner-Konvention (updater.py)

Das Feld installer_pfad kann zwei Formen haben:
- Datei-Pfad (endet auf .exe/.msi/etc.): ältere/explizite Schreibweise
- Ordner-Pfad (kein Dateiname): empfohlene, aktuelle Schreibweise

Die Funktion updater.effektiver_ablageordner(installer_pfad, name) ist die
zentrale Logik: sie sorgt immer dafür, dass jedes Tool in einem eigenen
Unterordner landet (benannt nach dem sanitisierten Tool-Namen), selbst wenn
im Feld nur ein geteilter Basisordner steht oder ein alter kompletter
Dateipfad. Das verhindert, dass mehrere Tools sich einen Ordner teilen und
Dateien "nackt" nebeneinander landen (siehe CHANGELOG, Runden r6–r8, dort
wurde das mehrfach nachgeschärft, nachdem reale Testfälle Lücken zeigten).

updater.verwaltete_datei_pfad(installer_pfad, name) findet die aktuell
abgelegte Datei (falls vorhanden), inkl. Rückwärtskompatibilität zu Dateien,
die noch am alten, nicht verschachtelten Ort liegen (Übergangslogik).

Wichtig für Weiterentwicklung: Jede neue Stelle im Code, die mit
Installer-Dateien arbeitet, MUSS über diese beiden Funktionen gehen, statt
installer_pfad direkt zu verwenden – sonst wird die
Unterordner-Garantie umgangen.

5. Automatische Aktualisierung (drei Quellen)

updater.pruefe_software(name, quelle, update_wert, aktuelle_version, installer_pfad)
ist der Einstiegspunkt, delegiert an:

  • pruefe_winget – ruft winget show/winget download auf (nur unter
    Windows verfügbar; auf Nicht-Windows-Systemen liefert es einen sauberen
    Fehler statt eines Absturzes)
  • pruefe_github – nutzt die öffentliche GitHub-API
    (api.github.com/repos/.../releases/latest), sucht ein .exe/.msi-Asset
  • pruefe_direkt – fester Download-Link, Versionsvergleich über
    HTTP-ETag/Last-Modified-Header statt echter Versionsnummer

Alle drei prüfen zusätzlich, ob die Datei physisch noch existiert – auch
bei gleicher Versionsnummer wird neu heruntergeladen, falls die Datei manuell
gelöscht wurde (Bug wurde in Runde r5 gefunden und behoben).

Vor jedem Ersetzen einer Datei wird über sichern_und_ersetzen() eine
Sicherheitskopie in einen _sicherungen-Unterordner gelegt.

6. "Rechner einrichten" – Verteilungs-Mechanismus

Route /einrichten (app.py) erzeugt bei Bedarf ein individuelles
Installations-Paket (ZIP mit einrichten.ps1 + Rechner-einrichten-starten.bat
+ den echten Installer-Dateien/ZIPs selbst unter tools/<id>.<endung>),
statt vorab für jede Mitarbeiter/Tool-Kombination ein festes Skript zu pflegen.

Wichtige Design-Entscheidung (Runde r16): Die Installer-Dateien werden
bereits beim Erzeugen des Pakets (auf dem PC, der die Checkliste ausfüllt)
eingebettet, nicht erst beim Ausführen auf dem Zielrechner nachgeladen. Ein
früherer Ansatz lud die Dateien per Invoke-WebRequest zur Laufzeit vom
Server nach – das erforderte Netzwerkzugriff des Zielrechners auf den Server
in genau dem Moment der Einrichtung, was in der Praxis nicht immer gegeben
ist (z. B. Rechner noch nicht ans Firmennetz angebunden). Jetzt braucht der
Zielrechner für die eigentliche Installation kein Netzwerk mehr – nur die
abschließende, optionale Meldung ans zentrale Protokoll (/zuweisungen/neu)
versucht noch einen Netzwerkzugriff, scheitert dieser, bleibt trotzdem das
lokale Protokoll (protokoll.log) vollständig.

Ablauf beim Ausführen auf dem Zielrechner:
1. .bat kopiert den kompletten entpackten Ordner (inkl. tools/-
Unterordner mit den echten Installer-Dateien) nach C:\ClientBibliothek
(per xcopy /E /I /Y)
2. Self-Elevation (Admin-Rechte-Anfrage) über Start-Process -Verb RunAs
3. Automatische Windows-Defender-Ausnahme für C:\ClientBibliothek
(Add-MpPreference -ExclusionPath) – ein einziger Pfad statt vieler
Einzelausnahmen
4. Pro ausgewähltem Tool: entweder stille Installation aus der lokal
mitgelieferten
Datei unter tools/<id>.<endung> (mit hinterlegtem
Silent-Parameter, .msi läuft über msiexec) oder – bei Tools ohne
einzelne Installer-Datei ("Ordner-Typ", z. B. portable Programme wie
Blender) – die mitgelieferte ZIP-Datei wird nach C:\ClientBibliothek\pakete\
kopiert, bewusst ohne automatisches Entpacken (Entscheidung in Runde
r14: der Zielort für portable Tools variiert, das soll eine Person bewusst
entscheiden)
5. Lokales Protokoll unter C:\ClientBibliothek\protokoll.log
6. Optionaler POST an /zuweisungen/neu auf dem Server, um das zentrale
Protokoll zu befüllen (Rechnername wird dabei über $env:COMPUTERNAME
automatisch erkannt) – best effort, kein Abbruch bei Fehlschlag

Die Ordner-Struktur unter C:\ClientBibliothek:
- \tools – die mitgelieferten Installer-Dateien/ZIPs (Quelle für die
Installation, kommen direkt aus dem heruntergeladenen Paket)
- \pakete – ZIPs von portablen Tools, bleiben bewusst liegen
- \protokoll.log – lokale Mitschrift

Serverseitig teilen sich /einrichten (POST) und /software/<id>/download
die Hilfsfunktion _zippe_ordner_in_bytes() für das ZIP-Packen eines
Ordners, um Code-Duplikation zu vermeiden.

Der Client-Ordner-Pfad ist eine Konstante (CLIENT_ORDNER in app.py,
aktuell C:\ClientBibliothek) – zentral anpassbar, falls die Infrastruktur
einen anderen Standardpfad vorgibt.

6a. Zugriffsbeschränkung: lokal (Server) vs. Netzwerk

Seit Runde r17 unterscheidet die Anwendung, ob eine Anfrage direkt vom Server
selbst kommt (z. B. per RDP-Sitzung, im Browser dort localhost/127.0.0.1
aufgerufen) oder über das Netzwerk von einem anderen Rechner.

  • ist_lokaler_zugriff() prüft request.remote_addr gegen 127.0.0.1/::1
  • @app.before_request-Hook _zugriff_beschraenken() blockiert für
    Netzwerk-Zugriffe alle Routen außer denen in OEFFENTLICHE_ENDPUNKTE
    (aktuell nur einrichten und static) mit einer 403-Seite
  • Die Seite /einrichten zeigt sich bei Netzwerkzugriff unter anderem Namen
    ("Software anfordern" statt "Rechner einrichten") und mit Hinweis auf die
    Protokollierung
  • Jeder Aufruf/Download über /einrichten wird in der Tabelle zugriffslog
    festgehalten (Zeitpunkt, IP-Adresse, per Reverse-DNS ermittelter
    Rechnername falls auflösbar, selbst angegebener Name, Aktion) – einsehbar
    unter /zugriffslog (selbst wieder nur lokal erreichbar)

Wichtige Einschränkung: Ein echter Windows-Benutzername lässt sich über
reines HTTP nicht zuverlässig ermitteln – dafür wäre eine
NTLM/Kerberos-Integration nötig (z. B. über einen IIS-Reverse-Proxy vor
Flask, der die Windows-Anmeldung übernimmt und den Benutzernamen per Header
an die Flask-App durchreicht). Das ist in diesem Prototyp nicht
umgesetzt. Aktuell basiert die Identifizierung auf der IP-Adresse, einem per
Reverse-DNS aufgelösten Rechnernamen (funktioniert nur, wenn im
Firmennetzwerk DNS-Einträge für Client-Rechner existieren) und dem selbst im
Formular angegebenen Namen (nicht verifiziert). Für eine belastbarere
Identifizierung müsste diese IIS/NTLM-Integration nachgerüstet werden.

Anpassung an andere Infrastruktur: Sollte der Server z. B. hinter einem
Reverse-Proxy laufen, liefert request.remote_addr ggf. die Proxy-IP statt
der echten Client-IP – in dem Fall müssten X-Forwarded-For-Header
ausgewertet werden (aktuell nicht implementiert, da kein Proxy im Einsatz).

7. Bekannte Grenzen / nicht vollständig getestet

  • Keine echte Authentifizierung, nur Herkunfts-Beschränkung. Seit r17
    ist die Verwaltung (Katalog, Protokolle, Zugriffsprotokoll) nur noch vom
    Server selbst aus erreichbar (siehe Abschnitt 6a) – das ist aber eine
    Netzwerk-/IP-basierte Einschränkung, kein Login mit Benutzername/Passwort.
    Wer direkt am Server sitzt (oder sich als diesen ausgibt, z. B. über
    IP-Spoofing im selben Netzwerksegment), hat vollen Zugriff. Für ein
    belastbares Sicherheitskonzept mit echten Benutzerkonten wäre ein
    richtiges Login-System nötig.
  • Kein HTTPS/TLS. Die Kommunikation läuft unverschlüsselt über HTTP. Für
    ein internes, abgeschottetes Netzwerk meist unkritisch, sollte aber vor
    einem größeren Rollout bewertet werden.
  • Flask-Entwicklungsserver, kein WSGI-Produktivserver (z. B. Waitress,
    gunicorn). Für kleine interne Nutzung ausreichend, bei mehr gleichzeitigen
    Nutzern ggf. wechseln.
  • PowerShell-Teile des Verteilungs-Skripts (Self-Elevation, msiexec,
    Add-MpPreference) konnten in der Entwicklungsumgebung (Linux-Sandbox)
    nicht Ende-zu-Ende ausgeführt werden, nur mit dem echten PowerShell-Parser
    auf Syntaxfehler geprüft, plus die reinen Netzwerk-Teile
    (Invoke-WebRequest/Invoke-RestMethod, Expand-Archive) wurden live gegen den
    echten Server getestet. Vor größerem Rollout: an 1–2 unkritischen Rechnern
    gegentesten.
  • winget-Pfad der automatischen Aktualisierung konnte aus demselben
    Grund nicht direkt ausgeführt werden (winget existiert nur unter Windows).
    Der GitHub- und Direkt-Link-Pfad wurden dagegen live gegen echte
    Schnittstellen getestet.
  • Keine mehrsprachige Oberfläche – komplett auf Deutsch hartkodiert.

8. Konfigurationspunkte für die Anpassung

Was Wo
Client-Ordner auf Zielrechnern CLIENT_ORDNER in app.py
Port des Webservers app.run(..., port=5000) am Ende von app.py, sowie in den .bat-Dateien/Firewall-Skript
Schwelle "Bitte prüfen"-Warnung TAGE_BIS_WARNUNG in app.py (aktuell 30 Tage)
Erlaubte Bildformate für Doku-Upload ERLAUBTE_BILD_ENDUNGEN in app.py
Max. Uploadgröße app.config["MAX_CONTENT_LENGTH"] in app.py
Versionsmarker (Diagnose, ob neue Version läuft) APP_VERSION in app.py