347 lines
9.9 KiB
Go
347 lines
9.9 KiB
Go
// Copyright (c) 2026 Winlin
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
package env
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"srsx/internal/errors"
|
|
"srsx/internal/logger"
|
|
)
|
|
|
|
// Indirections over os and filesystem primitives so tests can swap them
|
|
// without touching real process env or the filesystem.
|
|
var (
|
|
getEnv = os.Getenv
|
|
setEnv = os.Setenv
|
|
lookupEnv = os.LookupEnv
|
|
openFile = func(name string) (io.ReadCloser, error) {
|
|
return os.Open(name)
|
|
}
|
|
)
|
|
|
|
// ProxyEnvironment provides access to proxy environment variables.
|
|
type ProxyEnvironment interface {
|
|
// Go pprof profiling
|
|
GoPprof() string
|
|
// Graceful quit timeout
|
|
GraceQuitTimeout() string
|
|
// Force quit timeout
|
|
ForceQuitTimeout() string
|
|
// HTTP API server port
|
|
HttpAPI() string
|
|
// HTTP web server port
|
|
HttpServer() string
|
|
// RTMP media server port
|
|
RtmpServer() string
|
|
// WebRTC media server port (UDP)
|
|
WebRTCServer() string
|
|
// SRT media server port (UDP)
|
|
SRTServer() string
|
|
// System API server port
|
|
SystemAPI() string
|
|
// Static files directory
|
|
StaticFiles() string
|
|
// Load balancer type (memory or redis)
|
|
LoadBalancerType() string
|
|
// Redis host
|
|
RedisHost() string
|
|
// Redis port
|
|
RedisPort() string
|
|
// Redis password
|
|
RedisPassword() string
|
|
// Redis database
|
|
RedisDB() string
|
|
// Default backend enabled
|
|
DefaultBackendEnabled() string
|
|
// Default backend IP
|
|
DefaultBackendIP() string
|
|
// Default backend RTMP port
|
|
DefaultBackendRTMP() string
|
|
// Default backend HTTP port
|
|
DefaultBackendHttp() string
|
|
// Default backend API port
|
|
DefaultBackendAPI() string
|
|
// Default backend RTC port (UDP)
|
|
DefaultBackendRTC() string
|
|
// Default backend SRT port (UDP)
|
|
DefaultBackendSRT() string
|
|
}
|
|
|
|
type proxyEnvironment struct{}
|
|
|
|
// NewProxyEnvironment creates a new ProxyEnvironment instance, loading and building default environment variables.
|
|
func NewProxyEnvironment(ctx context.Context) (ProxyEnvironment, error) {
|
|
if err := loadEnvFile(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
buildDefaultEnvironmentVariables(ctx)
|
|
return &proxyEnvironment{}, nil
|
|
}
|
|
|
|
func (e *proxyEnvironment) GoPprof() string {
|
|
return getEnv("GO_PPROF")
|
|
}
|
|
|
|
func (e *proxyEnvironment) GraceQuitTimeout() string {
|
|
return getEnv("PROXY_GRACE_QUIT_TIMEOUT")
|
|
}
|
|
|
|
func (e *proxyEnvironment) ForceQuitTimeout() string {
|
|
return getEnv("PROXY_FORCE_QUIT_TIMEOUT")
|
|
}
|
|
|
|
func (e *proxyEnvironment) HttpAPI() string {
|
|
return getEnv("PROXY_HTTP_API")
|
|
}
|
|
|
|
func (e *proxyEnvironment) HttpServer() string {
|
|
return getEnv("PROXY_HTTP_SERVER")
|
|
}
|
|
|
|
func (e *proxyEnvironment) RtmpServer() string {
|
|
return getEnv("PROXY_RTMP_SERVER")
|
|
}
|
|
|
|
func (e *proxyEnvironment) WebRTCServer() string {
|
|
return getEnv("PROXY_WEBRTC_SERVER")
|
|
}
|
|
|
|
func (e *proxyEnvironment) SRTServer() string {
|
|
return getEnv("PROXY_SRT_SERVER")
|
|
}
|
|
|
|
func (e *proxyEnvironment) SystemAPI() string {
|
|
return getEnv("PROXY_SYSTEM_API")
|
|
}
|
|
|
|
func (e *proxyEnvironment) StaticFiles() string {
|
|
return getEnv("PROXY_STATIC_FILES")
|
|
}
|
|
|
|
func (e *proxyEnvironment) LoadBalancerType() string {
|
|
return getEnv("PROXY_LOAD_BALANCER_TYPE")
|
|
}
|
|
|
|
func (e *proxyEnvironment) RedisHost() string {
|
|
return getEnv("PROXY_REDIS_HOST")
|
|
}
|
|
|
|
func (e *proxyEnvironment) RedisPort() string {
|
|
return getEnv("PROXY_REDIS_PORT")
|
|
}
|
|
|
|
func (e *proxyEnvironment) RedisPassword() string {
|
|
return getEnv("PROXY_REDIS_PASSWORD")
|
|
}
|
|
|
|
func (e *proxyEnvironment) RedisDB() string {
|
|
return getEnv("PROXY_REDIS_DB")
|
|
}
|
|
|
|
func (e *proxyEnvironment) DefaultBackendEnabled() string {
|
|
return getEnv("PROXY_DEFAULT_BACKEND_ENABLED")
|
|
}
|
|
|
|
func (e *proxyEnvironment) DefaultBackendIP() string {
|
|
return getEnv("PROXY_DEFAULT_BACKEND_IP")
|
|
}
|
|
|
|
func (e *proxyEnvironment) DefaultBackendRTMP() string {
|
|
return getEnv("PROXY_DEFAULT_BACKEND_RTMP")
|
|
}
|
|
|
|
func (e *proxyEnvironment) DefaultBackendHttp() string {
|
|
return getEnv("PROXY_DEFAULT_BACKEND_HTTP")
|
|
}
|
|
|
|
func (e *proxyEnvironment) DefaultBackendAPI() string {
|
|
return getEnv("PROXY_DEFAULT_BACKEND_API")
|
|
}
|
|
|
|
func (e *proxyEnvironment) DefaultBackendRTC() string {
|
|
return getEnv("PROXY_DEFAULT_BACKEND_RTC")
|
|
}
|
|
|
|
func (e *proxyEnvironment) DefaultBackendSRT() string {
|
|
return getEnv("PROXY_DEFAULT_BACKEND_SRT")
|
|
}
|
|
|
|
// loadEnvFile loads the environment variables from .env file.
|
|
func loadEnvFile(ctx context.Context) error {
|
|
envMap, err := parseEnvFile(".env")
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
logger.Debug(ctx, "no .env file found, skipping")
|
|
return nil
|
|
}
|
|
return errors.Wrapf(err, "load .env file")
|
|
}
|
|
|
|
// Skip keys already set in the environment so we don't overwrite them.
|
|
for key, value := range envMap {
|
|
if _, ok := lookupEnv(key); !ok {
|
|
setEnv(key, value)
|
|
}
|
|
}
|
|
|
|
logger.Debug(ctx, "successfully loaded .env file")
|
|
return nil
|
|
}
|
|
|
|
// parseEnvFile opens filename and parses its contents as .env-formatted lines.
|
|
func parseEnvFile(filename string) (map[string]string, error) {
|
|
file, err := openFile(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
return parseEnvReader(file)
|
|
}
|
|
|
|
// parseEnvReader parses .env-formatted content from r. It performs no I/O
|
|
// beyond reading r, so it is trivially testable with strings.NewReader.
|
|
func parseEnvReader(r io.Reader) (map[string]string, error) {
|
|
envMap := make(map[string]string)
|
|
scanner := bufio.NewScanner(r)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
|
|
// Skip empty lines and comments.
|
|
if line == "" || line[0] == '#' {
|
|
continue
|
|
}
|
|
|
|
// Strip optional "export " prefix.
|
|
if strings.HasPrefix(line, "export ") {
|
|
line = strings.TrimPrefix(line, "export ")
|
|
line = strings.TrimSpace(line)
|
|
}
|
|
|
|
// Split on first '=' to get key and value.
|
|
key, value, found := strings.Cut(line, "=")
|
|
if !found {
|
|
continue
|
|
}
|
|
|
|
key = strings.TrimSpace(key)
|
|
value = strings.TrimSpace(value)
|
|
|
|
// Handle quoted values.
|
|
if len(value) >= 2 {
|
|
if value[0] == '\'' && value[len(value)-1] == '\'' {
|
|
// Single-quoted: raw literal, no escaping.
|
|
value = value[1 : len(value)-1]
|
|
} else if value[0] == '"' && value[len(value)-1] == '"' {
|
|
// Double-quoted: process escape sequences.
|
|
value = value[1 : len(value)-1]
|
|
value = strings.ReplaceAll(value, `\n`, "\n")
|
|
value = strings.ReplaceAll(value, `\r`, "\r")
|
|
value = strings.ReplaceAll(value, `\"`, `"`)
|
|
value = strings.ReplaceAll(value, `\\`, `\`)
|
|
} else {
|
|
// Unquoted: strip inline comments.
|
|
if idx := strings.Index(value, " #"); idx != -1 {
|
|
value = strings.TrimSpace(value[:idx])
|
|
}
|
|
}
|
|
} else {
|
|
// Unquoted short value: strip inline comments.
|
|
if idx := strings.Index(value, " #"); idx != -1 {
|
|
value = strings.TrimSpace(value[:idx])
|
|
}
|
|
}
|
|
|
|
envMap[key] = value
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return envMap, nil
|
|
}
|
|
|
|
// buildDefaultEnvironmentVariables setups the default environment variables.
|
|
func buildDefaultEnvironmentVariables(ctx context.Context) {
|
|
// Whether enable the Go pprof.
|
|
setEnvDefault("GO_PPROF", "")
|
|
// Force shutdown timeout.
|
|
setEnvDefault("PROXY_FORCE_QUIT_TIMEOUT", "30s")
|
|
// Graceful quit timeout.
|
|
setEnvDefault("PROXY_GRACE_QUIT_TIMEOUT", "20s")
|
|
|
|
// The HTTP API server.
|
|
setEnvDefault("PROXY_HTTP_API", "11985")
|
|
// The HTTP web server.
|
|
setEnvDefault("PROXY_HTTP_SERVER", "18080")
|
|
// The RTMP media server.
|
|
setEnvDefault("PROXY_RTMP_SERVER", "11935")
|
|
// The WebRTC media server, via UDP protocol.
|
|
setEnvDefault("PROXY_WEBRTC_SERVER", "18000")
|
|
// The SRT media server, via UDP protocol.
|
|
setEnvDefault("PROXY_SRT_SERVER", "20080")
|
|
// The API server of proxy itself.
|
|
setEnvDefault("PROXY_SYSTEM_API", "12025")
|
|
// The static directory for web server, optional.
|
|
setEnvDefault("PROXY_STATIC_FILES", "./trunk/research")
|
|
|
|
// The load balancer, use redis or memory.
|
|
setEnvDefault("PROXY_LOAD_BALANCER_TYPE", "memory")
|
|
// The redis server host.
|
|
setEnvDefault("PROXY_REDIS_HOST", "127.0.0.1")
|
|
// The redis server port.
|
|
setEnvDefault("PROXY_REDIS_PORT", "6379")
|
|
// The redis server password.
|
|
setEnvDefault("PROXY_REDIS_PASSWORD", "")
|
|
// The redis server db.
|
|
setEnvDefault("PROXY_REDIS_DB", "0")
|
|
|
|
// Whether enable the default backend server, for debugging.
|
|
setEnvDefault("PROXY_DEFAULT_BACKEND_ENABLED", "off")
|
|
// Default backend server IP, for debugging.
|
|
setEnvDefault("PROXY_DEFAULT_BACKEND_IP", "127.0.0.1")
|
|
// Default backend server port, for debugging.
|
|
setEnvDefault("PROXY_DEFAULT_BACKEND_RTMP", "1935")
|
|
// Default backend api port, for debugging.
|
|
setEnvDefault("PROXY_DEFAULT_BACKEND_API", "1985")
|
|
// Default backend udp rtc port, for debugging.
|
|
setEnvDefault("PROXY_DEFAULT_BACKEND_RTC", "8000")
|
|
// Default backend udp srt port, for debugging.
|
|
setEnvDefault("PROXY_DEFAULT_BACKEND_SRT", "10080")
|
|
|
|
logger.Debug(ctx, "load .env as GO_PPROF=%v, "+
|
|
"PROXY_FORCE_QUIT_TIMEOUT=%v, PROXY_GRACE_QUIT_TIMEOUT=%v, "+
|
|
"PROXY_HTTP_API=%v, PROXY_HTTP_SERVER=%v, PROXY_RTMP_SERVER=%v, "+
|
|
"PROXY_WEBRTC_SERVER=%v, PROXY_SRT_SERVER=%v, "+
|
|
"PROXY_SYSTEM_API=%v, PROXY_STATIC_FILES=%v, PROXY_DEFAULT_BACKEND_ENABLED=%v, "+
|
|
"PROXY_DEFAULT_BACKEND_IP=%v, PROXY_DEFAULT_BACKEND_RTMP=%v, "+
|
|
"PROXY_DEFAULT_BACKEND_HTTP=%v, PROXY_DEFAULT_BACKEND_API=%v, "+
|
|
"PROXY_DEFAULT_BACKEND_RTC=%v, PROXY_DEFAULT_BACKEND_SRT=%v, "+
|
|
"PROXY_LOAD_BALANCER_TYPE=%v, PROXY_REDIS_HOST=%v, PROXY_REDIS_PORT=%v, "+
|
|
"PROXY_REDIS_PASSWORD=%v, PROXY_REDIS_DB=%v",
|
|
getEnv("GO_PPROF"),
|
|
getEnv("PROXY_FORCE_QUIT_TIMEOUT"), getEnv("PROXY_GRACE_QUIT_TIMEOUT"),
|
|
getEnv("PROXY_HTTP_API"), getEnv("PROXY_HTTP_SERVER"), getEnv("PROXY_RTMP_SERVER"),
|
|
getEnv("PROXY_WEBRTC_SERVER"), getEnv("PROXY_SRT_SERVER"),
|
|
getEnv("PROXY_SYSTEM_API"), getEnv("PROXY_STATIC_FILES"), getEnv("PROXY_DEFAULT_BACKEND_ENABLED"),
|
|
getEnv("PROXY_DEFAULT_BACKEND_IP"), getEnv("PROXY_DEFAULT_BACKEND_RTMP"),
|
|
getEnv("PROXY_DEFAULT_BACKEND_HTTP"), getEnv("PROXY_DEFAULT_BACKEND_API"),
|
|
getEnv("PROXY_DEFAULT_BACKEND_RTC"), getEnv("PROXY_DEFAULT_BACKEND_SRT"),
|
|
getEnv("PROXY_LOAD_BALANCER_TYPE"), getEnv("PROXY_REDIS_HOST"), getEnv("PROXY_REDIS_PORT"),
|
|
getEnv("PROXY_REDIS_PASSWORD"), getEnv("PROXY_REDIS_DB"),
|
|
)
|
|
}
|
|
|
|
// setEnvDefault set env key=value if not set.
|
|
func setEnvDefault(key, value string) {
|
|
if getEnv(key) == "" {
|
|
setEnv(key, value)
|
|
}
|
|
}
|