131 lines
3.0 KiB
Go
131 lines
3.0 KiB
Go
package config
|
|
|
|
import (
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"git.netflux.io/rob/octoplex/internal/domain"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
//go:embed data/config.example.yml
|
|
var exampleConfig []byte
|
|
|
|
// Service provides configuration services.
|
|
type Service struct {
|
|
userConfigDir string
|
|
appConfigDir string
|
|
appStateDir string
|
|
}
|
|
|
|
// ConfigDirFunc is a function that returns the user configuration directory.
|
|
type ConfigDirFunc func() (string, error)
|
|
|
|
// NewDefaultService creates a new service with the default configuration file
|
|
// location.
|
|
func NewDefaultService() (*Service, error) {
|
|
return NewService(os.UserConfigDir)
|
|
}
|
|
|
|
// NewService creates a new service with provided ConfigDirFunc.
|
|
//
|
|
// The app data directories (config and state) are created if they do not
|
|
// exist.
|
|
func NewService(configDirFunc ConfigDirFunc) (*Service, error) {
|
|
configDir, err := configDirFunc()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("user config dir: %w", err)
|
|
}
|
|
|
|
appConfigDir, err := createAppConfigDir(configDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("app config dir: %w", err)
|
|
}
|
|
|
|
appStateDir, err := createAppStateDir()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("app state dir: %w", err)
|
|
}
|
|
|
|
return &Service{
|
|
userConfigDir: configDir,
|
|
appConfigDir: appConfigDir,
|
|
appStateDir: appStateDir,
|
|
}, nil
|
|
}
|
|
|
|
// ReadOrCreateConfig reads the configuration from the file at the given path or
|
|
// creates it with default values.
|
|
func (s *Service) ReadOrCreateConfig() (cfg Config, _ error) {
|
|
if _, err := os.Stat(s.Path()); os.IsNotExist(err) {
|
|
return s.createConfig()
|
|
} else if err != nil {
|
|
return cfg, fmt.Errorf("stat: %w", err)
|
|
}
|
|
|
|
return s.readConfig()
|
|
}
|
|
|
|
func (s *Service) readConfig() (cfg Config, _ error) {
|
|
contents, err := os.ReadFile(s.Path())
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("read file: %w", err)
|
|
}
|
|
|
|
if err = yaml.Unmarshal(contents, &cfg); err != nil {
|
|
return cfg, fmt.Errorf("unmarshal: %w", err)
|
|
}
|
|
|
|
s.setDefaults(&cfg)
|
|
|
|
if err = validate(cfg); err != nil {
|
|
return cfg, err
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func (s *Service) createConfig() (Config, error) {
|
|
if err := os.MkdirAll(s.appConfigDir, 0744); err != nil {
|
|
return Config{}, fmt.Errorf("mkdir: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(s.Path(), exampleConfig, 0644); err != nil {
|
|
return Config{}, fmt.Errorf("write file: %w", err)
|
|
}
|
|
|
|
return Config{}, nil
|
|
}
|
|
|
|
func (s *Service) Path() string {
|
|
return filepath.Join(s.appConfigDir, "config.yaml")
|
|
}
|
|
|
|
func (s *Service) setDefaults(cfg *Config) {
|
|
if cfg.LogFile.Enabled && cfg.LogFile.Path == "" {
|
|
cfg.LogFile.Path = filepath.Join(s.appStateDir, domain.AppName+".log")
|
|
}
|
|
|
|
for i := range cfg.Destinations {
|
|
if strings.TrimSpace(cfg.Destinations[i].Name) == "" {
|
|
cfg.Destinations[i].Name = fmt.Sprintf("Stream %d", i+1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func validate(cfg Config) error {
|
|
var err error
|
|
|
|
for _, dest := range cfg.Destinations {
|
|
if !strings.HasPrefix(dest.URL, "rtmp://") {
|
|
err = errors.Join(err, fmt.Errorf("destination URL must start with rtmp://"))
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|