feat: runtime config, Matomo analytics, chat accounts, DM history/search, and Prometheus retention
This commit is contained in:
1 parent
06ab5b4655
commit
121fdbacdc
27 files changed
+1841
-161
No files matched your search
@@ -0,0 +1,221 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user