Files
linearvoid/backend/chat/src/config/runtime.go
T

222 lines
5.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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(),
}
}