FreeBSD CGit

Posted on Wed 06 August 2014 in english

I copied this setup for a FreeBSD based git server with nginx and cgit from various sites and thus I should call it my Frankenstein jail...

First we must create a new jail, copy the resolv.conf, followed by the installation of various packages, namely:

# pkg install nginx fcgiwrap git cgit gitolite

GIT

Let's create a new user and a dedicated directory for storing our repositories now:

# pw groupadd -n git -g 9418
# pw useradd -n git -u 9418 -g git -c "Git User Account" -d /git \
    -s /usr/local/libexec/git-core/git-shell -h - 
#
# mkdir -p /git/base
#
# chown -R git:git /git
# chmod -R 0755 /git

As we want to clone via SSH we must copy our id_rsa.pub (public SSH key) to our server into $HOME/.ssh/authorized_keys.

Make sure it has the right permissions:

# cd /git
# mkdir .ssh
# mv KEYFILE.pub /git/.ssh/authorized_keys
# chmod 600 /git/.ssh/authorized_keys
# chown -R git:git /git/.ssh/

Everything should be fine now, so let's create our first repository:

$ mkdir /git/base/test.git
$ cd /git/base/test.git && git init --bare --shared

The last step to a clonable repo is configuring the SSH daemon:

# echo 'sshd_enable="YES"' >> /etc/rc.conf
# service sshd start

On the client try:

$ git clone git@IP_OF_YOUR_SERVER:base/test.git 
Cloning into 'test'...
Warning: Permanently added 'IP_OF_YOUR_SERVER' (ECDSA) to the list of known hosts.
warning: You appear to have cloned an empty repository.
Checking connectivity... done.

Now you're able to pull and push from/to your own git server. Congratulations!

CGit and Nginx

Enable Nginx and its dependency fcgiwrap (for CGit) in rc.conf config file:

$ echo 'fcgiwrap_enable="YES"' >> /etc/rc.conf
$ echo 'fcgiwrap_user="www"' >> /etc/rc.conf
$ echo 'nginx_enable="YES"' >> /etc/rc.conf

CGit wants its config file in /usr/local/etc/cgitrc:

css=/cgit.css
logo=/cgit.png

robots=noindex, nofollow

virtual-root=/

root-title=Lechindianer's CGIT
root-desc=...and now for something completely different

clone-url=http://git.lan/$CGIT_REPO_URL git@git.lan:$CGIT_REPO_URL

enable-index-links=1
enable-log-filecount=1
enable-log-linecount=1
enable-commit-graph=1
enable-remote-branches=1

snapshots=tar.gz tar.bz
max-stats=quarter
root-readme=/usr/local/www/cgit/about.htm

repo.group=various
repo.url=test
repo.path=/git/base/test.git
repo.desc=This is my test git repository
repo.owner=Pascal Schmid
repo.clone-url=http://git.lan/test/

For all various kinds of knobs of cgit see. I wanted to have a copy of the 3-clause BSD license on my website, so I put it into the root-readme. The enable- lines should be self-explanatory. If you have an old webgit server and want to adopt values set "enable-git-config=1" so every value starting with gitweb. will get mapped to the corresponding cgit.* one.

And for our webserver /usr/local/etc/nginx/nginx.conf use something like this:

user  www;
worker_processes  4;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    error_log /var/log/nginx.error.log error;
    sendfile        on;
    keepalive_timeout  70;

    server {
        listen       443;
        server_name  git2;

        ssl on; 
        ssl_certificate      /path/to/your/SSL/SERVER.pem;
        ssl_certificate_key  /path/to/your/SSL/SERVER.key;  
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        #ssl_ciphers  EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:EECDH+RC4:RSA+RC4:!MD5;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSKULL:!MD5:!DSS;
        ssl_session_timeout 5m;
        ssl_prefer_server_ciphers on;

        index cgit.cgi;
        access_log  /var/log/git.access.log;
        root /usr/local/www/cgit;
        try_files       $uri @cgit;

        # Require auth for requests sent to cgit that originated in location /
        location @cgit {
            # $document_root is now set properly, and you don't need to override it
            index cgit.cgi;
            fastcgi_param   SCRIPT_FILENAME $document_root/cgit.cgi;
            fastcgi_param   GIT_HTTP_EXPORT_ALL "";
            fastcgi_param   PATH_INFO       $uri;
            fastcgi_param   QUERY_STRING    $args;
            fastcgi_param   HTTP_HOST       $server_name;
            fastcgi_pass    unix:/var/run/fcgiwrap/fcgiwrap.sock;
            include fastcgi_params;

            gzip off;
            #rewrite ^ https://$server_name$request_uri permanent;
            rewrite ^/([^/]+/.*)?$ /cgit.cgi?url=$1 break;
        }

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

Create SSL keys:

# cd /usr/local/etc/
# mkdir ssl
# cd ssl
# openssl req -new -x509 -days 365 -nodes -out /path/to/your/SSL/SERVER.pem -keyout /path/to/your/SSL/SERVER.key -newkey rsa:2048

Start the services:

# service fcgiwrap start && service nginx start

The worker_processes value should represent your available CPU cores as mentioned in the Linode Nginx SSL tutorial.

Regarding the SSL cipher suites - choose a source you put more trust in:

For the keepalive_timeout value see the official Nginx documentation.

Gitolite

The problem with managing several git repositories with the authorized_keys file is that every allowed commiter can write in every repo on your server. It's controlled by that one entry.
You don't want this presumably so we have to find another mechanism to handle your commiters permissions. This is where Gitolite comes in handy. It's the successor of the meanwhile discontinued Gitosis.

The workflow is the following: Create a repo with your public ssh key and in this repo you will manage the permissions of all your other repos.

Gitolite uses a modified version of git's $HOME/.ssh/authorized_keys - when you open it you can see:

command="/usr/local/libexec/gitolite/gitolite-shell $USER",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa [very long public key] $USER@$HOST

The git-shell gets wrapped in this specialized auth_keys, so the first step to set up your Gitosis config is to delete the old auth_keys file. Then copy your public key in a directory where you can reach it with your git user. You must change your git user's shell from git-shell to something like /bin/sh for gitolite to work. Don't worry - the user is only allowed to do git-ish things allowed by the command above.

# rm -rf /git/base /git/.ssh
# chpass -s /bin/sh git
# su - git
$ gitolite setup -pk KEYFILE.pub

Now you can clone your gitolite-admin repo and make changes to its REPO/conf/gitolite.conf. See for more info on creating repos and giving permissions.

IMPORTANT: Gitolite has given my directories the wrong permissions so cgit was unable to show its content. To fix this issue change the UMASK value in gitolite's '.gitolite.rc' from 0077 to 0022 as suggested in the Arch Linux Wiki (works only for new repositories).

I had to fire off the following 2 command too:

# find /git/repositories/ -type d -exec chmod og+rx {} \;
# find /git/repositories/ -type f -exec chmod og+r {} \;

Voilà

cgit overview

test repo