78 lines
2.2 KiB
Go
78 lines
2.2 KiB
Go
package state
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
|
|
"github.com/example/ollama-fair-gateway/internal/config"
|
|
)
|
|
|
|
const bootstrapSecret = "<bootstrap-secret>"
|
|
|
|
type ConfigStore struct{ file AtomicJSON }
|
|
|
|
func NewConfigStore(path string) *ConfigStore {
|
|
return &ConfigStore{file: AtomicJSON{Path: path, Mode: 0600}}
|
|
}
|
|
func (s *ConfigStore) Path() string { return s.file.Path }
|
|
|
|
// LoadWithBootstrap restores secrets that deliberately remain owned by the
|
|
// startup configuration and are never copied into the persistent UI override.
|
|
func (s *ConfigStore) LoadWithBootstrap(base *config.Config) (*config.Config, error) {
|
|
b, err := os.ReadFile(s.file.Path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var c config.Config
|
|
if err := json.Unmarshal(b, &c); err != nil {
|
|
return nil, err
|
|
}
|
|
c.Auth.APIKeys = append([]config.APIKeyConfig(nil), base.Auth.APIKeys...)
|
|
if c.UI.SessionSecret == "" || c.UI.SessionSecret == bootstrapSecret {
|
|
c.UI.SessionSecret = base.UI.SessionSecret
|
|
}
|
|
if c.UI.OIDC.ClientSecret == "" || c.UI.OIDC.ClientSecret == bootstrapSecret {
|
|
c.UI.OIDC.ClientSecret = base.UI.OIDC.ClientSecret
|
|
}
|
|
merged, err := json.Marshal(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return config.ParseBytes(merged)
|
|
}
|
|
|
|
// Save writes a complete config override but strips bootstrap-owned secrets.
|
|
func (s *ConfigStore) Save(c *config.Config) error {
|
|
b, err := json.Marshal(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var clean config.Config
|
|
if err := json.Unmarshal(b, &clean); err != nil {
|
|
return err
|
|
}
|
|
for i := range clean.Auth.APIKeys {
|
|
clean.Auth.APIKeys[i].Key = bootstrapSecret
|
|
}
|
|
if clean.UI.SessionSecret != "" {
|
|
clean.UI.SessionSecret = bootstrapSecret
|
|
}
|
|
if clean.UI.OIDC.ClientSecret != "" {
|
|
clean.UI.OIDC.ClientSecret = bootstrapSecret
|
|
}
|
|
return s.file.Save(&clean)
|
|
}
|
|
func (s *ConfigStore) Delete() error { return s.file.Delete() }
|
|
func (s *ConfigStore) Exists() bool { _, err := os.Stat(s.file.Path); return err == nil }
|
|
func (s *ConfigStore) LoadIfExists(base *config.Config) (*config.Config, bool, error) {
|
|
c, err := s.LoadWithBootstrap(base)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil, false, nil
|
|
}
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
return c, true, nil
|
|
}
|