repos / git-pr

a self-hosted git collaboration server
git clone https://github.com/picosh/git-pr.git

commit
9e07ba1
parent
271b447
author
Eric Bower
date
2024-04-28 11:14:56 -0400 EDT
upload handler
4 files changed,  +147, -46
M go.mod
M go.sum
R ssh.go
M go.mod
+6, -1
 1@@ -6,11 +6,13 @@ require (
 2 	github.com/charmbracelet/soft-serve v0.7.4
 3 	github.com/charmbracelet/ssh v0.0.0-20240301204039-e79ff702f5b3
 4 	github.com/charmbracelet/wish v1.3.2
 5-	github.com/picosh/ptun v0.0.0-20240313192814-d0ca401937fe
 6+	github.com/picosh/send v0.0.0-20240217194807-77b972121e63
 7 )
 8 
 9 require (
10+	github.com/DavidGamba/go-getoptions v0.29.0 // indirect
11 	github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
12+	github.com/antoniomika/go-rsync-receiver v0.0.0-20231110145728-c94949e1ab7d // indirect
13 	github.com/aymanbagabas/git-module v1.8.4-0.20231101154130-8d27204ac6d2 // indirect
14 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
15 	github.com/caarlos0/env/v10 v10.0.0 // indirect
16@@ -31,6 +33,7 @@ require (
17 	github.com/go-logfmt/logfmt v0.6.0 // indirect
18 	github.com/gobwas/glob v0.2.3 // indirect
19 	github.com/golang-jwt/jwt/v5 v5.1.0 // indirect
20+	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
21 	github.com/google/uuid v1.4.0 // indirect
22 	github.com/jmoiron/sqlx v1.3.5 // indirect
23 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
24@@ -40,10 +43,12 @@ require (
25 	github.com/mattn/go-localereader v0.0.1 // indirect
26 	github.com/mattn/go-runewidth v0.0.15 // indirect
27 	github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 // indirect
28+	github.com/mmcloughlin/md4 v0.1.2 // indirect
29 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
30 	github.com/muesli/cancelreader v0.2.2 // indirect
31 	github.com/muesli/reflow v0.3.0 // indirect
32 	github.com/muesli/termenv v0.15.2 // indirect
33+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
34 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
35 	github.com/rivo/uniseg v0.4.7 // indirect
36 	github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 // indirect
M go.sum
+10, -2
 1@@ -1,5 +1,9 @@
 2+github.com/DavidGamba/go-getoptions v0.29.0 h1:cU8MjOyfAyPZke4hrgEuiGBJHS9PFYPAHve2fhDhdDk=
 3+github.com/DavidGamba/go-getoptions v0.29.0/go.mod h1:zE97E3PR9P3BI/HKyNYgdMlYxodcuiC6W68KIgeYT84=
 4 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 5 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 6+github.com/antoniomika/go-rsync-receiver v0.0.0-20231110145728-c94949e1ab7d h1:NyzUTxebDLLdtNu1gY5hn/amdAEnKG9DOawz82LwNTY=
 7+github.com/antoniomika/go-rsync-receiver v0.0.0-20231110145728-c94949e1ab7d/go.mod h1:zmqePVIo1hp+WEKxERLLGHJBDOr8/z/T4eFqXgWIw1w=
 8 github.com/aymanbagabas/git-module v1.8.4-0.20231101154130-8d27204ac6d2 h1:3w5KT+shE3hzWhORGiu2liVjEoaCEXm9uZP47+Gw4So=
 9 github.com/aymanbagabas/git-module v1.8.4-0.20231101154130-8d27204ac6d2/go.mod h1:d4gQ7/3/S2sPq4NnKdtAgUOVr6XtLpWFtxyVV5/+76U=
10 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
11@@ -56,6 +60,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
12 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
13 github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
14 github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
15+github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
16+github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
17 github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
18 github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
19 github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
20@@ -88,6 +94,8 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp
21 github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
22 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk=
23 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
24+github.com/mmcloughlin/md4 v0.1.2 h1:kGYl+iNbxhyz4u76ka9a+0TXP9KWt/LmnM0QhZwhcBo=
25+github.com/mmcloughlin/md4 v0.1.2/go.mod h1:AAxFX59fddW0IguqNzWlf1lazh1+rXeIt/Bj49cqDTQ=
26 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
27 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
28 github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
29@@ -96,8 +104,8 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
30 github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
31 github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
32 github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
33-github.com/picosh/ptun v0.0.0-20240313192814-d0ca401937fe h1:FmDSB2z/4iQ1m1WEVWQaDEwXiXlehd9Csqb8evWCMpY=
34-github.com/picosh/ptun v0.0.0-20240313192814-d0ca401937fe/go.mod h1:uQfDebjN3JJPsI3PAx8T5rmJwdpfmjvdRe7fXY33Kbw=
35+github.com/picosh/send v0.0.0-20240217194807-77b972121e63 h1:VSSbAejFzj2KBThfVnMcNXQwzHmwjPUridgi29LxihU=
36+github.com/picosh/send v0.0.0-20240217194807-77b972121e63/go.mod h1:1JCq0NVOdTDenQ0/Kd8e4rP80lu06UHJJ+6dQxhcpew=
37 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
38 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
39 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
A patch_handler.go
+82, -0
 1@@ -0,0 +1,82 @@
 2+package git
 3+
 4+import (
 5+	"fmt"
 6+	"io"
 7+	"log/slog"
 8+	"os"
 9+	"path/filepath"
10+
11+	"github.com/charmbracelet/ssh"
12+	"github.com/picosh/send/send/utils"
13+)
14+
15+type UploadHandler struct {
16+	Cfg    *GitCfg
17+	Logger *slog.Logger
18+}
19+
20+func NewUploadHandler(cfg *GitCfg, logger *slog.Logger) *UploadHandler {
21+	return &UploadHandler{
22+		Cfg:    cfg,
23+		Logger: logger,
24+	}
25+}
26+
27+func (h *UploadHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileInfo, utils.ReaderAtCloser, error) {
28+	cleanFilename := filepath.Base(entry.Filepath)
29+
30+	if cleanFilename == "" || cleanFilename == "." {
31+		return nil, nil, os.ErrNotExist
32+	}
33+
34+	return nil, nil, os.ErrNotExist
35+}
36+
37+func (h *UploadHandler) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
38+	var fileList []os.FileInfo
39+	cleanFilename := filepath.Base(fpath)
40+
41+	if cleanFilename == "" || cleanFilename == "." || cleanFilename == "/" {
42+		name := cleanFilename
43+		if name == "" {
44+			name = "/"
45+		}
46+
47+		fileList = append(fileList, &utils.VirtualFile{
48+			FName:  name,
49+			FIsDir: true,
50+		})
51+	} else {
52+	}
53+
54+	return fileList, nil
55+}
56+
57+func (h *UploadHandler) GetLogger() *slog.Logger {
58+	return h.Logger
59+}
60+
61+func (h *UploadHandler) Validate(s ssh.Session) error {
62+	return nil
63+}
64+
65+func (h *UploadHandler) Write(s ssh.Session, entry *utils.FileEntry) (string, error) {
66+	logger := h.GetLogger()
67+	user := s.User()
68+
69+	filename := filepath.Base(entry.Filepath)
70+	logger = logger.With(
71+		"user", user,
72+		"filename", filename,
73+	)
74+
75+	var text []byte
76+	if b, err := io.ReadAll(entry.Reader); err == nil {
77+		text = b
78+	}
79+
80+	fmt.Println(text)
81+
82+	return "", nil
83+}
R main.go => ssh.go
+49, -43
  1@@ -7,7 +7,6 @@ import (
  2 	"os"
  3 	"os/signal"
  4 	"path/filepath"
  5-	"slices"
  6 	"syscall"
  7 	"time"
  8 
  9@@ -15,65 +14,66 @@ import (
 10 	"github.com/charmbracelet/soft-serve/pkg/utils"
 11 	"github.com/charmbracelet/ssh"
 12 	"github.com/charmbracelet/wish"
 13+	"github.com/picosh/send/list"
 14+	wishrsync "github.com/picosh/send/send/rsync"
 15+	"github.com/picosh/send/send/scp"
 16 )
 17 
 18 func authHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 19 	return true
 20 }
 21 
 22-var cmdAllowlist = []string{
 23-	"git-receive-pack",
 24-	"git-upload-pack",
 25+func gitServiceCommands(sesh ssh.Session, cfg *GitCfg, cmd, repo string) error {
 26+	name := utils.SanitizeRepo(repo)
 27+	// git bare repositories should end in ".git"
 28+	// https://git-scm.com/docs/gitrepository-layout
 29+	repoDir := name + ".git"
 30+	reposDir := filepath.Join(cfg.DataPath, "repos")
 31+	err := git.EnsureWithin(reposDir, repoDir)
 32+	if err != nil {
 33+		return err
 34+	}
 35+	repoPath := filepath.Join(reposDir, repoDir)
 36+	serviceCmd := git.ServiceCommand{
 37+		Stdin:  sesh,
 38+		Stdout: sesh,
 39+		Stderr: sesh.Stderr(),
 40+		Dir:    repoPath,
 41+		Env:    sesh.Environ(),
 42+	}
 43+
 44+	if cmd == "git-receive-pack" {
 45+		err := git.ReceivePack(sesh.Context(), serviceCmd)
 46+		if err != nil {
 47+			return err
 48+		}
 49+	} else if cmd == "git-upload-pack" {
 50+		err := git.UploadPack(sesh.Context(), serviceCmd)
 51+		if err != nil {
 52+			return err
 53+		}
 54+	}
 55+
 56+	return nil
 57 }
 58 
 59 func GitServerMiddleware(cfg *GitCfg) wish.Middleware {
 60 	return func(next ssh.Handler) ssh.Handler {
 61 		return func(sesh ssh.Session) {
 62-			_, _, activePty := sesh.Pty()
 63-			if activePty {
 64-				next(sesh)
 65-				return
 66-			}
 67-
 68 			args := sesh.Command()
 69 			cmd := args[0]
 70 
 71-			if !slices.Contains(cmdAllowlist, cmd) {
 72-				wish.Fatalf(sesh, "%s not a valid command", cmd)
 73-				return
 74-			}
 75-
 76-			name := utils.SanitizeRepo(args[1])
 77-			// git bare repositories should end in ".git"
 78-			// https://git-scm.com/docs/gitrepository-layout
 79-			repoDir := name + ".git"
 80-			reposDir := filepath.Join(cfg.DataPath, "repos")
 81-			err := git.EnsureWithin(reposDir, repoDir)
 82-			if err != nil {
 83-				wish.Fatal(sesh, err.Error())
 84-			}
 85-			repoPath := filepath.Join(reposDir, repoDir)
 86-			serviceCmd := git.ServiceCommand{
 87-				Stdin:  sesh,
 88-				Stdout: sesh,
 89-				Stderr: sesh.Stderr(),
 90-				Dir:    repoPath,
 91-				Env:    sesh.Environ(),
 92-			}
 93-
 94-			if cmd == "git-receive-pack" {
 95-				err := git.ReceivePack(sesh.Context(), serviceCmd)
 96-				if err != nil {
 97-					wish.Fatal(sesh, err.Error())
 98-					return
 99-				}
100-				return
101-			} else if cmd == "git-upload-pack" {
102-				err := git.UploadPack(sesh.Context(), serviceCmd)
103+			if cmd == "git-receive-pack" || cmd == "git-upload-pack" {
104+				repoName := args[1]
105+				err := gitServiceCommands(sesh, cfg, cmd, repoName)
106 				if err != nil {
107 					wish.Fatal(sesh, err.Error())
108 					return
109 				}
110+			} else if cmd == "help" {
111+				wish.Println(sesh, "commands: [help, git-receive-pack, git-upload-pack]")
112+			} else {
113+				next(sesh)
114 				return
115 			}
116 		}
117@@ -102,12 +102,18 @@ func GitSshServer() {
118 
119 	cfg := NewGitCfg()
120 	logger := slog.Default()
121+	handler := NewUploadHandler(cfg, logger)
122 
123 	s, err := wish.NewServer(
124 		wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
125 		wish.WithHostKeyPath(filepath.Join(cfg.DataPath, "term_info_ed25519")),
126 		wish.WithPublicKeyAuth(authHandler),
127-		wish.WithMiddleware(GitServerMiddleware(cfg)),
128+		wish.WithMiddleware(
129+			list.Middleware(handler),
130+			scp.Middleware(handler),
131+			wishrsync.Middleware(handler),
132+			GitServerMiddleware(cfg),
133+		),
134 	)
135 
136 	if err != nil {