Files
ratatoskr-go/internal/config/types.go
Hermes ad9c2dd522
All checks were successful
CI / test (push) Successful in 41s
CI / build-and-package (amd64, linux) (push) Successful in 38s
CI / build-and-package (amd64, windows) (push) Successful in 35s
feat(config): уровень логирования log.level (info|debug), по умолчанию info
Отладочные логи API-вызовов к opencode serve теперь включаются только при
log.level=debug (гейт через Client.Debug <- Runner.Debug <- cfg.Log.Debug()).
По умолчанию 'info' — отладочных логов нет.

- config: добавлен LogCfg{Level}, дефолт 'info', валидация (info|debug)
- opencode: Client.Debug и Runner.Debug — гейттят логи do()
- app: Runner.Debug из cfg.Log.Debug()
- config.yaml.example: задокументирован log.level
- тесты: дефолт=info, level=debug, невалидный уровень
2026-08-18 18:44:37 +05:00

134 lines
5.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"
"strings"
"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"`
OpenCode OpenCodeCfg `yaml:"opencode"`
Git GitCfg `yaml:"git"`
Chat ChatCfg `yaml:"chat"`
Paths PathsCfg `yaml:"paths"`
Update UpdateCfg `yaml:"update"`
Log LogCfg `yaml:"log"`
}
// LogCfg — уровень логирования. Level: "info" (по умолчанию) или "debug".
// debug включает отладочные логи (напр. все API-вызовы к opencode serve).
type LogCfg struct {
Level string `yaml:"level" default:"info"`
}
// Debug возвращает true, если включён отладочный уровень логирования.
func (l LogCfg) Debug() bool { return strings.EqualFold(l.Level, "debug") }
// GitCfg — источник репозиториев (для git clone).
type GitCfg struct {
BaseURL string `yaml:"base_url" env:"GIT_BASE_URL"`
Token string `yaml:"token" env:"GIT_TOKEN"`
}
// UpdateCfg — автообновление.
// Enabled=false отключает и авто-проверку, и /update.
// BaseURL — хост Gitea для обновления (обязателен: без него update отключён,
// фоллбэк на git.base_url НЕ используется).
// Token — токен read:package. Приоритет: вшитый -X main.updateToken > этот.
// Package — имя пакета-хранилища (по умолчанию ratatoskr). Владелец пакета —
// жёсткая константа (packageOwner в app).
// CheckInterval — как часто авто-проверять наличие обновления
// (по умолчанию 24h). Авто-режим ТОЛЬКО уведомляет; замена — по /update.
type UpdateCfg struct {
Enabled bool `yaml:"enabled" default:"false"`
BaseURL string `yaml:"base_url" env:"UPDATE_BASE_URL"`
Token string `yaml:"token" env:"UPDATE_TOKEN"`
Package string `yaml:"package" default:"ratatoskr"`
CheckInterval Duration `yaml:"check_interval" default:"24h"`
}
type TelegramCfg struct {
Token string `yaml:"token" env:"TG_TOKEN"`
ChatID string `yaml:"chat_id" env:"TG_CHAT_ID"`
}
type OpenCodeCfg struct {
Bin string `yaml:"bin" default:"opencode"`
DBPath string `yaml:"db_path" default:""`
Config string `yaml:"config" env:"OPENCODE_CONFIG"`
ConfigDir string `yaml:"config_dir" env:"OPENCODE_CONFIG_DIR"`
HardTimeout Duration `yaml:"hard_timeout" default:"20m"`
IdleTimeout Duration `yaml:"idle_timeout" default:"5m"`
PollMs Duration `yaml:"poll_ms" default:"2s"`
Serve ServeCfg `yaml:"serve"`
}
// ServeCfg — настройки постоянных opencode serve-процессов (API-режим).
// Runner ходит к serve по HTTP API (v1.17+, /api). ratatoskr сам поднимает
// по одному serve на каталог через Pool (Hash: hostname/port; служебный
// root-сервер в worktree живёт всё время app, остальные — лениво).
//
// Поля enabled/url оставлены для обратной совместимости конфига и сейчас не
// меняют поведение: serve обязателен для API-режима, сервер всегда
// спавнится пулом (hostname/port задают базовые значения).
type ServeCfg struct {
Enabled bool `yaml:"enabled" default:"false"`
URL string `yaml:"url" env:"OPENCODE_SERVE_URL"`
Hostname string `yaml:"hostname" default:"127.0.0.1"`
Port int `yaml:"port" default:"4096"`
Password string `yaml:"password" env:"OPENCODE_SERVE_PASSWORD"`
}
type ChatCfg struct {
PollInterval Duration `yaml:"poll_interval" default:"30s"`
}
type PathsCfg struct {
Worktree string `yaml:"worktree" default:"./worktrees"`
DB string `yaml:"db" default:"./ratatoskr.db" env:"RATATOSKR_DB"`
}
// 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.Log.Debug() && !strings.EqualFold(c.Log.Level, "info") {
errs = append(errs, fmt.Errorf("%w: log.level (ожидается \"info\" или \"debug\")", ErrInvalidFormat))
}
return errors.Join(errs...)
}