Server-Bibliothek
vor 18 Std. 1f44739ba685cd77dc804adcc58fbf584b21e52b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import type { Einstellungen, Software } from 'bibliothek-types';
import { alleSoftware, softwareAnlegen, softwareLoeschen, umzugsAngabenSetzen } from './datenbank';
import { aktuelleEinstellungen, speichereEinstellungen } from './konfiguration';
import { ladeGeheimnisse, loescheGeheimnisse, speichereGeheimnisse, type Geheimniswert } from './geheimnisse';
 
/**
 * Umzug auf einen anderen Rechner.
 *
 * Bewusst als lesbares JSON und nicht als Ordner-Kopie:
 *  - Die geholten Dateien (mehrere hundert MB) und die Git-Arbeitskopien (GB)
 *    sind jederzeit reproduzierbar — ein Abgleich holt sie neu. Nur Katalog,
 *    Einstellungen und Geheimnisse sind unersetzlich, und das sind wenige KB.
 *  - Die Datenbank im laufenden Betrieb zu kopieren wäre unsicher: die jüngsten
 *    Änderungen stehen im WAL und fehlten in der Kopie.
 *  - JSON übersteht Schema-Änderungen: beim Einlesen werden Felder zugeordnet,
 *    statt eine Binärdatei einer bestimmten Version vorauszusetzen.
 */
 
/** Aktuelle Fassung des Austauschformats — erlaubt später sauberes Nachziehen. */
const FORMAT_FASSUNG = 1;
 
export interface UmzugsPaket {
  fassung: number;
  erstelltAm: string;
  einstellungen: Einstellungen;
  software: Software[];
  /** Nur enthalten, wenn ausdrücklich mit exportiert (enthält Klartext-Schlüssel!). */
  geheimnisse?: Record<string, Geheimniswert[]>;
}
 
export function erstelleUmzugsPaket(mitGeheimnissen: boolean): UmzugsPaket {
  const eintraege = alleSoftware();
  const paket: UmzugsPaket = {
    fassung: FORMAT_FASSUNG,
    erstelltAm: new Date().toISOString(),
    einstellungen: aktuelleEinstellungen(),
    software: eintraege,
  };
  if (mitGeheimnissen) {
    paket.geheimnisse = {};
    for (const eintrag of eintraege) {
      const werte = ladeGeheimnisse(eintrag.id);
      if (werte.length > 0) {
        paket.geheimnisse[String(eintrag.id)] = werte;
      }
    }
  }
  return paket;
}
 
export interface UmzugsErgebnis {
  uebernommen: number;
  mitGeheimnissen: number;
  hinweis: string;
}
 
/**
 * Liest ein Paket ein und ersetzt den bisherigen Katalog vollständig.
 * Die eigentlichen Programm-Dateien kommen nicht mit — sie werden beim
 * nächsten Abgleich neu geholt bzw. gebaut.
 */
export function uebernehmeUmzugsPaket(paket: UmzugsPaket): UmzugsErgebnis {
  if (!paket || typeof paket !== 'object' || !Array.isArray(paket.software)) {
    throw new Error('Die Datei ist kein gültiges Umzugs-Paket.');
  }
  if (paket.fassung > FORMAT_FASSUNG) {
    throw new Error(`Das Paket stammt aus einer neueren Fassung (${paket.fassung}) — bitte die Anwendung aktualisieren.`);
  }
 
  // Bisherigen Bestand räumen, damit keine Mischung aus altem und neuem Stand entsteht.
  for (const vorhandene of alleSoftware()) {
    loescheGeheimnisse(vorhandene.id);
    softwareLoeschen(vorhandene.id);
  }
 
  // Die Ids ändern sich beim Neuanlegen — für die Eltern-Zuordnung mitführen.
  const idZuordnung = new Map<number, number>();
  for (const eintrag of paket.software.filter((eintrag) => !eintrag.elternId)) {
    idZuordnung.set(eintrag.id, uebernehmeEintrag(eintrag, 0).id);
  }
  for (const eintrag of paket.software.filter((eintrag) => eintrag.elternId)) {
    idZuordnung.set(eintrag.id, uebernehmeEintrag(eintrag, idZuordnung.get(eintrag.elternId) ?? 0).id);
  }
 
  let mitGeheimnissen = 0;
  for (const [alteId, werte] of Object.entries(paket.geheimnisse ?? {})) {
    const neueId = idZuordnung.get(Number(alteId));
    if (neueId) {
      speichereGeheimnisse(neueId, werte);
      mitGeheimnissen++;
    }
  }
 
  if (paket.einstellungen) {
    // Datenordner und Port gehören zum Zielrechner und werden bewusst nicht überschrieben.
    speichereEinstellungen({
      gitBenutzer: paket.einstellungen.gitBenutzer ?? '',
      gitSchluessel: paket.einstellungen.gitSchluessel ?? '',
    });
  }
 
  return {
    uebernommen: idZuordnung.size,
    mitGeheimnissen,
    hinweis: 'Die Programm-Dateien wurden nicht mit übertragen — sie werden beim nächsten Abgleich neu geholt bzw. gebaut.',
  };
}
 
/** Legt einen Eintrag neu an und stellt den bekannten Abgleich-Stand wieder her. */
function uebernehmeEintrag(eintrag: Software, elternId: number): Software {
  const angelegt = softwareAnlegen({
    name: eintrag.name ?? '',
    beschreibung: eintrag.beschreibung ?? '',
    elternId,
    quelleTyp: eintrag.quelleTyp ?? '',
    quelleWert: eintrag.quelleWert ?? '',
    bauBefehl: eintrag.bauBefehl ?? '',
    bauErgebnis: eintrag.bauErgebnis ?? '',
    einstufung: eintrag.einstufung ?? 'lizenzfrei',
    programmart: eintrag.programmart ?? 'unbestimmt',
    hinweis: eintrag.hinweis ?? '',
    anleitungUrl: eintrag.anleitungUrl ?? '',
    erwarteterHerausgeber: eintrag.erwarteterHerausgeber ?? '',
  });
  // Version und Datei bleiben leer (Standard beim Anlegen): Die Datei fehlt auf dem
  // neuen Rechner noch, der nächste Abgleich soll sie wirklich holen. Bewusst KEIN
  // fehlgeschlagener Abgleich — „noch nicht geholt" ist kein Problemfall und soll
  // nicht in der Warnleiste auftauchen.
  umzugsAngabenSetzen(angelegt.id, eintrag.veroeffentlichtAm ?? '', eintrag.benoetigteGeheimnisse ?? []);
  return angelegt;
}