Compare commits
6
Commits
53dde70f26
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e5778b1e6 | ||
|
|
cba2e49211 | ||
|
|
c5db3baac0 | ||
|
|
274d7666ea | ||
|
|
890e00fb5c | ||
|
|
042e63812b |
@@ -11,6 +11,14 @@ dist/
|
||||
next-env.d.ts
|
||||
*.tsbuildinfo
|
||||
|
||||
# Eigene Anpassungen der Installation.
|
||||
#
|
||||
# Docker Compose liest diese Datei automatisch zusaetzlich zur
|
||||
# docker-compose.yml. Sie ist der vorgesehene Ort fuer Aenderungen, die ein
|
||||
# Update ueberleben sollen: update.sh prueft nur versionierte Dateien
|
||||
# (git status --untracked-files=no) und laesst sie deshalb in Ruhe.
|
||||
docker-compose.override.yml
|
||||
|
||||
# Umgebung - enthaelt Schluessel, gehoert NIE ins Repo
|
||||
.env
|
||||
.env.local
|
||||
|
||||
+82
-1
@@ -26,6 +26,86 @@ Wiederherstellung*.
|
||||
|
||||
_Noch nichts._
|
||||
|
||||
## [1.12.0] – 2026-09-01
|
||||
|
||||
Diese Fassung geht aus einer Fehlersuche im laufenden Betrieb hervor. Ein
|
||||
Schreiben ließ sich nicht in der Akte ablegen — und hinter derselben
|
||||
Meldung lagen nacheinander drei verschiedene Ursachen. Beim Nachsehen kam
|
||||
ein vierter Punkt dazu, der schwerer wiegt als alle anderen zusammen.
|
||||
|
||||
### Nach dem Update: einmal die Sicherung prüfen
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
docker compose exec sicherung /bin/sh /sicherung.sh
|
||||
```
|
||||
|
||||
Erwartet werden drei Zeilen: `Datenbank gesichert`, `Dokumente gesichert`
|
||||
und `Selbstpruefung bestanden - Datenbank und Dokumente.`
|
||||
|
||||
### Behoben
|
||||
|
||||
- **Deine Dokumente waren in keiner Sicherung.** Der Sicherungsdienst
|
||||
machte täglich ein `pg_dump` und sonst nichts. Nach einem Plattenschaden
|
||||
wäre die Akte vollständig wiederhergestellt worden — mit allen
|
||||
Einträgen, Verweisen und Prüfsummen, aber ohne eine einzige Datei
|
||||
dahinter. Die Prüfsumme in der Akte hätte dann ein Dokument belegt, das
|
||||
es nicht mehr gibt.
|
||||
|
||||
Je Lauf entstehen jetzt zwei verschlüsselte Dateien mit demselben
|
||||
Zeitstempel — der Datenbankstand und ein Archiv der Dokumente. Beide mit
|
||||
Prüfsumme, beide in der Aufbewahrung, beide in der Selbstprüfung.
|
||||
Schlägt der Dokumententeil fehl, bricht der Lauf ab und räumt den
|
||||
angefangenen Datenbankstand weg: Eine Sicherung, die nur die halbe
|
||||
Wahrheit enthält, aber vollständig aussieht, ist schlimmer als gar
|
||||
keine.
|
||||
|
||||
- **Der mitgelieferte Objektspeicher konnte keine Dokumente aufnehmen.**
|
||||
Die Anwendung verlangte bei jeder Ablage serverseitige Verschlüsselung,
|
||||
das mitgelieferte MinIO kann sie ohne Zusatzdienst nicht. Wer der
|
||||
Installationsanleitung folgte, landete zwangsläufig dort. Sie ist jetzt
|
||||
abschaltbar und standardmäßig aus — gebraucht wird sie nicht, weil die
|
||||
Anwendung seit 1.11.0 selbst verschlüsselt.
|
||||
|
||||
- **„Das Schreiben konnte nicht abgelegt werden" sagte nichts.** Jetzt
|
||||
steht dort, was los ist: fehlender Ablagebereich mit Verweis auf den
|
||||
Installationsschritt, abgelehnte Zugangsdaten, nicht erreichbarer
|
||||
Speicher. Und in jedem Fall, ob deine Eingaben erhalten bleiben.
|
||||
|
||||
- **Zwei Einstellungen erreichten die Anwendung nie.**
|
||||
`ERSTEINRICHTUNG_GEHEIMNIS` und `VERTRAUE_PROXY` standen zwar in der
|
||||
Anleitung, wurden aber nicht an den Behälter durchgereicht. Bei
|
||||
`VERTRAUE_PROXY` fiel das nicht auf: Die Anmeldesperre zählte still eine
|
||||
Dimension weniger.
|
||||
|
||||
- **Die Beispielkonfiguration zeigte auf einen fremden Anbieter.**
|
||||
`S3_ENDPOINT` war mit einer externen Adresse vorbelegt und hebelte damit
|
||||
den richtigen Standard aus. Die Zeile ist jetzt auskommentiert.
|
||||
|
||||
### Neu
|
||||
|
||||
- **Die Anwendung prüft ihre Dateiablage selbst.** Sie schreibt eine
|
||||
winzige Datei, liest sie zurück und räumt sie weg — und beantwortet
|
||||
damit auf einmal, ob der Speicher erreichbar ist, der Ablagebereich
|
||||
existiert und geschrieben werden darf. Das Ergebnis steht unter
|
||||
`/api/gesundheit` und im Log. Bisher fiel eine kaputte Konfiguration
|
||||
erst auf, wenn jemand ein Dokument ablegen wollte.
|
||||
|
||||
- **Ein neuer Abschnitt in der Anleitung: „Wo deine Daten liegen".**
|
||||
Die drei Orte, der Pfad auf der Platte, welche Schlüssel unverzichtbar
|
||||
sind — und wie man prüft, ob zu jedem Akteneintrag auch eine Datei
|
||||
gehört.
|
||||
|
||||
### Geändert
|
||||
|
||||
- **Ausschlussfristen warnen nur noch, wenn sie näherrücken.** Auf dem
|
||||
Dashboard standen dauerhaft drei rote Kästen mit Warndreieck — für
|
||||
Aufwandspauschalen, die 2027, 2028 und 2029 ablaufen. Eine Warnung, die
|
||||
monatelang dasteht und nichts von einem will, stumpft ab; man übersieht
|
||||
die vierte, die etwas will. Ab drei Monaten vor Ablauf meldet sich eine
|
||||
Frist wieder, Versäumtes immer. In der Fristenliste stehen sie
|
||||
unverändert weiter.
|
||||
|
||||
## [1.11.0] – 2026-08-24
|
||||
|
||||
Diese Fassung geht aus einer Sicherheitsprüfung hervor. Sie ändert nichts
|
||||
@@ -1265,7 +1345,8 @@ Was davon wann angegangen wird, steht in [`docs/ROADMAP.md`](docs/ROADMAP.md).
|
||||
|
||||
---
|
||||
|
||||
[Unveröffentlicht]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.11.0...HEAD
|
||||
[Unveröffentlicht]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.12.0...HEAD
|
||||
[1.12.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.11.0...v1.12.0
|
||||
[1.11.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.10.2...v1.11.0
|
||||
[1.10.2]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.10.1...v1.10.2
|
||||
[1.10.1]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.10.0...v1.10.1
|
||||
|
||||
@@ -332,6 +332,136 @@ Anwendung jederzeit verlassen. Verlangt eine erneute Anmeldung.
|
||||
|
||||
---
|
||||
|
||||
## Der Jahresabschluss, Schritt für Schritt
|
||||
|
||||
Einmal im Jahr will das Gericht zwei Dinge: einen **Bericht über die
|
||||
Person** und eine **Rechnung über das Vermögen**. Beides bezieht sich auf
|
||||
dein Betreuungsjahr, nicht auf das Kalenderjahr.
|
||||
|
||||
Die Reihenfolge unten ist keine Formalie. Der erste Schritt liegt sechs
|
||||
Wochen vor dem Stichtag, weil manche Auskünfte so lange brauchen — wer erst
|
||||
am Stichtag daran denkt, wartet und gerät in Verzug.
|
||||
|
||||
### Sechs Wochen vorher: anfordern, was du nicht selbst hast
|
||||
|
||||
Manche Werte kannst du nicht nachsehen, sondern nur erfragen:
|
||||
|
||||
- **Rückkaufswerte** aller Lebens-, Renten- und Sterbegeldversicherungen
|
||||
- **Bescheinigungen** über Geschäftsguthaben, Bausparguthaben, Depotwerte
|
||||
- **Jahresabrechnungen**, die noch ausstehen
|
||||
|
||||
Die Anwendung legt dir dafür rechtzeitig eine Frist an. Schreib die Briefe
|
||||
gleich, wenn sie fällig wird — unter **Vorgänge** oder direkt am Vertrag.
|
||||
Jedes Schreiben bekommt eine **Wiedervorlage**: Kommt in drei Wochen keine
|
||||
Antwort, fasst du nach. Ohne Wiedervorlage geht eine unbeantwortete Anfrage
|
||||
unter, und das merkst du am Stichtag.
|
||||
|
||||
### Am Stichtag: Stände eintragen
|
||||
|
||||
**Konten** — *Vermögen & Kasse → Konten → Konto öffnen → „Neuen Kontostand
|
||||
festhalten"*. Für jedes Konto einzeln: Stichtag, Betrag, **Quelle**.
|
||||
|
||||
Die Quelle ist kein Beiwerk. Sie steht später neben der Zahl im Bericht,
|
||||
und ein Betrag ohne Herkunft ist gegenüber dem Gericht wenig wert. Schreib
|
||||
hinein, woher du die Zahl hast: „Kontoauszug 11/2026", „Online-Banking,
|
||||
abgerufen am 07.11.".
|
||||
|
||||
Vergiss die kleinen nicht: **Verwahrgeld im Heim** und die **Barkasse**
|
||||
sind Konten wie jedes andere.
|
||||
|
||||
**Vermögen** — *Vermögen & Kasse → Vermögen*. Immobilie, Fahrzeug,
|
||||
Wertgegenstände, Forderungen, Schulden. Jeder Posten bekommt einen
|
||||
**Wertstand** zum Stichtag, ebenfalls mit Quelle — und hier kannst du das
|
||||
**Nachweisdokument gleich anhängen**.
|
||||
|
||||
> **Du musst nicht auf den Tag genau treffen.** Die Anwendung nimmt je
|
||||
> Konto den letzten Stand am oder vor dem Stichtag. Weicht er ab, schreibt
|
||||
> sie es in den Bericht: „Der Stand von ‚Sparbuch' stammt vom 31.10.2026,
|
||||
> nicht vom Stichtag." Fehlt ein Stand ganz, steht das Konto ohne Betrag
|
||||
> drin — mit Hinweis, nicht mit einer erfundenen Null.
|
||||
|
||||
### Dann die Berichte
|
||||
|
||||
1. **Vermögensübersicht oder Rechnungslegung** — *Vermögen & Kasse →
|
||||
Berichte*, Betreuungsjahr wählen.
|
||||
2. **Belegmappe** — *Berichte → Belegmappe*, derselbe Zeitraum. Das ist die
|
||||
Anlage.
|
||||
3. **Jahresbericht** — *Berichte → Jahresbericht*. Entwurf überarbeiten,
|
||||
dann ausgeben.
|
||||
|
||||
Vorher lohnt ein Blick ins **Kassenbuch**: Buchungen ohne Beleg und ohne
|
||||
Begründung fallen dem Gericht auf. Entweder den Beleg nachreichen oder in
|
||||
einem Satz begründen, warum es keinen gibt.
|
||||
|
||||
---
|
||||
|
||||
## Was aussieht wie ein Konto, aber keines ist
|
||||
|
||||
**Genossenschaftsanteile** sind der Leitfall. Im Alltag redet man von einem
|
||||
Konto, das Gericht fragt einen „Kontoauszug" an — nur gibt es keinen. Am
|
||||
Automaten schon gar nicht.
|
||||
|
||||
Der Grund: Ein Geschäftsguthaben bei einer Genossenschaft ist **kein
|
||||
Zahlungskonto**, sondern eine **Forderung gegen die Genossenschaft**. Es
|
||||
laufen keine Buchungen darüber, es gibt keine Umsätze, und ausgezahlt wird
|
||||
erst nach Kündigung der Mitgliedschaft — mit Frist.
|
||||
|
||||
### Deshalb: als Vermögensposten anlegen, nicht als Konto
|
||||
|
||||
*Vermögen & Kasse → Vermögen → Posten anlegen*, Art **Forderung**.
|
||||
|
||||
Das ist kein Formalismus, sondern hat zwei handfeste Gründe:
|
||||
|
||||
1. **Nur am Vermögensposten kannst du den Nachweis anhängen.** Ein
|
||||
Kontostand trägt eine Quellenangabe, aber kein Dokument. Die
|
||||
Bescheinigung der Genossenschaft — genau das Blatt, das das Gericht
|
||||
sehen will — hätte am Konto keinen Platz.
|
||||
2. **Als Konto stünde es in der Kontenliste** und würde dort einen Auszug
|
||||
und Buchungen erwarten, die es nie geben wird.
|
||||
|
||||
Trag dann einen **Wertstand** zum Stichtag ein, mit der Bescheinigung als
|
||||
Nachweisdokument.
|
||||
|
||||
### Den „Kontoauszug" bekommst du schriftlich
|
||||
|
||||
Die Genossenschaft stellt dir auf Anfrage eine **Bescheinigung über das
|
||||
Geschäftsguthaben** zum Stichtag aus. Viele verschicken ohnehin einmal
|
||||
jährlich eine Abrechnung nach der Generalversammlung — dann genügt es,
|
||||
diese abzuwarten und einzuscannen.
|
||||
|
||||
Fehlt sie, schreib hin. Unter **Vorlagen** liegt das *Auskunftsersuchen*
|
||||
als Kopiervorlage; darin änderst du die Bitte auf „Bescheinigung über das
|
||||
Geschäftsguthaben zum <Stichtag>". Und setz eine Wiedervorlage.
|
||||
|
||||
> **„Der Wert bleibt doch immer gleich."** Meistens ja — aber nicht
|
||||
> zwangsläufig. Wird eine **Dividende dem Geschäftsguthaben
|
||||
> gutgeschrieben** statt ausgezahlt, wächst es. Ebenso, wenn weitere
|
||||
> Anteile gezeichnet wurden. Genau deshalb fragt man einmal im Jahr nach,
|
||||
> statt den Vorjahreswert fortzuschreiben.
|
||||
|
||||
### Dieselbe Bauform, andere Namen
|
||||
|
||||
Nach demselben Muster behandelst du alles, was „Konto" heißt und keinen
|
||||
Auszug kennt:
|
||||
|
||||
| Was | Als was anlegen | Nachweis |
|
||||
|---|---|---|
|
||||
| Genossenschaftsanteile | Vermögensposten, *Forderung* | Bescheinigung der Genossenschaft |
|
||||
| Bausparvertrag | Vermögensposten, *Forderung* | Jahreskontoauszug der Bausparkasse |
|
||||
| Sterbegeld-, Lebensversicherung | **Vertrag**, Rückkaufswert am Vertrag | Rückkaufswertmitteilung |
|
||||
| Wertpapierdepot | Konto, Art *Depot* | Depotauszug zum Stichtag |
|
||||
| Darlehen, das die Person schuldet | Vermögensposten, *Verbindlichkeit* | Saldenbestätigung der Bank |
|
||||
|
||||
Das Depot ist die Ausnahme: Dort laufen Umsätze, es gibt einen Auszug, und
|
||||
es verhält sich wie ein Konto.
|
||||
|
||||
Bei Versicherungen nimmst du **nicht** den Vermögensbereich, sondern den
|
||||
Vertrag selbst — dort gehört der Rückkaufswert hin, samt Historie über die
|
||||
Jahre.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Anmelden mit dem Finger: Passkeys
|
||||
|
||||
Drei Felder auszufüllen, nur um kurz eine Frist nachzusehen, ist lästig —
|
||||
|
||||
+128
-13
@@ -34,6 +34,7 @@ durchgeht — dann weißt du im Fehlerfall genau, wo es hakt.
|
||||
- [Schritt 9 — Sicherung prüfen](#schritt-9--sicherung-prüfen)
|
||||
- [Schritt 10 — Wartungslauf einrichten](#schritt-10--wartungslauf-einrichten)
|
||||
- [Schritt 11 — Schlüssel in Sicherheit bringen](#schritt-11--schlüssel-in-sicherheit-bringen)
|
||||
- [Wo deine Daten liegen](#wo-deine-daten-liegen)
|
||||
- [Aktualisieren](#aktualisieren)
|
||||
- [Wenn etwas nicht klappt](#wenn-etwas-nicht-klappt)
|
||||
- [Abnahmeliste](#abnahmeliste)
|
||||
@@ -322,7 +323,7 @@ betreuungs-cockpit-sicherung-1 Up
|
||||
```bash
|
||||
# Antwortet die Anwendung? (Port anpassen, falls du APP_PORT gesetzt hast)
|
||||
curl -s http://127.0.0.1:3000/api/gesundheit
|
||||
# {"status":"bereit","version":"1.11.1","commit":"...",
|
||||
# {"status":"bereit","version":"1.12.0","commit":"...",
|
||||
# "speicher":{"status":"bereit","treiber":"s3"}}
|
||||
```
|
||||
|
||||
@@ -375,22 +376,37 @@ nichts kaputtmacht.
|
||||
Der Objektspeicher braucht einen Ablagebereich für die Dokumente:
|
||||
|
||||
```bash
|
||||
set -a && source .env && set +a
|
||||
|
||||
docker compose exec minio mc alias set lokal \
|
||||
http://localhost:9000 "$S3_ACCESS_KEY_ID" "$S3_SECRET_ACCESS_KEY"
|
||||
|
||||
docker compose exec minio mc mb lokal/betreuungs-cockpit
|
||||
docker compose exec minio sh -c '
|
||||
mc alias set lokal http://localhost:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null &&
|
||||
mc mb "lokal/$S3_BUCKET" 2>/dev/null || true
|
||||
mc ls lokal
|
||||
'
|
||||
```
|
||||
|
||||
### Kontrolle
|
||||
Es muss eine Zeile mit dem Namen deines Ablagebereichs erscheinen —
|
||||
standardmäßig `betreuungs-cockpit/`.
|
||||
|
||||
```bash
|
||||
docker compose exec minio mc ls lokal
|
||||
# [...] betreuungs-cockpit/
|
||||
```
|
||||
> **Warum der Befehl so umständlich aussieht.** Er holt die Zugangsdaten
|
||||
> aus den Variablen, die im MinIO-Behälter ohnehin stehen. Das hat drei
|
||||
> Gründe, und jeder davon hat schon einmal Zeit gekostet:
|
||||
>
|
||||
> 1. **Kein Passwort in der Befehlszeile** — es stünde sonst in der
|
||||
> Verlaufsdatei deiner Shell.
|
||||
> 2. **Kein `source .env`** — exportierte Werte haben Vorrang vor der
|
||||
> `.env`-Datei und überstimmen danach *jedes* `docker compose up`. Wer
|
||||
> später die `.env` ändert und neu startet, sieht keine Wirkung und
|
||||
> sucht den Fehler überall, nur nicht in der eigenen Shell.
|
||||
> 3. **Der Alias überlebt kein Neuerstellen.** `mc alias set` schreibt
|
||||
> nach `/root/.mc` **im Behälter**. Nach jedem `docker compose up -d
|
||||
> minio` ist er weg, und `mc ls lokal` liefert dann kommentarlos nichts
|
||||
> — man hält seine Dokumente für verschwunden. Deshalb setzt jeder
|
||||
> Aufruf den Alias selbst.
|
||||
>
|
||||
> Der Bucket-Name kommt aus `$S3_BUCKET` und ist nicht fest verdrahtet:
|
||||
> Sonst zeigen Anwendung und Ablagebereich aneinander vorbei, sobald
|
||||
> jemand den Namen ändert.
|
||||
|
||||
> **Serverseitige Verschluesselung?** Brauchst du nicht. Die Anwendung
|
||||
> **Serverseitige Verschlüsselung?** Brauchst du nicht. Die Anwendung
|
||||
> verschlüsselt jede Datei selbst, bevor sie den Server verlässt — mit
|
||||
> `ENC_KEY_V1`, der den Anbieter nie erreicht. Das mitgelieferte MinIO
|
||||
> läuft ohne KMS und würde jede Ablage ablehnen, sobald `S3_SSE` in der
|
||||
@@ -913,6 +929,105 @@ Denk an die letzten Pflichten:
|
||||
|
||||
---
|
||||
|
||||
## Wo deine Daten liegen
|
||||
|
||||
Drei Orte, und keiner davon ist ein Ordner, den du einfach öffnest.
|
||||
|
||||
| Was | Wo | In der täglichen Sicherung? |
|
||||
|---|---|---|
|
||||
| **Dokumente** (Bescheide, Schreiben, Belege) | Docker-Volume `minio-daten` | ja |
|
||||
| **Datenbank** (Akte, Fristen, Buchungen) | Docker-Volume `postgres-daten` | ja |
|
||||
| **Sicherungen** | `./backups` neben der `docker-compose.yml` | — |
|
||||
|
||||
### Den Pfad auf der Platte finden
|
||||
|
||||
```bash
|
||||
docker volume inspect betreuungs-cockpit_minio-daten --format '{{.Mountpoint}}'
|
||||
# /var/lib/docker/volumes/betreuungs-cockpit_minio-daten/_data
|
||||
```
|
||||
|
||||
Heißt dein Projektverzeichnis anders, heißen auch die Volumes anders —
|
||||
Docker stellt den Verzeichnisnamen voran:
|
||||
|
||||
```bash
|
||||
docker volume ls | grep -E "minio-daten|postgres-daten"
|
||||
```
|
||||
|
||||
> **Dort hineinzusehen bringt nichts.** MinIO legt jedes Dokument als
|
||||
> eigenes Verzeichnis mit Metadatei und Datenteilen ab, und der Inhalt ist
|
||||
> verschlüsselt. Du fändest dort keine anklickbaren PDFs, sondern
|
||||
> Fragmente. Wer die Dokumente sehen will, nimmt die Anwendung.
|
||||
|
||||
### Nachsehen, was abgelegt ist
|
||||
|
||||
```bash
|
||||
docker compose exec minio sh -c '
|
||||
mc alias set lokal http://localhost:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null &&
|
||||
mc ls --recursive "lokal/$S3_BUCKET"
|
||||
'
|
||||
```
|
||||
|
||||
Je Dokument eine Zeile unter `betreute/<id>/dokumente/<uuid>.pdf`.
|
||||
|
||||
### Prüfen, ob Akte und Speicher übereinstimmen
|
||||
|
||||
Die wichtigste Kontrolle: Zu **jedem** Eintrag in der Akte muss eine Datei
|
||||
gehören. Fehlt sie, belegt eine Prüfsumme ein Dokument, das es nicht mehr
|
||||
gibt — gegenüber dem Betreuungsgericht der schlechteste Zustand.
|
||||
|
||||
```bash
|
||||
docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -c "SELECT \"objektSchluessel\" FROM dokument WHERE \"geloeschtAm\" IS NULL ORDER BY 1;"' | sort > /tmp/erwartet.txt
|
||||
|
||||
docker compose exec minio sh -c '
|
||||
mc alias set lokal http://localhost:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null &&
|
||||
mc ls --recursive "lokal/$S3_BUCKET"
|
||||
' | awk '{print $NF}' | sort > /tmp/vorhanden.txt
|
||||
|
||||
echo "In der Akte: $(wc -l < /tmp/erwartet.txt) Im Speicher: $(wc -l < /tmp/vorhanden.txt)"
|
||||
echo "--- Fehlt im Speicher (Handlungsbedarf): ---"
|
||||
comm -23 /tmp/erwartet.txt /tmp/vorhanden.txt
|
||||
echo "--- Verwaist im Speicher (harmlos): ---"
|
||||
comm -13 /tmp/erwartet.txt /tmp/vorhanden.txt
|
||||
```
|
||||
|
||||
Beide Zahlen gleich und beide Listen leer — dann passt es.
|
||||
|
||||
Ein Eintrag unter *„Fehlt im Speicher"* ist ein Akteneintrag ohne Datei.
|
||||
Er entsteht, wenn eine Ablage scheitert. Solche Einträge löschen und das
|
||||
Dokument neu anlegen.
|
||||
|
||||
### Ohne welche Schlüssel nichts davon lesbar ist
|
||||
|
||||
| Schlüssel | Ohne ihn |
|
||||
|---|---|
|
||||
| `ENC_KEY_V1` | Diagnosen, IBANs, Notizen und alle Dokumente bleiben unlesbar — auch aus jeder Sicherung |
|
||||
| `SICHERUNG_PASSWORT` | keine Sicherung lässt sich öffnen |
|
||||
| `MINIO_KMS_SECRET_KEY` | nur falls gesetzt: MinIO gibt die Objekte nicht mehr heraus |
|
||||
|
||||
Alle gehören an die zwei Orte aus Schritt 11, und keiner davon ist dieser
|
||||
Server.
|
||||
|
||||
### Eigene Anpassungen, die ein Update überleben
|
||||
|
||||
Änderungen an der `docker-compose.yml` legt `scripts/update.sh` bei jedem
|
||||
Update per `git stash` beiseite — sie sind versioniert. Wer etwas dauerhaft
|
||||
ändern will, legt stattdessen eine **`docker-compose.override.yml`**
|
||||
daneben:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
environment:
|
||||
EIGENER_WERT: ${EIGENER_WERT:-}
|
||||
```
|
||||
|
||||
Docker Compose liest sie automatisch mit. `update.sh` prüft nur
|
||||
versionierte Dateien (`git status --untracked-files=no`) und lässt sie
|
||||
deshalb unangetastet.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Aktualisieren
|
||||
|
||||
```bash
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "betreuungs-cockpit",
|
||||
"version": "1.11.0",
|
||||
"version": "1.12.0",
|
||||
"private": true,
|
||||
"description": "Arbeits- und Dokumentationswerkzeug fuer die ehrenamtliche rechtliche Betreuung",
|
||||
"engines": {
|
||||
|
||||
@@ -100,7 +100,7 @@ echo "$(date -Iseconds) Datenbank wiederhergestellt."
|
||||
# --- Dokumente ---------------------------------------------------------------
|
||||
# Zu "cockpit_2026-09-01_0315.dump.enc" gehoert
|
||||
# "dokumente_2026-09-01_0315.tar.gz.enc" - gleicher Zeitstempel, anderer
|
||||
# Name. Fehlt sie, stammt die Sicherung aus einer Fassung vor v1.11.1.
|
||||
# Name. Fehlt sie, stammt die Sicherung aus einer Fassung vor v1.12.0.
|
||||
ZEITSTEMPEL="$(basename "$DATEI" | sed -n 's/^cockpit_\(.*\)\.dump\.enc$/\1/p')"
|
||||
DOKUMENTE_DATEI="$(dirname "$DATEI")/dokumente_${ZEITSTEMPEL}.tar.gz.enc"
|
||||
AUSPACKZIEL="$(dirname "$DATEI")/dokumente-wiederhergestellt_${ZEITSTEMPEL}"
|
||||
@@ -109,7 +109,7 @@ if [ ! -f "$DOKUMENTE_DATEI" ]; then
|
||||
echo ""
|
||||
echo "WARNUNG: Zu dieser Sicherung gibt es kein Dokumentenarchiv."
|
||||
echo " Erwartet wurde: $DOKUMENTE_DATEI"
|
||||
echo " Sicherungen aus Fassungen vor v1.11.1 enthalten nur die"
|
||||
echo " Sicherungen aus Fassungen vor v1.12.0 enthalten nur die"
|
||||
echo " Datenbank. Die Akte ist dann vollstaendig, die Dateien"
|
||||
echo " dahinter fehlen."
|
||||
else
|
||||
|
||||
@@ -6,7 +6,7 @@ import { speicherfehlerErkennen } from '@/lib/speicher/fehler'
|
||||
/**
|
||||
* Ist die Dateiablage benutzbar?
|
||||
*
|
||||
* Bis v1.11.1 fiel eine kaputte Speicherkonfiguration erst auf, wenn
|
||||
* Bis v1.12.0 fiel eine kaputte Speicherkonfiguration erst auf, wenn
|
||||
* jemand ein Dokument ablegen wollte. Auf dem Livesystem waren das
|
||||
* mehrere Wochen nach der Installation - und dann mitten in der Arbeit,
|
||||
* mit einer Meldung, die nichts sagte.
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
/** Muss zur "version" in der package.json passen. */
|
||||
export const VERSION = '1.11.0'
|
||||
export const VERSION = '1.12.0'
|
||||
|
||||
/**
|
||||
* Kurzer Git-Stand, beim Bauen hineingereicht:
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
/**
|
||||
* Die Installationsanleitung muss ausführbar sein, nicht nur lesbar.
|
||||
*
|
||||
* Anlass ist eine Fehlersuche vom 31.08.2026. Drei Stellen der Anleitung
|
||||
* haben dabei Zeit gekostet — keine davon war falsch formuliert, alle drei
|
||||
* waren schlicht nicht durchdacht:
|
||||
*
|
||||
* 1. `mc alias set` wurde einmalig gesetzt. Der Alias liegt in `/root/.mc`
|
||||
* **im Behälter** und ist nach jedem `docker compose up -d minio` weg.
|
||||
* `mc ls lokal` liefert dann kommentarlos nichts — man hält seine
|
||||
* Dokumente für verschwunden.
|
||||
* 2. Der Bucket-Name stand fest im Befehl, die Anwendung liest ihn aus
|
||||
* `S3_BUCKET`. Weichen sie ab, zeigen beide aneinander vorbei.
|
||||
* 3. `set -a && source .env` exportiert alle Werte in die Shell — und die
|
||||
* haben danach Vorrang vor der `.env`-Datei. Wer später etwas ändert
|
||||
* und neu startet, sieht keine Wirkung.
|
||||
*
|
||||
* Diese Prüfungen lesen den Text. Sie können die Anleitung nicht
|
||||
* ausführen, aber sie schlagen an, sobald jemand in eine der Fallen
|
||||
* zurückfällt.
|
||||
*/
|
||||
|
||||
const anleitung = readFileSync('docs/INSTALLATION.md', 'utf8')
|
||||
|
||||
/** Alle Zeilen mit einem mc-Aufruf, ohne Fließtext und Zitate. */
|
||||
function mcZeilen(): string[] {
|
||||
return anleitung
|
||||
.split('\n')
|
||||
.filter((zeile) => /\bmc (ls|mb|mirror|cp|rm|stat)\b/.test(zeile))
|
||||
.filter((zeile) => !zeile.trim().startsWith('>'))
|
||||
}
|
||||
|
||||
describe('Installationsanleitung', () => {
|
||||
it('setzt bei jedem mc-Aufruf den Alias selbst', () => {
|
||||
// Sonst funktioniert der Befehl nur, solange der Behaelter nicht neu
|
||||
// erstellt wurde - und das sagt einem niemand.
|
||||
const abschnitte = anleitung.split('```')
|
||||
const ohneAlias = abschnitte
|
||||
.filter((block) => /\bmc (ls|mb|mirror|cp|rm|stat)\b/.test(block))
|
||||
.filter((block) => !block.includes('mc alias set'))
|
||||
|
||||
expect(
|
||||
ohneAlias.length,
|
||||
'Ein mc-Befehl ohne "mc alias set" im selben Block läuft nur, solange ' +
|
||||
'der MinIO-Behälter nicht neu erstellt wurde.',
|
||||
).toBe(0)
|
||||
})
|
||||
|
||||
it('verdrahtet den Namen des Ablagebereichs nicht fest', () => {
|
||||
const festVerdrahtet = mcZeilen().filter((zeile) => /lokal\/betreuungs-cockpit/.test(zeile))
|
||||
|
||||
expect(
|
||||
festVerdrahtet,
|
||||
'Der Name gehört aus $S3_BUCKET - sonst zeigen Anwendung und ' +
|
||||
'Ablagebereich aneinander vorbei, sobald jemand ihn ändert.',
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('empfiehlt kein "source .env" mehr', () => {
|
||||
// Exportierte Werte ueberstimmen danach jedes "docker compose up".
|
||||
const zeilen = anleitung
|
||||
.split('\n')
|
||||
.filter((zeile) => !zeile.trim().startsWith('>'))
|
||||
.filter((zeile) => !zeile.trim().startsWith('#'))
|
||||
.filter((zeile) => /source \.env/.test(zeile))
|
||||
|
||||
expect(zeilen).toEqual([])
|
||||
})
|
||||
|
||||
it('erklärt trotzdem, warum es nicht empfohlen wird', () => {
|
||||
// Weglassen allein hilft nicht: Wer den Kniff von woanders kennt,
|
||||
// benutzt ihn weiter.
|
||||
expect(anleitung).toMatch(/Vorrang vor der/)
|
||||
})
|
||||
|
||||
it('sagt, wo die Daten liegen', () => {
|
||||
expect(anleitung).toMatch(/## Wo deine Daten liegen/)
|
||||
expect(anleitung).toMatch(/docker volume inspect/)
|
||||
expect(anleitung).toMatch(/minio-daten/)
|
||||
expect(anleitung).toMatch(/postgres-daten/)
|
||||
})
|
||||
|
||||
it('zeigt, wie man Akte und Speicher abgleicht', () => {
|
||||
// Die wichtigste Kontrolle: Zu jedem Akteneintrag muss eine Datei
|
||||
// gehoeren, sonst belegt eine Pruefsumme ein Dokument, das es nicht
|
||||
// mehr gibt.
|
||||
expect(anleitung).toMatch(/objektSchluessel/)
|
||||
expect(anleitung).toMatch(/comm -23/)
|
||||
})
|
||||
|
||||
it('nennt die Schlüssel, ohne die nichts mehr lesbar ist', () => {
|
||||
const abschnitt = anleitung.slice(anleitung.indexOf('## Wo deine Daten liegen'))
|
||||
for (const schluessel of ['ENC_KEY_V1', 'SICHERUNG_PASSWORT', 'MINIO_KMS_SECRET_KEY']) {
|
||||
expect(abschnitt, `${schluessel} fehlt in der Aufzählung`).toContain(schluessel)
|
||||
}
|
||||
})
|
||||
|
||||
it('weist docker-compose.override.yml als Ort für eigene Anpassungen aus', () => {
|
||||
expect(anleitung).toMatch(/docker-compose\.override\.yml/)
|
||||
expect(readFileSync('.gitignore', 'utf8')).toMatch(/docker-compose\.override\.yml/)
|
||||
})
|
||||
|
||||
it('behauptet nicht mehr, die Dokumente seien ungesichert', () => {
|
||||
// Stand bis v1.12.0 so drin und stimmt seit der Sicherung nicht mehr.
|
||||
expect(anleitung).not.toMatch(/Was das Backup nicht enthält:.*Dateien im Objektspeicher/s)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Die Bedienungsanleitung muss die Fragen beantworten, die tatsächlich
|
||||
* gestellt werden.
|
||||
*
|
||||
* Zwei davon kamen aus der Praxis:
|
||||
*
|
||||
* 1. „Wie mache ich den Jahresabschluss?" — die Anleitung beschrieb, was
|
||||
* in jedem Bericht steht, aber nicht die Reihenfolge und nicht, was man
|
||||
* vorher besorgen muss. Rückkaufswerte brauchen Wochen.
|
||||
* 2. „Das Gericht will einen Kontoauszug für die Genossenschaftsanteile —
|
||||
* den kann ich nicht am Automaten holen." Richtig: Es ist kein
|
||||
* Zahlungskonto, sondern eine Forderung, und der Nachweis ist eine
|
||||
* Bescheinigung, die man anfordert.
|
||||
*
|
||||
* Der zweite Fall ist der wichtigere, weil er zu einem stillen Fehler
|
||||
* führt: Wer die Anteile als Konto anlegt, kann die Bescheinigung nicht am
|
||||
* Wert hinterlegen — genau das Dokument, das das Gericht sehen will.
|
||||
*/
|
||||
describe('Bedienungsanleitung', () => {
|
||||
const bedienung = readFileSync('docs/ANLEITUNG.md', 'utf8')
|
||||
|
||||
it('erklärt den Jahresabschluss mit Reihenfolge', () => {
|
||||
expect(bedienung).toMatch(/## Der Jahresabschluss, Schritt für Schritt/)
|
||||
})
|
||||
|
||||
it('sagt, was vor dem Stichtag zu besorgen ist', () => {
|
||||
// Wer erst am Stichtag daran denkt, wartet Wochen auf die Auskunft.
|
||||
const abschnitt = bedienung.slice(bedienung.indexOf('## Der Jahresabschluss'))
|
||||
expect(abschnitt).toMatch(/Sechs Wochen vorher/)
|
||||
expect(abschnitt).toMatch(/Rückkaufswerte/)
|
||||
expect(abschnitt).toMatch(/Wiedervorlage/)
|
||||
})
|
||||
|
||||
it('besteht auf der Quellenangabe beim Kontostand', () => {
|
||||
// Ein Betrag ohne Herkunft ist gegenueber dem Gericht wenig wert.
|
||||
const abschnitt = bedienung.slice(bedienung.indexOf('## Der Jahresabschluss'))
|
||||
expect(abschnitt).toMatch(/Quelle/)
|
||||
expect(abschnitt).toMatch(/Kontoauszug 11\/2026|Online-Banking/)
|
||||
})
|
||||
|
||||
describe('Was aussieht wie ein Konto', () => {
|
||||
const abschnitt = bedienung.slice(bedienung.indexOf('## Was aussieht wie ein Konto'))
|
||||
|
||||
it('gibt es überhaupt', () => {
|
||||
expect(abschnitt.length).toBeGreaterThan(500)
|
||||
})
|
||||
|
||||
it('führt Genossenschaftsanteile als Forderung, nicht als Konto', () => {
|
||||
expect(abschnitt).toMatch(/Genossenschaftsanteile/)
|
||||
expect(abschnitt).toMatch(/Art \*\*Forderung\*\*/)
|
||||
})
|
||||
|
||||
it('nennt den Grund, statt ihn vorauszusetzen', () => {
|
||||
// Ohne Begruendung legt der naechste Leser es wieder als Konto an.
|
||||
expect(abschnitt).toMatch(/Nachweis anhängen|Nachweis anhaengen/)
|
||||
expect(abschnitt).toMatch(/kein Dokument/)
|
||||
})
|
||||
|
||||
it('warnt davor, den Vorjahreswert einfach fortzuschreiben', () => {
|
||||
// "Der Wert bleibt doch gleich" stimmt meistens - aber eine
|
||||
// gutgeschriebene Dividende erhoeht das Geschaeftsguthaben.
|
||||
expect(abschnitt).toMatch(/Dividende/)
|
||||
})
|
||||
|
||||
it('sagt, wie man den Nachweis bekommt', () => {
|
||||
expect(abschnitt).toMatch(/Bescheinigung über das\s+Geschäftsguthaben/)
|
||||
expect(abschnitt).toMatch(/Auskunftsersuchen/)
|
||||
})
|
||||
|
||||
it('behandelt die verwandten Fälle mit', () => {
|
||||
for (const fall of ['Bausparvertrag', 'Depot', 'Verbindlichkeit']) {
|
||||
expect(abschnitt, `${fall} fehlt in der Übersicht`).toContain(fall)
|
||||
}
|
||||
})
|
||||
|
||||
it('schickt Versicherungen zum Vertrag, nicht ins Vermögen', () => {
|
||||
// Der Rueckkaufswert gehoert an den Vertrag, samt Historie.
|
||||
expect(abschnitt).toMatch(/nicht\*\* den Vermögensbereich|Vertrag selbst/)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
/**
|
||||
* Die Bereitschaftsprüfung der Dateiablage.
|
||||
*
|
||||
* Bis v1.11.1 fiel eine kaputte Speicherkonfiguration erst auf, wenn
|
||||
* Bis v1.12.0 fiel eine kaputte Speicherkonfiguration erst auf, wenn
|
||||
* jemand ein Dokument ablegen wollte. Auf dem Livesystem waren das
|
||||
* mehrere Wochen nach der Installation — und dann mitten in der Arbeit,
|
||||
* mit einer Meldung, die nichts sagte.
|
||||
|
||||
Reference in New Issue
Block a user