ci / mobile / mobile (Metro-бандл Android) (push) Successful in 55s
ci / ts / ts (packages + web) (push) Successful in 1m0s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 34s
ci / api / api (push) Successful in 26s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 34s
- covers are taken from the embedded picture of the first track or a
cover/folder/front image in the album folder, converted by ffmpeg to
WebP 64/300/1200 and named by content hash, so albums sharing a cover
share the files; a full scan looks again for albums without one
- a 3-colour palette (dominant, second, most saturated accent) from a
deterministic k-means feeds placeholders, page tints and the wave
- /covers/{id} is public but HMAC-signed and immutable; nginx serves
the file via X-Accel-Redirect, X_ACCEL=false serves it from api
- the web client picks the size by display size and pixel density
- install.sh and update.sh generate STREAM_SIGNING_KEY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
71 lines
3.8 KiB
Go
71 lines
3.8 KiB
Go
// Package config читает конфигурацию сервиса из переменных окружения.
|
||
package config
|
||
|
||
import (
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/caarlos0/env/v11"
|
||
)
|
||
|
||
// API — конфигурация публичного сервиса api.
|
||
type API struct {
|
||
HTTPAddr string `env:"HTTP_ADDR" envDefault:":8080"`
|
||
DatabaseURL string `env:"DATABASE_URL,required"`
|
||
RedisURL string `env:"REDIS_URL,required"`
|
||
LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
|
||
MigrateOnStart bool `env:"MIGRATE_ON_START" envDefault:"true"`
|
||
ShutdownTimeout time.Duration `env:"SHUTDOWN_TIMEOUT" envDefault:"15s"`
|
||
|
||
SupportedLocales []string `env:"SUPPORTED_LOCALES" envDefault:"ru,en" envSeparator:","`
|
||
MinAndroidVersionCode int `env:"MIN_ANDROID_VERSION_CODE" envDefault:"0"`
|
||
|
||
// JWTPrivateKey — Ed25519 (base64: 32-байтовый seed или 64-байтовый ключ). Пусто — случайный ключ
|
||
// на время жизни процесса: после перезапуска всем придётся обновить access-токен (refresh остаётся).
|
||
JWTPrivateKey string `env:"JWT_PRIVATE_KEY"`
|
||
// JWTPreviousKeys — прежние ключи через запятую: токены, подписанные ими, ещё принимаются.
|
||
JWTPreviousKeys []string `env:"JWT_PREVIOUS_KEYS" envSeparator:","`
|
||
AccessTokenTTL time.Duration `env:"ACCESS_TOKEN_TTL" envDefault:"15m"`
|
||
// CookieSecure — флаг Secure у refresh-cookie. Отключать только для разработки по http не на localhost.
|
||
CookieSecure bool `env:"COOKIE_SECURE" envDefault:"true"`
|
||
|
||
BootstrapAdminUsername string `env:"BOOTSTRAP_ADMIN_USERNAME" envDefault:"admin"`
|
||
BootstrapAdminPassword string `env:"BOOTSTRAP_ADMIN_PASSWORD"`
|
||
|
||
// LibraryRoot — точки монтирования NFS-хранилищ (общий каталог с storage-agent).
|
||
LibraryRoot string `env:"LIBRARY_ROOT" envDefault:"/library"`
|
||
// LibraryHostRoot — LIBRARY_HOST внутри контейнера: local-хранилища — его подпапки.
|
||
LibraryHostRoot string `env:"LIBRARY_HOST_ROOT" envDefault:"/library-host"`
|
||
StorageAgentURL string `env:"STORAGE_AGENT_URL" envDefault:"http://storage-agent:8090"`
|
||
InternalToken string `env:"INTERNAL_TOKEN"`
|
||
FFprobePath string `env:"FFPROBE_PATH" envDefault:"ffprobe"`
|
||
|
||
// SigningKey — ключ подписи ссылок на обложки и стрим (base64, ≥ 16 байт). Пусто — случайный до перезапуска.
|
||
SigningKey string `env:"STREAM_SIGNING_KEY"`
|
||
CacheDir string `env:"CACHE_DIR" envDefault:"/cache"`
|
||
FFmpegPath string `env:"FFMPEG_PATH" envDefault:"ffmpeg"`
|
||
// XAccel — файлы отдаёт nginx по X-Accel-Redirect; false — для запуска api без nginx.
|
||
XAccel bool `env:"X_ACCEL" envDefault:"true"`
|
||
|
||
MeiliURL string `env:"MEILI_URL" envDefault:"http://meilisearch:7700"`
|
||
MeiliMasterKey string `env:"MEILI_MASTER_KEY"`
|
||
}
|
||
|
||
// StorageAgent — конфигурация внутреннего сервиса storage-agent.
|
||
type StorageAgent struct {
|
||
HTTPAddr string `env:"HTTP_ADDR" envDefault:":8090"`
|
||
InternalToken string `env:"INTERNAL_TOKEN,required,notEmpty"`
|
||
LibraryRoot string `env:"LIBRARY_ROOT" envDefault:"/library"`
|
||
LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
|
||
ShutdownTimeout time.Duration `env:"SHUTDOWN_TIMEOUT" envDefault:"15s"`
|
||
}
|
||
|
||
// Load заполняет структуру конфигурации T из окружения.
|
||
func Load[T any]() (T, error) {
|
||
var cfg T
|
||
if err := env.Parse(&cfg); err != nil {
|
||
return cfg, fmt.Errorf("config: %w", err)
|
||
}
|
||
return cfg, nil
|
||
}
|