From 98bce1b0da675715b2e5482c0ad61ba21135f62f Mon Sep 17 00:00:00 2001
From: Server-Bibliothek <server-bibliothek@lokal>
Date: Di, 04 Aug 2026 07:00:04 +0200
Subject: [PATCH] Add a desktop client that knows what is installed locally

---
 packages/bibliothek-server/src/abgleich.ts |  108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 108 insertions(+), 0 deletions(-)

diff --git a/packages/bibliothek-server/src/abgleich.ts b/packages/bibliothek-server/src/abgleich.ts
new file mode 100644
index 0000000..36f8c36
--- /dev/null
+++ b/packages/bibliothek-server/src/abgleich.ts
@@ -0,0 +1,108 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import type { AbgleichErgebnis, Software } from 'bibliothek-types';
+import { DATEIEN_ORDNER, QUELLEN_ORDNER, abgleichSpeichern } from './datenbank';
+import { bezugsquelleFuer, type QuellenAbgleich } from './bezugsquellen';
+import { pruefeSignatur, weichtHerausgeberAb } from './signatur';
+import { alsUmgebung, ladeGeheimnisse } from './geheimnisse';
+import { gitSshBefehl } from './konfiguration';
+
+/** Größe der aktuell bereitgestellten Datei — Grundlage der Plausibilitätsprüfung. */
+function bisherigeGroesse(software: Software): number {
+  if (!software.dateiname) {
+    return 0;
+  }
+  const pfad = path.join(DATEIEN_ORDNER, String(software.id), software.dateiname);
+  return fs.existsSync(pfad) ? fs.statSync(pfad).size : 0;
+}
+
+/** Unveränderten Stand zurückschreiben (bei Misserfolg oder unbekannter Quelle). */
+function unveraendert(software: Software, meldung: string, version = software.version): AbgleichErgebnis {
+  return {
+    version,
+    dateiname: software.dateiname,
+    erfolg: false,
+    meldung,
+    geaendert: false,
+    signaturStatus: software.signaturStatus,
+    signaturHerausgeber: software.signaturHerausgeber,
+    veroeffentlichtAm: software.veroeffentlichtAm,
+    benoetigteGeheimnisse: software.benoetigteGeheimnisse,
+  };
+}
+
+/** Gleicht eine Software mit ihrer Bezugsquelle ab und speichert das Ergebnis zurück. */
+export async function gleicheSoftwareAb(software: Software): Promise<void> {
+  if (software.quelleTyp === 'manuell') {
+    return; // Datei wird von Hand hinterlegt – es gibt nichts abzugleichen
+  }
+  if (!software.quelleTyp || !software.quelleWert) {
+    return; // keine Quelle gesetzt – nichts zu holen
+  }
+  const quelle = bezugsquelleFuer(software.quelleTyp);
+  if (!quelle) {
+    abgleichSpeichern(software.id, unveraendert(software, `Unbekannte Bezugsquelle: ${software.quelleTyp}`));
+    return;
+  }
+
+  const zielordner = path.join(DATEIEN_ORDNER, String(software.id));
+  const ergebnis = await quelle.gleicheAb({
+    wert: software.quelleWert,
+    aktuelleVersion: software.version,
+    zielordner,
+    arbeitsordner: path.join(QUELLEN_ORDNER, String(software.id)),
+    bauBefehl: software.bauBefehl,
+    bauErgebnis: software.bauErgebnis,
+    vorherigeGroesse: bisherigeGroesse(software),
+    geheimnisse: alsUmgebung(ladeGeheimnisse(software.id)),
+    gitSshBefehl: gitSshBefehl(),
+  });
+
+  if (!ergebnis.erfolg) {
+    abgleichSpeichern(software.id, unveraendert(software, ergebnis.meldung, ergebnis.version));
+    return;
+  }
+
+  abgleichSpeichern(software.id, await mitSignaturbefund(software, ergebnis, zielordner));
+}
+
+/**
+ * Ergänzt das Abgleich-Ergebnis um die Windows-Signatur der bereitgestellten Datei.
+ * Ist ein Signaturgeber erwartet und weicht der gefundene ab, wird die Datei nicht
+ * zum Download freigegeben (`dateiname` bleibt leer) — sie bleibt aber zur Ansicht liegen.
+ */
+async function mitSignaturbefund(software: Software, ergebnis: QuellenAbgleich, zielordner: string): Promise<AbgleichErgebnis> {
+  const befund = await pruefeSignatur(path.join(zielordner, ergebnis.dateiname));
+  const gemeinsam = {
+    version: ergebnis.version,
+    signaturStatus: befund.status,
+    signaturHerausgeber: befund.herausgeber,
+    veroeffentlichtAm: ergebnis.veroeffentlichtAm ?? software.veroeffentlichtAm,
+    benoetigteGeheimnisse: ergebnis.benoetigteGeheimnisse ?? software.benoetigteGeheimnisse,
+  };
+  if (weichtHerausgeberAb(software.erwarteterHerausgeber, befund.herausgeber)) {
+    return {
+      ...gemeinsam,
+      dateiname: '', // nicht ausliefern, bis das geklärt ist
+      erfolg: false,
+      meldung: `Signatur weicht ab: erwartet „${software.erwarteterHerausgeber}", gefunden „${befund.herausgeber || 'unsigniert'}" (${befund.status}). Die Datei wird nicht zum Download angeboten.`,
+      geaendert: false,
+    };
+  }
+  return {
+    ...gemeinsam,
+    dateiname: ergebnis.dateiname,
+    erfolg: true,
+    meldung: ergebnis.meldung,
+    geaendert: ergebnis.geaendert,
+  };
+}
+
+/** Gleicht nacheinander alle Einträge mit gesetzter Quelle ab (täglicher Auto-Check). */
+export async function gleicheAlleAb(alle: Software[]): Promise<void> {
+  for (const software of alle) {
+    if (software.quelleTyp && software.quelleWert) {
+      await gleicheSoftwareAb(software);
+    }
+  }
+}

--
Gitblit v1.9.3