From e76b7ef5a2660310ff23963f7747ca0047bd8d53 Mon Sep 17 00:00:00 2001 From: Gareth George Date: Mon, 27 Nov 2023 18:40:08 -0800 Subject: [PATCH] chore: cleanup environment variable option parsing and config handling --- README.md | 3 +- hack/build.sh | 5 +++ hack/install-deps.sh | 5 +++ internal/api/server.go | 17 +++++----- internal/config/config.go | 12 ++----- internal/envopts/envopts.go | 46 +++++++++++++++++++++++++++ internal/orchestrator/orchestrator.go | 12 +++---- pkg/restic/outputs_test.go | 2 +- proto/build.sh | 2 -- proto/update.sh | 3 ++ resticui.go | 23 ++++++++------ 11 files changed, 91 insertions(+), 39 deletions(-) create mode 100644 hack/build.sh create mode 100644 hack/install-deps.sh create mode 100644 internal/envopts/envopts.go delete mode 100755 proto/build.sh create mode 100755 proto/update.sh diff --git a/README.md b/README.md index 7addaca2..b351ed19 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,11 @@ Browsing snapshots: ## Dev Depedencies -**Basic Dependencies** +**Build Dependencies** * Node.JS for UI development * Go 1.21 or greater for server development + * go.rice `go install github.com/GeertJohan/go.rice@latest` and `go install github.com/GeertJohan/go.rice/rice@latest` **To Edit Protobuffers** ```sh diff --git a/hack/build.sh b/hack/build.sh new file mode 100644 index 00000000..91216965 --- /dev/null +++ b/hack/build.sh @@ -0,0 +1,5 @@ +#! /bin/sh + +(cd proto && ./build.sh) + +go build ./.. diff --git a/hack/install-deps.sh b/hack/install-deps.sh new file mode 100644 index 00000000..030c1544 --- /dev/null +++ b/hack/install-deps.sh @@ -0,0 +1,5 @@ +#! /bin/sh + +go install github.com/GeertJohan/go.rice/rice@latest +go install github.com/GeertJohan/go.rice@latest +pip install lastversion diff --git a/internal/api/server.go b/internal/api/server.go index 63fa5b50..68991fdf 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -22,13 +22,14 @@ import ( type Server struct { *v1.UnimplementedResticUIServer + config config.ConfigStore orchestrator *orchestrator.Orchestrator oplog *oplog.OpLog } var _ v1.ResticUIServer = &Server{} -func NewServer(orchestrator *orchestrator.Orchestrator, oplog *oplog.OpLog) *Server { +func NewServer(config config.ConfigStore, orchestrator *orchestrator.Orchestrator, oplog *oplog.OpLog) *Server { s := &Server{ orchestrator: orchestrator, oplog: oplog, @@ -39,12 +40,12 @@ func NewServer(orchestrator *orchestrator.Orchestrator, oplog *oplog.OpLog) *Ser // GetConfig implements GET /v1/config func (s *Server) GetConfig(ctx context.Context, empty *emptypb.Empty) (*v1.Config, error) { - return config.Default.Get() + return s.config.Get() } // SetConfig implements POST /v1/config func (s *Server) SetConfig(ctx context.Context, c *v1.Config) (*v1.Config, error) { - existing, err := config.Default.Get() + existing, err := s.config.Get() if err != nil { return nil, fmt.Errorf("failed to check current config: %w", err) } @@ -55,21 +56,21 @@ func (s *Server) SetConfig(ctx context.Context, c *v1.Config) (*v1.Config, error } c.Modno += 1 - if err := config.Default.Update(c); err != nil { + if err := s.config.Update(c); err != nil { return nil, fmt.Errorf("failed to update config: %w", err) } - newConfig, err := config.Default.Get() + newConfig, err := s.config.Get() if err != nil { return nil, fmt.Errorf("failed to get newly set config: %w", err) } s.orchestrator.ApplyConfig(newConfig) - return config.Default.Get() + return newConfig, nil } // AddRepo implements POST /v1/config/repo, it includes validation that the repo can be initialized. func (s *Server) AddRepo(ctx context.Context, repo *v1.Repo) (*v1.Config, error) { - c, err := config.Default.Get() + c, err := s.config.Get() if err != nil { return nil, fmt.Errorf("failed to get config: %w", err) } @@ -88,7 +89,7 @@ func (s *Server) AddRepo(ctx context.Context, repo *v1.Repo) (*v1.Config, error) } zap.L().Debug("Updating config") - if err := config.Default.Update(c); err != nil { + if err := s.config.Update(c); err != nil { return nil, fmt.Errorf("failed to update config: %w", err) } diff --git a/internal/config/config.go b/internal/config/config.go index 87018875..aa45c5c1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,7 +2,6 @@ package config import ( "errors" - "flag" "fmt" "os" "path" @@ -12,13 +11,6 @@ import ( ) var ErrConfigNotFound = fmt.Errorf("config not found") -var configDirFlag = flag.String("config_dir", "", "The directory to store the config file") - -var Default ConfigStore = &CachingValidatingStore{ - ConfigStore: &JsonFileStore{ - Path: path.Join(configDir(*configDirFlag), "config.json"), - }, -} type ConfigStore interface { Get() (*v1.Config, error) @@ -51,7 +43,7 @@ func configDir(override string) string { type CachingValidatingStore struct { ConfigStore - mu sync.Mutex + mu sync.Mutex config *v1.Config } @@ -94,4 +86,4 @@ func (c *CachingValidatingStore) Update(config *v1.Config) error { c.config = config return nil -} \ No newline at end of file +} diff --git a/internal/envopts/envopts.go b/internal/envopts/envopts.go new file mode 100644 index 00000000..99e0deb6 --- /dev/null +++ b/internal/envopts/envopts.go @@ -0,0 +1,46 @@ +package envopts + +import ( + "fmt" + "os" + "path" + "strings" +) + +func ConfigFilePath() string { + if val := os.Getenv("RESTICUI_CONFIG_PATH"); val != "" { + return val + } + if val := os.Getenv("XDG_CONFIG_HOME"); val != "" { + return path.Join(val, "resticui/config.json") + } + return path.Join(getHomeDir(), ".config/resticui/config.json") +} + +func DataDir() string { + if val := os.Getenv("RESTICUI_DATA_DIR"); val != "" { + return val + } + if val := os.Getenv("XDG_DATA_HOME"); val != "" { + return path.Join(val, "resticui") + } + return path.Join(getHomeDir(), ".local/share/resticui") +} + +func BindAddress() string { + if val := os.Getenv("RESTICUI_PORT"); val != "" { + if !strings.Contains(val, ":") { + return ":" + val + } + return val + } + return ":9898" +} + +func getHomeDir() string { + home, err := os.UserHomeDir() + if err != nil { + panic(fmt.Errorf("couldn't determine home directory: %v", err)) + } + return home +} diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 220a060e..ba317bef 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -30,15 +30,11 @@ type Orchestrator struct { externTasks chan Task // externTasks is a channel that externally added tasks can be added to, they will be consumed by Run() } -func NewOrchestrator(configProvider config.ConfigStore, oplog *oplog.OpLog) (*Orchestrator, error) { - cfg, err := configProvider.Get() - if err != nil { - return nil, fmt.Errorf("failed to get config: %w", err) - } - +func NewOrchestrator(cfg *v1.Config, oplog *oplog.OpLog) (*Orchestrator, error) { return &Orchestrator{ - config: cfg, - OpLog: oplog, + config: cfg, + OpLog: oplog, + // repoPool created with a memory store to ensure the config is updated in an atomic operation with the repo pool's config value. repoPool: newResticRepoPool(&config.MemoryStore{Config: cfg}), externTasks: make(chan Task, 2), }, nil diff --git a/pkg/restic/outputs_test.go b/pkg/restic/outputs_test.go index 1b218b45..2a142321 100644 --- a/pkg/restic/outputs_test.go +++ b/pkg/restic/outputs_test.go @@ -9,7 +9,7 @@ import ( func TestReadBackupProgressEntries(t *testing.T) { t.Parallel() testInput := `{"message_type":"status","percent_done":0,"total_files":1,"total_bytes":15} - {"message_type":"summary","files_new":0,"files_changed":0,"files_unmodified":166,"dirs_new":0,"dirs_changed":0,"dirs_unmodified":128,"data_blobs":0,"tree_blobs":0,"data_added":0,"total_files_processed":166,"total_bytes_processed":16754463,"total_duration":0.235433378,"id":"d4558b360cc1b7966e416e010382ab8feb49d14da7832266832d69a43af10147"}` + {"message_type":"summary","files_new":0,"files_changed":0,"files_unmodified":166,"dirs_new":0,"dirs_changed":0,"dirs_unmodified":128,"data_blobs":0,"tree_blobs":0,"data_added":0,"total_files_processed":166,"total_bytes_processed":16754463,"total_duration":0.235433378,"snapshot_id":"d4558b360cc1b7966e416e010382ab8feb49d14da7832266832d69a43af10147"}` b := bytes.NewBuffer([]byte(testInput)) diff --git a/proto/build.sh b/proto/build.sh deleted file mode 100755 index 0676734d..00000000 --- a/proto/build.sh +++ /dev/null @@ -1,2 +0,0 @@ -#! /bin/bash -buf generate diff --git a/proto/update.sh b/proto/update.sh new file mode 100755 index 00000000..a061fe7b --- /dev/null +++ b/proto/update.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +buf generate diff --git a/resticui.go b/resticui.go index 82dc5887..8fcd34b7 100644 --- a/resticui.go +++ b/resticui.go @@ -3,7 +3,6 @@ package main import ( "context" "errors" - "fmt" "io/fs" "net/http" "os" @@ -14,6 +13,7 @@ import ( "github.com/garethgeorge/resticui/internal/api" "github.com/garethgeorge/resticui/internal/config" + "github.com/garethgeorge/resticui/internal/envopts" "github.com/garethgeorge/resticui/internal/oplog" "github.com/garethgeorge/resticui/internal/orchestrator" static "github.com/garethgeorge/resticui/webui" @@ -25,16 +25,14 @@ import ( ) func main() { - port := os.Getenv("RESTICUI_PORT") - if port == "" { - port = "9898" - } - ctx := context.Background() ctx, cancel := context.WithCancel(ctx) go onterm(cancel) - if _, err := config.Default.Get(); err != nil { + configStore := createConfigProvider() + + cfg, err := configStore.Get() + if err != nil { zap.S().Fatalf("Error loading config: %v", err) } @@ -53,7 +51,7 @@ func main() { } defer oplog.Close() - orchestrator, err := orchestrator.NewOrchestrator(config.Default, oplog) + orchestrator, err := orchestrator.NewOrchestrator(cfg, oplog) if err != nil { zap.S().Fatalf("Error creating orchestrator: %v", err) } @@ -68,6 +66,7 @@ func main() { }() apiServer := api.NewServer( + configStore, orchestrator, // TODO: eliminate default config oplog, ) @@ -85,7 +84,7 @@ func main() { // Serve the HTTP gateway server := &http.Server{ - Addr: fmt.Sprintf(":%s", port), + Addr: envopts.BindAddress(), Handler: mux, } @@ -122,6 +121,12 @@ func init() { } } +func createConfigProvider() config.ConfigStore { + return &config.CachingValidatingStore{ + ConfigStore: &config.JsonFileStore{Path: envopts.ConfigFilePath()}, + } +} + func onterm(callback func()) { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, os.Interrupt, syscall.SIGTERM)