Gitolite

2025-Sep-02

I’ve successfully shifted everything from forgejo (gitea) to gitolite and pgit. My entire web presense is now static content, so the spiders can keep going nuts without killing my CPU as it endlessly renders pages for them to slurp down.

Here’s how I did it!

Gitolite

I was hesitant to use it, but now I’m a big fan of gitolite. It allowed me to keep multiple users, automate publishing to my static git site.

Gitolite stores its configuration in the gitolite-admin git repo, which is neato burrito. I set up some post-receive hooks to update pgit for each repo, and to rebuild the index of repos every time (it’s really fast).

Aaaand that’s it. That’s all I had to do.

If I want to make a repo public, or make it clone-able with http,

ssh git@oscar perms neale/repo + READERS gitweb  # Publish
ssh git@oscar perms neale/repo + READERS daemon  # Allow cloning

git-http-backend

I’m running git-http-backend so people can git clone a repo. Once I figured out how, it was pretty simple. I’ve attached the Dockerfile below for running it as a TCP-bound fastcgi server. Then I told my Caddy server to use it for git clone requests. Easy as pie.

What’s Improved

Creating a new repo is super easy now, I just push, and it makes it for me if the repo didn’t already exist.

The web site is nice and peppy, with 100% static content, and there are a finite number of URLs that return anything. Forgejo was fast for a dynamic site, but this is faster.

Setting up the CI/CD-type things to build the static site was so much easier than any CI/CD thing I’ve used before: I just write a shell script. I run the shell script on the server to test it, then check it in when it’s working. So much simpler than the CI/CD debugging dance on a forge.

What’s Missing

I’m missing one or two CI/CD things, so I’ll have to remember to run ./build.sh or make publish before I commit.

I’ve also lost the ability to do anything useful with my repos in the browser. I wasn’t using that anyhow, but a lot of people don’t understand git well enough to use it outside of a forge, so this solution won’t work for everyone.

I no longer have issue tracking, so I’ll have to go back to doing that in the repo itself, in something like TODO.md.

Forgejo did some other stuff that I never looked into, so I won’t miss any of that.

Configuration Files

gitolite-admin/conf/gitolite.conf

repo gitolite-admin
  RW+ = neale
  
repo woozle
  RW+ = neale
  RW = @family

repo CREATOR/..*
  C = @family
  RW+ = CREATOR
  RW = WRITERS
  R = READERS
  
repo neale/..*
  option hook.post-receive.50 = neale-pgit
  option hook.post-receive.60 = neale-git-index

gitolite-admin/local/hooks/repo-specific/neale-pgit

#! /bin/sh

set -e

cd $(dirname $0)/..
repo=$(basename $(pwd) .git)
desc=$(cat description || echo "No description provided")
out=/srv/sys/www/git.woozle.org/neale/$repo

# Only write output if gitweb is a reader
if ! grep -F -q "READERS gitweb" gl-perms; then
        rm -rf $out
        exit 0
fi

# If we're exporting it, include a clone URL
if [ -f git-daemon-export-ok ]; then
  clone_flag="-clone-url https://git.woozle.org/neale/$repo.git"
fi

# Build!
/home/git/bin/pgit \
  -out /srv/sys/www/git.woozle.org/neale/$repo \
  -root-relative /neale/$repo/ \
  -label "$repo" \
  -desc "$desc" \
  -max-commits 5 \
  $clone_flag \
  -repo .

gitolite-admin/local/hooks/repo-specific/neale-git-index

#! /bin/sh

set -e

INDEX=/srv/sys/www/git.woozle.org/neale/index.html.new

cd $(dirname $0)/..
repo=$(basename $(pwd) .git)

cat <<EOD >$INDEX
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Neale's Git Repositories</title>
  <link rel="stylesheet" href="https://woozle.org/assets/css/default.css">
</head>
<body>
  <h1>Neale's Git Repositories</h1>
  <dl>
EOD

cat /home/git/projects.list | while read project; do
        repo=${project%.git}
        case "$repo" in
                neale/*)
                        repo=${repo#neale/}
                        desc=$(cat /srv/repos/$project/description || echo "No description provided")
                        cat <<EOD >>$INDEX
    <dt><a href='$repo/'>$repo</a></dt>
    <dd>$desc</dd>

EOD
                        ;;
        esac
done

cat <<EOD >>$INDEX
  </dl>
</body>
</html>
EOD

mv $INDEX ${INDEX%.new}

git-fcgi-backend Dockerfile

FROM alpine

RUN apk add --no-cache git-daemon fcgiwrap
USER 101:101
CMD [ "fcgiwrap", "-s", "tcp:0.0.0.0:9000" ]

Caddy configuration

git.woozle.org {
  root * /srv/sys/www/git.woozle.org

  @daemon {
    query service=git-upload-pack
  }

  handle @daemon {
    reverse_proxy git:9000 {
      transport fastcgi {
        root /usr/libexec/git-core/
        env SCRIPT_NAME git-http-backend
        env SCRIPT_FILENAME /usr/libexec/git-core/git-http-backend
        env GIT_PROJECT_ROOT /repos
      }
    }
  }
  
  file_server
}