Friday, November 12, 2010

Hosting Mercurial on a Synology NAS

Some time ago I ditched my subversion and wanted to try out a distributed sourcecontrolsystem. I had the choice between GIT and Mercurial: There is nothing to explain why I chose Mercurial, I just flipped a coin: but since then I'm quite happy with that decision.

Installing Mercurial on my Synology NAS was a peace of cake: you just needed to have access via SSH or Telnet, having the NAS bootstrapped and use the ipkg install command for installing it. It doesn't matter which version you actually take: I already used python-2.6 so I took the py26-mercurial.

After that Mercurial was already usable if you had direct access to the repositories, but since I'm not always in my LAN, I wanted also the possibility to have access via HTTPS or HTTP.

Mercurial has a build-in web server, which can serve the repository online: although its not recommended to use this permanently because it lacks of authentication methods and security, I gave this a try.

This webservice can be configured quite easily within the command, or you do it like me and use a config-file for that.

[collections]
#location of the repositories
#setting it this way lets the webserver parse all subdirectories for repositories
[rootlocationOfRepos] = [rootlocationOfRepos]


[web]
style = gitweb #you have more choices here, but gitweb looks the best ;)
allow_archive = bz2 gz zip
contact = Max A. Pfandl, max[at]pfandl.name
push_ssl = false

[ui]
username = madmap

For starting and stopping the hg serve I wrote a simple sh script which started me this service on port 666, using a PID-File for coordinating the process and configured by the config-file provided.

#!/bin/sh
#
# Startup script for mercurial server.
#
# Change following ines
APP_BIN=[LocationOfHG]


# Path to PID file of running mercurial process.
PID_FILE=[PIDFile]


state=$1

case "$state" in
'start')
echo "Mecurial Server service starting."
(${APP_BIN} serve --webdir-conf [locationof]/hgweb.config -p 666 -d  --pid-file ${PID_FILE})
;;

'stop')
if [ -f "${PID_FILE}" ]; then
PID=`cat "${PID_FILE}"`
if [ "${PID}" -gt 1 ]; then
kill -TERM ${PID}
echo "Stopping the Mercurial service PID=${PID}."
else
echo Bad PID for Mercurial -- \"${PID}\"
fi
else
echo No PID file recorded for mercurial
fi
;;
'restart')
if [ -f "${PID_FILE}" ]; then
PID=`cat "${PID_FILE}"`
if [ "${PID}" -gt 1 ]; then
kill -TERM ${PID}
echo "Stopping the Mercurial service PID=${PID}."
else
echo Bad PID for Mercurial -- \"${PID}\"
fi
else
echo No PID file recorded for mercurial
fi
echo "Mecurial Server service starting."
(${APP_BIN} serve --webdir-conf [locationof]/hgweb.config -p 666 -d  --pid-file ${PID_FILE})
;;
*)
echo "$0 {start|stop}"
exit 1
;;
esac

So now I had access from everywhere over port 666 and without any authentication whatsoever: This works fine, but of course I don't want everybody to have full access on my precious sourcecode!

So the first thing I did was close the port on my router: and now I was exactly where I was before: I had access within my LAN, but not from anywhere else. I needed to find a solution for authentication and security.

Therefore I used nginx. This is a lightweight webserver which can be nicely configured to work as a proxy-forwarder.

Installing of nginx was again made easy by using the ipkg install command and then you need to configure it.

My nginx.config lookes like this:

user  root root;
worker_processes  1;

error_log  /volume1/public/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

pid        /volumeUSB3/usbshare/merc/nginx.pid;


events {
worker_connections  1024;
}


http {
include       mime.types;
default_type  application/octet-stream;

## Timeouts
client_body_timeout   600;
client_header_timeout 600;
send_timeout          600;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
proxy_cache off;
add_header    Cache-Control  no-cache;
## General Options
ignore_invalid_headers   on;
limit_zone gulag $binary_remote_addr 5m;
recursive_error_pages    on;
sendfile                 off;
server_name_in_redirect off;
server_tokens           off;

client_max_body_size 100m;
ssl_certificate [locationof]/hg.pfandl.name.pem;
ssl_certificate_key [locationof]/hg.pfandl.name.key;

gzip  on;

server {
listen 443;
ssl on;
server_name [publicURL];    # standard stuff
ssl_session_timeout 10m;

error_page 401 /401.html;
location = /{
auth_basic           "Required for Overview, if not provided you will get forwarded to the Public Area";
auth_basic_user_file [locationof]/htpasswd;
proxy_pass http://localhost:666; # or wherever hg serve is running
}

location = /robots.txt{
root [rootlocationofrobots];
}

location = /401.html{
#this 401.html only forwards to the public area
root [rootlocationof401];
}



location /{
#this only needs a password when you POST smth, for a GET request its allowed for anybody
limit_except GET{
auth_basic           "Restricted to push Changes";
auth_basic_user_file [locationof]/htpasswdPublic;
proxy_pass http://localhost:666; # or wherever hg serve is running
}
proxy_pass http://localhost:666; # or wherever hg serve is running
}
location ^~ /ATTIC/{
auth_basic           "Restricted Access to ATTIC Repo";
auth_basic_user_file[locationof]/htpasswd;
proxy_pass http://localhost:666; # or wherever hg serve is running
}

location ^~ /CAV/{
auth_basic           "Restricted Access to CAV Repo";
auth_basic_user_file [locationof]/htpasswd;
proxy_pass http://localhost:666; # or wherever hg serve is running
}

location ^~ /TECHTALK/{
auth_basic           "Restricted Access to TT Repo";
auth_basic_user_file [locationof]/htpasswd;
proxy_pass http://localhost:666; # or wherever hg serve is running
}
location ^~ /PRIVATE/{
auth_basic           "Restricted";
auth_basic_user_file [locationof]/htpasswd;
proxy_pass http://localhost:666; # or wherever hg serve is running
}

}
}

What you can see in this config is, that I created myself some selfsigned certificate and I use HTTPS so I have already some kind of encryption. Furthermore I can define in this config, who has which access to where: I can define readonly, full or no access per repository. The errorfile (401.html) you see is nothing but a forwarder to the public page on this server, so to the repository, everybody has read-access. The authentication is done via htpasswd-files which have the standard-format for linux. I used this Tool to create the files for me.


After doing all that stuff, everything was finally as expected: I had a running mercurial including full control over authentication (via nginx), encrypted file-transfers and public available over HTTPS.

1 comment:

  1. Very nice tutorial. I stopped before going to nginx but, up to then, every step was easily reproduceable.

    Thanks

    ReplyDelete