fix(server): hooks config path

Ayman Bagabas created

Make sure data path is defined correctly.

Fixes: 72c96afd58a0 ("fix(config): mkdir before writing config")

Change summary

cmd/soft/hook.go              |   5 -
server/backend/sqlite/user.go |   5 -
server/cmd/hook.go            |   3 
server/config/config.go       | 104 ++++++++++++++++++++----------------
4 files changed, 63 insertions(+), 54 deletions(-)

Detailed changes

cmd/soft/hook.go 🔗

@@ -141,9 +141,6 @@ func commonInit() (c *gossh.Client, s *gossh.Session, err error) {
 		return
 	}
 
-	// Use absolute path.
-	cfg.DataPath = filepath.Dir(configPath)
-
 	// Git runs the hook within the repository's directory.
 	// Get the working directory to determine the repository name.
 	wd, err := os.Getwd()
@@ -161,7 +158,7 @@ func commonInit() (c *gossh.Client, s *gossh.Session, err error) {
 		return
 	}
 	repoName := strings.TrimPrefix(wd, rs)
-	repoName = strings.TrimPrefix(repoName, fmt.Sprintf("%c", os.PathSeparator))
+	repoName = strings.TrimPrefix(repoName, string(os.PathSeparator))
 	c, err = newClient(cfg)
 	if err != nil {
 		return

server/backend/sqlite/user.go 🔗

@@ -118,10 +118,7 @@ func (d *SqliteBackend) AccessLevel(repo string, username string) backend.Access
 //
 // It implements backend.Backend.
 func (d *SqliteBackend) AccessLevelByPublicKey(repo string, pk ssh.PublicKey) backend.AccessLevel {
-	if ik, _, err := backend.ParseAuthorizedKey(d.cfg.InternalPublicKey); err == nil && backend.KeysEqual(ik, pk) {
-		return backend.AdminAccess
-	}
-	for _, k := range d.cfg.InitialAdminKeys {
+	for _, k := range append(d.cfg.InitialAdminKeys, d.cfg.InternalPublicKey) {
 		ik, _, err := backend.ParseAuthorizedKey(k)
 		if err == nil && backend.KeysEqual(pk, ik) {
 			return backend.AdminAccess

server/cmd/hook.go 🔗

@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/charmbracelet/keygen"
+	"github.com/charmbracelet/soft-serve/server/backend"
 	"github.com/charmbracelet/soft-serve/server/hooks"
 	"github.com/charmbracelet/ssh"
 	"github.com/spf13/cobra"
@@ -122,7 +123,7 @@ func checkIfInternal(cmd *cobra.Command, _ []string) error {
 		logger.Errorf("failed to read internal key: %v", err)
 		return err
 	}
-	if !ssh.KeysEqual(pk, kp.PublicKey()) {
+	if !backend.KeysEqual(pk, kp.PublicKey()) {
 		return ErrUnauthorized
 	}
 	return nil

server/config/config.go 🔗

@@ -1,6 +1,8 @@
 package config
 
 import (
+	"errors"
+	"fmt"
 	"os"
 	"path/filepath"
 	"strings"
@@ -104,41 +106,8 @@ type Config struct {
 	ClientPublicKey string `yaml:"-"`
 }
 
-// ParseConfig parses the configuration from the given file.
-func ParseConfig(path string) (*Config, error) {
-	cfg := &Config{}
-	f, err := os.Open(path)
-	if err != nil {
-		return nil, err
-	}
-	defer f.Close() // nolint: errcheck
-	if err := yaml.NewDecoder(f).Decode(cfg); err != nil {
-		return nil, err
-	}
-
-	if err := cfg.init(); err != nil {
-		return nil, err
-	}
-
-	return cfg, nil
-}
-
-// WriteConfig writes the configuration to the given file.
-func WriteConfig(path string, cfg *Config) error {
-	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
-		return err
-	}
-	return os.WriteFile(path, []byte(newConfigFile(cfg)), 0o600) // nolint: errcheck
-}
-
-// DefaultConfig returns a Config with the values populated with the defaults
-// or specified environment variables.
-func DefaultConfig() *Config {
-	dataPath := os.Getenv("SOFT_SERVE_DATA_PATH")
-	if dataPath == "" {
-		dataPath = "data"
-	}
-
+func parseConfig(path string) (*Config, error) {
+	dataPath := filepath.Dir(path)
 	cfg := &Config{
 		Name:     "Soft Serve",
 		DataPath: dataPath,
@@ -166,20 +135,65 @@ func DefaultConfig() *Config {
 		},
 	}
 
-	cp := filepath.Join(cfg.DataPath, "config.yaml")
-	f, err := os.Open(cp)
-	if err == nil {
-		defer f.Close() // nolint: errcheck
-		if err := yaml.NewDecoder(f).Decode(cfg); err != nil {
-			log.Error("failed to decode config", "err", err)
-		}
+	f, err := os.Open(path)
+	if err != nil {
+		return cfg, err
+	}
+
+	defer f.Close() // nolint: errcheck
+	if err := yaml.NewDecoder(f).Decode(cfg); err != nil {
+		return cfg, fmt.Errorf("decode config: %w", err)
 	}
 
 	// Override with environment variables
 	if err := env.Parse(cfg, env.Options{
 		Prefix: "SOFT_SERVE_",
 	}); err != nil {
-		log.Fatal(err)
+		return cfg, fmt.Errorf("parse environment variables: %w", err)
+	}
+
+	// Reset datapath to config dir.
+	// This is necessary because the environment variable may be set to
+	// a different directory.
+	cfg.DataPath = dataPath
+
+	return cfg, nil
+}
+
+// ParseConfig parses the configuration from the given file.
+func ParseConfig(path string) (*Config, error) {
+	cfg, err := parseConfig(path)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := cfg.validate(); err != nil {
+		return nil, err
+	}
+
+	return cfg, nil
+}
+
+// WriteConfig writes the configuration to the given file.
+func WriteConfig(path string, cfg *Config) error {
+	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
+		return err
+	}
+	return os.WriteFile(path, []byte(newConfigFile(cfg)), 0o600) // nolint: errcheck
+}
+
+// DefaultConfig returns a Config with the values populated with the defaults
+// or specified environment variables.
+func DefaultConfig() *Config {
+	dataPath := os.Getenv("SOFT_SERVE_DATA_PATH")
+	if dataPath == "" {
+		dataPath = "data"
+	}
+
+	cp := filepath.Join(dataPath, "config.yaml")
+	cfg, err := parseConfig(cp)
+	if err != nil && !errors.Is(err, os.ErrNotExist) {
+		log.Errorf("failed to parse config: %v", err)
 	}
 
 	// Write config if it doesn't exist
@@ -189,7 +203,7 @@ func DefaultConfig() *Config {
 		}
 	}
 
-	if err := cfg.init(); err != nil {
+	if err := cfg.validate(); err != nil {
 		log.Fatal(err)
 	}
 
@@ -202,7 +216,7 @@ func (c *Config) WithBackend(backend backend.Backend) *Config {
 	return c
 }
 
-func (c *Config) init() error {
+func (c *Config) validate() error {
 	// Use absolute paths
 	if !filepath.IsAbs(c.DataPath) {
 		dp, err := filepath.Abs(c.DataPath)