mirror of
https://github.com/screego/server.git
synced 2026-04-22 15:37:21 +08:00
Add config
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/screego/server/config/mode"
|
||||
)
|
||||
|
||||
var (
|
||||
prefix = "screego"
|
||||
files = []string{"screego.config.development.local", "screego.config.development", "screego.config.local", "screego.config"}
|
||||
absoluteFiles = []string{"/etc/screego/server.config"}
|
||||
osExecutable = os.Executable
|
||||
osStat = os.Stat
|
||||
)
|
||||
|
||||
const (
|
||||
AuthModeTurn = "turn"
|
||||
AuthModeAll = "all"
|
||||
AuthModeNone = "none"
|
||||
)
|
||||
|
||||
// Config represents the application configuration.
|
||||
type Config struct {
|
||||
LogLevel LogLevel `default:"info" split_words:"true"`
|
||||
|
||||
ExternalIP string `split_words:"true"`
|
||||
|
||||
TLSCertFile string `split_words:"true"`
|
||||
TLSKeyFile string `split_words:"true"`
|
||||
|
||||
ServerTLS bool `split_words:"true"`
|
||||
ServerAddress string `default:"0.0.0.0:5050" split_words:"true"`
|
||||
Secret []byte `split_words:"true"`
|
||||
|
||||
TurnAddress string `default:"0.0.0.0:3478" required:"true" split_words:"true"`
|
||||
TurnStrictAuth bool `default:"true" split_words:"true"`
|
||||
|
||||
TrustProxyHeaders bool `split_words:"true"`
|
||||
AuthMode string `default:"turn" split_words:"true"`
|
||||
CorsAllowedOrigins []string `split_words:"true"`
|
||||
UsersFile string `split_words:"true"`
|
||||
|
||||
CheckOrigin func(string) bool `ignored:"true" json:"-"`
|
||||
}
|
||||
|
||||
// Get loads the application config.
|
||||
func Get() (Config, []FutureLog) {
|
||||
var logs []FutureLog
|
||||
dir, log := getExecutableOrWorkDir()
|
||||
if log != nil {
|
||||
logs = append(logs, *log)
|
||||
}
|
||||
|
||||
for _, file := range getFiles(dir) {
|
||||
_, fileErr := osStat(file)
|
||||
if fileErr == nil {
|
||||
if err := godotenv.Load(file); err != nil {
|
||||
logs = append(logs, futureFatal(fmt.Sprintf("cannot load file %s: %s", file, err)))
|
||||
} else {
|
||||
logs = append(logs, FutureLog{
|
||||
Level: zerolog.DebugLevel,
|
||||
Msg: fmt.Sprintf("Loading file %s", file)})
|
||||
}
|
||||
} else if os.IsNotExist(fileErr) {
|
||||
continue
|
||||
} else {
|
||||
logs = append(logs, FutureLog{
|
||||
Level: zerolog.WarnLevel,
|
||||
Msg: fmt.Sprintf("cannot read file %s because %s", file, fileErr)})
|
||||
}
|
||||
}
|
||||
|
||||
config := Config{}
|
||||
err := envconfig.Process(prefix, &config)
|
||||
if err != nil {
|
||||
logs = append(logs,
|
||||
futureFatal(fmt.Sprintf("cannot parse env params: %s", err)))
|
||||
}
|
||||
|
||||
if config.AuthMode != AuthModeTurn && config.AuthMode != AuthModeAll && config.AuthMode != AuthModeNone {
|
||||
logs = append(logs,
|
||||
futureFatal(fmt.Sprintf("invalid SCREEGO_AUTH_MODE: %s", config.AuthMode)))
|
||||
}
|
||||
|
||||
if config.ExternalIP == "" {
|
||||
logs = append(logs, futureFatal("SCREEGO_EXTERNAL_IP must be set"))
|
||||
}
|
||||
|
||||
if config.ServerTLS {
|
||||
if config.TLSCertFile == "" {
|
||||
logs = append(logs, futureFatal("SCREEGO_TLS_CERT_FILE must be set if TLS is enabled"))
|
||||
}
|
||||
|
||||
if config.TLSKeyFile == "" {
|
||||
logs = append(logs, futureFatal("SCREEGO_TLS_KEY_FILE must be set if TLS is enabled"))
|
||||
}
|
||||
}
|
||||
|
||||
var compiledAllowedOrigins []*regexp.Regexp
|
||||
for _, origin := range config.CorsAllowedOrigins {
|
||||
compiled, err := regexp.Compile(origin)
|
||||
if err != nil {
|
||||
logs = append(logs, futureFatal(fmt.Sprintf("invalid regex: %s", err)))
|
||||
}
|
||||
compiledAllowedOrigins = append(compiledAllowedOrigins, compiled)
|
||||
}
|
||||
|
||||
config.CheckOrigin = func(origin string) bool {
|
||||
if origin == "" {
|
||||
return true
|
||||
}
|
||||
for _, compiledOrigin := range compiledAllowedOrigins {
|
||||
if compiledOrigin.Match([]byte(strings.ToLower(origin))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if len(config.Secret) == 0 {
|
||||
config.Secret = make([]byte, 32)
|
||||
if _, err := rand.Read(config.Secret); err == nil {
|
||||
logs = append(logs, FutureLog{
|
||||
Level: zerolog.InfoLevel,
|
||||
Msg: "SCREEGO_SECRET unset, user logins will be invalidated on restart"})
|
||||
} else {
|
||||
logs = append(logs, futureFatal(fmt.Sprintf("cannot create secret %s", err)))
|
||||
}
|
||||
}
|
||||
|
||||
return config, logs
|
||||
}
|
||||
|
||||
func getExecutableOrWorkDir() (string, *FutureLog) {
|
||||
dir, err := getExecutableDir()
|
||||
// when using `go run main.go` the executable lives in th temp directory therefore the env.development
|
||||
// will not be read, this enforces that the current work directory is used in dev mode.
|
||||
if err != nil || mode.Get() == mode.Dev {
|
||||
return filepath.Dir("."), err
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func getExecutableDir() (string, *FutureLog) {
|
||||
ex, err := osExecutable()
|
||||
if err != nil {
|
||||
return "", &FutureLog{
|
||||
Level: zerolog.ErrorLevel,
|
||||
Msg: "Could not get path of executable using working directory instead. " + err.Error()}
|
||||
}
|
||||
return filepath.Dir(ex), nil
|
||||
}
|
||||
|
||||
func getFiles(relativeTo string) []string {
|
||||
var result []string
|
||||
for _, file := range files {
|
||||
result = append(result, filepath.Join(relativeTo, file))
|
||||
}
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil {
|
||||
result = append(result, filepath.Join(homeDir, ".config/screego/server.config"))
|
||||
}
|
||||
result = append(result, absoluteFiles...)
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package config
|
||||
|
||||
import "github.com/rs/zerolog"
|
||||
|
||||
// FutureLog is an intermediate type for log messages. It is used before the config was loaded because without loaded
|
||||
// config we do not know the log level, so we log these messages once the config was initialized.
|
||||
type FutureLog struct {
|
||||
Level zerolog.Level
|
||||
Msg string
|
||||
}
|
||||
|
||||
func futureFatal(msg string) FutureLog {
|
||||
return FutureLog{
|
||||
Level: zerolog.FatalLevel,
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// LogLevel type that provides helper methods for decoding.
|
||||
type LogLevel zerolog.Level
|
||||
|
||||
// Decode decodes a string to a log level.
|
||||
func (ll *LogLevel) Decode(value string) error {
|
||||
if level, err := zerolog.ParseLevel(value); err == nil {
|
||||
*ll = LogLevel(level)
|
||||
return nil
|
||||
}
|
||||
*ll = LogLevel(zerolog.InfoLevel)
|
||||
return errors.New("unknown log level")
|
||||
}
|
||||
|
||||
// AsZeroLogLevel converts the LogLevel to a zerolog.Level.
|
||||
func (ll LogLevel) AsZeroLogLevel() zerolog.Level {
|
||||
return zerolog.Level(ll)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLogLevel_Decode_success(t *testing.T) {
|
||||
ll := new(LogLevel)
|
||||
err := ll.Decode("fatal")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ll.AsZeroLogLevel(), zerolog.FatalLevel)
|
||||
}
|
||||
|
||||
func TestLogLevel_Decode_fail(t *testing.T) {
|
||||
ll := new(LogLevel)
|
||||
err := ll.Decode("asdasdasdasdasdasd")
|
||||
assert.EqualError(t, err, "unknown log level")
|
||||
assert.Equal(t, ll.AsZeroLogLevel(), zerolog.InfoLevel)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package mode
|
||||
|
||||
const (
|
||||
// Dev for development mode
|
||||
Dev = "dev"
|
||||
// Prod for production mode
|
||||
Prod = "prod"
|
||||
)
|
||||
|
||||
var mode = Dev
|
||||
|
||||
// Set sets the new mode.
|
||||
func Set(newMode string) {
|
||||
mode = newMode
|
||||
}
|
||||
|
||||
// Get returns the current mode.
|
||||
func Get() string {
|
||||
return mode
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mode
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/magiconair/properties/assert"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
mode = Prod
|
||||
assert.Equal(t, Prod, Get())
|
||||
}
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
Set(Prod)
|
||||
assert.Equal(t, Prod, mode)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
SCREEGO_EXTERNAL_IP=127.0.0.1
|
||||
SCREEGO_LOG_LEVEL=debug
|
||||
SCREEGO_CORS_ALLOWED_ORIGINS=http://localhost:3000
|
||||
SCREEGO_USERS_FILE=./users
|
||||
@@ -0,0 +1,52 @@
|
||||
# The external ip of the server
|
||||
SCREEGO_EXTERNAL_IP=
|
||||
|
||||
# A secret which should be unique. Is used for cookie authentication.
|
||||
SCREEGO_SECRET=
|
||||
|
||||
# If TLS should be enabled for HTTP requests. Screego requires TLS,
|
||||
# you either have to enable this setting or serve TLS via a reverse proxy.
|
||||
SCREEGO_SERVER_TLS=false
|
||||
# The TLS cert file (only needed if TLS is enabled)
|
||||
SCREEGO_TLS_CERT_FILE=
|
||||
# The TLS key file (only needed if TLS is enabled)
|
||||
SCREEGO_TLS_KEY_FILE=
|
||||
|
||||
# The address the http server will listen on.
|
||||
SCREEGO_SERVER_ADDRESS=0.0.0.0:5050
|
||||
|
||||
# The address the TURN server will listen on.
|
||||
SCREEGO_TURN_ADDRESS=0.0.0.0:3478
|
||||
|
||||
# If reverse proxy headers should be trusted.
|
||||
# Screego uses ip whitelisting for authentication
|
||||
# of TURN connections. When behind a proxy the ip is always the proxy server.
|
||||
# To still allow whitelisting this setting must be enabled and
|
||||
# the `X-Real-Ip` header must be set by the reverse proxy.
|
||||
SCREEGO_TRUST_PROXY_HEADERS=false
|
||||
|
||||
# Defines when a user login is required
|
||||
# Possible values:
|
||||
# all: User login is always required
|
||||
# turn: User login is required for TURN connections
|
||||
# none: User login is never required
|
||||
SCREEGO_AUTH_MODE=turn
|
||||
|
||||
# Defines origins that will be allowed to access Screego (HTTP + WebSocket)
|
||||
# Example Value: https://screego.net,https://sub.gotify.net
|
||||
SCREEGO_CORS_ALLOWED_ORIGINS=
|
||||
|
||||
# Defines the location of the users file.
|
||||
# File Format:
|
||||
# user1:bcrypt_password_hash
|
||||
# user2:bcrypt_password_hash
|
||||
#
|
||||
# Example:
|
||||
# user1:$2a$12$WEfYCnWGk0PDzbATLTNiTuoZ7e/43v6DM/h7arOnPU6qEtFG.kZQy
|
||||
#
|
||||
# The user password pair can be created via
|
||||
# screego hash --name "user1" --pass "your password"
|
||||
SCREEGO_USERS_FILE=
|
||||
|
||||
# The loglevel (one of: debug, info, warn, error)
|
||||
SCREEGO_LOG_LEVEL=info
|
||||
Reference in New Issue
Block a user