Files
tyto/backend/internal/config/config.go
vikingowl c0e678931d feat: add database layer with SQLite and PostgreSQL support
Database Package (internal/database/):
- Database interface abstraction for multiple backends
- SQLite implementation with pure Go driver (no CGO)
- PostgreSQL implementation with connection pooling
- Factory pattern for creating database from config
- Tiered retention with automatic aggregation:
  - Raw metrics: 24h (5s resolution)
  - 1-minute aggregation: 7 days
  - 5-minute aggregation: 30 days
  - Hourly aggregation: 1 year

Schema includes:
- agents: registration, status, certificates
- users: local + LDAP authentication
- roles: RBAC with permissions JSON
- sessions: token-based authentication
- metrics_*: time-series with aggregation
- alerts: triggered alerts with acknowledgment

Configuration Updates:
- DatabaseConfig with SQLite path and PostgreSQL settings
- RetentionConfig for customizing data retention
- Environment variables: TYTO_DB_*, TYTO_DB_CONNECTION_STRING
- Default SQLite at /var/lib/tyto/tyto.db

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 08:18:48 +01:00

354 lines
9.0 KiB
Go

package config
import (
"os"
"time"
"gopkg.in/yaml.v3"
)
// Mode determines the operational mode of Tyto.
type Mode string
const (
// ModeStandalone is the default single-host monitoring mode.
// No database, no agents, minimal configuration.
ModeStandalone Mode = "standalone"
// ModeServer is the full multi-device monitoring mode.
// Requires database, supports agents, authentication, and RBAC.
ModeServer Mode = "server"
// ModeAgent runs as a lightweight agent that reports to a server.
ModeAgent Mode = "agent"
)
type Config struct {
// Mode determines standalone vs server operation
Mode Mode `yaml:"mode"`
// Server settings (HTTP API)
Port string `yaml:"port"`
RefreshInterval time.Duration `yaml:"-"`
RefreshSeconds int `yaml:"refresh_interval"`
// Paths for containerized access
ProcPath string `yaml:"proc_path"`
SysPath string `yaml:"sys_path"`
MtabPath string `yaml:"mtab_path"`
DockerSock string `yaml:"docker_socket"`
// Legacy authentication (standalone mode only)
AuthEnabled bool `yaml:"auth_enabled"`
AuthUser string `yaml:"auth_user"`
AuthPass string `yaml:"auth_pass"`
// TLS for HTTP server
TLSEnabled bool `yaml:"tls_enabled"`
TLSCertFile string `yaml:"tls_cert_file"`
TLSKeyFile string `yaml:"tls_key_file"`
// Alerts
Alerts AlertConfig `yaml:"alerts"`
// Server mode configuration
Server ServerConfig `yaml:"server"`
// Agent mode configuration
Agent AgentConfig `yaml:"agent"`
// Database configuration (server mode only)
Database DatabaseConfig `yaml:"database"`
}
// ServerConfig contains settings for server mode.
type ServerConfig struct {
// GRPCPort for agent connections
GRPCPort int `yaml:"grpc_port"`
// TLS settings for gRPC
TLS TLSConfig `yaml:"tls"`
// Registration settings
Registration RegistrationConfig `yaml:"registration"`
}
// TLSConfig contains mTLS settings.
type TLSConfig struct {
CACert string `yaml:"ca_cert"`
ServerCert string `yaml:"server_cert"`
ServerKey string `yaml:"server_key"`
}
// RegistrationConfig controls agent registration behavior.
type RegistrationConfig struct {
AutoEnabled bool `yaml:"auto_enabled"`
RequireApproval bool `yaml:"require_approval"`
}
// AgentConfig contains settings for agent mode.
type AgentConfig struct {
// ID uniquely identifies this agent
ID string `yaml:"id"`
// ServerURL is the address of the central server
ServerURL string `yaml:"server_url"`
// Interval between metric collections
Interval time.Duration `yaml:"-"`
IntervalSeconds int `yaml:"interval"`
// TLS settings for connecting to server
TLS AgentTLSConfig `yaml:"tls"`
}
// AgentTLSConfig contains agent-side TLS settings.
type AgentTLSConfig struct {
CACert string `yaml:"ca_cert"`
AgentCert string `yaml:"agent_cert"`
AgentKey string `yaml:"agent_key"`
}
// DatabaseConfig contains database connection settings.
type DatabaseConfig struct {
// Type is "sqlite" or "postgres"
Type string `yaml:"type"`
// SQLite settings
Path string `yaml:"path"` // Path to SQLite database file
// PostgreSQL settings
Host string `yaml:"host"`
Port int `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
Database string `yaml:"database"`
SSLMode string `yaml:"sslmode"`
ConnectionString string `yaml:"connection_string"` // Full connection string (overrides individual fields)
// Retention settings
Retention RetentionConfig `yaml:"retention"`
}
// RetentionConfig defines data retention policies.
type RetentionConfig struct {
RawRetention time.Duration `yaml:"raw"` // Raw metrics retention (default: 24h)
OneMinuteRetention time.Duration `yaml:"one_minute"` // 1-minute aggregation (default: 7d)
FiveMinuteRetention time.Duration `yaml:"five_minute"` // 5-minute aggregation (default: 30d)
HourlyRetention time.Duration `yaml:"hourly"` // Hourly aggregation (default: 1y)
}
type AlertConfig struct {
Enabled bool `yaml:"enabled"`
CPUThreshold float64 `yaml:"cpu_threshold"`
MemoryThreshold float64 `yaml:"memory_threshold"`
DiskThreshold float64 `yaml:"disk_threshold"`
TempThreshold float64 `yaml:"temp_threshold"`
}
func Load() *Config {
cfg := &Config{
Mode: ModeStandalone,
Port: "8080",
RefreshSeconds: 5,
ProcPath: "/proc",
SysPath: "/sys",
MtabPath: "/etc/mtab",
DockerSock: "/var/run/docker.sock",
Alerts: AlertConfig{
CPUThreshold: 90.0,
MemoryThreshold: 90.0,
DiskThreshold: 90.0,
TempThreshold: 80.0,
},
Server: ServerConfig{
GRPCPort: 9849,
Registration: RegistrationConfig{
AutoEnabled: true,
RequireApproval: true,
},
},
Agent: AgentConfig{
IntervalSeconds: 5,
},
Database: DatabaseConfig{
Type: "sqlite",
Path: "/var/lib/tyto/tyto.db",
Port: 5432,
SSLMode: "disable",
},
}
// Try to load from YAML config file
configPath := getEnv("TYTO_CONFIG", getEnv("CONFIG_FILE", "/etc/tyto/config.yaml"))
if data, err := os.ReadFile(configPath); err == nil {
yaml.Unmarshal(data, cfg)
}
// Environment variables override YAML
if val := os.Getenv("TYTO_MODE"); val != "" {
cfg.Mode = Mode(val)
}
if val := os.Getenv("PORT"); val != "" {
cfg.Port = val
}
if val := os.Getenv("PROC_PATH"); val != "" {
cfg.ProcPath = val
}
if val := os.Getenv("SYS_PATH"); val != "" {
cfg.SysPath = val
}
if val := os.Getenv("MTAB_PATH"); val != "" {
cfg.MtabPath = val
}
if val := os.Getenv("DOCKER_SOCKET"); val != "" {
cfg.DockerSock = val
}
if val := os.Getenv("AUTH_ENABLED"); val == "true" {
cfg.AuthEnabled = true
}
if val := os.Getenv("AUTH_USER"); val != "" {
cfg.AuthUser = val
}
if val := os.Getenv("AUTH_PASS"); val != "" {
cfg.AuthPass = val
}
if val := os.Getenv("TLS_ENABLED"); val == "true" {
cfg.TLSEnabled = true
}
if val := os.Getenv("TLS_CERT_FILE"); val != "" {
cfg.TLSCertFile = val
}
if val := os.Getenv("TLS_KEY_FILE"); val != "" {
cfg.TLSKeyFile = val
}
// Database environment variables
if val := os.Getenv("TYTO_DB_TYPE"); val != "" {
cfg.Database.Type = val
}
if val := os.Getenv("TYTO_DB_PATH"); val != "" {
cfg.Database.Path = val
}
if val := os.Getenv("TYTO_DB_HOST"); val != "" {
cfg.Database.Host = val
}
if val := os.Getenv("TYTO_DB_USER"); val != "" {
cfg.Database.User = val
}
if val := os.Getenv("TYTO_DB_PASSWORD"); val != "" {
cfg.Database.Password = val
}
if val := os.Getenv("TYTO_DB_NAME"); val != "" {
cfg.Database.Database = val
}
if val := os.Getenv("TYTO_DB_CONNECTION_STRING"); val != "" {
cfg.Database.ConnectionString = val
}
// Agent configuration
if val := os.Getenv("TYTO_AGENT_ID"); val != "" {
cfg.Agent.ID = val
}
if val := os.Getenv("TYTO_SERVER_URL"); val != "" {
cfg.Agent.ServerURL = val
}
// Parse intervals
if intervalStr := os.Getenv("DEFAULT_REFRESH_INTERVAL"); intervalStr != "" {
if d, err := time.ParseDuration(intervalStr); err == nil {
cfg.RefreshInterval = d
}
} else {
cfg.RefreshInterval = time.Duration(cfg.RefreshSeconds) * time.Second
}
cfg.Agent.Interval = time.Duration(cfg.Agent.IntervalSeconds) * time.Second
if cfg.Agent.Interval == 0 {
cfg.Agent.Interval = 5 * time.Second
}
return cfg
}
// DefaultConfig returns a config with default values.
func DefaultConfig() *Config {
return &Config{
Mode: ModeStandalone,
Port: "8080",
RefreshSeconds: 5,
ProcPath: "/proc",
SysPath: "/sys",
MtabPath: "/etc/mtab",
DockerSock: "/var/run/docker.sock",
Alerts: AlertConfig{
CPUThreshold: 90.0,
MemoryThreshold: 90.0,
DiskThreshold: 90.0,
TempThreshold: 80.0,
},
Server: ServerConfig{
GRPCPort: 9849,
Registration: RegistrationConfig{
AutoEnabled: true,
RequireApproval: true,
},
},
Agent: AgentConfig{
Interval: 5 * time.Second,
},
Database: DatabaseConfig{
Type: "sqlite",
Path: "/var/lib/tyto/tyto.db",
Port: 5432,
SSLMode: "disable",
},
RefreshInterval: 5 * time.Second,
}
}
// LoadFromPath loads configuration from a specific file path.
func LoadFromPath(path string) (*Config, error) {
cfg := DefaultConfig()
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(data, cfg); err != nil {
return nil, err
}
// Parse intervals from seconds
cfg.RefreshInterval = time.Duration(cfg.RefreshSeconds) * time.Second
cfg.Agent.Interval = time.Duration(cfg.Agent.IntervalSeconds) * time.Second
if cfg.Agent.Interval == 0 {
cfg.Agent.Interval = 5 * time.Second
}
return cfg, nil
}
// IsStandalone returns true if running in standalone mode.
func (c *Config) IsStandalone() bool {
return c.Mode == ModeStandalone || c.Mode == ""
}
// IsServer returns true if running in server mode.
func (c *Config) IsServer() bool {
return c.Mode == ModeServer
}
// IsAgent returns true if running in agent mode.
func (c *Config) IsAgent() bool {
return c.Mode == ModeAgent
}
func getEnv(key, defaultVal string) string {
if val := os.Getenv(key); val != "" {
return val
}
return defaultVal
}