194 lines
5.8 KiB
Go
194 lines
5.8 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
mysqlDriver "github.com/go-sql-driver/mysql"
|
|
)
|
|
|
|
type Config struct {
|
|
Listen string `json:"listen"`
|
|
FrontendDir string `json:"frontend_dir"`
|
|
MySQL MySQLConfig `json:"mysql"`
|
|
Redis RedisConfig `json:"redis"`
|
|
Auth AuthConfig `json:"auth"`
|
|
Upload UploadConfig `json:"upload"`
|
|
ReadTimeout time.Duration `json:"-"`
|
|
WriteTimeout time.Duration `json:"-"`
|
|
}
|
|
|
|
type UploadConfig struct {
|
|
Dir string `json:"dir"`
|
|
PublicPath string `json:"public_path"`
|
|
}
|
|
|
|
type MySQLConfig struct {
|
|
DSN string `json:"dsn"`
|
|
MaxOpenConns int `json:"max_open_conns"`
|
|
MaxIdleConns int `json:"max_idle_conns"`
|
|
}
|
|
|
|
type RedisConfig struct {
|
|
Addr string `json:"addr"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
DB int `json:"db"`
|
|
}
|
|
|
|
type AuthConfig struct {
|
|
APIKeys []string `json:"api_keys"`
|
|
SessionKeyPattern string `json:"session_key_pattern"`
|
|
SessionTTLSeconds int `json:"session_ttl_seconds"`
|
|
DefaultTenantID int64 `json:"default_tenant_id"`
|
|
DataEncryptionKey string `json:"data_encryption_key"`
|
|
LookupHMACKey string `json:"lookup_hmac_key"`
|
|
FinanceMasterKey string `json:"finance_master_key"`
|
|
AllowDevHeaderAuth bool `json:"allow_dev_header_auth"`
|
|
RequiredAPIKey bool `json:"required_api_key"`
|
|
}
|
|
|
|
func Load() (*Config, error) {
|
|
cfg := defaultConfig()
|
|
if path := strings.TrimSpace(os.Getenv("OLONS_SERVER_CONFIG")); path != "" {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := json.Unmarshal(data, cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
applyEnv(cfg)
|
|
if err := cfg.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func defaultConfig() *Config {
|
|
return &Config{
|
|
Listen: ":6061",
|
|
FrontendDir: "web/dist",
|
|
MySQL: MySQLConfig{
|
|
DSN: "root:root@tcp(127.0.0.1:3306)/olons_admin?parseTime=true&charset=utf8mb4&loc=Local",
|
|
MaxOpenConns: 15,
|
|
MaxIdleConns: 5,
|
|
},
|
|
Redis: RedisConfig{Addr: "127.0.0.1:6379"},
|
|
Auth: AuthConfig{
|
|
SessionKeyPattern: "olons_server:session:token:%s",
|
|
SessionTTLSeconds: 14 * 24 * 60 * 60,
|
|
DefaultTenantID: 0,
|
|
RequiredAPIKey: true,
|
|
},
|
|
Upload: UploadConfig{Dir: "storage/uploads", PublicPath: "/uploads"},
|
|
ReadTimeout: 8 * time.Second,
|
|
WriteTimeout: 8 * time.Second,
|
|
}
|
|
}
|
|
|
|
func applyEnv(cfg *Config) {
|
|
setString(&cfg.Listen, "OLONS_SERVER_LISTEN")
|
|
setString(&cfg.FrontendDir, "OLONS_SERVER_FRONTEND_DIR")
|
|
setString(&cfg.MySQL.DSN, "OLONS_SERVER_MYSQL_DSN")
|
|
setInt(&cfg.MySQL.MaxOpenConns, "OLONS_SERVER_MYSQL_MAX_OPEN_CONNS")
|
|
setInt(&cfg.MySQL.MaxIdleConns, "OLONS_SERVER_MYSQL_MAX_IDLE_CONNS")
|
|
setString(&cfg.Redis.Addr, "OLONS_SERVER_REDIS_ADDR")
|
|
setString(&cfg.Redis.Username, "OLONS_SERVER_REDIS_USERNAME")
|
|
setString(&cfg.Redis.Password, "OLONS_SERVER_REDIS_PASSWORD")
|
|
setInt(&cfg.Redis.DB, "OLONS_SERVER_REDIS_DB")
|
|
setString(&cfg.Auth.SessionKeyPattern, "OLONS_SERVER_SESSION_KEY_PATTERN")
|
|
setInt(&cfg.Auth.SessionTTLSeconds, "OLONS_SERVER_SESSION_TTL_SECONDS")
|
|
setInt64(&cfg.Auth.DefaultTenantID, "OLONS_SERVER_DEFAULT_TENANT_ID")
|
|
setString(&cfg.Auth.DataEncryptionKey, "OLONS_SERVER_DATA_ENCRYPTION_KEY")
|
|
setString(&cfg.Auth.LookupHMACKey, "OLONS_SERVER_LOOKUP_HMAC_KEY")
|
|
setString(&cfg.Auth.FinanceMasterKey, "OLONS_SERVER_FINANCE_MASTER_KEY")
|
|
setBool(&cfg.Auth.AllowDevHeaderAuth, "OLONS_SERVER_ALLOW_DEV_HEADER_AUTH")
|
|
setBool(&cfg.Auth.RequiredAPIKey, "OLONS_SERVER_REQUIRED_API_KEY")
|
|
setString(&cfg.Upload.Dir, "OLONS_SERVER_UPLOAD_DIR")
|
|
setString(&cfg.Upload.PublicPath, "OLONS_SERVER_UPLOAD_PUBLIC_PATH")
|
|
if keys := strings.TrimSpace(os.Getenv("OLONS_SERVER_API_KEYS")); keys != "" {
|
|
cfg.Auth.APIKeys = splitCSV(keys)
|
|
}
|
|
}
|
|
|
|
func (c *Config) validate() error {
|
|
if strings.TrimSpace(c.Listen) == "" {
|
|
return errors.New("listen is required")
|
|
}
|
|
if strings.TrimSpace(c.MySQL.DSN) == "" {
|
|
return errors.New("mysql dsn is required")
|
|
}
|
|
parsed, err := mysqlDriver.ParseDSN(strings.TrimPrefix(strings.TrimSpace(c.MySQL.DSN), "mysql://"))
|
|
if err != nil {
|
|
return fmt.Errorf("mysql dsn is invalid: %w", err)
|
|
}
|
|
if !strings.EqualFold(strings.TrimSpace(parsed.DBName), "olons_admin") {
|
|
return errors.New("mysql database must be olons_admin")
|
|
}
|
|
if strings.TrimSpace(c.Redis.Addr) == "" {
|
|
return errors.New("redis addr is required")
|
|
}
|
|
if !strings.Contains(c.Auth.SessionKeyPattern, "%s") {
|
|
return errors.New("session key pattern must include %s")
|
|
}
|
|
if c.Auth.SessionTTLSeconds <= 0 {
|
|
c.Auth.SessionTTLSeconds = 14 * 24 * 60 * 60
|
|
}
|
|
if c.Auth.DefaultTenantID < 0 {
|
|
return errors.New("default tenant id must not be negative")
|
|
}
|
|
if strings.TrimSpace(c.Upload.Dir) == "" || !strings.HasPrefix(c.Upload.PublicPath, "/") {
|
|
return errors.New("upload config is invalid")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setString(target *string, key string) {
|
|
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
|
|
*target = value
|
|
}
|
|
}
|
|
|
|
func setInt(target *int, key string) {
|
|
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
|
|
if parsed, err := strconv.Atoi(value); err == nil {
|
|
*target = parsed
|
|
}
|
|
}
|
|
}
|
|
|
|
func setInt64(target *int64, key string) {
|
|
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
|
|
if parsed, err := strconv.ParseInt(value, 10, 64); err == nil {
|
|
*target = parsed
|
|
}
|
|
}
|
|
}
|
|
|
|
func setBool(target *bool, key string) {
|
|
switch strings.ToLower(strings.TrimSpace(os.Getenv(key))) {
|
|
case "1", "true", "yes", "on":
|
|
*target = true
|
|
case "0", "false", "no", "off":
|
|
*target = false
|
|
}
|
|
}
|
|
|
|
func splitCSV(value string) []string {
|
|
parts := strings.Split(value, ",")
|
|
items := make([]string, 0, len(parts))
|
|
for _, part := range parts {
|
|
if item := strings.TrimSpace(part); item != "" {
|
|
items = append(items, item)
|
|
}
|
|
}
|
|
return items
|
|
}
|