Bearbeiten | Blame | Historie | Raw

Architektur

Die technische Innensicht: wie das Projekt aufgebaut ist, welche Entscheidungen warum so
getroffen wurden und wo die Grenzen liegen. Für Betrieb und Bedienung siehe
README.md.


1. Grundriss

npm-Workspaces mit drei Paketen. Die Typen liegen bewusst dazwischen: Server und
Oberfläche teilen sich einen Vertrag, den der Übersetzer prüft — ändert sich ein Feld,
bricht die Übersetzung auf beiden Seiten.

Paket Rolle
bibliothek-types Gemeinsame Typen. Kennt weder Server noch Browser
bibliothek-server Express, SQLite, Bezugsquellen, Zugriffsgrenze
bibliothek-client Oberfläche aus @dh-software/dh-components (Lit)

TypeScript durchgehend im strict-Modus.

Warum Node und nicht Flask

Der übernommene Prototyp war Flask. Der Umbau auf Node hatte zwei Gründe: Die Oberfläche
sollte auf dem hauseigenen Komponenten-Satz aufsetzen, und der Auslieferungsstand sollte
eine einzelne Programmdatei ohne installierte Laufzeitumgebung sein. Beides zieht Node
nach sich.

Warum keine Datenbankbibliothek

Ab Node 22 bringt die Laufzeitumgebung SQLite mit (node:sqlite). Damit entfällt eine
native Abhängigkeit, die für jede Node-Version neu übersetzt werden müsste — genau daran
war ein erster Versuch mit better-sqlite3 gescheitert. Journalmodus WAL, damit Lesen
während eines laufenden Abgleichs nicht blockiert.


2. Bezugsquellen

Das Herzstück. Jede Art, an eine Datei zu kommen, ist eine Klasse hinter derselben
Schnittstelle:

interface Bezugsquelle {
  gleicheAb(auftrag: Abgleichauftrag): Promise<QuellenAbgleich>;
}
Typ Vorgehen
direkt HTTP-Abruf eines festen Links
github Neuestes Release über die GitHub-Schnittstelle, passendes Asset wählen
winget winget download, danach winget show für das Veröffentlichungsdatum
svn svn export einer Datei
git Repository klonen bzw. aktualisieren, bauen, Ergebnis übernehmen
manuell Kein Abruf — die Datei wird einmalig hinterlegt und bleibt liegen

Eine neue Quelle anzubinden heißt: eine Klasse schreiben und in der Übersicht eintragen.
Der Rest — Abgleich, Prüfung, Speicherung, Anzeige — bleibt unberührt.

Nichts wird ersetzt, bevor es geprüft ist

Der teuerste Fehler wäre, eine funktionierende Datei gegen Müll zu tauschen, weil eine
Quelle stillschweigend etwas anderes liefert als erwartet. Deshalb schreibt keine Quelle
direkt in den Zielordner:

  1. Herunterladen bzw. bauen in <zielordner>.neu
  2. inhaltBeanstanden() prüft: leer? HTML statt Programmdatei (Signatur und
    Inhaltstyp)? plötzlich unter der Hälfte der bisherigen Größe und kleiner als 100 KB?
  3. Erst wenn nichts zu beanstanden ist, wird getauscht — sonst verworfen

Der Eintrag meldet dann das Problem, behält aber seine bisherige Datei. Eine tote Quelle
kostet Aktualität, nie den Download.

Selbstgebaute Werkzeuge

Bei git beschreibt das Repository seinen eigenen Bau in software-bibliothek.json:

{
  "buildCommand": "…",
  "artifact": "dist/MeinWerkzeug.exe",
  "secrets": [{ "name": "…", "description": "…" }]
}

Damit muss die Bibliothek nichts über einzelne Werkzeuge wissen — ein neues anzubinden
erfordert keine Änderung an ihr. Gebaut wird nur, wenn sich der Commit geändert hat.
Zeitgrenze 30 Minuten, weil Bauvorgänge mit vielen Abhängigkeiten lange dauern.

Die Kennung für den Zugriff kommt aus den Einstellungen (GIT_SSH_COMMAND), nicht aus der
URL. So bleibt der Katalogeintrag frei von Kontoangaben, und ein Kontowechsel ist eine
einzige Änderung statt einer pro Eintrag.

Bau-Geheimnisse

Werte liegen getrennt vom Katalog in secrets/<id>.json und werden beim Bau als
Umgebungsvariablen gesetzt — sie berühren die Platte der Arbeitskopie nie. Das Verfahren
im Ganzen beschreibt der Skill build-secrets im Repository dh-claude-rules.

Geschützt sind sie durch Dateiberechtigungen (icacls, beschränkt auf SYSTEM,
Administratoren und das Dienstkonto, bei jedem Start neu gesetzt), durch die
Zugriffsgrenze und dadurch, dass sie in keiner aus dem Netzwerk lesbaren Schnittstelle
auftauchen. Eine Verschlüsselung wäre unter Windows nur über die Datenschutz-Schnittstelle
des Betriebssystems sinnvoll; die ist aus der Laufzeitumgebung nicht ohne Zusatzpaket
erreichbar.


3. Zugriffsgrenze

Kein Anmeldeverfahren, sondern eine Grenze an der Netzwerkschnittstelle: Eine Middleware
prüft, ob die Anfrage von localhost kommt. Verwaltende Routen tragen sie, lesende nicht.

  • Frei erreichbar: Katalog lesen, einzeln oder als ZIP herunterladen, Umgebung abfragen
  • Nur lokal: Anlegen, Ändern, Löschen, Abgleich anstoßen, Datei hinterlegen, Geheimnisse,
    Export/Import, Einstellungen, Zugriffsprotokoll

Die Oberfläche fragt beim Start /api/umgebung und blendet aus, was ohnehin abgewiesen
würde. Das ist Bequemlichkeit, keine Absicherung — die liegt beim Server.

Das passt zum Auftrag („Vollzugriff nur am Server") und kommt ohne Benutzerverwaltung aus.
Der Preis: Wer am Server sitzt, darf alles. Rollen und Rechte sind im Ticket bewusst
zurückgestellt.


4. Oberfläche

Aufgebaut aus @dh-software/dh-components, damit das Werkzeug aussieht wie der Rest des
Hauses. Zwei Besonderheiten:

Light DOM statt Shadow DOM. Die Komponenten werden über createRenderRoot() ins Light
DOM gerendert, sonst greifen die globalen ::part()-Regeln des Themes nicht.

Themewechsel zur Laufzeit. Zwei <style>-Elemente mit den Farbdefinitionen für hell
und dunkel, umgeschaltet über .disabled. Drei Modi: System, hell, dunkel. Nach dem
Vorbild des Meeting-Transkriptors, damit sich die Werkzeuge gleich anfühlen.

Der Katalog ist eine kompakte Liste statt Karten — er soll dreistellige Zahlen an
Einträgen aushalten. Reiter trennen lizenzfrei, lizenzpflichtig und intern; die
Mehrfachauswahl wirkt über Reitergrenzen hinweg und lädt als ZIP.

Zusatzinhalte (Sprachpakete, Erweiterungen) hängen über elternId an ihrem Werkzeug und
stehen eingerückt darunter, ohne eigene Trennlinie — Werkzeug und Zubehör lesen sich als
ein Block.


5. Auslieferung als eine Datei

npm run build:exe in vier Schritten:

  1. TypeScript übersetzen
  2. Server mit esbuild zu einer CommonJS-Datei bündeln
  3. Die Oberfläche als Einzelressourcen in die SEA-Konfiguration eintragen
  4. Node-Programmdatei kopieren und den Blob per postject hineinschreiben

Warum Einzelressourcen und nicht ein eingebettetes Bündel: Ein früher Versuch hat das
JavaScript-Bündel als Zeichenkette in die Server-Datei eingebettet. Die eingebettete
Schriftart enthält Bytefolgen, die als UTF-8 ungültig sind — sie wurden zu U+FFFD und das
Bündel damit unbrauchbar. Die Seite blieb weiß, ohne Fehlermeldung. Aufgefallen ist es
erst beim Vergleich der ausgelieferten Bytes mit der Datei auf der Platte. Über
sea.getRawAsset() kommen die Ressourcen unverändert an.

Warum esbuild und nicht webpack: webpack scheitert an einer Abhängigkeit in der
Server-Kette, deren tsconfig.json per extends auf ein nicht aufgelöstes Paket zeigt.
esbuild kümmert das nicht.

Der ZIP-Erzeuger für Mehrfach-Downloads ist von Hand geschrieben (nur speichernd, ohne
Kompression — Installer sind ohnehin komprimiert). Das spart eine Abhängigkeit in einem
Bündel, das ohne Fremdcode auskommen soll.


6. Datenmodell

Eine Tabelle software und eine Tabelle für das Zugriffsprotokoll. Neue Spalten kommen
über eine Liste hinzu, die beim Start mit dem tatsächlichen Schema abgeglichen wird —
fehlende werden ergänzt. Damit übersteht eine bestehende Installation ein Update ohne
Handarbeit.

Ordnernamen im Datenverzeichnis haben denselben Weg: Eine Umbenennungsliste beim Start
zieht ältere Installationen nach.

Zur Version: Angezeigt wird das Veröffentlichungsdatum des Anbieters, nicht der
Abrufzeitpunkt. Nur wenn eine Quelle keins liefert, wird ersatzweise der Abrufzeitpunkt
genannt und als solcher benannt — die Frage „ist das aktuell?" beantwortet das Datum des
Anbieters, unser Abrufzeitpunkt sagt darüber nichts.


7. Grenzen

  • Kein HTTPS. Damit fehlt dem Browser der sichere Kontext, und die Schnittstellen für
    Dateizugriff (showDirectoryPicker, crypto.subtle) stehen an der Netzwerkadresse nicht
    zur Verfügung. Nachgemessen: an localhost vorhanden, an der IP nicht.
  • Kein Dienstbetrieb. Die Programmdatei muss von Hand gestartet werden.
  • Keine automatisierten Tests. Geprüft wurde bisher von Hand gegen die echten Quellen.
  • Keine Rollen. Wer am Server sitzt, darf alles.
  • Ein hinterlegtes Geheimnis lässt sich über die Oberfläche nicht einzeln löschen
    ein leeres Feld bedeutet „unverändert lassen". Nur über die Schnittstelle mit einer
    Liste, die den Namen weglässt.
  • Bauvorgänge laufen mit den Rechten des Dienstkontos. Wer einen Bau-Befehl hinterlegen
    kann, kann Code auf dem Server ausführen. Das setzt Vertrauen in die angebundenen
    Repositories voraus.