Help us shape the future of Go! Take the 2026 Go Developer Survey

protocol

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 10, 2026 License: AGPL-3.0 Imports: 21 Imported by: 0

Documentation

Overview

Package protocol carries the smart-HTTP and (later) smart-SSH bits of the git wire protocol that we generate ourselves. The bulk of the protocol — capability negotiation, want/have, multi-ack, side-band — stays inside canonical `git`'s `upload-pack` and `receive-pack`. We only emit the framing we need to wrap their advertise-refs output for HTTP transport.

Index

Constants

View Source
const (
	MsgRepoNotFound = "shithub: repository not found"
	MsgPermDenied   = "shithub: permission denied"
	MsgArchived     = "shithub: this repository is archived; pushes are disabled"
	MsgSuspended    = "shithub: your account is suspended"
)

Friendly stderr messages — these are what the user sees in their terminal when `git clone` fails. Keep them actionable.

View Source
const FlushPkt = "0000"

FlushPkt is the literal pkt-line sequence that says "no more packets." git uses it to terminate sub-streams.

View Source
const MaxPktLine = 65520

MaxPktLine is git's hard limit for a single packet: 65520 bytes of payload + 4 bytes of length prefix. Anything longer is malformed.

Variables

View Source
var (
	ErrSSHRepoNotFound = errors.New("ssh dispatch: repo not found")
	ErrSSHPermDenied   = errors.New("ssh dispatch: permission denied")
	ErrSSHArchived     = errors.New("ssh dispatch: archived")
	ErrSSHSuspended    = errors.New("ssh dispatch: suspended")
	ErrSSHInternal     = errors.New("ssh dispatch: internal error")
)

Sentinel errors callers can errors.Is to map to friendly messages.

View Source
var ErrInvalidSSHPath = errors.New("ssh: invalid repo path")

ErrInvalidSSHPath is returned when the parsed path fails owner/repo validation. This is the path-traversal defense.

View Source
var ErrSubprocessFailed = errors.New("git subprocess exited non-zero")

ErrSubprocessFailed is returned by Run when the git subprocess exits non-zero. The captured stderr is wrapped via %s so the operator sees it in logs without it being escaped through the chain.

View Source
var ErrUnknownSSHCommand = errors.New("ssh: unsupported command")

ErrUnknownSSHCommand is returned when SSH_ORIGINAL_COMMAND doesn't match the strict whitelist. Callers should surface a friendly "shithub does not allow shell access" message and exit non-zero.

Functions

func Cmd

func Cmd(ctx context.Context, svc Service, gitDir string, advertiseRefs bool, extraEnv []string) *exec.Cmd

Cmd builds an *exec.Cmd that drives the requested service against the bare repo at gitDir. AdvertiseRefs flips on the --advertise-refs flag used by the info/refs response. extraEnv is appended to os.Environ() — the caller uses it to thread SHITHUB_USER_ID/etc. through git's environment so post-receive hooks can read them.

Subprocess lifecycle:

  • Killed on ctx cancel (Go ≥ 1.20: cmd.Cancel default kills with SIGKILL; we tighten to a 250ms WaitDelay so a stuck subprocess doesn't pin a worker forever).
  • Stderr is drained by the helper Drain() so the OS pipe buffer doesn't fill and deadlock the caller's stdout copy. Captured stderr is exposed via Stderr() on the returned Process.

func DrainStderr

func DrainStderr(cmd *exec.Cmd) func() []byte

DrainStderr starts a goroutine that copies stderr into a bounded ring buffer; returns a function the caller invokes after Wait() to get the captured bytes. The cap is `stderrCap`; further writes are silently discarded.

Always call this BEFORE Start() and call the returned closure AFTER Wait() to avoid the deadlock where a verbose stderr fills the OS pipe.

func FriendlyMessageFor

func FriendlyMessageFor(err error, requestID string) string

FriendlyMessageFor returns the user-facing stderr line for a typed dispatch error. Unknown errors collapse to a generic "internal error (request_id=...)" message — never leak the underlying cause.

func ParseRemoteIP

func ParseRemoteIP(sshConnection string) string

ParseRemoteIP extracts the connecting client's IP from the SSH_CONNECTION env var ("<client> <cport> <server> <sport>"). Returns "" when malformed; callers may pass the empty string through to the hook env.

func PrepareDispatch

PrepareDispatch is the brains of the SSH-shell command. It parses SSH_ORIGINAL_COMMAND, resolves user + repo, runs the inline owner- only authorization, and builds the env + argv the caller needs to exec git. It does NOT call exec itself — the caller does, after closing the DB pool (syscall.Exec preserves all open FDs and we don't want a leaked Postgres connection).

func WriteFlush

func WriteFlush(w io.Writer) error

WriteFlush emits the flush packet (`0000`). Marks the end of a sub-stream like the service advertisement preamble.

func WritePkt

func WritePkt(w io.Writer, payload string) error

WritePkt writes one pkt-line packet — a 4-hex-digit length prefix (covering payload + 4) followed by the payload. Exposed so the smart- HTTP info/refs handler can prepend its `# service=...` advertisement.

func WriteServiceAdvertisement

func WriteServiceAdvertisement(w io.Writer, service string) error

WriteServiceAdvertisement writes the standard preamble that the smart- HTTP info/refs response uses to tell git which service is being advertised. The wire format is:

001e# service=git-upload-pack\n0000
└┬┘└──────────┬─────────────┘└─┬─┘
 │            │                └ flush
 │            └ payload (trailing newline included)
 └ length prefix (hex)

Then `git upload-pack --advertise-refs --stateless-rpc <repo>` writes its actual ref advertisement after this.

Types

type ParsedSSHCommand

type ParsedSSHCommand struct {
	Service Service // UploadPack or ReceivePack
	Owner   string  // already lowercase, validated via the same shape rules as RepoFS
	Repo    string  // already lowercase, validated via repo-name rules
}

ParsedSSHCommand is the validated shape of `SSH_ORIGINAL_COMMAND`. The dispatcher resolves Owner+Repo against the DB and feeds Service to the same exec helper used by the HTTP transport.

func ParseSSHCommand

func ParseSSHCommand(cmd string) (ParsedSSHCommand, error)

ParseSSHCommand strict-parses SSH_ORIGINAL_COMMAND into Service + Owner + Repo. Returns ErrUnknownSSHCommand for anything that isn't a `git-{upload,receive}-pack <path>` invocation, and ErrInvalidSSHPath when the path fails the storage whitelist.

Caller is expected to have already trimmed surrounding whitespace if any; we additionally enforce no leading/trailing whitespace ourselves.

type SSHDispatchDeps

type SSHDispatchDeps struct {
	Pool   *pgxpool.Pool
	RepoFS *storage.RepoFS
}

SSHDispatchDeps wires the dispatcher. The DB pool is sized small at the call site (max 2 conns) — every SSH connection runs through here and the latency floor matters.

type SSHDispatchInput

type SSHDispatchInput struct {
	OriginalCommand string
	UserID          int64
	RemoteIP        string // parsed from SSH_CONNECTION first field
}

SSHDispatchInput is everything that comes from the OS environment at dispatch time. The cobra command in cmd/shithubd/ssh.go reads SSH_ORIGINAL_COMMAND / SSH_CONNECTION / argv and passes them in.

type SSHDispatchResult

type SSHDispatchResult struct {
	Argv0     string
	Argv0Args []string
	Env       []string
}

SSHDispatchResult is what the caller needs to syscall.Exec the right git plumbing binary. Argv0 is the canonical binary path resolved from PATH; Argv0Args is just `[Argv0, GitDir]` (git-upload-pack and git-receive-pack each take exactly one positional argument). Env is the full environment for the new process — already includes PATH and the SHITHUB_* vars.

type Service

type Service string

Service identifies which git plumbing program we're driving.

const (
	UploadPack  Service = "git-upload-pack"
	ReceivePack Service = "git-receive-pack"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL