🚜 Tretbeckenreinigungs-Team — Administrations-Doku

Diese Doku beschreibt die Pflege und Wartung der Tretbeckenreinigungs-App. Sie ergänzt die anleitung.html im Treckertreff-Paket (die nur die Erst-Installation beschreibt).

ℹ️ Erreichbarkeit: https://tbrt.rusti.ipv64.net
Hosting: VM 122-g50zeiten (192.168.178.122), parallel zur Stundenabrechnung
Datenbank: MariaDB, Datenbank treckertreff_db, User treckertreff_user
Stand: 21. Mai 2026

Architektur — wer macht was?

Die App nutzt dieselbe Infrastruktur wie die Stundenabrechnung. Eine Anfrage durchläuft folgende Stationen:

SchrittKomponenteWas passiert
1 Browser/Smartphone Aufruf von https://tbrt.rusti.ipv64.net
2 DNS (ipv64.net) Löst tbrt.rusti.ipv64.net in deine Heim-IP auf
3 FritzBox Port 80/443 → 192.168.178.131 (NPM)
4 NPM (192.168.178.131) Reverse-Proxy + Let's-Encrypt-SSL → weiter an 192.168.178.122:80
5 Apache (.122) VirtualHost tbrt.rusti.ipv64.net liefert Dateien aus /var/www/treckertreff/
6 PHP / MariaDB api.php spricht mit Datenbank treckertreff_db

Datei-Layout auf .122

Alle App-Dateien liegen in /var/www/treckertreff/ und gehören www-data:www-data:

DateiFunktion
index.htmlLogin-Seite mit Team-Foto
app.htmlHauptansicht (Termin + Mitbringliste)
api.phpBackend: Login, Termin, Kategorien, Einträge
config.phpDB-Zugang, App-Passwort-Hash, Teilnehmer-Liste
setup.sqlTabellen-Struktur (für Neuinstallation)
.htaccessApache-Schutzregeln
team-foto.jpgFoto der Truppe (Hintergrund)
favicon.svgBrowser-Tab-Icon
apple-touch-icon.pngiOS-Homescreen-Icon
icon-192.png / icon-512.pngAndroid-Homescreen-Icons
manifest.jsonPWA-Manifest
anleitung.htmlInstallations-Anleitung (statisch)

Apache-Konfiguration

Die VirtualHost-Datei liegt in:

/etc/apache2/sites-available/treckertreff.conf

Aktiviert mit a2ensite treckertreff.conf. Wichtige Punkte:

Datenbank-Struktur

Die App nutzt eine eigene MariaDB-Datenbank treckertreff_db mit drei Tabellen:

Tabelle termin

Enthält immer genau einen Datensatz (den aktuellen Termin).

SpalteTypBeschreibung
idINT PRIMARY KEYAuto-Inkrement
datumDATETermin-Datum, z.B. 2026-06-16
uhrzeitTIMETermin-Uhrzeit, z.B. 17:00:00
geaendert_amTIMESTAMPWann zuletzt geändert
geaendert_vonVARCHAR(50)Wer hat geändert

Tabelle kategorien

SpalteTypBeschreibung
idINT PRIMARY KEYAuto-Inkrement
nameVARCHAR(100)z.B. "Fleisch", "Brot"
iconVARCHAR(20)Emoji, z.B. 🥩
reihenfolgeINTSortier-Reihenfolge
erstellt_amTIMESTAMP

Tabelle eintraege

SpalteTypBeschreibung
idINT PRIMARY KEYAuto-Inkrement
kategorie_idINT FKVerweis auf kategorien
personVARCHAR(50)z.B. "Steffen"
beschreibungVARCHAR(255)z.B. "Steaks"
mengeVARCHAR(50)z.B. "2 kg" (optional)
erstellt_am / geaendert_amTIMESTAMP

Foreign Key: Wenn eine Kategorie gelöscht wird, werden alle zugehörigen Einträge automatisch mit gelöscht (ON DELETE CASCADE).


App-Passwort ändern

Das Passwort wird als bcrypt-Hash in config.php gespeichert. So wechselst du es:

1 Per SSH einloggen
ssh root@192.168.178.122
2 Neues Passwort wählen und Hash generieren
PASSWORT='NeuesPasswort2026'

HASH=$(curl -s "http://localhost/api.php?aktion=hash_erzeugen&pw=$PASSWORT" \
  -H "Host: tbrt.rusti.ipv64.net" \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['hash'])")

echo "Hash-Länge: ${#HASH}"   # sollte 60 zeigen
3 Hash in config.php einsetzen
sed -i "s|APP_PASSWORT_HASH', '[^']*'|APP_PASSWORT_HASH', '$HASH'|" \
  /var/www/treckertreff/config.php
4 Testen
curl -s -X POST http://localhost/api.php \
  -H "Host: tbrt.rusti.ipv64.net" \
  -d "aktion=login&passwort=$PASSWORT"

# Erwartung: {"erfolg":true}
5 Aufräumen
unset PASSWORT HASH
ℹ️ Wichtig: Wenn jemand bereits in der App eingeloggt ist, bleibt seine Session bestehen — bis sie nach 7 Tagen abläuft. Neu einloggen geht nur mit dem neuen Passwort.

Personen hinzufügen / ändern

Die 6 Teilnehmer sind hart in config.php hinterlegt:

define('TEAM_MITGLIEDER', json_encode([
    'Fin', 'Josef', 'Jürgen', 'Rudolf', 'Steffen', 'Thomas'
]));

Neuen Teilnehmer hinzufügen

nano /var/www/treckertreff/config.php
# Liste anpassen, z.B. 'Werner' hinzufügen
# Ctrl+O, Enter, Ctrl+X

Person umbenennen

⚠️ Wenn jemand die App schon benutzt hat und in eintraege.person steht, ist der alte Name in der Datenbank. Daher:

nano /var/www/treckertreff/config.php   # neue Liste eintragen

# Alte Einträge in der DB auf neuen Namen aktualisieren
mariadb treckertreff_db -e "UPDATE eintraege SET person = 'Werner' WHERE person = 'Wolfgang';"
mariadb treckertreff_db -e "UPDATE termin SET geaendert_von = 'Werner' WHERE geaendert_von = 'Wolfgang';"

Person entfernen

Aus der Liste in config.php löschen + alte Einträge aus der DB entfernen:

mariadb treckertreff_db -e "DELETE FROM eintraege WHERE person = 'Wolfgang';"

Backup erstellen

Die App ist klein — ein Backup besteht aus 2 Sachen:

Datenbank-Backup

mariadb-dump treckertreff_db > /root/treckertreff_$(date +%Y%m%d).sql
ls -la /root/treckertreff_*.sql

Konfigurations-Backup

cp /var/www/treckertreff/config.php /root/config_$(date +%Y%m%d).php.bak
cp /etc/apache2/sites-available/treckertreff.conf /root/treckertreff.conf.bak

Vollständig: Tarball

tar czf /root/treckertreff_full_$(date +%Y%m%d).tar.gz \
  /var/www/treckertreff/ \
  /etc/apache2/sites-available/treckertreff.conf

mariadb-dump treckertreff_db > /root/treckertreff_db_$(date +%Y%m%d).sql

ls -la /root/treckertreff_*
💡 Tipp: Per scp auf den Mac herunterladen:
scp root@192.168.178.122:/root/treckertreff_* ~/Backups/

Backup einspielen

1 Datenbank zurücksetzen und einspielen
mariadb -e "DROP DATABASE IF EXISTS treckertreff_db; \
            CREATE DATABASE treckertreff_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

mariadb treckertreff_db < /root/treckertreff_db_20260521.sql

mariadb treckertreff_db -e "SHOW TABLES;"
2 Dateien zurückspielen (falls nötig)
cd /
tar xzf /root/treckertreff_full_20260521.tar.gz
chown -R www-data:www-data /var/www/treckertreff/
systemctl reload apache2

Alles zurücksetzen (vor neuem Treff)

Wenn ein Treff vorbei ist und du für den nächsten alles leeren willst:

# Backup nicht vergessen!
mariadb-dump treckertreff_db > /root/treckertreff_letzter_treff.sql

# Alle Einträge löschen, Kategorien und Termin bleiben
mariadb treckertreff_db -e "DELETE FROM eintraege;"

# Optional: Termin auf "in 3 Wochen" setzen
mariadb treckertreff_db -e "UPDATE termin SET datum = DATE_ADD(CURDATE(), INTERVAL 21 DAY), \
                            uhrzeit = '18:00:00', geaendert_von = 'System' \
                            WHERE id = 1;"
💡 Alternativ: Du kannst den Termin auch einfach in der App ändern (Termin-Button) und die Einträge zurücklassen — die Personen werden dann ihre Einträge selbst löschen oder anpassen.

Code-Änderungen einspielen

Wenn ich (Claude) dir eine neue Version von app.html, api.php oder einer anderen Datei schicke:

1 Datei auf den Mac herunterladen

Aus dem Chat in den Downloads-Ordner.

2 Mit scp hochladen

Vom Mac:

cd ~/Downloads
scp app.html root@192.168.178.122:/var/www/treckertreff/

Vom Windows-PC (PowerShell):

cd $env:USERPROFILE\Downloads
scp app.html root@192.168.178.122:/var/www/treckertreff/
3 Berechtigungen prüfen
ssh root@192.168.178.122 "ls -la /var/www/treckertreff/app.html"

Wenn die Datei nicht www-data:www-data gehört:

ssh root@192.168.178.122 "chown www-data:www-data /var/www/treckertreff/app.html"
4 Im Browser Hard-Reload

Cmd+Shift+R (Mac) oder Strg+F5 (Windows) — ohne das holst du dir nur die alte Version aus dem Browser-Cache.

SSL-Zertifikat erneuern

Das Zertifikat ist Let's-Encrypt und wird vom NPM auf 192.168.178.131 automatisch alle 60 Tage erneuert. Du musst nichts tun.

Falls doch mal manuell nötig:

  1. NPM-Web-UI öffnen: http://192.168.178.131:81
  2. Hosts → Proxy Hosts → tbrt.rusti.ipv64.net → Edit
  3. SSL-Tab → "Request a new SSL Certificate" → Save
⚠️ Let's-Encrypt-Rate-Limit: Pro Domain dürfen nur 5 Zertifikate pro Woche ausgestellt werden. Wenn du häufig "Request new Certificate" drückst, kannst du in das Limit laufen.

Logs prüfen

Apache-Logs

BefehlZweck
tail -f /var/log/apache2/treckertreff_access.logLive alle Zugriffe
tail -f /var/log/apache2/treckertreff_error.logLive alle Fehler
tail -50 /var/log/apache2/treckertreff_error.logLetzte 50 Fehler
grep -i "fatal\|error" /var/log/apache2/treckertreff_error.log | tail -20Nur Fehler

PHP-Fehler sichtbar machen

Wenn du beim Debuggen PHP-Fehler direkt im Browser sehen willst, in api.php ganz oben einfügen:

ini_set('display_errors', '1');
error_reporting(E_ALL);

⚠️ Nach dem Debuggen wieder entfernen — Produktiv-Setup zeigt keine Fehler!

API direkt testen (ohne Browser)

Sehr nützlich um Fehler einzukreisen — Browser-Cache, JavaScript etc. raus aus der Gleichung.

Login simulieren

curl -c /tmp/cookies.txt -s -X POST http://localhost/api.php \
  -H "Host: tbrt.rusti.ipv64.net" \
  -d "aktion=login&passwort=DEINPASSWORT"
# Erwartung: {"erfolg":true}

Daten laden

curl -b /tmp/cookies.txt -s http://localhost/api.php?aktion=alles_holen \
  -H "Host: tbrt.rusti.ipv64.net" | python3 -m json.tool

Termin ändern

curl -b /tmp/cookies.txt -s -X POST http://localhost/api.php \
  -H "Host: tbrt.rusti.ipv64.net" \
  -d "aktion=termin_speichern&datum=2026-06-16&uhrzeit=17:00&person=Steffen"

Eintrag hinzufügen

curl -b /tmp/cookies.txt -s -X POST http://localhost/api.php \
  -H "Host: tbrt.rusti.ipv64.net" \
  -d "aktion=eintrag_hinzu&kategorie_id=1&person=Steffen&beschreibung=Steaks&menge=2 kg"

Datenbank-Tests

Inhalte ansehen

mariadb treckertreff_db -e "SELECT * FROM termin;"
mariadb treckertreff_db -e "SELECT * FROM kategorien;"
mariadb treckertreff_db -e "SELECT * FROM eintraege;"

Encoding prüfen (HEX-Bytes anschauen)

mariadb treckertreff_db -e "SELECT id, name, HEX(name), HEX(icon) FROM kategorien;"

Was die HEX-Werte bedeuten:

ZeichenUTF-8 (korrekt)Doppelt-UTF-8 (kaputt)
äC3 A4 (2 Bytes)C3 83 C2 A4 (4 Bytes)
öC3 B6C3 83 C2 B6
üC3 BCC3 83 C2 BC
🥩F0 9F A5 A9 (4 Bytes)C3 B0 C5 B8 C2 A5 C2 A9 (8 Bytes)

Bot-Scans erkennen

Sobald die App öffentlich erreichbar ist, kommen automatisierte Scans aus dem Internet. Im Apache-Error-Log siehst du Anfragen wie:

script '/var/www/treckertreff/wp-login.php' not found
script '/var/www/treckertreff/.env.php' not found
client denied: /var/www/treckertreff/server-status

Das sind automatische Bot-Netze, die nach bekannten Sicherheits-Lücken suchen (WordPress-Login, Konfigurationsdateien etc.). Da du diese Software gar nicht installiert hast, finden sie nichts. Es ist kein Angriff auf die App selbst.

Pfad im LogWas der Bot sucht
/wp-login.phpWordPress-Admin
/.env / /.env.phpKonfigurations-Files mit Passwörtern
/info.php / /phpinfo.phpPHP-Info-Seiten
/server-statusApache-Status-Seite
/db_backup.sql / /backup.sqlVersehentlich öffentliche Backups
/.git/configGit-Repos die öffentlich sind

Alle diese Anfragen werden mit 404 Not Found oder 403 Forbidden abgewiesen — kein Anlass zur Sorge.


Emojis / Umlaute kaputt

Wenn Emojis als 🥩 oder Getränke angezeigt werden:

Ursache 1: Apache liefert keinen Charset-Header

curl -sI -H "Host: tbrt.rusti.ipv64.net" http://localhost/ | grep -i content-type
# Sollte zeigen: Content-Type: text/html; charset=UTF-8

Wenn charset=UTF-8 fehlt:

echo "AddDefaultCharset UTF-8" > /etc/apache2/conf-available/charset.conf
a2enconf charset
systemctl restart apache2

Ursache 2: Doppelt-UTF-8 in der Datenbank

Tritt auf wenn beim Import mit mariadb < setup.sql die Datenbank das UTF-8 als latin1 interpretiert.

Diagnose:

mariadb treckertreff_db -e "SELECT name, HEX(name), HEX(icon) FROM kategorien;"

Wenn ä als C383C2A4 erscheint (statt C3A4), ist es doppelt kodiert.

Reparatur:

# Backup zuerst!
mariadb-dump treckertreff_db > /tmp/treckertreff_backup.sql

# name-Spalte reparieren
mariadb treckertreff_db <<'EOF'
UPDATE kategorien
SET name = CONVERT(CAST(CONVERT(name USING latin1) AS BINARY) USING utf8mb4);
EOF

# icon-Spalte reparieren
mariadb treckertreff_db <<'EOF'
UPDATE kategorien
SET icon = CONVERT(CAST(CONVERT(icon USING latin1) AS BINARY) USING utf8mb4);
EOF

# Verifizieren
mariadb treckertreff_db -e "SELECT name, HEX(name), HEX(icon) FROM kategorien;"
ℹ️ Hinweis zum Fehler "Cannot convert utf8mb4 to latin1": Wenn die Konvertierung beim zweiten Mal diesen Fehler zeigt, ist das gut — es bedeutet, die Daten sind schon korrekt und der Schutz verhindert, dass sie wieder kaputt gemacht werden.

Login funktioniert nicht

Häufige Ursachen und Diagnose:

1. Hash-Format prüfen

python3 <<'EOF'
import re
with open('/var/www/treckertreff/config.php', 'r') as f:
    content = f.read()
m = re.search(r"APP_PASSWORT_HASH'\s*,\s*'([^']+)'", content)
if m:
    h = m.group(1)
    print(f"Länge: {len(h)}")
    print(f"Beginnt mit: {h[:7]}")
    print(f"OK" if h.startswith('$2y$') and len(h) == 60 else "FALSCH")
EOF

Erwartung: Länge 60, beginnt mit $2y$10$ oder $2y$12$.

2. Hash und Passwort gehören zusammen?

HASH='HIER_DEINEN_HASH_EINSETZEN'
PASSWORT='HIER_DEIN_PASSWORT'
php -r "echo password_verify('$PASSWORT', '$HASH') ? 'PASST' : 'PASST NICHT';"

3. Brute-Force-Sperre aktiv?

Nach 5 Fehlversuchen wird die IP für 15 Min gesperrt. Wenn du dauernd "Zu viele Fehlversuche" bekommst, einfach 15 Min warten oder die PHP-Session resetten:

# Session-Daten löschen (für ALLE User!)
rm /var/lib/php/sessions/sess_*
# Browser-Cookie löschen oder Inkognito-Tab nutzen

Internal Server Error (500)

Browser zeigt "500 Internal Server Error" → PHP-Fehler. Diagnose:

tail -30 /var/log/apache2/treckertreff_error.log

Häufige Ursachen:

Browser-Cache-Probleme

Symptom: Du hast was geändert, im Browser kommt aber noch die alte Version.

BrowserHard-Reload
Chrome / Edge (Windows)Strg+F5 oder Strg+Shift+R
Chrome / Edge / Firefox (Mac)Cmd+Shift+R
Firefox (Windows)Strg+Shift+R
Safari (Mac)Cmd+Option+E (Cache leeren) dann Cmd+R
Mobiles Safari (iOS)App schließen, Inkognito-Tab öffnen
Mobile Chrome (Android)⋮ → Verlauf → Browserdaten löschen
💡 Profi-Tipp: Privat-Fenster / Inkognito-Modus haben keinen Cache — zum Testen die schnellste Lösung.

Was schützt die App?

SchutzWie
Verschlüsselung HTTPS via Let's Encrypt (NPM), Force-SSL aktiv → kein http erlaubt
Brute-Force 5 Versuche → 15 Min Sperre (in config.php konfigurierbar)
Passwort-Speicherung bcrypt Cost 12 (langsamer Hash, schützt vor Wörterbuchattacken)
Sessions HttpOnly, Secure, SameSite=Lax — schützt vor XSS und CSRF
SQL-Injection Alle DB-Zugriffe nutzen Prepared Statements
config.php Per Apache <FilesMatch> blockiert
Clickjacking HTTP-Header X-Frame-Options: SAMEORIGIN
MIME-Sniffing HTTP-Header X-Content-Type-Options: nosniff
Referrer-Leak HTTP-Header Referrer-Policy: strict-origin-when-cross-origin

Notfall-Wiederherstellung

App ist komplett down

Schritt 1: Services prüfen:

systemctl status apache2 mariadb

Schritt 2: Logs anschauen:

tail -50 /var/log/apache2/treckertreff_error.log
tail -50 /var/log/mysql/error.log

Schritt 3: Apache-Config testen:

apache2ctl configtest

Schritt 4: Services neu starten:

systemctl restart apache2
systemctl restart mariadb

Datenbank kaputt — Backup einspielen

# Aktuelle DB als Notfall-Sicherung
mariadb-dump treckertreff_db > /root/treckertreff_kaputt.sql

# Zurücksetzen und letztes Backup einspielen
mariadb -e "DROP DATABASE treckertreff_db; \
            CREATE DATABASE treckertreff_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mariadb treckertreff_db < /root/treckertreff_db_LETZTES.sql

Alles ist kaputt — VM-Rollback

Im Proxmox-Web-UI:

  1. VM 122-g50zeiten auswählen
  2. Links auf "Backup"
  3. Letztes funktionierendes Backup auswählen
  4. "Zurückspielen" klicken

⚠️ Damit gehen ALLE Änderungen seit dem Backup verloren — auch auf der Stundenabrechnung!


Verwandte Dokus

Treckertreff-Doku Stand: 21.05.2026
https://tbrt.rusti.ipv64.net auf VM 122-g50zeiten