- Migration 00008: upload_batches, upload_files, upload_quota. - Quota reserved under a row lock when a batch is created; every status change goes through one transition that frees space in the same transaction. SHA-256 duplicates are rejected before any upload. - tus 1.0.0 core (HEAD/PATCH) for the author only; offset is the real staging file size, data beyond the declared size is refused (ADR-015). - Processing worker: ffprobe checks, SHA-256 verification, exact and Chromaprint duplicates against library and queue, metadata from tags or the dropped folder path, possible_duplicate on metadata match, and the target path from write_template reusing existing folder spelling. - Import worker: edited tags written with ffmpeg stream copy, file written by storage-agent, point scan of the new file, imported status; idempotent and resumed after restart. Stale uploads expire after 24 h, orphan staging files are removed at startup. - API: quota, precheck, batches, my uploads, withdraw, admin list, metadata edit, bulk review, retry and queue settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
5.4 KiB
Go
93 lines
5.4 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"`
|
||
// FPCalcPath — fpcalc (Chromaprint) для акустических отпечатков; нет бинарника — считается только SHA-256.
|
||
FPCalcPath string `env:"FPCALC_PATH" envDefault:"fpcalc"`
|
||
|
||
// 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"`
|
||
// TranscodeConcurrency — параллельных ffmpeg; 0 — половина ядер.
|
||
TranscodeConcurrency int `env:"TRANSCODE_CONCURRENCY" envDefault:"0"`
|
||
TranscodeCacheLimitGB int `env:"TRANSCODE_CACHE_LIMIT_GB" envDefault:"50"`
|
||
|
||
RecommenderURL string `env:"RECOMMENDER_URL" envDefault:"http://recommender:8000"`
|
||
|
||
MeiliURL string `env:"MEILI_URL" envDefault:"http://meilisearch:7700"`
|
||
MeiliMasterKey string `env:"MEILI_MASTER_KEY"`
|
||
|
||
// Самообновление Android: сервер читает релизы Gitea своим токеном и кэширует APK в UpdatesDir.
|
||
GiteaURL string `env:"GITEA_URL"`
|
||
GiteaRepo string `env:"GITEA_REPO"`
|
||
GiteaToken string `env:"GITEA_TOKEN"`
|
||
UpdatesDir string `env:"UPDATES_DIR" envDefault:"/updates"`
|
||
// UploadsDir — staging очереди на добавление (локальный диск, общий со storage-agent на чтение).
|
||
UploadsDir string `env:"UPLOADS_DIR" envDefault:"/uploads"`
|
||
}
|
||
|
||
// 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"`
|
||
// Запись импортированных загрузок: rw-точки NFS (только внутри агента), LIBRARY_HOST на запись, staging.
|
||
LibraryRWRoot string `env:"LIBRARY_RW_ROOT" envDefault:"/library-rw"`
|
||
HostRWRoot string `env:"LIBRARY_HOST_RW_ROOT" envDefault:"/library-host-rw"`
|
||
UploadsRoot string `env:"UPLOADS_ROOT" envDefault:"/uploads"`
|
||
// Владелец новых файлов на файловом сервере; -1 — оставить как есть.
|
||
FileUID int `env:"PIPU_FILE_UID" envDefault:"-1"`
|
||
FileGID int `env:"PIPU_FILE_GID" envDefault:"-1"`
|
||
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
|
||
}
|