feat(config): create default repo

Ayman Bagabas created

Change summary

server/config/config.go  | 30 +++++++++++++++---
server/config/default.go | 69 ++++++++++++++++++++++++++++++++++++++++++
server/server.go         | 17 ++++++---
3 files changed, 105 insertions(+), 11 deletions(-)

Detailed changes

server/config/config.go 🔗

@@ -23,6 +23,8 @@ type Callbacks interface {
 
 // SSHConfig is the SSH configuration for the server.
 type SSHConfig struct {
+	Key           string `env:"KEY"`
+	KeyPath       string `env:"KEY_PATH" envDefault:"soft_serve"`
 	Port          int    `env:"PORT" envDefault:"23231"`
 	AllowKeyless  bool   `env:"ALLOW_KEYLESS" envDefault:"true"`
 	AllowPassword bool   `env:"ALLOW_PASSWORD" envDefault:"false"`
@@ -115,7 +117,12 @@ func (c *Config) SSHPath() string {
 
 // PrivateKeyPath returns the path to the SSH key.
 func (c *Config) PrivateKeyPath() string {
-	return filepath.Join(c.SSHPath(), "soft_serve")
+	return filepath.Join(c.SSHPath(), c.SSH.KeyPath)
+}
+
+// DBPath returns the path to the database.
+func (c *Config) DBPath() string {
+	return filepath.Join(c.DataPath, "db", "soft-serve.db")
 }
 
 // DefaultConfig returns a Config with the values populated with the defaults
@@ -142,20 +149,27 @@ func DefaultConfig() *Config {
 		migrateWarn = true
 	}
 	if migrateWarn {
-		log.Printf("warning: please run `soft serve --migrate` to migrate your server and configuration.")
+		log.Printf("warning: please run `soft serve migrate` to migrate your server and configuration.")
+	}
+	// init data path and db
+	if err := os.MkdirAll(cfg.RepoPath(), 0755); err != nil {
+		log.Fatalln(err)
+	}
+	if err := cfg.createDefaultConfigRepo(); err != nil {
+		log.Fatalln(err)
 	}
 	var db db.Store
 	switch cfg.Db.Driver {
 	case "sqlite":
-		if err := os.MkdirAll(filepath.Join(cfg.DataPath, "db"), 0755); err != nil {
+		if err := os.MkdirAll(filepath.Dir(cfg.DBPath()), 0755); err != nil {
 			log.Fatalln(err)
 		}
-		db, err = sqlite.New(filepath.Join(cfg.DataPath, "db", "soft-serve.db"))
+		db, err = sqlite.New(cfg.DBPath())
 		if err != nil {
 			log.Fatalln(err)
 		}
 	}
-	return cfg.WithDB(db)
+	return cfg.WithDB(db).WithDataPath(cfg.DataPath)
 }
 
 // DB returns the database for the configuration.
@@ -174,3 +188,9 @@ func (c *Config) WithDB(db db.Store) *Config {
 	c.db = db
 	return c
 }
+
+// WithDataPath sets the data path for the configuration.
+func (c *Config) WithDataPath(path string) *Config {
+	c.DataPath = path
+	return c
+}

server/config/default.go 🔗

@@ -0,0 +1,69 @@
+package config
+
+import (
+	"errors"
+	"path/filepath"
+	"time"
+
+	"github.com/go-git/go-billy/v5/memfs"
+	gogit "github.com/go-git/go-git/v5"
+	"github.com/go-git/go-git/v5/plumbing/object"
+	"github.com/go-git/go-git/v5/plumbing/transport"
+	"github.com/go-git/go-git/v5/storage/memory"
+)
+
+const (
+	defaultConfigRepo = "config"
+	defaultReadme     = "# Soft Serve\n\n Welcome! You can configure your Soft Serve server by cloning this repo and pushing changes.\n\n```\ngit clone ssh://localhost:23231/config\n```"
+)
+
+func (cfg *Config) createDefaultConfigRepo() error {
+	rp := filepath.Join(cfg.RepoPath(), defaultConfigRepo) + ".git"
+	_, err := gogit.PlainOpen(rp)
+	if errors.Is(err, gogit.ErrRepositoryNotExists) {
+		repo, err := gogit.PlainInit(rp, true)
+		if err != nil {
+			return err
+		}
+		repo, err = gogit.Clone(memory.NewStorage(), memfs.New(), &gogit.CloneOptions{
+			URL: rp,
+		})
+		if err != nil && err != transport.ErrEmptyRemoteRepository {
+			return err
+		}
+		wt, err := repo.Worktree()
+		if err != nil {
+			return err
+		}
+		rm, err := wt.Filesystem.Create("README.md")
+		if err != nil {
+			return err
+		}
+		_, err = rm.Write([]byte(defaultReadme))
+		if err != nil {
+			return err
+		}
+		_, err = wt.Add("README.md")
+		if err != nil {
+			return err
+		}
+		author := object.Signature{
+			Name:  "Soft Serve Server",
+			Email: "vt100@charm.sh",
+			When:  time.Now(),
+		}
+		_, err = wt.Commit("Default init", &gogit.CommitOptions{
+			All:       true,
+			Author:    &author,
+			Committer: &author,
+		})
+		if err != nil {
+			return err
+		}
+		err = repo.Push(&gogit.PushOptions{})
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

server/server.go 🔗

@@ -47,18 +47,23 @@ func NewServer(cfg *config.Config) *Server {
 		),
 	}
 
-	opts := []ssh.Option{ssh.PublicKeyAuth(cfg.PublicKeyHandler)}
+	opts := []ssh.Option{
+		wish.WithAddress(fmt.Sprintf("%s:%d", cfg.Host, cfg.SSH.Port)),
+		wish.WithPublicKeyAuth(cfg.PublicKeyHandler),
+		wish.WithMiddleware(mw...),
+	}
 	if cfg.SSH.AllowKeyless {
 		opts = append(opts, ssh.KeyboardInteractiveAuth(cfg.KeyboardInteractiveHandler))
 	}
 	if cfg.SSH.AllowPassword {
 		opts = append(opts, ssh.PasswordAuth(cfg.PasswordHandler))
 	}
-	opts = append(opts,
-		wish.WithAddress(fmt.Sprintf("%s:%d", cfg.Host, cfg.SSH.Port)),
-		wish.WithHostKeyPath(cfg.PrivateKeyPath()),
-		wish.WithMiddleware(mw...),
-	)
+	if cfg.SSH.Key != "" {
+		opts = append(opts, wish.WithHostKeyPEM([]byte(cfg.SSH.Key)))
+	} else {
+		opts = append(opts, wish.WithHostKeyPath(cfg.PrivateKeyPath()))
+	}
+	opts = append(opts)
 	sh, err := wish.NewServer(opts...)
 	if err != nil {
 		log.Fatalln(err)