1
0
Fork 0

feat: initial release

This commit is contained in:
Arthur K. 2026-01-17 18:14:50 +03:00
parent a3cf21f5bd
commit 761174d035
Signed by: wzray
GPG key ID: B97F30FDC4636357
41 changed files with 2008 additions and 217 deletions

95
internal/config/config.go Normal file
View file

@ -0,0 +1,95 @@
package config
import (
"errors"
"fmt"
"os"
"git.wzray.com/homelab/mastermind/internal/types"
"github.com/BurntSushi/toml"
)
type Configs struct {
Master MasterConfig
Dns DnsConfig
Host HostConfig
}
type Config struct {
Node NodeConfig
Configs Configs
Roles []types.Role
}
func FromFile(filename string) (Config, error) {
data, err := os.ReadFile(filename)
if err != nil {
return Config{}, fmt.Errorf("read config file: %w", err)
}
var temp struct {
Node NodeConfig `toml:"node"`
Configs struct {
Host *HostConfig `toml:"host"`
Dns *DnsConfig `toml:"dns"`
Master *MasterConfig `toml:"master"`
} `toml:"roles"`
}
if err := toml.Unmarshal(data, &temp); err != nil {
return Config{}, fmt.Errorf("parse config file: %w", err)
}
config := defaultConfig
config.Node.Merge(temp.Node)
if c := temp.Configs.Master; c != nil {
c.set = true
config.Roles = append(config.Roles, types.MasterRole)
config.Configs.Master.Merge(*c)
}
if c := temp.Configs.Dns; c != nil {
c.set = true
config.Roles = append(config.Roles, types.DnsRole)
config.Configs.Dns.Merge(*c)
}
if c := temp.Configs.Host; c != nil {
c.set = true
config.Roles = append(config.Roles, types.HostRole)
config.Configs.Host.Merge(*c)
}
return config, nil
}
func (c Config) Validate() error {
if err := c.Node.Validate(); err != nil {
return fmt.Errorf("node: %w", err)
}
if c.Configs.Host.set {
if err := c.Configs.Host.Validate(); err != nil {
return fmt.Errorf("configs.host: %w", err)
}
}
if c.Configs.Dns.set {
if err := c.Configs.Dns.Validate(); err != nil {
return fmt.Errorf("configs.dns: %w", err)
}
}
if c.Configs.Host.set {
if err := c.Configs.Master.Validate(); err != nil {
return fmt.Errorf("configs.master: %w", err)
}
}
if len(c.Roles) == 0 {
return errors.New("no roles configured")
}
return nil
}

View file

@ -0,0 +1,17 @@
package config
var defaultConfig = Config{
Node: NodeConfig{
ListenOn: "0.0.0.0",
Port: 56714,
KeepaliveInterval: 1,
LogLevel: LogLevelInfo,
},
Configs: Configs{
Master: MasterConfig{
ObserverInterval: 10,
BackoffSeconds: 2,
BackoffCount: 3,
},
},
}

20
internal/config/dns.go Normal file
View file

@ -0,0 +1,20 @@
package config
type DnsConfig struct {
UseSystemd bool `toml:"use_systemd"`
baseRoleConfig
}
func (c DnsConfig) Validate() error {
return nil
}
func (c *DnsConfig) Merge(other DnsConfig) {
if other.set {
c.set = other.set
}
if other.UseSystemd {
c.UseSystemd = other.UseSystemd
}
}

70
internal/config/host.go Normal file
View file

@ -0,0 +1,70 @@
package config
import (
"errors"
"fmt"
"net"
)
type HostConfig struct {
Domain string `toml:"domain"`
IpAddress string `toml:"ip"`
LocalAddress string `toml:"local_address"`
InternalEntrypoint string `toml:"internal_entrypoint"`
ExternalEntrypoint string `toml:"external_entrypoint"`
baseRoleConfig
}
func (c HostConfig) Validate() error {
if c.Domain == "" {
return errors.New("missing domain")
}
if c.IpAddress == "" {
return errors.New("missing ip")
}
if net.ParseIP(c.IpAddress) == nil {
return fmt.Errorf("invalid ip: %q", c.IpAddress)
}
if c.LocalAddress == "" {
return errors.New("missing local address")
}
if c.InternalEntrypoint == "" {
return errors.New("missing internal entrypoint")
}
if c.ExternalEntrypoint == "" {
return errors.New("missing external entrypoint")
}
return nil
}
func (c *HostConfig) Merge(other HostConfig) {
if other.set {
c.set = other.set
}
if other.Domain != "" {
c.Domain = other.Domain
}
if other.IpAddress != "" {
c.IpAddress = other.IpAddress
}
if other.LocalAddress != "" {
c.LocalAddress = other.LocalAddress
}
if other.InternalEntrypoint != "" {
c.InternalEntrypoint = other.InternalEntrypoint
}
if other.ExternalEntrypoint != "" {
c.ExternalEntrypoint = other.ExternalEntrypoint
}
}

47
internal/config/master.go Normal file
View file

@ -0,0 +1,47 @@
package config
import "errors"
type MasterConfig struct {
ObserverInterval int `toml:"observer_interval"`
BackoffSeconds int `toml:"backoff_seconds"`
BackoffCount int `toml:"backoff_count"`
baseRoleConfig
}
func (c MasterConfig) Validate() error {
if c.ObserverInterval < 1 {
return errors.New("invalid observer_interval")
}
if c.BackoffSeconds < 1 {
return errors.New("invalid backoff_seconds")
}
if c.BackoffCount < 1 {
return errors.New("invalid backoff_count")
}
return nil
}
func (c *MasterConfig) Merge(other MasterConfig) {
if other.set {
c.set = true
}
if other.ObserverInterval != 0 {
c.ObserverInterval = other.ObserverInterval
}
if other.BackoffSeconds != 0 {
c.BackoffSeconds = other.BackoffSeconds
}
if other.BackoffCount != 0 {
c.BackoffCount = other.BackoffCount
}
}

85
internal/config/node.go Normal file
View file

@ -0,0 +1,85 @@
package config
import (
"errors"
"fmt"
"strings"
)
type LogLevel string
const (
LogLevelDebug LogLevel = "DEBUG"
LogLevelInfo LogLevel = "INFO"
LogLevelWarn LogLevel = "WARN"
LogLevelError LogLevel = "ERROR"
)
func (l *LogLevel) UnmarshalText(data []byte) error {
raw := strings.ToUpper(string(data))
switch LogLevel(raw) {
case LogLevelDebug, LogLevelInfo, LogLevelWarn, LogLevelError:
*l = LogLevel(raw)
return nil
default:
return fmt.Errorf("invalid log level: %q", data)
}
}
type NodeConfig struct {
Hostname string `toml:"hostname"`
Endpoint string `toml:"endpoint"`
KeepaliveInterval int `toml:"keepalive_interval"`
LogLevel LogLevel `toml:"log_level"`
BootstrapMaster string `toml:"bootstrap_master"`
ListenOn string `toml:"listen_on"`
Port int `toml:"port"`
}
func (c NodeConfig) Validate() error {
if c.Hostname == "" {
return errors.New("missing hostname")
}
if c.Endpoint == "" {
return errors.New("missing endpoint")
}
if c.KeepaliveInterval < 1 && c.KeepaliveInterval != -1 {
return errors.New("invalid keepalive_interval")
}
return nil
}
func (c *NodeConfig) Merge(other NodeConfig) {
if other.Hostname != "" {
c.Hostname = other.Hostname
}
if other.Endpoint != "" {
c.Endpoint = other.Endpoint
}
if other.BootstrapMaster != "" {
c.BootstrapMaster = other.BootstrapMaster
}
if other.ListenOn != "" {
c.ListenOn = other.ListenOn
}
if other.Port != 0 {
c.Port = other.Port
}
if other.KeepaliveInterval != 0 {
c.KeepaliveInterval = other.KeepaliveInterval
}
if other.LogLevel != "" {
c.LogLevel = other.LogLevel
}
}

5
internal/config/role.go Normal file
View file

@ -0,0 +1,5 @@
package config
type baseRoleConfig struct {
set bool
}