package config import ( "errors" "fmt" "os" "github.com/BurntSushi/toml" ) type ConfigSSL struct { HandleSSL bool `toml:"handle_ssl"` // Whether Linstrom should handle SSL encryption itself // If Linstrom is to handle SSL, whether it should use LetsEncrypt for certificates UseLetsEncrypt *bool `toml:"use_lets_encrypt"` // Path to the certificate if Linstrom is to handle SSL while not using LetsEncrypt CertificateFile *string `toml:"certificate_file"` // Mail adress to use in case of using LetsEncrypt AdminMail *string `toml:"admin_mail"` } type ConfigGeneral struct { Domain string `toml:"domain"` // The domain this server operates under FullDomain string `toml:"full_domain"` // The full url the server operates under (without port), eg "http://localhost", the public port will be appended as needed PublicPort int `toml:"public_port"` // The public facing port, usually either 80 or 443 PrivatePort int `toml:"private_port"` // The port the server should launch at // Explanation: // The public port is the port to connect to from outside the server to access it. // The private port is where the server will open for itself on launch // Important for reverse proxies like nginx or traeffik // Where you the public port would be either 80 (http) or 443 (https), but the private one could be 3000 for example } type ConfigAdmin struct { Username string `toml:"username"` PasswordHash string `toml:"password_hash"` } type ConfigStorage struct { IsPostgres bool `toml:"is_postgres"` Uri string `toml:"uri"` } type ConfigMail struct { Host string `toml:"host"` Port int `toml:"port"` Username string `toml:"username"` Password string `toml:"password"` EncryptionOverwrite *string `toml:"encryption_overwrite,omitempty"` KeepAliveOverwrite *bool `toml:"keep_alive_overwrite,omitempty"` ConnectTimeoutSecondsOverwrite *int `toml:"connect_timeout_seconds_overwrite,omitempty"` SendTimeoutSecondsOverwrite *int `toml:"send_timeout_seconds_overwrite,omitempty"` TemplateOverwriteDirectory *string `toml:"template_overwrite_directory,omitempty"` } type Config struct { General ConfigGeneral `toml:"general"` SSL ConfigSSL `toml:"ssl"` Admin ConfigAdmin `toml:"admin"` Storage ConfigStorage `toml:"storage"` Mail ConfigMail `toml:"mail"` } const DEFAULT_CONFIG_FILE_PATH = "config.toml" // "Global" variable for accessing the config // If nil, no config has been loaded yet var Global *Config var ErrGlobalConfigNotSet = errors.New("global config not set") // The default config is for a local debug environment var defaultConfig = Config{ General: ConfigGeneral{ Domain: "localhost", PublicPort: 8080, PrivatePort: 8080, }, SSL: ConfigSSL{ HandleSSL: false, UseLetsEncrypt: nil, CertificateFile: nil, AdminMail: nil, }, Admin: ConfigAdmin{ Username: "admin", PasswordHash: "", // No password }, Storage: ConfigStorage{ IsPostgres: false, Uri: "db.sqlite", }, } func LoadConfigFromFile(file string, tryDefaultPath, saveIntoGlobal bool) (*Config, error) { conf := &Config{} data, err := os.ReadFile(file) if err != nil && !tryDefaultPath { return nil, fmt.Errorf("failed to read config %s: %w", file, err) } if err != nil && tryDefaultPath { conf, err = loadFromDefaultPath() if err != nil { return nil, fmt.Errorf("failed to read custom and default config: %w", err) } } err = toml.Unmarshal(data, &conf) if err != nil { return nil, fmt.Errorf("failed to convert from toml: %w", err) } if saveIntoGlobal { Global = conf } return conf, nil } func loadFromDefaultPath() (*Config, error) { data, err := os.ReadFile(DEFAULT_CONFIG_FILE_PATH) if err != nil { return nil, fmt.Errorf( "failed to load default config path %s: %w", DEFAULT_CONFIG_FILE_PATH, err, ) } conf := Config{} err = toml.Unmarshal(data, &conf) if err != nil { return nil, fmt.Errorf( "failed to parse file content of %s as toml: %w", DEFAULT_CONFIG_FILE_PATH, err, ) } return &conf, nil }