all repos

mugit @ 3d9ab8a0e8b7a5eb03fb193389aa71eeb4b21327

🐮 git server that your cow will love
2 files changed, 39 insertions(+), 15 deletions(-)
improve error messaging in ssh
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-01-21 01:49:14 +0200
Change ID: nnnnrqovvonmukovzpopkpqyvmzwkvtn
Parent: a00560c
M internal/git/gitservice/gitservice.go

@@ -29,10 +29,10 @@ if err := cmd.Start(); err != nil {

return fmt.Errorf("start git-upload-pack: %w", err) } - if err := packLine(out, "# service=git-upload-pack\n"); err != nil { + if err := PackLine(out, "# service=git-upload-pack\n"); err != nil { return fmt.Errorf("write pack line: %w", err) } - if err := packFlush(out); err != nil { + if err := PackFlush(out); err != nil { return fmt.Errorf("flush pack: %w", err) }

@@ -64,11 +64,12 @@ Stdout: out,

}) } -func ReceivePack(dir string, in io.Reader, out io.Writer) error { +func ReceivePack(dir string, in io.Reader, out, stderr io.Writer) error { return gitCmd("receive-pack", config{ Dir: dir, Stdin: in, Stdout: out, + Stderr: stderr, }) }

@@ -79,6 +80,7 @@ AllowFilter bool

ExtraArgs []string Stdin io.Reader Stdout io.Writer + Stderr io.Writer } func gitCmd(service string, c config) error {

@@ -115,7 +117,11 @@ stdout, err := cmd.StdoutPipe()

if err != nil { return err } - cmd.Stderr = cmd.Stdout + if c.Stderr != nil { + cmd.Stderr = c.Stderr + } else { + cmd.Stderr = cmd.Stdout + } if err := cmd.Start(); err != nil { return fmt.Errorf("start %s: %w", service, err)

@@ -149,12 +155,26 @@ }

return nil } -func packLine(w io.Writer, s string) error { +// PackLine writes a pkt-line formatted string. +func PackLine(w io.Writer, s string) error { _, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s) return err } -func packFlush(w io.Writer) error { +// PackFlush writes a flush packet. +func PackFlush(w io.Writer) error { _, err := fmt.Fprint(w, "0000") return err } + +// PackSideband writes a message to sideband channel (displays as "remote: <msg>" in git client). +// Channel 2 = progress/info, Channel 3 = error. +func PackSideband(w io.Writer, channel byte, msg string) error { + return PackLine(w, string(channel)+msg) +} + +// PackError writes an ERR packet for protocol-level errors. +// Git displays this as: fatal: remote error: <msg> +func PackError(w io.Writer, msg string) error { + return PackLine(w, "ERR "+msg) +}
M internal/ssh/server.go

@@ -81,11 +81,10 @@

repoPath = filepath.Join(s.c.Repo.Dir, filepath.Clean(repoPath)) _, err := git.Open(repoPath, "") if err != nil { - s.error(sess, err) + slog.Error("ssh: failed to open repo", "err", err) + s.repoNotFound(sess) return } - - fmt.Println(repoPath) switch gitCmd { case "git-upload-pack":

@@ -96,11 +95,11 @@ }

sess.Exit(0) case "git-receive-pack": if !authorized { - s.repoNotFound(sess) + s.unauthorized(sess) return } - if err := gitservice.ReceivePack(repoPath, sess, sess); err != nil { + if err := gitservice.ReceivePack(repoPath, sess, sess, sess.Stderr()); err != nil { s.error(sess, err) return }

@@ -108,7 +107,7 @@ sess.Exit(0)

default: slog.Error("ssh unsupported command", "cmd", cmd) - fmt.Fprintln(sess, "Unsupported command") + gitservice.PackError(sess, "Unsupported command.") sess.Exit(1) } }

@@ -127,12 +126,17 @@ return nil

} func (s *Server) repoNotFound(sess ssh.Session) { - fmt.Fprintln(sess, "Sorry but repo you're looking for is not found.") + gitservice.PackError(sess, "Repository not found.") + sess.Exit(1) +} + +func (s *Server) unauthorized(sess ssh.Session) { + gitservice.PackError(sess, "You are not authorized to push to this repository.") sess.Exit(1) } func (s *Server) error(sess ssh.Session, err error) { - fmt.Fprintln(sess, "unexpected server error") - sess.Exit(1) slog.Error("error on ssh side", "err", err) + gitservice.PackError(sess, "Unexpected server error.") + sess.Exit(1) }