Detailed changes
@@ -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
}
@@ -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",
+ })
+}
@@ -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
@@ -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=
@@ -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)
@@ -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
+}
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMwLvyV3ouVrTysUYGoJdl5Vgn5BACKov+n9PlzfPwH a@b