Serving git's smart http protocol and a web interface with Apache

Posted on Tue 03 November 2015 in Git on the server

While git's original http protocol implementation was fairly dumb, the so-called smart http protocol is much better to use and rivals the ssh and git protocols in speed.

It can also be combined with a web-based repository viewer such as gitweb or cgit, allowing you to serve repositories and their web-based viewers from the same location. With bonus points for also supporting the dumb http protocol for really old git clients.

The git documentation has some example configurations, but not really one that ties all this together. The configuration below lets you create a git http server that lets users browse your repositories with cgit or gitweb, and clone using both dumb and smart http.

 Download apache-http.conf
# Your domain name Define SERVER_NAME git.example.com # Path to your project root. Make sure it matches what's in cgitrc/gitweb.conf Define GIT_PROJECT_ROOT /srv/gitroot # Comment this out if you want to use the per-repository git-daemon-export-ok files SetEnv GIT_HTTP_EXPORT_ALL # Which features to use Define USE_GITWEB #Define USE_CGIT Define USE_SMART_HTTP Define USE_DUMB_HTTP # Where do the applications live (these are Debian's defaults) Define GIT_LIBDIR /usr/lib/git-core Define GITWEB_SHAREDIR /usr/share/gitweb Define CGIT_LIBDIR /usr/lib/cgit Define CGIT_SHAREDIR /usr/share/cgit # Below here nothing should need changing if you set the properties above correctly <VirtualHost *:80> ServerName ${SERVER_NAME} ErrorLog ${APACHE_LOG_DIR}/${SERVER_NAME}/error.log CustomLog ${APACHE_LOG_DIR}/${SERVER_NAME}/access.log combined # Gitweb <IfDefine USE_GITWEB> <IfDefine USE_CGIT> Error "Both gitweb and cgit are enabled. Please enable only one." </IfDefine> DocumentRoot ${GITWEB_SHAREDIR} <Directory ${GITWEB_SHAREDIR}> Options +FollowSymLinks +ExecCGI AddHandler cgi-script .cgi DirectoryIndex gitweb.cgi Require all granted </Directory> RewriteEngine on RewriteRule ^/(.*\.git(|(/(?!(HEAD|info|objects|refs|git-(upload|receive)-pack)).*)))$ \ /gitweb.cgi%{REQUEST_URI} [L,PT] </IfDefine> # CGit <IfDefine USE_CGIT> <Directory ${CGIT_LIBDIR}> Options +FollowSymLinks +ExecCGI AddHandler cgi-script .cgi Require all granted </Directory> <Directory ${CGIT_SHAREDIR}> Require all granted </Directory> RewriteEngine on RewriteRule ^/(.*\.git(|(/(?!(HEAD|info|objects|refs|git-(upload|receive)-pack)).*)))?$ \ ${CGIT_LIBDIR}/cgit.cgi/$1 Alias /cgit-css "${CGIT_SHAREDIR}" </IfDefine> # HTTP transports <IfDefine USE_SMART_HTTP> <Directory ${GIT_LIBDIR}> Require all granted </Directory> SetEnv GIT_PROJECT_ROOT ${GIT_PROJECT_ROOT} ScriptAliasMatch "^/(.*\.git/(HEAD|info/refs))$" \ ${GIT_LIBDIR}/git-http-backend/$1 ScriptAliasMatch "^/(.*\.git/git-(upload|receive)-pack)$" \ ${GIT_LIBDIR}/git-http-backend/$1 </IfDefine> <IfDefine USE_DUMB_HTTP> <Directory ${GIT_PROJECT_ROOT}> Require all granted </Directory> <IfDefine !USE_SMART_HTTP> AliasMatch "^/(.*\.git/(HEAD|info/refs))$" \ ${GIT_PROJECT_ROOT}/$1 </IfDefine> AliasMatch "^/(.*\.git/objects/(info/[^/]+|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx)))" \ ${GIT_PROJECT_ROOT}/$1 </IfDefine> </VirtualHost>

The following minimal configuration is needed for gitweb:

 Download gitweb.conf
# path to git projects (<project>.git) $projectroot = "/srv/gitroot"; # directory to use for temp files $git_temp = "/tmp"; # git-diff-tree(1) options to use for generated patches #@diff_opts = ("-M"); @diff_opts = (); # Features $feature{'pathinfo'}{'default'} = [1];

And for cgit, this is the absolute minimum

 Download cgitrc
# # cgit config # see cgitrc(5) for details css=/cgit-css/cgit.css logo=/cgit-css/cgit.png scan-path=/srv/gitroot

HTTPS and authentication

The configuration above is absolutely fine if you want world-readable read-only repositories and all your pushing is done over ssh. But if you want to accept pushes over https, you mush also enable authentication and SSL. The configuration below does this. It does not do anything with authorization though, it assumes that if you can authenticate, you can push. If you want more fine-grained authorization you can use per-repository update hooks to authorize your users.

As you'll see, the configuration is very similar to the http-only version. The differences are that all http traffic is redirected to https, ssl is enabled and secured using Mozilla's recommendations and that authentication is mandatory.

 Download apache-https.conf
# Your domain name Define SERVER_NAME git.example.com Define SSL_CERT /etc/ssl/certs/${SERVER_NAME}.pem Define SSL_KEY /etc/ssl/private/${SERVER_NAME}.key # Path to your project root. Make sure it matches what's in cgitrc/gitweb.conf Define GIT_PROJECT_ROOT /srv/gitroot # Comment this out if you want to use the per-repository git-daemon-export-ok files SetEnv GIT_HTTP_EXPORT_ALL # Which features to use Define USE_GITWEB #Define USE_CGIT Define USE_SMART_HTTP Define USE_DUMB_HTTP # Where do the applications live (these are Debian's defaults) Define GIT_LIBDIR /usr/lib/git-core Define GITWEB_SHAREDIR /usr/share/gitweb Define CGIT_LIBDIR /usr/lib/cgit Define CGIT_SHAREDIR /usr/share/cgit # HTTP to HTTPS redirect <VirtualHost *:80> ServerName ${SERVER_NAME} ErrorLog ${APACHE_LOG_DIR}/${SERVER_NAME}/error.log CustomLog ${APACHE_LOG_DIR}/${SERVER_NAME}/access.log combined RedirectMatch (.*) https://${SERVER_NAME}$1 </VirtualHost> <VirtualHost *:443> ServerName ${SERVER_NAME} SSLEngine on SSLCertificateFile ${SSL_CERT} SSLCertificateKeyFile ${SSL_KEY} SSLProtocol ALL -SSLv2 -SSLv3 SSLHonorCipherOrder on SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH SSLCompression Off ErrorLog ${APACHE_LOG_DIR}/${SERVER_NAME}/error.log CustomLog ${APACHE_LOG_DIR}/${SERVER_NAME}/access.log combined # Authentication <Location /> AuthType Basic AuthName "${SERVER_NAME} git access" # You'll probably want to customize this, using ldap or pam authentication. AuthBasicProvider file AuthUserFile /etc/git-users Require valid-user </Location> # Gitweb <IfDefine USE_GITWEB> <IfDefine USE_CGIT> Error "Both gitweb and cgit are enabled. Please enable only one." </IfDefine> DocumentRoot ${GITWEB_SHAREDIR} <Directory ${GITWEB_SHAREDIR}> Options +FollowSymLinks +ExecCGI AddHandler cgi-script .cgi DirectoryIndex gitweb.cgi Require all granted </Directory> RewriteEngine on RewriteRule ^/(.*\.git(|(/(?!(HEAD|info|objects|refs|git-(upload|receive)-pack)).*)))$ \ /gitweb.cgi%{REQUEST_URI} [L,PT] </IfDefine> # CGit <IfDefine USE_CGIT> <Directory ${CGIT_LIBDIR}> Options +FollowSymLinks +ExecCGI AddHandler cgi-script .cgi Require all granted </Directory> <Directory ${CGIT_SHAREDIR}> Require all granted </Directory> RewriteEngine on RewriteRule ^/(.*\.git(|(/(?!(HEAD|info|objects|refs|git-(upload|receive)-pack)).*)))?$ \ ${CGIT_LIBDIR}/cgit.cgi/$1 Alias /cgit-css "${CGIT_SHAREDIR}" </IfDefine> # HTTP transports <IfDefine USE_SMART_HTTP> <Directory ${GIT_LIBDIR}> Require all granted </Directory> SetEnv GIT_PROJECT_ROOT ${GIT_PROJECT_ROOT} ScriptAliasMatch "^/(.*\.git/(HEAD|info/refs))$" \ ${GIT_LIBDIR}/git-http-backend/$1 ScriptAliasMatch "^/(.*\.git/git-(upload|receive)-pack)$" \ ${GIT_LIBDIR}/git-http-backend/$1 </IfDefine> <IfDefine USE_DUMB_HTTP> <Directory ${GIT_PROJECT_ROOT}> Require all granted </Directory> <IfDefine !USE_SMART_HTTP> AliasMatch "^/(.*\.git/(HEAD|info/refs))$" \ ${GIT_PROJECT_ROOT}/$1 </IfDefine> AliasMatch "^/(.*\.git/objects/(info/[^/]+|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx)))" \ ${GIT_PROJECT_ROOT}/$1 </IfDefine> </VirtualHost>