# 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:** Bausteine 1 bis 6 von 10 sind fertig – Fundament, Inhalte, > Wochenplan, Merkkarten, Praxisaufgaben, Checklisten. Der > [Fahrplan](ROADMAP.md) zeigt, was folgt, das > [Änderungsprotokoll](CHANGELOG.md), was schon drin ist. --- ## 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 ```bash git clone lernbar cd lernbar docker compose up -d --build ``` Die Anwendung lauscht danach auf `127.0.0.1:3000`. Erstes Ausbilderkonto anlegen: ```bash docker compose exec lernbar node -e "1" >/dev/null # Container erreichbar? docker compose exec lernbar sh -c \ 'node werkzeuge/konto.js --name "Vorname Nachname" --email ausbilder@betrieb.de --rolle ausbilder' ``` > Solange das Konto-Werkzeug im Abbild noch nicht liegt, geht es vom > Entwicklungsrechner aus: > `npm run konto:anlegen -- --name "…" --email … --rolle ausbilder` Danach im Browser anmelden, unter **Verwaltung** das Azubikonto anlegen und das Ausbildungsverhältnis mit Beginndatum einrichten. ### Entwicklung ```bash 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 . Datenbank und Anhänge landen unter `daten/` (nicht im Repository). --- ## Beispiel für einen Reverse Proxy ```nginx 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 rahmenplan.yaml Berufsbildpositionen und Lernfelder ``` Ein Handout ist Markdown mit YAML-Kopf: ```markdown --- 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: ```bash npm run inhalte:pruefen ``` --- ## Datensicherung Gesichert werden **Datenbank und Anhänge**. Die Inhalte stecken im Repository und brauchen keine eigene Sicherung. ### Sicherung ziehen ```bash # 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: ```bash 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 ```bash 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-/` verschoben. ### Nächtlich per Cronjob `/etc/cron.d/lernbar-sicherung`: ```cron # 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: ```bash 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` | `/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 ```bash npm run typecheck # TypeScript npm run lint # ESLint npm run test # Vitest – Logik, die wehtut npm run e2e # Playwright – der Hauptweg von Anfang bis Ende ``` --- ## 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](ROADMAP.md) · [Änderungsprotokoll](CHANGELOG.md) · [Arbeitstagebuch](DEVLOG.md) · [Entscheidungen](ENTSCHEIDUNGEN.md)