- Status offen / in Arbeit / zur Abnahme / abgenommen, mit Zeitstempeln - Notizfeld fuer den Azubi, Dateianhaenge bis 25 MB - Speicherpfad nur aus selbst erzeugter UUID, nie aus dem Dateinamen - Auslieferung ueber /anhaenge/<kennung> mit Rechtepruefung bei jedem Abruf, CSP sandbox und nosniff; SVG ausdruecklich nicht erlaubt - Anhaenge haengen an Aufgabe UND Azubi - Ausbilder hakt Kriterien einzeln ab, Note und Rueckmeldung, Abnahme sperrt - Zurueckgeben zur Nacharbeit - "Was auf dich wartet" zeigt offene Abnahmen - Abgenommene Aufgaben zaehlen im Wochenfortschritt mit Behoben: Next.js deckelt Serveraktionen ab Werk bei 1 MB - damit waere kein Handyfoto durchgekommen. bodySizeLimit jetzt 30 MB, Vorabpruefung im Browser, client_max_body_size im Proxy-Beispiel auf 30m. Geaendert: Seiten verweigern fremde Daten mit 404 statt mit einem Fehler. Eigene Seiten fuer "Nicht gefunden" und Stoerfaelle. 24 neue Tests, insgesamt 157. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
266 lines
7.9 KiB
Markdown
266 lines
7.9 KiB
Markdown
# 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 5 von 10 sind fertig – Fundament, Inhalte,
|
||
> Wochenplan, Merkkarten, Praxisaufgaben. 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 <repository-url> 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 <http://localhost:3000>. 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-<zeitstempel>/` 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` | `<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
|
||
|
||
```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)
|