// Package config handles configuration loading. package config import ( "fmt" "os" "gopkg.in/yaml.v3" ) // Config holds all application configuration. type Config struct { Server ServerConfig `yaml:"server"` Database DatabaseConfig `yaml:"database"` Storage StorageConfig `yaml:"storage"` Schemas SchemasConfig `yaml:"schemas"` FreeCAD FreeCADConfig `yaml:"freecad"` Odoo OdooConfig `yaml:"odoo"` Auth AuthConfig `yaml:"auth"` Jobs JobsConfig `yaml:"jobs"` Workflows WorkflowsConfig `yaml:"workflows"` Modules ModulesConfig `yaml:"modules"` } // ModulesConfig holds explicit enable/disable toggles for optional modules. // A nil pointer means "use the module's default state". type ModulesConfig struct { Auth *ModuleToggle `yaml:"auth"` Projects *ModuleToggle `yaml:"projects"` Audit *ModuleToggle `yaml:"audit"` Odoo *ModuleToggle `yaml:"odoo"` FreeCAD *ModuleToggle `yaml:"freecad"` Jobs *ModuleToggle `yaml:"jobs"` DAG *ModuleToggle `yaml:"dag"` } // ModuleToggle holds an optional enabled flag. The pointer allows // distinguishing "not set" (nil) from "explicitly false". type ModuleToggle struct { Enabled *bool `yaml:"enabled"` } // AuthConfig holds authentication and authorization settings. type AuthConfig struct { Enabled bool `yaml:"enabled"` SessionSecret string `yaml:"session_secret"` Local LocalAuth `yaml:"local"` LDAP LDAPAuth `yaml:"ldap"` OIDC OIDCAuth `yaml:"oidc"` CORS CORSConfig `yaml:"cors"` } // LocalAuth holds settings for local account authentication. type LocalAuth struct { Enabled bool `yaml:"enabled"` DefaultAdminUsername string `yaml:"default_admin_username"` DefaultAdminPassword string `yaml:"default_admin_password"` } // LDAPAuth holds settings for LDAP/FreeIPA authentication. type LDAPAuth struct { Enabled bool `yaml:"enabled"` URL string `yaml:"url"` BaseDN string `yaml:"base_dn"` UserSearchDN string `yaml:"user_search_dn"` BindDN string `yaml:"bind_dn"` BindPassword string `yaml:"bind_password"` UserAttr string `yaml:"user_attr"` EmailAttr string `yaml:"email_attr"` DisplayAttr string `yaml:"display_attr"` GroupAttr string `yaml:"group_attr"` RoleMapping map[string][]string `yaml:"role_mapping"` TLSSkipVerify bool `yaml:"tls_skip_verify"` } // OIDCAuth holds settings for OIDC/Keycloak authentication. type OIDCAuth struct { Enabled bool `yaml:"enabled"` IssuerURL string `yaml:"issuer_url"` ClientID string `yaml:"client_id"` ClientSecret string `yaml:"client_secret"` RedirectURL string `yaml:"redirect_url"` Scopes []string `yaml:"scopes"` AdminRole string `yaml:"admin_role"` EditorRole string `yaml:"editor_role"` DefaultRole string `yaml:"default_role"` } // CORSConfig holds CORS settings. type CORSConfig struct { AllowedOrigins []string `yaml:"allowed_origins"` } // ServerConfig holds HTTP server settings. type ServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` BaseURL string `yaml:"base_url"` ReadOnly bool `yaml:"read_only"` } // DatabaseConfig holds PostgreSQL connection settings. type DatabaseConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` Name string `yaml:"name"` User string `yaml:"user"` Password string `yaml:"password"` SSLMode string `yaml:"sslmode"` MaxConnections int `yaml:"max_connections"` } // StorageConfig holds object storage settings. type StorageConfig struct { Backend string `yaml:"backend"` // "minio" (default) or "filesystem" Endpoint string `yaml:"endpoint"` AccessKey string `yaml:"access_key"` SecretKey string `yaml:"secret_key"` Bucket string `yaml:"bucket"` UseSSL bool `yaml:"use_ssl"` Region string `yaml:"region"` Filesystem FilesystemConfig `yaml:"filesystem"` } // FilesystemConfig holds local filesystem storage settings. type FilesystemConfig struct { RootDir string `yaml:"root_dir"` } // SchemasConfig holds schema loading settings. type SchemasConfig struct { Directory string `yaml:"directory"` Default string `yaml:"default"` } // FreeCADConfig holds FreeCAD integration settings. type FreeCADConfig struct { URIScheme string `yaml:"uri_scheme"` Executable string `yaml:"executable"` } // JobsConfig holds worker/runner system settings. type JobsConfig struct { Directory string `yaml:"directory"` // default /etc/silo/jobdefs RunnerTimeout int `yaml:"runner_timeout"` // seconds, default 90 JobTimeoutCheck int `yaml:"job_timeout_check"` // seconds, default 30 DefaultPriority int `yaml:"default_priority"` // default 100 } // WorkflowsConfig holds approval workflow definition settings. type WorkflowsConfig struct { Directory string `yaml:"directory"` // default /etc/silo/workflows } // OdooConfig holds Odoo ERP integration settings. type OdooConfig struct { Enabled bool `yaml:"enabled"` URL string `yaml:"url"` Database string `yaml:"database"` Username string `yaml:"username"` APIKey string `yaml:"api_key"` } // Load reads configuration from a YAML file. func Load(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("reading config file: %w", err) } // Expand environment variables data = []byte(os.ExpandEnv(string(data))) var cfg Config if err := yaml.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("parsing config YAML: %w", err) } // Apply defaults if cfg.Server.Port == 0 { cfg.Server.Port = 8080 } if cfg.Database.Port == 0 { cfg.Database.Port = 5432 } if cfg.Database.SSLMode == "" { cfg.Database.SSLMode = "require" } if cfg.Database.MaxConnections == 0 { cfg.Database.MaxConnections = 10 } if cfg.Storage.Region == "" { cfg.Storage.Region = "us-east-1" } if cfg.Schemas.Directory == "" { cfg.Schemas.Directory = "/etc/silo/schemas" } if cfg.FreeCAD.URIScheme == "" { cfg.FreeCAD.URIScheme = "silo" } if cfg.Jobs.Directory == "" { cfg.Jobs.Directory = "/etc/silo/jobdefs" } if cfg.Jobs.RunnerTimeout == 0 { cfg.Jobs.RunnerTimeout = 90 } if cfg.Jobs.JobTimeoutCheck == 0 { cfg.Jobs.JobTimeoutCheck = 30 } if cfg.Jobs.DefaultPriority == 0 { cfg.Jobs.DefaultPriority = 100 } if cfg.Workflows.Directory == "" { cfg.Workflows.Directory = "/etc/silo/workflows" } // Override with environment variables if v := os.Getenv("SILO_DB_HOST"); v != "" { cfg.Database.Host = v } if v := os.Getenv("SILO_DB_NAME"); v != "" { cfg.Database.Name = v } if v := os.Getenv("SILO_DB_USER"); v != "" { cfg.Database.User = v } if v := os.Getenv("SILO_DB_PASSWORD"); v != "" { cfg.Database.Password = v } if v := os.Getenv("SILO_MINIO_ENDPOINT"); v != "" { cfg.Storage.Endpoint = v } if v := os.Getenv("SILO_MINIO_ACCESS_KEY"); v != "" { cfg.Storage.AccessKey = v } if v := os.Getenv("SILO_MINIO_SECRET_KEY"); v != "" { cfg.Storage.SecretKey = v } // Auth defaults if cfg.Auth.LDAP.UserAttr == "" { cfg.Auth.LDAP.UserAttr = "uid" } if cfg.Auth.LDAP.EmailAttr == "" { cfg.Auth.LDAP.EmailAttr = "mail" } if cfg.Auth.LDAP.DisplayAttr == "" { cfg.Auth.LDAP.DisplayAttr = "displayName" } if cfg.Auth.LDAP.GroupAttr == "" { cfg.Auth.LDAP.GroupAttr = "memberOf" } if cfg.Auth.OIDC.DefaultRole == "" { cfg.Auth.OIDC.DefaultRole = "viewer" } // Auth environment variable overrides if v := os.Getenv("SILO_SESSION_SECRET"); v != "" { cfg.Auth.SessionSecret = v } if v := os.Getenv("SILO_OIDC_CLIENT_SECRET"); v != "" { cfg.Auth.OIDC.ClientSecret = v } if v := os.Getenv("SILO_LDAP_BIND_PASSWORD"); v != "" { cfg.Auth.LDAP.BindPassword = v } if v := os.Getenv("SILO_ADMIN_USERNAME"); v != "" { cfg.Auth.Local.DefaultAdminUsername = v } if v := os.Getenv("SILO_ADMIN_PASSWORD"); v != "" { cfg.Auth.Local.DefaultAdminPassword = v } return &cfg, nil }