Accueil > Outils > Git > Host a Git repository over HTTP(S) with limited access to the (...)
Git with a .htaccess and a CGI script
mardi 10 janvier 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 1.7.2.5
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 .htaccess
:
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.
Now, re-loading 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
URL to 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.
In your .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 :
https://example.com/whatever/git-hosting/git.cgi/example-repo.git/HEAD
It should normally show something like ref: refs/heads/master
.
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"
and/or run git clone
with more debug information
activated :
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 $INSTALL_PATH
with
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.
Next to 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 gitweb.cgi
, and
hopefully get the list of projects :-)
git gc
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)