====== Mercurial + Nginx ======


Поднял на днях для личных нужд сервер с Mercurial репозиториями. С авторизацией и шифрованием, как и положено личным нуждам. Почему не Git? Да кто его знает. Git’а мне и на работе хватает, кроме того, уж очень инопланетянский у него интерфейс. Mercurial в этом плане мне более симпатичен. А почему не CVS/SVN? Мне нравится возможность иметь локально репозиторий со всей историей. Кроме того в DVCS работа с ветками сделана поприятнее.

В общем-то в процессе поднимания ничего особенного нет, вся информация доступна в Интернете. Но для интересующихся приведу последовательность команд с краткими комментариями. Установка происходит на Debian Lenny с nginx и без Apache.

Ставим пакеты mercurial и apache2-utils. Последний понадобится для создания файла с паролями для авторизации.

  apt-get install mercurial apache2-utils
  
Создаем пользователя, из под которого будет работать сервер.

  useradd -c "Mercurial Server" -d /var/hg -r -s /bin/false -U hg
  
Создаем структуру директорий.

  mkdir -p /var/hg/{conf,logs,repos}
  
Ставим правильные права.

  chown hg:hg /var/hg/{logs,repos}
  chmod o-rwx /var/hg/{logs,repos}
  
Должно получиться как-то так:

  # ls -l /var/hg
  total 12
  drwxr-xr-x 2 root root 4096 2010-05-14 15:53 conf
  drwxr-x--- 2 hg   hg   4096 2010-05-14 16:19 logs
  drwxr-x--- 3 hg   hg   4096 2010-05-14 16:14 repos
  
Создаем пробный репозиторий.

  su -s /usr/bin/hg hg init /var/hg/repos/test
  
Проверяем.

  # ls -l /var/hg/repos
  total 4
  drwxr-xr-x 3 hg hg 4096 2010-05-17 10:06 test

Теперь создаем файл /var/hg/conf/webdir.conf следующего содержания:

  [collections]
  /var/hg/repos = /var/hg/repos
  
Такой конфиг позволяет не описывать репозитории по одному, а указать все сразу в виде коллекции. Еще нам понадобится файл /var/hg/conf/hgrc, который мы будем копировать в каждый новый репозиторий. В этом файле отключается встроенный в Mercurial SSL (потому что мы будем использовать SSL в nginx) и разрешается всем push (потому что мы будем использовать авторизацию в nginx).

  # cat /var/hg/conf/hgrc
  [web]
  push_ssl = false
  allow_push = *
  # cp /var/hg/conf/hgrc /var/hg/repos/test/.hg/
  
Все готово для запуска сервера. Чтобы было совсем хорошо, создаем скрипт /etc/init.d/hg-serve с таким содержимым:

<code sh>
  #!/bin/sh
  ### BEGIN INIT INFO
  # Provides:          hg-serve
  # Required-Start:    $remote_fs
  # Required-Stop:     $remote_fs
  # Default-Start:     2 3 4 5
  # Default-Stop:      0 1 6
  # Short-Description: Mercurial server
  ### END INIT INFO
  
  PATH=/sbin:/usr/sbin:/bin:/usr/bin
  DESC="Mercurial server"
  NAME=hg
  HOME=/var/$NAME
  DAEMON=/usr/bin/$NAME
  PIDFILE=$HOME/logs/$NAME.pid
  DAEMON_ARGS="serve -d -A $HOME/logs/access.log -E $HOME/logs/error.log \
         -p 9001 -a 127.0.0.1 --webdir-conf $HOME/conf/webdir.conf \
         --pid-file $PIDFILE"
  SCRIPTNAME=/etc/init.d/$NAME
  
  [ -x "$DAEMON" ] || exit 0
  . /lib/init/vars.sh
  . /lib/lsb/init-functions
  
  do_start()
  {
  	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
  		--test > /dev/null || return 1
  	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
  		--chuid hg:hg -- $DAEMON_ARGS || return 2
  }
  
  do_stop()
  {
  	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 \
  		--pidfile $PIDFILE --name $NAME
  	RETVAL="$?"
  	[ "$RETVAL" = 2 ] && return 2
  	start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 \
  		--exec $DAEMON
  	[ "$?" = 2 ] && return 2
  	rm -f $PIDFILE
  	return "$RETVAL"
  }
  
  case "$1" in
    start)
  	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
  	do_start
  	case "$?" in
  		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
  		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
  	esac
  	;;
    stop)
  	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
  	do_stop
  	case "$?" in
  		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
  		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
  	esac
  	;;
    restart|force-reload)
  	log_daemon_msg "Restarting $DESC" "$NAME"
  	do_stop
  	case "$?" in
  	  0|1)
  		do_start
  		case "$?" in
  			0) log_end_msg 0 ;;
  			1) log_end_msg 1 ;; # Old process is still running
  			*) log_end_msg 1 ;; # Failed to start
  		esac
  		;;
  	  *)
  	  	# Failed to stop
  		log_end_msg 1
  		;;
  	esac
  	;;
    *)
  	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
  	exit 3
  	;;
  esac
  
  :
</code>

Добавляем в автозагрузку.

  update-rc.d hg-serve defaults
  
И запускаем.

  /etc/init.d/hg-serve start

Проверяем, что все работает.


  # hg clone http://localhost:9001/test
  destination directory: test
  no changes found
  updating working directory
  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
  # cd test
  # touch test
  # hg add test
  # hg commit -m test
  No username found, using 'root@example.com' instead
  # hg push
  pushing to http://localhost:9001/test
  searching for changes
  adding changesets
  adding manifests
  adding file changes
  added 1 changesets with 1 changes to 1 files
  # cd .. && rm -rf test
  
Теперь настраиваем nginx. Сначала заводим пользователя с паролем для авторизации.

  htpasswd -c /var/hg/conf/htpasswd user
  
Далее создаем самоподписанный (а для личных нужд другого и не требуется) SSL сертификат. Привожу только команды, подробнее о процессе можно почитать, например, тут.

  cd /var/hg/conf
  openssl genrsa -des3 -out server.key 1024
  openssl req -new -key server.key -out server.csr
  cp server.key server.key.org
  openssl rsa -in server.key.org -out server.key
  openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
  
Исправляем права для секретных файлов.

  chgrp www-data /var/hg/conf/{htpasswd,server.*}
  chmod o-rwx /var/hg/conf/{htpasswd,server.*}
  
Проверяем.

  # ls -l /var/hg/conf
  total 28
  -rw-r--r-- 1 root root       38 2010-05-14 15:41 hgrc
  -rw-r----- 1 root www-data   19 2010-05-17 10:30 htpasswd
  -rw-r----- 1 root www-data 1001 2010-05-14 15:45 server.crt
  -rw-r----- 1 root www-data  725 2010-05-14 15:45 server.csr
  -rw-r----- 1 root www-data  891 2010-05-14 15:45 server.key
  -rw-r----- 1 root www-data  963 2010-05-14 15:45 server.key.org
  -rw-r--r-- 1 root root       44 2010-05-14 15:53 webdir.conf
  
Создаем конфиг для nginx /etc/nginx/sites-available/hg.example.com:

  server {
  	listen 443;
  	server_name hg.example.com;
  	access_log /var/log/nginx/hg.example.com.access.log;
  
  	ssl on;
  	ssl_certificate /var/hg/conf/server.crt;
  	ssl_certificate_key /var/hg/conf/server.key;
  
  	ssl_session_timeout 5m;
  
  	ssl_protocols SSLv2 SSLv3 TLSv1;
  	ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
  	ssl_prefer_server_ciphers on;
  
  	location / {
  		auth_basic "Mercurial Repositories";
  		auth_basic_user_file /var/hg/conf/htpasswd;
  
  		proxy_pass http://localhost:9001;
  	}
  }
  
Включаем конфиг и перезапускаем nginx.

  cd /etc/nginx/sites-enabled
  ln -s /etc/nginx/sites-available/hg.example.com
  /etc/init.d/nginx restart
  
Финальная проверка с удаленной машины.

  $ hg clone https://hg.example.com/test
  http authorization required
  realm: Mercurial Repositories
  user: user
  password:
  destination directory: test
  requesting all changes
  adding changesets
  adding manifests
  adding file changes
  added 1 changesets with 1 changes to 1 files
  updating working directory
  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
  $ cd test
  $ ls
  test
  $ echo >>test
  $ hg commit -m test
  $ hg push
  http authorization required
  realm: Mercurial Repositories
  user: user
  password:
  pushing to https://hg.example.com/test
  searching for changes
  adding changesets
  adding manifests
  adding file changes
  added 1 changesets with 1 changes to 1 files