- TypeScript 95.1%
- CSS 2.5%
- Shell 1.4%
- HTML 0.8%
- Dockerfile 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| apps | ||
| docs | ||
| games | ||
| legal | ||
| packages | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| compose.yaml | ||
| deploy.sh | ||
| Dockerfile | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| tsconfig.base.json | ||
| vitest.config.ts | ||
games
Eine Sammlung einfacher Browser-Spiele, die auf Handys und Computern spielbar sind. Nach jeder Spielrunde wird für eine festgelegte Zeit eine Werbetafel mit Stellenanzeigen eingeblendet. Über ein Backend wird gesteuert, welche Spiele angeboten und welche Anzeigen geschaltet werden.
Entwicklung
Voraussetzungen: Node.js ≥ 22 und pnpm.
pnpm install
pnpm dev # API (Port 3000), Portal (http://localhost:5173) und
# Admin (http://localhost:5174/admin/) starten
pnpm test # Tests mit Vitest
pnpm typecheck # TypeScript-Prüfung aller Pakete
pnpm build # Portal usw. nach dist/ bauen
Die Entwicklungsdatenbank liegt unter data/games.db (nicht eingecheckt).
Das erste Admin-Konto legt die API aus ADMIN_USER und ADMIN_PASSWORD an.
Die Werte kommen aus der .env im Wurzelverzeichnis, falls vorhanden, oder
aus der Umgebung, z. B. ADMIN_USER=dev ADMIN_PASSWORD=… pnpm dev.
Fiktive Beispielanzeigen legt pnpm --filter @games/api seed:beispiele an.
Nach Änderungen an apps/api/src/db/schema.ts erzeugt
pnpm --filter @games/api db:generate eine neue Migration. Migrationen
werden beim Start der API automatisch eingespielt.
Deployment
Die Anwendung läuft als ein Docker-Container auf einem Linux-Server. TLS übernimmt ein vorgelagerter Reverse Proxy (ADR 0007).
Voraussetzungen auf dem Server
- git
- Docker mit dem Compose-Plugin (
docker compose, v2). Unter Debian/Ubuntu z. B.apt install docker-compose-plugin(Paketquelle von Docker) bzw.apt install docker-compose-v2(Ubuntu). Das altedocker-compose(v1) funktioniert auch, ist aber veraltet.deploy.shumgeht dessen Fehler mit neueren Docker-Versionen. - ein Reverse Proxy mit TLS, z. B. Caddy, nginx oder Traefik
Erstinstallation
git clone https://git.bonhuis.de/karsten/games.git
cd games
./deploy.sh
Beim ersten Lauf legt deploy.sh die Datei .env an und fragt die
fehlenden Werte ab:
| Variable | Bedeutung |
|---|---|
PUBLIC_HOSTNAME |
Öffentlicher Hostname, z. B. spiele.example.de |
HTTP_BIND |
Adresse und Port für den Reverse Proxy (Standard 127.0.0.1:3000) |
ADMIN_USER |
Benutzername des ersten Admins (Standard admin) |
ADMIN_PASSWORD |
Passwort des ersten Admins (mindestens 10 Zeichen, verdeckte Eingabe) |
SESSION_SECRET |
Wird automatisch erzeugt |
Die Vorlage ist .env.example. Die .env wird nicht
eingecheckt und nur für den Besitzer lesbar angelegt. Ohne Terminal, z. B. in
einer Automatisierung, können die Werte auch als Umgebungsvariablen übergeben
werden:
PUBLIC_HOSTNAME=spiele.example.de ADMIN_PASSWORD='…' ./deploy.sh
Außerdem legt es legal/impressum.md und legal/datenschutz.md aus den
Vorlagen (*.md.example) an, falls sie fehlen. Beide Dateien sind Markdown
und werden wie die .env nicht eingecheckt. Solange sie noch Stellen mit
[BITTE ANPASSEN …] enthalten, bricht das Script ab. Also Texte anpassen,
Hinweiszeilen löschen und ./deploy.sh erneut aufrufen. Die Seiten
erscheinen dann unter /impressum und /datenschutz. Die Vorlagen sind
keine Rechtsberatung. Nach Änderungen an den Texten ./deploy.sh erneut
ausführen.
Danach baut das Script das Image, startet den Container, wartet auf den
Health-Check (/api/health) und räumt alte Images auf.
Reverse Proxy
Der Proxy terminiert TLS und leitet alle Anfragen für PUBLIC_HOSTNAME an
HTTP_BIND weiter (Standard 127.0.0.1:3000). Er muss die Header
X-Forwarded-For, X-Forwarded-Proto und X-Forwarded-Host setzen, denn
die Anwendung wertet sie aus. In den Beispielen spiele.example.de und die
Zertifikatspfade anpassen. Zertifikate lassen sich z. B. mit certbot
(Let's Encrypt) erzeugen.
nginx
/etc/nginx/sites-available/spiele.example.de, danach nach
sites-enabled/ verlinken und nginx -t && systemctl reload nginx:
server {
listen 80;
listen [::]:80;
server_name spiele.example.de;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name spiele.example.de;
ssl_certificate /etc/letsencrypt/live/spiele.example.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/spiele.example.de/privkey.pem;
# Uploads im Admin (z. B. Logos)
client_max_body_size 10m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
proxy_set_header X-Forwarded-Host $host;
}
}
Apache
Benötigte Module aktivieren: a2enmod proxy proxy_http headers ssl rewrite.
Dann /etc/apache2/sites-available/spiele.example.de.conf anlegen,
a2ensite spiele.example.de ausführen und mit
apachectl configtest && systemctl reload apache2 neu laden:
<VirtualHost *:80>
ServerName spiele.example.de
RewriteEngine On
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
<VirtualHost *:443>
ServerName spiele.example.de
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/spiele.example.de/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/spiele.example.de/privkey.pem
ProxyPreserveHost On
ProxyRequests Off
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
# X-Forwarded-For und X-Forwarded-Host setzt mod_proxy automatisch.
RequestHeader set X-Forwarded-Proto "https"
</VirtualHost>
Caddy
Caddy holt die Zertifikate selbst und setzt die X-Forwarded-*-Header
automatisch:
spiele.example.de {
reverse_proxy 127.0.0.1:3000
}
Admin
Der Admin ist unter https://<PUBLIC_HOSTNAME>/admin/ erreichbar.
- Anmeldung: Beim ersten Start wird das Konto aus
ADMIN_USERundADMIN_PASSWORDangelegt. Spätere Änderungen an der.envwirken sich darauf nicht mehr aus. Passwörter ändert man im Admin unter „Konten“. - Spiele: freischalten, sperren und die Reihenfolge im Portal festlegen. Neu hinzugekommene Spiele starten gesperrt. Per Häkchen lässt sich ein Spiel in der Übersicht als „NEU“ kennzeichnen. „Testen“ öffnet ein Spiel ohne Werbung, auch wenn es gesperrt ist. Dabei wird nichts gezählt (ADR 0016).
- Anzeigen: anlegen, bearbeiten, pausieren und löschen, mit Pflicht-Link zur Stellenanzeige in Langform, Zeitraum und Gewichtung. Eine Live-Vorschau zeigt die Werbetafel so, wie Spieler sie sehen.
- Statistik: Einblendungen, Klicks und Klickrate pro Tag und Anzeige;
außerdem gespielte Runden pro Tag und Spiel, nach Uhrzeit und als Anteil
(ADR 0017, Uhrzeit in
STATS_TIMEZONE, StandardEurope/Berlin). - Übersichtsseite: Überschrift (Standard „Spiele“) und optionaler Einleitungstext unter „Einstellungen“.
- Einstellungen: Anzeigedauer der Werbetafel, optionales früheres Überspringen und die Zahl der werbefreien Runden pro Spiel und Tag (Standard 3). Gezählt wird im Browser der Spieler. Die Spieler sehen keinen Hinweis darauf (ADR 0014, ADR 0015). Außerdem die Primärfarbe für Schaltflächen und Akzente im Portal. Die Textfarbe darauf wird automatisch gewählt, eine Vorschau zeigt den Kontrast.
Das Sitzungs-Cookie ist nur über HTTPS gültig. Ohne Reverse Proxy
funktioniert die Anmeldung deshalb nur über http://localhost, z. B. per
SSH-Tunnel ssh -L 3000:127.0.0.1:3000 <server> und dann
http://localhost:3000/admin/.
Aktualisieren
cd games
./deploy.sh
Welcher Stand installiert wird, steuern Release-Tags im git
(ADR 0018). Das Script holt
Commits und Tags von origin, checkt das neueste Tag der Form v… aus
(nach Versionsnummer sortiert), baut den Container neu und ersetzt den
laufenden. Bereits gesetzte Werte in der .env bleiben erhalten.
| Aufruf | Installierter Stand |
|---|---|
./deploy.sh |
neuestes Release-Tag, z. B. v1.3.0 |
./deploy.sh --tag v1.2.0 |
genau dieses Tag, z. B. um zurückzugehen |
./deploy.sh --latest |
neuester Commit auf main, unabhängig von Tags |
./deploy.sh --no-pull |
der ausgecheckte Stand, ohne git |
Was sich von Version zu Version ändert und was beim Aktualisieren von Hand zu
tun ist, steht in docs/WAS-IST-NEU.md.
Der installierte Stand steht im Portal neben den Links zu Impressum und
Datenschutz: das Release-Tag, bei --latest ohne passendes Tag die kurze
Commit-ID.
Hat das Repo auf dem Server lokale Änderungen an eingecheckten Dateien, bricht
das Script ab. .env und die Rechtstexte sind nicht eingecheckt und davon
nicht betroffen.
Ein Release anlegen (auf dem Entwicklungsrechner):
-
In
docs/WAS-IST-NEU.mdden Abschnitt „Noch nicht veröffentlicht“ in einen Abschnitt für die neue Version umwandeln (mit Datum) und einen leeren Abschnitt „Noch nicht veröffentlicht“ anlegen. Committen. -
Tag setzen und pushen:
git tag -a v1.3.0 -m "Abgestempelt! und neue Statistik" git push origin v1.3.0
Tags werden vom Commit-Hook nicht automatisch gepusht. Die Versionsnummer folgt grob Semantic Versioning: neue Spiele oder Funktionen erhöhen die mittlere Stelle, Korrekturen die letzte.
Daten und Backup
Datenbank und hochgeladene Logos liegen im Docker-Volume games_games-data (unter
/data im Container). Für ein Backup dieses Volume sichern, z. B.:
docker run --rm -v games_games-data:/data -v "$PWD":/backup busybox \
tar czf /backup/games-data-$(date +%F).tar.gz -C /data .
Spiele als App
Jedes Spiel hat eine eigene Adresse, z. B. https://<PUBLIC_HOSTNAME>/spiele/antragsflut/,
und lässt sich dort als App installieren. Unter Android und am Desktop bietet
der Browser das an, unter iOS geht es über „Teilen → Zum Home-Bildschirm“.
Die installierte App startet direkt ins Spiel und funktioniert auch offline,
dann ohne Werbetafel.
Dokumentation
Neue Spiele entwickeln: Die Anleitung für Coding Agents (und Menschen)
steht in docs/SPIELE-ENTWICKELN.md. Sie
beschreibt Aufbau, Schnittstelle, Regeln und eine Checkliste für neue Spiele.
Die Planung steht in docs/ROADMAP.md, Spielkonzepte in
docs/spiele/.
Architekturentscheidungen werden als ADRs (Architecture Decision Records) in
docs/adr/ festgehalten.