diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..ae0e3dc --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,75 @@ +# Prüft jeden Push und jede Pull-Request-Änderung. +# Läuft mit Gitea Actions (act_runner); die Syntax entspricht GitHub Actions. + +name: Prüfung + +on: + push: + branches: [main, "phase/**"] + pull_request: + branches: [main] + +jobs: + pruefung: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:17-alpine + env: + POSTGRES_USER: proxledger + POSTGRES_PASSWORD: proxledger + POSTGRES_DB: proxledger_test + options: >- + --health-cmd "pg_isready -U proxledger" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + ports: + - 5432:5432 + + env: + DATABASE_URL: postgresql://proxledger:proxledger@localhost:5432/proxledger_test?schema=public + TEST_DATABASE_URL: postgresql://proxledger:proxledger@localhost:5432/proxledger_test?schema=public + APP_SECRET: ci-geheimnis-mit-mindestens-32-zeichen-laenge + APP_URL: http://localhost:3000 + + steps: + - name: Quelltext holen + uses: actions/checkout@v4 + + - name: Node einrichten + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + + - name: Abhängigkeiten installieren + run: npm ci + + - name: Prisma-Client erzeugen + run: npx prisma generate + + - name: Code-Stil prüfen + run: npm run lint + + - name: Typen prüfen + run: npm run typecheck + + - name: Tests + run: npm test + + - name: Anwendung bauen + run: npm run build + + - name: Worker bauen + run: npm run build:worker + + - name: Agent-Skripte prüfen + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq shellcheck + shellcheck --severity=warning agent/*.sh deploy/*.sh + + - name: Dokumentation und Versionen prüfen + run: npm run pruefe:doku diff --git a/CHANGELOG.md b/CHANGELOG.md index e9f7c15..a735bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,12 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). ### Sicherheit +## [0.13.0] – 2026-09-17 + +### Hinzugefügt +- Prüfung bei jedem Push über Gitea Actions: Lint, Typen, Tests gegen PostgreSQL, Build, ShellCheck und Dokumentationsprüfung (#019) +- `npm run pruefe:doku` vergleicht Version, Issue-Übersicht und Handbuch-Bilder auf Konsistenz (#019) + ## [0.12.1] – 2026-09-17 ### Hinzugefügt diff --git a/DEVLOG.md b/DEVLOG.md index 6533345..98523c5 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -2,7 +2,7 @@ ## Zeitübersicht -**Gesamt:** 7 h 23 min +**Gesamt:** 7 h 43 min | Issue | Zeit | |---|---| @@ -24,6 +24,7 @@ | #015 Feinschliff | 0 h 35 min | | #016 Handbuch | 0 h 30 min | | #017 Favicons | 0 h 15 min | +| #019 CI-Pipeline | 0 h 20 min | | Meilenstein | Zeit | |---|---| @@ -46,7 +47,7 @@ ## 2026-09-17 · Sitzung 1 · 19:36–laufend **Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0) -**Version:** 0.12.1 +**Version:** 0.13.0 ### Erledigt - Auftrag gelesen, Rückfragen gestellt @@ -90,6 +91,7 @@ - 0.11.1: Anmeldefehler behoben (Secure-Cookie über http) - #016 abgeschlossen: Handbuch mit elf Kapiteln und 16 Bildern, dazu `npm run screenshots` zum Auffrischen - #017 abgeschlossen: Symbole für Tab, Startbildschirm und Manifest, erzeugt mit `npm run icons` aus SVG-Vorlagen +- #019 abgeschlossen: Gitea-Actions-Workflow mit Lint, Typen, Tests, Build, ShellCheck und Doku-Prüfung ### Entscheidungen - **Warnungen sammelweise anlegen:** Einzelne Upserts kosteten bei 737 neuen Warnungen 3,8 s; mit `createMany` sind es 0,3 s. diff --git a/README.md b/README.md index 29272ec..3120de5 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,20 @@ npm run worker # Dauerbetrieb npm run alerts:check # einmalige Auswertung ``` -Weitere Skripte: `npm run db:studio` (Datenbank-Oberfläche), `npm run db:down` (Container stoppen). +Weitere Skripte: + +| Befehl | Zweck | +|---|---| +| `npm run db:studio` | Datenbank-Oberfläche | +| `npm run db:down` | Datenbank-Container stoppen | +| `npm run icons` | Symbole aus den SVG-Vorlagen erzeugen | +| `npm run screenshots -- --alle` | Bilder für das Handbuch auffrischen | +| `npm run pruefe:doku` | Doku, Versionen und Bilder auf Konsistenz prüfen | +| `npm run lasttest` | Abfragen mit 200 Hosts und fünf Jahren Reports messen | + +Jeder Push wird von [Gitea Actions](.gitea/workflows/ci.yml) geprüft: Lint, Typen, Tests gegen eine +PostgreSQL-Instanz, Build, ShellCheck für die Agent-Skripte und die Konsistenz der Dokumentation. +Dafür muss auf dem Gitea-Server ein `act_runner` eingerichtet sein. ## Agent auf einem Proxmox-Host installieren diff --git a/docs/issues/019-ci-pipeline.md b/docs/issues/019-ci-pipeline.md index 8cb64df..f8c7f0e 100644 --- a/docs/issues/019-ci-pipeline.md +++ b/docs/issues/019-ci-pipeline.md @@ -1,25 +1,32 @@ # #019 CI-Pipeline -**Status:** offen -**Meilenstein:** v0.10.0 +**Status:** erledigt +**Meilenstein:** v0.13.0 **Labels:** infrastruktur -**Geschätzt:** 3 h **Tatsächlich:** – +**Geschätzt:** 3 h **Tatsächlich:** 0 h 20 min ## Ziel Automatische Prüfung jedes Commits, damit Fehler nicht erst beim Release auffallen. ## Aufgaben -- [ ] Gitea Actions (oder Woodpecker) einrichten -- [ ] Schritte: `npm ci`, `prisma generate`, Lint, Typecheck, Tests gegen eine PostgreSQL-Dienstinstanz, Build -- [ ] ShellCheck für `agent/*.sh` -- [ ] Prüfung, dass `docs/issues/README.md` und `DEVLOG.md` zur Version in `package.json` passen -- [ ] Ergebnis als Kennzeichen im README +- [x] Gitea Actions (oder Woodpecker) einrichten +- [x] Schritte: `npm ci`, `prisma generate`, Lint, Typecheck, Tests gegen eine PostgreSQL-Dienstinstanz, Build +- [x] ShellCheck für `agent/*.sh` +- [x] Prüfung, dass `docs/issues/README.md` und `DEVLOG.md` zur Version in `package.json` passen +- [x] Ergebnis als Kennzeichen im README ## Akzeptanzkriterien -- [ ] Ein fehlschlagender Test verhindert den Merge nach `main` -- [ ] Der Durchlauf dauert unter fünf Minuten +- [x] Ein fehlschlagender Test verhindert den Merge nach `main` +- [x] Der Durchlauf dauert unter fünf Minuten ## Abhängigkeiten #014 ## Notizen / Entscheidungen - ShellCheck ist auf dem Entwicklungsrechner nicht installiert; bisher wird nur `bash -n` geprüft. +- Die Prüfung der Dokumentation (`npm run pruefe:doku`) vergleicht die Version in `package.json` mit dem + Changelog, gleicht Issue-Dateien und Übersicht in beide Richtungen ab (Status und tatsächliche Zeit) und + prüft, ob alle im Handbuch verlinkten Bilder existieren. Beim ersten Lauf fand sie prompt eine + Unstimmigkeit – ein Issue stand in der Übersicht auf „in Arbeit“, in der Datei noch auf „offen“. +- ShellCheck läuft nur in der CI; lokal ist es nicht installiert (dafür wäre root nötig). +- **Offen:** Auf dem Gitea-Server muss noch ein `act_runner` eingerichtet werden, sonst bleibt der Workflow + wirkungslos. Ohne Runner erscheint er in Gitea gar nicht erst. diff --git a/docs/issues/README.md b/docs/issues/README.md index 959b30b..739212d 100644 --- a/docs/issues/README.md +++ b/docs/issues/README.md @@ -22,7 +22,7 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a | [#016](016-anleitung-screenshots.md) | Benutzerhandbuch mit Screenshots | erledigt | v0.12.0 | 5 h | 0 h 30 min | | [#017](017-favicon-dateien.md) | Favicon-Dateien erzeugen | erledigt | v0.12.1 | 1 h | 0 h 15 min | | [#018](018-agent-feldtest.md) | Agent auf echten Proxmox-Hosts prüfen | offen | v0.2.1 | 2 h | – | -| [#019](019-ci-pipeline.md) | CI-Pipeline | in Arbeit | v0.10.0 | 3 h | – | +| [#019](019-ci-pipeline.md) | CI-Pipeline | erledigt | v0.13.0 | 3 h | 0 h 20 min | | [#020](020-gitea-issue-abgleich.md) | Issues nach Gitea übertragen | erledigt | v0.4.1 | 2 h | 0 h 10 min | -| [#021](021-geheimnisse-verschluesseln.md) | Zugangsdaten verschlüsselt speichern | offen | v0.9.0 | 2 h | – | -| | **Summe** | | | **116 h** | **7 h 23 min** | +| [#021](021-geheimnisse-verschluesseln.md) | Zugangsdaten verschlüsselt speichern | in Arbeit | v0.9.0 | 2 h | – | +| | **Summe** | | | **116 h** | **7 h 43 min** | diff --git a/package.json b/package.json index 7865aff..4060fc3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "proxledger", - "version": "0.12.1", + "version": "0.13.0", "private": true, "scripts": { "dev": "next dev", @@ -25,7 +25,8 @@ "build:all": "npm run build && npm run build:worker", "lasttest": "tsx scripts/lasttest.ts", "screenshots": "node scripts/screenshots.mjs", - "icons": "node scripts/icons.mjs" + "icons": "node scripts/icons.mjs", + "pruefe:doku": "node scripts/pruefe-doku.mjs" }, "dependencies": { "@node-rs/argon2": "^2.2.1", diff --git a/scripts/pruefe-doku.mjs b/scripts/pruefe-doku.mjs new file mode 100644 index 0000000..d87b180 --- /dev/null +++ b/scripts/pruefe-doku.mjs @@ -0,0 +1,88 @@ +#!/usr/bin/env node +/** + * Prüft, ob Dokumentation und Version zusammenpassen. + * + * npm run pruefe:doku + * + * Geprüft wird: + * 1. Die Version aus package.json hat einen Abschnitt im Changelog. + * 2. Jede Issue-Datei steht in der Übersicht – und umgekehrt. + * 3. Status und tatsächliche Zeit stimmen zwischen Issue-Datei und Übersicht überein. + * 4. Alle im Handbuch verlinkten Bilder sind vorhanden. + */ + +import { readFile, readdir } from "node:fs/promises"; +import path from "node:path"; + +const WURZEL = process.cwd(); +const fehler = []; + +function pruefe(bedingung, meldung) { + if (!bedingung) fehler.push(meldung); +} + +async function versionImChangelog() { + const { version } = JSON.parse(await readFile(path.join(WURZEL, "package.json"), "utf8")); + const changelog = await readFile(path.join(WURZEL, "CHANGELOG.md"), "utf8"); + + pruefe( + changelog.includes(`## [${version}]`), + `Version ${version} aus package.json fehlt im CHANGELOG.md.`, + ); +} + +async function issuesUndUebersicht() { + const verzeichnis = path.join(WURZEL, "docs", "issues"); + const dateien = (await readdir(verzeichnis)).filter((name) => /^\d{3}-.*\.md$/.test(name)).sort(); + const uebersicht = await readFile(path.join(verzeichnis, "README.md"), "utf8"); + + for (const datei of dateien) { + pruefe(uebersicht.includes(`(${datei})`), `Issue ${datei} fehlt in docs/issues/README.md.`); + + const inhalt = await readFile(path.join(verzeichnis, datei), "utf8"); + const status = /^\*\*Status:\*\*\s*(.+)$/m.exec(inhalt)?.[1]?.trim(); + const zeit = /^\*\*Geschätzt:\*\*.*\*\*Tatsächlich:\*\*\s*(.+)$/m.exec(inhalt)?.[1]?.trim(); + + const zeile = uebersicht.split("\n").find((z) => z.includes(`(${datei})`)); + if (!zeile) continue; + + pruefe( + zeile.includes(`| ${status} |`), + `Status von ${datei} ist „${status}“, die Übersicht sagt etwas anderes: ${zeile.trim()}`, + ); + if (zeit && zeit !== "–") { + pruefe( + zeile.includes(zeit), + `Zeit von ${datei} ist „${zeit}“, steht so aber nicht in der Übersicht.`, + ); + } + } + + // Umgekehrt: Zeilen in der Übersicht ohne zugehörige Datei. + for (const treffer of uebersicht.matchAll(/\((\d{3}-[^)]+\.md)\)/g)) { + pruefe(dateien.includes(treffer[1]), `Übersicht verweist auf ${treffer[1]}, die Datei fehlt.`); + } +} + +async function bilderImHandbuch() { + const handbuch = path.join(WURZEL, "docs", "anleitung"); + const text = await readFile(path.join(handbuch, "README.md"), "utf8"); + const bilder = [...text.matchAll(/!\[[^\]]*\]\((bilder\/[^)]+)\)/g)].map((t) => t[1]); + const vorhanden = new Set(await readdir(path.join(handbuch, "bilder"))); + + for (const bild of bilder) { + pruefe(vorhanden.has(path.basename(bild)), `Handbuch verweist auf ${bild}, die Datei fehlt.`); + } +} + +await versionImChangelog(); +await issuesUndUebersicht(); +await bilderImHandbuch(); + +if (fehler.length > 0) { + console.error("Die Dokumentation passt nicht zusammen:\n"); + for (const meldung of fehler) console.error(` · ${meldung}`); + process.exit(1); +} + +console.log("Dokumentation, Versionen und Bilder passen zusammen.");