Konfiguration¶
Grom wird über eine YAML-Datei konfiguriert. Standardmäßig sucht er config.yaml im aktuellen Arbeitsverzeichnis. Mit --config (oder -c) wählen Sie einen anderen Pfad.
- Beispielprofile:
cmd/grom/config-examples/ - Alle Felder mit Kommentaren:
config.full.yaml
Erforderlich: auth.jwt_secret — ein langes Zufallsgeheimnis zum Signieren von JWT-Access-Token.
Wichtige Einstellungen¶
| Bereich | Was setzen |
|---|---|
server.port / server.tls |
Listen-Ports und TLS-Modus (off, static oder autocert) |
storage.driver / location / temp_dir |
file (Standard; Tests / winzige Instanzen) oder bbolt (empfohlen für normale Installationen); Datenwurzel und Temp-Verzeichnisse |
storage.bbolt.path |
Optionaler Pfad zu grom.db bei bbolt (Standard: {location}/grom.db) |
federation.enabled / federation.domain |
ActivityPub; erfordert HTTPS |
auth.reset / mailer |
Passwort-Reset per E-Mail (public_base_url, SMTP oder Log-Treiber) |
auth.captcha |
Optionales ALTCHA-PoW bei Registrierung/Login/„vergessen“ (enabled, optional hmac_secret / cost / expires_seconds) |
logging.level / logging.format |
debug/info/warn/error; text (Dev) oder json (Prod). Standard: info + json. Gin-Framework-Debug ([GIN-debug]) nur bei logging.level: debug; sonst Gin im Release-Modus |
Relative Pfade in storage.*, server.tls.cert_file / key_file, server.tls.autocert.cache_dir und federation.ca_cert_file werden relativ zum Verzeichnis der grom-Binary aufgelöst (absolute Pfade bleiben unverändert).
TLS-Profile¶
| Profil | Config-Datei | tls.mode |
Föderation |
|---|---|---|---|
| Dev, nur HTTP | config.dev.notls.yaml |
off |
deaktiviert |
| Dev, self-signed TLS | config.dev.tls.yaml |
static |
aktiviert |
| Prod, nur HTTP | config.prod.notls.yaml |
off |
deaktiviert |
| Prod, Let's Encrypt | config.prod.tls.yaml |
autocert |
aktiviert |
Dev mit static TLS — Zertifikate erzeugen, dann starten:
cd cmd/grom
go run . gencerts --ip 192.168.1.251 --domain 192.168.1.251
go run . --config config-examples/config.dev.tls.yaml
Für Föderation zwischen lokalen Instanzen setzen Sie federation.tls_insecure_skip_verify: true und optional federation.ca_cert_file, um Ihrer Dev-CA zu vertrauen.
Production mit autocert — öffentlicher DNS-Name in federation.domain (nur Hostname), Ports 80 und 443 aus dem Internet erreichbar. ACME-Zertifikate werden standardmäßig unter acme-cache neben der grom-Binary gecacht (Override: server.tls.autocert.cache_dir; absoluten Pfad nutzen, wenn die Binary unter einem Systemverzeichnis wie /usr/bin liegt):
Hinweise:
- Föderation erfordert HTTPS (
tls.mode: staticoderautocert). Mittls.mode: offläuft sie nicht. - Bei aktivierter Föderation werden Likes auf Remote-Workouts als ActivityPub
Like/Undozugestellt, Kommentare alsCreate/DeleteNote (inReplyTo); lokale Workouts akzeptieren eingehende Likes und Kommentare von anderen Instanzen. Likes und Kommentare auf derselben Instanz funktionieren ohne Föderation. Kontolöschung (DELETE /api/v1/auth/me) liefert einDeletedes lokalen Actors an bekannte Remote-Inboxes (best-effort), bevor lokale Daten gelöscht werden; die Inbox wendet Remote-Actor-Deletean, indem der föderierte Cache dieses Besitzers für den Empfänger bereinigt wird. - Legacy-Configs mit
server.tls.enabled: true(ohnemode) gelten alsmode: static.
Speicher-Treiber¶
file eignet sich für Tests und sehr kleine Instanzen (YAML-Metadaten auf der Platte sind leicht einsehbar). Für normale oder Production-Installationen bevorzugen Sie bbolt — Metadaten in einer Bolt-DB, Tracks, Fotos und andere Blobs bleiben auf dem Dateisystem.
| Treiber | Metadaten | Diagramme (Geschwindigkeit / Herzfrequenz) | Workout-Likes / -Kommentare | Blobs (Tracks, Fotos, Avatare, Schlüssel) |
|---|---|---|---|---|
file (Standard) |
YAML unter storage.location (users.yaml, pro Nutzer equipment.yaml, profile.yaml, Workout-YAML, …) |
speed-chart.json und heartrate-chart.json im jeweiligen Workout-Verzeichnis |
likes.yaml und comments.yaml neben jedem lokalen Workout; föderierter Like-/Kommentar-Cache und Outbox-Activity-IDs unter dem federation/-Baum des Betrachters |
Dieselbe Baumstruktur |
bbolt |
JSON in {location}/grom.db (oder storage.bbolt.path); inkl. Bucket user_profiles für UI-Präferenzen |
Gepackte Binärwerte in bbolt-Buckets speed_charts / fed_speed_charts und heart_rate_charts / fed_heart_rate_charts (föderierte Inbox) |
Buckets workout_likes, fed_workout_likes, like_activities, workout_comments, fed_workout_comments und comment_activities |
Dasselbe Dateisystem-Layout unter storage.location |
postgres ist in der Config reserviert, aber nicht implementiert.
Metadaten zwischen Treibern migrieren (Server zuerst stoppen; Track-/Medien-/Avatar-Blobs sind gemeinsam und werden nicht kopiert). Geschwindigkeits- und Herzfrequenz-Diagramme werden zwischen Datei-JSON-Blobs und bbolt-Binärbuckets konvertiert. Workout-Likes und -Kommentare (lokal, föderierter Cache und ausgehende Activity-IDs) sowie persönliche Zugriffstoken werden kopiert, damit sie nach dem Treiberwechsel lesbar bleiben:
Kopiert von migrate-storage |
Nicht kopiert |
|---|---|
| Nutzer, Profile, Ausrüstung, Workouts | Passwort-Reset-Token (kurzlebig; laufende Reset-Links werden ungültig) |
| Follows, Föderations-Follower, föderierte Inbox-Autoren/Workouts | Blob-Dateien (Tracks, Fotos, Avatare, Schlüssel) — gemeinsam auf der Platte |
| Lokale/föderierte Likes und Kommentare + ausgehende Activity-IDs | Temporäre Strava-Jobs / Captcha-Zustand im Speicher |
| Speed-/HR-Diagramme (Formatkonvertierung) | |
| Persönliche Zugriffstoken (PAT) |
grom migrate-storage --config config.yaml --from file --to bbolt --verify
# dann storage.driver: bbolt setzen und neu starten
grom migrate-storage --config config.yaml --from bbolt --to file --verify
--dry-run zählt Datensätze ohne Schreiben; --force überschreibt eine vorhandene bbolt-Datenbank.
Passwort-Reset-Token (reset_tokens.yaml / bbolt reset_tokens) sind kurzlebig und werden von migrate-storage nicht kopiert; laufende Reset-Links werden nach einer Migration ungültig. Legacy-Plaintext-Like-Activity-IDs (ohne object_id) werden über die föderierte Inbox und, wenn federation.domain gesetzt ist, über lokale Workout-Objekt-URLs rekonstruiert.
Mailer und Passwort-Reset¶
Ausgehende E-Mail ist optional. Bei mailer.driver: off (Standard) ist Passwort-Reset deaktiviert und GET /api/v1/server-info meldet password_reset_enabled: false.
| Einstellung | Zweck |
|---|---|
auth.reset.public_base_url |
Basis-URL in Reset-Links (ohne trailing slash). Erforderlich, wenn der Mailer an ist. |
auth.reset.token_ttl_minutes |
Token-Lebensdauer (Standard 60) |
mailer.driver |
off, log (in den Server-Log schreiben — nützlich in Dev) oder smtp |
mailer.from |
Absenderadresse |
mailer.smtp.host / port |
SMTP-Relay (erforderlich bei driver: smtp). Übliche Ports: 587 (STARTTLS), 465 (implizites TLS) |
mailer.smtp.username / password |
Optionale SMTP-Credentials |
mailer.smtp.encryption |
starttls (Standard; auch Standard für Port 587), tls (Standard bei Port 465) oder none |
Es gibt keine Abhängigkeit von lokalem MTA / sendmail: der Prozess spricht SMTP (über go-mail) mit einem externen Anbieter (Gmail-App-Passwort, SES, Mailgun usw.) oder loggt die Nachricht bei driver: log.
Passwort-Reset-Endpunkte nutzen einen In-Memory-Rate-Limiter mit festem Fenster (15 Minuten): forgot — 10 Anfragen pro Client-IP und 3 pro E-Mail; Confirm-Reset — 20 pro Client-IP. Limits nutzen Gins ClientIP() (berücksichtigt X-Forwarded-For / X-Real-IP, falls vorhanden). Grom exponiert noch keine Trusted-Proxies-Einstellung; behandeln Sie Forwarded-Header als untrusted, solange Ihr Reverse Proxy sie nicht überschreibt oder entfernt.
Beispiel (Production-SMTP auf Port 587):
auth:
jwt_secret: "..."
reset:
public_base_url: "https://grom.example.com"
mailer:
driver: smtp
from: "Grom <noreply@grom.example.com>"
smtp:
host: smtp.example.com
port: 587
username: "apikey"
password: "secret"
encryption: starttls
Auth-Captcha (ALTCHA)¶
Optionales self-hosted ALTCHA-Proof-of-Work-Captcha (kein Drittanbieterdienst, keine API-Keys). Bei auth.captcha.enabled: true:
- Geschützt:
POST /api/v1/auth/register,/auth/loginund/auth/password/forgot— Request-Body muss ein gelöstesaltcha-Payload enthalten (base64-JSON). - Nicht geschützt:
POST /api/v1/auth/password/reset(Token aus dem E-Mail-Link reicht). - Challenge:
GET /api/v1/captcha/challengeliefert eine PoW-Challenge (200). Bei ausgeschaltetem Captcha →404; bei Challenge-Rate-Limit für die IP →429mitRetry-After. - Client-Erkennung:
GET /api/v1/server-infoenthältcaptcha_enabled. Die Flutter-Web-/Android-UI zeigt eine Checkbox I'm not a robot / „Ich bin kein Roboter“ und löst die Challenge lokal vor dem Absenden.
| Einstellung | Zweck |
|---|---|
auth.captcha.enabled |
Captcha bei Registrierung/Login/„vergessen“ verlangen. Standard: false. |
auth.captcha.hmac_secret |
HMAC-Schlüssel für Challenge-Signaturen. Optional; wenn leer, wird auth.jwt_secret genutzt. In Production lieber ein separates Geheimnis, wenn Sie JWT unabhängig rotieren. |
auth.captcha.cost |
PBKDF2-Iterationskosten für PoW. Standard: 1000. Höhere Werte belasten die Client-CPU stärker. |
auth.captcha.expires_seconds |
Challenge-Lebensdauer in Sekunden. Standard: 300. |
Challenge-Ausgabe ist In-Memory rate-limited (60 Anfragen pro Client-IP pro 15-Minuten-Fenster). Gelöste Payloads sind einmalig bis zum Ablauf (Replay abgelehnt). Rate Limits und Replay-Store sind prozesslokal — sie resetten beim Neustart und werden nicht über mehrere Grom-Prozesse geteilt; bei Multi-Instance TLS/Rate-Limit an einem Reverse Proxy terminieren oder Captcha aus lassen, bis Sticky Sessions / gemeinsamer Store existieren.
Client-IP-Auflösung wie beim Passwort-Reset: Gins ClientIP() (berücksichtigt X-Forwarded-For / X-Real-IP). Ohne vertrauenswürdigen Reverse Proxy, der diese Header überschreibt, behandeln Sie sie als untrusted.
Verifikationsfehler liefern 400 mit Meldungen wie captcha is required, invalid captcha, captcha expired oder captcha already used.
Beispiel:
auth:
jwt_secret: "..."
captcha:
enabled: true
# hmac_secret: "optional-separate-secret"
cost: 1000
expires_seconds: 300
Siehe auch¶
- Installation und Start
- Strava-Massenimport (
storage.temp_dir)