Picture this: you’re a DevOps engineer in charge of an on-premises, five-rack datacenter hosting a heavy CI/CD load. The local Atlassian instance is being decommissioned and the Bitbucket instance has been migrated to the other side of the Atlantic.

Except that the building’s SDSL uplink is anemic, the local IT router is melting down, and some CI/CD jobs now take up to 15 minutes to complete a recursive shallow clone, if they don’t fail halfway through.

What do you do?

The pieces of the puzzle

Synology NAS

All I had on hand with ample spare storage were Synology NASes, but they are not powerhouses when it comes to CPU or RAM. The spare one on hand wasn’t Docker-capable either, so sane options were rather limited.

Synology does provide Git as an installable package, though.

One Git server protocol unlike the others

Git has several server protocols to choose from. All of them rely on these commands to transfer data:

  • git-upload-archive, used to send an archive with a remote git archive invocation;
  • git-upload-pack, used to send Git objects (i.e. clone or fetch);
  • git-receive-pack, used to push Git objects.

Remember: these are run server-side, so they are uploads and downloads from the server’s point of view.

The specifics of the pkt-line protocol are not relevant here; all that matters is that they rely on a server like git-daemon or git-http-backend to get things started. That is, except for SSH, which can work over a standard Unix shell session.

URL rewriting

Git comes with a powerful URL rewriting capability for remotes in the form of insteadOf. For example, this command does not simply redirect HTTPS traffic to an SSH server; it also preserves the original URL as a suffix and passes it to the server as a path.

$ git config --global url."ssh://git@your-host.example/https://".insteadOf "https://"

Unix shell accounts

While most Unix accounts are set up with some descendant of the Bourne shell, nothing prevents a system administrator from setting up a custom program… including a Bash script.

You can override the shell in a user account on a Synology NAS, but the firmware regenerates /etc/passwd on each boot, replacing it with the default shell. Thankfully, a scheduled task on boot can patch it back to a custom shell with a simple sed.

Putting it together

What if…

  • I put a Bash shell script on the Synology NAS;
  • That is set as the shell for a git account on it;
  • Which pretends to run the git-upload-pack command it receives when something logs in;
  • But unpacks the incoming URL and performs a git fetch (or git clone on an uncached repository) under the hood;
  • Before invoking the requested command from the local copy?

That is my Git proxy cache in a nutshell. Its only requirements are an SSH server, Git, and Bash, so it will run on just about anything.

Note that the git user that runs this script is also affected by its Git configuration, meaning you can also use insteadOf on the server to inject credentials or redirect URLs at will.

This is very useful for example when IT decides to migrate stuff, as you can make the old URLs point to the new ones transparently, without telling the clients of the Git proxy cache.

It is deceptively fast too:

  • Although access to a given remote repository is serialized behind a flock, a git fetch on an up-to-date (or slightly out-of-date) repository is practically instantaneous;
  • Access to different remote repositories is parallelized by virtue of not doing anything special;
  • Serving a client on the LAN with git-upload-pack is absurdly fast, especially with shallow clones.

Statute of limitations

It is still in production.

Worse, years later my successor once tried to get rid of it, but when the CI/CD load was aimed upstream, GitHub (the recipient of yet another IT migration) promptly rate-limited the building to a crawl, even with authenticated requests.

That’s right: it’s even more load-bearing than before now…

The original script was barely a hundred lines long and trusted the clients not to do anything stupid. I’ve cleaned it up for public diffusion so it’s a bit longer and safer now, but it still operates on the same principles. It could use some improvements, like skipping fetching if the client only asks for hashes that are already present locally.

Still, not bad for something thrown together in an afternoon to get 50 developers off my back in a hurry.