feat: add persistent proxy profile management
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type FileStore struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func NewFileStore(path string) *FileStore {
|
||||
return &FileStore{path: path}
|
||||
}
|
||||
|
||||
func ConfigPath() (string, error) {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("locate user configuration directory: %w", err)
|
||||
}
|
||||
return filepath.Join(configDir, "proxyctl", "profiles.json"), nil
|
||||
}
|
||||
|
||||
func (s *FileStore) Load() (State, error) {
|
||||
data, err := os.ReadFile(s.path)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return State{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return State{}, fmt.Errorf("read Profile configuration: %w", err)
|
||||
}
|
||||
var state State
|
||||
if err := json.Unmarshal(data, &state); err != nil {
|
||||
return State{}, fmt.Errorf("parse Profile configuration: %w", err)
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (s *FileStore) Save(state State) error {
|
||||
if err := os.MkdirAll(filepath.Dir(s.path), 0o700); err != nil {
|
||||
return fmt.Errorf("create Profile configuration directory: %w", err)
|
||||
}
|
||||
if err := os.Chmod(filepath.Dir(s.path), 0o700); err != nil {
|
||||
return fmt.Errorf("secure Profile configuration directory: %w", err)
|
||||
}
|
||||
data, err := json.MarshalIndent(state, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode Profile configuration: %w", err)
|
||||
}
|
||||
data = append(data, '\n')
|
||||
temp, err := os.CreateTemp(filepath.Dir(s.path), ".profiles-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temporary Profile configuration: %w", err)
|
||||
}
|
||||
tempName := temp.Name()
|
||||
defer os.Remove(tempName)
|
||||
if err := temp.Chmod(0o600); err != nil {
|
||||
temp.Close()
|
||||
return fmt.Errorf("secure temporary Profile configuration: %w", err)
|
||||
}
|
||||
if _, err := temp.Write(data); err != nil {
|
||||
temp.Close()
|
||||
return fmt.Errorf("write temporary Profile configuration: %w", err)
|
||||
}
|
||||
if err := temp.Sync(); err != nil {
|
||||
temp.Close()
|
||||
return fmt.Errorf("sync temporary Profile configuration: %w", err)
|
||||
}
|
||||
if err := temp.Close(); err != nil {
|
||||
return fmt.Errorf("close temporary Profile configuration: %w", err)
|
||||
}
|
||||
if err := os.Rename(tempName, s.path); err != nil {
|
||||
return fmt.Errorf("replace Profile configuration: %w", err)
|
||||
}
|
||||
if err := os.Chmod(s.path, 0o600); err != nil {
|
||||
return fmt.Errorf("secure Profile configuration: %w", err)
|
||||
}
|
||||
dir, err := os.Open(filepath.Dir(s.path))
|
||||
if err == nil {
|
||||
err = dir.Sync()
|
||||
_ = dir.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("sync Profile configuration directory: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user