Конфигурация¶
Grom настраивается YAML-файлом. По умолчанию ищет config.yaml в текущем рабочем каталоге. Передайте --config (или -c), чтобы указать другой путь.
- Примеры профилей:
cmd/grom/config-examples/ - Все поля с комментариями:
config.full.yaml
Обязательно: auth.jwt_secret — длинный случайный секрет для подписи JWT access-токенов.
Основные параметры¶
| Область | Что задать |
|---|---|
server.port / server.tls |
Порты прослушивания и режим TLS (off, static или autocert) |
storage.driver / location / temp_dir |
file (по умолчанию; тесты / крошечные инстансы) или bbolt (рекомендуется для обычных установок); корень данных и temp |
storage.bbolt.path |
Необязательный путь к grom.db при bbolt (по умолчанию: {location}/grom.db) |
federation.enabled / federation.domain |
ActivityPub; нужен HTTPS |
auth.reset / mailer |
Email для сброса пароля (public_base_url, SMTP или log-драйвер) |
auth.captcha |
Опциональный ALTCHA PoW на регистрацию/вход/«забыли» (enabled, опционально hmac_secret / cost / expires_seconds) |
logging.level / logging.format |
debug/info/warn/error; text (dev) или json (prod). По умолчанию: info + json. Отладочный вывод Gin ([GIN-debug]) включается только при logging.level: debug; иначе Gin в release mode |
Относительные пути в storage.*, server.tls.cert_file / key_file, server.tls.autocert.cache_dir и federation.ca_cert_file резолвятся относительно каталога бинарника grom (абсолютные пути используются как есть).
Профили TLS¶
| Профиль | Файл конфига | tls.mode |
Федерация |
|---|---|---|---|
| Dev, только HTTP | config.dev.notls.yaml |
off |
выключена |
| Dev, self-signed TLS | config.dev.tls.yaml |
static |
включена |
| Prod, только HTTP | config.prod.notls.yaml |
off |
выключена |
| Prod, Let's Encrypt | config.prod.tls.yaml |
autocert |
включена |
Dev со static TLS — сгенерируйте сертификаты, затем запустите:
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
Для федерации между локальными инстансами задайте federation.tls_insecure_skip_verify: true и при необходимости federation.ca_cert_file, чтобы доверять вашему dev CA.
Production с autocert — нужен публичный DNS-имя в federation.domain (только hostname), порты 80 и 443 доступны из интернета. ACME-сертификаты по умолчанию кэшируются в acme-cache рядом с бинарником grom (переопределение: server.tls.autocert.cache_dir; используйте абсолютный путь, если бинарник лежит в системном каталоге вроде /usr/bin):
Замечания:
- Федерация требует HTTPS (
tls.mode: staticилиautocert). Сtls.mode: offона не работает. - При включённой федерации лайки удалённых тренировок доставляются как ActivityPub
Like/Undo, комментарии — какCreate/DeleteNote (inReplyTo); локальные тренировки принимают входящие лайки и комментарии с других инстансов. Лайки и комментарии на том же инстансе работают без федерации. Удаление аккаунта (DELETE /api/v1/auth/me) доставляетDeleteлокального актора в известные удалённые inbox (best-effort) перед очисткой локальных данных; inbox применяет удалённый actorDelete, очищая федеративный кэш этого владельца для получателя. - Устаревшие конфиги с
server.tls.enabled: true(безmode) трактуются какmode: static.
Драйверы хранилища¶
file подходит для тестов и очень маленьких инстансов (YAML-метаданные на диске удобно смотреть). Для обычной или production-установки предпочтителен bbolt — метаданные в Bolt DB, треки, фото и прочие blob остаются на файловой системе.
| Драйвер | Метаданные | Графики (скорость / пульс) | Лайки / комментарии тренировок | Blob (треки, фото, аватары, ключи) |
|---|---|---|---|---|
file (по умолчанию) |
YAML под storage.location (users.yaml, per-user equipment.yaml, profile.yaml, YAML тренировок, …) |
speed-chart.json и heartrate-chart.json в каталоге каждой тренировки |
likes.yaml и comments.yaml рядом с каждой локальной тренировкой; федеративный кэш лайков/комментариев и ID outbox-activity в дереве federation/ зрителя |
То же дерево |
bbolt |
JSON в {location}/grom.db (или storage.bbolt.path); включая бакет user_profiles для UI-предпочтений |
Упакованные бинарные значения в бакетах bbolt speed_charts / fed_speed_charts и heart_rate_charts / fed_heart_rate_charts (федеративный inbox) |
Бакеты workout_likes, fed_workout_likes, like_activities, workout_comments, fed_workout_comments и comment_activities |
Та же раскладка ФС под storage.location |
postgres зарезервирован в конфиге, но не реализован.
Миграция метаданных между драйверами (сначала остановите сервер; blob треков/медиа/аватаров общие и не копируются). Графики скорости и пульса конвертируются между JSON-blob файла и бинарными бакетами bbolt. Лайки и комментарии тренировок (локальные, федеративный кэш и исходящие activity id) и персональные токены копируются, чтобы оставаться читаемыми после смены драйвера:
Копирует migrate-storage |
Не копирует |
|---|---|
| Пользователи, профили, снаряжение, тренировки | Токены сброса пароля (короткоживущие; незавершённые ссылки сброса становятся недействительными) |
| Подписки, федеративные подписчики, авторы/тренировки федеративного inbox | Blob-файлы (треки, фото, аватары, ключи) — общие на диске |
| Локальные/федеративные лайки и комментарии + исходящие activity id | Временные задания Strava / captcha в памяти |
| Графики скорости/ЧСС (конвертация формата) | |
| Персональные токены доступа (PAT) |
grom migrate-storage --config config.yaml --from file --to bbolt --verify
# затем задайте storage.driver: bbolt и перезапустите
grom migrate-storage --config config.yaml --from bbolt --to file --verify
--dry-run считает записи без записи; --force перезаписывает существующую базу bbolt.
Токены сброса пароля (reset_tokens.yaml / bbolt reset_tokens) короткоживущие и не копируются migrate-storage; незавершённые ссылки сброса после миграции недействительны. Устаревшие plain-text Like activity id (без object_id) восстанавливаются через федеративный inbox и, если задан federation.domain, через URL объектов локальных тренировок.
Mailer и сброс пароля¶
Исходящая почта опциональна. При mailer.driver: off (по умолчанию) сброс пароля выключен, и GET /api/v1/server-info сообщает password_reset_enabled: false.
| Параметр | Назначение |
|---|---|
auth.reset.public_base_url |
Базовый URL в ссылках сброса (без завершающего слэша). Обязателен, когда mailer включён. |
auth.reset.token_ttl_minutes |
Время жизни токена (по умолчанию 60) |
mailer.driver |
off, log (писать в лог сервера — удобно в dev) или smtp |
mailer.from |
Адрес отправителя |
mailer.smtp.host / port |
SMTP-релей (обязателен при driver: smtp). Частые порты: 587 (STARTTLS), 465 (implicit TLS) |
mailer.smtp.username / password |
Необязательные учётные данные SMTP |
mailer.smtp.encryption |
starttls (по умолчанию; также по умолчанию для порта 587), tls (по умолчанию при порте 465) или none |
Нет зависимости от локального MTA / sendmail: процесс говорит SMTP (через go-mail) с внешним провайдером (пароль приложения Gmail, SES, Mailgun и т.д.) или пишет сообщение в лог при driver: log.
Эндпоинты сброса пароля используют in-memory rate limiter с фиксированным окном (15 минут): forgot — 10 запросов на IP клиента и 3 на email; confirm reset — 20 на IP. Лимиты через Gin ClientIP() (учитывает X-Forwarded-For / X-Real-IP, если есть). Grom пока не раскрывает настройку trusted proxies, поэтому считайте forwarded-заголовки недоверенными, пока reverse proxy их не перезаписывает или не снимает.
Пример (production SMTP на порту 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)¶
Опциональная self-hosted ALTCHA proof-of-work captcha (без стороннего сервиса и API-ключей). При auth.captcha.enabled: true:
- Защищены:
POST /api/v1/auth/register,/auth/loginи/auth/password/forgot— в теле запроса должен быть решённый payloadaltcha(base64 JSON). - Не защищены:
POST /api/v1/auth/password/reset(достаточно токена из письма). - Challenge:
GET /api/v1/captcha/challengeвозвращает PoW-challenge (200). При выключенной captcha →404; при лимите challenge для IP →429сRetry-After. - Обнаружение клиентом:
GET /api/v1/server-infoвключаетcaptcha_enabled. Flutter web/Android UI показывает чекбокс I'm not a robot / «Я не робот» и решает challenge локально перед отправкой.
| Параметр | Назначение |
|---|---|
auth.captcha.enabled |
Требовать captcha на регистрацию/вход/«забыли». По умолчанию: false. |
auth.captcha.hmac_secret |
HMAC-ключ для подписей challenge. Необязателен; если пусто, используется auth.jwt_secret. В production предпочтителен отдельный секрет, если JWT ротируете независимо. |
auth.captcha.cost |
Стоимость итераций PBKDF2 для PoW. По умолчанию: 1000. Больше — тяжелее для CPU клиента. |
auth.captcha.expires_seconds |
Время жизни challenge в секундах. По умолчанию: 300. |
Выдача challenge ограничена по rate in-memory (60 запросов на IP клиента за 15-минутное окно). Решённые payload одноразовые до истечения (replay отклоняется). Rate limits и replay-хранилище локальны для процесса — сбрасываются при перезапуске и не разделяются между несколькими процессами Grom; для multi-instance завершайте TLS/rate-limit на одном reverse proxy или держите captcha выключенной, пока нет sticky sessions / общего хранилища.
Разрешение IP клиента как у сброса пароля: Gin ClientIP() (учитывает X-Forwarded-For / X-Real-IP). Без доверенного reverse proxy, который перезаписывает эти заголовки, считайте их недоверенными.
Ошибки проверки возвращают 400 с сообщениями вроде captcha is required, invalid captcha, captcha expired или captcha already used.
Пример:
auth:
jwt_secret: "..."
captcha:
enabled: true
# hmac_secret: "optional-separate-secret"
cost: 1000
expires_seconds: 300
См. также¶
- Установка и запуск
- Массовый импорт Strava (
storage.temp_dir)