222 lines
5.8 KiB
Go
222 lines
5.8 KiB
Go
package config
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/url"
|
||
"os"
|
||
"path/filepath"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
)
|
||
|
||
// Runtime configuration keys stored in the runtime config file.
|
||
const (
|
||
keyMatomoURL = "matomo_url"
|
||
keyMatomoSiteID = "matomo_site_id"
|
||
keyHLSSegmentDur = "hls_segment_duration"
|
||
keyHLSWindowSize = "hls_window_size"
|
||
)
|
||
|
||
// Default HLS values used when neither env nor persisted config is set.
|
||
const (
|
||
defaultHLSSegmentDuration = 6
|
||
defaultHLSWindowSize = 24
|
||
)
|
||
|
||
var (
|
||
runtimeMu sync.RWMutex
|
||
runtimeStore = map[string]string{}
|
||
)
|
||
|
||
// ConfigPath returns the runtime config JSON file path from env or default.
|
||
// The default lives in the shared playlist volume so the playout service can
|
||
// read the same file (chat has read-write, playout read-only).
|
||
func ConfigPath() string {
|
||
p := strings.TrimSpace(os.Getenv("CONFIG_PATH"))
|
||
if p == "" {
|
||
return "/app/playlist/config.json"
|
||
}
|
||
return p
|
||
}
|
||
|
||
// GetRuntime returns a value from the runtime store and whether it is present.
|
||
func GetRuntime(key string) (string, bool) {
|
||
runtimeMu.RLock()
|
||
defer runtimeMu.RUnlock()
|
||
v, ok := runtimeStore[key]
|
||
return v, ok
|
||
}
|
||
|
||
// SetRuntime stores a value in the runtime store (in-memory only).
|
||
func SetRuntime(key, value string) {
|
||
runtimeMu.Lock()
|
||
defer runtimeMu.Unlock()
|
||
runtimeStore[key] = value
|
||
}
|
||
|
||
// LoadRuntimeConfig loads the persisted runtime config from disk. If the file does
|
||
// not exist, it seeds the store from environment variables.
|
||
func LoadRuntimeConfig() error {
|
||
data, err := os.ReadFile(ConfigPath())
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
runtimeMu.Lock()
|
||
runtimeStore = map[string]string{}
|
||
runtimeMu.Unlock()
|
||
seedRuntimeDefaults()
|
||
return nil
|
||
}
|
||
return fmt.Errorf("read runtime config: %w", err)
|
||
}
|
||
|
||
values := map[string]string{}
|
||
if err := json.Unmarshal(data, &values); err != nil {
|
||
return fmt.Errorf("parse runtime config: %w", err)
|
||
}
|
||
|
||
runtimeMu.Lock()
|
||
defer runtimeMu.Unlock()
|
||
runtimeStore = values
|
||
return nil
|
||
}
|
||
|
||
// SaveRuntimeConfig persists the runtime store to disk as JSON.
|
||
func SaveRuntimeConfig() error {
|
||
runtimeMu.RLock()
|
||
data, err := json.MarshalIndent(runtimeStore, "", " ")
|
||
runtimeMu.RUnlock()
|
||
if err != nil {
|
||
return fmt.Errorf("marshal runtime config: %w", err)
|
||
}
|
||
if dir := filepath.Dir(ConfigPath()); dir != "" && dir != "." {
|
||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||
return fmt.Errorf("create runtime config dir: %w", err)
|
||
}
|
||
}
|
||
if err := os.WriteFile(ConfigPath(), append(data, '\n'), 0o644); err != nil {
|
||
return fmt.Errorf("write runtime config: %w", err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// seedRuntimeDefaults populates the store from environment variables.
|
||
func seedRuntimeDefaults() {
|
||
if v := strings.TrimSpace(os.Getenv("MATOMO_URL")); v != "" {
|
||
SetRuntime(keyMatomoURL, v)
|
||
}
|
||
if v := strings.TrimSpace(os.Getenv("MATOMO_SITE_ID")); v != "" {
|
||
SetRuntime(keyMatomoSiteID, v)
|
||
}
|
||
if v := strings.TrimSpace(os.Getenv("HLS_SEGMENT_DURATION")); v != "" {
|
||
SetRuntime(keyHLSSegmentDur, v)
|
||
}
|
||
if v := strings.TrimSpace(os.Getenv("HLS_WINDOW_SIZE")); v != "" {
|
||
SetRuntime(keyHLSWindowSize, v)
|
||
}
|
||
}
|
||
|
||
// MatomoURL returns the Matomo analytics URL.
|
||
func MatomoURL() string {
|
||
if v, ok := GetRuntime(keyMatomoURL); ok && strings.TrimSpace(v) != "" {
|
||
return strings.TrimSpace(v)
|
||
}
|
||
return strings.TrimSpace(os.Getenv("MATOMO_URL"))
|
||
}
|
||
|
||
// MatomoSiteID returns the Matomo site ID.
|
||
func MatomoSiteID() string {
|
||
if v, ok := GetRuntime(keyMatomoSiteID); ok && strings.TrimSpace(v) != "" {
|
||
return strings.TrimSpace(v)
|
||
}
|
||
return strings.TrimSpace(os.Getenv("MATOMO_SITE_ID"))
|
||
}
|
||
|
||
// HLSSegmentDuration returns the HLS segment duration in seconds.
|
||
func HLSSegmentDuration() int {
|
||
raw := ""
|
||
if v, ok := GetRuntime(keyHLSSegmentDur); ok {
|
||
raw = strings.TrimSpace(v)
|
||
}
|
||
if raw == "" {
|
||
raw = strings.TrimSpace(os.Getenv("HLS_SEGMENT_DURATION"))
|
||
}
|
||
n, err := strconv.Atoi(raw)
|
||
if err != nil || n <= 0 {
|
||
return defaultHLSSegmentDuration
|
||
}
|
||
return n
|
||
}
|
||
|
||
// HLSWindowSize returns the HLS playlist window size in segments.
|
||
func HLSWindowSize() int {
|
||
raw := ""
|
||
if v, ok := GetRuntime(keyHLSWindowSize); ok {
|
||
raw = strings.TrimSpace(v)
|
||
}
|
||
if raw == "" {
|
||
raw = strings.TrimSpace(os.Getenv("HLS_WINDOW_SIZE"))
|
||
}
|
||
n, err := strconv.Atoi(raw)
|
||
if err != nil || n <= 0 {
|
||
return defaultHLSWindowSize
|
||
}
|
||
return n
|
||
}
|
||
|
||
// SetMatomoURL validates and stores the Matomo URL.
|
||
func SetMatomoURL(raw string) error {
|
||
raw = strings.TrimSpace(raw)
|
||
if raw == "" {
|
||
SetRuntime(keyMatomoURL, "")
|
||
return nil
|
||
}
|
||
u, err := url.Parse(raw)
|
||
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
|
||
return fmt.Errorf("matomo URL must be an absolute http(s) URL")
|
||
}
|
||
SetRuntime(keyMatomoURL, raw)
|
||
return nil
|
||
}
|
||
|
||
// SetMatomoSiteID validates and stores the Matomo site ID.
|
||
func SetMatomoSiteID(raw string) error {
|
||
raw = strings.TrimSpace(raw)
|
||
if raw != "" {
|
||
if _, err := strconv.Atoi(raw); err != nil {
|
||
return fmt.Errorf("matomo site ID must be numeric")
|
||
}
|
||
}
|
||
SetRuntime(keyMatomoSiteID, raw)
|
||
return nil
|
||
}
|
||
|
||
// SetHLSSegmentDuration validates (2–15s) and stores the HLS segment duration.
|
||
func SetHLSSegmentDuration(seconds int) error {
|
||
if seconds < 2 || seconds > 15 {
|
||
return fmt.Errorf("HLS segment duration must be between 2 and 15 seconds")
|
||
}
|
||
SetRuntime(keyHLSSegmentDur, strconv.Itoa(seconds))
|
||
return nil
|
||
}
|
||
|
||
// SetHLSWindowSize validates (3–100) and stores the HLS window size.
|
||
func SetHLSWindowSize(size int) error {
|
||
if size < 3 || size > 100 {
|
||
return fmt.Errorf("HLS window size must be between 3 and 100 segments")
|
||
}
|
||
SetRuntime(keyHLSWindowSize, strconv.Itoa(size))
|
||
return nil
|
||
}
|
||
|
||
// RuntimeConfigSnapshot returns the resolved runtime configuration values.
|
||
func RuntimeConfigSnapshot() map[string]any {
|
||
return map[string]any{
|
||
"matomoUrl": MatomoURL(),
|
||
"matomoSiteId": MatomoSiteID(),
|
||
"hlsSegmentDuration": HLSSegmentDuration(),
|
||
"hlsWindowSize": HLSWindowSize(),
|
||
}
|
||
}
|