package config import ( "fmt" "os" "path/filepath" "gopkg.in/yaml.v3" ) const DefaultConfigPath = "/etc/unitdore/units.yaml" // Unit represents a single managed container unit. type Unit struct { Name string `yaml:"name"` Runtime string `yaml:"runtime"` // podman | docker | exec User string `yaml:"user,omitempty"` // empty = root/system unit Command string `yaml:"command,omitempty"` // override ExecStart Order int `yaml:"order"` Delay string `yaml:"delay,omitempty"` // e.g. "5s" Enabled bool `yaml:"enabled"` DisabledReason string `yaml:"disabled_reason,omitempty"` Restart bool `yaml:"restart,omitempty"` // enable Restart=on-failure RestartSec int `yaml:"restart_sec,omitempty"` // seconds between restarts RestartRetries int `yaml:"restart_retries,omitempty"` // max restart attempts (StartLimitBurst) } // Config is the root config structure. type Config struct { Units []Unit `yaml:"units"` Prefix string `yaml:"prefix,omitempty"` // prepended to generated service name, e.g. "prod-" Suffix string `yaml:"suffix,omitempty"` // appended to generated service name, e.g. "-svc" } // Load reads and parses the config file at path. func Load(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("reading config: %w", err) } var cfg Config if err := yaml.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("parsing config: %w", err) } return &cfg, nil } // LoadOrEmpty loads the config file, or returns an empty config if it doesn't exist. func LoadOrEmpty(path string) (*Config, error) { _, err := os.Stat(path) if os.IsNotExist(err) { return &Config{}, nil } return Load(path) } // Save writes the config back to disk, creating parent directories if needed. func (c *Config) Save(path string) error { if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return fmt.Errorf("creating config dir: %w", err) } data, err := yaml.Marshal(c) if err != nil { return fmt.Errorf("marshalling config: %w", err) } if err := os.WriteFile(path, data, 0644); err != nil { return fmt.Errorf("writing config: %w", err) } return nil } // FindUnit returns a pointer to the unit with the given name, or nil. func (c *Config) FindUnit(name string) *Unit { for i := range c.Units { if c.Units[i].Name == name { return &c.Units[i] } } return nil } // AddUnit appends a unit if one with that name doesn't already exist. // Returns true if it was added. func (c *Config) AddUnit(u Unit) bool { if c.FindUnit(u.Name) != nil { return false } c.Units = append(c.Units, u) return true }