Files
ratatoskr-go/internal/config/types.go
Hermes 2f26b6ae88
Some checks failed
CI / test (push) Successful in 54s
CI / build-and-package (amd64, linux) (push) Failing after 1h58m55s
CI / build-and-package (amd64, windows) (push) Successful in 59s
feat: opencode serve — постоянный сервер вместо разовых subprocess (вариант A)
Супервайзер Server spawn'ит opencode serve (или ходит на внешний URL),
Runner ходит к нему через 'opencode run --attach <url>'. По умолчанию
serve.enabled=false — историческая spawn-модель сохранена; наличие
serve.url переключает на внешний сервер.

- config: ServeCfg (enabled/hostname/port/url/password) + дефолты; лоадер
  научился int/bool (раньше только string/Duration/struct).
- opencode: Server (Start/Run/Close, /global/health, рестарт упавшего,
  reaper-горутина владеет Wait; .Exited() непригоден для SIGKILL).
- Runner.AttachURL: run --attach <url> при заданном URL, иначе как раньше.
- app: composition root — при включённом serve запускает супервайзер.
- тесты: TestRun_AttachMode, TestServer_{ExternalURL,OwnProcess,Restart}.
2026-08-18 08:35:16 +05:00

118 lines
5.0 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"
"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"`
}
// 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 (режим --attach).
// enabled=true: ratatoskr сам запускает serve (супервайзер) и подключает
// Runner через команду `run --attach <url>`. Если задан url — вместо
// собственного spawn используется внешний (уже запущенный) сервер.
// enabled=false (по умолчанию): историческая модель — каждый вызов
// сам спавнит `opencode run` (без сервера).
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))
}
return errors.Join(errs...)
}