187 lines
3.6 KiB
Go
187 lines
3.6 KiB
Go
package shell
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/AlexxIT/go2rtc/pkg/yaml"
|
|
)
|
|
|
|
var (
|
|
secretReplacer *strings.Replacer
|
|
secretValues map[string]bool
|
|
secretMutex sync.RWMutex
|
|
)
|
|
|
|
func QuoteSplit(s string) []string {
|
|
var a []string
|
|
|
|
for len(s) > 0 {
|
|
switch c := s[0]; c {
|
|
case '\t', '\n', '\r', ' ': // unicode.IsSpace
|
|
s = s[1:]
|
|
case '"', '\'': // quote chars
|
|
if i := strings.IndexByte(s[1:], c); i > 0 {
|
|
a = append(a, s[1:i+1])
|
|
s = s[i+2:]
|
|
} else {
|
|
return nil // error
|
|
}
|
|
default:
|
|
i := strings.IndexAny(s, "\t\n\r ")
|
|
if i > 0 {
|
|
a = append(a, s[:i])
|
|
s = s[i:]
|
|
} else {
|
|
a = append(a, s)
|
|
s = ""
|
|
}
|
|
}
|
|
}
|
|
|
|
return a
|
|
}
|
|
|
|
// ReplaceEnvVars - support format ${CAMERA_PASSWORD} and ${RTSP_USER:admin}
|
|
func ReplaceEnvVars(text string) string {
|
|
var cfg struct {
|
|
Env map[string]string `yaml:"env"`
|
|
Secrets map[string]map[string]string `yaml:"secrets"`
|
|
}
|
|
|
|
yaml.Unmarshal([]byte(text), &cfg)
|
|
|
|
buildSecretReplacer(cfg)
|
|
|
|
re := regexp.MustCompile(`\${([^}{]+)}`)
|
|
return re.ReplaceAllStringFunc(text, func(match string) string {
|
|
key := match[2 : len(match)-1]
|
|
|
|
var def string
|
|
var dok bool
|
|
|
|
if i := strings.IndexByte(key, ':'); i > 0 {
|
|
key, def = key[:i], key[i+1:]
|
|
dok = true
|
|
}
|
|
|
|
if dir, vok := os.LookupEnv("CREDENTIALS_DIRECTORY"); vok {
|
|
value, err := os.ReadFile(filepath.Join(dir, key))
|
|
if err == nil {
|
|
return strings.TrimSpace(string(value))
|
|
}
|
|
}
|
|
|
|
if value, vok := os.LookupEnv(key); vok {
|
|
return value
|
|
}
|
|
|
|
if cfg.Env != nil {
|
|
if value, ok := cfg.Env[key]; ok {
|
|
return value
|
|
}
|
|
}
|
|
|
|
if cfg.Secrets != nil {
|
|
for secretName, secretValues := range cfg.Secrets {
|
|
for k, v := range secretValues {
|
|
name := fmt.Sprintf("%s_%s", secretName, k)
|
|
if key == name {
|
|
return v
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if dok {
|
|
return def
|
|
}
|
|
|
|
return match
|
|
})
|
|
}
|
|
|
|
func RunUntilSignal() {
|
|
sigs := make(chan os.Signal, 1)
|
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
|
println("exit with signal:", (<-sigs).String())
|
|
}
|
|
|
|
func Redact(text string) string {
|
|
secretMutex.RLock()
|
|
defer secretMutex.RUnlock()
|
|
|
|
if secretReplacer == nil {
|
|
return text
|
|
}
|
|
|
|
return secretReplacer.Replace(text)
|
|
}
|
|
|
|
func buildSecretReplacer(cfg struct {
|
|
Env map[string]string `yaml:"env"`
|
|
Secrets map[string]map[string]string `yaml:"secrets"`
|
|
}) {
|
|
secretMutex.Lock()
|
|
defer secretMutex.Unlock()
|
|
|
|
if secretValues == nil {
|
|
secretValues = make(map[string]bool)
|
|
}
|
|
|
|
var newSecrets []string
|
|
|
|
if dir, ok := os.LookupEnv("CREDENTIALS_DIRECTORY"); ok {
|
|
entries, err := os.ReadDir(dir)
|
|
if err == nil {
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
value, err := os.ReadFile(filepath.Join(dir, entry.Name()))
|
|
if err == nil {
|
|
cleanValue := strings.TrimSpace(string(value))
|
|
if len(cleanValue) > 0 && !secretValues[cleanValue] {
|
|
secretValues[cleanValue] = true
|
|
newSecrets = append(newSecrets, cleanValue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if cfg.Secrets != nil {
|
|
for _, secretMap := range cfg.Secrets {
|
|
for _, value := range secretMap {
|
|
if len(value) > 0 && !secretValues[value] {
|
|
secretValues[value] = true
|
|
newSecrets = append(newSecrets, value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(newSecrets) > 0 {
|
|
rebuildReplacer()
|
|
}
|
|
}
|
|
|
|
func rebuildReplacer() {
|
|
var replacements []string
|
|
|
|
for secret := range secretValues {
|
|
replacements = append(replacements, secret, "*****")
|
|
}
|
|
|
|
if len(replacements) > 0 {
|
|
secretReplacer = strings.NewReplacer(replacements...)
|
|
} else {
|
|
secretReplacer = nil
|
|
}
|
|
}
|