Ever considered setting up and running your very own git server? It’s actually
quite easy! In this post, I’ll outline the steps I took to set up my own so
that you can give it a try yourself. But first, why might you even want to go
through the trouble of setting up your own server?

After all, there are a wide array of excellent and free to use choices out
there, such as GitHub, GitLab, and the up-and-coming sourcehut.

One reason is ownership: in today’s world of corporate surveillance, rampant
privacy violations, and data breaches, there is something to be said of truly
owning your own data. Both git and the web itself were designed and built on
principles of decentralization and distribution. Standing up your own server is
one way to tap into that heritage.

It’s also just plain fun, at least if you’re into that sort of thing. You get
to build something useful and put your name on it. It’s something you
control. You get to decide how it works, how it looks, who can access it, and
what exists on it.

Setting up a git server is actually relatively straight-forward. Almost all of
the heavy lifting is done by git itself, but I will also introduce a few
supplementary tools to handle things like access control and HTTP access.

Evaluating the choices

Depending on your needs, you may want to install something like GitLab’s
Community Edition which is very easy to set up and provides things
like user accounts, issue tracking, and more.

However, if all you need is a simple single-user system where you (and maybe a
few other trusted collaborators) are the only one making changes to your source
code, GitLab may be a bit overkill. Other options include Gitea or
Gogs, both of which offer slimmed down versions of full-scale offerings
like GitLab and are designed to run on smaller hardware like a Raspberry Pi.

In this guide, we’ll be using Gitolite to provide and manage SSH access to
our git repositories. Gitolite does not offer any kind of HTTP access or a web
frontend: for this, we’ll turn to the old-fashioned (but still viable!) gitweb.
Finally, we’ll use the built-in git daemon to serve repositories over the
git:// protocol.

Preparing your server

You can run your git server on something as simple as a Raspberry Pi or a
Digital Ocean droplet. It’s not very demanding, so you don’t need much
horsepower.

First, create a git user on your server. This user’s home directory will act
as the base location for all of your repositories. I chose /var/lib/git as
the home directory, but /home/git or /srv/git are other common choices.

# useradd -r -d /var/lib/git git

Make sure to set the password for the new user:

# passwd git

And make sure you can SSH to the git user on your server:

$ ssh git@yourserver.com

Gitolite

Next, install the excellent Gitolite tool. Gitolite makes managing your
server much easier and allows you to do things like user management, access
control, triggers, hooks, and more. Copy your SSH public key into your git
user’s home directory and then clone Gitolite into the git user’s home
directory.

The gitolite-admin repo is where all of the Gitolite configuration takes
place. You can clone this to your home computer and push your changes back to
your server and Gitolite will automatically re-configure itself. This is also
how you add new SSH keys (either for yourself or for other users).

Gitolite also interfaces quite nicely with git-daemon and gitweb. Be sure
to check out the thorough documentation on Gitolite’s website.

git daemon

Git includes a daemon subcommand that will listen for incoming connections
and serve data over the git:// protocol. This allows you to clone repos from
your server using

$ git clone git://yourserver/repo.git

The git:// protocol is the fastest and simplest option for read-only access,
and it’s trivial to set up. Assuming your git user’s home directory is
/var/lib/git, simply create the following systemd service file (e.g. at
/etc/systemd/system/git-daemon.service):

Then simply run systemctl enable --now git-daemon. Make sure port 9418 is
open on your firewall.

If you don’t use systemd, you can probably find alternative init scripts for
your particular init system in your package manager. Debian, for example, has
git-daemon-sysvinit and git-daemon-run if you use SysV or runit,
respectively.

The git daemon only serves repositories that have a file called
git-daemon-export-ok in their directory root. This allows you to control
which repositories you want to be publicly accessible. Gitolite will
automatically create this file for you for any repos that are
readable by the special daemon user.

Gitweb and HTTP

The most difficult part of setting up a git server is the web frontend and HTTP
access. The git documentation has a section on Smart HTTP which is a good
starting point, but it still took me quite a while to get everything working.
Part of the reason I had some difficulty was because I insisted on running the
webserver in a Docker container (so that I can version control the
configuration).

By default, your git installation includes the git-http-backend script
which allows you to clone over HTTP. If you also want to host a web frontend,
then you have to create some rules for your webserver so that it knows when to
serve the web page and when to direct traffic to the git-http-backend script.

The above configuration allows repos to be cloned with or without the .git
extension, but since Gitolite creates all repositories with the .git
suffix you’ll have to create non-suffixed symlinks to those directories.

I run Apache behind a reverse proxy that handles all of the TLS
encryption, so the configuration above doesn’t deal with any of that. Setting
up TLS for a webserver is out of scope for this guide and if that’s
not something you care to do, you can always omit the HTTP/S portion of your
server and simply serve via git:// and SSH. Like I said, this part is the
most complex and will likely take more time than the other steps. The above
snippet should be a good starting point though.

Unfortunately, even after getting the web server configured, I had another
problem. The default Gitweb web page is a bit&mldr; utilitarian, to phrase it
mildly.

Gitweb uses a Perl CGI script to generate the HTML, which means unless you want
to manually modify that script your basically stuck with the HTML that Gitweb
gives you.

Fortunately, CSS is quite powerful these days and there is a lot you can do
with it. With only 6.25 KB of CSS I was able to transform the drab, default
Gitweb interface into what you see today at git.gpanders.com1.
You may think it still looks drab, but I’m fairly proud of how it turned out.

Gitolite tips and tricks

Here are some of the Gitolite tricks I’ve set up on my server that you may find
useful.

Mirroring to GitHub

I still mirror most of my repositories to GitHub, as that’s still the best
place for discoverability and compatibility with other programs.

This is easy to set up in Gitolite. In your gitolite.conf file, add the
following to configure the repos you want to mirror to GitHub:

In the above script, only repositories which are publicly acessible on Gitweb
have a symlink created, since I only really care about the .git extension on
the web interface (e.g. I want to use git.gpanders.com/dotfiles instead of
git.gpanders.com/dotfiles.git). If you want this to apply to all repos, simply
remove the gitolite access check.

You’ll also need a second trigger at
triggers/post-compile/update-gitweb-access-list. This trigger is supplied by
Gitolite by default, so creating a new trigger just overrides the default one.
The only change we need to make to the default trigger is to exclude the .git
suffix from the list of repositories that Gitolite makes available to Gitweb.

Generate a README

GitHub and other sites automatically convert your README file (if it exists)
from Markdown, reStructured Text, etc. into HTML when you visit the web page.
We can do this too with Gitolite!

If the file README.html exists in the root of your repository, Gitweb will
display it on the project’s summary page (example). We can create a hook to
automatically create this file from the plain text README file in the repo.
Create a hook at hooks/repo-specific/readme in your local code
directory with the following contents: