Desde que empecé a utilizar Mercurial me ha parecido una herramienta excelente para el control de versiones.
Además de ser open source, la encuentro ágil, sencilla y multiplataforma y con una documentación (Mercurial Red Book) que ya le gustaría tener a otros. Basta con sólo leer este único manual para comprender el concepto detrás del control de versiones distribuído, los comandos de uso, la administración de los repositorios y la arquitectura interna del producto.
Pero, la Ley de Metcalfe (que otros quieren rebautizar como Teoría del bar lleno) ha hecho que los usuarios de DVCS utilicen en forma masiva Git, quizás impulsados más por el éxito de GitHub que por el sistema de control de versiones sobre el que se basa.
Esta instalación funcionó correctamente durante varios años (desde el 2014 hasta finales de 2017) y, si bien puede servir de referencia para futuras instalaciones, debe tenerse en cuenta que los sistemas y productos en los que se basa puedan haber sufrido cambios que requieran actualizar el procedimiento aquí descripto.
Contenido
Objetivo
El servicio gestiona dos repositorios:
- Un repositorio público, accesible por http y que permite operaciones de lectura solamente.
- Un repositorio privado, al que únicamente accederán usuarios autenticados accediendo por https y que permitirá operaciones tanto de lectura como de escritura.
En caso que se necesite un repositorio público que permita a la vez operaciones de escritura, siempre podrá ser creado en una de las dos áreas y enlazado simbólicamente a la otra o, mejor aún; creado en el área privada y clonado en el área pública. Así, se podrá controlar cuáles cambios son trasladados o no al área pública.
Como se generarán simultáneamente dos servicios distintos, en cada sección se tratará básicamente cómo configurar el área de repositorios pública y posteriormente se comentarán los cambios a realizar para implementar el área privada.
Requisitos
- Se cuenta con un nombre de dominio (FQDN) específico para este servicio. En este ejemplo, el nombre utilizado es
hg.example.org
. - Se cuenta con certificados para el dominio. Ya sea auto-firmados como se explica en How to create SSL certificates u obtenidos por una CA.
-
Se cuenta con una distribución Linux con un servidor Nginx operacional donde se ha instalado Mercurial y los paquetes de gestión de fastcgi.
En caso de tratarse de una distribución basada en Debian, el comando de intalación es:# apt-get install mercurial nginx apache2-utils python-flup spawn-fcgi
-
Se cuenta con un directorio donde alojar los scripts, archivos de configuración y repositorios. Este directorio debe contar con los permisos correctos de acceso y ejecución del servidor web.
En este ejemplo, el directorio escogido es/var/local/hg
, por lo que la secuencia de comandos será:# mkdir -p /var/local/hg/scripts # mkdir -p /var/local/hg/repos/public # mkdir -p /var/local/hg/repos/private # chown -R www-data:www-data /var/local/hg/ # chmod -R o-rwx /var/local/hg/
Scripts de publicación
En esta sección se definen los scripts fastcgi a ejecutarse para publicar los 2 servicios que se desean configurar.
Script de publicación del servicio público
Se edita un nuevo fichero de nombre /var/local/hg/scripts/hgwebpub.fcgi
y se puebla con el siguiente contenido:
#!/usr/bin/env python # # An example CGI script to export multiple hgweb repos, edit as necessary # adjust python path if not a system-wide install: #import sys #sys.path.insert(0, "/path/to/python/lib") # enable demandloading to reduce startup time from mercurial import demandimport; demandimport.enable() # Uncomment to send python tracebacks to the browser if an error occurs: #import cgitb #cgitb.enable() # If you'd like to serve pages with UTF-8 instead of your default # locale charset, you can do so by uncommenting the following lines. # Note that this will cause your .hgrc files to be interpreted in # UTF-8 and all your repo files to be displayed using UTF-8. # import os os.environ["HGENCODING"] = "UTF-8" from mercurial.hgweb.hgwebdir_mod import hgwebdir from flup.server.fcgi import WSGIServer # The config file looks like this. You can have paths to individual # repos, collections of repos in a directory tree, or both. # # [paths] # virtual/path1 = /real/path1 # virtual/path2 = /real/path2 # virtual/root = /real/root/* # / = /real/root2/* # # paths example: # # * First two lines mount one repository into one virtual path, like # '/real/path1' into 'virtual/path1'. # # * The third entry tells every mercurial repository found in # '/real/root', recursively, should be mounted in 'virtual/root'. This # format is preferred over the [collections] one, using absolute paths # as configuration keys is not supported on every platform (including # Windows). # # * The last entry is a special case mounting all repositories in # '/real/root2' in the root of the virtual directory. # # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples # or use a dictionary with entries like 'virtual/path': '/real/path' WSGIServer(hgwebdir('/var/local/hg/scripts/hgwebpub.config')).run() # EOF : hgwebpub.fcgi
Y se ajustan los permisos de acceso y ejecución al script con:
# chown www-data:www-data /var/local/hg/scripts/hgwebpub.fcgi # chmod 0750 /var/local/hg/scripts/hgwebpub.fcgi
Script de publicación del servicio privado
Dado que ambos servicios son muy parecidos, la solución más sencilla consiste en copiar el archivo:
/var/local/hg/scripts/hgwebpub.fcgi
como:
/var/local/hg/scripts/hgwebprv.fcgi
y editar este último modificando la línea:
WSGIServer(hgwebdir('/var/local/hg/scripts/hgwebpub.config')).run()
por:
WSGIServer(hgwebdir('/var/local/hg/scripts/hgwebprv.config')).run()
cambiando por lo tanto el archivo de configuración a utilizar por uno y otro script.
Como en el script anterior, se ajustan los permisos de acceso y ejecución con:
# chown www-data:www-data /var/local/hg/scripts/hgwebprv.fcgi # chmod 0750 /var/local/hg/scripts/hgwebprv.fcgi
Creación de archivos de configuración
Archivo de configuración del servicio público
Se edita un nuevo fichero de nombre /var/local/hg/scripts/hgwebpub.config
y se puebla con el siguiente contenido:
[web] baseurl = / push_ssl = True descend = True collapse = True [paths] /public = /var/local/hg/repos/public/**
Al no incluirse en este archivo la declaración allow_push
se garangiza que ningún usuario tendrá permisos de escritura sobre los repositorios de este servicio. Lo cual es irrelevante dado que la declaración push_ssl = True
no permite operaciones de escritura salvo que se acceda vía https, y el protocolo https no será configurado en el servidor web para esta área.
Se ajustan los permisos de acceso y ejecución al archivo con:
# chown www-data:www-data /var/local/hg/scripts/hgwebpub.config # chmod 0640 /var/local/hg/scripts/hgwebpub.config
Archivo de configuración del servicio privado
Se edita un nuevo fichero de nombre /var/local/hg/scripts/hgwebprv.config
y se puebla con el siguiente contenido:
[web] baseurl = / allow_push = * push_ssl = True descend = True collapse = True [paths] /public = /var/local/hg/repos/public/** /private = /var/local/hg/repos/private/**
Dado que la autenticación de usuarios ya se realiza en el servidor web, es seguro permitir el acceso de escritura irrestricto con la declaración allow_push = *
. Si se desea realizar un control de acceso individualizado para cada repositorio en particular, se tiene que editar el fichero .hg/hgrc
en ese repositorio especificando cuáles serán los usuarios con permisos de escritura.
Se ajustan los permisos de acceso y ejecución al script con:
# chown www-data:www-data /var/local/hg/scripts/hgwebprv.config # chmod 0640 /var/local/hg/scripts/hgwebprv.config
Creación de scripts de inicio automático
En esta sección se definen los scripts que permiten el arranque y apagado automático de los scripts de servicio cada vez que se encienda o apague el equipo donde se alojan los servicios de publicación.
Script de inicio del servicio público
Se edita un nuevo fichero de nombre /etc/init.d/hgwebpub
y se puebla con el siguiente contenido:
#! /bin/sh # # hgwebpub.init Startup script for the fast cgi Mercurial Server # # description: Loading hgwebpub.fcgi using spawn-cgi # # Author: Ryan Norbauer # Modified: Geoffrey Grosenbach http://topfunky.com # Modified: David Krmpotic http://davidhq.com # Modified: Kun Xi http://kunxi.org # Modified: Jose Ignacio Croce http://crossforests.com PATH=/var/local/hg/scripts:$PATH DAEMON=/usr/bin/spawn-fcgi FCGISOCK=/var/run/hgwebpub.sock FCGIUSER=www-data FCGIGROUP=www-data FCGIAPP=/var/local/hg/scripts/hgwebpub.fcgi PIDFILE=/var/run/hgwebpub.pid DESC="HG (public) in FastCGI mode" # Gracefully exit if the package has been removed. test -x $DAEMON || exit 0 test -x $FCGIAPP || exit 0 start() { $DAEMON -s $FCGISOCK -u $FCGIUSER -g $FCGIGROUP \ -f $FCGIAPP -P $PIDFILE 2> /dev/null || echo -en "already running" } stop() { kill -QUIT `cat $PIDFILE` || echo -en "not running" } restart() { kill -HUP `cat $PIDFILE` || echo -en "can't reload" } case "$1" in start) echo -n "Starting $DESC: " start ;; stop) echo -n "Stopping $DESC: " stop ;; restart|reload) echo -n "Restarting $DESC: " stop # One second might not be time enough for a daemon to stop, # if this happens, d_start will fail (and dpkg will break if # the package is being upgraded). Change the timeout if needed # be, or change d_stop to have start-stop-daemon use --retry. # Notice that using --retry slows down the shutdown process somewhat. sleep 1 start ;; *) echo "Usage: $SCRIPTNAME {start|stop|restart|reload}" >&2 exit 3 ;; esac exit $?
Se ajustan los permisos de acceso y ejecución al script y se habilita el inicio automático al encender el sistema con:
# chown root:root /etc/init.d/hgwebpub # chmod 0750 /etc/init.d/hgwebpub # update-rc.d hgwebpub defaults
A partir de este momento, el servicio podrá ser iniciado con comandos del tipo:
# service hgwebpub start
y detenido con comandos como:
# service hgwebpub stop
Script de inicio del servicio privado
Se edita un nuevo fichero de nombre /etc/init.d/hgwebprv
y se puebla con el siguiente contenido:
#! /bin/sh # # hgwebprv.init Startup script for the fast cgi Mercurial Server # # description: Loading hgwebprv.fcgi using spawn-cgi # # Author: Ryan Norbauer # Modified: Geoffrey Grosenbach http://topfunky.com # Modified: David Krmpotic http://davidhq.com # Modified: Kun Xi http://kunxi.org # Modified: Jose Ignacio Croce http://crossforests.com PATH=/var/local/hg/scripts:$PATH DAEMON=/usr/bin/spawn-fcgi FCGISOCK=/var/run/hgwebprv.sock FCGIUSER=www-data FCGIGROUP=www-data FCGIAPP=/var/local/hg/scripts/hgwebprv.fcgi PIDFILE=/var/run/hgwebprv.pid DESC="HG (private) in FastCGI mode" # Gracefully exit if the package has been removed. test -x $DAEMON || exit 0 test -x $FCGIAPP || exit 0 start() { $DAEMON -s $FCGISOCK -u $FCGIUSER -g $FCGIGROUP \ -f $FCGIAPP -P $PIDFILE 2> /dev/null || echo -en "already running" } stop() { kill -QUIT `cat $PIDFILE` || echo -en "not running" } restart() { kill -HUP `cat $PIDFILE` || echo -en "can't reload" } case "$1" in start) echo -n "Starting $DESC: " start ;; stop) echo -n "Stopping $DESC: " stop ;; restart|reload) echo -n "Restarting $DESC: " stop # One second might not be time enough for a daemon to stop, # if this happens, d_start will fail (and dpkg will break if # the package is being upgraded). Change the timeout if needed # be, or change d_stop to have start-stop-daemon use --retry. # Notice that using --retry slows down the shutdown process somewhat. sleep 1 start ;; *) echo "Usage: $SCRIPTNAME {start|stop|restart|reload}" >&2 exit 3 ;; esac exit $?
Se ajustan los permisos de acceso y ejecución al script y se habilita el inicio automático al encender el sistema con:
# chown root:root /etc/init.d/hgwebprv # chmod 0750 /etc/init.d/hgwebprv # update-rc.d hgwebprv defaults
A partir de este momento, el servicio podrá ser iniciado con comandos del tipo:
# service hgwebprv start
y detenido con comandos como:
# service hgwebprv stop
Creación de la base de datos de usuarios
La base de datos de los usuarios habilitados para acceder al sistema privado se ubica en /var/local/hg/scripts/hgusers
y se crea con la opción ‑c
del comando htpasswd
como se muestra en el siguiente ejemplo:
# htpasswd -c /var/local/hg/scripts/hgusers user1
Al ejecutar este comando, se solicita que se ingrese dos veces la clave con la que el usuario se autenticará en el sistema.
La creación de usuarios adicionales también se realiza con el comando htpasswd user_name
pero cuidando de nunca incluir la opción ‑c
ya que, de hacerlo, la base de datos actual sería truncada a cero y reiniciada; sólo existiendo en su lugar el nuevo usuario creado.
# htpasswd /var/local/hg/scripts/hgusers user2
Una vez creado el archivo, se ajustan los permisos de acceso y ejecución con:
# chown www-data:www-data /var/local/hg/scripts/hgusers # chmod 0640 /var/local/hg/scripts/hgusers
Configuración del servidor Nginx
Se crea un site específico para el servicio. Para ello, se crea el fichero /etc/nginx/sites-available/hg
y se puebla con el siguiente contenido:
# # hg : Mercurial web server configuration # # 15DEC2014 - Jose Ignacio Croce # server { listen 80; server_name hg.example.org; root /var/local/hg/repos; location / { include fastcgi_params; fastcgi_pass unix:/var/run/hgwebpub.sock; fastcgi_param DOCUMENT_ROOT /var/local/hg/repos; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_FILENAME /var/local/hg/scripts/hgwebpub.fcgi; } } server { listen 443 ssl; server_name hg.example.org; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; root /var/local/hg/repos; location / { include fastcgi_params; fastcgi_pass unix:/var/run/hgwebprv.sock; fastcgi_param DOCUMENT_ROOT /var/local/hg/repos; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_FILENAME /var/local/hg/scripts/hgwebprv.fcgi; auth_basic 'hgwebprv'; auth_basic_user_file /var/local/hg/scripts/hgusers; } } # EOF : hg
Se ajustan los permisos del archivo y se activa el site con la siguiente secuencia de comandos:
# chown www-data:www-data /etc/nginx/sites-available/hg # chmod 0640 /etc/nginx/sites-available/hg # ln -s /etc/nginx/sites-available/hg /etc/nginx/sites-enabled/hg
Validación
Reiniciamos el servidor con:
# /etc/init.d/hgwebpub restart # /etc/init.d/hgwebprv restart # /etc/init.d/nginx restart
Y comprobamos que el servicio funciona correctamente con el siguiente procedimiento.
Creación de repositorio de pruebas privado
Se accede al directorio de repositorios privado y se genera un repositorio de pruebas mediante el comando hg init
y se ajustan los permisos para garantizar el acceso desde el servidor web:
# cd /var/local/hg/repos/private/ # hg init testprv # chown -R www-data:www-data testprv/ # chmod -R o-rwx testprv/
Creación de repositorio de pruebas público
Se accede al directorio de repositorios público y se genera un repositorio de pruebas mediante el comando hg init
y se ajustan los permisos para garantizar el acceso exclusivamente de lectura desde el servidor web:
# cd /var/local/hg/repos/public/ # hg init testpub # chown -R root:www-data testpub/ # chmod -R o-rwx testpub/ # chmod -R g-w testpub/
En este ejemplo sólo el usuario root tendrá permiso para realizar operaciones de escritura para el repositorio, lo cual da un grado de seguridad adicional frente al acceso público.
Pruebas de acceso web
Desde un navegador web se accede a la URL http://hg.example.org
. El resultado esperado es una página como la siguiente:
Haciendo click en el nombre public, se debería ver el repositorio generado en el área pública:
Y haciendo click en el nombre del repositorio, la ventana esperada es:
Ahora, si en lugar de utilizar el acceso http, se intenta el acceso seguro a https://hg.example.org
, el servidor solicitará autenticación con nombre y clave. Una vez superada la fase de autenticación la página a viusalizar debería ser la siguiente: