Siosm's blog

Some thoughts from a systemd, Rust and security aficionado

Setup cgit with nginx on Arch Linux

Last updated on 2012-11-11: Add systemd support

I recently had to setup cgit with nginx on Arch Linux for https://siosm.fr/git and it took me quite some time as I was not familiar with cgi, fcgi, fcgiwrap, spawn-fcgi and had no idea how they worked together.

Moreover, most of the howtos I found on the internet where using custom init scripts or perl scripts which didn’t feel really right and maintainable. On Arch Linux we won’t even have to compile anything from source as all the required packages are available in the repositories:

1
sudo pacman -S nginx cgit fgciwrap spawn-fcgi git

I chose to copy cgit.css and cgit.png to an other folder (/srv/cgit) to be able to easily edit them but you can keep the default location (found using pacman -Ql cgit).

So how do we setup cgit with nginx ? nginx seems to only support the FastCGI protocol so we’ll need something to make the link between cgit and nginx as cgit is a “cgi for git”. Here comes fcgiwrap, which will “serve CGI applications over FastCGI”. Reading the manpages, you may notice this line:

1
The recommended way to deploy fcgiwrap is to run it under a process manager that takes care of opening the socket.

and this one:

1
Most probably you will want to launch fcgiwrap by spawn-fcgi using a configuration like this: ...

So let’s have a look at spawn-fcgi. It’s a daemon that will take care of setup for our fcgi applications. As we’re now using systemd, here is a sample fcgiwrap.service systemd service unit file which can be copied in /etc/systemd/system:

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=Simple server for running CGI applications over FastCGI
After=syslog.target network.target

[Service]
Type=forking
Restart=on-abort
PIDFile=/var/run/fcgiwrap.pid
ExecStart=/usr/bin/spawn-fcgi -s /var/run/fcgiwrap.sock -P /var/run/fcgiwrap.pid -u http -g http -M 700 -- /usr/sbin/fcgiwrap
ExecStop=/usr/bin/kill -15 $MAINPID

[Install]
WantedBy=multi-user.target

This config uses a local unix socket, restricts user and group to http and will handle FastCGI requests from nginx.

I then configured nginx with this extract from /etc/nginx/conf/nginx.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
	listen       80; 
	server_name  git.example.fr;

	# Serve static files
		location ~* ^.+\.(css|png|ico)$ {
		root /srv/cgit;
		expires 30d;
	}   

	# Send everything else to the fcgiwrap socket
	location / { 
		rewrite ^/([^?/]+/[^?]*)?(?:\?(.*))?$ /cgit?url=$1&$2 last;

		include fastcgi_params;

		fastcgi_param DOCUMENT_ROOT     /srv/cgit/;
		fastcgi_param SCRIPT_FILENAME   /usr/lib/cgit/cgit.cgi;
		fastcgi_pass unix:/var/run/fcgiwrap.sock;

		gzip off;
	}
}

You will then have to tell systemd to reload unit files and to restart nginx and fcgiwrap:

1
2
sudo systemctl --system daemon-reload
sudo systemctl restart fcgiwrap nginx

I’m also using gitolite and the git-daemon to manage access to my repositories. The configuration for gitolite is clearly detailed on the gitolite wiki, but the git-daemon is not securly configured by default as it is running as root.

Thanks to systemd, we can easily use socket-based daemon activation here. Thus I copied the git-daemon@.service file (not git-daemon.service) to /etc/systemd/system and modified it to increase security:

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=Git Daemon Instance

[Service]
User=nobody
Group=git
# The '-' is to ignore non-zero exit statuses
ExecStart=-/usr/lib/git-core/git-daemon --inetd --base-path=/srv/git /srv/git
StandardInput=socket
StandardOutput=inherit
StandardError=journal

We can now enable the git-daemon socket with systemd and it will spawn git-dameon instances for us:

1
2
sudo systemctl --system daemon-reload
sudo systemctl enable git-daemon.socket

So what’s the lesson learned this time? RTFM. I clearly did not read the manuals carefully enough and wasted a lot of time. The only thing that wasn’t in the manuals was the rewrite rule for the nginx configuration.

Old config when using initscripts

/etc/conf.d/fcgiwrap:

1
2
3
4
5
6
7
8
9
10
11
SPAWNER='/usr/bin/spawn-fcgi'

FCGI_SOCKET='/var/run/fcgiwrap.sock'
FCGI_USER='http'
FCGI_GROUP='http'
FCGI_EXTRA_OPTIONS='-M 700'

FCGI_PROGRAM='/usr/sbin/fcgiwrap'
ALLOWED_ENV="PATH"

SPAWNER_ARGS="-u $FCGI_USER -g $FCGI_GROUP -s $FCGI_SOCKET $FCGI_EXTRA_OPTIONS -- /usr/sbin/fcgiwrap"

/etc/conf.d/git-daemon.conf:

1
2
3
4
5
6
7
8
9
10
# path to git repositories served
GIT_REPO="/srv/git/"

# user and group
GIT_USER="nobody"
GIT_GROUP="git"

# see `man git-daemon` for all available options
# $GIT_REPO will be present twice in most configs
GIT_DAEMON_ARGS="--detach --syslog --verbose --base-path=$GIT_REPO --user=$GIT_USER --group=$GIT_GROUP $GIT_REPO"

References

Comments