Skip to content

Git Server

Down to the minimum, a git "server" could just be a bare repo located somewhere in the filesystem:

$ git init --bare repo.git
$ ls repo.git/
$ git clone repo.git
$ ls repo/

Thus it's possible to remotely access it using NFS or SSH.

Since the repo is just a directory on the filesystem, it uses the filesystem permissions. It's not supported for multiple users to be able to write to the bare repository, and only the owner can do so. Also, there're no permission control or branch protection because the owner could easily override things in that directory.

Thus, advanced permission manage solutions exist, like Gitea, GitLab, GitHub, or if you prefer something simpler, gitolite.

Gitea, GitLab, and GitHub are complete Git solutions that not only does bare Git repo management, but also integrates permission control, web browsing, CI / CD, and lots more. They are also easier (somehow) to setup, and may be suitable and easy for more people (especially for those prefer pull requests over email patches).

Git -> HTTP Interface

It's awesome to have a HTTP interface that allows users to preview the repo without cloning it alltogether. Besides complicated all-in-one solutions like GitLab, simple programs exist, notably gitweb and cgit.

It is worth noting that the web interface has nothing todo with HTTP cloning. HTTP cloning is a complete different protocol using HTTP, and it is handled using a different program.

gitweb is the default web interface shipped with git. I used it in the past but quickly replaced it with cgit for just a little more fancy features.

cgit is developed by Jason A. Donenfeld (the person who developed WireGuard), and it is actively maintained. Its git repo is at git.zx2c4.com/cgit.

Both gitweb and cgit are FastCGI programs. Make sure you have a basic understanding of how FastCGI works before proceeding.

And here is my Nginx configuration for cgit:

root            /usr/share/webapps/cgit/;
location ~* ^.+\.(css)$ {
        root /usr/share/webapps/cgit/;
}
location /robots.txt {
        root /usr/share/webapps/cgit/;
}
location ~* ^.+\.(png|ico)$ {
        root /srv/http/cgit/;
}
location / {
        include  fastcgi_params;
        index      cgit.cgi;
        fastcgi_param   SCRIPT_FILENAME $document_root/cgit.cgi;
        fastcgi_param   PATH_INFO       $uri;
        fastcgi_param   QUERY_STRING    $args;
        fastcgi_param   HTTP_HOST       $server_name;
        fastcgi_pass    unix:/run/fcgiwrap.sock;
}

The configuration is pretty straightforward and self-explanatory. Configuring HTTP rewrite could be a little bit troublesome, though. In my configuration, I set virtual-root=/ in cgitrc(5) to solve path issues.

The cgit configuration file cgitrc(5) is at /etc/cgitrc, and it is also pretty straightforward. Some key points in my setup are:

# Solve path issues
virtual-root=/

# Enable source highlighting. Slow
source-filter=/usr/lib/cgit/filters/syntax-highlighting.py

# Enable README rendering.
about-filter=/usr/lib/cgit/filters/about-formatting.sh
readme=:README.md # Enable readme preview. Copied from cgitrc(5)
# Append more readme=: here.

# Automatically load repos from the path.
scan-path=/srv/git/

clone-prefix=https://git.yuuta.moe

cgit itself does not do authentication. My friend wrote a tool called cgit-simple-authentication which may sound interesting to you.

HTTP Cloning

HTTP cloning is handled by git-http-backend, a FastCGI application shipped with git. Running it doesn't require any configuration except a set of well-defined HTTP path regular expressions, so the web server works fine with both HTTP cloning and cgit in the same domain. Here's a Nginx configuration I got from the Internet:

location ~ ^.*\.git/objects/([0-9a-f]+/[0-9a-f]+|pack/pack-[0-9a-f]+.(pack|idx))$ {
        root    /srv/git/;
}
location ~ ^.*\.git/(HEAD|info/refs|objects/info/.*|git-(upload|receive)-pack)$ {
        root /srv/git/;
        fastcgi_pass  unix:/run/fcgiwrap.sock;
        fastcgi_param SCRIPT_FILENAME   /usr/lib/git-core/git-http-backend;
        fastcgi_param PATH_INFO  $uri;
        fastcgi_param GIT_PROJECT_ROOT  $document_root;
        fastcgi_param GIT_HTTP_EXPORT_ALL "";
        fastcgi_param REMOTE_USER $remote_user;
        include fastcgi_params;
}

In this configuration:

  • Repos must be suffixed .git. Without .git it will be directed to cgit.
  • The path of git repos are /srv/git, the same as scan-path in cgitrc(5).

Hooks

Script to run as the current user during certain actions (e.g., after pushing). They are located in bare repo hooks/*.

Post receive

The hook to be executed after each hook. I'm using a simple (and ugly) script to build my website. It is absolutely better to use some kinda solutions like Jenkins.

#!/bin/sh
set -e
GITDIR="$(pwd)"
BUILDDIR="/tmp/build_kb_$(date +%s)"
BRANCH=$(cat | sed -e "s/[a-z0-9]* [a-z0-9]* refs\/heads\/\(.*\)/\1/g")
if test "$BRANCH" != "master"; then
        exit 0
fi
echo "Deploying ..."
git clone --recurse-submodules -q $GITDIR $BUILDDIR
cd $BUILDDIR
set +e
unset GIT_DIR
make
set -e

cd $GITDIR
rsync -r --delete $BUILDDIR/site/ /srv/http/kb/
rm -rf $BUILDDIR

Environment variables

A number of environment variables will be added when executing the script, and this may affect git(1) operations inside the script. Take them with cautious. The following example may not refelect the actual value in your setup.

$ env | grep GIT
GIT_DIR=.
GIT_EXEC_PATH=/usr/lib/git-core
GIT_PUSH_OPTION_COUNT=0

Last update: November 7, 2023
Created: November 5, 2023