Git with a .htaccess and a CGI script
Tuesday 10 January 2012
Since version 1.6.6, Git is able to tunnel its native protocol through HTTP or HTTPS, which makes roughly as efficient over HTTP than it is over SSH. This is often called “smart HTTP”. While I like SSH, I sometimes have co-workers behind a firewall, who do not have SSH access. In these cases, HTTP is the solution.
Standard installation instructions for smart HTTP are available in man git-http-backend or in Pro Git, but they often assume that one has a shell account on the server, and/or is the administrator of the server. This document explains how to work with just a user access to the server, just uploading files (this document was written and tested with direct NFS access, but ftp/rsync/whatever should work too), including .htaccess, and running CGI scripts (this obviously assumes the server is configured to accept .htaccess and running CGI-scripts, and will be easier if Git is installed on the server). This has been tested with apache, and probably won’t work with any other webserver.
(Note that these instructions do not require shell access to the server, but security-wise, the ability to run arbitrary CGI scripts is essentially equivalent to a shell access)
First, create a directory in which you’re going to host the repository. In this directory, create a file
.htaccess with the content :
Options +ExecCGI AddHandler cgi-script cgi
This way, any file ending with
.cgi will be considered as a CGI script, and the webserver will execute it to serve it.
Now, we’ll get some information about the server. Write a small
config.cgi script containing this:
#! /bin/sh echo 'Content-type: text/plain' echo pwd git --version
and make sure the file is executable (chmod +x config.cgi if needed). Now, open the corresponding URL in your browser, you should get something like this:
/www/whatever/git-hosting git version 184.108.40.206
The first line is the absolute path to your hosting directory on the server, and the second ensures Git is installed on the server and shows you which version.
We’re going to make a private repository, hence need authentication. We’ll rely on the Apache authentication mechanism, at the HTTP level.
Now that we know the absolute path of our directory, we can set up authentication. Add this lines to your
AuthUserFile /www/whatever/git-hosting/.htpasswd AuthType Basic AuthName "Git Private" Require valid-user
and use htpasswd to create a
.htpasswd file with the users/hashes you whish.
config.cgi in your browser should ask you a password.
We’ll have our repositories physically hosted in a subdirectory
git-repos of the directory where we did the installation.
Creating an empty Git repository could be as simple as
git init, but you may run into permissions issues: the user uploading the files may not be the one running Apache (typically www-data). So, we’ll let a CGI script run this initialization to create the repository with www-data as owner. Create a script
init.cgi with this content:
echo 'Content-type: text/plain' echo # may require a temporary "chmod 777 ." as normal user. mkdir -p git-repos/ git init --bare git-repos/example-repo.git/ 2>&1 echo 'Description of my Example' > git-repos/example-repo.git/description
Load it in your web browser, it should create the repository.
When you’re done, it is advised to disable this script (for example by
commenting-out its content) for security reasons. Otherwise, someone
may run it by accident or malice in the future (the content above
should be safe, but it won’t be the day you add an
rm -fr ... !).
We’ll now create a small CGI script that wraps
git-http-backend. In theory, you could directly map some
git-http-backend, but I like having a little
control (i.e. ability to add if/then/else, to export variables, or to
do whatever I want before and after serving the repository) over
what’s going on with a small script.
.htaccess, add the following two lines:
SetEnv GIT_PROJECT_ROOT /www/whatever/git-hosting/git-repos SetEnv GIT_HTTP_EXPORT_ALL
Create a script
git.cgi with this content:
#! /bin/sh git http-backend "$@"
Now, you should be able to clone your empty Git repository with e.g.
git clone https://example.com/whatever/git-hosting/git.cgi/example-repo.git/
If it doesn’t work, try loading this URL (well, adapted to your case obviously) in your browser:
It should normally show something like
If it still doesn’t work, you may add some logging code to your
git.cgi, like this:
#! /bin/sh logfile=/www/verimag/htdocs/PEOPLE/moy/cgi/log.txt date >> "$logfile" #PATH=/www/verimag/htdocs/PEOPLE/moy/cgi/:"$PATH" git http-backend "$@" 2>> "$logfile" || echo failed >> "$logfile" echo >> "$logfile"
git clone with more debug information
GIT_CURL_VERBOSE=1 GIT_TRACE=1 git clone -v http://...
The simplest solution I found to have a running Gitweb instance is to
install it manually. From the
gitweb/ subdirectory of
Git’s source tree, run this (replacing
the place where you want to install it):
make GIT=git DESTDIR=$INSTALL_PATH gitwebdir=. bindir=/usr/bin/ make GIT=git DESTDIR=$INSTALL_PATH gitwebdir=. bindir=/usr/bin/ install
This should create a file
gitweb.cgi and a
static/ directory in your installation directory. Gitweb
is relatively lightweight (< 500Ko), so it’s no big problem to copy
the whole thing for multiple instances.
gitweb.cgi, create a file
gitweb_config.perl with the following content (adapted
to your needs):
$projectroot = "/www/whatever/git-hosting/git-repos";
(no trailing-slash on the path)
Now load the URL corresponding to
hopefully get the list of projects :-)
Git repositories like to be regularly garbage-collected. You can do that with a simple CGI script like this one:
#! /bin/sh echo 'Content-Type: text/plain' echo for d in git-repos/*/; do printf "%s ... " "$d" (cd "$d" && git gc) && echo "OK" || echo "failed" done
(to be called from your web browser)