Übersicht
meyton-sync synchronisiert Schießergebnisse aus der lokalen Datenbank der Schießanlage kontinuierlich auf einen externen Webserver. Benutzer und Applikationen können die Ergebnisse dort über eine REST-API abrufen.
SSMDB2 (MySQL)
FastAPI + MariaDB
Admin-UI
Das System besteht aus zwei unabhängigen Komponenten:
-
Source – läuft auf einem Rechner im Netz der Schießanlage,
liest Änderungen aus der Datenbank
SSMDB2und sendet sie gebündelt per HTTPS an den Zielserver. -
Target – läuft auf dem externen Webserver, empfängt die
Datenpakete, speichert sie in der Replikationsdatenbank
SSMDB2cund stellt sie über eine REST-API sowie eine Admin-Oberfläche bereit.
Datenbankstruktur
| Tabelle | Inhalt |
|---|---|
Scheiben | Treffer-Scheiben (Schütze, Disziplin, Gesamtergebnis, …) |
Serien | Serien je Scheibe (Stellung, Ringergebnis) |
Treffer | Einzeltreffer mit X/Y-Koordinaten, Teiler, Qualität |
Users | API-Benutzer mit Rollen und Passwort-Hashes |
Voraussetzungen
Source (Schießanlage)
| Anforderung | Details |
|---|---|
| Betriebssystem | openSUSE 15.2+, SLES, Debian oder Ubuntu |
| Python | Python 3 |
| Python-Pakete | PyMySQL, requests, apscheduler |
| Datenbank | Lesezugriff auf SSMDB2 (MySQL/MariaDB) |
| Netzwerk | Ausgehende HTTPS-Verbindung (Port 443) zum Zielserver |
Target (Webserver)
| Anforderung | Details |
|---|---|
| Betriebssystem | Debian oder Ubuntu (Pflicht) |
| Python | Python 3 |
| Python-Pakete | FastAPI, Uvicorn, python-multipart, passlib, bcrypt, PyJWT, PyMySQL |
| Webserver | Apache 2 mit mod_proxy und mod_proxy_http |
| Datenbank | MariaDB/MySQL lokal (wird automatisch eingerichtet) |
| Netzwerk | HTTPS-Erreichbarkeit; Uvicorn läuft auf 127.0.0.1:8000 |
Installation
Source installieren
- Installationsskript auf dem Quell-Rechner ausführen:
bash install-source.sh - Das Skript fragt interaktiv nach den Datenbankzugangsdaten, der Ziel-URL sowie den Synchronisierungstoken.
- Anschließend wird der Systemd-Dienst
meyton-sync-sourceangelegt und gestartet. - Konfigurationsdatei prüfen:
/opt/meyton-sync/source/source.conf
source.conf – Konfigurationsparameter
[database]
host = 192.168.10.200 # IP der SSMDB2-Datenbank
port = 3306
user = meyton
password = <Passwort>
database = SSMDB2
[sync]
target_url = https://www.mydomain.de/meyton/sync
own_token = <32-Byte-Hex> # Token dieser Source
target_token = <32-Byte-Hex> # Erwarteter Token des Targets
interval = 60 # Sekunden zwischen Sync-Läufen (1–600)
stands = 15 # Anzahl Schießstände
batch_size = 100 # Scheiben pro Batch
request_timeout = 30
[logging]
level = INFO
file = source.log
Target installieren
- Installationsskript auf dem Webserver ausführen (Root-Rechte erforderlich):
sudo bash install-target.sh - Das Skript richtet Datenbank, Benutzer und Apache-Proxy automatisch ein.
- Systemd-Dienst
meyton-sync-targetwird gestartet; die API ist anschließend unter/meyton/erreichbar. - Konfigurationsdatei prüfen:
/opt/meyton-sync/target/target.conf
target.conf – Konfigurationsparameter
[database]
host = localhost
port = 3306
user = meyton
password = <Passwort>
database = SSMDB2c
[sync]
own_token = <32-Byte-Hex> # Token dieses Targets
source_token = <32-Byte-Hex> # Erwarteter Token der Source
[api]
secret_key = <64-Byte-Hex> # JWT-Signing-Key
token_expire_minutes = 60
[logging]
level = INFO
file = target.log
Apache-Reverse-Proxy
Das Installationsskript fügt in die aktive Apache-Konfiguration ein:
ProxyPass /meyton/ http://127.0.0.1:8000/
ProxyPassReverse /meyton/ http://127.0.0.1:8000/
Admin-Passwort zurücksetzen
Falls das Admin-Passwort verloren geht, kann es mit folgendem Hilfsprogramm zurückgesetzt werden:
cd /opt/meyton-sync/target
python3 set_admin_password.py
erhkm-673BX#a.
Es muss direkt nach der Installation in der Admin-Oberfläche geändert werden.
Dienste verwalten
# Status prüfen
systemctl status meyton-sync-source
systemctl status meyton-sync-target
# Starten / Stoppen / Neustarten
systemctl start meyton-sync-target
systemctl stop meyton-sync-target
systemctl restart meyton-sync-target
# Log-Ausgabe
journalctl -u meyton-sync-target -f
Funktionsweise Sync
Ablauf auf Source-Seite
Der Source-Prozess läuft als systemd-Dienst und führt in regelmäßigen Abständen (Standard: 60 s) einen Sync-Lauf durch:
- Zustandsprüfung: Beim ersten Start (keine
source.state-Datei) oder wenn der Target 0 Scheiben meldet, wird ein vollständiger Sync durchgeführt. - Inkrementeller Sync: Danach werden nur Datensätze
übertragen, deren
Zeitstempelnach dem letzten erfolgreichen Sync liegt. - Batching: Scheiben werden in Paketen (Standard: 100) zusammengefasst. Zu jeder Scheibe werden die zugehörigen Serien und Treffer mitgesendet.
- Komprimierung: Jedes Paket wird per gzip komprimiert und per HTTP-POST an den Target gesendet.
- Zustandsspeicherung: Nach erfolgreichem Versand wird
der Zeitstempel in
source.stateaktualisiert.
Ablauf auf Target-Seite
- Eingehende Anfragen werden anhand des Bearer-Tokens authentifiziert.
- Die empfangenen Scheiben werden per UPSERT in die Datenbank geschrieben
(
INSERT … ON DUPLICATE KEY UPDATE). - Zugehörige Serien und Treffer werden gelöscht und neu eingefügt, um Inkonsistenzen zu vermeiden.
- Anzahl und Typ (vollständig / inkrementell) werden protokolliert.
Token-Authentifizierung
Source und Target tauschen bei jedem Sync-Aufruf zwei Pre-Shared Tokens aus:
- own_token (Source) / source_token (Target): Identifiziert die Source.
- target_token (Source) / own_token (Target): Bestätigt den echten Target-Server.
Beide Token müssen auf beiden Seiten übereinstimmen, sonst wird der Sync abgelehnt.
Kompatibilität älterer Datenbanken
StandNrText und Serie noch nicht.
meyton-sync erkennt dies automatisch und passt die Abfragen entsprechend an.
REST-API
Alle API-Endpunkte sind unter https://<server>/meyton/api/
erreichbar. Eine interaktive Swagger-Dokumentation steht unter
/meyton/docs zur Verfügung.
Authentifizierung
Die API verwendet JWT-Bearer-Token. Token werden über den Login-Endpunkt bezogen und sind standardmäßig 60 Minuten gültig.
# Login (gibt JWT-Token zurück)
POST /meyton/api/token
Content-Type: application/x-www-form-urlencoded
username=<login>&password=<passwort>
Den Token anschließend im Authorization-Header mitsenden:
Authorization: Bearer <token>
Scheiben-Endpunkte
| Methode | Pfad | Beschreibung | Mindestrolle |
|---|---|---|---|
| GET | /api/scheiben/today |
Alle Scheiben des heutigen Tages | user |
| GET | /api/scheiben/last-per-stand |
Letzte Scheibe je Stand | user |
| GET | /api/scheiben/sportpass/{id}?limit=20 |
Scheiben eines Schützen nach Sportpass-ID | restricted / singe * |
| GET | /api/scheiben/shooter?nachname=X&vorname=Y&limit=20 |
Scheiben nach Name des Schützen | restricted / singe * |
| GET | /api/scheibe/{scheiben_id} |
Vollständige Scheibe mit Serien und Einzeltreffern | restricted / singe * |
* singe-Benutzer erhalten nur Ergebnisse, die zu ihrem
eigenen Profil gehören – alle anderen Anfragen werden mit 403 abgelehnt.
Weitere Details siehe Singe-Rolle.
Benutzerverwaltung (nur Admin)
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /api/users |
Alle Benutzer auflisten (inkl. Nachname, Vorname, SportpassID) |
| POST | /api/users |
Neuen Benutzer anlegen (Rollen: user, restricted oder singe; bei singe sind Nachname und Vorname Pflicht) |
| DELETE | /api/users/{login} |
Benutzer löschen (Admin-Konto kann nicht gelöscht werden) |
| PUT | /api/users/{login}/password |
Passwort eines Benutzers ändern |
| PUT | /api/users/{login}/role |
Rolle eines Benutzers ändern; bei Wechsel auf singe müssen Nachname und Vorname mitübergeben werden |
| PUT | /api/admin/password |
Admin-Passwort ändern |
Datenbankoperationen (nur Admin)
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /api/db/disziplinen |
Alle Disziplinen auflisten |
| GET | /api/db/starterlisten |
Alle Starterlisten auflisten |
| DELETE | /api/db/clearErgebnisse |
Alle Ergebnisse löschen |
| DELETE | /api/db/clearErgebnisseDisziplin?disziplin=X |
Ergebnisse einer Disziplin löschen |
| DELETE | /api/db/clearErgebnisseStarterliste?starterliste=X |
Ergebnisse einer Starterliste löschen |
Sync-Endpunkte (intern)
Diese Endpunkte werden ausschließlich von der Source-Komponente verwendet und sind nicht in der Swagger-Dokumentation sichtbar.
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /sync/status |
Liefert Token und aktuelle Scheiben-Anzahl zurück |
| POST | /sync |
Empfängt komprimiertes Batch-Paket mit Scheiben-Daten |
Passwort-Anforderungen
- Mindestens 10 Zeichen
- Muss sowohl Buchstaben als auch Ziffern enthalten
Rollen & Berechtigungen
Jeder Benutzer hat genau eine der vier Rollen. Die Rolle wird beim Login in den JWT-Token eingebettet und bei jeder Anfrage geprüft.
| Rolle | Beschreibung | Zugang |
|---|---|---|
admin |
Systemadministrator | Vollzugriff auf alle Endpunkte inkl. Benutzerverwaltung und Datenbank-Operationen |
user |
Normaler Benutzer | Lesezugriff auf alle Scheiben-Endpunkte (today, last-per-stand, Suche nach Name/SportpassID, Einzelscheibe) |
restricted |
Eingeschränkter Benutzer | Nur Schützen-bezogene Suchen (sportpass, shooter, Einzelscheibe) – kein Zugriff auf Tages- oder Standübersichten; keine Filterung nach Eigentümer |
singe |
Einzelschütze (persönlich) | Wie restricted, aber alle Suchergebnisse werden auf das eigene Profil (Nachname, Vorname, SportpassID) gefiltert. Fremde Ergebnisse werden mit 403 abgelehnt. |
Berechtigungsmatrix
| Endpunkt | admin | user | restricted | singe |
|---|---|---|---|---|
scheiben/today |
✓ | ✓ | ✗ | ✗ |
scheiben/last-per-stand |
✓ | ✓ | ✗ | ✗ |
scheiben/sportpass/{id} |
✓ | ✓ | ✓ | nur eigene |
scheiben/shooter |
✓ | ✓ | ✓ | nur eigene |
scheibe/{id} |
✓ | ✓ | ✓ | nur eigene |
| Benutzerverwaltung | ✓ | ✗ | ✗ | ✗ |
| Datenbankoperationen | ✓ | ✗ | ✗ | ✗ |
Statistik (stats) |
✓ | ✗ | ✗ | ✗ |
Singe-Rolle
Die Rolle singe ist für Einzelschützen gedacht, die ausschließlich
ihre eigenen Ergebnisse abrufen dürfen. Sie hat dieselben Zugriffsrechte wie
restricted, mit dem Unterschied, dass alle zurückgegebenen Ergebnisse
serverseitig auf das Profil des eingeloggten Benutzers gefiltert werden.
Profilfelder
Jeder singe-Benutzer hat in der Datenbank drei zusätzliche Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
Nachname | Text (max. 50 Zeichen) | Nachname des Schützen, wie er in den Scheiben-Daten steht (Pflicht) |
Vorname | Text (max. 50 Zeichen) | Vorname des Schützen (Pflicht) |
SportpassID | Ganzzahl (bigint) | Sportpass-Nummer des Schützen (optional, 0 = nicht gesetzt) |
Users-Tabelle für alle
Benutzerrollen; bei admin, user und restricted
bleiben sie leer und haben keine Auswirkung auf die API-Filterung.
Filterlogik
Die Profilfelder werden beim Login in den JWT-Token eingebettet und bei jeder Anfrage serverseitig ausgewertet:
GET /api/scheiben/sportpass/{sportpass_id}
Der angefragte Wert sportpass_id muss exakt mit der im Profil
hinterlegten SportpassID übereinstimmen. Jede andere ID wird
mit 403 Keine Berechtigung für diese SportpassID abgewiesen.
GET /api/scheiben/shooter?nachname=…&vorname=…
Nachname und Vorname aus der Anfrage müssen (Groß-/Kleinschreibung wird
ignoriert) mit den Profilfeldern übereinstimmen. Abweichende Namen werden
mit 403 Keine Berechtigung für diesen Schützen abgewiesen.
GET /api/scheibe/{scheiben_id}
Der Server prüft, ob die Scheibe dem Benutzer gehört. Die Prüfung erfolgt in dieser Reihenfolge:
- Wenn
SportpassID > 0: Scheibe muss dieselbe SportpassID haben. - Sonst: Nachname und Vorname der Scheibe müssen mit dem Profil übereinstimmen.
Gehört die Scheibe nicht zum Benutzer, wird 403 Keine Berechtigung für diese Scheibe zurückgegeben.
Benutzer anlegen
Beim Anlegen über POST /api/users müssen für die Rolle
singe mindestens nachname und vorname
angegeben werden. Die sportpass_id ist optional (Standard: 0).
POST /meyton/api/users
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"login": "mueller.hans",
"password": "Passwort123!",
"role": "singe",
"nachname": "Mueller",
"vorname": "Hans",
"sportpass_id": 1234567
}
Rolle nachträglich ändern
Beim Wechsel auf singe über PUT /api/users/{login}/role
müssen Nachname und Vorname ebenfalls mitübergeben werden:
PUT /meyton/api/users/mueller.hans/role
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"role": "singe",
"nachname": "Mueller",
"vorname": "Hans",
"sportpass_id": 1234567
}
Nachname, Vorname
und SportpassID immer überschrieben – auch wenn die neue Rolle
nicht singe ist. Bei einem Wechsel zurück auf user
oder restricted können die Felder leer gelassen werden; sie
haben dann keinen Einfluss auf das Verhalten der API.
Statistik-API
Der Endpunkt GET /api/stats liefert eine Übersicht über den
aktuellen Datenbankinhalt sowie den Synchronisierungsstatus. Er ist
ausschließlich für die Rolle admin zugänglich.
GET /meyton/api/stats
Authorization: Bearer <admin-token>
Antwortfelder
| Feld | Typ | Beschreibung |
|---|---|---|
scheiben |
Ganzzahl | Gesamtanzahl der gespeicherten Scheiben |
serien |
Ganzzahl | Gesamtanzahl der Serien-Einträge |
treffer |
Ganzzahl | Gesamtanzahl der Einzeltreffer |
scheiben_pro_disziplin |
Liste | Anzahl Scheiben je Disziplin (disziplin + count), absteigend sortiert |
letzter_verbindungsaufbau |
ISO-8601-Zeitstempel / null | Zeitpunkt des letzten GET /sync/status-Aufrufs durch die Source-Komponente |
letzte_synchronisierung |
ISO-8601-Zeitstempel / null | Zeitpunkt des letzten erfolgreich empfangenen Sync-Pakets |
letzte_scheibe |
ISO-8601-Zeitstempel / null | Zeitstempel der jüngsten Scheibe in der Datenbank |
Beispielantwort
{
"scheiben": 4821,
"serien": 28926,
"treffer": 144630,
"scheiben_pro_disziplin": [
{ "disziplin": "LG", "count": 2410 },
{ "disziplin": "LP", "count": 1837 },
{ "disziplin": "KK", "count": 574 }
],
"letzter_verbindungsaufbau": "2026-05-10T08:15:02+00:00",
"letzte_synchronisierung": "2026-05-10T08:15:05+00:00",
"letzte_scheibe": "2026-05-10T08:14:51"
}
letzter_verbindungsaufbau und
letzte_synchronisierung erlauben eine einfache Überwachung
des Sync-Prozesses. Liegt letzter_verbindungsaufbau mehr als
zwei Sync-Intervalle (Standard: 2 × 60 s) in der Vergangenheit, ist die
Verbindung zur Schießanlage unterbrochen. letzte_synchronisierung
kann älter sein, wenn in der Zwischenzeit keine neuen Scheiben erfasst wurden.
Admin-Oberfläche
Die Admin-Oberfläche ist unter https://<server>/meyton/admin
erreichbar und steht ausschließlich Benutzern mit der Rolle admin
zur Verfügung.
Login
Nach dem Aufrufen der Seite erscheint ein Login-Formular. Nach erfolgreicher Anmeldung wird ein JWT-Token im Browser gespeichert und alle weiteren Aktionen laufen im Hintergrund über die REST-API.
Tab: Benutzerverwaltung
- Alle vorhandenen Benutzer mit Rolle, Nachname, Vorname und SportpassID anzeigen
- Neuen Benutzer anlegen (Rollen:
user,restrictedodersinge) - Bei Rolle
singe: Nachname und Vorname sind Pflichtfelder; SportpassID ist optional - Passwort eines bestehenden Benutzers ändern
- Rolle eines Benutzers ändern (außer Admin); beim Wechsel auf
singemüssen Nachname und Vorname angegeben werden - Benutzer löschen (außer Admin)
PUT /api/admin/password oder über das Hilfsskript
set_admin_password.py zurückgesetzt werden.
Tab: Statistiken
- Gesamtanzahl von Scheiben, Serien und Einzeltreffern
- Aufschlüsselung nach Disziplin mit prozentualem Balkendiagramm
- Zeitpunkt des letzten Verbindungsaufbaus durch die Source-Komponente
- Zeitpunkt der letzten erfolgreichen Synchronisierung
- Zeitstempel der jüngsten Scheibe in der Datenbank
- Schaltfläche zum manuellen Aktualisieren der Anzeige
Tab: Datenbank
- Alle Ergebnisse löschen: Löscht sämtliche Scheiben, Serien und Treffer (Sicherheitsabfrage erforderlich)
- Nach Disziplin löschen: Ergebnisse einer bestimmten Disziplin entfernen (Auswahl über Dropdown)
- Nach Starterliste löschen: Ergebnisse einer Starterliste entfernen (Auswahl über Dropdown)
source.state
auf dem Source-Rechner).
Vollständigen Sync erzwingen
Wenn nach einem Löschvorgang alle Daten erneut eingespielt werden sollen:
# Auf dem Source-Rechner:
sudo systemctl stop meyton-sync-source
sudo rm /opt/meyton-sync/source/source.state
sudo systemctl start meyton-sync-source
Beim nächsten Start erkennt der Source-Dienst das Fehlen der Statusdatei und überträgt alle Datensätze vollständig.