.gitignore 🔗
@@ -1,5 +1,5 @@
soft
-soft-serve
+data
dist
testdata
completions/
Ayman Bagabas created
* implement a fakedb for testing
.gitignore | 2
proto/provider.go | 12 ++
proto/repo.go | 56 +++++++++-
server/cmd/cat.go | 25 +++-
server/cmd/cmd.go | 8
server/cmd/git.go | 24 +++-
server/cmd/list.go | 22 ++-
server/cmd/reload.go | 6
server/config/access.go | 63 ++----------
server/config/config.go | 1
server/config/repo.go | 170 ++++++++++++++++++++++++++++++++++
server/config/user.go | 3
server/db/db.go | 15 ---
server/db/fakedb/db.go | 73 ++++++-------
server/db/sqlite/repo.go | 58 -----------
server/db/sqlite/sql.go | 20 ----
server/db/sqlite/sqlite.go | 82 ++-------------
server/file/file.go | 108 ---------------------
server/git/daemon/daemon_test.go | 2
server/server.go | 13 -
server/session.go | 12 +-
server/session_test.go | 12 +-
22 files changed, 361 insertions(+), 426 deletions(-)
@@ -1,5 +1,5 @@
soft
-soft-serve
+data
dist
testdata
completions/
@@ -1,7 +1,15 @@
package proto
-// Provider is a repository provider.
+// Provider is a Git repository provider.
type Provider interface {
// Open opens a repository.
- Open(name string) (RepositoryService, error)
+ Open(name string) (Repository, error)
+ // ListRepos lists all repositories.
+ ListRepos() ([]Metadata, error)
+}
+
+// MetadataProvider is a Git repository metadata provider.
+type MetadataProvider interface {
+ // Metadata gets a repository's metadata.
+ Metadata(name string) (Metadata, error)
}
@@ -1,17 +1,57 @@
package proto
-// Repository is Git repository.
-type Repository interface {
+import (
+ "path/filepath"
+
+ "github.com/charmbracelet/soft-serve/git"
+ "github.com/gobwas/glob"
+)
+
+// Metadata is a repository's metadata.
+type Metadata interface {
Name() string
ProjectName() string
Description() string
IsPrivate() bool
+ Collabs() []User
+ Open() (Repository, error)
+}
+
+// Repository is Git repository.
+type Repository interface {
+ Name() string
+ Repository() *git.Repository
}
-// RepositoryService is a service for managing repositories metadata.
-type RepositoryService interface {
- Repository
- SetProjectName(string) error
- SetDescription(string) error
- SetPrivate(bool) error
+// LatestFile returns the contents of the latest file at the specified path in
+// the repository and its file path.
+func LatestFile(r Repository, pattern string) (string, string, error) {
+ g := glob.MustCompile(pattern)
+ dir := filepath.Dir(pattern)
+ head, err := r.Repository().HEAD()
+ if err != nil {
+ return "", "", err
+ }
+ t, err := r.Repository().TreePath(head, dir)
+ if err != nil {
+ return "", "", err
+ }
+ ents, err := t.Entries()
+ if err != nil {
+ return "", "", err
+ }
+ for _, e := range ents {
+ fp := filepath.Join(dir, e.Name())
+ if e.IsTree() {
+ continue
+ }
+ if g.Match(fp) {
+ bts, err := e.Contents()
+ if err != nil {
+ return "", "", err
+ }
+ return string(bts), fp, nil
+ }
+ }
+ return "", "", git.ErrFileNotFound
}
@@ -7,7 +7,6 @@ import (
"github.com/alecthomas/chroma/lexers"
gansi "github.com/charmbracelet/glamour/ansi"
"github.com/charmbracelet/lipgloss"
- "github.com/charmbracelet/soft-serve/config"
"github.com/charmbracelet/soft-serve/proto"
"github.com/charmbracelet/soft-serve/ui/common"
"github.com/muesli/termenv"
@@ -32,27 +31,35 @@ func CatCommand() *cobra.Command {
Short: "Outputs the contents of the file at path.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
- ac, s := fromContext(cmd)
+ cfg, s := fromContext(cmd)
ps := strings.Split(args[0], "/")
- rn := ps[0]
+ rn := strings.TrimSuffix(ps[0], ".git")
fp := strings.Join(ps[1:], "/")
- auth := ac.AuthRepo(rn, s.PublicKey())
+ auth := cfg.AuthRepo(rn, s.PublicKey())
if auth < proto.ReadOnlyAccess {
return ErrUnauthorized
}
- var repo *config.Repo
+ var repo proto.Repository
repoExists := false
- for _, rp := range ac.Source.AllRepos() {
- if rp.Repo() == rn {
+ repos, err := cfg.ListRepos()
+ if err != nil {
+ return err
+ }
+ for _, rp := range repos {
+ if rp.Name() == rn {
+ re, err := rp.Open()
+ if err != nil {
+ continue
+ }
repoExists = true
- repo = rp
+ repo = re
break
}
}
if !repoExists {
return ErrRepoNotFound
}
- c, _, err := repo.LatestFile(fp)
+ c, _, err := proto.LatestFile(repo, fp)
if err != nil {
return err
}
@@ -3,7 +3,7 @@ package cmd
import (
"fmt"
- appCfg "github.com/charmbracelet/soft-serve/config"
+ "github.com/charmbracelet/soft-serve/server/config"
"github.com/gliderlabs/ssh"
"github.com/spf13/cobra"
)
@@ -77,9 +77,9 @@ func RootCommand() *cobra.Command {
return rootCmd
}
-func fromContext(cmd *cobra.Command) (*appCfg.Config, ssh.Session) {
+func fromContext(cmd *cobra.Command) (*config.Config, ssh.Session) {
ctx := cmd.Context()
- ac := ctx.Value(ConfigCtxKey).(*appCfg.Config)
+ cfg := ctx.Value(ConfigCtxKey).(*config.Config)
s := ctx.Value(SessionCtxKey).(ssh.Session)
- return ac, s
+ return cfg, s
}
@@ -4,39 +4,47 @@ import (
"io"
"os/exec"
- "github.com/charmbracelet/soft-serve/config"
"github.com/charmbracelet/soft-serve/proto"
"github.com/spf13/cobra"
)
+// TODO: remove this command.
// GitCommand returns a command that handles Git operations.
func GitCommand() *cobra.Command {
gitCmd := &cobra.Command{
Use: "git REPO COMMAND",
Short: "Perform Git operations on a repository.",
RunE: func(cmd *cobra.Command, args []string) error {
- ac, s := fromContext(cmd)
- auth := ac.AuthRepo("config", s.PublicKey())
+ cfg, s := fromContext(cmd)
+ auth := cfg.AuthRepo("config", s.PublicKey())
if auth < proto.AdminAccess {
return ErrUnauthorized
}
if len(args) < 1 {
return runGit(nil, s, s, "")
}
- var repo *config.Repo
+ var repo proto.Repository
rn := args[0]
repoExists := false
- for _, rp := range ac.Source.AllRepos() {
- if rp.Repo() == rn {
+ repos, err := cfg.ListRepos()
+ if err != nil {
+ return err
+ }
+ for _, rp := range repos {
+ if rp.Name() == rn {
+ re, err := rp.Open()
+ if err != nil {
+ continue
+ }
repoExists = true
- repo = rp
+ repo = re
break
}
}
if !repoExists {
return ErrRepoNotFound
}
- return runGit(nil, s, s, repo.Path(), args[1:]...)
+ return runGit(nil, s, s, repo.Repository().Path, args[1:]...)
},
}
gitCmd.Flags().SetInterspersed(false)
@@ -18,36 +18,40 @@ func ListCommand() *cobra.Command {
Short: "List file or directory at path.",
Args: cobra.RangeArgs(0, 1),
RunE: func(cmd *cobra.Command, args []string) error {
- ac, s := fromContext(cmd)
+ cfg, s := fromContext(cmd)
rn := ""
path := ""
ps := []string{}
if len(args) > 0 {
path = filepath.Clean(args[0])
ps = strings.Split(path, "/")
- rn = ps[0]
- auth := ac.AuthRepo(rn, s.PublicKey())
+ rn = strings.TrimSuffix(ps[0], ".git")
+ auth := cfg.AuthRepo(rn, s.PublicKey())
if auth < proto.ReadOnlyAccess {
return ErrUnauthorized
}
}
if path == "" || path == "." || path == "/" {
- for _, r := range ac.Source.AllRepos() {
- if ac.AuthRepo(r.Repo(), s.PublicKey()) >= proto.ReadOnlyAccess {
- fmt.Fprintln(s, r.Repo())
+ repos, err := cfg.ListRepos()
+ if err != nil {
+ return err
+ }
+ for _, r := range repos {
+ if cfg.AuthRepo(r.Name(), s.PublicKey()) >= proto.ReadOnlyAccess {
+ fmt.Fprintln(s, r.Name())
}
}
return nil
}
- r, err := ac.Source.GetRepo(rn)
+ r, err := cfg.Open(rn)
if err != nil {
return err
}
- head, err := r.HEAD()
+ head, err := r.Repository().HEAD()
if err != nil {
return err
}
- tree, err := r.Tree(head, "")
+ tree, err := r.Repository().TreePath(head, "")
if err != nil {
return err
}
@@ -11,12 +11,12 @@ func ReloadCommand() *cobra.Command {
Use: "reload",
Short: "Reloads the configuration",
RunE: func(cmd *cobra.Command, args []string) error {
- ac, s := fromContext(cmd)
- auth := ac.AuthRepo("config", s.PublicKey())
+ cfg, s := fromContext(cmd)
+ auth := cfg.AuthRepo("config", s.PublicKey())
if auth < proto.AdminAccess {
return ErrUnauthorized
}
- return ac.Reload()
+ return nil
},
}
return reloadCmd
@@ -1,8 +1,6 @@
package config
import (
- "strings"
-
"github.com/charmbracelet/soft-serve/proto"
"github.com/gliderlabs/ssh"
gossh "golang.org/x/crypto/ssh"
@@ -40,18 +38,24 @@ func (c *Config) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
// If repo exists, and not private, then access is based on config.AnonAccess.
func (c *Config) accessForKey(repo string, pk ssh.PublicKey) proto.AccessLevel {
anon := c.AnonAccess
- private := c.isPrivate(repo)
- // Find user
+ info, err := c.Metadata(repo)
+ if err != nil || info == nil {
+ return anon
+ }
+ private := info.IsPrivate()
+ collabs := info.Collabs()
if pk != nil {
- if u := c.findUser(pk); u != nil {
+ for _, u := range collabs {
if u.IsAdmin() {
return proto.AdminAccess
}
- if c.isCollab(repo, pk) {
- if anon > proto.ReadWriteAccess {
- return anon
+ for _, k := range u.PublicKeys() {
+ if ssh.KeysEqual(pk, k) {
+ if anon > proto.ReadWriteAccess {
+ return anon
+ }
+ return proto.ReadWriteAccess
}
- return proto.ReadWriteAccess
}
if !private {
if anon > proto.ReadOnlyAccess {
@@ -76,44 +80,3 @@ func (c *Config) countUsers() int {
}
return count
}
-
-func (c *Config) findUser(pk ssh.PublicKey) proto.User {
- k := strings.TrimSpace(string(gossh.MarshalAuthorizedKey(pk)))
- u, err := c.DB().GetUserByPublicKey(k)
- if err != nil {
- return nil
- }
- ks, err := c.DB().GetUserPublicKeys(u)
- if err != nil {
- return nil
- }
- return &user{user: u, keys: ks}
-}
-
-func (c *Config) findRepo(repo string) proto.Repository {
- r, err := c.DB().Open(repo)
- if err != nil {
- return nil
- }
- return r
-}
-
-func (c *Config) isPrivate(repo string) bool {
- if r := c.findRepo(repo); r != nil {
- return r.IsPrivate()
- }
- return false
-}
-
-func (c *Config) isCollab(repo string, pk ssh.PublicKey) bool {
- pks, err := c.DB().ListRepoPublicKeys(repo)
- if err != nil {
- return false
- }
- for _, k := range pks {
- if ssh.KeysEqual(pk, k) {
- return true
- }
- }
- return false
-}
@@ -86,6 +86,7 @@ type Config struct {
Git GitConfig `env:"GIT" envPrefix:"GIT_"`
Db DBConfig `env:"DB" envPrefix:"DB_"`
+ ServerName string `env:"SERVER_NAME" envDefault:"Soft Serve"`
AnonAccess proto.AccessLevel `env:"ANON_ACCESS" envDefault:"read-only"`
DataPath string `env:"DATA_PATH" envDefault:"data"`
@@ -0,0 +1,170 @@
+package config
+
+import (
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/charmbracelet/soft-serve/git"
+ "github.com/charmbracelet/soft-serve/proto"
+ "github.com/charmbracelet/soft-serve/server/db/types"
+)
+
+var _ proto.Provider = &Config{}
+var _ proto.MetadataProvider = &Config{}
+
+// Metadata returns the repository's metadata.
+func (c *Config) Metadata(name string) (proto.Metadata, error) {
+ i, err := c.db.GetRepo(name)
+ if err != nil {
+ return nil, err
+ }
+ return &repo{
+ cfg: c,
+ info: i,
+ }, nil
+}
+
+// Open opens a repository.
+func (c *Config) Open(name string) (proto.Repository, error) {
+ if name == "" {
+ return nil, os.ErrNotExist
+ }
+ r, err := git.Open(filepath.Join(c.RepoPath(), name+".git"))
+ if err != nil {
+ log.Printf("error opening repository %q: %v", name, err)
+ return nil, err
+ }
+ return &repo{
+ cfg: c,
+ repo: r,
+ }, nil
+}
+
+// ListRepos lists all repositories metadata.
+func (c *Config) ListRepos() ([]proto.Metadata, error) {
+ md := make([]proto.Metadata, 0)
+ ds, err := os.ReadDir(c.RepoPath())
+ if err != nil {
+ return nil, err
+ }
+ for _, d := range ds {
+ name := strings.TrimSuffix(d.Name(), ".git")
+ r, err := c.db.GetRepo(name)
+ if err != nil || r == nil {
+ md = append(md, &emptyMetadata{
+ name: name,
+ cfg: c,
+ })
+ } else {
+ md = append(md, &repo{
+ cfg: c,
+ info: r,
+ })
+ }
+ }
+ return md, nil
+}
+
+var _ proto.Metadata = emptyMetadata{}
+
+type emptyMetadata struct {
+ name string
+ cfg *Config
+}
+
+// Collabs implements proto.Metadata.
+func (emptyMetadata) Collabs() []proto.User {
+ return []proto.User{}
+}
+
+// Description implements proto.Metadata.
+func (emptyMetadata) Description() string {
+ return ""
+}
+
+// IsPrivate implements proto.Metadata.
+func (emptyMetadata) IsPrivate() bool {
+ return false
+}
+
+// Name implements proto.Metadata.
+func (e emptyMetadata) Name() string {
+ return e.name
+}
+
+// Open implements proto.Metadata.
+func (e emptyMetadata) Open() (proto.Repository, error) {
+ return e.cfg.Open(e.Name())
+}
+
+// ProjectName implements proto.Metadata.
+func (emptyMetadata) ProjectName() string {
+ return ""
+}
+
+var _ proto.Metadata = &repo{}
+var _ proto.Repository = &repo{}
+
+// repo represents a Git repository.
+type repo struct {
+ cfg *Config
+ repo *git.Repository
+ info *types.Repo
+}
+
+// Open opens the underlying Repository.
+func (r *repo) Open() (proto.Repository, error) {
+ return r.cfg.Open(r.Name())
+}
+
+// Name returns the name of the repository.
+func (r *repo) Name() string {
+ if r.repo != nil {
+ strings.TrimSuffix(filepath.Base(r.repo.Path), ".git")
+ }
+ return r.info.Name
+}
+
+// ProjectName returns the repository's project name.
+func (r *repo) ProjectName() string {
+ return r.info.ProjectName
+}
+
+// Description returns the repository's description.
+func (r *repo) Description() string {
+ return r.info.Description
+}
+
+// IsPrivate returns true if the repository is private.
+func (r *repo) IsPrivate() bool {
+ return r.info.Private
+}
+
+// Collabs returns the repository's collaborators.
+func (r *repo) Collabs() []proto.User {
+ collabs := make([]proto.User, 0)
+ cs, err := r.cfg.db.ListRepoCollabs(r.Name())
+ if err != nil {
+ return collabs
+ }
+ for i, c := range cs {
+ ks, err := r.cfg.db.GetUserPublicKeys(c)
+ if err != nil {
+ log.Printf("error getting public keys for %q: %v", c.Name, err)
+ continue
+ }
+ u := &user{
+ user: c,
+ keys: ks,
+ }
+ collabs[i] = u
+ }
+ return collabs
+}
+
+// Repository returns the underlying git.Repository.
+func (r *repo) Repository() *git.Repository {
+ return r.repo
+}
@@ -3,10 +3,13 @@ package config
import (
"net/mail"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/charmbracelet/soft-serve/server/db/types"
"golang.org/x/crypto/ssh"
)
+var _ proto.User = &user{}
+
type user struct {
user *types.User
keys []*types.PublicKey
@@ -1,21 +1,9 @@
package db
import (
- "github.com/charmbracelet/soft-serve/proto"
"github.com/charmbracelet/soft-serve/server/db/types"
)
-// ConfigStore is a configuration database storage.
-type ConfigStore interface {
- // Config
- GetConfig() (*types.Config, error)
- SetConfigName(string) error
- SetConfigHost(string) error
- SetConfigPort(int) error
- SetConfigAnonAccess(string) error
- SetConfigAllowKeyless(bool) error
-}
-
// UserStore is a user database storage.
type UserStore interface {
// Users
@@ -63,9 +51,6 @@ type CollabStore interface {
// Store is a database.
type Store interface {
- proto.Provider
-
- ConfigStore
UserStore
PublicKeyStore
RepoStore
@@ -1,181 +1,176 @@
package fakedb
import (
- "github.com/charmbracelet/soft-serve/proto"
"github.com/charmbracelet/soft-serve/server/db"
"github.com/charmbracelet/soft-serve/server/db/types"
)
var _ db.Store = &FakeDB{}
+// FakeDB is a fake database for testing.
type FakeDB struct{}
-// Open implements db.Store
-func (*FakeDB) Open(name string) (proto.RepositoryService, error) {
- return nil, nil
-}
-
-// GetConfig implements db.Store
+// GetConfig implements db.Store.
func (*FakeDB) GetConfig() (*types.Config, error) {
return nil, nil
}
-// SetConfigAllowKeyless implements db.Store
+// SetConfigAllowKeyless implements db.Store.
func (*FakeDB) SetConfigAllowKeyless(bool) error {
return nil
}
-// SetConfigAnonAccess implements db.Store
+// SetConfigAnonAccess implements db.Store.
func (*FakeDB) SetConfigAnonAccess(string) error {
return nil
}
-// SetConfigHost implements db.Store
+// SetConfigHost implements db.Store.
func (*FakeDB) SetConfigHost(string) error {
return nil
}
-// SetConfigName implements db.Store
+// SetConfigName implements db.Store.
func (*FakeDB) SetConfigName(string) error {
return nil
}
-// SetConfigPort implements db.Store
+// SetConfigPort implements db.Store.
func (*FakeDB) SetConfigPort(int) error {
return nil
}
-// AddUser implements db.Store
+// AddUser implements db.Store.
func (*FakeDB) AddUser(name string, login string, email string, password string, isAdmin bool) error {
return nil
}
-// CountUsers implements db.Store
+// CountUsers implements db.Store.
func (*FakeDB) CountUsers() (int, error) {
return 0, nil
}
-// DeleteUser implements db.Store
+// DeleteUser implements db.Store.
func (*FakeDB) DeleteUser(int) error {
return nil
}
-// GetUser implements db.Store
+// GetUser implements db.Store.
func (*FakeDB) GetUser(int) (*types.User, error) {
return nil, nil
}
-// GetUserByEmail implements db.Store
+// GetUserByEmail implements db.Store.
func (*FakeDB) GetUserByEmail(string) (*types.User, error) {
return nil, nil
}
-// GetUserByLogin implements db.Store
+// GetUserByLogin implements db.Store.
func (*FakeDB) GetUserByLogin(string) (*types.User, error) {
return nil, nil
}
-// GetUserByPublicKey implements db.Store
+// GetUserByPublicKey implements db.Store.
func (*FakeDB) GetUserByPublicKey(string) (*types.User, error) {
return nil, nil
}
-// SetUserAdmin implements db.Store
+// SetUserAdmin implements db.Store.
func (*FakeDB) SetUserAdmin(*types.User, bool) error {
return nil
}
-// SetUserEmail implements db.Store
+// SetUserEmail implements db.Store.
func (*FakeDB) SetUserEmail(*types.User, string) error {
return nil
}
-// SetUserLogin implements db.Store
+// SetUserLogin implements db.Store.
func (*FakeDB) SetUserLogin(*types.User, string) error {
return nil
}
-// SetUserName implements db.Store
+// SetUserName implements db.Store.
func (*FakeDB) SetUserName(*types.User, string) error {
return nil
}
-// SetUserPassword implements db.Store
+// SetUserPassword implements db.Store.
func (*FakeDB) SetUserPassword(*types.User, string) error {
return nil
}
-// AddUserPublicKey implements db.Store
+// AddUserPublicKey implements db.Store.
func (*FakeDB) AddUserPublicKey(*types.User, string) error {
return nil
}
-// DeleteUserPublicKey implements db.Store
+// DeleteUserPublicKey implements db.Store.
func (*FakeDB) DeleteUserPublicKey(int) error {
return nil
}
-// GetUserPublicKeys implements db.Store
+// GetUserPublicKeys implements db.Store.
func (*FakeDB) GetUserPublicKeys(*types.User) ([]*types.PublicKey, error) {
return nil, nil
}
-// AddRepo implements db.Store
+// AddRepo implements db.Store.
func (*FakeDB) AddRepo(name string, projectName string, description string, isPrivate bool) error {
return nil
}
-// DeleteRepo implements db.Store
+// DeleteRepo implements db.Store.
func (*FakeDB) DeleteRepo(string) error {
return nil
}
-// GetRepo implements db.Store
+// GetRepo implements db.Store.
func (*FakeDB) GetRepo(string) (*types.Repo, error) {
return nil, nil
}
-// SetRepoDescription implements db.Store
+// SetRepoDescription implements db.Store.
func (*FakeDB) SetRepoDescription(string, string) error {
return nil
}
-// SetRepoPrivate implements db.Store
+// SetRepoPrivate implements db.Store.
func (*FakeDB) SetRepoPrivate(string, bool) error {
return nil
}
-// SetRepoProjectName implements db.Store
+// SetRepoProjectName implements db.Store.
func (*FakeDB) SetRepoProjectName(string, string) error {
return nil
}
-// AddRepoCollab implements db.Store
+// AddRepoCollab implements db.Store.
func (*FakeDB) AddRepoCollab(string, *types.User) error {
return nil
}
-// DeleteRepoCollab implements db.Store
+// DeleteRepoCollab implements db.Store.
func (*FakeDB) DeleteRepoCollab(int, int) error {
return nil
}
-// ListRepoCollabs implements db.Store
+// ListRepoCollabs implements db.Store.
func (*FakeDB) ListRepoCollabs(string) ([]*types.User, error) {
return nil, nil
}
-// ListRepoPublicKeys implements db.Store
+// ListRepoPublicKeys implements db.Store.
func (*FakeDB) ListRepoPublicKeys(string) ([]*types.PublicKey, error) {
return nil, nil
}
-// Close implements db.Store
+// Close implements db.Store.
func (*FakeDB) Close() error {
return nil
}
-// CreateDB implements db.Store
+// CreateDB implements db.Store.
func (*FakeDB) CreateDB() error {
return nil
}
@@ -1,58 +0,0 @@
-package sqlite
-
-import (
- "github.com/charmbracelet/soft-serve/proto"
- "github.com/charmbracelet/soft-serve/server/db/types"
-)
-
-// Open opens a repository.
-func (d *Sqlite) Open(name string) (proto.RepositoryService, error) {
- r, err := d.GetRepo(name)
- if err != nil {
- return nil, err
- }
- return &repository{
- repo: r,
- db: d,
- }, nil
-}
-
-type repository struct {
- repo *types.Repo
- db *Sqlite
-}
-
-// Name returns the repository's name.
-func (r *repository) Name() string {
- return r.repo.Name
-}
-
-// ProjectName returns the repository's project name.
-func (r *repository) ProjectName() string {
- return r.repo.ProjectName
-}
-
-// SetProjectName sets the repository's project name.
-func (r *repository) SetProjectName(name string) error {
- return r.db.SetRepoProjectName(r.repo.Name, name)
-}
-
-// Description returns the repository's description.
-func (r *repository) Description() string {
- return r.repo.Description
-}
-
-// SetDescription sets the repository's description.
-func (r *repository) SetDescription(desc string) error {
- return r.db.SetRepoDescription(r.repo.Name, desc)
-}
-
-// IsPrivate returns whether the repository is private.
-func (r *repository) IsPrivate() bool {
- return r.repo.Private
-}
-
-// SetPrivate sets whether the repository is private.
-func (r *repository) SetPrivate(p bool) error {
- return r.db.SetRepoPrivate(r.repo.Name, p)
-}
@@ -1,17 +1,6 @@
package sqlite
var (
- sqlCreateConfigTable = `CREATE TABLE IF NOT EXISTS config (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL,
- host TEXT NOT NULL,
- port INTEGER NOT NULL,
- anon_access TEXT NOT NULL,
- allow_keyless BOOLEAN NOT NULL,
- created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME NOT NULL
- );`
-
sqlCreateUserTable = `CREATE TABLE IF NOT EXISTS user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
@@ -63,15 +52,6 @@ var (
ON UPDATE CASCADE
);`
- // Config.
- sqlInsertConfig = `INSERT INTO config (name, host, port, anon_access, allow_keyless, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP);`
- sqlSelectConfig = `SELECT id, name, host, port, anon_access, allow_keyless, created_at, updated_at FROM config WHERE id = ?;`
- sqlUpdateConfigName = `UPDATE config SET name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
- sqlUpdateConfigHost = `UPDATE config SET host = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
- sqlUpdateConfigPort = `UPDATE config SET port = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
- sqlUpdateConfigAnon = `UPDATE config SET anon_access = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
- sqlUpdateConfigKeyless = `UPDATE config SET allow_keyless = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
-
// User.
sqlInsertUser = `INSERT INTO user (name, login, email, password, admin, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP);`
sqlDeleteUser = `DELETE FROM user WHERE id = ?;`
@@ -50,9 +50,6 @@ func (d *Sqlite) Close() error {
// CreateDB creates the database and tables.
func (d *Sqlite) CreateDB() error {
return d.wrapTransaction(func(tx *sql.Tx) error {
- if _, err := tx.Exec(sqlCreateConfigTable); err != nil {
- return err
- }
if _, err := tx.Exec(sqlCreateUserTable); err != nil {
return err
}
@@ -69,63 +66,6 @@ func (d *Sqlite) CreateDB() error {
})
}
-const defaultConfigID = 1
-
-// GetConfig returns the server config.
-func (d *Sqlite) GetConfig() (*types.Config, error) {
- var c types.Config
- if err := d.wrapTransaction(func(tx *sql.Tx) error {
- r := tx.QueryRow(sqlSelectConfig, defaultConfigID)
- if err := r.Scan(&c.ID, &c.Name, &c.Host, &c.Port, &c.AnonAccess, &c.AllowKeyless, &c.CreatedAt, &c.UpdatedAt); err != nil {
- return err
- }
- return nil
- }); err != nil {
- return nil, err
- }
- return &c, nil
-}
-
-// SetConfigName sets the server config name.
-func (d *Sqlite) SetConfigName(name string) error {
- return d.wrapTransaction(func(tx *sql.Tx) error {
- _, err := tx.Exec(sqlUpdateConfigName, name, defaultConfigID)
- return err
- })
-}
-
-// SetConfigHost sets the server config host.
-func (d *Sqlite) SetConfigHost(host string) error {
- return d.wrapTransaction(func(tx *sql.Tx) error {
- _, err := tx.Exec(sqlUpdateConfigHost, host, defaultConfigID)
- return err
- })
-}
-
-// SetConfigPort sets the server config port.
-func (d *Sqlite) SetConfigPort(port int) error {
- return d.wrapTransaction(func(tx *sql.Tx) error {
- _, err := tx.Exec(sqlUpdateConfigPort, port, defaultConfigID)
- return err
- })
-}
-
-// SetConfigAnonAccess sets the server config anon access.
-func (d *Sqlite) SetConfigAnonAccess(access string) error {
- return d.wrapTransaction(func(tx *sql.Tx) error {
- _, err := tx.Exec(sqlUpdateConfigAnon, access, defaultConfigID)
- return err
- })
-}
-
-// SetConfigAllowKeyless sets the server config allow keyless.
-func (d *Sqlite) SetConfigAllowKeyless(allow bool) error {
- return d.wrapTransaction(func(tx *sql.Tx) error {
- _, err := tx.Exec(sqlUpdateConfigKeyless, allow, defaultConfigID)
- return err
- })
-}
-
// AddUser adds a new user.
func (d *Sqlite) AddUser(name, login, email, password string, isAdmin bool) error {
var l *string
@@ -475,16 +415,20 @@ func (d *Sqlite) wrapTransaction(f func(tx *sql.Tx) error) error {
}
for {
err = f(tx)
- if err != nil && !errors.Is(err, sql.ErrNoRows) {
- serr, ok := err.(*sqlite.Error)
- if ok {
- switch serr.Code() {
- case sqlitelib.SQLITE_BUSY:
- continue
+ if err != nil {
+ switch {
+ case errors.Is(err, sql.ErrNoRows):
+ default:
+ serr, ok := err.(*sqlite.Error)
+ if ok {
+ switch serr.Code() {
+ case sqlitelib.SQLITE_BUSY:
+ continue
+ }
+ log.Printf("error in transaction: %d: %s", serr.Code(), serr)
+ } else {
+ log.Printf("error in transaction: %s", err)
}
- log.Printf("error in transaction: %d: %s", serr.Code(), serr)
- } else {
- log.Printf("error in transaction: %s", err)
}
return err
}
@@ -1,108 +0,0 @@
-package file
-
-import (
- "errors"
- "os"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/charmbracelet/soft-serve/git"
- "github.com/charmbracelet/soft-serve/proto"
-)
-
-type key int
-
-const (
- projectName key = iota
- description
- private
-)
-
-var keys = map[key]string{
- projectName: "soft-serve.projectName",
- description: "soft-serve.description",
- private: "soft-serve.private",
-}
-
-var _ proto.Provider = &File{}
-
-// File is a file-based repository provider.
-type File struct {
- repoPath string
-}
-
-// New returns a new File provider.
-func New(repoPath string) *File {
- f := &File{
- repoPath: repoPath,
- }
- return f
-}
-
-// Open opens a new repository and returns a new FileRepo.
-func (f *File) Open(name string) (proto.RepositoryService, error) {
- fp := filepath.Join(f.repoPath, name)
- r, err := git.Open(fp)
- if errors.Is(err, os.ErrNotExist) {
- r, err = git.Open(fp + ".git")
- }
- if err != nil {
- return nil, err
- }
- return &FileRepo{r}, nil
-}
-
-var _ proto.Repository = &FileRepo{}
-
-// FileRepo is a file-based repository.
-type FileRepo struct { // nolint:revive
- repo *git.Repository
-}
-
-// Name returns the name of the repository.
-func (r *FileRepo) Name() string {
- return strings.TrimSuffix(r.repo.Name(), ".git")
-}
-
-// ProjectName returns the project name of the repository.
-func (r *FileRepo) ProjectName() string {
- pn, err := r.repo.Config(keys[projectName])
- if err != nil {
- return ""
- }
- return pn
-}
-
-// SetProjectName sets the project name of the repository.
-func (r *FileRepo) SetProjectName(name string) error {
- return r.repo.SetConfig(keys[projectName], name)
-}
-
-// Description returns the description of the repository.
-func (r *FileRepo) Description() string {
- desc, err := r.repo.Config(keys[description])
- if err != nil {
- return ""
- }
- return desc
-}
-
-// SetDescription sets the description of the repository.
-func (r *FileRepo) SetDescription(desc string) error {
- return r.repo.SetConfig(keys[description], desc)
-}
-
-// IsPrivate returns whether the repository is private.
-func (r *FileRepo) IsPrivate() bool {
- p, err := r.repo.Config(keys[private])
- if err != nil {
- return false
- }
- return p == "true"
-}
-
-// SetPrivate sets whether the repository is private.
-func (r *FileRepo) SetPrivate(p bool) error {
- return r.repo.SetConfig(keys[private], strconv.FormatBool(p))
-}
@@ -28,7 +28,7 @@ func TestMain(m *testing.M) {
DataPath: tmp,
AnonAccess: proto.ReadOnlyAccess,
Git: config.GitConfig{
- // Reduce the max read timeout to 1 second so we can test the timeout.
+ // Reduce the max read timeout to 3 second so we can test the timeout.
IdleTimeout: 3,
// Reduce the max timeout to 100 second so we can test the timeout.
MaxTimeout: 100,
@@ -6,7 +6,6 @@ import (
"log"
"time"
- appCfg "github.com/charmbracelet/soft-serve/config"
cm "github.com/charmbracelet/soft-serve/server/cmd"
"github.com/charmbracelet/soft-serve/server/config"
"github.com/charmbracelet/soft-serve/server/git/daemon"
@@ -25,7 +24,6 @@ type Server struct {
SSHServer *ssh.Server
GitServer *daemon.Daemon
Config *config.Config
- config *appCfg.Config
}
// NewServer returns a new *ssh.Server configured to serve Soft Serve. The SSH
@@ -35,19 +33,15 @@ type Server struct {
// publicly writable until configured otherwise by cloning the `config` repo.
func NewServer(cfg *config.Config) *Server {
s := &Server{Config: cfg}
- ac, err := appCfg.NewConfig(cfg)
- if err != nil {
- log.Fatal(err)
- }
mw := []wish.Middleware{
rm.MiddlewareWithLogger(
cfg.ErrorLog,
// BubbleTea middleware.
- bm.MiddlewareWithProgramHandler(SessionHandler(ac), termenv.ANSI256),
+ bm.MiddlewareWithProgramHandler(SessionHandler(cfg), termenv.ANSI256),
// Command middleware must come after the git middleware.
cm.Middleware(cfg),
// Git middleware.
- gm.Middleware(cfg.RepoPath(), ac),
+ gm.Middleware(cfg.RepoPath(), cfg),
// Logging middleware must be last to be executed first.
lm.Middleware(),
),
@@ -86,7 +80,8 @@ func NewServer(cfg *config.Config) *Server {
// Reload reloads the server configuration.
func (s *Server) Reload() error {
- return s.config.Reload()
+ return nil
+ // return s.config.Reload()
}
// Start starts the SSH server.
@@ -5,9 +5,9 @@ import (
"github.com/aymanbagabas/go-osc52"
tea "github.com/charmbracelet/bubbletea"
- appCfg "github.com/charmbracelet/soft-serve/config"
"github.com/charmbracelet/soft-serve/proto"
cm "github.com/charmbracelet/soft-serve/server/cmd"
+ "github.com/charmbracelet/soft-serve/server/config"
"github.com/charmbracelet/soft-serve/ui"
"github.com/charmbracelet/soft-serve/ui/common"
"github.com/charmbracelet/soft-serve/ui/keymap"
@@ -19,7 +19,7 @@ import (
)
// SessionHandler is the soft-serve bubbletea ssh session handler.
-func SessionHandler(ac *appCfg.Config) bm.ProgramHandler {
+func SessionHandler(cfg *config.Config) bm.ProgramHandler {
return func(s ssh.Session) *tea.Program {
pty, _, active := s.Pty()
if !active {
@@ -29,14 +29,14 @@ func SessionHandler(ac *appCfg.Config) bm.ProgramHandler {
initialRepo := ""
if len(cmd) == 1 {
initialRepo = cmd[0]
- auth := ac.AuthRepo(initialRepo, s.PublicKey())
+ auth := cfg.AuthRepo(initialRepo, s.PublicKey())
if auth < proto.ReadOnlyAccess {
wish.Fatalln(s, cm.ErrUnauthorized)
return nil
}
}
- if ac.Cfg.Callbacks != nil {
- ac.Cfg.Callbacks.Tui("new session")
+ if cfg.Callbacks != nil {
+ cfg.Callbacks.Tui("new session")
}
envs := s.Environ()
envs = append(envs, fmt.Sprintf("TERM=%s", pty.Term))
@@ -50,7 +50,7 @@ func SessionHandler(ac *appCfg.Config) bm.ProgramHandler {
Zone: zone.New(),
}
m := ui.New(
- ac,
+ cfg,
s,
c,
initialRepo,
@@ -8,7 +8,7 @@ import (
"testing"
"time"
- appCfg "github.com/charmbracelet/soft-serve/config"
+ "github.com/charmbracelet/soft-serve/proto"
cm "github.com/charmbracelet/soft-serve/server/cmd"
"github.com/charmbracelet/soft-serve/server/config"
bm "github.com/charmbracelet/wish/bubbletea"
@@ -53,9 +53,8 @@ func TestSession(t *testing.T) {
}
func setup(tb testing.TB) *gossh.Session {
- is := is.New(tb)
tb.Helper()
- ac, err := appCfg.NewConfig(&config.Config{
+ cfg := &config.Config{
Host: "",
SSH: config.SSHConfig{Port: randomPort()},
Git: config.GitConfig{Port: 9418},
@@ -63,11 +62,10 @@ func setup(tb testing.TB) *gossh.Session {
InitialAdminKeys: []string{
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMJlb/qf2B2kMNdBxfpCQqI2ctPcsOkdZGVh5zTRhKtH",
},
- })
- ac.AnonAccess = "read-only"
- is.NoErr(err)
+ AnonAccess: proto.ReadOnlyAccess,
+ }
return testsession.New(tb, &ssh.Server{
- Handler: bm.MiddlewareWithProgramHandler(SessionHandler(ac), termenv.ANSI256)(func(s ssh.Session) {
+ Handler: bm.MiddlewareWithProgramHandler(SessionHandler(cfg), termenv.ANSI256)(func(s ssh.Session) {
_, _, active := s.Pty()
tb.Logf("PTY active %v", active)
tb.Log(s.Command())