194 lines
6.4 KiB
Go
194 lines
6.4 KiB
Go
// Package settings owns this project's configuration; it adds no business identity model.
|
|
package settings
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"git.yuntuo.hk/k2safe/go-omni/config"
|
|
"git.yuntuo.hk/k2safe/go-omni/database"
|
|
"git.yuntuo.hk/k2safe/go-omni/logx"
|
|
"git.yuntuo.hk/k2safe/go-omni/storage"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
Server config.Config `json:"server"`
|
|
Database database.Config `json:"database"`
|
|
Storage StorageConfig `json:"storage"`
|
|
}
|
|
|
|
func Default() Config {
|
|
server := config.Default()
|
|
server.Address = "127.0.0.1:8080"
|
|
server.EnableLogger = true
|
|
server.SlowRequestThreshold = 500 * time.Millisecond
|
|
server.Log.Service = "example.com/userService"
|
|
server.Log.Format = "json"
|
|
server.Log.File = "./var/log/server.log"
|
|
server.Log.Rotation = logx.RotationConfig{Enabled: true, MaxSizeMB: 100, MaxBackups: 10, MaxAgeDays: 7, Compress: true}
|
|
|
|
return Config{Server: server, Database: database.DefaultConfig(), Storage: StorageConfig{
|
|
Provider: "local", Root: "./var/files", PublicURL: "http://127.0.0.1:8080/storage/object",
|
|
Secure: true, MaxBytes: 32 << 20, ContentTypes: []string{"text/plain", "image/png", "image/jpeg", "application/pdf"},
|
|
LinkTTL: 5 * time.Minute, RequestTimeout: 30 * time.Second,
|
|
}}
|
|
}
|
|
|
|
// Load uses defaults -> strict YAML/JSON -> nonempty environment variables.
|
|
// Pass an empty path to use only defaults and environment.
|
|
func Load(path string) (Config, error) {
|
|
cfg := Default()
|
|
var err error
|
|
if path != "" {
|
|
cfg, err = config.DecodeFile(path, cfg)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
}
|
|
cfg.Server, err = config.ApplyEnv(cfg.Server, "")
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
// A stream explicitly selected by environment overrides a file profile.
|
|
if output := strings.TrimSpace(os.Getenv("GO_OMNI_LOG_OUTPUT")); output != "" && strings.TrimSpace(os.Getenv("GO_OMNI_LOG_FILE")) == "" {
|
|
cfg.Server.Log.File = ""
|
|
cfg.Server.Log.Rotation = logx.RotationConfig{}
|
|
}
|
|
|
|
if err = cfg.Storage.applyEnv(); err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
cfg.Database, err = database.ApplyEnv(cfg.Database, "")
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
if err = cfg.Validate(); err != nil {
|
|
return Config{}, err
|
|
}
|
|
return cfg, nil
|
|
}
|
|
func (c Config) Validate() error {
|
|
|
|
if err := c.Database.Validate(); err != nil {
|
|
return err
|
|
}
|
|
if c.Database.Driver != "mysql" || c.Database.MaxOpenConns < 1 {
|
|
return errors.New("database requires driver mysql and a positive maxOpenConns")
|
|
}
|
|
|
|
if err := c.Server.Validate(); err != nil {
|
|
return err
|
|
}
|
|
if c.Server.Log.Format != "json" && c.Server.Log.Format != "text" {
|
|
return errors.New("log.format must be json or text")
|
|
}
|
|
switch strings.ToLower(c.Server.Log.Level) {
|
|
case "debug", "info", "warn", "warning", "error":
|
|
default:
|
|
return errors.New("invalid log.level")
|
|
}
|
|
return c.Storage.Validate()
|
|
}
|
|
|
|
type StorageConfig struct {
|
|
Provider string `json:"provider"`
|
|
Root string `json:"root"`
|
|
PublicURL string `json:"publicUrl"`
|
|
Endpoint string `json:"endpoint"`
|
|
Region string `json:"region"`
|
|
Bucket string `json:"bucket"`
|
|
AccountID string `json:"accountId"`
|
|
Secure bool `json:"secure"`
|
|
MaxBytes int64 `json:"maxBytes"`
|
|
ContentTypes []string `json:"contentTypes"`
|
|
LinkTTL time.Duration `json:"linkTtl"`
|
|
RequestTimeout time.Duration `json:"requestTimeout"`
|
|
// Secrets are environment-only and cannot be populated by config files.
|
|
FilesToken string `json:"-"`
|
|
SigningKey string `json:"-"`
|
|
AccessKey string `json:"-"`
|
|
SecretKey string `json:"-"`
|
|
SessionToken string `json:"-"`
|
|
}
|
|
|
|
func (s StorageConfig) Policy() storage.UploadPolicy {
|
|
return storage.UploadPolicy{MaxBytes: s.MaxBytes, ContentTypes: s.ContentTypes}
|
|
}
|
|
func (s *StorageConfig) applyEnv() error {
|
|
values := map[string]*string{
|
|
"GO_OMNI_STORAGE_ROOT": &s.Root, "GO_OMNI_STORAGE_PUBLIC_URL": &s.PublicURL,
|
|
"GO_OMNI_STORAGE_ENDPOINT": &s.Endpoint, "GO_OMNI_STORAGE_REGION": &s.Region,
|
|
"GO_OMNI_STORAGE_BUCKET": &s.Bucket, "GO_OMNI_STORAGE_ACCOUNT_ID": &s.AccountID,
|
|
"GO_OMNI_STORAGE_SIGNING_KEY": &s.SigningKey, "GO_OMNI_FILES_TOKEN": &s.FilesToken,
|
|
"GO_OMNI_STORAGE_ACCESS_KEY": &s.AccessKey, "GO_OMNI_STORAGE_SECRET_KEY": &s.SecretKey,
|
|
"GO_OMNI_STORAGE_SESSION_TOKEN": &s.SessionToken,
|
|
}
|
|
for name, target := range values {
|
|
if value := strings.TrimSpace(os.Getenv(name)); value != "" {
|
|
*target = value
|
|
}
|
|
}
|
|
for name, target := range map[string]*time.Duration{"GO_OMNI_STORAGE_LINK_TTL": &s.LinkTTL, "GO_OMNI_STORAGE_REQUEST_TIMEOUT": &s.RequestTimeout} {
|
|
if value := strings.TrimSpace(os.Getenv(name)); value != "" {
|
|
v, err := time.ParseDuration(value)
|
|
if err != nil {
|
|
return fmt.Errorf("%s must be a duration", name)
|
|
}
|
|
*target = v
|
|
}
|
|
}
|
|
if value := os.Getenv("GO_OMNI_STORAGE_MAX_BYTES"); value != "" {
|
|
v, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
return errors.New("GO_OMNI_STORAGE_MAX_BYTES must be an integer")
|
|
}
|
|
s.MaxBytes = v
|
|
}
|
|
if value := os.Getenv("GO_OMNI_STORAGE_SECURE"); value != "" {
|
|
v, err := strconv.ParseBool(value)
|
|
if err != nil {
|
|
return errors.New("GO_OMNI_STORAGE_SECURE must be a boolean")
|
|
}
|
|
s.Secure = v
|
|
}
|
|
if value := strings.TrimSpace(os.Getenv("GO_OMNI_STORAGE_CONTENT_TYPES")); value != "" {
|
|
s.ContentTypes = strings.Split(value, ",")
|
|
for i := range s.ContentTypes {
|
|
s.ContentTypes[i] = strings.TrimSpace(s.ContentTypes[i])
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func (s StorageConfig) Validate() error {
|
|
if s.Provider != "local" {
|
|
return errors.New("storage.provider must match the adapter selected at generation")
|
|
}
|
|
if err := s.Policy().Validate(); err != nil {
|
|
return err
|
|
}
|
|
if s.LinkTTL < time.Second || s.LinkTTL > time.Hour {
|
|
return errors.New("storage.linkTtl must be 1s..1h")
|
|
}
|
|
if s.RequestTimeout <= 0 || s.RequestTimeout > 5*time.Minute {
|
|
return errors.New("storage.requestTimeout must be >0 and <=5m")
|
|
}
|
|
|
|
if s.Root == "" {
|
|
return errors.New("storage.root is required")
|
|
}
|
|
u, err := url.Parse(s.PublicURL)
|
|
if err != nil || u.Host == "" || u.Hostname() == "" || (u.Scheme != "http" && u.Scheme != "https") || u.User != nil || u.RawQuery != "" || u.Fragment != "" || u.Path != "/storage/object" {
|
|
return errors.New("storage.publicUrl must be an absolute HTTP(S) URL ending in /storage/object")
|
|
}
|
|
if len(s.SigningKey) < 32 {
|
|
return errors.New("set GO_OMNI_STORAGE_SIGNING_KEY to a stable secret of at least 32 bytes")
|
|
}
|
|
|
|
return nil
|
|
}
|