176 lines
9.5 KiB
Markdown
176 lines
9.5 KiB
Markdown
# 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):
|
|
|
|
```bash
|
|
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:
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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:
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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.
|