Herr Bischoff

How to Install cgit With Gitolite and Nginx on FreeBSD 13

Recently, I dove into creating a very simple Git server setup, gradually moving away from the big Git providers. First I used Gitea for some time but (unsurprisingly) realized that I like editing text files in the terminal a lot more than clicking around web sites. So I did embark on the mission of setting up a robust but simple Git server solution.

Along the way I encountered a couple of not particularly well-documented trip wires, so this document attempts to do that, in a small way.

First off, install everything that’s required:

pkg install cgit fcgiwrap git gitolite nginx py38-docutils py38-markdown py38-pygments python3 python38

Enable the services:

# /etc/rc.conf

[...]
sshd_enable="YES"
nginx_enable="YES"
fcgiwrap_enable="YES"
fcgiwrap_flags="-f"
fcgiwrap_user="www"
fcgiwrap_group="www"
fcgiwrap_socket_owner="www"
fcgiwrap_socket_group="www"

Start the SSH service:

service sshd start

Create a git user:

pw useradd git -d /var/git
mkdir /var/git
chown -R git:git /var/git

Gitolite needs an admin user which is identified by an SSH key. Copy the one you want to use to a file named like the username you wish to use. If your username to be is charlie, the filename would be charlie.pub.

Create the required files and folders:

su - git
mkdir .ssh
chmod 700 .ssh
echo "ssh-ed25519 AAAAC3N... charlie@example.com" > .ssh/charlie.pub

Run Gitolite setup:

/usr/local/bin/gitolite setup -pk .ssh/charlie.pub

It’s likely you’re seeing one or two “hint” lines, originating from Git:

hint: Using ‘master’ as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: git config –global init.defaultBranch
hint:
hint: Names commonly chosen instead of ‘master’ are ‘main’, ’trunk’ and
hint: ‘development’. The just-created branch can be renamed via this command:
hint:
hint: git branch -m

No matter where you stand on changing “master”, do not change this before running the setup command. For the time being, the gitolite-admin.git repository only works with a “master” branch. Using another default branch name will lead to your configuration changes not being picked up. This bit me hard.

To prevent future, annoying “hint” messages, just set the global default branch setting, even if you want to stick with “master” and drop down to the root shell:

git config --global init.defaultBranch master
exit

In order for cgit to be able to read the Git repositories, the umask setting of Gitolite needs to be adjusted. In .gitolite.rc, change the default of

UMASK => 0077,

to

UMASK => 0027,

Now, on your workstation, clone the configuration repository and enter the folder. This should just work. If you’re asked for a password, something went wrong.

git clone git@gitserver:gitolite-admin.git
cd gitolite-admin

Basic configuration is way simpler to start with than it may appear at first. It all happens in this special repository. Take a look at what’s present at this moment:

# conf/gitolite.conf

repo gitolite-admin
    RW+     =   charlie

repo testing
    RW+     =   @all

To add a new repository, add a new entry like so:

repo baz
    RW+     =   charlie

Commit the change and push. The new, empty repository baz is now created and ready for use. In the above example, charlie is the name of the key we added when setting up, @all is a shorthand for all used keys.

Speaking of keys, to add a new user, just add their public key in the keydir folder with the known pattern of username.pub.

Gitolite’s access rules are very powerful. Here is a more detailed example, from the project’s README:

repo foo
    RW+                     =   alice
    -   master              =   bob
    -   refs/tags/v[0-9]    =   bob
    RW                      =   bob
    RW  refs/tags/v[0-9]    =   carol
    R                       =   dave

Here’s what these example rules say:

  • alice can do anything to any branch or tag – create, push, delete, rewind/overwrite etc.
  • bob can create or fast-forward push any branch whose name does not start with “master” and create any tag whose name does not start with “v”+digit.
  • carol can create tags whose names start with “v”+digit.
  • dave can clone/fetch.

Due to using SSH, anonymous access is not possible.

Back on the server, replace the Nginx configuration file with something like the following, minimal example. You’re responsible for properly securing the web server. This is alright for running in a jail behind a reverse proxy.

# /usr/local/etc/nginx/nginx.conf

worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type application/octet-stream;

    sendfile on;
    keepalive_timeout 65;
    gzip on;

    server {
        listen 80;
        server_name  _;
        root /usr/local/www/cgit;
        try_files $uri @cgit;

        location @cgit {
            client_max_body_size 0;

            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME /usr/local/www/cgit/cgit.cgi;
            fastcgi_param PATH_INFO $uri;
            fastcgi_param QUERY_STRING $args;
            fastcgi_param HTTP_HOST $server_name;
            fastcgi_pass unix:/var/run/fcgiwrap/fcgiwrap.sock;
            fastcgi_read_timeout 300;
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root /usr/local/www/nginx-dist;
        }
    }
}

We need the “www” user to be able read the Git repositories:

pw usermod www -G www,git

Also, all existing repositories need to be made readable for the “www” user:

chmod -R g+rX /var/git/repositories

Start the CGI and Nginx services:

service fcgiwrap start
service nginx start

From your workstation, you should now be able to access the cgit installation by vising the IP or hostname. However, for now, you’re greeted with:

No repositories found

To change that, create the following configuration file:

# /usr/local/etc/cgitrc

## root for all cgit links
virtual-root=/

# if you do not want that webcrawler (like google) index your site
robots=noindex, nofollow

# Specify some default clone urls using macro expansion
clone-url=http://gitserver/$CGIT_REPO_URL

# Set the title and heading of the repository index page
root-title=My self-hosted Git repositories

# Set a subheading for the repository index page
root-desc=Source code of various projects.

# Include some more info about example.com on the index page
# root-readme=/var/www/htdocs/about.html

## style-sheet and custom logo
# css=/cgit.css
# logo=/logo.png
# favicon=/favicon.ico

# Enable caching of up to 10000 output entries
cache-size=10000
cache-root-ttl=15

# Show owner on index page
enable-index-owner=1

# Allow http transport git clone
enable-http-clone=1

# Show extra links for each repository on the index page
# enable-index-links=1

# Enable blame page and create links to it from tree page
enable-blame=1

# Enable ASCII art commit history graph on the log pages
enable-commit-graph=1

# Show number of affected files per commit on the log pages
enable-log-filecount=1

# Show number of added/removed lines per commit on the log pages
enable-log-linecount=1

# Sort branches by date
branch-sort=age

# Add a cgit favicon
# favicon=/favicon.ico

# Enable statistics per week, month and quarter
max-stats=quarter

# Allow download of tar.gz, tar.bz2 and zip-files
snapshots=tar.gz tar.bz2 zip

##
## List of common mimetypes
##

mimetype.gif=image/gif
mimetype.html=text/html
mimetype.jpg=image/jpeg
mimetype.jpeg=image/jpeg
mimetype.pdf=application/pdf
mimetype.png=image/png
mimetype.svg=image/svg+xml

# Highlight source code with python pygments-based highlighter
source-filter=/usr/local/lib/cgit/filters/syntax-highlighting.py

# Format markdown, restructuredtext, manpages, text files, and html files
# through the right converters
about-filter=/usr/local/lib/cgit/filters/about-formatting.sh

##
## Search for these files in the root of the default branch of repositories
## for coming up with the about page:
##
readme=:README.md
readme=:readme.md
readme=:README.mkd
readme=:readme.mkd
readme=:README.rst
readme=:readme.rst
readme=:README.html
readme=:readme.html
readme=:README.htm
readme=:readme.htm
readme=:README.txt
readme=:readme.txt
readme=:README
readme=:readme
readme=:INSTALL.md
readme=:install.md
readme=:INSTALL.mkd
readme=:install.mkd
readme=:INSTALL.rst
readme=:install.rst
readme=:INSTALL.html
readme=:install.html
readme=:INSTALL.htm
readme=:install.htm
readme=:INSTALL.txt
readme=:install.txt
readme=:INSTALL
readme=:install

##
## Repositories
###
#

repo.url=testing
repo.path=/var/git/repositories/testing.git
repo.desc=Test repository.

repo.url=baz
repo.path=/var/git/repositories/baz.git
repo.desc=First repository set up with Gitolite.

Create the default cache folder:

mkdir /var/cache/cgit/
chown www:www /var/cache/cgit

For good measure restart the web services and you’re done:

service fcgiwrap start
service nginx start

Further reading: