// Package config — загрузка конфигурации Ratatoskr-go. // // Источники (каждый следующий имеет приоритет над предыдущим): // 1. Файл config.yaml (с подстановкой ${VAR:-default}) // 2. Переменные окружения (env-теги в структуре) // // Порядок: Load("config.yaml") → YAML → os.Expand → env override → Validate. package config import ( "errors" "fmt" "time" ) // Duration — time.Duration с YAML-сериализацией (строка "5s", "10m"). type Duration time.Duration func (d Duration) Duration() time.Duration { return time.Duration(d) } func (d Duration) String() string { return time.Duration(d).String() } func (d *Duration) UnmarshalYAML(unmarshal func(any) error) error { var s string if err := unmarshal(&s); err != nil { return fmt.Errorf("%w: %v", ErrInvalidFormat, err) } td, err := time.ParseDuration(s) if err != nil { return fmt.Errorf("%w: parse duration %q: %v", ErrInvalidFormat, s, err) } *d = Duration(td) return nil } // Config — полная конфигурация бинаря. type Config struct { Telegram TelegramCfg `yaml:"telegram"` Gitea GiteaCfg `yaml:"gitea"` OpenCode OpenCodeCfg `yaml:"opencode"` Chat ChatCfg `yaml:"chat"` Paths PathsCfg `yaml:"paths"` } type TelegramCfg struct { Token string `yaml:"token" env:"TG_TOKEN"` ChatID string `yaml:"chat_id" env:"TG_CHAT_ID"` } type GiteaCfg struct { URL string `yaml:"url" env:"GITEA_URL"` Token string `yaml:"token" env:"GITEA_TOKEN"` User string `yaml:"user" env:"GITEA_USER"` } type OpenCodeCfg struct { Bin string `yaml:"bin" default:"opencode"` DBPath string `yaml:"db_path" default:""` Config string `yaml:"config" env:"OPENCODE_CONFIG"` HardTimeout Duration `yaml:"hard_timeout" default:"20m"` IdleTimeout Duration `yaml:"idle_timeout" default:"2m"` PollMs Duration `yaml:"poll_ms" default:"2s"` } type ChatCfg struct { PollInterval Duration `yaml:"poll_interval" default:"30s"` } type PathsCfg struct { Worktree string `yaml:"worktree" default:"/opt/data/ratatoskr/worktrees"` } // Validate проверяет обязательные поля. Возвращает C1 MissingField // (склеенную, все ошибки сразу) или C2 InvalidFormat. func (c *Config) Validate() error { var errs []error if c.Telegram.Token == "" { errs = append(errs, fmt.Errorf("%w: telegram.token", ErrMissingField)) } if c.Telegram.ChatID == "" { errs = append(errs, fmt.Errorf("%w: telegram.chat_id", ErrMissingField)) } if c.Gitea.URL == "" { errs = append(errs, fmt.Errorf("%w: gitea.url", ErrMissingField)) } if c.Gitea.Token == "" { errs = append(errs, fmt.Errorf("%w: gitea.token", ErrMissingField)) } return errors.Join(errs...) }