Files
silo-server/internal/config/yaml_import.go
T

221 lines
9.2 KiB
Go

package config
import (
"fmt"
"os"
"strconv"
"gopkg.in/yaml.v3"
)
// setIfNonEmpty sets m[key] = value only when value is non-empty.
func setIfNonEmpty(m map[string]string, key, value string) {
if value != "" {
m[key] = value
}
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if value != "" {
return value
}
}
return ""
}
// YAMLToSettingsMap reads a YAML config file and converts it to a flat
// map[string]string suitable for insertion into the server_settings table.
//
// setDefaults() is called first so that boolean and integer fields that are
// absent from the YAML file are represented with their default values rather
// than Go zero-values.
func YAMLToSettingsMap(path string) (map[string]string, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading YAML config: %w", err)
}
raw := setDefaults()
if err := yaml.Unmarshal(data, raw); err != nil {
return nil, fmt.Errorf("parsing YAML config: %w", err)
}
m := make(map[string]string)
// Server
setIfNonEmpty(m, "server.listen", raw.Server.Listen)
setIfNonEmpty(m, "server.mode", raw.Server.Mode)
setIfNonEmpty(m, "server.log_level", raw.Server.LogLevel)
setIfNonEmpty(m, "server.log_format", raw.Server.LogFormat)
setIfNonEmpty(m, "server.log_quiet", raw.Server.LogQuiet)
// Database
setIfNonEmpty(m, "database.url", raw.Database.URL)
if raw.Database.MaxConnections != 0 {
m["database.max_connections"] = strconv.Itoa(raw.Database.MaxConnections)
}
legacyOperationalConfigured :=
raw.S3.OperationalEndpoint != "" ||
raw.S3.OperationalPublicEndpoint != "" ||
raw.S3.OperationalRegion != "" ||
raw.S3.OperationalBucket != "" ||
raw.S3.OperationalKeyPrefix != "" ||
raw.S3.OperationalAccessKey != "" ||
raw.S3.OperationalSecretKey != "" ||
raw.S3.OperationalURLAuth != "" ||
raw.S3.OperationalTokenSecret != "" ||
raw.S3.OperationalTokenParam != "" ||
raw.S3.OperationalTokenTTL > 0 ||
!raw.S3.OperationalPathStyle
publicConfigured :=
raw.S3.PublicEndpoint != "" ||
raw.S3.PublicReadEndpoint != "" ||
raw.S3.PublicRegion != "" ||
raw.S3.PublicBucket != "" ||
raw.S3.PublicKeyPrefix != "" ||
raw.S3.PublicAccessKey != "" ||
raw.S3.PublicSecretKey != "" ||
raw.S3.PublicURLAuth != "" ||
raw.S3.PublicTokenSecret != "" ||
raw.S3.PublicTokenParam != "" ||
raw.S3.PublicTokenTTL > 0 ||
raw.S3.MetadataPresignExpiry != ""
privateConfigured :=
raw.S3.PrivateEndpoint != "" ||
raw.S3.PrivateRegion != "" ||
raw.S3.PrivateBucket != "" ||
raw.S3.PrivateKeyPrefix != "" ||
raw.S3.PrivateAccessKey != "" ||
raw.S3.PrivateSecretKey != ""
publicPathStyle := raw.S3.PublicPathStyle
if !publicConfigured && legacyOperationalConfigured {
publicPathStyle = raw.S3.OperationalPathStyle
}
privatePathStyle := raw.S3.PrivatePathStyle
if !privateConfigured && legacyOperationalConfigured {
privatePathStyle = raw.S3.OperationalPathStyle
}
// S3 — Public assets
setIfNonEmpty(m, "s3.public_endpoint", firstNonEmpty(raw.S3.PublicEndpoint, raw.S3.OperationalEndpoint))
setIfNonEmpty(m, "s3.public_read_endpoint", firstNonEmpty(raw.S3.PublicReadEndpoint, raw.S3.OperationalPublicEndpoint))
setIfNonEmpty(m, "s3.public_region", firstNonEmpty(raw.S3.PublicRegion, raw.S3.OperationalRegion))
m["s3.public_path_style"] = strconv.FormatBool(publicPathStyle)
setIfNonEmpty(m, "s3.public_bucket", firstNonEmpty(raw.S3.PublicBucket, raw.S3.OperationalBucket))
setIfNonEmpty(m, "s3.public_key_prefix", firstNonEmpty(raw.S3.PublicKeyPrefix, raw.S3.OperationalKeyPrefix))
setIfNonEmpty(m, "s3.public_access_key", firstNonEmpty(raw.S3.PublicAccessKey, raw.S3.OperationalAccessKey))
setIfNonEmpty(m, "s3.public_secret_key", firstNonEmpty(raw.S3.PublicSecretKey, raw.S3.OperationalSecretKey))
setIfNonEmpty(m, "s3.public_url_auth", firstNonEmpty(raw.S3.PublicURLAuth, raw.S3.OperationalURLAuth))
setIfNonEmpty(m, "s3.public_token_secret", firstNonEmpty(raw.S3.PublicTokenSecret, raw.S3.OperationalTokenSecret))
setIfNonEmpty(m, "s3.public_token_param", firstNonEmpty(raw.S3.PublicTokenParam, raw.S3.OperationalTokenParam))
if ttl := raw.S3.PublicTokenTTL; ttl > 0 {
m["s3.public_token_ttl"] = strconv.Itoa(ttl)
} else if raw.S3.OperationalTokenTTL > 0 {
m["s3.public_token_ttl"] = strconv.Itoa(raw.S3.OperationalTokenTTL)
}
setIfNonEmpty(m, "s3.metadata_presign_expiry", raw.S3.MetadataPresignExpiry)
// S3 — Private internal
setIfNonEmpty(m, "s3.private_endpoint", firstNonEmpty(raw.S3.PrivateEndpoint, raw.S3.OperationalEndpoint))
setIfNonEmpty(m, "s3.private_region", firstNonEmpty(raw.S3.PrivateRegion, raw.S3.OperationalRegion))
m["s3.private_path_style"] = strconv.FormatBool(privatePathStyle)
setIfNonEmpty(m, "s3.private_bucket", firstNonEmpty(raw.S3.PrivateBucket, raw.S3.OperationalBucket))
setIfNonEmpty(m, "s3.private_key_prefix", firstNonEmpty(raw.S3.PrivateKeyPrefix, raw.S3.OperationalKeyPrefix))
setIfNonEmpty(m, "s3.private_access_key", firstNonEmpty(raw.S3.PrivateAccessKey, raw.S3.OperationalAccessKey))
setIfNonEmpty(m, "s3.private_secret_key", firstNonEmpty(raw.S3.PrivateSecretKey, raw.S3.OperationalSecretKey))
// S3 — User DB
setIfNonEmpty(m, "s3.user_db_endpoint", raw.S3.UserDBEndpoint)
setIfNonEmpty(m, "s3.user_db_region", raw.S3.UserDBRegion)
m["s3.user_db_path_style"] = strconv.FormatBool(raw.S3.UserDBPathStyle)
setIfNonEmpty(m, "s3.user_db_bucket", raw.S3.UserDBBucket)
setIfNonEmpty(m, "s3.user_db_key_prefix", raw.S3.UserDBKeyPrefix)
setIfNonEmpty(m, "s3.user_db_access_key", raw.S3.UserDBAccessKey)
setIfNonEmpty(m, "s3.user_db_secret_key", raw.S3.UserDBSecretKey)
// UserDB (YAML key: user_db)
setIfNonEmpty(m, "userdb.backend", raw.UserDB.Backend)
if raw.UserDB.PoolMaxOpen != 0 {
m["userdb.pool_max_open"] = strconv.Itoa(raw.UserDB.PoolMaxOpen)
}
setIfNonEmpty(m, "userdb.idle_timeout", raw.UserDB.IdleTimeout)
setIfNonEmpty(m, "userdb.litestream_sync", raw.UserDB.LitestreamSync)
if raw.UserDB.StaleGraceSeconds != 0 {
m["userdb.stale_grace_seconds"] = strconv.Itoa(raw.UserDB.StaleGraceSeconds)
}
// Scanner
if raw.Scanner.Workers != 0 {
m["scanner.workers"] = strconv.Itoa(raw.Scanner.Workers)
}
if raw.Scanner.MaxConcurrentLibraries != 0 {
m["scanner.max_concurrent_libraries"] = strconv.Itoa(raw.Scanner.MaxConcurrentLibraries)
}
if raw.Scanner.MaxConcurrentScoped != 0 {
m["scanner.max_concurrent_scoped"] = strconv.Itoa(raw.Scanner.MaxConcurrentScoped)
}
setIfNonEmpty(m, "scanner.file_removal_grace", raw.Scanner.FileRemovalGrace)
m["scanner.empty_trash_after_scan"] = strconv.FormatBool(raw.Scanner.EmptyTrashAfterScan)
// Matcher
if raw.Matcher.Workers != 0 {
m["matcher.workers"] = strconv.Itoa(raw.Matcher.Workers)
}
if raw.Matcher.BatchSize != 0 {
m["matcher.batch_size"] = strconv.Itoa(raw.Matcher.BatchSize)
}
m["matcher.enable_tv_series_root_queue"] = strconv.FormatBool(raw.Matcher.TVSeriesRootQueueEnabled())
// Playback
setIfNonEmpty(m, "playback.ffmpeg_path", raw.Playback.FFmpegPath)
setIfNonEmpty(m, "playback.transcode_dir", raw.Playback.TranscodeDir)
setIfNonEmpty(m, "playback.hw_accel", raw.Playback.HWAccel)
if raw.Playback.ChapterThumbnailWorkers != 0 {
m["playback.chapter_thumbnail_workers"] = strconv.Itoa(raw.Playback.ChapterThumbnailWorkers)
}
setIfNonEmpty(m, "playback.chapter_thumbnail_execution", raw.Playback.ChapterThumbnailExecution)
if raw.Playback.ChapterThumbnailNodeCapacity != 0 {
m["playback.chapter_thumbnail_node_capacity"] = strconv.Itoa(raw.Playback.ChapterThumbnailNodeCapacity)
}
m["playback.transcode_enabled"] = strconv.FormatBool(raw.Playback.TranscodeEnabled)
m["playback.allow_hevc_encoding"] = strconv.FormatBool(raw.Playback.AllowHEVCEncoding)
if raw.Playback.TranscodeAheadSegments != 0 {
m["playback.transcode_ahead_segments"] = strconv.Itoa(raw.Playback.TranscodeAheadSegments)
}
if raw.Playback.SegmentDuration != 0 {
m["playback.segment_duration"] = strconv.Itoa(raw.Playback.SegmentDuration)
}
// Redis
m["redis.url"] = raw.Redis.URL
// Redis Sentinel
setIfNonEmpty(m, "redis.sentinel_master", raw.Redis.SentinelMaster)
setIfNonEmpty(m, "redis.sentinel_password", raw.Redis.SentinelPassword)
// SentinelAddresses is []string — stored in YAML only, not in the flat map
// Rate Limiting
m["ratelimit.enabled"] = strconv.FormatBool(raw.RateLimit.Enabled)
setIfNonEmpty(m, "ratelimit.backend", raw.RateLimit.Backend)
// Auth
setIfNonEmpty(m, "auth.jwt_secret", raw.Auth.JWTSecret)
setIfNonEmpty(m, "auth.access_token_expiry", raw.Auth.AccessTokenExpiry)
setIfNonEmpty(m, "auth.refresh_token_expiry", raw.Auth.RefreshTokenExpiry)
// JellyfinCompat
setIfNonEmpty(m, "jellyfin_compat.listen", raw.JellyfinCompat.Listen)
setIfNonEmpty(m, "jellyfin_compat.public_url", raw.JellyfinCompat.PublicURL)
setIfNonEmpty(m, "jellyfin_compat.emulated_server_version", raw.JellyfinCompat.EmulatedServerVersion)
setIfNonEmpty(m, "jellyfin_compat.server_id", raw.JellyfinCompat.ServerID)
setIfNonEmpty(m, "jellyfin_compat.server_name", raw.JellyfinCompat.ServerName)
setIfNonEmpty(m, "jellyfin_compat.web_version", raw.JellyfinCompat.WebVersion)
setIfNonEmpty(m, "jellyfin_compat.web_dir", raw.JellyfinCompat.WebDir)
setIfNonEmpty(m, "jellyfin_compat.session_ttl", raw.JellyfinCompat.SessionTTL)
setIfNonEmpty(m, "jellyfin_compat.playback_session_ttl", raw.JellyfinCompat.PlaybackSessionTTL)
return m, nil
}