Automatically generated from the Gemini capsule gemini://l-3.space
Time-stamp: <2026-09-06 13h03 UTC>
SourceHut recently made a policy decision that i don't agree with, for reasons i don't agree with. I can't tell which i find more frustrating, but this post is not about that.
This post is about self-hosting my git repos so that i can be free. Here's the result
Perhaps in the future i'll make a version of this for gemini?
It turns out that there's not much more to this than gitolite3 + nginx + cgit. My present configuration has the conveniences i depend on:
The most complicated parts of this journey were figuring out how to set up this visibility system and patching cgit.
This was do-able in a very straightforward way. My gitolite3 config contains
...
repo CREATOR/..*
C = @all
RW+ = CREATOR
R = @all
config cgit.ignore = 1
...
coupled with enable `local/commands` in gitolite.rc means that i can `ssh my.host visibility user/repo some_option` with the following script checked into the admin repo.
#!/bin/sh
# usage: ssh git@host visibility <repo> [public|unlisted|private]
# no state argument: print current visibility
die() { echo "$@" >&2; exit 1; }
[ -z "$GL_USER" ] && die "GL_USER not set"
repo="$1"; state="$2"
gitolite creator "$repo" "$GL_USER" || die "you are not authorized"
rp="$GL_REPO_BASE/$repo.git"
[ -d "$rp" ] || die "no such repo"
if [ -z "$state" ]; then
if [ "$(git -C "$rp" config --get cgit.ignore)" = 1 ]; then echo private
elif [ "$(git -C "$rp" config --get cgit.hide)" = 1 ]; then echo unlisted
else echo public
fi
exit 0
fi
case "$state" in
public) git -C "$rp" config --unset-all cgit.ignore; git -C "$rp" config --unset-all cgit.hide ;;
unlisted) git -C "$rp" config --unset-all cgit.ignore; git -C "$rp" config cgit.hide 1 ;;
private) git -C "$rp" config --unset-all cgit.hide; git -C "$rp" config cgit.ignore 1 ;;
*) die "usage: visibility <repo> [public|unlisted|private]" ;;
esac
echo "$repo -> $state"
My repos are more or less a project showcase, i want people to be able to quickly determine whether they are interested enough in what's going on to browse the code or clone it to do so locally. Unfortunately cgit writes the links for repos to point to their summaries, and doubly unfortunately it does so by simply point at the top-level directory. This means that the "summary" breadcrumb will always point to `my.host/$repo` so no amount of nginx hackery will solve this.
Instead i opted to patch cgit to do two things:
In combination this means that i can now have my index point visitors to the `about` tab instead of the summary tab by default, assuming that i have provided a README file. If not, it will gracefully fall back to `summary`.
The patch is available here, i don't really know how to suggest this upstream so for now i have a local build
https://git.l-3.space/dotfiles/tree/0001-Add-default-tab-option-and-explicit-summary-page-URL.patch
Other than the fact that git is a dvcs, i have my home server configured with its own gitolite user on my vps. This together with the line `R = @all` in the gitolite3 config means that it can poll for all of the available repos and clone/fetch them periodically. Honestly this is probably more than i ever had before.
I used to have to maintain two copies of my gemlog, one which was on my public git repos and another which was on my vps with my post-receive hook. Well, now they can be the same!