convert the configuration into JSON before loading it
This commit is contained in:
@@ -303,7 +303,7 @@ paths:
|
||||
/v1/config/get:
|
||||
get:
|
||||
operationId: configGet
|
||||
summary: returns the current configuration.
|
||||
summary: returns the configuration.
|
||||
description: ''
|
||||
responses:
|
||||
'200':
|
||||
@@ -318,7 +318,7 @@ paths:
|
||||
/v1/config/set:
|
||||
post:
|
||||
operationId: configSet
|
||||
summary: changes the current configuration.
|
||||
summary: changes the configuration.
|
||||
description: all fields are optional.
|
||||
requestBody:
|
||||
required: true
|
||||
|
||||
@@ -17,7 +17,7 @@ require (
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
replace github.com/notedit/rtmp => github.com/aler9/rtmp v0.0.0-20210403095203-3be4a5535927
|
||||
|
||||
@@ -100,7 +100,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
+113
-76
@@ -2,6 +2,7 @@ package conf
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -53,98 +54,134 @@ func decrypt(key string, byts []byte) ([]byte, error) {
|
||||
return decrypted, nil
|
||||
}
|
||||
|
||||
// Conf is the main program configuration.
|
||||
func loadFromFile(fpath string, conf *Conf) (bool, error) {
|
||||
// rtsp-simple-server.yml is optional
|
||||
// other configuration files are not
|
||||
if fpath == "rtsp-simple-server.yml" {
|
||||
if _, err := os.Stat(fpath); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
byts, err := ioutil.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
if key, ok := os.LookupEnv("RTSP_CONFKEY"); ok {
|
||||
byts, err = decrypt(key, byts)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
|
||||
// load YAML config into a generic map
|
||||
var temp interface{}
|
||||
err = yaml.Unmarshal(byts, &temp)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
// convert interface{} keys into string keys to avoid JSON errors
|
||||
var convert func(i interface{}) interface{}
|
||||
convert = func(i interface{}) interface{} {
|
||||
switch x := i.(type) {
|
||||
case map[interface{}]interface{}:
|
||||
m2 := map[string]interface{}{}
|
||||
for k, v := range x {
|
||||
m2[k.(string)] = convert(v)
|
||||
}
|
||||
return m2
|
||||
case []interface{}:
|
||||
a2 := make([]interface{}, len(x))
|
||||
for i, v := range x {
|
||||
a2[i] = convert(v)
|
||||
}
|
||||
return a2
|
||||
}
|
||||
return i
|
||||
}
|
||||
temp = convert(temp)
|
||||
|
||||
// convert the generic map into JSON
|
||||
byts, err = json.Marshal(temp)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
// load the configuration from JSON
|
||||
err = json.Unmarshal(byts, conf)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Conf is a configuration.
|
||||
type Conf struct {
|
||||
// general
|
||||
LogLevel string `yaml:"logLevel" json:"logLevel"`
|
||||
LogLevelParsed logger.Level `yaml:"-" json:"-"`
|
||||
LogDestinations []string `yaml:"logDestinations" json:"logDestinations"`
|
||||
LogDestinationsParsed map[logger.Destination]struct{} `yaml:"-" json:"-"`
|
||||
LogFile string `yaml:"logFile" json:"logFile"`
|
||||
ReadTimeout StringDuration `yaml:"readTimeout" json:"readTimeout"`
|
||||
WriteTimeout StringDuration `yaml:"writeTimeout" json:"writeTimeout"`
|
||||
ReadBufferCount int `yaml:"readBufferCount" json:"readBufferCount"`
|
||||
API bool `yaml:"api" json:"api"`
|
||||
APIAddress string `yaml:"apiAddress" json:"apiAddress"`
|
||||
Metrics bool `yaml:"metrics" json:"metrics"`
|
||||
MetricsAddress string `yaml:"metricsAddress" json:"metricsAddress"`
|
||||
PPROF bool `yaml:"pprof" json:"pprof"`
|
||||
PPROFAddress string `yaml:"pprofAddress" json:"pprofAddress"`
|
||||
RunOnConnect string `yaml:"runOnConnect" json:"runOnConnect"`
|
||||
RunOnConnectRestart bool `yaml:"runOnConnectRestart" json:"runOnConnectRestart"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
LogLevelParsed logger.Level `json:"-"`
|
||||
LogDestinations []string `json:"logDestinations"`
|
||||
LogDestinationsParsed map[logger.Destination]struct{} `json:"-"`
|
||||
LogFile string `json:"logFile"`
|
||||
ReadTimeout StringDuration `json:"readTimeout"`
|
||||
WriteTimeout StringDuration `json:"writeTimeout"`
|
||||
ReadBufferCount int `json:"readBufferCount"`
|
||||
API bool `json:"api"`
|
||||
APIAddress string `json:"apiAddress"`
|
||||
Metrics bool `json:"metrics"`
|
||||
MetricsAddress string `json:"metricsAddress"`
|
||||
PPROF bool `json:"pprof"`
|
||||
PPROFAddress string `json:"pprofAddress"`
|
||||
RunOnConnect string `json:"runOnConnect"`
|
||||
RunOnConnectRestart bool `json:"runOnConnectRestart"`
|
||||
|
||||
// rtsp
|
||||
RTSPDisable bool `yaml:"rtspDisable" json:"rtspDisable"`
|
||||
Protocols []string `yaml:"protocols" json:"protocols"`
|
||||
ProtocolsParsed map[Protocol]struct{} `yaml:"-" json:"-"`
|
||||
Encryption string `yaml:"encryption" json:"encryption"`
|
||||
EncryptionParsed Encryption `yaml:"-" json:"-"`
|
||||
RTSPAddress string `yaml:"rtspAddress" json:"rtspAddress"`
|
||||
RTSPSAddress string `yaml:"rtspsAddress" json:"rtspsAddress"`
|
||||
RTPAddress string `yaml:"rtpAddress" json:"rtpAddress"`
|
||||
RTCPAddress string `yaml:"rtcpAddress" json:"rtcpAddress"`
|
||||
MulticastIPRange string `yaml:"multicastIPRange" json:"multicastIPRange"`
|
||||
MulticastRTPPort int `yaml:"multicastRTPPort" json:"multicastRTPPort"`
|
||||
MulticastRTCPPort int `yaml:"multicastRTCPPort" json:"multicastRTCPPort"`
|
||||
ServerKey string `yaml:"serverKey" json:"serverKey"`
|
||||
ServerCert string `yaml:"serverCert" json:"serverCert"`
|
||||
AuthMethods []string `yaml:"authMethods" json:"authMethods"`
|
||||
AuthMethodsParsed []headers.AuthMethod `yaml:"-" json:"-"`
|
||||
ReadBufferSize int `yaml:"readBufferSize" json:"readBufferSize"`
|
||||
// RTSP
|
||||
RTSPDisable bool `json:"rtspDisable"`
|
||||
Protocols []string `json:"protocols"`
|
||||
ProtocolsParsed map[Protocol]struct{} `json:"-"`
|
||||
Encryption string `json:"encryption"`
|
||||
EncryptionParsed Encryption `json:"-"`
|
||||
RTSPAddress string `json:"rtspAddress"`
|
||||
RTSPSAddress string `json:"rtspsAddress"`
|
||||
RTPAddress string `json:"rtpAddress"`
|
||||
RTCPAddress string `json:"rtcpAddress"`
|
||||
MulticastIPRange string `json:"multicastIPRange"`
|
||||
MulticastRTPPort int `json:"multicastRTPPort"`
|
||||
MulticastRTCPPort int `json:"multicastRTCPPort"`
|
||||
ServerKey string `json:"serverKey"`
|
||||
ServerCert string `json:"serverCert"`
|
||||
AuthMethods []string `json:"authMethods"`
|
||||
AuthMethodsParsed []headers.AuthMethod `json:"-"`
|
||||
ReadBufferSize int `json:"readBufferSize"`
|
||||
|
||||
// rtmp
|
||||
RTMPDisable bool `yaml:"rtmpDisable" json:"rtmpDisable"`
|
||||
RTMPAddress string `yaml:"rtmpAddress" json:"rtmpAddress"`
|
||||
// RTMP
|
||||
RTMPDisable bool `json:"rtmpDisable"`
|
||||
RTMPAddress string `json:"rtmpAddress"`
|
||||
|
||||
// hls
|
||||
HLSDisable bool `yaml:"hlsDisable" json:"hlsDisable"`
|
||||
HLSAddress string `yaml:"hlsAddress" json:"hlsAddress"`
|
||||
HLSAlwaysRemux bool `yaml:"hlsAlwaysRemux" json:"hlsAlwaysRemux"`
|
||||
HLSSegmentCount int `yaml:"hlsSegmentCount" json:"hlsSegmentCount"`
|
||||
HLSSegmentDuration StringDuration `yaml:"hlsSegmentDuration" json:"hlsSegmentDuration"`
|
||||
HLSAllowOrigin string `yaml:"hlsAllowOrigin" json:"hlsAllowOrigin"`
|
||||
// HLS
|
||||
HLSDisable bool `json:"hlsDisable"`
|
||||
HLSAddress string `json:"hlsAddress"`
|
||||
HLSAlwaysRemux bool `json:"hlsAlwaysRemux"`
|
||||
HLSSegmentCount int `json:"hlsSegmentCount"`
|
||||
HLSSegmentDuration StringDuration `json:"hlsSegmentDuration"`
|
||||
HLSAllowOrigin string `json:"hlsAllowOrigin"`
|
||||
|
||||
// paths
|
||||
Paths map[string]*PathConf `yaml:"paths" json:"paths"`
|
||||
Paths map[string]*PathConf `json:"paths"`
|
||||
}
|
||||
|
||||
// Load loads a Conf.
|
||||
func Load(fpath string) (*Conf, bool, error) {
|
||||
conf := &Conf{}
|
||||
|
||||
// read from file
|
||||
found, err := func() (bool, error) {
|
||||
// rtsp-simple-server.yml is optional
|
||||
if fpath == "rtsp-simple-server.yml" {
|
||||
if _, err := os.Stat(fpath); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
byts, err := ioutil.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
if key, ok := os.LookupEnv("RTSP_CONFKEY"); ok {
|
||||
byts, err = decrypt(key, byts)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(byts, conf)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}()
|
||||
found, err := loadFromFile(fpath, conf)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// read from environment
|
||||
err = loadFromEnvironment("RTSP", conf)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@@ -158,7 +195,7 @@ func Load(fpath string) (*Conf, bool, error) {
|
||||
return conf, found, nil
|
||||
}
|
||||
|
||||
// CheckAndFillMissing checks the configuration for errors and fill missing fields.
|
||||
// CheckAndFillMissing checks the configuration for errors and fills missing fields.
|
||||
func (conf *Conf) CheckAndFillMissing() error {
|
||||
if conf.LogLevel == "" {
|
||||
conf.LogLevel = "info"
|
||||
|
||||
+34
-34
@@ -43,8 +43,8 @@ func parseIPCidrList(in []string) ([]interface{}, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// CheckPathName checks if a path name is valid.
|
||||
func CheckPathName(name string) error {
|
||||
// IsValidPathName checks if a path name is valid.
|
||||
func IsValidPathName(name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("cannot be empty")
|
||||
}
|
||||
@@ -66,42 +66,42 @@ func CheckPathName(name string) error {
|
||||
|
||||
// PathConf is a path configuration.
|
||||
type PathConf struct {
|
||||
Regexp *regexp.Regexp `yaml:"-" json:"-"`
|
||||
Regexp *regexp.Regexp `json:"-"`
|
||||
|
||||
// source
|
||||
Source string `yaml:"source" json:"source"`
|
||||
SourceProtocol string `yaml:"sourceProtocol" json:"sourceProtocol"`
|
||||
SourceProtocolParsed *gortsplib.ClientProtocol `yaml:"-" json:"-"`
|
||||
SourceAnyPortEnable bool `yaml:"sourceAnyPortEnable" json:"sourceAnyPortEnable"`
|
||||
SourceFingerprint string `yaml:"sourceFingerprint" json:"sourceFingerprint"`
|
||||
SourceOnDemand bool `yaml:"sourceOnDemand" json:"sourceOnDemand"`
|
||||
SourceOnDemandStartTimeout StringDuration `yaml:"sourceOnDemandStartTimeout" json:"sourceOnDemandStartTimeout"` //nolint:lll
|
||||
SourceOnDemandCloseAfter StringDuration `yaml:"sourceOnDemandCloseAfter" json:"sourceOnDemandCloseAfter"`
|
||||
SourceRedirect string `yaml:"sourceRedirect" json:"sourceRedirect"`
|
||||
DisablePublisherOverride bool `yaml:"disablePublisherOverride" json:"disablePublisherOverride"`
|
||||
Fallback string `yaml:"fallback" json:"fallback"`
|
||||
Source string `json:"source"`
|
||||
SourceProtocol string `json:"sourceProtocol"`
|
||||
SourceProtocolParsed *gortsplib.ClientProtocol `json:"-"`
|
||||
SourceAnyPortEnable bool `json:"sourceAnyPortEnable"`
|
||||
SourceFingerprint string `json:"sourceFingerprint"`
|
||||
SourceOnDemand bool `json:"sourceOnDemand"`
|
||||
SourceOnDemandStartTimeout StringDuration `json:"sourceOnDemandStartTimeout"`
|
||||
SourceOnDemandCloseAfter StringDuration `json:"sourceOnDemandCloseAfter"`
|
||||
SourceRedirect string `json:"sourceRedirect"`
|
||||
DisablePublisherOverride bool `json:"disablePublisherOverride"`
|
||||
Fallback string `json:"fallback"`
|
||||
|
||||
// authentication
|
||||
PublishUser string `yaml:"publishUser" json:"publishUser"`
|
||||
PublishPass string `yaml:"publishPass" json:"publishPass"`
|
||||
PublishIPs []string `yaml:"publishIPs" json:"publishIPs"`
|
||||
PublishIPsParsed []interface{} `yaml:"-" json:"-"`
|
||||
ReadUser string `yaml:"readUser" json:"readUser"`
|
||||
ReadPass string `yaml:"readPass" json:"readPass"`
|
||||
ReadIPs []string `yaml:"readIPs" json:"readIPs"`
|
||||
ReadIPsParsed []interface{} `yaml:"-" json:"-"`
|
||||
PublishUser string `json:"publishUser"`
|
||||
PublishPass string `json:"publishPass"`
|
||||
PublishIPs []string `json:"publishIPs"`
|
||||
PublishIPsParsed []interface{} `json:"-"`
|
||||
ReadUser string `json:"readUser"`
|
||||
ReadPass string `json:"readPass"`
|
||||
ReadIPs []string `json:"readIPs"`
|
||||
ReadIPsParsed []interface{} `json:"-"`
|
||||
|
||||
// custom commands
|
||||
RunOnInit string `yaml:"runOnInit" json:"runOnInit"`
|
||||
RunOnInitRestart bool `yaml:"runOnInitRestart" json:"runOnInitRestart"`
|
||||
RunOnDemand string `yaml:"runOnDemand" json:"runOnDemand"`
|
||||
RunOnDemandRestart bool `yaml:"runOnDemandRestart" json:"runOnDemandRestart"`
|
||||
RunOnDemandStartTimeout StringDuration `yaml:"runOnDemandStartTimeout" json:"runOnDemandStartTimeout"`
|
||||
RunOnDemandCloseAfter StringDuration `yaml:"runOnDemandCloseAfter" json:"runOnDemandCloseAfter"`
|
||||
RunOnPublish string `yaml:"runOnPublish" json:"runOnPublish"`
|
||||
RunOnPublishRestart bool `yaml:"runOnPublishRestart" json:"runOnPublishRestart"`
|
||||
RunOnRead string `yaml:"runOnRead" json:"runOnRead"`
|
||||
RunOnReadRestart bool `yaml:"runOnReadRestart" json:"runOnReadRestart"`
|
||||
RunOnInit string `json:"runOnInit"`
|
||||
RunOnInitRestart bool `json:"runOnInitRestart"`
|
||||
RunOnDemand string `json:"runOnDemand"`
|
||||
RunOnDemandRestart bool `json:"runOnDemandRestart"`
|
||||
RunOnDemandStartTimeout StringDuration `json:"runOnDemandStartTimeout"`
|
||||
RunOnDemandCloseAfter StringDuration `json:"runOnDemandCloseAfter"`
|
||||
RunOnPublish string `json:"runOnPublish"`
|
||||
RunOnPublishRestart bool `json:"runOnPublishRestart"`
|
||||
RunOnRead string `json:"runOnRead"`
|
||||
RunOnReadRestart bool `json:"runOnReadRestart"`
|
||||
}
|
||||
|
||||
func (pconf *PathConf) checkAndFillMissing(name string) error {
|
||||
@@ -111,7 +111,7 @@ func (pconf *PathConf) checkAndFillMissing(name string) error {
|
||||
|
||||
// normal path
|
||||
if name[0] != '~' {
|
||||
err := CheckPathName(name)
|
||||
err := IsValidPathName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid path name: %s (%s)", err, name)
|
||||
}
|
||||
@@ -245,7 +245,7 @@ func (pconf *PathConf) checkAndFillMissing(name string) error {
|
||||
|
||||
if pconf.Fallback != "" {
|
||||
if strings.HasPrefix(pconf.Fallback, "/") {
|
||||
err := CheckPathName(pconf.Fallback[1:])
|
||||
err := IsValidPathName(pconf.Fallback[1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("'%s': %s", pconf.Fallback, err)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// StringDuration is a duration that is unmarshaled from a string.
|
||||
@@ -37,29 +35,3 @@ func (d *StringDuration) UnmarshalJSON(b []byte) error {
|
||||
*d = StringDuration(du)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML marshals a StringDuration into a string.
|
||||
func (d StringDuration) MarshalYAML() ([]byte, error) {
|
||||
return yaml.Marshal(time.Duration(d).String())
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshals a StringDuration from a string.
|
||||
func (d *StringDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var v interface{}
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value, ok := v.(string)
|
||||
if !ok {
|
||||
return errors.New("invalid duration")
|
||||
}
|
||||
|
||||
du, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*d = StringDuration(du)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ func (pm *pathManager) createPath(confName string, conf *conf.PathConf, name str
|
||||
}
|
||||
|
||||
func (pm *pathManager) findPathConf(name string) (string, *conf.PathConf, error) {
|
||||
err := conf.CheckPathName(name)
|
||||
err := conf.IsValidPathName(name)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("invalid path name: %s (%s)", err, name)
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ rtspDisable: no
|
||||
protocols: [udp, multicast, tcp]
|
||||
# encrypt handshake and TCP streams with TLS (RTSPS).
|
||||
# available values are "no", "strict", "optional".
|
||||
encryption: no
|
||||
encryption: "no"
|
||||
# address of the TCP/RTSP listener. This is needed only when encryption is "no" or "optional".
|
||||
rtspAddress: :8554
|
||||
# address of the TCP/TLS/RTSPS listener. This is needed only when encryption is "strict" or "optional".
|
||||
|
||||
Reference in New Issue
Block a user