141 lines
7.9 KiB
Cheetah
141 lines
7.9 KiB
Cheetah
// Package settings owns this project's configuration; it adds no business identity model.
|
|
package settings
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
{{if ne .Storage "none"}}"fmt"
|
|
{{if ne .Storage "r2"}}"net/url"{{else}}"regexp"{{end}}
|
|
"strconv"
|
|
"git.yuntuo.hk/k2safe/go-omni/storage"{{end}}
|
|
{{if and .API (eq .Database "mysql")}}"git.yuntuo.hk/k2safe/go-omni/database"{{end}}
|
|
"git.yuntuo.hk/k2safe/go-omni/config"
|
|
"git.yuntuo.hk/k2safe/go-omni/logx"
|
|
)
|
|
|
|
type Config struct {
|
|
Server config.Config `json:"server"`
|
|
{{if and .API (eq .Database "mysql")}}Database database.Config `json:"database"`{{end}}
|
|
{{if ne .Storage "none"}}Storage StorageConfig `json:"storage"`{{end}}
|
|
}
|
|
|
|
func Default() Config {
|
|
server:=config.Default()
|
|
server.Address="127.0.0.1:8080"
|
|
server.EnableLogger=true
|
|
server.SlowRequestThreshold=500*time.Millisecond
|
|
server.Log.Service={{printf "%q" .Module}}
|
|
server.Log.Format={{printf "%q" .LogFormat}}
|
|
{{if eq .LogOutput "file"}}server.Log.File="./var/log/server.log"
|
|
server.Log.Rotation=logx.RotationConfig{Enabled:true,MaxSizeMB:100,MaxBackups:10,MaxAgeDays:7,Compress:true}
|
|
{{else}}server.Log.Output={{printf "%q" .LogOutput}}{{end}}
|
|
return Config{Server:server{{if and .API (eq .Database "mysql")}},Database:database.DefaultConfig(){{end}}{{if ne .Storage "none"}},Storage:StorageConfig{
|
|
Provider:{{printf "%q" .Storage}}, 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,
|
|
{{if eq .Storage "s3"}}Region:"us-east-1",{{end}}
|
|
{{if eq .Storage "r2"}}Region:"auto",{{end}}
|
|
{{if eq .Storage "oss"}}Region:"cn-hangzhou",{{end}}
|
|
}{{end}}}
|
|
}
|
|
|
|
// 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 ne .Storage "none"}}
|
|
if err=cfg.Storage.applyEnv();err!=nil{return Config{},err}
|
|
{{end}}
|
|
{{if and .API (eq .Database "mysql")}}cfg.Database,err=database.ApplyEnv(cfg.Database,"");if err!=nil{return Config{},err}{{end}}
|
|
if err=cfg.Validate();err!=nil{return Config{},err}
|
|
return cfg,nil
|
|
}
|
|
func (c Config) Validate()error {
|
|
{{if and .API (eq .Database "mysql")}}
|
|
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")}
|
|
{{end}}
|
|
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")}
|
|
{{if ne .Storage "none"}}return c.Storage.Validate(){{else}}return nil{{end}}
|
|
}
|
|
|
|
{{if ne .Storage "none"}}
|
|
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!={{printf "%q" .Storage}} {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 not .API}}if len(s.FilesToken)<32 || strings.ContainsAny(s.FilesToken," \t\r\n") {return errors.New("set GO_OMNI_FILES_TOKEN to a token of at least 32 bytes without whitespace")}{{end}}
|
|
{{if eq .Storage "local"}}
|
|
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")}
|
|
{{else}}
|
|
if s.Bucket=="" || s.Region=="" || s.AccessKey=="" || s.SecretKey=="" {return errors.New("storage bucket, region and GO_OMNI_STORAGE_ACCESS_KEY/SECRET_KEY are required")}
|
|
{{if eq .Storage "r2"}}
|
|
if !regexp.MustCompile(`^[a-fA-F0-9]{32}$`).MatchString(s.AccountID) || s.Region!="auto" || !s.Secure {return errors.New("R2 requires a 32-hex accountId, region auto and secure=true")}
|
|
if s.Endpoint!=""{return errors.New("this R2 profile derives endpoint from accountId; customize servicecontext for a jurisdiction-specific endpoint")}
|
|
{{else if eq .Storage "s3"}}
|
|
u,err:=url.Parse("https://"+s.Endpoint)
|
|
if err!=nil || s.Endpoint=="" || u.Hostname()=="" || u.User!=nil || u.Path!="" || u.RawQuery!="" || u.Fragment!="" {return errors.New("S3 endpoint must be host[:port] without scheme or path")}
|
|
{{else if eq .Storage "oss"}}
|
|
if s.Endpoint!="" {u,err:=url.Parse(s.Endpoint);if err!=nil || u.Scheme!="https" || u.Hostname()=="" || u.User!=nil || u.Path!="" || u.RawQuery!="" || u.Fragment!="" {return errors.New("OSS endpoint must be an HTTPS origin")}}
|
|
if !s.Secure{return errors.New("OSS requires secure=true")}
|
|
{{end}}
|
|
{{end}}
|
|
return nil
|
|
}
|
|
{{end}}
|