GoDoxy/internal/config/config.go

227 lines
5.4 KiB
Go

package config
import (
"os"
"github.com/sirupsen/logrus"
"github.com/yusing/go-proxy/internal/autocert"
"github.com/yusing/go-proxy/internal/common"
E "github.com/yusing/go-proxy/internal/error"
PR "github.com/yusing/go-proxy/internal/proxy/provider"
R "github.com/yusing/go-proxy/internal/route"
"github.com/yusing/go-proxy/internal/types"
U "github.com/yusing/go-proxy/internal/utils"
F "github.com/yusing/go-proxy/internal/utils/functional"
W "github.com/yusing/go-proxy/internal/watcher"
"github.com/yusing/go-proxy/internal/watcher/events"
"gopkg.in/yaml.v3"
)
type Config struct {
value *types.Config
proxyProviders F.Map[string, *PR.Provider]
autocertProvider *autocert.Provider
l logrus.FieldLogger
watcher W.Watcher
reloadReq chan struct{}
}
var instance *Config
func GetInstance() *Config {
return instance
}
func Load() E.NestedError {
if instance != nil {
return nil
}
instance = &Config{
value: types.DefaultConfig(),
proxyProviders: F.NewMapOf[string, *PR.Provider](),
l: logrus.WithField("module", "config"),
watcher: W.NewConfigFileWatcher(common.ConfigFileName),
reloadReq: make(chan struct{}, 1),
}
return instance.load()
}
func Validate(data []byte) E.NestedError {
return U.ValidateYaml(U.GetSchema(common.ConfigSchemaPath), data)
}
func MatchDomains() []string {
if instance == nil {
logrus.Panic("config has not been loaded, please check if there is any errors")
}
return instance.value.MatchDomains
}
func (cfg *Config) Value() types.Config {
if cfg == nil {
logrus.Panic("config has not been loaded, please check if there is any errors")
}
return *cfg.value
}
func (cfg *Config) GetAutoCertProvider() *autocert.Provider {
if instance == nil {
logrus.Panic("config has not been loaded, please check if there is any errors")
}
return cfg.autocertProvider
}
func (cfg *Config) Reload() (err E.NestedError) {
cfg.stopProviders()
err = cfg.load()
cfg.StartProxyProviders()
return
}
func (cfg *Config) StartProxyProviders() {
cfg.controlProviders("start", (*PR.Provider).StartAllRoutes)
}
func (cfg *Config) WatchChanges() {
task := common.NewTask("Config watcher")
defer task.Finished()
go func() {
for {
select {
case <-task.Context().Done():
return
case <-cfg.reloadReq:
if err := cfg.Reload(); err != nil {
cfg.l.Error(err)
}
}
}
}()
go func() {
eventCh, errCh := cfg.watcher.Events(task.Context())
for {
select {
case <-task.Context().Done():
return
case event := <-eventCh:
if event.Action == events.ActionFileDeleted || event.Action == events.ActionFileRenamed {
cfg.l.Error("config file deleted or renamed, ignoring...")
continue
} else {
cfg.reloadReq <- struct{}{}
}
case err := <-errCh:
cfg.l.Error(err)
continue
}
}
}()
}
func (cfg *Config) forEachRoute(do func(alias string, r *R.Route, p *PR.Provider)) {
cfg.proxyProviders.RangeAll(func(_ string, p *PR.Provider) {
p.RangeRoutes(func(a string, r *R.Route) {
do(a, r, p)
})
})
}
func (cfg *Config) load() (res E.NestedError) {
b := E.NewBuilder("errors loading config")
defer b.To(&res)
cfg.l.Debug("loading config")
defer cfg.l.Debug("loaded config")
data, err := E.Check(os.ReadFile(common.ConfigPath))
if err != nil {
b.Add(E.FailWith("read config", err))
logrus.Fatal(b.Build())
}
if !common.NoSchemaValidation {
if err = Validate(data); err != nil {
b.Add(E.FailWith("schema validation", err))
logrus.Fatal(b.Build())
}
}
model := types.DefaultConfig()
if err := E.From(yaml.Unmarshal(data, model)); err != nil {
b.Add(E.FailWith("parse config", err))
logrus.Fatal(b.Build())
}
// errors are non fatal below
b.Add(cfg.initAutoCert(&model.AutoCert))
b.Add(cfg.loadProviders(&model.Providers))
cfg.value = model
R.SetFindMuxDomains(model.MatchDomains)
return
}
func (cfg *Config) initAutoCert(autocertCfg *types.AutoCertConfig) (err E.NestedError) {
if cfg.autocertProvider != nil {
return
}
cfg.l.Debug("initializing autocert")
defer cfg.l.Debug("initialized autocert")
cfg.autocertProvider, err = autocert.NewConfig(autocertCfg).GetProvider()
if err != nil {
err = E.FailWith("autocert provider", err)
}
return
}
func (cfg *Config) loadProviders(providers *types.ProxyProviders) (res E.NestedError) {
cfg.l.Debug("loading providers")
defer cfg.l.Debug("loaded providers")
b := E.NewBuilder("errors loading providers")
defer b.To(&res)
for _, filename := range providers.Files {
p, err := PR.NewFileProvider(filename)
if err != nil {
b.Add(err.Subject(filename))
continue
}
cfg.proxyProviders.Store(p.GetName(), p)
b.Add(p.LoadRoutes().Subject(filename))
}
for name, dockerHost := range providers.Docker {
p, err := PR.NewDockerProvider(name, dockerHost)
if err != nil {
b.Add(err.Subjectf("%s (%s)", name, dockerHost))
continue
}
cfg.proxyProviders.Store(p.GetName(), p)
b.Add(p.LoadRoutes().Subject(p.GetName()))
}
return
}
func (cfg *Config) controlProviders(action string, do func(*PR.Provider) E.NestedError) {
errors := E.NewBuilder("errors in %s these providers", action)
cfg.proxyProviders.RangeAllParallel(func(name string, p *PR.Provider) {
if err := do(p); err != nil {
errors.Add(err.Subject(p))
}
})
if err := errors.Build(); err != nil {
cfg.l.Error(err)
}
}
func (cfg *Config) stopProviders() {
cfg.controlProviders("stop routes", (*PR.Provider).StopAllRoutes)
}