package config import ( "encoding/json" "fmt" "os" "path/filepath" ) // Config holds forge-mesh server configuration loaded from config.json. type Config struct { ListenAddr string `json:"listen_addr"` DataDir string `json:"data_dir"` DatabasePath string `json:"database_path"` OperatorClearance int `json:"operator_clearance"` Auth AuthConfig `json:"auth"` WalletPolicy WalletPolicy `json:"wallet_policy"` Stratum StratumConfig `json:"stratum"` Forge ForgeConfig `json:"forge"` Court CourtConfig `json:"court"` Telegram TelegramConfig `json:"telegram"` } // TelegramConfig holds alert bot settings (W19). type TelegramConfig struct { Enabled bool `json:"enabled"` BotToken string `json:"bot_token"` ChatID string `json:"chat_id"` } type AuthConfig struct { BasicUsername string `json:"basic_username"` BasicPassword string `json:"basic_password"` FleetSecret string `json:"fleet_secret"` } type WalletPolicy struct { DefaultWallet string `json:"default_wallet"` Currency string `json:"currency"` } type StratumConfig struct { XMRListen string `json:"xmr_listen"` RVNListen string `json:"rvn_listen"` UpstreamXMR string `json:"upstream_xmr"` UpstreamRVN string `json:"upstream_rvn"` } type ForgeConfig struct { SigningKeyPath string `json:"signing_key_path"` ArtifactsDir string `json:"artifacts_dir"` } type CourtConfig struct { OllamaURL string `json:"ollama_url"` Enabled bool `json:"enabled"` } // Load reads and parses config from the given JSON file path. func Load(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read config: %w", err) } var cfg Config if err := json.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("parse config: %w", err) } cfg.applyDefaults(path) if err := cfg.validate(); err != nil { return nil, err } return &cfg, nil } func (c *Config) applyDefaults(configPath string) { if c.ListenAddr == "" { c.ListenAddr = ":8989" } if c.DataDir == "" { c.DataDir = filepath.Dir(configPath) } if c.DatabasePath == "" { c.DatabasePath = filepath.Join(c.DataDir, "forge-mesh.db") } if c.Auth.BasicUsername == "" { c.Auth.BasicUsername = "admin" } if c.Auth.BasicPassword == "" { c.Auth.BasicPassword = "changeme" } if c.WalletPolicy.Currency == "" { c.WalletPolicy.Currency = "XMR" } if c.Stratum.XMRListen == "" { c.Stratum.XMRListen = ":3333" } if c.Stratum.RVNListen == "" { c.Stratum.RVNListen = ":3388" } if c.Forge.ArtifactsDir == "" { c.Forge.ArtifactsDir = filepath.Join(c.DataDir, "artifacts") } if c.Forge.SigningKeyPath == "" { c.Forge.SigningKeyPath = filepath.Join(c.DataDir, "signing.key") } if c.Court.OllamaURL == "" { c.Court.OllamaURL = "http://127.0.0.1:11434" } if c.OperatorClearance == 0 { c.OperatorClearance = 4 } } func (c *Config) validate() error { if c.ListenAddr == "" { return fmt.Errorf("listen_addr is required") } return nil } // EnsureDataDirs creates data directories referenced by the config. func (c *Config) EnsureDataDirs() error { dirs := []string{c.DataDir, c.Forge.ArtifactsDir} for _, dir := range dirs { if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("mkdir %s: %w", dir, err) } } return nil }