vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Phase 5: LFS objects, over SSH for writes and the proxy for reads
64ac5071a173e4e9ce8bc8d7170f9b0411bee52a · Keenan Nemetz · 17d ago · parent 35eaeccf
Phase 5: LFS objects, over SSH for writes and the proxy for reads Large files leave the git object database and land in one content-addressed store per instance. Reads are anonymous like clone; writes ride the SSH key that already authenticates the push, so no token exists anywhere. - internal/lfs: sha256-addressed store that verifies content and size before an object becomes visible; parser for the pointer format a client writes - HTTP: POST .../info/lfs/objects/batch for downloads, GET .../objects/<oid> with the object id as ETag; upload over HTTP is refused and names SSH - SSH: git-lfs-transfer speaking the pure SSH protocol (pkt-line), so `git lfs push` needs nothing but the key at the handshake - pre-receive: a pointer whose object is missing, or whose size disagrees with the stored object, fails the push that introduced it; a binary over lfs_warn_bytes is accepted with a `git lfs track` suggestion. Checks run before the protected-branch gate, because an admin's approval bypass is about review, not about whether the commit can be checked out - runs: a finished log becomes a content-addressed object in the same write that records the status, served at /r/<repo>/runs/<id>/log - vole gc [--dry-run] [--grace]: marks pointer targets from every ref in every repository plus archived logs, sweeps the rest. Mark-and-sweep rather than reference counts: a counter that is one too low deletes an artifact, a walk over the refs cannot be wrong about what is reachable
internal/sshserver/sshserver.go +15
| @@ -27,6 +27,7 @@ import ( | |||
| 27 | 27 | "golang.org/x/crypto/ssh" | |
| 28 | 28 | ||
| 29 | 29 | "github.com/rodent-software/vole/internal/identity" | |
| 30 | "github.com/rodent-software/vole/internal/lfs" | ||
| 30 | 31 | "github.com/rodent-software/vole/internal/repo" | |
| 31 | 32 | "github.com/rodent-software/vole/internal/runs" | |
| 32 | 33 | ) | |
| @@ -47,6 +48,10 @@ type Config struct { | |||
| 47 | 48 | RunsDB string | |
| 48 | 49 | // Runs is the CI queue, served to runners over the vole-runner verb. | |
| 49 | 50 | Runs *runs.Store | |
| 51 | // LFS is the object store behind git-lfs-transfer. | ||
| 52 | LFS *lfs.Store | ||
| 53 | // LFSWarnBytes is handed to pre-receive as the threshold for suggesting LFS. | ||
| 54 | LFSWarnBytes int64 | ||
| 50 | 55 | // Lease is how long a runner may hold a job without a heartbeat. | |
| 51 | 56 | Lease time.Duration | |
| 52 | 57 | } | |
| @@ -200,6 +205,13 @@ func (s *Server) exec(ch ssh.Channel, req *ssh.Request, command, user, accountID | |||
| 200 | 205 | exit(ch, 0) | |
| 201 | 206 | return | |
| 202 | 207 | } | |
| 208 | // git-lfs speaks its own protocol over an SSH remote: the client runs | ||
| 209 | // `git-lfs-transfer <repo> upload|download` on the same connection family as | ||
| 210 | // the push. No token changes hands; the key is the identity. | ||
| 211 | if len(argv) == 3 && argv[0] == "git-lfs-transfer" { | ||
| 212 | s.serveLFS(ch, req, argv[1], argv[2], user, accountID) | ||
| 213 | return | ||
| 214 | } | ||
| 203 | 215 | if len(argv) != 2 { | |
| 204 | 216 | refuse("only git-upload-pack and git-receive-pack are available") | |
| 205 | 217 | return | |
| @@ -267,6 +279,9 @@ func (s *Server) exec(ch ssh.Channel, req *ssh.Request, command, user, accountID | |||
| 267 | 279 | "VOLE_KEY_ID="+fp, | |
| 268 | 280 | "VOLE_REPO="+name, | |
| 269 | 281 | "VOLE_MAX_BLOB="+strconv.FormatInt(s.cfg.MaxBlobBytes, 10), | |
| 282 | "VOLE_LFS_DIR="+lfsRoot(s.cfg.LFS), | ||
| 283 | "VOLE_LFS_MAX="+strconv.FormatInt(lfsMax(s.cfg.LFS), 10), | ||
| 284 | "VOLE_LFS_WARN="+strconv.FormatInt(s.cfg.LFSWarnBytes, 10), | ||
| 270 | 285 | "VOLE_AUDIT="+s.cfg.AuditPath, | |
| 271 | 286 | "VOLE_SYSTEM_REPO="+s.cfg.Identity.Dir(), | |
| 272 | 287 | "VOLE_SPOOL="+s.cfg.SpoolDir, | |