Refactor: Dokumentation nach docs/ verschoben

- Alle Markdown-Dateien (außer README.md) nach docs/ verschoben
- Referenzen in README.md aktualisiert
- /docs zu .dockerignore hinzugefügt
This commit is contained in:
Hördle Bot
2025-12-01 17:58:32 +01:00
parent 107739ade9
commit e3a09864a6
12 changed files with 4 additions and 3 deletions

289
docs/CADDY_SETUP.md Normal file
View File

@@ -0,0 +1,289 @@
# Caddy-Setup für Hördle
Diese Anleitung erklärt, wie du Caddy als Reverse-Proxy mit automatischen Let's Encrypt Wildcard-Zertifikaten für die Domains `hoerdle.de` und `hördle.de` (xn--hrdle-jua.de) einrichtest.
## Übersicht
Caddy übernimmt folgende Aufgaben:
- Automatische SSL/TLS-Zertifikate via Let's Encrypt
- Wildcard-Zertifikate für beide Domains (inkl. Subdomains)
- Reverse Proxy zu deinem Hördle-Container
- HTTP zu HTTPS Redirect
- Optimierte Einstellungen für Audio-Streaming und Uploads
## Voraussetzungen
1. Docker und Docker Compose installiert
2. Zugriff auf deine GoDaddy Domain-Verwaltung
3. Ports 80 und 443 müssen frei sein (Caddy übernimmt diese)
## Schritt 1: GoDaddy DNS-API-Zugangsdaten erstellen
Für Wildcard-Zertifikate benötigt Caddy DNS-01 Challenge, was API-Zugriff auf dein GoDaddy-Konto erfordert.
### GoDaddy API-Keys erstellen
1. Gehe zu [GoDaddy Developer Portal](https://developer.godaddy.com/)
2. Melde dich mit deinem GoDaddy-Konto an
3. Klicke auf **"Keys"** in der Navigation
4. Klicke auf **"Create New API Key"**
5. Fülle das Formular aus:
- **Key Name**: z.B. "Hördle Caddy DNS"
- **Environment**: Production (für echte Domains)
6. Klicke auf **"Create"**
7. **Wichtig**: Kopiere dir den **API Key** und das **API Secret** - das Secret wird nur einmal angezeigt!
### Alternative: Manuelle DNS-TXT-Records (ohne API)
Wenn du keine API-Keys verwenden möchtest, kannst du die DNS-TXT-Records manuell setzen. **Hinweis**: Dies ist nur für die initiale Zertifikatsanfrage möglich, nicht für automatische Erneuerungen.
Siehe Abschnitt "Manuelle DNS-Konfiguration (ohne API)" weiter unten.
## Schritt 2: Environment-Variablen konfigurieren
Erstelle eine `.env`-Datei im Projektverzeichnis (oder erweitere die bestehende):
```bash
# GoDaddy API-Credentials für DNS-01 Challenge
GODADDY_API_KEY=your_api_key_here
GODADDY_API_SECRET=your_api_secret_here
# Optional: Email für Let's Encrypt Benachrichtigungen
CADDY_EMAIL=markus@hoerdle.de
```
**Wichtig**: Die `.env`-Datei sollte nicht in Git committed werden (sollte bereits in `.gitignore` sein).
## Schritt 3: Docker-Netzwerk erstellen
Caddy und Hördle müssen im gleichen Docker-Netzwerk kommunizieren:
```bash
# Prüfe, ob das Netzwerk bereits existiert
docker network ls | grep hoerdle
# Falls das Netzwerk bereits existiert, aber falsche Labels hat:
# 1. Stoppe alle Container, die das Netzwerk nutzen
docker compose -f docker-compose.yml down
# 2. Lösche das alte Netzwerk (falls keine Container mehr dranhängen)
docker network rm hoerdle_default
# 3. Erstelle das Netzwerk neu
docker network create hoerdle_default
# Falls das Netzwerk nicht existiert, erstelle es:
docker network create hoerdle_default
```
**Hinweis**: Die docker-compose.caddy.yml ist so konfiguriert, dass sie das Netzwerk als externes Netzwerk nutzt. Das bedeutet, dass das Netzwerk bereits existieren muss, bevor Caddy gestartet wird.
## Schritt 4: Caddy starten
### Option A: Mit docker-compose (Empfohlen)
```bash
# Starte Hördle + Caddy zusammen
docker compose -f docker-compose.yml -f docker-compose.caddy.yml --profile production up -d
# Nur Caddy starten (wenn Hördle bereits läuft)
docker compose -f docker-compose.caddy.yml --profile production up -d
```
### Option B: Nur Caddy starten (Hördle läuft bereits)
```bash
docker compose -f docker-compose.caddy.yml --profile production up -d
```
## Schritt 5: DNS-Konfiguration in GoDaddy
### Automatisch (mit API-Keys)
Wenn du API-Keys konfiguriert hast, wird Caddy automatisch die benötigten DNS-TXT-Records erstellen. Keine manuellen DNS-Änderungen nötig!
### Manuell (ohne API-Keys)
Wenn du die API-Keys nicht verwenden möchtest, musst du die DNS-TXT-Records manuell setzen:
#### Für hoerdle.de:
1. Gehe zu deinem [GoDaddy DNS-Verwaltung](https://dcc.godaddy.com/manage/YOUR_DOMAIN/dns)
2. Für jedes Wildcard-Zertifikat benötigst du einen TXT-Record:
- **Typ**: TXT
- **Name**: `_acme-challenge`
- **Wert**: (wird von Let's Encrypt generiert - siehe Caddy-Logs)
- **TTL**: 600 (10 Minuten)
**Wichtig**: Für Wildcard-Zertifikate brauchst du:
- Einen TXT-Record für `_acme-challenge.hoerdle.de` (Domain selbst)
- Einen TXT-Record für `_acme-challenge.*.hoerdle.de` (Wildcard)
#### Für hördle.de (xn--hrdle-jua.de):
Das gleiche Vorgehen für die Punycode-Domain:
- `_acme-challenge.xn--hrdle-jua.de`
- `_acme-challenge.*.xn--hrdle-jua.de`
**Hinweis**: Die manuelle Methode funktioniert nur für die initiale Zertifikatsanfrage. Für automatische Erneuerungen benötigst du die API-Keys.
## Schritt 6: Prüfen, ob alles funktioniert
### Caddy-Logs ansehen
```bash
docker logs -f hoerdle-caddy
```
Du solltest sehen:
- Caddy startet erfolgreich
- Let's Encrypt-Zertifikate werden angefordert
- Zertifikate sind gültig
### Zertifikate prüfen
```bash
# Prüfe Zertifikate im Browser
# Öffne: https://hoerdle.de
# Öffne: https://hördle.de
```
Oder via Command-Line:
```bash
# Prüfe Zertifikat für hoerdle.de
openssl s_client -connect hoerdle.de:443 -servername hoerdle.de < /dev/null 2>/dev/null | openssl x509 -noout -text | grep "Subject:"
# Prüfe Zertifikat für hördle.de
openssl s_client -connect xn--hrdle-jua.de:443 -servername xn--hrdle-jua.de < /dev/null 2>/dev/null | openssl x509 -noout -text | grep "Subject:"
```
## Troubleshooting
### Caddy startet nicht
**Problem**: Container stoppt sofort nach Start.
**Lösung**:
1. Prüfe Caddy-Logs: `docker logs hoerdle-caddy`
2. Prüfe Caddyfile-Syntax: `docker run --rm -v $(pwd)/Caddyfile:/etc/caddy/Caddyfile:ro caddy:2-alpine caddy validate --config /etc/caddy/Caddyfile`
3. Prüfe, ob Ports 80/443 frei sind: `sudo netstat -tlnp | grep -E ':80|:443'`
### Zertifikate werden nicht erstellt
**Problem**: Let's Encrypt-Zertifikate werden nicht angefordert.
**Lösung**:
1. Prüfe GoDaddy API-Credentials in `.env`
2. Prüfe Caddy-Logs für DNS-Challenge-Fehler
3. Stelle sicher, dass die Domains korrekt auf deinen Server zeigen (A-Records)
4. Bei manueller DNS-Konfiguration: Prüfe, ob TXT-Records korrekt gesetzt sind
### DNS-Challenge schlägt fehl
**Problem**: DNS-01 Challenge kann DNS-Records nicht erstellen.
**Lösung**:
1. Prüfe GoDaddy API-Permissions
2. Stelle sicher, dass API-Keys Production-Keys sind (nicht Development)
3. Prüfe Domain-Ownership in GoDaddy
4. Warte einige Minuten - DNS-Propagierung kann dauern
### Audio-Dateien funktionieren nicht
**Problem**: MP3-Dateien werden nicht korrekt gestreamt.
**Lösung**:
1. Prüfe Caddy-Logs: `docker logs hoerdle-caddy | grep -i range`
2. Prüfe, ob Range-Header weitergegeben werden (Browser DevTools → Network)
3. Stelle sicher, dass der `/uploads/` Handle korrekt konfiguriert ist
### Container können nicht kommunizieren
**Problem**: Caddy kann den hoerdle-Container nicht erreichen.
**Lösung**:
1. Prüfe, ob beide Container im gleichen Netzwerk sind:
```bash
docker network inspect hoerdle_default
```
2. Prüfe, ob hoerdle-Container läuft: `docker ps | grep hoerdle`
3. Teste Verbindung von Caddy zu Hördle:
```bash
docker exec hoerdle-caddy wget -O- http://hoerdle:3000/api/health
```
**Hinweis**: Der Container-Port ist 3000 (nicht 3010, das ist nur der Host-Port).
### Netzwerk-Warnung beim Deployment
**Problem**: Warnung `network hoerdle_default was found but has incorrect label`
**Erklärung**: Diese Warnung ist **harmlos** und kann ignoriert werden. Docker Compose funktioniert trotzdem einwandfrei. Sie entsteht, wenn das Netzwerk bereits existiert, aber nicht von Docker Compose erstellt wurde.
**Optional: Warnung beheben** (nur wenn sie stört):
```bash
# Reparatur-Skript ausführen (stoppt Container kurz)
./scripts/fix-network.sh
# Danach Container neu starten
docker compose up -d
```
**Hinweis**: Das Reparatur-Skript stoppt alle Container kurz, die das Netzwerk nutzen. In Produktion sollte dies außerhalb der Hauptnutzungszeit erfolgen.
## Deployment-Workflow
### Caddy nur in Produktion aktivieren
Die `docker-compose.caddy.yml` verwendet das `production`-Profile. Um Caddy zu aktivieren:
```bash
# Mit Production-Profile
docker compose -f docker-compose.yml -f docker-compose.caddy.yml --profile production up -d
# Ohne Caddy (nur Hördle)
docker compose -f docker-compose.yml up -d
```
### Caddy aktualisieren
```bash
# Pull neues Caddy-Image
docker compose -f docker-compose.caddy.yml pull
# Restart Caddy-Container
docker compose -f docker-compose.caddy.yml --profile production restart caddy
```
### Caddy-Konfiguration ändern
Nach Änderungen am Caddyfile:
```bash
# Caddyfile validieren
docker run --rm -v $(pwd)/Caddyfile:/etc/caddy/Caddyfile:ro caddy:2-alpine caddy validate --config /etc/caddy/Caddyfile
# Caddy neu laden (ohne Downtime)
docker compose -f docker-compose.caddy.yml --profile production exec caddy caddy reload --config /etc/caddy/Caddyfile
```
## Sicherheit
### API-Keys schützen
- **Niemals** API-Keys in Git committen
- Verwende `.env`-Dateien (sollten in `.gitignore` sein)
- Setze minimale Berechtigungen für API-Keys in GoDaddy
- Rotiere API-Keys regelmäßig
### Firewall
Stelle sicher, dass nur Ports 80 und 443 öffentlich erreichbar sind. Port 3010 (Hördle) sollte nicht öffentlich erreichbar sein.
## Weitere Ressourcen
- [Caddy Dokumentation](https://caddyserver.com/docs/)
- [Caddy DNS-Provider](https://caddyserver.com/docs/modules/tls.dns)
- [GoDaddy API Dokumentation](https://developer.godaddy.com/doc/endpoint/domains)
- [Let's Encrypt Wildcard-Zertifikate](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge)

View File

@@ -0,0 +1,183 @@
# Caddy Zertifikat-Troubleshooting
## Problem: Zertifikat für Punycode-Domain (hördle.de / xn--hrdle-jua.de) fehlt
Wenn die Domain `hördle.de` (xn--hrdle-jua.de) einen `ERR_SSL_PROTOCOL_ERROR` zeigt, bedeutet das, dass kein gültiges SSL-Zertifikat vorhanden ist.
### Schritt 1: Zertifikat-Status prüfen
Führe das Check-Script aus:
```bash
./scripts/check-caddy-certificates.sh
```
Dieses Script prüft:
- Ob Caddy läuft
- Welche Zertifikate vorhanden sind
- Ob die DNS-Einträge korrekt sind
- Ob die HTTPS-Verbindungen funktionieren
### Schritt 2: DNS-Einträge prüfen
**Wichtig**: Beide Domains müssen auf die gleiche Server-IP zeigen!
#### In GoDaddy prüfen:
1. Gehe zu [GoDaddy DNS-Verwaltung](https://dcc.godaddy.com/manage/hoerdle.de/dns)
2. Prüfe die A-Records:
**Für hoerdle.de:**
- Name: `@` oder `hoerdle.de`
- Typ: `A`
- Wert: `DEINE_SERVER_IP`
**Für hördle.de (Punycode):**
- Name: `@` oder `xn--hrdle-jua.de` (oder der Unicode-Name, falls unterstützt)
- Typ: `A`
- Wert: **GLEICHE_SERVER_IP wie hoerdle.de**
#### DNS manuell testen:
```bash
# Prüfe hoerdle.de
dig +short hoerdle.de @8.8.8.8
# Prüfe xn--hrdle-jua.de (Punycode)
dig +short xn--hrdle-jua.de @8.8.8.8
# Beide sollten die gleiche IP zurückgeben!
```
### Schritt 3: Zertifikat neu erstellen
Wenn die DNS-Einträge korrekt sind, lösche das alte (fehlgeschlagene) Zertifikat und lass Caddy es neu erstellen:
```bash
./scripts/renew-caddy-certificates.sh
```
Wähle Option 2: "Nur Zertifikat für xn--hrdle-jua.de löschen"
### Schritt 4: Caddy-Logs überwachen
Während Caddy das Zertifikat erstellt, überwache die Logs:
```bash
docker logs hoerdle-caddy -f
```
Du solltest sehen:
- `[INFO] attempting ACME challenge` - Caddy versucht die Challenge
- `[INFO] successfully completed ACME challenge` - Challenge erfolgreich
- `[INFO] certificate obtained successfully` - Zertifikat erstellt
Bei Fehlern siehst du:
- `[ERROR] acme: error` - Challenge fehlgeschlagen
- `[ERROR] unable to validate` - Validierung fehlgeschlagen
### Schritt 5: Häufige Probleme und Lösungen
#### Problem 1: DNS zeigt auf falsche IP
**Symptom**: `dig` zeigt eine andere IP als erwartet
**Lösung**:
1. Prüfe DNS-Einträge in GoDaddy
2. Warte auf DNS-Propagierung (kann 5-60 Minuten dauern)
3. Verwende einen DNS-Checker: https://www.whatsmydns.net/
#### Problem 2: Port 80 nicht erreichbar
**Symptom**: Caddy-Logs zeigen "connection refused" oder Timeout
**Lösung**:
1. Prüfe Firewall: `sudo ufw status`
2. Prüfe ob Port 80 offen ist: `sudo netstat -tulpn | grep :80`
3. Prüfe ob Caddy auf Port 80 lauscht: `docker exec hoerdle-caddy netstat -tulpn | grep :80`
#### Problem 3: Let's Encrypt Rate Limit
**Symptom**: Logs zeigen "too many certificates already issued"
**Lösung**:
- Warte 1 Woche (Rate Limit von Let's Encrypt)
- Oder verwende Staging-Environment zum Testen:
```caddyfile
tls {
staging
}
```
#### Problem 4: Punycode-Domain wird nicht erkannt
**Symptom**: Caddy erstellt Zertifikat nur für hoerdle.de, nicht für xn--hrdle-jua.de
**Lösung**:
1. Prüfe ob beide Domains in der Caddyfile stehen
2. Prüfe DNS-Einträge (siehe Schritt 2)
3. Erzwinge Zertifikat-Erstellung (siehe Schritt 3)
### Manuelle Zertifikat-Löschung
Falls das Script nicht funktioniert, kannst du Zertifikate manuell löschen:
```bash
# Alle Zertifikate löschen
docker exec hoerdle-caddy rm -rf /data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/*
# Nur Punycode-Zertifikat löschen (manuell)
docker exec hoerdle-caddy find /data/caddy/certificates -name "*xn--*" -delete
# Container neu starten
docker compose -f docker-compose.caddy.yml --profile production restart caddy
```
### DNS-Propagierung prüfen
Nach DNS-Änderungen kann es bis zu 60 Minuten dauern, bis alle DNS-Server aktualisiert sind:
```bash
# Prüfe DNS-Propagierung weltweit
curl "https://dnschecker.org/#A/hoerdle.de"
curl "https://dnschecker.org/#A/xn--hrdle-jua.de"
```
### Test-Zertifikat erstellen (Staging)
Zum Testen ohne Rate-Limits kannst du ein Staging-Zertifikat erstellen:
1. Temporär Caddyfile ändern (in beiden Domain-Blocks):
```caddyfile
tls {
staging
}
```
2. Container neu starten
3. Zertifikat erstellen lassen
4. Zurück zu Produktion ändern (Staging-Block entfernen)
5. Erneut Container neu starten
### Verifizieren, dass es funktioniert
Nach erfolgreicher Zertifikats-Erstellung:
```bash
# Teste HTTPS-Verbindung
curl -I https://hoerdle.de
curl -I https://xn--hrdle-jua.de
# Prüfe Zertifikat-Details
echo | openssl s_client -connect hoerdle.de:443 -servername hoerdle.de 2>/dev/null | openssl x509 -noout -subject -dates
echo | openssl s_client -connect xn--hrdle-jua.de:443 -servername xn--hrdle-jua.de 2>/dev/null | openssl x509 -noout -subject -dates
```
### Support
Falls das Problem weiterhin besteht:
1. Prüfe Caddy-Logs: `docker logs hoerdle-caddy`
2. Prüfe DNS: `dig +short xn--hrdle-jua.de @8.8.8.8`
3. Prüfe Firewall: `sudo ufw status`
4. Prüfe Port-Zugriff: `curl -I http://hoerdle.de`

185
docs/DEBUG_VERSION.md Normal file
View File

@@ -0,0 +1,185 @@
# Debug Version Display - Remote Server Checklist
## 1. Überprüfe Git-Tags auf dem Remote-Server
```bash
# Im Projekt-Verzeichnis auf dem Remote-Server
cd /path/to/hoerdle
# Zeige alle Tags
git tag -l
# Zeige aktuellen Tag/Version
git describe --tags --always
# Wenn keine Tags angezeigt werden:
git fetch --tags
git describe --tags --always
```
**Erwartetes Ergebnis:** Sollte `v0.1.0.2` oder ähnlich zeigen
---
## 2. Überprüfe die version.txt im Container
```bash
# Zeige den Inhalt der Version-Datei im laufenden Container
docker exec hoerdle cat /app/version.txt
# Sollte die Version zeigen, z.B. "v0.1.0.2"
```
**Erwartetes Ergebnis:** Die aktuelle Version, nicht "unknown" oder "dev"
---
## 3. Überprüfe die Umgebungsvariable im Container
```bash
# Zeige alle Umgebungsvariablen
docker exec hoerdle env | grep APP_VERSION
# Sollte APP_VERSION=v0.1.0.2 oder ähnlich zeigen
```
**Erwartetes Ergebnis:** `APP_VERSION=v0.1.0.2`
---
## 4. Überprüfe die Container-Logs beim Start
```bash
# Zeige die letzten Logs beim Container-Start
docker logs hoerdle | head -20
# Suche speziell nach Version-Ausgaben
docker logs hoerdle | grep -i version
```
**Erwartetes Ergebnis:** Eine Zeile wie "App version: v0.1.0.2"
---
## 5. Teste die API direkt
```bash
# Rufe die Version-API auf
curl http://localhost:3010/api/version
# Sollte JSON zurückgeben: {"version":"v0.1.0.2"}
```
**Erwartetes Ergebnis:** `{"version":"v0.1.0.2"}`
---
## 6. Überprüfe wann der Container gebaut wurde
```bash
# Zeige Image-Informationen
docker images | grep hoerdle
# Zeige detaillierte Container-Informationen
docker inspect hoerdle | grep -i created
```
**Wichtig:** Wenn das Image vor deinem letzten Deployment erstellt wurde, wurde es noch nicht neu gebaut!
---
## 7. Überprüfe Build-Logs
```bash
# Baue das Image neu und beobachte die Ausgabe
docker compose build --no-cache 2>&1 | tee build.log
# Suche nach der Version-Ausgabe im Build
grep -i "Building version" build.log
```
**Erwartetes Ergebnis:** Eine Zeile wie "Building version: v0.1.0.2"
---
## Häufige Probleme und Lösungen
### Problem 1: Tags nicht auf dem Server
```bash
git fetch --tags
git describe --tags --always
```
### Problem 2: Container wurde nicht neu gebaut
```bash
docker compose build --no-cache
docker compose up -d
```
### Problem 3: Alte version.txt im Container
```bash
# Stoppe Container, lösche Image, baue neu
docker compose down
docker rmi $(docker images | grep hoerdle | awk '{print $3}')
docker compose build --no-cache
docker compose up -d
```
### Problem 4: .git Verzeichnis nicht im Build-Context
```bash
# Überprüfe ob .git existiert
ls -la .git
# Überprüfe .dockerignore (sollte .git NICHT ausschließen)
cat .dockerignore 2>/dev/null || echo "Keine .dockerignore Datei"
```
---
## Vollständiger Neustart (wenn nichts anderes hilft)
```bash
# 1. Stoppe alles
docker compose down
# 2. Lösche alte Images
docker rmi $(docker images | grep hoerdle | awk '{print $3}')
# 3. Hole neueste Änderungen und Tags
git pull
git fetch --tags
# 4. Überprüfe Version lokal
git describe --tags --always
# 5. Baue komplett neu
docker compose build --no-cache
# 6. Starte Container
docker compose up -d
# 7. Überprüfe Logs
docker logs hoerdle | grep -i version
# 8. Teste API
curl http://localhost:3010/api/version
```
---
## Debugging-Befehl für alle Checks auf einmal
```bash
echo "=== Git Tags ===" && \
git describe --tags --always && \
echo -e "\n=== version.txt im Container ===" && \
docker exec hoerdle cat /app/version.txt 2>/dev/null || echo "Container läuft nicht oder Datei fehlt" && \
echo -e "\n=== APP_VERSION Env ===" && \
docker exec hoerdle env | grep APP_VERSION || echo "Variable nicht gesetzt" && \
echo -e "\n=== API Response ===" && \
curl -s http://localhost:3010/api/version && \
echo -e "\n\n=== Container Created ===" && \
docker inspect hoerdle | grep -i created | head -1
```
Kopiere diesen Befehl und führe ihn auf dem Remote-Server aus. Schicke mir die Ausgabe!

116
docs/DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,116 @@
# Deployment Guide
## Automated Deployment
Use the deployment script for zero-downtime deployments:
```bash
./scripts/deploy.sh
```
This script will:
1. Create a database backup
2. Pull latest changes from git
3. Fetch all git tags (for version display)
4. Build the new Docker image
5. Restart the container with minimal downtime
6. Clean up old images
## Manual Deployment
If you need to deploy manually:
```bash
# Pull latest changes
git pull
# Fetch tags (important for version display!)
git fetch --tags
# Build and restart
docker compose build
docker compose up -d
```
## Version Display
The app displays the current version in the footer. The version is determined as follows:
1. **During Docker build**: The version is extracted from git tags using `git describe --tags --always`
2. **At runtime**: The version is read from `/app/version.txt` and exposed via the `/api/version` endpoint
3. **Local development**: The version is extracted directly from git on each request
### Building with a specific version
You can override the version during build:
```bash
docker compose build --build-arg APP_VERSION=v1.2.3
```
### Troubleshooting
If the version shows as "dev" or "unknown":
1. Make sure git tags are pushed to the remote repository:
```bash
git push --tags
```
2. On the deployment server, fetch the tags:
```bash
git fetch --tags
```
3. Verify tags are available:
```bash
git describe --tags --always
```
4. Rebuild the Docker image:
```bash
docker compose build --no-cache
docker compose up -d
```
## Health Check
The container includes a health check that monitors the `/api/daily` endpoint. Check the health status:
```bash
docker ps
```
Look for the "healthy" status in the STATUS column.
## Caddy Reverse Proxy (Optional - Production)
For production deployments with automatic SSL/TLS certificates, Caddy can be used as a reverse proxy. Caddy provides:
- Automatic Let's Encrypt certificates (including wildcard certificates)
- HTTP to HTTPS redirect
- Optimized settings for audio streaming and file uploads
- Support for both `hoerdle.de` and `hördle.de` (Punycode: `xn--hrdle-jua.de`)
### Quick Start
1. **Follow the setup guide**: See `CADDY_SETUP.md` for detailed instructions
2. **Configure environment variables**: Add GoDaddy API credentials to your `.env` file
3. **Start with Caddy**:
```bash
docker compose -f docker-compose.yml -f docker-compose.caddy.yml --profile production up -d
```
### Without Caddy
If you don't want to use Caddy, you can deploy normally:
```bash
docker compose -f docker-compose.yml up -d
```
The application will still be accessible on port 3010, but you'll need to configure SSL/TLS separately (e.g., with nginx).
### Caddy Troubleshooting
See `CADDY_SETUP.md` for detailed troubleshooting information.

106
docs/DOCKER_BUILD_FIX.md Normal file
View File

@@ -0,0 +1,106 @@
# Docker Build Fix: Upload-Dateien ausschließen
## Problem
Der Docker Build schlug fehl mit:
```
no space left on device
```
**Ursache**: Die großen MP3-Dateien in `public/uploads/` wurden in den Build-Context kopiert und verbrauchten zu viel Speicherplatz.
## Lösung
Eine `.dockerignore` Datei wurde erstellt, die folgende Dateien/Ordner vom Build ausschließt:
- `public/uploads/*` - Upload-Dateien (werden als Volume gemountet)
- `data/*` - Datenbank-Dateien (werden als Volume gemountet)
- `node_modules` - werden während des Builds installiert
- `.next`, `out`, `build` - Build-Artefakte
- Backup-Dateien, Logs, temporäre Dateien
## Zusätzliche Maßnahmen
Falls der Build weiterhin Probleme macht:
### 1. Docker aufräumen
```bash
# Entferne nicht verwendete Images
docker image prune -a
# Entferne nicht verwendete Container
docker container prune
# Entferne nicht verwendete Volumes (VORSICHT: kann Daten löschen!)
docker volume prune
# Kompletter Cleanup (alles außer laufenden Containern)
docker system prune -a
```
### 2. Speicherplatz prüfen
```bash
# Zeige Speicherplatz
df -h
# Zeige Docker-Speicherverbrauch
docker system df
# Zeige größte Images
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | sort -k3 -h
```
### 3. Build-Kontext prüfen
```bash
# Prüfe was in den Build-Context kopiert wird
docker build --no-cache --progress=plain -t test-build . 2>&1 | grep "transferring context"
```
### 4. Upload-Dateien manuell ausschließen
Falls die `.dockerignore` nicht greift, können Upload-Dateien vorübergehend verschoben werden:
```bash
# Vor dem Build
mv public/uploads public/uploads.backup
mkdir -p public/uploads
touch public/uploads/.gitkeep
# Build durchführen
docker compose build
# Uploads wiederherstellen
rm -rf public/uploads
mv public/uploads.backup public/uploads
```
## Wichtig
Die Upload-Dateien werden **nicht** ins Docker-Image kopiert, sondern als Volume gemountet:
```yaml
volumes:
- ./public/uploads:/app/public/uploads
```
Das bedeutet:
- Upload-Dateien bleiben auf dem Host-System
- Sie werden zur Laufzeit gemountet
- Sie sollten **nicht** ins Image kopiert werden (spart viel Speicher)
## Verifikation
Nach dem Build sollte das Image deutlich kleiner sein:
```bash
# Zeige Image-Größe
docker images hoerdle-hoerdle
# Prüfe ob Uploads im Image sind
docker run --rm hoerdle-hoerdle ls -lh /app/public/uploads
# Sollte nur .gitkeep oder Covers zeigen, keine MP3-Dateien
```

83
docs/FIX_I18N.md Normal file
View File

@@ -0,0 +1,83 @@
# Fix für i18n-Daten (String → JSON Konvertierung)
## Problem
Die Datenbank hat Genre-/Special-/News-Namen als einfache Strings (`"Rock"`) statt JSON (`{"de": "Rock", "en": "Rock"}`) gespeichert, was zu `SyntaxError: "Rock" is not valid JSON` führt.
## Lösung: Manuell ausführen
Führe diese Befehle **direkt auf dem Server** aus:
```bash
cd ~/hoerdle
# 1. Backup erstellen
docker cp hoerdle:/app/data/prod.db ./data/prod.db.backup.$(date +%Y%m%d_%H%M%S)
# 2. Kopiere DB lokal
docker cp hoerdle:/app/data/prod.db ./data/prod.db.tmp
# 3. Setze Berechtigungen
sudo chmod 666 ./data/prod.db.tmp
sudo chmod 775 ./data
# 4. Prüfe ob sqlite3 installiert ist
which sqlite3 || sudo apt-get install -y sqlite3
# 5. Fixe die Datenbank (kopiere diesen Block komplett)
sqlite3 ./data/prod.db.tmp << 'EOF'
UPDATE Genre SET name = json_object('de', name, 'en', name) WHERE typeof(name) = 'text' AND name NOT LIKE '{%';
UPDATE Genre SET subtitle = json_object('de', subtitle, 'en', subtitle) WHERE subtitle IS NOT NULL AND typeof(subtitle) = 'text' AND subtitle NOT LIKE '{%';
UPDATE Special SET name = json_object('de', name, 'en', name) WHERE typeof(name) = 'text' AND name NOT LIKE '{%';
UPDATE Special SET subtitle = json_object('de', subtitle, 'en', subtitle) WHERE subtitle IS NOT NULL AND typeof(subtitle) = 'text' AND subtitle NOT LIKE '{%';
UPDATE News SET title = json_object('de', title, 'en', title) WHERE typeof(title) = 'text' AND title NOT LIKE '{%';
UPDATE News SET content = json_object('de', content, 'en', content) WHERE typeof(content) = 'text' AND content NOT LIKE '{%';
SELECT '✅ Fertig!' as status;
EOF
# 6. Kopiere zurück
docker cp ./data/prod.db.tmp hoerdle:/app/data/prod.db
# 7. Aufräumen
rm ./data/prod.db.tmp
# 8. Container neu starten
docker compose restart hoerdle
# 9. Logs prüfen
docker logs hoerdle --tail=50
```
Falls Schritt 5 mit "permission denied" fehlschlägt, verwende `sudo`:
```bash
sudo sqlite3 ./data/prod.db.tmp << 'EOF'
[... SQL-Befehle wie oben ...]
EOF
```
## Automatisiertes Skript
Alternativ kannst du das automatische Skript verwenden:
```bash
./scripts/fix-i18n-easy.sh
```
Oder das lokale Skript:
```bash
./scripts/fix-i18n-local.sh
```
## Prüfen ob es funktioniert hat
Nach dem Neustart sollte die Seite wieder funktionieren:
```bash
# Prüfe Logs (sollte keine JSON-Fehler mehr zeigen)
docker logs hoerdle --tail=100 | grep -i "json\|error" || echo "✅ Keine JSON-Fehler gefunden"
# Teste die Seite
curl -s https://hoerdle.de/de | head -20
```

349
docs/I18N.md Normal file
View File

@@ -0,0 +1,349 @@
# Internationalisierung (i18n) Dokumentation
Hördle unterstützt vollständige Mehrsprachigkeit (Internationalisierung) für Deutsch und Englisch.
## Übersicht
Die i18n-Implementierung basiert auf [next-intl](https://next-intl-docs.vercel.app/) und nutzt den Next.js App Router mit dynamischen `[locale]`-Segmenten.
## Unterstützte Sprachen
- **Deutsch (de)** - Standardsprache
- **Englisch (en)**
## URL-Struktur
Alle Routen sind lokalisiert:
- `http://localhost:3000/` → Redirect zu `/de` (Standard)
- `http://localhost:3000/de` → Deutsche Version
- `http://localhost:3000/en` → Englische Version
- `http://localhost:3000/de/admin` → Admin-Dashboard (Deutsch)
- `http://localhost:3000/de/Rock` → Rock Genre (Deutsch)
- `http://localhost:3000/de/special/Weihnachtslieder` → Special (Deutsch)
## Architektur
### Verzeichnisstruktur
```
app/
[locale]/ # Lokalisierte Routen
layout.tsx # Root Layout mit i18n Provider
page.tsx # Homepage
admin/
page.tsx # Admin Dashboard
[genre]/
page.tsx # Genre-spezifische Seite
special/
[name]/
page.tsx # Special-Seite
i18n/
request.ts # next-intl Konfiguration
messages/
de.json # Deutsche Übersetzungen
en.json # Englische Übersetzungen
lib/
i18n.ts # Helper-Funktionen für lokalisierte DB-Werte
navigation.ts # Lokalisierte Navigation (Link, useRouter, etc.)
```
### Übersetzungsdateien
Die Übersetzungen sind in JSON-Dateien unter `messages/` organisiert:
```json
{
"Common": {
"loading": "Laden...",
"error": "Ein Fehler ist aufgetreten"
},
"Game": {
"play": "Abspielen",
"pause": "Pause",
"won": "Gewonnen!"
},
"Home": {
"welcome": "Willkommen bei Hördle"
}
}
```
### Datenbank-Schema
Die folgenden Modelle unterstützen mehrsprachige Felder:
#### Genre
- `name`: JSON `{ "de": "Rock", "en": "Rock" }`
- `subtitle`: JSON `{ "de": "Klassischer Rock", "en": "Classic Rock" }`
#### Special
- `name`: JSON `{ "de": "Weihnachtslieder", "en": "Christmas Songs" }`
- `subtitle`: JSON `{ "de": "Festliche Musik", "en": "Festive Music" }`
#### News
- `title`: JSON `{ "de": "Neues Feature", "en": "New Feature" }`
- `content`: JSON `{ "de": "Markdown Inhalt...", "en": "Markdown content..." }`
### Helper-Funktionen
#### `getLocalizedValue(value, locale, fallback?)`
Extrahiert den lokalisierten Wert aus einem JSON-Objekt:
```typescript
import { getLocalizedValue } from '@/lib/i18n';
const genreName = getLocalizedValue(genre.name, 'de'); // "Rock"
const genreNameEn = getLocalizedValue(genre.name, 'en'); // "Rock"
```
**Fallback-Verhalten:**
1. Versucht die angeforderte Locale (`de` oder `en`)
2. Fallback zu `de` falls nicht vorhanden
3. Fallback zu `en` falls nicht vorhanden
4. Fallback zum ersten verfügbaren Schlüssel
5. Fallback zum übergebenen `fallback`-Parameter
#### `createLocalizedObject(de, en?)`
Erstellt ein lokalisiertes Objekt:
```typescript
import { createLocalizedObject } from '@/lib/i18n';
const name = createLocalizedObject('Rock', 'Rock');
// { de: "Rock", en: "Rock" }
```
## Verwendung in Komponenten
### Server Components
```typescript
import { getTranslations } from 'next-intl/server';
import { getLocalizedValue } from '@/lib/i18n';
export default async function Page({ params }: { params: { locale: string } }) {
const { locale } = await params;
const t = await getTranslations('Home');
const genreName = getLocalizedValue(genre.name, locale);
return <h1>{t('welcome')}</h1>;
}
```
### Client Components
```typescript
'use client';
import { useTranslations } from 'next-intl';
import { useLocale } from 'next-intl';
export default function Game() {
const t = useTranslations('Game');
const locale = useLocale();
return <button>{t('play')}</button>;
}
```
### Navigation
Verwende die lokalisierte Navigation aus `lib/navigation.ts`:
```typescript
import { Link } from '@/lib/navigation';
// Automatisch lokalisiert
<Link href="/admin">Admin</Link>
<Link href="/Rock">Rock</Link>
```
## Admin-Interface
Das Admin-Dashboard unterstützt mehrsprachige Eingaben:
1. **Sprach-Tabs:** Wechsle zwischen `DE` und `EN` Tabs
2. **Genre/Special/News:** Alle Felder können in beiden Sprachen bearbeitet werden
3. **Vorschau:** Sieh dir die lokalisierte Version direkt an
### Beispiel: Genre erstellen
1. Öffne `/de/admin`
2. Wähle den `DE` Tab
3. Gib Name und Subtitle ein
4. Wechsle zum `EN` Tab
5. Gib die englischen Übersetzungen ein
6. Speichere
## Migration bestehender Daten
Bestehende Daten werden automatisch migriert:
1. **Migration `20251128131405_add_i18n_columns`:** Fügt neue JSON-Spalten hinzu
2. **Migration `20251128132806_switch_to_json_columns`:** Konvertiert String-Spalten zu JSON
**Wichtig:** Alte String-Werte werden automatisch in beide Sprachen kopiert:
- `"Rock"``{ "de": "Rock", "en": "Rock" }`
## Proxy
Der Proxy (`proxy.ts`) leitet Anfragen automatisch um:
- `/``/de` (Standard)
- Ungültige Locales → 404
- Validiert Locale-Parameter
## Sprachumschalter
Die `LanguageSwitcher`-Komponente ermöglicht Nutzern, zwischen Sprachen zu wechseln:
```typescript
import LanguageSwitcher from '@/components/LanguageSwitcher';
<LanguageSwitcher />
```
Die aktuelle Route bleibt erhalten, nur die Locale ändert sich:
- `/de/admin``/en/admin`
- `/de/Rock``/en/Rock`
## API-Endpunkte
API-Routen unterstützen einen optionalen `locale`-Parameter:
```typescript
GET /api/genres?locale=de
GET /api/specials?locale=en
GET /api/news?locale=de
```
Falls kein `locale` angegeben wird, wird `de` als Standard verwendet.
## Best Practices
### 1. Immer `getLocalizedValue` verwenden
**Falsch:**
```typescript
<span>{genre.name}</span> // Rendert { de: "...", en: "..." }
```
**Richtig:**
```typescript
<span>{getLocalizedValue(genre.name, locale)}</span>
```
### 2. Übersetzungsschlüssel konsistent benennen
Verwende Namespaces für bessere Organisation:
- `Common.*` - Allgemeine UI-Elemente
- `Game.*` - Spiel-spezifische Texte
- `Home.*` - Homepage-Texte
- `Navigation.*` - Navigations-Elemente
### 3. Fallbacks definieren
Immer einen Fallback-Wert angeben:
```typescript
const name = getLocalizedValue(genre.name, locale, 'Unbekannt');
```
### 4. Neue Übersetzungen hinzufügen
1. Füge den Schlüssel zu `messages/de.json` hinzu
2. Füge den Schlüssel zu `messages/en.json` hinzu
3. Verwende `useTranslations('Namespace')` oder `getTranslations('Namespace')`
## Troubleshooting
### 404-Fehler auf `/de` oder `/en`
**Problem:** Route wird nicht gefunden.
**Lösung:**
1. Überprüfe, ob `proxy.ts` korrekt konfiguriert ist
2. Stelle sicher, dass `app/[locale]/layout.tsx` existiert
3. Prüfe die `i18n/request.ts` Konfiguration
### "Objects are not valid as a React child"
**Problem:** Ein JSON-Objekt wird direkt gerendert statt des lokalisierten Werts.
**Lösung:**
Verwende `getLocalizedValue()`:
```typescript
// ❌ Falsch
<span>{genre.name}</span>
// ✅ Richtig
<span>{getLocalizedValue(genre.name, locale)}</span>
```
### Übersetzungen werden nicht angezeigt
**Problem:** Texte erscheinen als Schlüssel (z.B. `"Game.play"`).
**Lösung:**
1. Überprüfe, ob der Übersetzungsschlüssel in `messages/de.json` und `messages/en.json` existiert
2. Stelle sicher, dass der Namespace korrekt ist: `useTranslations('Game')` für `Game.play`
3. Prüfe die JSON-Syntax auf Fehler
### Admin-Interface zeigt Objekte statt Text
**Problem:** In Dropdowns oder Listen werden `{ de: "...", en: "..." }` angezeigt.
**Lösung:**
Verwende `getLocalizedValue()` in allen Render-Funktionen:
```typescript
// ❌ Falsch
<option value={s.id}>{s.name}</option>
// ✅ Richtig
<option value={s.id}>{getLocalizedValue(s.name, activeTab)}</option>
```
## Erweiterung um weitere Sprachen
Um eine neue Sprache hinzuzufügen (z.B. Französisch):
1. **Übersetzungsdatei erstellen:**
```bash
cp messages/de.json messages/fr.json
```
2. **Übersetzungen hinzufügen:**
Bearbeite `messages/fr.json` mit französischen Übersetzungen
3. **Locale zur Konfiguration hinzufügen:**
- `i18n/request.ts`: `const locales = ['en', 'de', 'fr'];`
- `proxy.ts`: `locales: ['en', 'de', 'fr']`
- `lib/navigation.ts`: `export const locales = ['de', 'en', 'fr'] as const;`
4. **Layout aktualisieren:**
```typescript
// app/[locale]/layout.tsx
if (!['en', 'de', 'fr'].includes(locale)) {
notFound();
}
```
5. **LanguageSwitcher erweitern:**
Füge einen Button für `fr` hinzu
6. **Datenbank-Migration:**
Bestehende Daten behalten ihre Struktur, neue Einträge können optional `fr` enthalten
## Weitere Ressourcen
- [next-intl Dokumentation](https://next-intl-docs.vercel.app/)
- [Next.js App Router i18n](https://nextjs.org/docs/app/building-your-application/routing/internationalization)

206
docs/TROUBLESHOOTING.md Normal file
View File

@@ -0,0 +1,206 @@
# Troubleshooting Guide
## Application Error: "a server-side exception has occurred"
Dieser Fehler tritt auf, wenn die Next.js-Anwendung auf dem Server einen Fehler hat.
### ⚠️ Datenbank-Berechtigungen (wenn DB von anderem Server kopiert wurde)
**Symptom**: Application Error nach dem Kopieren einer Datenbank von einem anderen Server
**Ursache**: SQLite benötigt Schreibrechte auf:
- Die Datenbankdatei selbst (`prod.db`)
- Das Datenbankverzeichnis (für temporäre Dateien wie `-wal`, `-shm`)
**Sofort-Lösung (auf dem Server ausführen)**:
```bash
# 1. Setze Berechtigungen für Datenbankverzeichnis und Datei
chmod 775 ./data
chmod 664 ./data/prod.db
# 2. Falls temporäre SQLite-Dateien existieren, auch diese:
chmod 664 ./data/*.db-wal 2>/dev/null || true
chmod 664 ./data/*.db-shm 2>/dev/null || true
# 3. Oder verwende das Fix-Skript:
./scripts/fix-database-permissions.sh
# 4. Container neu starten
docker compose restart hoerdle
# 5. Logs prüfen
docker logs hoerdle --tail=50
```
**Warum passiert das?**
- Wenn du eine Datenbankdatei von einem anderen Server kopierst, behält sie die ursprünglichen Berechtigungen
- SQLite muss Schreibrechte haben, um zu funktionieren
- Auch das Verzeichnis braucht Schreibrechte (für SQLite-WAL-Modus)
### Sofort-Diagnose (auf dem Server ausführen)
```bash
# 1. Container-Logs prüfen (die wichtigste Information!)
docker logs hoerdle --tail=100
# 2. Container-Status prüfen
docker ps | grep hoerdle
# 3. Prüfe ob Datenbank existiert
docker exec hoerdle ls -lh /app/data/prod.db
# 4. Prüfe ob Server auf Port 3000 antwortet (intern)
docker exec hoerdle curl -f http://localhost:3000/api/daily
# 5. Prüfe Health Check
docker inspect hoerdle --format='{{json .State.Health}}' | python3 -m json.tool
```
### Häufige Ursachen und Lösungen
#### 1. Datenbankfehler / Migrationen fehlgeschlagen
**Symptom**: Logs zeigen Prisma-Fehler oder "database locked"
**Lösung**:
```bash
# Container-Logs prüfen
docker logs hoerdle | grep -i "migration\|database\|prisma"
# Falls Migrationen fehlgeschlagen sind:
docker compose restart hoerdle
# Bei persistierenden Problemen: Datenbank-Backup prüfen
ls -lh ./backups/
```
#### 2. Container läuft nicht oder ist crashed
**Symptom**: Container existiert nicht oder Status zeigt "Exited"
**Lösung**:
```bash
# Container-Status prüfen
docker ps -a | grep hoerdle
# Container neu starten
docker compose up -d
# Falls Container nicht startet, Logs prüfen
docker logs hoerdle --tail=200
```
#### 3. Caddy kann Container nicht erreichen
**Symptom**: 502 Bad Gateway oder "connection refused" in Caddy-Logs
**Lösung**:
```bash
# Prüfe ob hoerdle Container läuft
docker ps | grep hoerdle
# Prüfe Netzwerk
docker network inspect hoerdle_default
# Prüfe Caddy-Logs
docker logs hoerdle-caddy --tail=50
# Stelle sicher, dass Caddyfile Port 3000 verwendet (nicht 3010!)
grep "reverse_proxy" Caddyfile
```
#### 4. Fehlende Umgebungsvariablen
**Symptom**: Logs zeigen undefined variables
**Lösung**:
```bash
# Prüfe wichtige Umgebungsvariablen
docker exec hoerdle env | grep -E "DATABASE_URL|NODE_ENV"
# Prüfe .env Datei (falls vorhanden)
cat .env | grep DATABASE_URL
```
#### 5. Build-Fehler oder fehlerhafte Dateien
**Symptom**: Container startet, aber App crasht sofort
**Lösung**:
```bash
# Container komplett neu bauen
docker compose down
docker compose build --no-cache
docker compose up -d
# Prüfe Build-Logs
docker compose build 2>&1 | tee build.log
```
### Detaillierte Log-Analyse
```bash
# Alle Fehler in Logs finden
docker logs hoerdle 2>&1 | grep -i -E "error|exception|fatal|panic" | tail -50
# Prisma-spezifische Fehler
docker logs hoerdle 2>&1 | grep -i prisma | tail -20
# Next.js-spezifische Fehler
docker logs hoerdle 2>&1 | grep -i "next\|react" | tail -20
```
### Netzwerk-Debugging
```bash
# Teste Verbindung von Caddy zu Hördle
docker exec hoerdle-caddy wget -O- http://hoerdle:3000/api/daily
# Prüfe alle Container im Netzwerk
docker network inspect hoerdle_default --format='{{range .Containers}}{{.Name}}: {{.IPv4Address}}{{"\n"}}{{end}}'
```
### Datenbank-Debugging
```bash
# Prüfe Datenbank-Integrität
docker exec hoerdle npx prisma db pull
# Prüfe Datenbank-Struktur
docker exec hoerdle npx prisma studio &
# (dann Browser öffnen - erfordert X11 forwarding oder lokalen Zugriff)
```
### Quick-Fix: Vollständiger Neustart
Wenn nichts anderes hilft:
```bash
# 1. Backup erstellen
cp ./data/prod.db ./backups/prod_$(date +%Y%m%d_%H%M%S).db
# 2. Container stoppen
docker compose down
# 3. Container neu starten
docker compose up -d
# 4. Logs beobachten
docker compose logs -f
```
## Bei weiterem Bedarf
Sammle folgende Informationen für weitere Hilfe:
```bash
echo "=== Container Status ===" && \
docker ps -a | grep hoerdle && \
echo -e "\n=== Letzte 50 Log-Zeilen ===" && \
docker logs hoerdle --tail=50 && \
echo -e "\n=== Fehler in Logs ===" && \
docker logs hoerdle 2>&1 | grep -i error | tail -20
```
Kopiere die vollständige Ausgabe und sende sie weiter.

99
docs/WHITE_LABEL.md Normal file
View File

@@ -0,0 +1,99 @@
# White Labeling Guide
This application is designed to be easily white-labeled. You can customize the branding, colors, and configuration without modifying the core code.
## Configuration
The application is configured via environment variables. You can set these in a `.env` or `.env.local` file.
### Branding
| Variable | Description | Default |
|----------|-------------|---------|
| `NEXT_PUBLIC_APP_NAME` | The name of the application. | `Hördle` |
| `NEXT_PUBLIC_APP_DESCRIPTION` | The description used in metadata. | `Daily music guessing game...` |
| `NEXT_PUBLIC_DOMAIN` | The domain name (used for sharing). | `hoerdle.elpatron.me` |
| `NEXT_PUBLIC_TWITTER_HANDLE` | Twitter handle for metadata. | `@elpatron` |
### Analytics (Plausible)
| Variable | Description | Default |
|----------|-------------|---------|
| `NEXT_PUBLIC_PLAUSIBLE_DOMAIN` | The domain to track in Plausible. | `hoerdle.elpatron.me` |
| `NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC` | The URL of the Plausible script. | `https://plausible.elpatron.me/js/script.js` |
### Credits
| Variable | Description | Default |
|----------|-------------|---------|
| `NEXT_PUBLIC_CREDITS_ENABLED` | Enable/disable footer credits (`true`/`false`). | `true` |
| `NEXT_PUBLIC_CREDITS_TEXT` | Text before the link. | `Vibe coded with ☕ and 🍺 by` |
| `NEXT_PUBLIC_CREDITS_LINK_TEXT` | Text of the link. | `@elpatron@digitalcourage.social` |
| `NEXT_PUBLIC_CREDITS_LINK_URL` | URL of the link. | `https://digitalcourage.social/@elpatron` |
## Theming
The application uses CSS variables for theming. You can override these variables in your own CSS file or by modifying `app/globals.css`.
### Key Colors
| Variable | Description | Default |
|----------|-------------|---------|
| `--primary` | Main action color (buttons). | `#000000` |
| `--secondary` | Secondary actions. | `#4b5563` |
| `--accent` | Accent color. | `#667eea` |
| `--success` | Success state (correct guess). | `#22c55e` |
| `--danger` | Error state (wrong guess). | `#ef4444` |
| `--warning` | Warning state (stars). | `#ffc107` |
| `--muted` | Muted backgrounds. | `#f3f4f6` |
### Example: Red Theme
To create a red-themed version, add this to your CSS:
```css
:root {
--primary: #dc2626;
--accent: #ef4444;
--accent-gradient: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%);
}
```
## Assets
To replace the logo and icons:
1. Replace `public/favicon.ico`.
2. Replace `public/icon.png` (if it exists).
3. Update `app/manifest.ts` if you have custom icon paths.
3. Update `app/manifest.ts` if you have custom icon paths.
## Docker Deployment
When deploying with Docker, please note that **Next.js inlines `NEXT_PUBLIC_` environment variables at build time**.
This means you cannot simply change the environment variables in `docker-compose.yml` and restart the container to change the branding. You must **rebuild the image**.
### Using Docker Compose
1. Create a `.env` file with your custom configuration:
```bash
NEXT_PUBLIC_APP_NAME="My Music Game"
NEXT_PUBLIC_THEME_COLOR="#ff0000"
# ... other variables
```
2. Ensure your `docker-compose.yml` passes these variables as build arguments (already configured in `docker-compose.example.yml`):
```yaml
services:
hoerdle:
build:
context: .
args:
NEXT_PUBLIC_APP_NAME: ${NEXT_PUBLIC_APP_NAME}
# ...
```
3. Build and start the container:
```bash
docker compose up --build -d
```

55
docs/walkthrough.md Normal file
View File

@@ -0,0 +1,55 @@
# Genre/Tag System Implementation Walkthrough
## Overview
Implemented a comprehensive Genre/Tag system for Hördle, allowing songs to be categorized and users to play genre-specific daily puzzles.
## Changes
### Database
- **New Model**: `Genre` (id, name, songs, dailyPuzzles).
- **Updated Model**: `Song` (added M-N relation to `Genre`).
- **Updated Model**: `DailyPuzzle` (added optional `genreId`, updated unique constraint to `[date, genreId]`).
### Backend API
- **`app/api/genres/route.ts`**: New endpoints for GET (list) and POST (create) and DELETE genres.
- **`app/api/songs/route.ts`**: Updated to handle genre assignment (POST/PUT) and retrieval (GET).
- **`app/api/daily/route.ts`**: Updated to support `?genre=<name>` query parameter.
- **`lib/dailyPuzzle.ts`**: Shared logic for fetching/creating daily puzzles (Global or Genre-specific).
### Frontend (Admin)
- **Genre Management**: Create and delete genres.
- **Song Assignment**: Assign genres during upload and edit.
- **Post-Upload Workflow**: Prompt to assign genres immediately after upload.
- **Song List**: Display assigned genres in the table.
### Frontend (User)
- **Genre Selection**: Links on the main page to switch between Global and Genre-specific games.
- **Game Logic**: Refactored to support independent game states per genre (localStorage keys: `hoerdle_game_state_<genre>`).
- **Dynamic Route**: `app/[genre]/page.tsx` for genre-specific URLs.
- **Sharing**: Share text now includes the genre name.
### Deployment
- **Auto-Migration**: Added `scripts/docker-entrypoint.sh` to run `prisma migrate deploy` on startup.
- **Dockerfile**: Updated to use the entrypoint script.
- **Dependencies**: Moved `prisma` to `dependencies` in `package.json`.
## Verification Results
### Automated Build
- `npm run build` passed successfully.
- `npx prisma generate` passed.
### Manual Verification Steps (Recommended)
1. **Admin Dashboard**:
* Go to `/admin`.
* Create a new genre (e.g., "Rock").
* Upload a song and assign "Rock" to it.
* Edit an existing song and assign "Rock".
2. **User Interface**:
* Go to `/`. Verify "Global" game works.
* Click "Rock". Verify URL changes to `/Rock`.
* Play the "Rock" game. Verify it picks a song tagged with "Rock".
* Verify stats are separate for Global and Rock.
3. **Deployment**:
* Deploy to Docker.
* Verify migrations run automatically on startup.