# 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// 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//` | ## 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/.`), 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/.` (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//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` |