diff --git a/config/config.go b/config/config.go index 3180492f39c2ba6e3062f5b587d7039fdd92816f..7155c83e5f0e65b747764c265ab9de00be0fa5f4 100644 --- a/config/config.go +++ b/config/config.go @@ -4,7 +4,7 @@ import ( "log" "path/filepath" - "github.com/meowgorithm/babyenv" + "github.com/caarlos0/env/v6" ) // Callbacks provides an interface that can be used to run callbacks on different events. @@ -16,12 +16,12 @@ type Callbacks interface { // Config is the configuration for Soft Serve. type Config struct { - Host string `env:"SOFT_SERVE_HOST"` - Port int `env:"SOFT_SERVE_PORT"` - KeyPath string `env:"SOFT_SERVE_KEY_PATH"` - RepoPath string `env:"SOFT_SERVE_REPO_PATH"` - InitialAdminKey string `env:"SOFT_SERVE_INITIAL_ADMIN_KEY"` - Callbacks Callbacks + Host string `env:"SOFT_SERVE_HOST"` + Port int `env:"SOFT_SERVE_PORT"` + KeyPath string `env:"SOFT_SERVE_KEY_PATH"` + RepoPath string `env:"SOFT_SERVE_REPO_PATH"` + InitialAdminKeys []string `env:"SOFT_SERVE_INITIAL_ADMIN_KEY" envSeparator:"\n"` + Callbacks Callbacks } func (c *Config) applyDefaults() { @@ -41,8 +41,7 @@ func (c *Config) applyDefaults() { // or specified environment variables. func DefaultConfig() *Config { var scfg Config - err := babyenv.Parse(&scfg) - if err != nil { + if err := env.Parse(&scfg); err != nil { log.Fatalln(err) } scfg.applyDefaults() @@ -50,7 +49,7 @@ func DefaultConfig() *Config { } // WithCallbacks applies the given Callbacks to the configuration. -func (cfg *Config) WithCallbacks(c Callbacks) *Config { - cfg.Callbacks = c - return cfg +func (c *Config) WithCallbacks(callbacks Callbacks) *Config { + c.Callbacks = callbacks + return c } diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6a8eb7e4890f4ff535b7523491936a6273590755 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,19 @@ +package config + +import ( + "os" + "testing" + + "github.com/matryer/is" +) + +func TestParseMultipleKeys(t *testing.T) { + is := is.New(t) + is.NoErr(os.Setenv("SOFT_SERVE_INITIAL_ADMIN_KEY", "testdata/k1.pub\ntestdata/k2.pub")) + t.Cleanup(func() { is.NoErr(os.Unsetenv("SOFT_SERVE_INITIAL_ADMIN_KEY")) }) + cfg := DefaultConfig() + is.Equal(cfg.InitialAdminKeys, []string{ + "testdata/k1.pub", + "testdata/k2.pub", + }) +} diff --git a/go.mod b/go.mod index 55ad5bf8686f8b9a3e8a1f6dda5492a5c902a8b4..515eae0295bd259da2b86267ba6ee2857a4737f3 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/charmbracelet/soft-serve go 1.17 require ( + github.com/caarlos0/env/v6 v6.9.1 github.com/charmbracelet/bubbles v0.10.0 github.com/charmbracelet/bubbletea v0.19.3 github.com/charmbracelet/glamour v0.4.0 @@ -12,8 +13,9 @@ require ( github.com/gliderlabs/ssh v0.3.3 github.com/go-git/go-billy/v5 v5.3.1 github.com/go-git/go-git/v5 v5.4.2 - github.com/meowgorithm/babyenv v1.3.1 + github.com/matryer/is v1.2.0 github.com/muesli/reflow v0.3.0 + golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce gopkg.in/yaml.v2 v2.4.0 ) @@ -47,7 +49,6 @@ require ( github.com/xanzy/ssh-agent v0.3.1 // indirect github.com/yuin/goldmark v1.4.4 // indirect github.com/yuin/goldmark-emoji v1.0.1 // indirect - golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect golang.org/x/net v0.0.0-20220111093109-d55c255bac03 // indirect golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect diff --git a/go.sum b/go.sum index b84386d53e26f12556700364c3514efe51000f93..d57c68afdd99e72d61acb01deaa45791f4d883e0 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/caarlos0/env/v6 v6.9.1 h1:zOkkjM0F6ltnQ5eBX6IPI41UP/KDGEK7rRPwGCNos8k= +github.com/caarlos0/env/v6 v6.9.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= github.com/charmbracelet/bubbles v0.10.0 h1:ZYqBwnmFGp91HSRRbhxKq5jr6bUPsVUBdkrGGWtv0Wk= github.com/charmbracelet/bubbles v0.10.0/go.mod h1:4tiDrWzH1MTD4t5NnrcthaedmI3MxU0FIutax7//dvk= github.com/charmbracelet/bubbletea v0.19.0/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA= @@ -94,8 +96,6 @@ github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/meowgorithm/babyenv v1.3.1 h1:18ZEYIgbzoFQfRLF9+lxjRfk/ui6w8U0FWl07CgWvvc= -github.com/meowgorithm/babyenv v1.3.1/go.mod h1:lwNX+J6AGBFqNrMZ2PTLkM6SO+W4X8DOg9zBDO4j3Ig= github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y= github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE= diff --git a/internal/config/config.go b/internal/config/config.go index a0fc5e2231f99c34b35a46eb1724c107bd66bc79..e9da10ada7ca0cc131fa87292e989bd8f96c817e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -53,15 +53,19 @@ func NewConfig(cfg *config.Config) (*Config, error) { var displayHost string host := cfg.Host port := cfg.Port - pk := cfg.InitialAdminKey - if bts, err := os.ReadFile(pk); err == nil { - // pk is a file, set its contents as pk - pk = string(bts) - } - // it is a valid ssh key, nothing to do - if _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk)); err != nil { - return nil, fmt.Errorf("invalid initial admin key: %w", err) + pks := make([]string, 0, len(cfg.InitialAdminKeys)) + for _, k := range cfg.InitialAdminKeys { + var pk = strings.TrimSpace(k) + if bts, err := os.ReadFile(k); err == nil { + // pk is a file, set its contents as pk + pk = string(bts) + } + // it is a valid ssh key, nothing to do + if _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk)); err != nil { + return nil, fmt.Errorf("invalid initial admin key %q: %w", k, err) + } + pks = append(pks, pk) } rs := git.NewRepoSource(cfg.RepoPath) @@ -71,7 +75,7 @@ func NewConfig(cfg *config.Config) (*Config, error) { c.Host = cfg.Host c.Port = port c.Source = rs - if pk == "" { + if len(pks) == 0 { anonAccess = "read-write" } else { anonAccess = "no-access" @@ -82,14 +86,14 @@ func NewConfig(cfg *config.Config) (*Config, error) { displayHost = host } yamlConfig := fmt.Sprintf(defaultConfig, displayHost, port, anonAccess) - if pk != "" { - pks := "" - for _, key := range strings.Split(strings.TrimSpace(pk), "\n") { - pks += fmt.Sprintf(" - %s\n", key) - } - yamlUsers = fmt.Sprintf(hasKeyUserConfig, pks) - } else { + if len(pks) == 0 { yamlUsers = defaultUserConfig + } else { + var result string + for _, pk := range pks { + result += fmt.Sprintf(" - %s\n", pk) + } + yamlUsers = fmt.Sprintf(hasKeyUserConfig, result) } yaml := fmt.Sprintf("%s%s%s", yamlConfig, yamlUsers, exampleUserConfig) err := c.createDefaultConfigRepo(yaml) diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..cbcdc0cb87540a00f323646fc1306651da374a46 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,35 @@ +package config + +import ( + "testing" + + "github.com/charmbracelet/soft-serve/config" + "github.com/matryer/is" +) + +func TestMultipleInitialKeys(t *testing.T) { + cfg, err := NewConfig(&config.Config{ + RepoPath: t.TempDir(), + KeyPath: t.TempDir(), + InitialAdminKeys: []string{ + "testdata/k1.pub", + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxIobhwtfdwN7m1TFt9wx3PsfvcAkISGPxmbmbauST8 a@b", + }, + }) + is := is.New(t) + is.NoErr(err) + is.Equal(cfg.Users[0].PublicKeys, []string{ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMwLvyV3ouVrTysUYGoJdl5Vgn5BACKov+n9PlzfPwH a@b", + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxIobhwtfdwN7m1TFt9wx3PsfvcAkISGPxmbmbauST8 a@b", + }) // should have both keys +} + +func TestEmptyInitialKeys(t *testing.T) { + cfg, err := NewConfig(&config.Config{ + RepoPath: t.TempDir(), + KeyPath: t.TempDir(), + }) + is := is.New(t) + is.NoErr(err) + is.Equal(len(cfg.Users), 0) // should not have any users +} diff --git a/internal/config/testdata/k1.pub b/internal/config/testdata/k1.pub new file mode 100644 index 0000000000000000000000000000000000000000..d82e29394d343e6e36bc1759b06689a399ea80a4 --- /dev/null +++ b/internal/config/testdata/k1.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMwLvyV3ouVrTysUYGoJdl5Vgn5BACKov+n9PlzfPwH a@b