2d5089e43ee5067760d9b40d9454dacb00709425

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

Message

refactor(server): move into modules

Diff

This diff is truncated to protect this page.

  1diff --git a/server/daemon.go b/server/daemon/daemon.go
  2rename from server/daemon.go
  3rename to server/daemon/daemon.go
  4index 79b94d88418307fedd16858afa6c13842067b4f9..1a10c8c3bc204183e9362f74a0da948e8e816621 100644
  5--- a/server/daemon.go
  6+++ b/server/daemon/daemon.go
  7@@ -1,4 +1,4 @@
  8-package server
  9+package daemon
 10 
 11 import (
 12 	"bytes"
 13@@ -9,14 +9,20 @@ import (
 14 	"sync"
 15 	"time"
 16 
 17+	"github.com/charmbracelet/log"
 18 	"github.com/charmbracelet/soft-serve/server/backend"
 19 	"github.com/charmbracelet/soft-serve/server/config"
 20+	"github.com/charmbracelet/soft-serve/server/git"
 21 	"github.com/charmbracelet/soft-serve/server/utils"
 22 	"github.com/go-git/go-git/v5/plumbing/format/pktline"
 23 	"github.com/prometheus/client_golang/prometheus"
 24 	"github.com/prometheus/client_golang/prometheus/promauto"
 25 )
 26 
 27+var (
 28+	logger = log.WithPrefix("server.daemon")
 29+)
 30+
 31 var (
 32 	uploadPackGitCounter = promauto.NewCounterVec(prometheus.CounterOpts{
 33 		Namespace: "soft_serve",
 34@@ -33,8 +39,11 @@ var (
 35 	}, []string{"repo"})
 36 )
 37 
 38-// ErrServerClosed indicates that the server has been closed.
 39-var ErrServerClosed = fmt.Errorf("git: %w", net.ErrClosed)
 40+var (
 41+
 42+	// ErrServerClosed indicates that the server has been closed.
 43+	ErrServerClosed = fmt.Errorf("git: %w", net.ErrClosed)
 44+)
 45 
 46 // connections synchronizes access to to a net.Conn pool.
 47 type connections struct {
 48@@ -133,7 +142,7 @@ func (d *GitDaemon) Start() error {
 49 		// Close connection if there are too many open connections.
 50 		if d.conns.Size()+1 >= d.cfg.Git.MaxConnections {
 51 			logger.Debugf("git: max connections reached, closing %s", conn.RemoteAddr())
 52-			fatal(conn, ErrMaxConnections)
 53+			fatal(conn, git.ErrMaxConnections)
 54 			continue
 55 		}
 56 
 57@@ -146,7 +155,7 @@ func (d *GitDaemon) Start() error {
 58 }
 59 
 60 func fatal(c net.Conn, err error) {
 61-	writePktline(c, err)
 62+	git.WritePktline(c, err)
 63 	if err := c.Close(); err != nil {
 64 		logger.Debugf("git: error closing connection: %v", err)
 65 	}
 66@@ -176,10 +185,10 @@ func (d *GitDaemon) handleClient(conn net.Conn) {
 67 		if !s.Scan() {
 68 			if err := s.Err(); err != nil {
 69 				if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
 70-					fatal(c, ErrTimeout)
 71+					fatal(c, git.ErrTimeout)
 72 				} else {
 73 					logger.Debugf("git: error scanning pktline: %v", err)
 74-					fatal(c, ErrSystemMalfunction)
 75+					fatal(c, git.ErrSystemMalfunction)
 76 				}
 77 			}
 78 			return
 79@@ -197,32 +206,32 @@ func (d *GitDaemon) handleClient(conn net.Conn) {
 80 		line := s.Bytes()
 81 		split := bytes.SplitN(line, []byte{' '}, 2)
 82 		if len(split) != 2 {
 83-			fatal(c, ErrInvalidRequest)
 84+			fatal(c, git.ErrInvalidRequest)
 85 			return
 86 		}
 87 
 88-		gitPack := uploadPack
 89+		gitPack := git.UploadPack
 90 		counter := uploadPackGitCounter
 91 		cmd := string(split[0])
 92 		switch cmd {
 93-		case uploadPackBin:
 94-			gitPack = uploadPack
 95-		case uploadArchiveBin:
 96-			gitPack = uploadArchive
 97+		case git.UploadPackBin:
 98+			gitPack = git.UploadPack
 99+		case git.UploadArchiveBin:
100+			gitPack = git.UploadArchive
101 			counter = uploadArchiveGitCounter
102 		default:
103-			fatal(c, ErrInvalidRequest)
104+			fatal(c, git.ErrInvalidRequest)
105 			return
106 		}
107diff --git a/server/daemon_test.go b/server/daemon/daemon_test.go
108rename from server/daemon_test.go
109rename to server/daemon/daemon_test.go
110index 765e62c9a84cc7240529768e04744a982f46e573..a87cdbfb92cac21f6cf9d70c7f9dfe2af7e0ddc0 100644
111--- a/server/daemon_test.go
112+++ b/server/daemon/daemon_test.go
113@@ -1,4 +1,4 @@
114-package server
115+package daemon
116 
117 import (
118 	"bytes"
119@@ -14,6 +14,8 @@ import (
120 
121 	"github.com/charmbracelet/soft-serve/server/backend/sqlite"
122 	"github.com/charmbracelet/soft-serve/server/config"
123+	"github.com/charmbracelet/soft-serve/server/git"
124+	"github.com/charmbracelet/soft-serve/server/test"
125 	"github.com/go-git/go-git/v5/plumbing/format/pktline"
126 )
127 
128@@ -29,7 +31,7 @@ func TestMain(m *testing.M) {
129 	os.Setenv("SOFT_SERVE_GIT_MAX_CONNECTIONS", "3")
130 	os.Setenv("SOFT_SERVE_GIT_MAX_TIMEOUT", "100")
131 	os.Setenv("SOFT_SERVE_GIT_IDLE_TIMEOUT", "1")
132-	os.Setenv("SOFT_SERVE_GIT_LISTEN_ADDR", fmt.Sprintf(":%d", randomPort()))
133+	os.Setenv("SOFT_SERVE_GIT_LISTEN_ADDR", fmt.Sprintf(":%d", test.RandomPort()))
134 	cfg := config.DefaultConfig()
135 	d, err := NewGitDaemon(cfg)
136 	if err != nil {
137@@ -67,8 +69,8 @@ func TestIdleTimeout(t *testing.T) {
138 	if err != nil && !errors.Is(err, io.EOF) {
139 		t.Fatalf("expected nil, got error: %v", err)
140 	}
141-	if out != ErrTimeout.Error() || out == "" {
142-		t.Fatalf("expected %q error, got %q", ErrTimeout, out)
143+	if out != git.ErrTimeout.Error() || out == "" {
144+		t.Fatalf("expected %q error, got %q", git.ErrTimeout, out)
145 	}
146 }
147 
148@@ -84,8 +86,8 @@ func TestInvalidRepo(t *testing.T) {
149 	if err != nil {
150 		t.Fatalf("expected nil, got error: %v", err)
151 	}
152-	if out != ErrInvalidRepo.Error() {
153-		t.Fatalf("expected %q error, got %q", ErrInvalidRepo, out)
154+	if out != git.ErrInvalidRepo.Error() {
155+		t.Fatalf("expected %q error, got %q", git.ErrInvalidRepo, out)
156 	}
157 }
158 
159diff --git a/server/git.go b/server/git/git.go
160rename from server/git.go
161rename to server/git/git.go
162index 41a1609f1710051b7b1f46ebe0a789386c59a54d..53c192d5468cbecf84ea8d740945e9cc8c0d5ce0 100644
163--- a/server/git.go
164+++ b/server/git/git.go
165@@ -1,4 +1,4 @@
166-package server
167+package git
168 
169 import (
170 	"errors"
171@@ -36,13 +36,13 @@ var (
172 
173 // Git protocol commands.
174 const (
175-	receivePackBin   = "git-receive-pack"
176-	uploadPackBin    = "git-upload-pack"
177-	uploadArchiveBin = "git-upload-archive"
178+	ReceivePackBin   = "git-receive-pack"
179+	UploadPackBin    = "git-upload-pack"
180+	UploadArchiveBin = "git-upload-archive"
181 )
182 
183-// uploadPack runs the git upload-pack protocol against the provided repo.
184-func uploadPack(in io.Reader, out io.Writer, er io.Writer, repoDir string) error {
185+// UploadPack runs the git upload-pack protocol against the provided repo.
186+func UploadPack(in io.Reader, out io.Writer, er io.Writer, repoDir string) error {
187 	exists, err := fileExists(repoDir)
188 	if !exists {
189 		return ErrInvalidRepo
190@@ -50,11 +50,11 @@ func uploadPack(in io.Reader, out io.Writer, er io.Writer, repoDir string) error
191 	if err != nil {
192 		return err
193 	}
194-	return runGit(in, out, er, "", uploadPackBin[4:], repoDir)
195+	return RunGit(in, out, er, "", UploadPackBin[4:], repoDir)
196 }
197 
198-// uploadArchive runs the git upload-archive protocol against the provided repo.
199-func uploadArchive(in io.Reader, out io.Writer, er io.Writer, repoDir string) error {
200+// UploadArchive runs the git upload-archive protocol against the provided repo.
201+func UploadArchive(in io.Reader, out io.Writer, er io.Writer, repoDir string) error {
202 	exists, err := fileExists(repoDir)
203 	if !exists {
204 		return ErrInvalidRepo
205@@ -62,19 +62,19 @@ func uploadArchive(in io.Reader, out io.Writer, er io.Writer, repoDir string) er
206 	if err != nil {
207 		return err
208 	}
209-	return runGit(in, out, er, "", uploadArchiveBin[4:], repoDir)
210+	return RunGit(in, out, er, "", UploadArchiveBin[4:], repoDir)
211 }
212 
213-// receivePack runs the git receive-pack protocol against the provided repo.
214-func receivePack(in io.Reader, out io.Writer, er io.Writer, repoDir string) error {
215-	if err := runGit(in, out, er, "", receivePackBin[4:], repoDir); err != nil {
216+// ReceivePack runs the git receive-pack protocol against the provided repo.
217+func ReceivePack(in io.Reader, out io.Writer, er io.Writer, repoDir string) error {
218+	if err := RunGit(in, out, er, "", ReceivePackBin[4:], repoDir); err != nil {
219 		return err
220 	}
221-	return ensureDefaultBranch(in, out, er, repoDir)
222+	return EnsureDefaultBranch(in, out, er, repoDir)
223 }
224 
225-// runGit runs a git command in the given repo.
226-func runGit(in io.Reader, out io.Writer, err io.Writer, dir string, args ...string) error {
227+// RunGit runs a git command in the given repo.
228+func RunGit(in io.Reader, out io.Writer, err io.Writer, dir string, args ...string) error {
229 	c := git.NewCommand(args...)
230 	return c.RunInDirWithOptions(dir, git.RunInDirOptions{
231 		Stdin:  in,
232@@ -83,8 +83,8 @@ func runGit(in io.Reader, out io.Writer, err io.Writer, dir string, args ...stri
233 	})
234 }
235 
236-// writePktline encodes and writes a pktline to the given writer.
237-func writePktline(w io.Writer, v ...interface{}) {
238+// WritePktline encodes and writes a pktline to the given writer.
239+func WritePktline(w io.Writer, v ...interface{}) {
240 	msg := fmt.Sprintln(v...)
241 	pkt := pktline.NewEncoder(w)
242 	if err := pkt.EncodeString(msg); err != nil {
243@@ -95,8 +95,8 @@ func writePktline(w io.Writer, v ...interface{}) {
244 	}
245 }
246 
247-// ensureWithin ensures the given repo is within the repos directory.
248-func ensureWithin(reposDir string, repo string) error {
249+// EnsureWithin ensures the given repo is within the repos directory.
250+func EnsureWithin(reposDir string, repo string) error {
251 	repoDir := filepath.Join(reposDir, repo)
252 	absRepos, err := filepath.Abs(reposDir)
253 	if err != nil {
254@@ -129,7 +129,7 @@ func fileExists(path string) (bool, error) {
255 	return true, err
256 }
257 
258-func ensureDefaultBranch(in io.Reader, out io.Writer, er io.Writer, repoPath string) error {
259+func EnsureDefaultBranch(in io.Reader, out io.Writer, er io.Writer, repoPath string) error {
260 	r, err := git.Open(repoPath)
261 	if err != nil {
262 		return err
263@@ -144,7 +144,7 @@ func ensureDefaultBranch(in io.Reader, out io.Writer, er io.Writer, repoPath str
264 	// Rename the default branch to the first branch available
265diff --git a/server/server.go b/server/server.go
266index b4645f3cab1262667c37a07bd6e2c15763085f8e..d8be8069717ad7b0175b82ed4c8bf37c4797f617 100644
267--- a/server/server.go
268+++ b/server/server.go
269@@ -13,6 +13,10 @@ import (
270 	"github.com/charmbracelet/soft-serve/server/backend/sqlite"
271 	"github.com/charmbracelet/soft-serve/server/config"
272 	"github.com/charmbracelet/soft-serve/server/cron"
273+	"github.com/charmbracelet/soft-serve/server/daemon"
274+	sshsrv "github.com/charmbracelet/soft-serve/server/ssh"
275+	"github.com/charmbracelet/soft-serve/server/stats"
276+	"github.com/charmbracelet/soft-serve/server/web"
277 	"github.com/charmbracelet/ssh"
278 	"golang.org/x/sync/errgroup"
279 )
280@@ -23,10 +27,10 @@ var (
281 
282 // Server is the Soft Serve server.
283 type Server struct {
284-	SSHServer   *SSHServer
285-	GitDaemon   *GitDaemon
286-	HTTPServer  *HTTPServer
287-	StatsServer *StatsServer
288+	SSHServer   *sshsrv.SSHServer
289+	GitDaemon   *daemon.GitDaemon
290+	HTTPServer  *web.HTTPServer
291+	StatsServer *stats.StatsServer
292 	Cron        *cron.CronScheduler
293 	Config      *config.Config
294 	Backend     backend.Backend
295@@ -81,22 +85,22 @@ func NewServer(ctx context.Context, cfg *config.Config) (*Server, error) {
296 	// Add cron jobs.
297 	srv.Cron.AddFunc(jobSpecs["mirror"], mirrorJob(cfg))
298 
299-	srv.SSHServer, err = NewSSHServer(cfg, srv)
300+	srv.SSHServer, err = sshsrv.NewSSHServer(cfg, srv)
301 	if err != nil {
302 		return nil, err
303 	}
304 
305-	srv.GitDaemon, err = NewGitDaemon(cfg)
306+	srv.GitDaemon, err = daemon.NewGitDaemon(cfg)
307 	if err != nil {
308 		return nil, err
309 	}
310 
311-	srv.HTTPServer, err = NewHTTPServer(cfg)
312+	srv.HTTPServer, err = web.NewHTTPServer(cfg)
313 	if err != nil {
314 		return nil, err
315 	}
316 
317-	srv.StatsServer, err = NewStatsServer(cfg)
318+	srv.StatsServer, err = stats.NewStatsServer(cfg)
319 	if err != nil {
320 		return nil, err
321 	}
322@@ -124,7 +128,7 @@ func (s *Server) Start() error {
323 	errg, ctx := errgroup.WithContext(s.ctx)
324 	errg.Go(func() error {
325 		logger.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)
326-		if err := start(ctx, s.GitDaemon.Start); !errors.Is(err, ErrServerClosed) {
327+		if err := start(ctx, s.GitDaemon.Start); !errors.Is(err, daemon.ErrServerClosed) {
328 			return err
329 		}
330 		return nil
331diff --git a/server/server_test.go b/server/server_test.go
332index 3992794e936426dcce2a477921552811666512bc..55c614bf8f39ca669561f4b4c399bc66e2382644 100644
333--- a/server/server_test.go
334+++ b/server/server_test.go
335@@ -3,34 +3,28 @@ package server
336 import (
337 	"context"
338 	"fmt"
339-	"net"
340 	"path/filepath"
341 	"strings"
342 	"testing"
343 
344 	"github.com/charmbracelet/keygen"
345 	"github.com/charmbracelet/soft-serve/server/config"
346+	"github.com/charmbracelet/soft-serve/server/test"
347 	"github.com/charmbracelet/ssh"
348 	"github.com/matryer/is"
349 	gossh "golang.org/x/crypto/ssh"
350 )
351 
352-func randomPort() int {
353-	addr, _ := net.Listen("tcp", ":0") //nolint:gosec
354-	_ = addr.Close()
355-	return addr.Addr().(*net.TCPAddr).Port
356-}
357-
358 func setupServer(tb testing.TB) (*Server, *config.Config, string) {
359 	tb.Helper()
360 	tb.Log("creating keypair")
361 	pub, pkPath := createKeyPair(tb)
362 	dp := tb.TempDir()
363-	sshPort := fmt.Sprintf(":%d", randomPort())
364+	sshPort := fmt.Sprintf(":%d", test.RandomPort())
365 	tb.Setenv("SOFT_SERVE_DATA_PATH", dp)
366 	tb.Setenv("SOFT_SERVE_INITIAL_ADMIN_KEY", authorizedKey(pub))
367 	tb.Setenv("SOFT_SERVE_SSH_LISTEN_ADDR", sshPort)
368-	tb.Setenv("SOFT_SERVE_GIT_LISTEN_ADDR", fmt.Sprintf(":%d", randomPort()))
369+	tb.Setenv("SOFT_SERVE_GIT_LISTEN_ADDR", fmt.Sprintf(":%d", test.RandomPort()))
370 	cfg := config.DefaultConfig()
371 	tb.Log("configuring server")
372 	ctx := context.TODO()
373diff --git a/server/session.go b/server/ssh/session.go
374rename from server/session.go
375rename to server/ssh/session.go
376index cd7ddd272c08f2264666d761ac0ae6c92e649344..754fdbe68aeaeef79c379127339da9450d51d509 100644
377--- a/server/session.go
378+++ b/server/ssh/session.go
379@@ -1,4 +1,4 @@
380-package server
381+package ssh
382 
383 import (
384 	"fmt"
385diff --git a/server/session_test.go b/server/ssh/session_test.go
386rename from server/session_test.go
387rename to server/ssh/session_test.go
388index 673e85a6099668009d6499ee4bfe70c812507214..a39d500d25823a1b07e496ea75f111076f209a17 100644
389--- a/server/session_test.go
390+++ b/server/ssh/session_test.go
391@@ -1,4 +1,4 @@
392-package server
393+package ssh
394 
395 import (
396 	"context"
397@@ -11,6 +11,7 @@ import (
398 
399 	"github.com/charmbracelet/soft-serve/server/backend/sqlite"
400 	"github.com/charmbracelet/soft-serve/server/config"
401+	"github.com/charmbracelet/soft-serve/server/test"
402 	"github.com/charmbracelet/ssh"
403 	bm "github.com/charmbracelet/wish/bubbletea"
404 	"github.com/charmbracelet/wish/testsession"
405@@ -49,7 +50,7 @@ func setup(tb testing.TB) (*gossh.Session, func() error) {
406 	dp := tb.TempDir()
407 	is.NoErr(os.Setenv("SOFT_SERVE_DATA_PATH", dp))
408 	is.NoErr(os.Setenv("SOFT_SERVE_GIT_LISTEN_ADDR", ":9418"))
409-	is.NoErr(os.Setenv("SOFT_SERVE_SSH_LISTEN_ADDR", fmt.Sprintf(":%d", randomPort())))
410+	is.NoErr(os.Setenv("SOFT_SERVE_SSH_LISTEN_ADDR", fmt.Sprintf(":%d", test.RandomPort())))
411 	tb.Cleanup(func() {
412 		is.NoErr(os.Unsetenv("SOFT_SERVE_DATA_PATH"))
413 		is.NoErr(os.Unsetenv("SOFT_SERVE_GIT_LISTEN_ADDR"))
414diff --git a/server/ssh.go b/server/ssh/ssh.go
415rename from server/ssh.go
416rename to server/ssh/ssh.go
417index c99e41bf11b923a218d0ad43daf2a21ebc0e40a1..e3156e6e4974746925760be34892e255db37e715 100644
418--- a/server/ssh.go
419+++ b/server/ssh/ssh.go
420@@ -1,4 +1,4 @@
421-package server
422+package ssh
423 
424 import (
425 	"context"
426@@ -13,6 +13,7 @@ import (
427 	"github.com/charmbracelet/soft-serve/server/backend"
428 	cm "github.com/charmbracelet/soft-serve/server/cmd"
429 	"github.com/charmbracelet/soft-serve/server/config"
430+	"github.com/charmbracelet/soft-serve/server/git"
431 	"github.com/charmbracelet/soft-serve/server/hooks"
432 	"github.com/charmbracelet/soft-serve/server/utils"
433 	"github.com/charmbracelet/ssh"
434@@ -26,6 +27,10 @@ import (
435 	gossh "golang.org/x/crypto/ssh"
436 )
437 
438+var (
439+	logger = log.WithPrefix("server.ssh")
440+)
441+
442 var (
443 	publicKeyCounter = promauto.NewCounterVec(prometheus.CounterOpts{
444 		Namespace: "soft_serve",
445@@ -187,7 +192,7 @@ func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware {
446 					// https://git-scm.com/docs/gitrepository-layout
447 					repo := name + ".git"
448 					reposDir := filepath.Join(cfg.DataPath, "repos")
449-					if err := ensureWithin(reposDir, repo); err != nil {
450+					if err := git.EnsureWithin(reposDir, repo); err != nil {
451 						sshFatal(s, err)
452 						return
453 					}
454@@ -195,9 +200,9 @@ func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware {
455 					logger.Debug("git middleware", "cmd", gc, "access", access.String())
456 					repoDir := filepath.Join(reposDir, repo)
457 					switch gc {
458-					case receivePackBin:
459+					case git.ReceivePackBin:
460 						if access < backend.ReadWriteAccess {
461-							sshFatal(s, ErrNotAuthed)
462+							sshFatal(s, git.ErrNotAuthed)
463 							return
464 						}
465 						if _, err := cfg.Backend.Repository(name); err != nil {
466@@ -208,29 +213,29 @@ func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware {
467 							}
468 							createRepoCounter.WithLabelValues(ak, s.User(), name).Inc()
469 						}
470-						if err := receivePack(s, s, s.Stderr(), repoDir); err != nil {
471-							sshFatal(s, ErrSystemMalfunction)
472+						if err := git.ReceivePack(s, s, s.Stderr(), repoDir); err != nil {
473+							sshFatal(s, git.ErrSystemMalfunction)
474 						}
475 						receivePackCounter.WithLabelValues(ak, s.User(), name).Inc()
476 						return
477-					case uploadPackBin, uploadArchiveBin:
478+					case git.UploadPackBin, git.UploadArchiveBin:
479 						if access < backend.ReadOnlyAccess {
480-							sshFatal(s, ErrNotAuthed)
481+							sshFatal(s, git.ErrNotAuthed)
482 							return
483 						}
484 
485-						gitPack := uploadPack
486+						gitPack := git.UploadPack
487 						counter := uploadPackCounter
488-						if gc == uploadArchiveBin {
489-							gitPack = uploadArchive
490+						if gc == git.UploadArchiveBin {
491+							gitPack = git.UploadArchive
492 							counter = uploadArchiveCounter
493 						}
494 
495 						err := gitPack(s, s, s.Stderr(), repoDir)
496-						if errors.Is(err, ErrInvalidRepo) {
497-							sshFatal(s, ErrInvalidRepo)
498+						if errors.Is(err, git.ErrInvalidRepo) {
499+							sshFatal(s, git.ErrInvalidRepo)
500 						} else if err != nil {
501-							sshFatal(s, ErrSystemMalfunction)
502+							sshFatal(s, git.ErrSystemMalfunction)
503 						}
504 
505 						counter.WithLabelValues(ak, s.User(), name).Inc()
506@@ -244,6 +249,6 @@ func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware {
507 
508 // sshFatal prints to the session's STDOUT as a git response and exit 1.
509 func sshFatal(s ssh.Session, v ...interface{}) {
510-	writePktline(s, v...)
511+	git.WritePktline(s, v...)
512 	s.Exit(1) // nolint: errcheck
513 }
514diff --git a/server/stats.go b/server/stats/stats.go
515rename from server/stats.go
516rename to server/stats/stats.go
517index 30b8cd0bef9ba125c293b7bd7a6d5b72e882bee0..d0029d65aed0b1d0d68aa7e4d625a10838b4d512 100644
518--- a/server/stats.go
519+++ b/server/stats/stats.go
520@@ -1,4 +1,4 @@
521-package server
522+package stats
523 
524 import (
525 	"context"
526diff --git a/server/test/test.go b/server/test/test.go
527new file mode 100644
528index 0000000000000000000000000000000000000000..2dfe85c97dea227186565a04ef3ad86b31e120f8
529--- /dev/null
530+++ b/server/test/test.go
531@@ -0,0 +1,9 @@
532+package test
533+
534+import "net"
535+
536+func RandomPort() int {
537+	addr, _ := net.Listen("tcp", ":0") //nolint:gosec
538+	_ = addr.Close()
539+	return addr.Addr().(*net.TCPAddr).Port
540+}
541diff --git a/server/http.go b/server/web/http.go
542rename from server/http.go
543rename to server/web/http.go
544index 60a8650c76adc523fd0963eb74f675f308ee8c24..694f58807f0750ea75ff84ed2637c0f977ef00ae 100644
545--- a/server/http.go
546+++ b/server/web/http.go
547@@ -1,4 +1,4 @@
548-package server
549+package web
550 
551 import (
552 	"context"
553@@ -12,6 +12,7 @@ import (
554 	"text/template"
555 	"time"
556 
557+	"github.com/charmbracelet/log"
558 	"github.com/charmbracelet/soft-serve/server/backend"
559 	"github.com/charmbracelet/soft-serve/server/config"
560 	"github.com/charmbracelet/soft-serve/server/utils"
561@@ -23,6 +24,10 @@ import (
562 	"goji.io/pattern"
563 )
564 
565+var (
566+	logger = log.WithPrefix("server.web")
567+)
568+
569 var (
570 	gitHttpCounter = promauto.NewCounterVec(prometheus.CounterOpts{
571 		Namespace: "soft_serve",