Files
CC-Manager/README.md
T
Harlekin 3ec0ff4df1 Container-Setup, Backups und Dokumentation
Mehrstufiges Image: gebaut wird mit allen Abhaengigkeiten, ausgeliefert wird
nur die Laufzeit. Migrationen laufen beim Containerstart. Der App-Port wird
bewusst nicht nach aussen gemappt - davor gehoert ein Reverse Proxy, der HTTPS
terminiert.

Der Backup-Service schreibt taeglich einen pg_dump und raeumt Dumps aelter als
14 Tage weg.
2026-09-18 11:55:06 +02:00

179 lines
7.1 KiB
Markdown

# CC-Manager
Selbst gehostete Web-App zur Verwaltung von Custom Characters (CCs) auf einem SCP-RP-Server.
Sie führt pro Spieler ein Konto über gekaufte Ansprüche und deren Verbrauch, damit die Frage
„darf der gerade einen neuen CC?" nicht mehr von Hand abgeglichen werden muss.
## Das Prinzip
Jeder Spieler hat drei Guthaben-Töpfe:
| Topf | Wird gefüllt durch | Wird verbraucht durch |
|------|--------------------|-----------------------|
| CC-Guthaben | jedes CC-Paket (+1) | ein neuer CC |
| Redesign-Guthaben (bezahlt) | jedes CC-Paket (+1, immer inklusive) und jedes Redesign-Paket (+1) | ein Redesign |
| Kulanz-Redesign | manuell eingetragene Kulanz (+1) | ein Redesign |
Zwei Regeln sind fest verdrahtet:
1. **Ein CC-Paket erzeugt beide Guthaben.** Der inklusive Redesign wird automatisch mitgebucht,
in derselben Transaktion und mit gemeinsamer Gruppen-ID — ein Storno nimmt beide zurück.
2. **Inaktive CCs kosten unterschiedlich.** Wurde ein CC nur wegen Spieler-Inaktivität vom Server
entfernt, kommt er gratis zurück. Wurde er redesignt und existiert im Original nicht mehr,
kostet die Reaktivierung ein Redesign- oder CC-Guthaben.
Guthaben werden als **Kontobuch** geführt: jede Bewegung ist eine eigene Zeile, der Stand eines
Topfes ist deren Summe. Das macht jeden Stand nachvollziehbar und Korrekturen sauber — storniert
wird per Gegenbuchung, nie durch Löschen. Ein überzogenes Konto ist erlaubt (kommt vor, wenn ein
Redesign vor dem zugehörigen Paket erfasst wird), wird aber rot markiert.
Ein Redesign legt einen **neuen** CC-Datensatz an und verkettet ihn mit dem alten. So bleibt die
Kette „A → B → C" sichtbar und die Reaktivierungsregel greift automatisch richtig.
## Schnellstart
Vorausgesetzt wird Docker mit Compose-Plugin (oder DockGe).
```bash
git clone https://git.harlelux.de/Harlekin/CC-Manager.git
cd CC-Manager
cp .env.example .env
```
In der `.env` mindestens `DB_PASSWORD` und `SESSION_SECRET` setzen:
```bash
openssl rand -base64 48 # für SESSION_SECRET
```
Dann starten:
```bash
docker compose up -d --build
docker compose logs -f app
```
Beim ersten Start legt die App einen Administrator an. Ist `BOOTSTRAP_ADMIN_PASSWORD` leer,
erzeugt sie ein Einmal-Passwort und schreibt es **einmalig** ins Log — dort steht es in der
Ausgabe oben. Beim ersten Login ist ein Passwortwechsel Pflicht; bis dahin sind alle anderen
Seiten gesperrt.
Für DockGe: Repo-Inhalt als Stack anlegen, `.env` daneben, fertig. Datenbank-Migrationen laufen
bei jedem Containerstart automatisch mit.
## HTTPS
Der App-Port wird bewusst **nicht** nach außen gemappt. Davor gehört ein Reverse Proxy
(Nginx Proxy Manager, Traefik oder Caddy), der HTTPS terminiert und intern auf `app:3000`
weiterleitet. So ist nur Port 443 offen und die Zertifikate laufen über Let's Encrypt.
Läuft die App hinter einem Proxy, muss `TRUST_PROXY=true` gesetzt bleiben — sonst landen im
Audit-Log die Proxy-IPs statt der echten. In Produktion werden Cookies automatisch auf `Secure`
gesetzt; nur zum Testen ohne TLS lässt sich das über `COOKIE_SECURE=false` abschalten.
## Backups
Der `backup`-Service im Stack schreibt beim Start und danach alle 24 Stunden einen
`pg_dump` nach `./backups/` und räumt Dumps älter als 14 Tage weg. Die Daten bilden bezahlte
Ansprüche ab — das Verzeichnis gehört auf eine Sicherung außerhalb des Hosts.
Wiederherstellen:
```bash
gunzip -c backups/ccmanager-2025-01-15-0300.sql.gz | \
docker compose exec -T db psql -U ccuser -d ccmanager
```
## Import
Kaufdaten lassen sich als CSV oder JSON hochladen; das Format ist nicht festgelegt. Der Ablauf:
1. Datei hochladen — Spalten und Beispielzeilen werden angezeigt.
2. Spalten zuordnen (Spieler-Kennung, Produktname, optional Bestell-ID und Datum). Die üblichen
Spaltennamen werden vorausgewählt.
3. **Vorschau** prüfen: für jede Zeile steht da, was passieren würde.
4. Buchen.
Welcher Produktname welches Guthaben ergibt, steht in der Mapping-Tabelle unter *Verwaltung →
Produkt-Mappings* (Teiltreffer, Groß-/Kleinschreibung egal). Neue Produkte lassen sich dort
jederzeit ergänzen, ohne den Code anzufassen.
Wird eine Bestell-ID zugeordnet, bucht ein zweiter Import dieselben Käufe **nicht** erneut —
Dubletten werden sowohl gegen bereits gebuchte Bestellungen als auch innerhalb der Datei erkannt.
Ohne Bestell-ID entfällt dieser Schutz; darauf weist das Formular hin.
Kulanz-Redesigns tauchen in keinem Shop-Export auf und werden immer von Hand eingetragen.
## Rollen
Es gibt **keine öffentliche Registrierung**. Konten legt ausschließlich ein Administrator an.
- **Viewer** — darf nur lesen.
- **Editor** — Spieler anlegen, Guthaben eintragen, CCs erstellen, redesignen, reaktivieren.
- **Admin** — zusätzlich Import, Nutzerverwaltung, Produkt-Mappings, Audit-Log und Korrekturbuchungen.
Jede ändernde Aktion landet im Audit-Log. Für Administratoren lässt sich unter *Mein Konto*
eine Zwei-Faktor-Anmeldung per TOTP einrichten.
## Entwicklung
Gebraucht werden Node 20+ und eine erreichbare PostgreSQL-Instanz.
```bash
npm install
cp server/.env.example server/.env # DATABASE_URL auf die lokale DB zeigen lassen
npm run migrate:dev
npm run dev # API auf :3000, Frontend auf :5173
```
Das Frontend leitet `/api` im Entwicklungsbetrieb an den API-Port weiter, Anmeldung und Cookies
funktionieren also wie in Produktion.
Die Tests brauchen **keine** laufende Datenbank — sie starten eine Postgres-Instanz im Speicher:
```bash
npm test
```
Abgedeckt sind die Guthaben-Regeln, der CC-Lebenszyklus samt Reaktivierungslogik, Korrekturen
und Stornos, Anmeldung mit CSRF- und Rollenprüfung sowie der Import mit Dedup.
### Aufbau
```
server/
prisma/schema.prisma Datenmodell und Migrationen
src/services/ Fachlogik: Ledger, CC-Lebenszyklus, Import
src/routes/ HTTP-Endpunkte
src/auth/ Passwörter, Sitzungen, CSRF, Rollen, TOTP
test/ Integrationstests gegen eine echte Postgres-Instanz
web/
src/pages/ Login, Übersicht, Spieler-Detail, Import, Verwaltung
src/components/ Wiederverwendete Bausteine
```
Backend ist Fastify mit Prisma, Frontend eine React-SPA. Gebaut wird beides in ein einziges
Image: Fastify liefert die API und die fertigen Frontend-Dateien aus.
Nach Änderungen am Datenmodell:
```bash
npm run migrate:dev -- --name beschreibung-der-aenderung
```
## Sicherheit
- Passwörter mit Argon2id gehasht, Mindestlänge 12 Zeichen.
- Sitzungen serverseitig; im Cookie steht nur ein Token, in der Datenbank dessen Hash.
Cookie ist `httpOnly`, `SameSite=Lax` und in Produktion `Secure`.
- CSRF-Schutz für alle ändernden Anfragen über ein aus der Sitzung abgeleitetes Token.
- Alle Eingaben werden validiert, alle Datenbankzugriffe laufen parametrisiert über das ORM.
- Rate-Limit auf dem Login, generische Fehlermeldung ohne Hinweis auf existierende Konten.
- Sicherheits-Header inklusive Content-Security-Policy.
- Ein gesperrtes Konto verliert seine offenen Sitzungen sofort; der letzte aktive Administrator
kann sich nicht selbst entfernen.
## Lizenz
GPL-3.0-or-later, siehe [LICENSE](LICENSE).