87 lines
2.5 KiB
Go
87 lines
2.5 KiB
Go
// Package config handles configuration loading and validation.
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
// Config holds the complete application configuration.
|
|
type Config struct {
|
|
Matrix MatrixConfig `toml:"matrix"`
|
|
}
|
|
|
|
// MatrixConfig holds Matrix-native bot settings.
|
|
type MatrixConfig struct {
|
|
Shadow bool `toml:"shadow"` // Shadow mode (log only)
|
|
HealthAddr string `toml:"healthAddr"` // Health server listen address
|
|
Server string `toml:"server"` // Homeserver base URL
|
|
AccessToken string `toml:"accessToken"` // Access token for bot user
|
|
UserID string `toml:"userId"` // Full Matrix user ID
|
|
Rooms []string `toml:"rooms"` // Allowlisted room IDs
|
|
StateStorePath string `toml:"stateStorePath"` // Path to state store (sync token/dedupe)
|
|
}
|
|
|
|
// Load reads and parses a TOML configuration file.
|
|
func Load(path string) (*Config, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading config file: %w", err)
|
|
}
|
|
|
|
var cfg Config
|
|
if err := toml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parsing config file: %w", err)
|
|
}
|
|
|
|
if err := cfg.Validate(); err != nil {
|
|
return nil, fmt.Errorf("validating config: %w", err)
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
// Validate checks that required configuration fields are set.
|
|
func (c *Config) Validate() error {
|
|
if c.Matrix.Server == "" {
|
|
return fmt.Errorf("matrix.server is required")
|
|
}
|
|
if c.Matrix.AccessToken == "" {
|
|
return fmt.Errorf("matrix.accessToken is required")
|
|
}
|
|
if c.Matrix.UserID == "" {
|
|
return fmt.Errorf("matrix.userId is required")
|
|
}
|
|
if len(c.Matrix.Rooms) == 0 {
|
|
return fmt.Errorf("matrix.rooms must include at least one room")
|
|
}
|
|
if c.Matrix.StateStorePath == "" {
|
|
c.Matrix.StateStorePath = "data/matrix-state.db"
|
|
}
|
|
if c.Matrix.HealthAddr != "" {
|
|
if err := validateLocalHealthAddr(c.Matrix.HealthAddr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateLocalHealthAddr(addr string) error {
|
|
host, _, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
return fmt.Errorf("matrix.healthAddr must be host:port: %w", err)
|
|
}
|
|
if host == "" {
|
|
return fmt.Errorf("matrix.healthAddr must bind to localhost (use 127.0.0.1:PORT)")
|
|
}
|
|
normalized := strings.ToLower(host)
|
|
if normalized == "localhost" || normalized == "127.0.0.1" || normalized == "::1" {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("matrix.healthAddr must bind to localhost (got %s)", host)
|
|
}
|