Minimalistic git hosting
February 26, 2025You may or may not have noticed, that about one month ago, the URLs on this site changed, and that I’ve updated the website source link.
Up until November last year, I mirrored the source code for my website
from my personal git forge to Codeberg, to make it easier for others to view.
However, because I store the photos from the photography section in the tree,
it would have exceeded the Codeberg size limit.
Given that it is my personal website, I don’t really expect anyone to contribute to it,
which is why I made the decision to only publish the source code on my personal git forge
(which is public, btw).
The old setup
Back then, I was still using a self-hosted Sourcehut instance.
In case you don’t know, Sourcehut is a software development platform,
that provides git hosting, CI, mailing lists, and more.
I initially set up Sourcehut, as I quite like the minimalistic user interface;
especially not having to wait ages for JavaScript to load.
Over time, I grew frustrated with the LXC setup,
as most of my other software is deployed using Docker,
so I basically had to maintain two software stacks.
Adding to that, the LXC network bridge would sometimes break Docker network access,
meaning I had to reboot my server.
Lastly, whilst I really quite like working with the Sourcehut CI, the VM runner was really slow,
probably because my server isn’t really high-spec.
This meant that I could run most CI tasks up to ten times faster on my laptop.
Almost two years ago, I started working on a collection of Sourcehut Docker containers,
with the goal of replacing the LXC setup with them.
To my disappointment, the setup wasn’t reliable and would sometimes throw errors that I didn’t manage to replicate.
Eventually, I fixed most of them, but I never ended up deploying it to production,
because I had stumbled across a better solution by then.
Requirements
Before I continue with the next segment, I think I should make my requirements clear, to avoid confusion.
I host most of my projects on Codeberg
and use my personal git forge as a way to back up repos or push development versions, when switching devices.
This also means that I don’t need a separate CI system, as Forgejo Actions
can already do most of the things I need.
I also don’t need virtual machine runners for most tasks.
In fact using Docker containers makes most jobs run much faster.
And if I ever do need a VM, I could either run the tasks directly on my laptop or use
docker-vm-runner.
I manage issues and pull requests on Codeberg.
All I really need is a web UI, for on-the-go navigation and a way to pull/push using git.
In fact, back when I was using Sourcehut, I mainly used three components: git, projects, and builds.
New Frontend
Let’s start with the frontend: cgit a git web interface written in C.
As you might be able to guess by looking at it, I chose it because I like the minimalistic web UI.
It also supports everything I need: displaying a simple project about page,
viewing commit logs, and navigating the file tree.1
My Ansbile playbook takes care of the installation
and setup process.
Cgit is configured to run on the cgit subdomain and serve files from /srv/git.
I’ve also added common README formats as options for the about section.
My personal favorite config option is section-from-path,
which allows me to categorize my projects by placing them in folders.
For example, I’ve placed my Docker-related git repos inside a docker folder,
which neatly groups them under the docker section
and in the main index.
New Backend
At first, I had a lot of trouble deciding on a backend.
I briefly considered just syncing files using rsync, but as I said,
I want to be able to use git pull/push properly.
I also looked at gitoxide and gitolite,
but they looked way too complicated for me, especially, because this is a single-person setup.
Well, as it turns out, there is a simpler option: git.
It is even explained in the git book.
All you have to do is create bare repositories on the server, and provide the full path to when cloning or adding a remote.
I already have proper SSH access to my server,
so all I needed to do was create bare versions of my repositories in the folder, that cgit uses to scan for repositories.
Because cgit doesn’t properly recognize my READMEs I had to manually configure the default branch,
so I wrote a small script to set up new repositories:
#!/bin/sh
name="$1"
desc="$2"
if [ -z "$name" ] || [ -z "$desc" ]; then
echo "Missing name and/or description"
echo "Usage: ./new-repo <name> <desc>"
fi
mkdir "$name"
cd "$name"
git init --bare
git branch -m main
echo "defbranch=main" > cgitrc
echo "$desc" > description
Instead of initializing a bare repository, you could use git clone --bare,
to clone an already existing repo.
I also had to create a post-receive hook, and copy it to every repo, to fix the cgit age calculation.
You can find the hook in the official cgit repo.
Now I can simply clone repos using git clone <username>@ccw.icu:/srv/git/<repo>
and view the repositories in the cgit web interface, which is all I need.
In fact, this entire blog is served from the cgit web interface using caddy,
which is probably not a good idea, as it can be a little slow.
But I don’t care because it is fast enough for me.
Sidenote: Most of the links to my personal projects in this post link to my cgit instance. But you can find most of them on my Codeberg account as well.