No description
  • TypeScript 95.1%
  • CSS 2.5%
  • Shell 1.4%
  • HTML 0.8%
  • Dockerfile 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Karsten Bonhuis 39fd0893b3 Was ist neu: v1.4.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:30:49 +02:00
apps Admin: Hinweis bei Schaltern unter dem Häkchen; Doku zu Ein/Aus-Einstellungen 2026-10-05 20:29:31 +02:00
docs Was ist neu: v1.4.1 2026-10-05 20:30:49 +02:00
games Dienstreise: im Admin wählbar, aus welchen Eigenschaften Fragen entstehen 2026-10-05 20:28:37 +02:00
legal Datenschutz-Vorlage: Zählung der Spielrunden 2026-09-29 17:03:47 +02:00
packages Ein/Aus-Einstellungen für Spiele: SDK und Admin (ADR 0020) 2026-10-05 20:27:25 +02:00
.dockerignore Dockerfile für Build und Betrieb 2026-09-27 22:56:06 +02:00
.env.example compose.yaml und angepasste .env-Vorlage 2026-09-27 22:56:15 +02:00
.gitignore Dienstreise: Länderdaten einchecken, Typprüfung ohne Icon-Skript 2026-09-29 18:30:54 +02:00
compose.yaml Portal zeigt den installierten Stand (Tag oder Commit-ID) neben Impressum und Datenschutz 2026-09-29 18:16:13 +02:00
deploy.sh Portal zeigt den installierten Stand (Tag oder Commit-ID) neben Impressum und Datenschutz 2026-09-29 18:16:13 +02:00
Dockerfile Portal zeigt den installierten Stand (Tag oder Commit-ID) neben Impressum und Datenschutz 2026-09-29 18:16:13 +02:00
package.json Admin: Grundgerüst mit Anmeldung, Navigation und API-Client 2026-09-27 23:46:58 +02:00
pnpm-lock.yaml Fliegende Tasse: zufällige Schreibtisch-Landschaft mit Untersetzer (Stufe 1) 2026-10-05 20:00:55 +02:00
pnpm-workspace.yaml API: SQLite-Datenbank mit Drizzle, Schema und erste Migration 2026-09-27 23:30:46 +02:00
README.md Was ist neu: Änderungen pro Release (v1.0.0 bis v1.2.0) 2026-09-29 19:13:13 +02:00
tsconfig.base.json TypeScript: nur entfernbare Syntax erlauben, damit Node TS direkt ausführen kann 2026-09-27 22:54:50 +02:00
vitest.config.ts game-sdk: Werkzeug, das Pixel-Sprites als PNG speichert (für App-Icons) 2026-09-28 00:14:39 +02:00

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 alte docker-compose (v1) funktioniert auch, ist aber veraltet. deploy.sh umgeht 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_USER und ADMIN_PASSWORD angelegt. Spätere Änderungen an der .env wirken 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, Standard Europe/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):

  1. In docs/WAS-IST-NEU.md den 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.

  2. 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.