package config import ( "bytes" "log" "os" "path" "path/filepath" "strings" "sync" "text/template" "github.com/autobrr/autobrr/internal/api" "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/pkg/errors" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) var configTemplate = `# config.toml # Hostname / IP # # Default: "localhost" # host = "{{ .host }}" # Port # # Default: 7474 # port = 7474 # Base url # Set custom baseUrl eg /autobrr/ to serve in subdirectory. # Not needed for subdomain, or by accessing with the :port directly. # # Optional # #baseUrl = "/autobrr/" # autobrr logs file # If not defined, logs to stdout # # Optional # #logPath = "log/autobrr.log" # Log level # # Default: "DEBUG" # # Options: "ERROR", "DEBUG", "INFO", "WARN", "TRACE" # logLevel = "DEBUG" # Session secret # sessionSecret = "{{ .sessionSecret }}" ` func writeConfig(configPath string, configFile string) error { cfgPath := filepath.Join(configPath, configFile) // check if configPath exists, if not create it if _, err := os.Stat(configPath); errors.Is(err, os.ErrNotExist) { err := os.MkdirAll(configPath, os.ModePerm) if err != nil { log.Println(err) return err } } // check if config exists, if not create it if _, err := os.Stat(cfgPath); errors.Is(err, os.ErrNotExist) { // set default host host := "127.0.0.1" if _, err := os.Stat("/.dockerenv"); err == nil { // docker creates a .dockerenv file at the root // of the directory tree inside the container. // if this file exists then the viewer is running // from inside a container so return true host = "0.0.0.0" } else if pd, _ := os.Open("/proc/1/cgroup"); pd != nil { defer pd.Close() b := make([]byte, 4096, 4096) pd.Read(b) if strings.Contains(string(b), "/docker") || strings.Contains(string(b), "/lxc") { host = "0.0.0.0" } } f, err := os.Create(cfgPath) if err != nil { // perm 0666 // handle failed create log.Printf("error creating file: %q", err) return err } defer f.Close() // generate default sessionSecret sessionSecret := api.GenerateSecureToken(16) // setup text template to inject variables into tmpl, err := template.New("config").Parse(configTemplate) if err != nil { return errors.Wrap(err, "could not create config template") } tmplVars := map[string]string{ "host": host, "sessionSecret": sessionSecret, } var buffer bytes.Buffer if err = tmpl.Execute(&buffer, &tmplVars); err != nil { return errors.Wrap(err, "could not write torrent url template output") } if _, err = f.WriteString(buffer.String()); err != nil { log.Printf("error writing contents to file: %v %q", configPath, err) return err } return f.Sync() } return nil } type Config interface { DynamicReload(log logger.Logger) } type AppConfig struct { Config *domain.Config m sync.Mutex } func New(configPath string, version string) *AppConfig { c := &AppConfig{} c.defaults() c.Config.Version = version c.Config.ConfigPath = configPath c.load(configPath) return c } func (c *AppConfig) defaults() { c.Config = &domain.Config{ Version: "dev", Host: "localhost", Port: 7474, LogLevel: "TRACE", LogPath: "", BaseURL: "/", SessionSecret: "secret-session-key", CustomDefinitions: "", DatabaseType: "sqlite", PostgresHost: "", PostgresPort: 0, PostgresDatabase: "", PostgresUser: "", PostgresPass: "", } } func (c *AppConfig) load(configPath string) { // or use viper.SetDefault(val, def) //viper.SetDefault("host", config.Host) //viper.SetDefault("port", config.Port) //viper.SetDefault("logLevel", config.LogLevel) //viper.SetDefault("logPath", config.LogPath) viper.SetConfigType("toml") // clean trailing slash from configPath configPath = path.Clean(configPath) if configPath != "" { //viper.SetConfigName("config") // check if path and file exists // if not, create path and file if err := writeConfig(configPath, "config.toml"); err != nil { log.Printf("write error: %q", err) } viper.SetConfigFile(path.Join(configPath, "config.toml")) } else { viper.SetConfigName("config") // Search config in directories viper.AddConfigPath(".") viper.AddConfigPath("$HOME/.config/autobrr") viper.AddConfigPath("$HOME/.autobrr") } // read config if err := viper.ReadInConfig(); err != nil { log.Printf("config read error: %q", err) } if err := viper.Unmarshal(&c.Config); err != nil { log.Fatalf("Could not unmarshal config file: %v", viper.ConfigFileUsed()) } } func (c *AppConfig) DynamicReload(log logger.Logger) { viper.OnConfigChange(func(e fsnotify.Event) { c.m.Lock() logLevel := viper.GetString("logLevel") c.Config.LogLevel = logLevel log.SetLogLevel(c.Config.LogLevel) logPath := viper.GetString("logPath") c.Config.LogPath = logPath log.Debug().Msg("config file reloaded!") c.m.Unlock() }) viper.WatchConfig() return }