0aec8c2ed4b8d1a9a416128cdae72995bd2ea006

Author
Ayman Bagabas <ayman.bagabas@gmail.com>
Committer
Ayman Bagabas <ayman.bagabas@gmail.com>
Date

Message

feat: add soft-serve middleware commands

* list files
* cat files
* reload config
* git command

Diff

This diff is truncated to protect this page.

  1diff --git a/go.mod b/go.mod
  2index f81b8c1dac06beb4aa618bd5678591a70fb6a759..a1fae4825654acfc2d901bc0a852be14e7b07faa 100755
  3--- a/go.mod
  4+++ b/go.mod
  5@@ -29,6 +29,7 @@ require (
  6 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
  7 	github.com/muesli/mango v0.1.0
  8 	github.com/muesli/roff v0.1.0
  9+	github.com/spf13/cobra v1.4.0
 10 )
 11 
 12 require (
 13@@ -45,6 +46,7 @@ require (
 14 	github.com/go-git/gcfg v1.5.0 // indirect
 15 	github.com/gorilla/css v1.0.0 // indirect
 16 	github.com/imdario/mergo v0.3.12 // indirect
 17+	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 18 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 19 	github.com/kevinburke/ssh_config v1.1.0 // indirect
 20 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
 21@@ -57,6 +59,7 @@ require (
 22 	github.com/olekukonko/tablewriter v0.0.5 // indirect
 23 	github.com/rivo/uniseg v0.2.0 // indirect
 24 	github.com/sahilm/fuzzy v0.1.0 // indirect
 25+	github.com/spf13/pflag v1.0.5 // indirect
 26 	github.com/xanzy/ssh-agent v0.3.1 // indirect
 27 	github.com/yuin/goldmark v1.4.4 // indirect
 28 	github.com/yuin/goldmark-emoji v1.0.1 // indirect
 29diff --git a/go.sum b/go.sum
 30index 8dc59b6d4986b7b5a014f448aa0204dc51a8661f..85a6b542c3b828213c3ec81be4688c62fb7fad0f 100644
 31--- a/go.sum
 32+++ b/go.sum
 33@@ -40,6 +40,7 @@ github.com/charmbracelet/wish v0.3.1-0.20220405152319-bea68c3da3b1/go.mod h1:+Eg
 34 github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
 35 github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
 36 github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
 37+github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 38 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 39 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 40 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 41@@ -76,6 +77,8 @@ github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
 42 github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
 43 github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
 44 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 45+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 46+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 47 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 48 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 49 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
 50@@ -135,12 +138,17 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 51 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 52 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 53 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 54+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 55 github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
 56 github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
 57 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
 58 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 59 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 60 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 61+github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
 62+github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
 63+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 64+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 65 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 66 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 67 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 68diff --git a/server/cmd/cat.go b/server/cmd/cat.go
 69new file mode 100644
 70index 0000000000000000000000000000000000000000..18ff5678c982d9e4b73cab235c331c9d57d78d3b
 71--- /dev/null
 72+++ b/server/cmd/cat.go
 73@@ -0,0 +1,123 @@
 74+package cmd
 75+
 76+import (
 77+	"fmt"
 78+	"strings"
 79+
 80+	"github.com/alecthomas/chroma/lexers"
 81+	gansi "github.com/charmbracelet/glamour/ansi"
 82+	"github.com/charmbracelet/lipgloss"
 83+	"github.com/charmbracelet/soft-serve/internal/git"
 84+	"github.com/charmbracelet/soft-serve/tui/common"
 85+	gitwish "github.com/charmbracelet/wish/git"
 86+	"github.com/muesli/termenv"
 87+	"github.com/spf13/cobra"
 88+)
 89+
 90+var (
 91+	lineDigitStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("239"))
 92+	lineBarStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color("236"))
 93+	dirnameStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color("#00AAFF"))
 94+	filenameStyle  = lipgloss.NewStyle()
 95+	filemodeStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("#777777"))
 96+)
 97+
 98+// CatCommand returns a command that prints the contents of a file.
 99+func CatCommand() *cobra.Command {
100+	var linenumber bool
101+	var color bool
102+
103+	catCmd := &cobra.Command{
104+		Use:   "cat PATH",
105+		Short: "Outputs the contents of the file at path.",
106+		Args:  cobra.ExactArgs(1),
107+		RunE: func(cmd *cobra.Command, args []string) error {
108+			ac, s := fromContext(cmd)
109+			ps := strings.Split(args[0], "/")
110+			rn := ps[0]
111+			fp := strings.Join(ps[1:], "/")
112+			auth := ac.AuthRepo(rn, s.PublicKey())
113+			if auth < gitwish.ReadOnlyAccess {
114+				return ErrUnauthorized
115+			}
116+			var repo *git.Repo
117+			repoExists := false
118+			for _, rp := range ac.Source.AllRepos() {
119+				if rp.Name() == rn {
120+					repoExists = true
121+					repo = rp
122+					break
123+				}
124+			}
125+			if !repoExists {
126+				return ErrRepoNotFound
127+			}
128+			c, _, err := repo.LatestFile(fp)
129+			if err != nil {
130+				return err
131+			}
132+			if color {
133+				c, err = withFormatting(fp, c)
134+				if err != nil {
135+					return err
136+				}
137+			}
138+			if linenumber {
139+				c = withLineNumber(c, color)
140+			}
141+			fmt.Fprint(s, c)
142+			return nil
143+		},
144+	}
145+	catCmd.Flags().BoolVarP(&linenumber, "linenumber", "l", false, "Print line numbers")
146+	catCmd.Flags().BoolVarP(&color, "color", "c", false, "Colorize output")
147+
148+	return catCmd
149+}
150+
151+func withLineNumber(s string, color bool) string {
152+	lines := strings.Split(s, "\n")
153+	// NB: len() is not a particularly safe way to count string width (because
154+	// it's counting bytes instead of runes) but in this case it's okay
155+	// because we're only dealing with digits, which are one byte each.
156+	mll := len(fmt.Sprintf("%d", len(lines)))
157+	for i, l := range lines {
158+		digit := fmt.Sprintf("%*d", mll, i+1)
159+		bar := "│"
160+		if color {
161+			digit = lineDigitStyle.Render(digit)
162+			bar = lineBarStyle.Render(bar)
163+		}
164+		if i < len(lines)-1 || len(l) != 0 {
165+			// If the final line was a newline we'll get an empty string for
166+			// the final line, so drop the newline altogether.
167+			lines[i] = fmt.Sprintf(" %s %s %s", digit, bar, l)
168+		}
169+	}
170+	return strings.Join(lines, "\n")
171+}
172+
173diff --git a/server/cmd/cmd.go b/server/cmd/cmd.go
174new file mode 100644
175index 0000000000000000000000000000000000000000..e71ab65357ed0978b0a65bc9c6f77e3167da9941
176--- /dev/null
177+++ b/server/cmd/cmd.go
178@@ -0,0 +1,70 @@
179+package cmd
180+
181+import (
182+	"fmt"
183+
184+	appCfg "github.com/charmbracelet/soft-serve/internal/config"
185+	"github.com/gliderlabs/ssh"
186+	"github.com/spf13/cobra"
187+)
188+
189+var (
190+	// ErrUnauthorized is returned when the user is not authorized to perform action.
191+	ErrUnauthorized = fmt.Errorf("Unauthorized")
192+	// ErrRepoNotFound is returned when the repo is not found.
193+	ErrRepoNotFound = fmt.Errorf("Repository not found")
194+	// ErrFileNotFound is returned when the file is not found.
195+	ErrFileNotFound = fmt.Errorf("File not found")
196+
197+	usageTemplate = `Usage:{{if .Runnable}}
198+  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
199+  {{.UseLine}} [command]{{end}}{{if gt (len .Aliases) 0}}
200+
201+Aliases:
202+  {{.NameAndAliases}}{{end}}{{if .HasExample}}
203+
204+Examples:
205+{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
206+
207+Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
208+  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
209+
210+Flags:
211+{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
212+
213+Global Flags:
214+{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
215+
216+Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
217+  {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
218+
219+Use "{{.UseLine}} [command] --help" for more information about a command.{{end}}
220+`
221+)
222+
223+// RootCommand is the root command for the server.
224+func RootCommand() *cobra.Command {
225+	rootCmd := &cobra.Command{
226+		Use:                   "ssh [-p PORT] HOST",
227+		Long:                  "Soft Serve is a self-hostable Git server for the command line.",
228+		Args:                  cobra.MinimumNArgs(1),
229+		DisableFlagsInUseLine: true,
230+	}
231+	rootCmd.SetUsageTemplate(usageTemplate)
232+	rootCmd.CompletionOptions.DisableDefaultCmd = true
233+	rootCmd.AddCommand(
234+		ReloadCommand(),
235+		CatCommand(),
236+		ListCommand(),
237+		GitCommand(),
238+	)
239+
240+	return rootCmd
241+}
242+
243+func fromContext(cmd *cobra.Command) (*appCfg.Config, ssh.Session) {
244+	ctx := cmd.Context()
245+	ac := ctx.Value("config").(*appCfg.Config)
246+	s := ctx.Value("session").(ssh.Session)
247+	return ac, s
248+}
249diff --git a/server/cmd/git.go b/server/cmd/git.go
250new file mode 100644
251index 0000000000000000000000000000000000000000..fd2c3be87505141eb56f3c2dd625fcd43097c84f
252--- /dev/null
253+++ b/server/cmd/git.go
254@@ -0,0 +1,54 @@
255+package cmd
256+
257+import (
258+	"io"
259+	"os/exec"
260+
261+	"github.com/charmbracelet/soft-serve/internal/git"
262+	gitwish "github.com/charmbracelet/wish/git"
263+	"github.com/spf13/cobra"
264+)
265+
266+// GitCommand returns a command that handles Git operations.
267+func GitCommand() *cobra.Command {
268+	gitCmd := &cobra.Command{
269+		Use:   "git REPO COMMAND",
270+		Short: "Perform Git operations on a repository.",
271+		RunE: func(cmd *cobra.Command, args []string) error {
272+			ac, s := fromContext(cmd)
273+			auth := ac.AuthRepo("config", s.PublicKey())
274+			if auth < gitwish.AdminAccess {
275+				return ErrUnauthorized
276+			}
277+			if len(args) < 1 {
278+				return runGit(nil, s, s, "")
279+			}
280+			var repo *git.Repo
281+			rn := args[0]
282+			repoExists := false
283+			for _, rp := range ac.Source.AllRepos() {
284+				if rp.Name() == rn {
285+					repoExists = true
286+					repo = rp
287+					break
288+				}
289+			}
290+			if !repoExists {
291+				return ErrRepoNotFound
292+			}
293+			return runGit(nil, s, s, repo.Path(), args[1:]...)
294+		},
295+	}
296+	gitCmd.Flags().SetInterspersed(false)
297+
298+	return gitCmd
299+}
300+
301+func runGit(in io.Reader, out, err io.Writer, dir string, args ...string) error {
302+	cmd := exec.Command("git", args...)
303+	cmd.Stdin = in
304+	cmd.Stdout = out
305+	cmd.Stderr = err
306+	cmd.Dir = dir
307+	return cmd.Run()
308+}
309diff --git a/server/cmd/list.go b/server/cmd/list.go
310new file mode 100644
311index 0000000000000000000000000000000000000000..f07a2a68f3600357e0020ea6269a954714740220
312--- /dev/null
313+++ b/server/cmd/list.go
314@@ -0,0 +1,81 @@
315+package cmd
316+
317+import (
318+	"fmt"
319+	"path/filepath"
320+	"strings"
321+
322+	"github.com/charmbracelet/soft-serve/git"
323+	gitwish "github.com/charmbracelet/wish/git"
324+	"github.com/spf13/cobra"
325+)
326+
327+// ListCommand returns a command that list file or directory at path.
328+func ListCommand() *cobra.Command {
329+	lsCmd := &cobra.Command{
330+		Use:     "ls PATH",
331+		Aliases: []string{"list"},
332+		Short:   "List file or directory at path.",
333+		Args:    cobra.RangeArgs(0, 1),
334+		RunE: func(cmd *cobra.Command, args []string) error {
335+			ac, s := fromContext(cmd)
336+			rn := ""
337+			path := ""
338+			ps := []string{}
339+			if len(args) > 0 {
340+				path = filepath.Clean(args[0])
341+				ps = strings.Split(path, "/")
342+				rn = ps[0]
343+				auth := ac.AuthRepo(rn, s.PublicKey())
344+				if auth < gitwish.ReadOnlyAccess {
345+					return ErrUnauthorized
346+				}
347+			}
348+			if path == "" || path == "." || path == "/" {
349+				for _, r := range ac.Source.AllRepos() {
350+					fmt.Fprintln(s, r.Name())
351+				}
352+				return nil
353+			}
354+			r, err := ac.Source.GetRepo(rn)
355+			if err != nil {
356+				return err
357+			}
358+			head, err := r.HEAD()
359+			if err != nil {
360+				return err
361+			}
362+			tree, err := r.Tree(head, "")
363+			if err != nil {
364+				return err
365+			}
366+			subpath := strings.Join(ps[1:], "/")
367+			ents := git.Entries{}
368+			te, err := tree.TreeEntry(subpath)
369+			if err == git.ErrRevisionNotExist {
370+				return ErrFileNotFound
371+			}
372+			if err != nil {
373+				return err
374+			}
375+			if te.Type() == "tree" {
376+				tree, err = tree.SubTree(subpath)
377+				if err != nil {
378+					return err
379+				}
380+				ents, err = tree.Entries()
381+				if err != nil {
382+					return err
383+				}
384+			} else {
385+				ents = append(ents, te)
386+			}
387+			ents.Sort()
388+			for _, ent := range ents {
389+				fmt.Fprintf(s, "%s\t%d\t %s\n", ent.Mode(), ent.Size(), ent.Name())
390+			}
391+			return nil
392+		},
393+	}
394+	return lsCmd
395+}
396diff --git a/server/cmd/reload.go b/server/cmd/reload.go
397new file mode 100644
398index 0000000000000000000000000000000000000000..7f2312ab6e550109e9c52d0ee9c0ef80f3fa1253
399--- /dev/null
400+++ b/server/cmd/reload.go
401@@ -0,0 +1,23 @@
402+package cmd
403+
404+import (
405+	gitwish "github.com/charmbracelet/wish/git"
406+	"github.com/spf13/cobra"
407+)
408+
409+// ReloadCommand returns a command that reloads the server configuration.
410+func ReloadCommand() *cobra.Command {
411+	reloadCmd := &cobra.Command{
412+		Use:   "reload",
413+		Short: "Reloads the configuration",
414+		RunE: func(cmd *cobra.Command, args []string) error {
415+			ac, s := fromContext(cmd)
416+			auth := ac.AuthRepo("config", s.PublicKey())
417+			if auth < gitwish.AdminAccess {
418+				return ErrUnauthorized
419+			}
420+			return ac.Reload()
421+		},
422+	}
423+	return reloadCmd
424+}
425diff --git a/server/middleware.go b/server/middleware.go
426index cfe89a3fd9a444ee19de17eb504fe4bfdf23de47..3dd61517925175e8478cd94d6ac607d85f643afb 100644
427--- a/server/middleware.go
428+++ b/server/middleware.go
429@@ -1,186 +1,48 @@
430 package server
431 
432 import (
433+	"context"
434 	"fmt"
435-	"path/filepath"
436-	"strings"
437 
438-	"github.com/alecthomas/chroma/lexers"
439-	gansi "github.com/charmbracelet/glamour/ansi"
440-	"github.com/charmbracelet/lipgloss"
441-	"github.com/charmbracelet/soft-serve/git"
442 	appCfg "github.com/charmbracelet/soft-serve/internal/config"
443-	"github.com/charmbracelet/soft-serve/tui/common"
444+	"github.com/charmbracelet/soft-serve/server/cmd"
445 	"github.com/charmbracelet/wish"
446-	gitwish "github.com/charmbracelet/wish/git"
447 	"github.com/gliderlabs/ssh"
448-	ggit "github.com/gogs/git-module"
449-	"github.com/muesli/termenv"
450 )
451 
452-var (
453-	lineDigitStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("239"))
454-	lineBarStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color("236"))
455-	dirnameStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color("#00AAFF"))
456-	filenameStyle  = lipgloss.NewStyle()
457-	filemodeStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("#777777"))
458-)
459-
460-// softServeMiddleware is a middleware that handles displaying files with the
461-// option of syntax highlighting and line numbers.
462-func softServeMiddleware(ac *appCfg.Config) wish.Middleware {
463+// softMiddleware is the Soft Serve middleware that handles SSH commands.
464+func softMiddleware(ac *appCfg.Config) wish.Middleware {
465 	return func(sh ssh.Handler) ssh.Handler {
466 		return func(s ssh.Session) {
467-			_, _, active := s.Pty()
468-			cmds := s.Command()
469-			if !active && len(cmds) > 0 {
470-				func() {
471-					color := false
472-					lineno := false
473-					fp := filepath.Clean(cmds[0])
474-					ps := strings.Split(fp, "/")
475-					repo := ps[0]
476-					if repo == "config" {
477-						return
478-					}
479-					repoExists := false
480-					for _, rp := range ac.Source.AllRepos() {
481-						if rp.Name() == repo {
482-							repoExists = true
483-							break
484-						}
485-					}
486-					if !repoExists {
487-						s.Write([]byte("repository not found"))
488-						s.Exit(1)
489-						return
490-					}
491-					auth := ac.AuthRepo(repo, s.PublicKey())
492-					if auth < gitwish.ReadOnlyAccess {
493-						s.Write([]byte("unauthorized"))
494-						s.Exit(1)
495-						return
496-					}
497-					for _, op := range cmds[1:] {
498-						if op == "-c" || op == "--color" {
499-							color = true
500-						} else if op == "-l" || op == "--lineno" || op == "--linenumber" {
501-							lineno = true
502-						}
503-					}
504-					rs, err := ac.Source.GetRepo(repo)
505-					if err != nil {
506-						_, _ = s.Write([]byte(err.Error()))
507-						_ = s.Exit(1)
508-						return
509-					}
510-					ref, err := rs.HEAD()
511-					if err != nil {
512-						_, _ = s.Write([]byte(err.Error()))
513-						_ = s.Exit(1)
514-						return
515-					}
516-					p := strings.Join(ps[1:], "/")
517-					t, err := rs.Tree(ref, p)
518-					if err != nil && err != ggit.ErrRevisionNotExist {
519-						_, _ = s.Write([]byte(err.Error()))
520-						_ = s.Exit(1)
521-						return
522-					}
523-					if err == ggit.ErrRevisionNotExist {
524-						_, _ = s.Write([]byte(git.ErrFileNotFound.Error()))
525-						_ = s.Exit(1)
526-						return
527-					}
528-					ents, err := t.Entries()
529diff --git a/server/middleware_test.go b/server/middleware_test.go
530index 9b8fe2661ad16c5c42995755c8415b122d655691..94ac3ebbfef8dd25bbdcf392ec31e48f4f6301f4 100644
531--- a/server/middleware_test.go
532+++ b/server/middleware_test.go
533@@ -1,8 +1,10 @@
534 package server
535 
536 import (
537+	"os"
538 	"testing"
539 
540+	sconfig "github.com/charmbracelet/soft-serve/config"
541 	"github.com/charmbracelet/soft-serve/internal/config"
542 	"github.com/charmbracelet/wish/testsession"
543 	"github.com/gliderlabs/ssh"
544@@ -12,13 +14,21 @@ import (
545 var ()
546 
547 func TestMiddleware(t *testing.T) {
548+	t.Cleanup(func() {
549+		os.RemoveAll("testmiddleware")
550+	})
551 	is := is.New(t)
552-	appCfg, err := config.NewConfig(cfg)
553+	appCfg, err := config.NewConfig(&sconfig.Config{
554+		Host:     "localhost",
555+		Port:     22223,
556+		RepoPath: "testmiddleware/repos",
557+		KeyPath:  "testmiddleware/key",
558+	})
559 	is.NoErr(err)
560 	_ = testsession.New(t, &ssh.Server{
561-		Handler: softServeMiddleware(appCfg)(func(s ssh.Session) {
562+		Handler: softMiddleware(appCfg)(func(s ssh.Session) {
563 			t.Run("TestCatConfig", func(t *testing.T) {
564-				_, err := s.Write([]byte("config/config.json"))
565+				_, err := s.Write([]byte("cat config/config.json"))
566 				if err == nil {
567 					t.Errorf("Expected error, got nil")
568 				}
569diff --git a/server/server.go b/server/server.go
570index 87bc4755118f52ec809b9f9fa4084bb5ae24a2a5..aa4063728974d110a19fc60dee32fa595576162b 100644
571--- a/server/server.go
572+++ b/server/server.go
573@@ -36,7 +36,7 @@ func NewServer(cfg *config.Config) *Server {
574 	mw := []wish.Middleware{
575 		rm.MiddlewareWithLogger(
576 			cfg.ErrorLog,
577-			softServeMiddleware(ac),
578+			softMiddleware(ac),
579 			bm.Middleware(tui.SessionHandler(ac)),
580 			gm.Middleware(cfg.RepoPath, ac),
581 			lm.Middleware(),