Files
2026-09-24 17:57:54 +02:00

9.5 KiB

Deployment auf cPanel („Setup Python App“ / Phusion Passenger)

Diese Anleitung beschreibt die Installation auf einem Shared-Hosting mit cPanel und CloudLinux-„Setup Python App“. Die App braucht keine Hintergrundprozesse, keinen Redis, keine WebSockets und keine kompilierten Pakete: Flask, python-dotenv und SQLite reichen.

Überblick

cPanel-Feld Wert
Python version 3.10 oder neuer (neueste verfügbare wählen)
Application root Ordner im Home-Verzeichnis, z. B. glory_defense
Application URL Domain oder Unterpfad, z. B. example.ch/ oder example.ch/glory
Application startup file passenger_wsgi.py
Application Entry point application

1. Upload-Paket bauen

Auf dem eigenen Rechner im Projektordner (Windows, Linux oder macOS):

python scripts/build_deploy_zip.py

Das erzeugt dist/glory_defense.zip mit genau den Dateien, die der Server braucht: passenger_wsgi.py, requirements.txt, .env.example und den Ordner app/ (inkl. app/static/js/lib/phaser.min.js und app/static/assets/). Nicht enthalten sind .env (Geheimnisse), instance/ (Datenbank), Tests, .venv, node_modules und Caches. Node.js wird auf dem Server nicht benötigt.

Bitte nicht mit „Komprimierter Ordner“ bzw. Compress-Archive aus Windows PowerShell 5.1 packen: Das schreibt Pfade mit Backslash (app\api.py) ins ZIP, und manche Entpacker auf Linux-Servern machen daraus flache Dateinamen. Dann startet die App mit No module named 'app' nicht. Das Skript verwendet immer /.

Alternativ kann das Repository über cPanel → Git™ Version Control direkt in den Application-Root geklont werden.

2. Python-App anlegen

  1. cPanel → Software → Setup Python App → Create Application.
  2. Felder wie in der Tabelle oben ausfüllen.
  3. Create klicken. cPanel legt den Ordner, eine virtuelle Umgebung (~/virtualenv/glory_defense/<version>/) und eine Beispiel-passenger_wsgi.py an.
  4. Oben auf der Seite erscheint der Befehl zum Aktivieren der venv, etwa:
    source /home/BENUTZER/virtualenv/glory_defense/3.11/bin/activate && cd /home/BENUTZER/glory_defense
    
    Diesen Befehl für Schritt 5 kopieren.

3. Dateien hochladen

  1. cPanel → File Manager → in den Application-Root wechseln (z. B. ~/glory_defense).
  2. Die von cPanel erzeugte passenger_wsgi.py löschen oder überschreiben lassen.
  3. glory_defense.zip hochladen und dort Extract wählen.
  4. Prüfen: passenger_wsgi.py und der Ordner app/ liegen direkt im Application-Root.

4. Umgebungsvariablen setzen

In „Setup Python App“ bei der App unter Environment variables eintragen und danach Save klicken:

Name Wert
SECRET_KEY langer Zufallswert, z. B. Ausgabe von python -c "import secrets; print(secrets.token_hex(32))"
GLORY_DB_PATH optional, Standard instance/glory_defense.sqlite3 (relativ zum Application-Root)
GLORY_RATE_LIMIT_MAX optional, Standard 5
GLORY_RATE_LIMIT_WINDOW optional, Standard 600 (Sekunden)
GLORY_TRUST_PROXY optional, Standard 0. Nur 1, wenn ein vertrauenswürdiger Proxy (z. B. CDN) davorsteht, siehe „Typische Fehler“
GLORY_NAME_BLOCKLIST optional, Standard instance/name_blocklist.txt, siehe „Moderation der Ehrentafel“

Alternativ eine Datei .env im Application-Root anlegen (Vorlage .env.example). In cPanel gesetzte Variablen haben Vorrang vor der .env.

Ohne SECRET_KEY erzeugt die App beim ersten Spielstart instance/secret_key selbst. Das funktioniert, ein eigener Wert in cPanel ist aber sauberer und übersteht Neuinstallationen.

5. Abhängigkeiten installieren

Variante A, über die Oberfläche: In „Setup Python App“ bei der App unter Configuration files requirements.txt eintragen, Add und dann Run Pip Install klicken.

Variante B, per Terminal (cPanel → Terminal oder SSH):

source /home/BENUTZER/virtualenv/glory_defense/3.11/bin/activate && cd /home/BENUTZER/glory_defense
pip install -r requirements.txt

Die Datenbank legt die App beim ersten Aufruf selbst an, ein eigener Schritt ist nicht nötig.

6. Neustarten und prüfen

  • In „Setup Python App“ auf Restart klicken, oder per Terminal:
    mkdir -p tmp && touch tmp/restart.txt
    
  • Im Browser aufrufen:
    • https://example.ch/healthz muss {"status":"ok"} liefern
    • https://example.ch/ zeigt den Disclaimer und danach das Hauptmenü
    • https://example.ch/api/highscores liefert {"highscores":[]}

Läuft die App unter einem Unterpfad (z. B. /glory), funktionieren alle Links automatisch: Das Template erzeugt URLs über url_for, und Passenger übergibt den Pfad als SCRIPT_NAME.

7. Updates einspielen

  1. Lokal python scripts/build_deploy_zip.py ausführen, das neue ZIP hochladen und entpacken (vorhandene Dateien überschreiben). instance/ dabei nicht löschen, denn dort liegen Highscores, Sperrliste und ggf. der automatisch erzeugte Schlüssel.
  2. Falls sich requirements.txt geändert hat: Run Pip Install bzw. pip install -r requirements.txt.
  3. Restart (oder touch tmp/restart.txt).
  4. Im Browser mit Strg+F5 neu laden, damit keine alten JavaScript-Dateien aus dem Cache kommen.

8. Datensicherung

Zu sichern ist nur der Ordner instance/:

  • instance/glory_defense.sqlite3: Highscores
  • instance/name_blocklist.txt: Sperrliste, falls angelegt
  • instance/secret_key: nur vorhanden, wenn kein SECRET_KEY gesetzt ist

9. Moderation der Ehrentafel

Spielernamen erscheinen öffentlich. Unpassende Einträge (Beleidigungen, Namen realer Personen) lassen sich im Terminal löschen. Zuerst die venv aktivieren (Befehl aus Schritt 2.4), dann:

flask --app passenger_wsgi scores list                 # Einträge mit ID anzeigen
flask --app passenger_wsgi scores delete 17 23         # Einträge per ID löschen
flask --app passenger_wsgi scores delete-name "Name"   # alle Einträge mit diesem Namen löschen

Die Befehle brauchen keinen SECRET_KEY. Das ist wichtig, denn in cPanel gesetzte Umgebungsvariablen stehen im Terminal nicht zur Verfügung. Eine .env im Application-Root wird dagegen auch hier gelesen.

Sperrliste (optional): Die Datei instance/name_blocklist.txt anlegen, ein Begriff pro Zeile, # leitet Kommentare ein. Namen, die einen dieser Begriffe enthalten, lehnt die API ab. Groß-/Kleinschreibung, Leerzeichen und Satzzeichen spielen dabei keine Rolle, „Max Muster“ sperrt also auch „max.muster“. Die Datei wird bei jeder Einreichung neu gelesen, ein Neustart ist nicht nötig.

Typische Fehler

Symptom Ursache und Lösung
„Incomplete response received from application“ oder Fehler 500 Details stehen in stderr.log im Application-Root bzw. in cPanel → Errors. Meist fehlen die Abhängigkeiten (Schritt 4) oder die Startdatei ist falsch eingetragen.
ModuleNotFoundError: No module named 'app' passenger_wsgi.py liegt nicht im Application-Root, der Ordner app/ fehlt bzw. liegt eine Ebene zu tief (ZIP in einen Unterordner entpackt), oder das ZIP wurde mit Backslash-Pfaden erstellt (Dateien heißen dann app\api.py). Paket mit python scripts/build_deploy_zip.py bauen.
ModuleNotFoundError: No module named 'flask' Pip-Install lief in der falschen Umgebung. Befehl aus Schritt 2.4 verwenden (venv aktivieren) und erneut installieren (Schritt 5).
SyntaxError oder Flask-Importfehler beim Start Python-Version zu alt. In „Setup Python App“ 3.10 oder neuer wählen (die App kann dafür neu angelegt werden, Dateien bleiben erhalten).
sqlite3.OperationalError: unable to open database file Der Ordner der Datenbank ist nicht beschreibbar. instance/ muss dem cPanel-Benutzer gehören (Rechte 755). GLORY_DB_PATH nicht auf einen fremden Ordner setzen.
database is locked bei viel Last SQLite wartet bis zu 10 s auf Sperren. Bei sehr vielen gleichzeitigen Einreichungen hilft ein kleineres Rate-Limit. Für ein Spiel mit Highscore-Liste reicht SQLite in der Regel.
Seite lädt, Spiel bleibt schwarz, Konsole zeigt 404 für phaser.min.js app/static/js/lib/phaser.min.js wurde nicht mit hochgeladen (manche FTP-Clients überspringen große Dateien). Das Upload-ZIP aus Schritt 1 enthält sie.
Änderungen nicht sichtbar App neu starten (Schritt 6) und Browser-Cache leeren (Strg+F5).
Alle Spieler teilen sich ein Rate-Limit Ein vorgeschalteter Proxy (z. B. CDN) liefert allen dieselbe IP. Wenn der Proxy X-Forwarded-For zuverlässig setzt, GLORY_TRUST_PROXY=1 setzen. Sonst beim Hoster mod_remoteip erfragen.
/healthz ok, aber Highscores werden mit „Run-Token ungültig“ abgelehnt SECRET_KEY wurde zwischen Spielstart und Einreichen geändert (z. B. durch Neustart mit neuem Wert). Einfach eine neue Runde spielen.

Sicherheitshinweise

  • Die App setzt eine strikte Content-Security-Policy. Das Spiel lädt keine Skripte von fremden Servern.
  • IP-Adressen werden nicht gespeichert, nur ein HMAC-Hash (mit SECRET_KEY) für das Rate-Limit. Einträge werden nach Ablauf des Zeitfensters automatisch gelöscht.
  • Spielernamen werden bereinigt (max. 16 Zeichen, keine Steuer- oder Sonderzeichen) und im Frontend immer als Text ausgegeben, nie als HTML. Unpassende Einträge lassen sich löschen oder per Sperrliste verhindern (Abschnitt 9).
  • Mit GLORY_TRUST_PROXY=1 übernimmt die App vom Proxy nur Client-IP und Protokoll, nicht Host oder Pfadpräfix. So lassen sich keine fremden URLs in die Seite schleusen.