Recently I installed Gitolite on our server and wanted it to manage the repositories for Ikiwiki-based wikis. It turned out that this is not as trivial task as one might expect so here is how I did it.

First, let’s look at how ikiwiki uses the git repositories. Every wiki needs two git repositories at the server. One of them is a bare repository and pushing changes to this repository updates the wiki. The other repository has a work tree and is used to “compile” the wiki to HTML and by Ikiwiki’s CGI wrapper to edit the wiki via web interface. Whenever somebody pushes to the bare repository, git invokes post-update hook, whose goal is to update the second repository and to compile the wiki to HTML.

On our server, gitolite runs under git user and no other user (besides root) has direct access to repositories. All ikiwiki wrappers (git post-update hook and CGI binary), which are setuid binaries, run as ikiwiki user and cannot access the bare repositories.

So the first problem to solve is where should ikiwiki generate the git hook. This is an easy problem. In the ikiwiki’s configuration file (*.setup) I have the following line:

git_wrapper => '/home/ikiwiki/hooks/<wiki-name>',

The second problem is how to invoke this hook from the bare repository managed by gitolite. If you do not use custom hooks, the answer is straightforward – simply create symbolic link from git to the wrapper:

ln -s ../../../../ikiwiki/hooks/<wiki-name> /home/git/repositories/<wiki-name>.git/hooks/post-update

However, this simple approach does not work if you restricted umask in your .gitolite.rc:

$REPO_UMASK = 0027;       # gives you 'rwxr-x---'

In this case, as I wrote above, the repositories cannot be accessed (even for reading) by any user and therefore the git hook (ikiwiki-generated setuid binary) fails, because it cannot access its current directory, which happens to be the bare git repository. The solution is to introduce another wrapper script which will execute the ikiwiki wrapper in another directory:

#!/bin/sh

wrapper=$(git config --get hook.ikiwiki-wrapper)

# The cd below is why we must use this script. The user running the
# ikiwiki may not have permission to accessible the current directory
# (git repo). In this case, the execution of the ikiwiki wrapper will
# fail with "E: Failed to change to directory '...': Permission denied"
cd /

exec "$wrapper"

This script can be put to /home/git/.gitolite/hooks/common/post-update. Then you must run gl-setup to propagate this hook to all repositories. And finally, you need to configure hook.ikiwiki-wrapper for git repositories that contain ikiwiki content:

repo <wiki-name>
     R  = @all
     RW+ = admin
     RW = ikiwiki <other users>
     config hook.ikiwiki-wrapper = /home/ikiwiki/hook/<wiki-name>

Now, you must create public/private key pair for ikiwiki user. The public key must be commited into gitolite-admin repository as ikiwiki.pub. The private key (not passphrase protected) has to be stored in /home/ikiwiki/.ssh/id_rsa. The non-bare ikiwiki repository has to be configured to access the ikiwiki bare repository via gitolite by running this command:

git config remote.origin.url git@localhost:<wiki-name>

And we are done. Now, pushing to the gitolite managed repo updates the wiki on the web and simultaneously, editing the wiki through CGI wrapper updates the gitolite managed bare repo.


In fact I have the setup on the server a bit more complex. To allow having more post-update hooks, the post-update hook looks like the following:

#!/bin/sh

for hook in $(git config --get-all hook.post-update-chain) 
do
    if test -x "hooks/post-update.d/$hook"; then
    hooks/post-update.d/$hook "$@"
    else if test -x "$hook"; then
    $hook "$@"
    fi
    fi
done

and the wrapper script containing cd is located in /home/git/.gitolite/hooks/common/post-update.d/ikiwiki-wrapper. The configuration of the repo contains one additional line to run the ikiwiki wrapper:

config hook.post-update-chain = ikiwiki-wrapper