release: 1.1.0 - YAML config, web server, web auth and UI improvements
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
SMTP SMTPConfig `yaml:"smtp"`
|
||||
Web WebConfig `yaml:"web"`
|
||||
}
|
||||
|
||||
type SMTPConfig struct {
|
||||
ListenAddress string `yaml:"listen_address"`
|
||||
ListenPort int `yaml:"listen_port"`
|
||||
StorageDir string `yaml:"storage_dir"`
|
||||
DomainName string `yaml:"domain_name"`
|
||||
MaxMessageSize int64 `yaml:"max_message_size"`
|
||||
EnableAuth bool `yaml:"enable_auth"`
|
||||
}
|
||||
|
||||
type WebConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Port int `yaml:"port"`
|
||||
Directory string `yaml:"directory"`
|
||||
BasicAuth BasicAuthConfig `yaml:"basic_auth"`
|
||||
}
|
||||
|
||||
type BasicAuthConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
}
|
||||
|
||||
func getExecutableDir() (string, error) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Dir(exe), nil
|
||||
}
|
||||
|
||||
func defaultConfig() *Config {
|
||||
return &Config{
|
||||
SMTP: SMTPConfig{
|
||||
ListenAddress: "0.0.0.0",
|
||||
ListenPort: 1025,
|
||||
StorageDir: "emails",
|
||||
DomainName: "m.ric930.ru",
|
||||
MaxMessageSize: 50 * 1024 * 1024,
|
||||
EnableAuth: true,
|
||||
},
|
||||
Web: WebConfig{
|
||||
Enabled: true,
|
||||
Port: 8089,
|
||||
Directory: "web",
|
||||
BasicAuth: BasicAuthConfig{
|
||||
Enabled: true,
|
||||
Username: "admin",
|
||||
Password: "admin",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resolveConfigPaths(cfg *Config, exeDir string) {
|
||||
if !filepath.IsAbs(cfg.SMTP.StorageDir) {
|
||||
cfg.SMTP.StorageDir = filepath.Join(exeDir, cfg.SMTP.StorageDir)
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(cfg.Web.Directory) {
|
||||
cfg.Web.Directory = filepath.Join(exeDir, cfg.Web.Directory)
|
||||
}
|
||||
}
|
||||
|
||||
func validateConfig(cfg *Config) error {
|
||||
if cfg.SMTP.ListenPort < 1 || cfg.SMTP.ListenPort > 65535 {
|
||||
return fmt.Errorf("invalid smtp.listen_port: %d", cfg.SMTP.ListenPort)
|
||||
}
|
||||
|
||||
if cfg.SMTP.StorageDir == "" {
|
||||
return fmt.Errorf("smtp.storage_dir must not be empty")
|
||||
}
|
||||
|
||||
if cfg.SMTP.DomainName == "" {
|
||||
return fmt.Errorf("smtp.domain_name must not be empty")
|
||||
}
|
||||
|
||||
if cfg.SMTP.MaxMessageSize <= 0 {
|
||||
return fmt.Errorf("smtp.max_message_size must be greater than zero")
|
||||
}
|
||||
|
||||
if cfg.Web.Enabled {
|
||||
if cfg.Web.Port < 1 || cfg.Web.Port > 65535 {
|
||||
return fmt.Errorf("invalid web.port: %d", cfg.Web.Port)
|
||||
}
|
||||
|
||||
if cfg.Web.Directory == "" {
|
||||
return fmt.Errorf("web.directory must not be empty")
|
||||
}
|
||||
|
||||
if cfg.Web.BasicAuth.Enabled {
|
||||
if cfg.Web.BasicAuth.Username == "" {
|
||||
return fmt.Errorf("web.basic_auth.username must not be empty")
|
||||
}
|
||||
|
||||
if cfg.Web.BasicAuth.Password == "" {
|
||||
return fmt.Errorf("web.basic_auth.password must not be empty")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadConfig(configPath string) (*Config, error) {
|
||||
exeDir, err := getExecutableDir()
|
||||
if err != nil {
|
||||
log.Printf(
|
||||
"Warning: cannot get executable directory: %v, using current directory",
|
||||
err,
|
||||
)
|
||||
exeDir = "."
|
||||
}
|
||||
|
||||
finalConfigPath := configPath
|
||||
if !filepath.IsAbs(finalConfigPath) {
|
||||
finalConfigPath = filepath.Join(exeDir, finalConfigPath)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(finalConfigPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("failed to read config: %w", err)
|
||||
}
|
||||
|
||||
cfg := defaultConfig()
|
||||
|
||||
cfgData, marshalErr := yaml.Marshal(cfg)
|
||||
if marshalErr != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to create default config data: %w",
|
||||
marshalErr,
|
||||
)
|
||||
}
|
||||
|
||||
if writeErr := os.WriteFile(finalConfigPath, cfgData, 0600); writeErr != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to create default config: %w",
|
||||
writeErr,
|
||||
)
|
||||
}
|
||||
|
||||
log.Printf("Created default config at %s", finalConfigPath)
|
||||
log.Printf(
|
||||
"WARNING: change the default web Basic Auth password in %s",
|
||||
finalConfigPath,
|
||||
)
|
||||
|
||||
resolveConfigPaths(cfg, exeDir)
|
||||
|
||||
if err := validateConfig(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to parse YAML config %s: %w",
|
||||
finalConfigPath,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
if err := validateConfig(&cfg); err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"invalid config %s: %w",
|
||||
finalConfigPath,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
resolveConfigPaths(&cfg, exeDir)
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
Reference in New Issue
Block a user