package app import ( "errors" "flag" "fmt" "os" "os/exec" "path/filepath" "runtime" "runtime/debug" "strings" "github.com/AlexxIT/go2rtc/pkg/shell" "github.com/AlexxIT/go2rtc/pkg/yaml" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) var Version = "1.9.2" var UserAgent = "go2rtc/" + Version var ConfigPath string var Info = map[string]any{ "version": Version, } const usage = `Usage of go2rtc: -c, --config Path to config file or config string as YAML or JSON, support multiple -d, --daemon Run in background -v, --version Print version and exit ` func Init() { var confs Config var daemon bool var version bool flag.Var(&confs, "config", "") flag.Var(&confs, "c", "") flag.BoolVar(&daemon, "daemon", false, "") flag.BoolVar(&daemon, "d", false, "") flag.BoolVar(&version, "version", false, "") flag.BoolVar(&version, "v", false, "") flag.Usage = func() { fmt.Print(usage) } flag.Parse() revision, vcsTime := readRevisionTime() if version { fmt.Printf("go2rtc version %s (%s) %s/%s\n", Version, revision, runtime.GOOS, runtime.GOARCH) os.Exit(0) } if daemon { if runtime.GOOS == "windows" { fmt.Println("Daemon not supported on Windows") os.Exit(1) } args := os.Args[1:] for i, arg := range args { if arg == "-daemon" { args[i] = "" } } // Re-run the program in background and exit cmd := exec.Command(os.Args[0], args...) if err := cmd.Start(); err != nil { log.Fatal().Err(err).Send() } fmt.Println("Running in daemon mode with PID:", cmd.Process.Pid) os.Exit(0) } if confs == nil { confs = []string{"go2rtc.yaml"} } for _, conf := range confs { if len(conf) == 0 { continue } if conf[0] == '{' { // config as raw YAML or JSON configs = append(configs, []byte(conf)) } else if data := parseConfString(conf); data != nil { configs = append(configs, data) } else { // config as file if ConfigPath == "" { ConfigPath = conf } if data, _ = os.ReadFile(conf); data == nil { continue } data = []byte(shell.ReplaceEnvVars(string(data))) configs = append(configs, data) } } if ConfigPath != "" { if !filepath.IsAbs(ConfigPath) { if cwd, err := os.Getwd(); err == nil { ConfigPath = filepath.Join(cwd, ConfigPath) } } Info["config_path"] = ConfigPath } Info["revision"] = revision var cfg struct { Mod map[string]string `yaml:"log"` } cfg.Mod = map[string]string{ "format": "", // useless, but anyway "level": "info", "output": "stdout", // TODO: change to stderr someday "time": zerolog.TimeFormatUnixMs, } LoadConfig(&cfg) log.Logger = NewLogger(cfg.Mod) modules = cfg.Mod platform := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH) log.Info().Str("version", Version).Str("platform", platform).Str("revision", revision).Msg("go2rtc") log.Debug().Str("version", runtime.Version()).Str("vcs.time", vcsTime).Msg("build") if ConfigPath != "" { log.Info().Str("path", ConfigPath).Msg("config") } migrateStore() } func LoadConfig(v any) { for _, data := range configs { if err := yaml.Unmarshal(data, v); err != nil { log.Warn().Err(err).Msg("[app] read config") } } } func PatchConfig(key string, value any, path ...string) error { if ConfigPath == "" { return errors.New("config file disabled") } // empty config is OK b, _ := os.ReadFile(ConfigPath) b, err := yaml.Patch(b, key, value, path...) if err != nil { return err } return os.WriteFile(ConfigPath, b, 0644) } // internal type Config []string func (c *Config) String() string { return strings.Join(*c, " ") } func (c *Config) Set(value string) error { *c = append(*c, value) return nil } var configs [][]byte func readRevisionTime() (revision, vcsTime string) { if info, ok := debug.ReadBuildInfo(); ok { for _, setting := range info.Settings { switch setting.Key { case "vcs.revision": if len(setting.Value) > 7 { revision = setting.Value[:7] } else { revision = setting.Value } case "vcs.time": vcsTime = setting.Value case "vcs.modified": if setting.Value == "true" { revision = "mod." + revision } } } } return } func parseConfString(s string) []byte { i := strings.IndexByte(s, '=') if i < 0 { return nil } items := strings.Split(s[:i], ".") if len(items) < 2 { return nil } // `log.level=trace` => `{log: {level: trace}}` var pre string var suf = s[i+1:] for _, item := range items { pre += "{" + item + ": " suf += "}" } return []byte(pre + suf) }