Florian WildeandClaude Opus 5.5 31d7779f45 Bremse gegen Passwortraten
Nach fuenf Fehlversuchen wird eine E-Mail-Adresse gesperrt, erst eine
Minute, dann doppelt so lang, hoechstens 15 Minuten. Gezaehlt wird je
Adresse, auch fuer unbekannte - sonst verriete die Sperre, welche Konten
es gibt. Waehrend der Sperre kommt auch das richtige Passwort nicht durch.

Jeder Versuch zaehlt sofort und wird bei Erfolg zurueckgenommen. Zwischen
Sperrpruefung und Zaehlen liegt damit kein await; gleichzeitige Anfragen
kommen so nicht alle an der Sperre vorbei.

Der Zaehler liegt im Speicher des einen Prozesses. Ein Neustart setzt ihn
zurueck - der Notausgang, falls sich das einzige Ausbilderkonto aussperrt.
Beim Aufraeumen wird eine laufende Sperre nie verdraengt.

Die Containerabnahme prueft die Sperre im laufenden Abbild. Das
Abnahmeprotokoll ist gegen diesen Stand neu erzeugt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 18:25:04 +02:00
2026-10-04 18:25:04 +02:00
2026-10-04 18:25:04 +02:00
2026-10-04 18:25:04 +02:00
2026-10-04 18:25:04 +02:00
2026-10-04 18:25:04 +02:00
2026-10-03 00:50:59 +02:00
2026-10-03 00:50:59 +02:00
2026-10-04 18:25:04 +02:00

Lern-Bar

Selbst gehostete Anwendung für die betriebliche Ausbildung zum/zur Fachinformatiker/-in für Systemintegration. Sie ersetzt den Stapel PDFs: Wochenplan, Handouts, Merkkarten, Praxisaufgaben, Checklisten, Lernzielkontrollen und – der wichtigste Teil – den gesetzlich vorgeschriebenen Ausbildungsnachweis („Berichtsheft").

Gebaut für einen Ausbilder und ein bis drei Auszubildende. Kein Lernplattform-Baukasten, keine Mandanten, keine Cloud.

Stand: Alle zehn Bausteine sind fertig – Fassung 1.0.0.


In fünf Minuten zum laufenden System

Voraussetzungen

  • Betrieb: Docker mit Compose-Erweiterung (Docker 24 oder neuer)
  • Entwicklung: Node.js 22 oder neuer, Git
  • Ein Reverse Proxy, der TLS beendet (nginx, Caddy, Traefik) – die Anwendung selbst spricht nur HTTP und lauscht nur auf der Schleife

Betrieb mit Docker

git clone <repository-url> lernbar
cd lernbar
docker compose up -d --build

Die Anwendung lauscht danach auf 127.0.0.1:3000. Erstes Ausbilderkonto anlegen:

docker compose exec lernbar node werkzeuge/konto.js \
  --name "Vorname Nachname" --email ausbilder@betrieb.de --rolle ausbilder

Ohne --passwort wird eines erzeugt und ausgegeben. Gibt es die Adresse schon, setzt derselbe Aufruf das Passwort neu – das ist zugleich der Weg, wenn das einzige Ausbilderkonto sein Passwort vergessen hat.

Danach im Browser anmelden, unter Verwaltung das Azubikonto anlegen und das Ausbildungsverhältnis mit Beginndatum einrichten.

Entwicklung

npm install
npm approve-scripts better-sqlite3 esbuild unrs-resolver   # nur beim ersten Mal
npm run konto:anlegen -- --name "Vorname Nachname" --email du@betrieb.de --rolle ausbilder
npm run dev

Ohne npm approve-scripts bleiben better-sqlite3 und esbuild unübersetzt – npm blockiert seit Version 11 die Installationsskripte fremder Pakete.

Die Anwendung läuft dann auf http://localhost:3000. Datenbank und Anhänge landen unter daten/ (nicht im Repository).


Beispiel für einen Reverse Proxy

server {
    listen 443 ssl http2;
    server_name lernbar.betrieb.local;

    ssl_certificate     /etc/ssl/lernbar.crt;
    ssl_certificate_key /etc/ssl/lernbar.key;

    client_max_body_size 30m;   # Fotos vom Aufbau als Anhang (Grenze der Anwendung: 25 MB)

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Inhalte pflegen

Die fachlichen Inhalte sind Dateien im Repository, keine Datenbankeinträge – Bearbeiten in VS Code, Versionieren mit Git, Sichern mit git clone.

content/
  wochen/                01-arbeitsalltag.yaml …
  handouts/              N3-subnetting-rechnen.md …
  aufgaben/              2.2-notebooks-vergleichen.md …
  checklisten/           02-windows-installieren.yaml …
  lernzielkontrollen/    woche-03.yaml …
  glossar.yaml
  feedbackgespraech.yaml Leitfaden und Kriterien für das Gespräch
  rahmenplan.yaml        Berufsbildpositionen und Lernfelder

Ein Handout ist Markdown mit YAML-Kopf:

---
id: N3
titel: Subnetting rechnen – der Weg in vier Schritten
block: netzwerk
woche: 3
worum: Eine Adresse und eine Maske reichen, um Netz, Broadcast und Hostbereich zu bestimmen.
rahmenplan: [B2, A8]
lernfeld: LF3
merksatz: Blockgröße finden, Vielfache aufschreiben, eigene Adresse einsortieren.
karten:
  - frage: Zu welchem Netz gehört 192.168.10.200 bei der Maske 255.255.255.224?
    antwort: Blockgröße 32, Netze bei .192 und .224 – die Adresse gehört zu 192.168.10.192/27.
---

## Das Wesentliche
…

Beim Start werden alle Inhalte eingelesen und geprüft: Jede Kennung muss es geben, jedes Handout gehört zu einer Woche, jede Rahmenplanposition steht in rahmenplan.yaml. Passt etwas nicht, startet die Anwendung nicht und nennt Datei und Stelle. Prüfen ohne Start:

npm run inhalte:pruefen

Datensicherung

Gesichert werden Datenbank und Anhänge. Die Inhalte stecken im Repository und brauchen keine eigene Sicherung.

Sicherung ziehen

# im laufenden Container
docker compose exec lernbar /app/werkzeuge/sicherung.sh /daten/sicherungen

# aus dem Volume herausholen
docker compose cp lernbar:/daten/sicherungen ./sicherungen

Auf dem Entwicklungsrechner:

LERNBAR_DATEN=./daten npm run backup -- ./sicherungen

Das Archiv heißt lernbar-JJJJMMTT-HHMMSS.tar.gz und enthält lernbar.sqlite, anhaenge/ und eine kurze Beschreibung. Die Kopie der Datenbank entsteht über die Sicherungsschnittstelle von SQLite, ist also auch im laufenden Betrieb in sich stimmig.

Zurückspielen

docker compose stop lernbar
docker compose cp ./sicherungen/lernbar-20260910-0300.tar.gz lernbar:/tmp/
docker compose start lernbar
docker compose exec lernbar /app/werkzeuge/wiederherstellen.sh /tmp/lernbar-20260910-0300.tar.gz
docker compose restart lernbar

Der bisherige Stand wird nicht gelöscht, sondern nach /daten/verdraengt-<zeitstempel>/ verschoben.

Nächtlich per Cronjob

/etc/cron.d/lernbar-sicherung:

# Jede Nacht um 03:00 sichern, Archive älter als 30 Tage wegräumen.
SHELL=/bin/sh
PATH=/usr/local/bin:/usr/bin:/bin

0 3 * * * root cd /opt/lernbar && docker compose exec -T lernbar /app/werkzeuge/sicherung.sh /daten/sicherungen >> /var/log/lernbar-sicherung.log 2>&1
30 3 * * * root cd /opt/lernbar && docker compose exec -T lernbar find /daten/sicherungen -name 'lernbar-*.tar.gz' -mtime +30 -delete

Liegt das Volume auf einem Pfad, den die Sicherung des Servers ohnehin mitnimmt, ist damit alles erledigt. Sonst zusätzlich mit docker compose cp herauskopieren und wegschaffen.


Aktualisieren

Auf dem Linux-Server:

cd /opt/lernbar
./update.sh

Das Skript sichert zuerst, holt den Quelltext, baut das Abbild neu, schaltet um und wartet auf das Lebenszeichen. Meldet sich die neue Fassung nicht gesund, schaltet es selbsttätig auf das vorherige Abbild zurück und zeigt die letzten Protokollzeilen.

Angabe Wirkung
(ohne) sichern, holen, bauen, umschalten
--ohne-git nur neu bauen und neu starten
--ohne-sicherung Sicherung überspringen (nicht empfohlen)
--zweig main einen bestimmten Git-Zweig holen
--hilfe Kurzhilfe

Umgebungsvariablen

Name Vorgabe im Container Bedeutung
PORT 3000 Lauschport
TZ Europe/Berlin Zeitzone
LERNBAR_DATEN /daten Wurzel für Datenbank und Anhänge
LERNBAR_DB <daten>/lernbar.sqlite abweichender Pfad der Datenbank
LERNBAR_INHALTE /app/content Wurzel der Inhaltsdateien
LERNBAR_MIGRATIONEN /app/migrationen Verzeichnis der SQL-Migrationen

Prüfen und Testen

npm run typecheck    # TypeScript
npm run lint         # ESLint
npm run test         # Vitest – Logik, die wehtut (315 Tests)
npm run abnahme      # der Abnahmelauf, siehe unten
npm run abnahme:container   # derselbe Weg gegen das Docker-Abbild

Der Abnahmelauf

npm run abnahme ist die Prüfung, auf die es ankommt. Er legt ein Wegwerfverzeichnis an, startet die Anwendung mit einer frischen, leeren Datenbank und spielt den ganzen Weg durch:

  1. Migrationen laufen an, das erste Ausbilderkonto entsteht auf der Kommandozeile
  2. Die Inhaltsprüfung wird mit einem absichtlich eingebauten Fehler auf die Probe gestellt – sie muss Datei, Zeile und Wert nennen
  3. Der Ausbilder legt ein Azubikonto an; das Passwort darf danach nirgends mehr auftauchen, in der Datenbank steht ein Argon2id-Hash
  4. Der Azubi liest ein Handout, lernt eine Karte, meldet eine Aufgabe zur Abnahme
  5. Der Ausbilder nimmt sie ab; sie schlägt in der Rahmenplanabdeckung durch
  6. Das Wochenblatt wird angelegt und als PDF ausgegeben
  7. Gesichert, absichtlich beschädigt, zurückgespielt. Eine Sicherung, die nie zurückgespielt wurde, ist keine Sicherung

Das Protokoll landet in docs/ABNAHME.md.

Voraussetzung: npm run build ist gelaufen, und Port 3399 ist frei – sonst bricht der Lauf mit einer Meldung ab, statt gegen einen fremden Datenbestand zu prüfen.

Der Containerlauf

npm run abnahme prüft über next start, betrieben wird aber der Container. npm run abnahme:container baut deshalb das Abbild, startet es mit leerem Volume und geht den Weg aus dieser README: Lebenszeichen, erstes Konto über werkzeuge/konto.js, Anmeldung, Passwort neu setzen, Sperre nach Fehlversuchen, Sicherung im Container. Danach werden Container, Volume und Abbild wieder entfernt.

Voraussetzung: Docker läuft, Port 3398 ist frei (LERNBAR_CONTAINER_PORT).

Einzelne Zusagen

node scripts/pruefungen/musterloesung-dichtheit.mjs  # Musterlösung dicht?
node scripts/pruefungen/baustein-9.mjs               # Abdeckung, Feedback
node scripts/pruefungen/baustein-10.mjs              # PWA, Offline, Druck

Diese drei brauchen eine laufende Anwendung unter http://localhost:3000 mit den Beispieldaten.


Aufbau

src/
  app/                 Seiten und Serveraktionen (App Router)
  komponenten/         wiederverwendete Bausteine der Oberfläche
  lib/
    auth/              Passwörter, Sitzungen, Rechteregeln
    db/                Verbindung, Schema, Migrationen
    repos/             der gesamte Datenzugriff – nur hier wird SQL gesprochen
    zeit/              Europe/Berlin, ISO-Kalenderwochen
content/               fachliche Inhalte als Markdown und YAML
scripts/               Werkzeuge für die Kommandozeile
  im-container/        Sicherung und Wiederherstellung (Shell, läuft überall)

Weiterführend: Fahrplan · Änderungsprotokoll · Arbeitstagebuch · Entscheidungen

S
Description
No description provided
Readme
16 MiB
Languages
TypeScript 85.3%
JavaScript 7.7%
CSS 5.6%
Shell 1.1%
Dockerfile 0.3%