mirror of
https://github.com/garethgeorge/backrest.git
synced 2025-12-16 02:25:37 +00:00
147 lines
3.1 KiB
Go
147 lines
3.1 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
v1 "github.com/garethgeorge/backrest/gen/go/v1"
|
|
"github.com/garethgeorge/backrest/internal/config/migrations"
|
|
"github.com/garethgeorge/backrest/internal/cryptoutil"
|
|
"github.com/garethgeorge/backrest/internal/eventemitter"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var ErrConfigNotFound = fmt.Errorf("config not found")
|
|
|
|
type ConfigManager struct {
|
|
Store ConfigStore
|
|
OnChange eventemitter.EventEmitter[struct{}]
|
|
|
|
migrateOnce sync.Once
|
|
migrateErr error
|
|
|
|
cachedMu sync.Mutex
|
|
cached *v1.Config
|
|
}
|
|
|
|
var _ ConfigStore = &ConfigManager{}
|
|
|
|
func (m *ConfigManager) migrate(config *v1.Config) error {
|
|
// Check if we need to migrate
|
|
mutated, err := PopulateRequiredFields(config)
|
|
if err != nil {
|
|
return fmt.Errorf("populate required fields: %w", err)
|
|
}
|
|
if config.Version < migrations.CurrentVersion {
|
|
zap.S().Infof("migrating config from version %d to %d", config.Version, migrations.CurrentVersion)
|
|
if err := migrations.ApplyMigrations(config); err != nil {
|
|
return err
|
|
}
|
|
mutated = true
|
|
}
|
|
if mutated {
|
|
// Check validations
|
|
if err := ValidateConfig(config); err != nil {
|
|
return fmt.Errorf("validation after migration: %v", err)
|
|
}
|
|
|
|
// Write back the migrated config.
|
|
if err := m.Store.Update(config); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *ConfigManager) Get() (*v1.Config, error) {
|
|
m.cachedMu.Lock()
|
|
defer m.cachedMu.Unlock()
|
|
|
|
if m.cached != nil {
|
|
return m.cached, nil
|
|
}
|
|
|
|
config, err := m.Store.Get()
|
|
if err != nil {
|
|
if errors.Is(err, ErrConfigNotFound) {
|
|
m.cached = NewDefaultConfig()
|
|
return m.cached, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Try to apply migrations
|
|
m.migrateOnce.Do(func() {
|
|
m.migrateErr = m.migrate(config)
|
|
})
|
|
if m.migrateErr != nil {
|
|
return nil, m.migrateErr
|
|
}
|
|
|
|
// Validate the config
|
|
if err := ValidateConfig(config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Finally cache it for performance
|
|
m.cached = config
|
|
|
|
return config, err
|
|
}
|
|
|
|
func (m *ConfigManager) Update(config *v1.Config) error {
|
|
m.cachedMu.Lock()
|
|
defer m.cachedMu.Unlock()
|
|
|
|
if err := ValidateConfig(config); err != nil {
|
|
return err
|
|
}
|
|
|
|
err := m.Store.Update(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m.cached = config
|
|
m.OnChange.Emit(struct{}{})
|
|
return nil
|
|
}
|
|
|
|
type ConfigStore interface {
|
|
Get() (*v1.Config, error)
|
|
Update(config *v1.Config) error
|
|
}
|
|
|
|
func NewDefaultConfig() *v1.Config {
|
|
cfg := &v1.Config{
|
|
Version: migrations.CurrentVersion,
|
|
Instance: "",
|
|
Repos: []*v1.Repo{},
|
|
Plans: []*v1.Plan{},
|
|
Auth: &v1.Auth{
|
|
Disabled: true,
|
|
},
|
|
}
|
|
_, err := PopulateRequiredFields(cfg)
|
|
if err != nil {
|
|
zap.S().Fatalf("failed to populate required fields in default config: %v", err)
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
func PopulateRequiredFields(config *v1.Config) (mutated bool, err error) {
|
|
if config.GetMultihost() == nil {
|
|
config.Multihost = &v1.Multihost{}
|
|
mutated = true
|
|
}
|
|
if config.GetMultihost().Identity == nil {
|
|
identity, err := cryptoutil.GeneratePrivateKey()
|
|
if err != nil {
|
|
return false, fmt.Errorf("generate private key: %w", err)
|
|
}
|
|
config.GetMultihost().Identity = identity
|
|
mutated = true
|
|
}
|
|
return
|
|
}
|