
Git Granary
Git Granary êŽë š
Captainâs log
Itâs been 10 days since I entered the Git LFS rabbit hole. Following an emergency in-prod GitHub replacement I got nerd sniped by the Git Large File Storage API (git-lfs/git-lfs).
Git Granary
đŸ Git Granary (dbushell/granary) is the end result. I coded my own LFS server!
I plan to self-host Granary locally alongside my Gitea instance. Why not just use Giteaâs LFS? Because I honestly forgot Gitea supported LFS âŠand that Iâd configured it. Itâs still sitting there empty. I may move to Forgejo[1] I need to catch up on the drama.
Anyway, Iâll have separate Git and LFS servers on my local network. Theyâre not exposed to the internet 24/7. I will need to temporarily tunnel the LFS server when I deploy my website from the public GitHub repo. I havenât figured that part out yet. Can I install Tailscale in an action runner?
Granary is designed for self-hosted personal use. Technically it can support multiple users but they must use the same basic HTTP auth (dbushell/granary). The only multi-user issue I see is if two users try to upload the exact same object simultaneously. In that case Iâm guessing the file would be locked by the JavaScript runtime and one request would fail.
Runtimes
Granary (dbushell/granary) can run under Bun, Deno, and Node. Iâve coded it with an adapter pattern to provide runtime specific filesystem API and HTTP server functionality. Bun and Node adapters are minimum viable implementations.
Deno is the primary runtime. serveFile from the standard library does the heavy lifting for download operations. For uploads Iâm using the extended Web Crypto that support streaming through digest. This allows me to tee the request.body and stream it to disk whilst calculating the SHA-256. I found Bunâs file API to be lacking so it uses the same node:fs code from the Node adapter. Bun and Node donât check the hash. Thatâs the main difference from Deno. They could but Iâm not all that interested maintaining multiple adapters. I just wanted to prove the concept.
Each runtime uses itâs own HTTP server; Bun.serve, Deno.serve, and Nodeâs createServer. I had to fudge the Node one because it doesnât use standard Fetch API objects. I borrowed from @hono/node-server (honojs/node-server) to polyfill Request. Iâm not actually using Hono for routing. Simple URL pattern matching is sufficient in the root handler.
For example:
async function handle(request: Request): Promise<Response> {
const pathname = '/:repo([a-zA-Z0-9._-]+)/objects/batch';
const batchMatch = new URLPattern({pathname}).exec(url);
if (match) {
const {repo} = match.pathname.groups;
/* Do something... */
}
}
Earlier this year I experimented with a request/response router based on URL patterns. Theyâre slower than string or regular expression matching. âSlowerâ in the synthetic benchmark sense. Real world you wouldnât be using JavaScript if you needed that many requests per second.
Open Source
Granary is MIT licensed (dbushell/granary) feel free to use it if youâre daring! I will be testing it myself. I donât foresee many changes or bug fixes. The Git LFS API (git-lfs/git-lfs) is remarkably simple.
If I donât report back in a few months youâll know Iâve binned it.
Sources on 'Forgejo'(1)
Forgejo is an open source git code forge (way cooler than GitHub). I self-host my own instance. â©ïž