1f673f30802b88a426a83683f950eab8376f0ccf
- Author
- Ayman Bagabas <ayman.bagabas@gmail.com>
- Committer
- Ayman Bagabas <ayman.bagabas@gmail.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/server/backend/access.go b/server/backend/access.go
2index 52cc16eff8357c46fa84d18bed37336ba81ded82..e13e20fcc5e175ca68f633339a36b293ce39adb9 100644
3--- a/server/backend/access.go
4+++ b/server/backend/access.go
5@@ -1,7 +1,5 @@
6 package backend
7
8-import "golang.org/x/crypto/ssh"
9-
10 // AccessLevel is the level of access allowed to a repo.
11 type AccessLevel int
12
13@@ -34,9 +32,3 @@ func (a AccessLevel) String() string {
14 return "unknown"
15 }
16 }
17-
18-// AccessMethod is an interface that handles repository authorization.
19-type AccessMethod interface {
20- // AccessLevel returns the access level for the given repository and key.
21- AccessLevel(repo string, pk ssh.PublicKey) AccessLevel
22-}
23diff --git a/server/backend/file/file.go b/server/backend/file/file.go
24index 66d19ac4fb08559eaeb7cd0095c6dd55b1db9f01..68e5d92ff701dcadc50ba3939b66e26f560cda51 100644
25--- a/server/backend/file/file.go
26+++ b/server/backend/file/file.go
27@@ -31,6 +31,7 @@ import (
28 "github.com/charmbracelet/log"
29 "github.com/charmbracelet/soft-serve/git"
30 "github.com/charmbracelet/soft-serve/server/backend"
31+ "github.com/charmbracelet/soft-serve/server/utils"
32 "github.com/charmbracelet/ssh"
33 gossh "golang.org/x/crypto/ssh"
34 )
35@@ -59,8 +60,6 @@ var (
36
37 var _ backend.Backend = &FileBackend{}
38
39-var _ backend.AccessMethod = &FileBackend{}
40-
41 // FileBackend is a backend that uses the filesystem.
42 type FileBackend struct { // nolint:revive
43 // path is the path to the directory containing the repositories and config
44@@ -78,11 +77,6 @@ func (fb *FileBackend) reposPath() string {
45 return filepath.Join(fb.path, repos)
46 }
47
48-// RepositoryStorePath returns the path to the repository store.
49-func (fb *FileBackend) RepositoryStorePath() string {
50- return fb.reposPath()
51-}
52-
53 func (fb *FileBackend) settingsPath() string {
54 return filepath.Join(fb.path, settings)
55 }
56@@ -95,10 +89,6 @@ func (fb *FileBackend) collabsPath(repo string) string {
57 return filepath.Join(fb.path, collabs, repo, collabs)
58 }
59
60-func sanatizeRepo(repo string) string {
61- return strings.TrimSuffix(repo, ".git")
62-}
63-
64 func readOneLine(path string) (string, error) {
65 f, err := os.Open(path)
66 if err != nil {
67@@ -221,7 +211,7 @@ func (fb *FileBackend) AddAdmin(pk gossh.PublicKey, memo string) error {
68 //
69 // It implements backend.Backend.
70 func (fb *FileBackend) AddCollaborator(pk gossh.PublicKey, memo string, repo string) error {
71- name := sanatizeRepo(repo)
72+ name := utils.SanitizeRepo(repo)
73 repo = name + ".git"
74 // Check if repo exists
75 if !exists(filepath.Join(fb.reposPath(), repo)) {
76@@ -278,7 +268,7 @@ func (fb *FileBackend) Admins() ([]string, error) {
77 //
78 // It implements backend.Backend.
79 func (fb *FileBackend) Collaborators(repo string) ([]string, error) {
80- name := sanatizeRepo(repo)
81+ name := utils.SanitizeRepo(repo)
82 repo = name + ".git"
83 // Check if repo exists
84 if !exists(filepath.Join(fb.reposPath(), repo)) {
85@@ -359,7 +349,7 @@ func (fb *FileBackend) RemoveAdmin(pk gossh.PublicKey) error {
86 //
87 // It implements backend.Backend.
88 func (fb *FileBackend) RemoveCollaborator(pk gossh.PublicKey, repo string) error {
89- name := sanatizeRepo(repo)
90+ name := utils.SanitizeRepo(repo)
91 repo = name + ".git"
92 // Check if repo exists
93 if !exists(filepath.Join(fb.reposPath(), repo)) {
94@@ -458,7 +448,7 @@ func (fb *FileBackend) AnonAccess() backend.AccessLevel {
95 //
96 // It implements backend.Backend.
97 func (fb *FileBackend) Description(repo string) string {
98- repo = sanatizeRepo(repo) + ".git"
99+ repo = utils.SanitizeRepo(repo) + ".git"
100 r := &Repo{path: filepath.Join(fb.reposPath(), repo), root: fb.reposPath()}
101 return r.Description()
102 }
103@@ -501,7 +491,7 @@ func (fb *FileBackend) IsAdmin(pk gossh.PublicKey) bool {
104 //
105 // It implements backend.Backend.
106 func (fb *FileBackend) IsCollaborator(pk gossh.PublicKey, repo string) bool {
107- repo = sanatizeRepo(repo) + ".git"
108+ repo = utils.SanitizeRepo(repo) + ".git"
109 _, err := os.Stat(fb.collabsPath(repo))
110 if err != nil {
111 return false
112@@ -532,7 +522,7 @@ func (fb *FileBackend) IsCollaborator(pk gossh.PublicKey, repo string) bool {
113 //
114 // It implements backend.Backend.
115 func (fb *FileBackend) IsPrivate(repo string) bool {
116- repo = sanatizeRepo(repo) + ".git"
117+ repo = utils.SanitizeRepo(repo) + ".git"
118 r := &Repo{path: filepath.Join(fb.reposPath(), repo), root: fb.reposPath()}
119 return r.IsPrivate()
120 }
121@@ -569,7 +559,7 @@ func (fb *FileBackend) SetAnonAccess(level backend.AccessLevel) error {
122 //
123 // It implements backend.Backend.
124 func (fb *FileBackend) SetDescription(repo string, desc string) error {
125- repo = sanatizeRepo(repo) + ".git"
126+ repo = utils.SanitizeRepo(repo) + ".git"
127diff --git a/server/backend/noop/noop.go b/server/backend/noop/noop.go
128index b91ffbee395e9daca5a465a61175d33a90c119e4..db7324bc0b60fe7fa006b70d1d530e94358446af 100644
129--- a/server/backend/noop/noop.go
130+++ b/server/backend/noop/noop.go
131@@ -14,18 +14,11 @@ var ErrNotImpl = fmt.Errorf("not implemented")
132
133 var _ backend.Backend = (*Noop)(nil)
134
135-var _ backend.AccessMethod = (*Noop)(nil)
136-
137 // Noop is a backend that does nothing. It's used for testing.
138 type Noop struct {
139 Port string
140 }
141
142-// RepositoryStorePath implements backend.Backend
143-func (*Noop) RepositoryStorePath() string {
144- return ""
145-}
146-
147 // Admins implements backend.Backend
148 func (*Noop) Admins() ([]string, error) {
149 return nil, nil
150diff --git a/server/backend/repo.go b/server/backend/repo.go
151index 6cf46c807e7ca42bb0a0deeb32e1a786715a0e8a..05d2de240656cca7749dcec0afb5bdd5d4bfd8e8 100644
152--- a/server/backend/repo.go
153+++ b/server/backend/repo.go
154@@ -11,8 +11,6 @@ type RepositoryStore interface {
155 Repository(repo string) (Repository, error)
156 // Repositories returns a list of all repositories.
157 Repositories() ([]Repository, error)
158- // RepositoryStorePath returns the path to the repository store.
159- RepositoryStorePath() string
160 // CreateRepository creates a new repository.
161 CreateRepository(name string, private bool) (Repository, error)
162 // DeleteRepository deletes a repository.
163@@ -35,6 +33,8 @@ type RepositoryMetadata interface {
164
165 // RepositoryAccess is an interface for managing repository access.
166 type RepositoryAccess interface {
167+ // AccessLevel returns the access level for the given repository and key.
168+ AccessLevel(repo string, pk ssh.PublicKey) AccessLevel
169 // IsCollaborator returns true if the authorized key is a collaborator on the repository.
170 IsCollaborator(pk ssh.PublicKey, repo string) bool
171 // AddCollaborator adds the authorized key as a collaborator on the repository.
172diff --git a/server/cmd/cmd.go b/server/cmd/cmd.go
173index 44dd4b67153b085ff990dcf6eba4c9f9ec36329f..e32836abb31be39b40829fddcb9f8082aaa8b60e 100644
174--- a/server/cmd/cmd.go
175+++ b/server/cmd/cmd.go
176@@ -3,10 +3,10 @@ package cmd
177 import (
178 "context"
179 "fmt"
180- "strings"
181
182 "github.com/charmbracelet/soft-serve/server/backend"
183 "github.com/charmbracelet/soft-serve/server/config"
184+ "github.com/charmbracelet/soft-serve/server/utils"
185 "github.com/charmbracelet/ssh"
186 "github.com/charmbracelet/wish"
187 "github.com/spf13/cobra"
188@@ -75,8 +75,8 @@ func checkIfReadable(cmd *cobra.Command, args []string) error {
189 repo = args[0]
190 }
191 cfg, s := fromContext(cmd)
192- rn := strings.TrimSuffix(repo, ".git")
193- auth := cfg.Access.AccessLevel(rn, s.PublicKey())
194+ rn := utils.SanitizeRepo(repo)
195+ auth := cfg.Backend.AccessLevel(rn, s.PublicKey())
196 if auth < backend.ReadOnlyAccess {
197 return ErrUnauthorized
198 }
199@@ -97,8 +97,8 @@ func checkIfCollab(cmd *cobra.Command, args []string) error {
200 repo = args[0]
201 }
202 cfg, s := fromContext(cmd)
203- rn := strings.TrimSuffix(repo, ".git")
204- auth := cfg.Access.AccessLevel(rn, s.PublicKey())
205+ rn := utils.SanitizeRepo(repo)
206+ auth := cfg.Backend.AccessLevel(rn, s.PublicKey())
207 if auth < backend.ReadWriteAccess {
208 return ErrUnauthorized
209 }
210diff --git a/server/cmd/list.go b/server/cmd/list.go
211index 0bec7fd198aeca000b5ed3a02c82d7e31b134f1e..86f95d7770087a404814c5c23313a5ec03ac61bc 100644
212--- a/server/cmd/list.go
213+++ b/server/cmd/list.go
214@@ -13,21 +13,21 @@ import (
215 // listCommand returns a command that list file or directory at path.
216 func listCommand() *cobra.Command {
217 listCmd := &cobra.Command{
218- Use: "list PATH",
219- Aliases: []string{"ls"},
220- Short: "List files at repository.",
221- Args: cobra.RangeArgs(0, 1),
222- PersistentPreRunE: checkIfReadable,
223+ Use: "list PATH",
224+ Aliases: []string{"ls"},
225+ Short: "List files at repository.",
226+ Args: cobra.RangeArgs(0, 1),
227 RunE: func(cmd *cobra.Command, args []string) error {
228 cfg, s := fromContext(cmd)
229 rn := ""
230 path := ""
231 ps := []string{}
232 if len(args) > 0 {
233+ // FIXME: nested repos are not supported.
234 path = filepath.Clean(args[0])
235 ps = strings.Split(path, "/")
236 rn = strings.TrimSuffix(ps[0], ".git")
237- auth := cfg.Access.AccessLevel(rn, s.PublicKey())
238+ auth := cfg.Backend.AccessLevel(rn, s.PublicKey())
239 if auth < backend.ReadOnlyAccess {
240 return ErrUnauthorized
241 }
242@@ -38,7 +38,7 @@ func listCommand() *cobra.Command {
243 return err
244 }
245 for _, r := range repos {
246- if cfg.Access.AccessLevel(r.Name(), s.PublicKey()) >= backend.ReadOnlyAccess {
247+ if cfg.Backend.AccessLevel(r.Name(), s.PublicKey()) >= backend.ReadOnlyAccess {
248 cmd.Println(r.Name())
249 }
250 }
251diff --git a/server/cmd/show.go b/server/cmd/show.go
252index cab515e4f78b2d2c7a8a0a922a2da2ae10302293..14981dc27d95c1ecaa97ec189c3b615e57dbf5b8 100644
253--- a/server/cmd/show.go
254+++ b/server/cmd/show.go
255@@ -33,14 +33,11 @@ func showCommand() *cobra.Command {
256 Args: cobra.ExactArgs(1),
257 PersistentPreRunE: checkIfReadable,
258 RunE: func(cmd *cobra.Command, args []string) error {
259- cfg, s := fromContext(cmd)
260+ cfg, _ := fromContext(cmd)
261+ // FIXME: nested repos are not supported.
262 ps := strings.Split(args[0], "/")
263 rn := strings.TrimSuffix(ps[0], ".git")
264 fp := strings.Join(ps[1:], "/")
265- auth := cfg.Access.AccessLevel(rn, s.PublicKey())
266- if auth < backend.ReadOnlyAccess {
267- return ErrUnauthorized
268- }
269 var repo backend.Repository
270 repoExists := false
271 repos, err := cfg.Backend.Repositories()
272diff --git a/server/config/config.go b/server/config/config.go
273index e005cdea7a879bbe6711a0f98d785f48465dad8a..8595fe48a599007d4425bfcfcd5ea1e75519309a 100644
274--- a/server/config/config.go
275+++ b/server/config/config.go
276@@ -6,7 +6,6 @@ import (
277 "github.com/caarlos0/env/v6"
278 "github.com/charmbracelet/log"
279 "github.com/charmbracelet/soft-serve/server/backend"
280- "github.com/charmbracelet/soft-serve/server/backend/file"
281 )
282
283 // SSHConfig is the configuration for the SSH server.
284@@ -20,6 +19,9 @@ type SSHConfig struct {
285 // KeyPath is the path to the SSH server's private key.
286 KeyPath string `env:"KEY_PATH"`
287
288+ // InternalKeyPath is the path to the SSH server's internal private key.
289+ InternalKeyPath string `env:"INTERNAL_KEY_PATH"`
290+
291 // MaxTimeout is the maximum number of seconds a connection can take.
292 MaxTimeout int `env:"MAX_TIMEOUT" envDefault:"0"`
293
294@@ -73,9 +75,6 @@ type Config struct {
295
296 // Backend is the Git backend to use.
297 Backend backend.Backend
298-
299- // Access is the access control backend to use.
300- Access backend.AccessMethod
301 }
302
303 // DefaultConfig returns a Config with the values populated with the defaults
304@@ -90,13 +89,10 @@ func DefaultConfig() *Config {
305 if cfg.SSH.KeyPath == "" {
306 cfg.SSH.KeyPath = filepath.Join(cfg.DataPath, "ssh", "soft_serve")
307 }
308- fb, err := file.NewFileBackend(cfg.DataPath)
309- if err != nil {
310- log.Fatal(err)
311+ if cfg.SSH.InternalKeyPath == "" {
312+ cfg.SSH.InternalKeyPath = filepath.Join(cfg.DataPath, "ssh", "soft_serve_internal")
313 }
314- // Add the initial admin keys to the list of admins.
315- fb.AdditionalAdmins = cfg.InitialAdminKeys
316- return cfg.WithBackend(fb).WithAccessMethod(fb)
317+ return cfg
318 }
319
320 // WithBackend sets the backend for the configuration.
321@@ -104,9 +100,3 @@ func (c *Config) WithBackend(backend backend.Backend) *Config {
322 c.Backend = backend
323 return c
324 }
325-
326-// WithAccessMethod sets the access control method for the configuration.
327-func (c *Config) WithAccessMethod(access backend.AccessMethod) *Config {
328- c.Access = access
329- return c
330-}
331diff --git a/server/daemon.go b/server/daemon.go
332index 789f1e9bd39b2adc7fd4ce7152d3b1844e2f4195..86ad93e4167f908198a177fd176b9c79079db47d 100644
333--- a/server/daemon.go
334+++ b/server/daemon.go
335@@ -12,6 +12,7 @@ import (
336
337 "github.com/charmbracelet/soft-serve/server/backend"
338 "github.com/charmbracelet/soft-serve/server/config"
339+ "github.com/charmbracelet/soft-serve/server/utils"
340 "github.com/go-git/go-git/v5/plumbing/format/pktline"
341 )
342
343@@ -201,19 +202,19 @@ func (d *GitDaemon) handleClient(conn net.Conn) {
344 return
345 }
346
347- name := sanitizeRepoName(string(opts[0]))
348+ name := utils.SanitizeRepo(string(opts[0]))
349 logger.Debugf("git: connect %s %s %s", c.RemoteAddr(), cmd, name)
350 defer logger.Debugf("git: disconnect %s %s %s", c.RemoteAddr(), cmd, name)
351 // git bare repositories should end in ".git"
352 // https://git-scm.com/docs/gitrepository-layout
353 repo := name + ".git"
354- reposDir := d.cfg.Backend.RepositoryStorePath()
355+ reposDir := filepath.Join(d.cfg.DataPath, "repos")
356 if err := ensureWithin(reposDir, repo); err != nil {
357 fatal(c, err)
358 return
359 }
360
361- auth := d.cfg.Access.AccessLevel(name, nil)
362+ auth := d.cfg.Backend.AccessLevel(name, nil)
363 if auth < backend.ReadOnlyAccess {
364 fatal(c, ErrNotAuthed)
365 return
366diff --git a/server/daemon_test.go b/server/daemon_test.go
367index e2e059e42cd6553cf5b707a386061204a1852b98..220624000d8cd733a1686f0bcb38214376059db3 100644
368--- a/server/daemon_test.go
369+++ b/server/daemon_test.go
370@@ -8,10 +8,12 @@ import (
371 "log"
372 "net"
373 "os"
374+ "path/filepath"
375 "strings"
376 "testing"
377 "time"
378
379+ "github.com/charmbracelet/soft-serve/server/backend/file"
380 "github.com/charmbracelet/soft-serve/server/config"
381 "github.com/go-git/go-git/v5/plumbing/format/pktline"
382 )
383@@ -29,7 +31,11 @@ func TestMain(m *testing.M) {
384 os.Setenv("SOFT_SERVE_GIT_MAX_TIMEOUT", "100")
385 os.Setenv("SOFT_SERVE_GIT_IDLE_TIMEOUT", "1")
386 os.Setenv("SOFT_SERVE_GIT_LISTEN_ADDR", fmt.Sprintf(":%d", randomPort()))
387- cfg := config.DefaultConfig()
388+ fb, err := file.NewFileBackend(filepath.Join(tmp, "repos"))
389+ if err != nil {
390+ log.Fatal(err)
391+ }
392+ cfg := config.DefaultConfig().WithBackend(fb).WithAccessMethod(fb)
393 d, err := NewGitDaemon(cfg)
394 if err != nil {
395 log.Fatal(err)
396diff --git a/server/http.go b/server/http.go
397index fdc58c551f394e920852a1fe47cb337501b956ef..5c7d168840031de3122b15441728c77e4fd11562 100644
398--- a/server/http.go
399+++ b/server/http.go
400@@ -13,6 +13,7 @@ import (
401
402 "github.com/charmbracelet/soft-serve/server/backend"
403 "github.com/charmbracelet/soft-serve/server/config"
404+ "github.com/charmbracelet/soft-serve/server/utils"
405 "github.com/dustin/go-humanize"
406 "goji.io"
407 "goji.io/pat"
408@@ -68,7 +69,7 @@ func NewHTTPServer(cfg *config.Config) (*HTTPServer, error) {
409 mux := goji.NewMux()
410 s := &HTTPServer{
411 cfg: cfg,
412- dirHandler: http.FileServer(http.Dir(cfg.Backend.RepositoryStorePath())),
413+ dirHandler: http.FileServer(http.Dir(filepath.Join(cfg.DataPath, "repos"))),
414 server: &http.Server{
415 Addr: cfg.HTTP.ListenAddr,
416 Handler: mux,
417@@ -114,7 +115,7 @@ Redirecting to docs at <a href="https://godoc.org/{{.ImportRoot}}/{{.Repo}}">god
418
419 func (s *HTTPServer) repoIndexHandler(w http.ResponseWriter, r *http.Request) {
420 repo := pat.Param(r, "repo")
421- repo = sanitizeRepoName(repo)
422+ repo = utils.SanitizeRepo(repo)
423
424 // Only respond to go-get requests
425 if r.URL.Query().Get("go-get") != "1" {
426@@ -122,7 +123,7 @@ func (s *HTTPServer) repoIndexHandler(w http.ResponseWriter, r *http.Request) {
427 return
428 }
429
430- access := s.cfg.Access.AccessLevel(repo, nil)
431+ access := s.cfg.Backend.AccessLevel(repo, nil)
432 if access < backend.ReadOnlyAccess {
433 http.NotFound(w, r)
434 return
435@@ -149,16 +150,16 @@ func (s *HTTPServer) repoIndexHandler(w http.ResponseWriter, r *http.Request) {
436
437 func (s *HTTPServer) dumbGitHandler(w http.ResponseWriter, r *http.Request) {
438 repo := pat.Param(r, "repo")
439- repo = sanitizeRepoName(repo) + ".git"
440+ repo = utils.SanitizeRepo(repo) + ".git"
441
442- access := s.cfg.Access.AccessLevel(repo, nil)
443+ access := s.cfg.Backend.AccessLevel(repo, nil)
444 if access < backend.ReadOnlyAccess || !s.cfg.Backend.AllowKeyless() {
445 httpStatusError(w, http.StatusUnauthorized)
446 return
447 }
448
449 path := pattern.Path(r.Context())
450- stat, err := os.Stat(filepath.Join(s.cfg.Backend.RepositoryStorePath(), repo, path))
451+ stat, err := os.Stat(filepath.Join(s.cfg.DataPath, "repos", repo, path))
452 // Restrict access to files
453 if err != nil || stat.IsDir() {
454 http.NotFound(w, r)
455diff --git a/server/server.go b/server/server.go
456index f1c703af2a1e195c530a13a87db7d369e052b22a..5cfa6192a967f08123e8334ce80a4b98743df35e 100644
457--- a/server/server.go
458+++ b/server/server.go
459@@ -7,6 +7,7 @@ import (
460 "github.com/charmbracelet/log"
461
462 "github.com/charmbracelet/soft-serve/server/backend"
463+ "github.com/charmbracelet/soft-serve/server/backend/file"
464 "github.com/charmbracelet/soft-serve/server/config"
465 "github.com/charmbracelet/ssh"
466 "golang.org/x/sync/errgroup"
467@@ -23,7 +24,6 @@ type Server struct {
468 HTTPServer *HTTPServer
469 Config *config.Config
470 Backend backend.Backend
471- Access backend.AccessMethod
472 }
473
474 // NewServer returns a new *ssh.Server configured to serve Soft Serve. The SSH
475@@ -33,10 +33,19 @@ type Server struct {
476 // publicly writable until configured otherwise by cloning the `config` repo.
477 func NewServer(cfg *config.Config) (*Server, error) {
478 var err error
479+ if cfg.Backend == nil {
480+ fb, err := file.NewFileBackend(cfg.DataPath)
481+ if err != nil {
482+ logger.Fatal(err)
483+ }
484+ // Add the initial admin keys to the list of admins.
485+ fb.AdditionalAdmins = cfg.InitialAdminKeys
486+ cfg = cfg.WithBackend(fb)
487+ }
488+
489 srv := &Server{
490 Config: cfg,
491 Backend: cfg.Backend,
492- Access: cfg.Access,
493 }
494 srv.SSHServer, err = NewSSHServer(cfg)
495 if err != nil {
496diff --git a/server/session.go b/server/session.go
497index 56bc02ed88ef87d9aa1474dcc99435627e0f4b20..03df433ddb530e753e083af6c55f4c4f96d9c044 100644
498--- a/server/session.go
499+++ b/server/session.go
500@@ -26,7 +26,7 @@ func SessionHandler(cfg *config.Config) bm.ProgramHandler {
501 initialRepo := ""
502 if len(cmd) == 1 {
503 initialRepo = cmd[0]
504- auth := cfg.Access.AccessLevel(initialRepo, s.PublicKey())
505+ auth := cfg.Backend.AccessLevel(initialRepo, s.PublicKey())
506 if auth < backend.ReadOnlyAccess {
507 wish.Fatalln(s, cm.ErrUnauthorized)
508 return nil
509diff --git a/server/session_test.go b/server/session_test.go
510index bd81a2320ba8fa14c2f5712b4154a8e6aa98ed76..a120321a266005fdf6d98f6504dde1523e5ffc4b 100644
511--- a/server/session_test.go
512+++ b/server/session_test.go
513@@ -3,10 +3,13 @@ package server
514 import (
515 "errors"
516 "fmt"
517+ "log"
518 "os"
519+ "path/filepath"
520 "testing"
521 "time"
522
523+ "github.com/charmbracelet/soft-serve/server/backend/file"
524 "github.com/charmbracelet/soft-serve/server/config"
525 "github.com/charmbracelet/ssh"
526 bm "github.com/charmbracelet/wish/bubbletea"
527@@ -49,7 +52,11 @@ func setup(tb testing.TB) *gossh.Session {
528 is.NoErr(os.Unsetenv("SOFT_SERVE_SSH_LISTEN_ADDR"))
529 is.NoErr(os.RemoveAll(dp))
530 })
531- cfg := config.DefaultConfig()
532+ fb, err := file.NewFileBackend(filepath.Join(dp, "repos"))
533+ if err != nil {
534+ log.Fatal(err)
535+ }
536+ cfg := config.DefaultConfig().WithBackend(fb).WithAccessMethod(fb)
537 return testsession.New(tb, &ssh.Server{
538 Handler: bm.MiddlewareWithProgramHandler(SessionHandler(cfg), termenv.ANSI256)(func(s ssh.Session) {
539 _, _, active := s.Pty()
540diff --git a/server/ssh.go b/server/ssh.go
541index 25dcdaed86d01b6e9c3d18263f27bab7037aeafd..61e8f7f54c7072866f7927b583375b6cfb11802b 100644
542--- a/server/ssh.go
543+++ b/server/ssh.go
544@@ -12,6 +12,7 @@ import (
545 "github.com/charmbracelet/soft-serve/server/backend"
546 cm "github.com/charmbracelet/soft-serve/server/cmd"
547 "github.com/charmbracelet/soft-serve/server/config"
548+ "github.com/charmbracelet/soft-serve/server/utils"
549 "github.com/charmbracelet/ssh"
550 "github.com/charmbracelet/wish"
551 bm "github.com/charmbracelet/wish/bubbletea"
552@@ -88,7 +89,7 @@ func (s *SSHServer) Shutdown(ctx context.Context) error {
553
554 // PublicKeyAuthHandler handles public key authentication.
555 func (s *SSHServer) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
556- return s.cfg.Access.AccessLevel("", pk) > backend.NoAccess
557+ return s.cfg.Backend.AccessLevel("", pk) > backend.NoAccess
558 }
559
560 // KeyboardInteractiveHandler handles keyboard interactive authentication.
561@@ -109,14 +110,14 @@ func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware {
562 if len(cmd) >= 2 && strings.HasPrefix(cmd[0], "git") {
563 gc := cmd[0]
564 // repo should be in the form of "repo.git"
565- name := sanitizeRepoName(cmd[1])
566+ name := utils.SanitizeRepo(cmd[1])
567 pk := s.PublicKey()
568- access := cfg.Access.AccessLevel(name, pk)
569+ access := cfg.Backend.AccessLevel(name, pk)
570 // git bare repositories should end in ".git"
571 // https://git-scm.com/docs/gitrepository-layout
572 repo := name + ".git"
573
574- reposDir := cfg.Backend.RepositoryStorePath()
575+ reposDir := filepath.Join(cfg.DataPath, "repos")
576 if err := ensureWithin(reposDir, repo); err != nil {
577 sshFatal(s, err)
578 return
579@@ -168,10 +169,3 @@ func sshFatal(s ssh.Session, v ...interface{}) {
580 WritePktline(s, v...)
581 s.Exit(1) // nolint: errcheck
582 }
583-
584-func sanitizeRepoName(repo string) string {
585- repo = strings.TrimPrefix(repo, "/")
586- repo = filepath.Clean(repo)
587- repo = strings.TrimSuffix(repo, ".git")
588- return repo
589-}
590diff --git a/server/utils/utils.go b/server/utils/utils.go
591new file mode 100644
592index 0000000000000000000000000000000000000000..3f2fe5dabe16b0daa646a56d1bf3e01c0b5572cb
593--- /dev/null
594+++ b/server/utils/utils.go
595@@ -0,0 +1,14 @@
596+package utils
597+
598+import (
599+ "path/filepath"
600+ "strings"
601+)
602+
603+// SanitizeRepo returns a sanitized version of the given repository name.
604+func SanitizeRepo(repo string) string {
605+ repo = strings.TrimPrefix(repo, "/")
606+ repo = filepath.Clean(repo)
607+ repo = strings.TrimSuffix(repo, ".git")
608+ return repo
609+}