nginx+uwsgi架设自用Mercurial hg仓库:hgwebdir

Mercurial/hg是分布式版本管理工具之一,如google code等公共代码服务都直接支持。不过公共服务通常都有如公开开源、空间大小等等的限制,如果特殊情况,需要自己在VPS上假设一套,也不太难。

如果单独一个hg仓库,直接运行hg serve就已经是在线仓库,用nginx做的http_proxy反向代理就够了,各种功能齐全。但是要host多个hg仓库,需要用hgwebdir。

Ubuntu当中安装的mercurial已经包含了架设仓库所用的程序,包括cgi/fcgi/wcgi的,主接口在/usr/share/doc/mercurial-common/examples下的hgwebdir.(ws|(f)c)gi。虽然其中的wsgi有效的只有三行。

Mercurial官方Wiki上主要介绍了用apache2来跑cgi方式的仓库,但是作为python的程序,wsgi才是最原生的嘛。用Nginx+uwsgi的方式,资源占用、响应效率等,都要比apache + cgi靠谱。

Nginx+uwsgi的方式很普遍,参见本博客之前的MoinMoin 与 Nginx, fastcgi 与 uwsgi 的配置一文。

但是因为uwsgi处于快速开发过程当中,ppa的打包也还不十分成熟,里面的路径跟以前有些不一样了。

add-apt-repository ppa:nginx/stable
add-apt-repository ppa:uwsgi/release
apt-get update
apt-get install nginx uwsgi-python python-virtualenv mercurial
 
mkdir -p /var/www/hgwebdir
virtualenv /var/www/hgwebdir/python-home
 
cat >/var/www/hgwebdir/hgwebdir_wsgi.py << EOF
from mercurial import demandimport; demandimport.enable()
from mercurial.hgweb.hgwebdir_mod import hgwebdir
 
class hgwebdir_with_fixed_staticfile(hgwebdir):
    def __call__(self, env, respond):
        response = super(self.__class__, self).__call__(env, respond)
        return response[0] if isinstance(response, tuple) else response
 
application = hgwebdir_with_fixed_staticfile('/var/www/hgwebdir/hgweb.config')
EOF
 
cat >/var/www/hgwebdir/hgweb.config << EOF
[collections]
/home/hg-repo = /home/hg-repo
 
[web]
style = monoblue
allow_archive = gz, zip, bz2
baseurl = /
push_ssl = true
hidden = false
allow_read = *
allow_push = user1 user2
 
[extensions]
#highlight = 
EOF
 
cat >/etc/uwsgi-python/apps-enabled/uwsgi.xml << EOF
<uwsgi>
  <limit-as>256</limit-as>
  <processes>6</processes>
  <memory-report/>
  <vhost/>
  <no-site/>
</uwsgi>
EOF
 
cat >/etc/nginx/sites-enabled/hgwebdir << EOF
server {
    listen   80; 
    client_max_body_size 512m;
    server_name hg.mydomian.com;
    location / {
                include uwsgi_params;
                uwsgi_pass unix:///var/run/uwsgi-python/uwsgi/socket;
                uwsgi_param UWSGI_PYHOME /var/www/hgwebdir/python-home;
                uwsgi_param UWSGI_CHDIR /var/www/hgwebdir/;
                uwsgi_param UWSGI_SCRIPT hgwebdir_wsgi;
                uwsgi_param UWSGI_SCHEME \$scheme;
                uwsgi_param REMOTE_USER \$remote_user;
        }
}
EOF
 
mkdir /home/hg-repo
cd /home/hg-repo
hg init example-repo1
hg init example-repo2
chown -R www-data:www-data /home/hg-repo
 
/etc/init.d/nginx restart
/etc/init.d/uwsgi-python restart

所以,/home/hg-repo下的所有仓库(新建的两个空仓库example-repo1/2)都会被hgwebdir列表在目录上,直接使用其地址就可以pull/push了。

注意的是hgweb.config文件里面的配置,push_ssl = true只允许使用https协议的时候push,也就是需要另外配置一个支持https的nginx。这个属于nginx的基本配置,这里不重提了。

对于不公开的仓库,除了HTTPS,还需要用户名密码,只需要配置nginx的basic http auth,上面命令当中的uwsgi_param REMOTE_USER $remote_user一行配置会把http auth的USER通过协议发送到hgweb,完成其认证过程。

其实hgweb的认证仅仅是用户名的匹配,hgweb.config里面的allow_push = user1 user2是全局设定,各个仓库的.hg/hgrc里面的同样的[web]区下也可以定义allow_push/read,即仓库各自的读写权限。

hgweb.config中注释掉的highlight一行,是启动语法高亮插件用的,打开需要先安装python-pygments包,不然会出错。

这个版本的uwsgi程序的log在/var/log/uwsgi-python/uwsgi.log,如果出任何问题,大概看看log的提示按提示去修复即可。

Update: 2011-3-17

原hgwebdir的程序跟wsgi2协议兼容不好,会出现仓库目录的首页里面的静态文件全部无法打开,log里面的信息是invalid WSGI2.0 response size: 1 .。 上述代码中已经修复(见1楼留言。) Thank You, Leonid.

文章分类 Programming, Python, Unix/Linux 标签: , , ,
2 comments on “nginx+uwsgi架设自用Mercurial hg仓库:hgwebdir
  1. Leonid Borisenko说道:

    Bug with missing static files could be fixed with following hgwebdir_wsgi.py:

    #——————-
    from mercurial import demandimport; demandimport.enable()
    from mercurial.hgweb.hgwebdir_mod import hgwebdir

    class hgwebdir_with_fixed_staticfile(hgwebdir):
    def __call__(self, env, respond):
    response = super(self.__class__, self).__call__(env, respond)
    return response[0] if isinstance(response, tuple) else response

    application = hgwebdir_with_fixed_staticfile(‘/var/www/hgwebdir/hgweb.config’)
    #——————-

    If you’ll use provided script, recompiling of uWSGI will not be needed.

    This is bug in hgwebdir. Static file content is wrapped in tuple but it must be returned as is. Tuple response is misleading uWSGI, so uWSGI mistaking it as (wrong) WSGI 2.0 response.

    Also, your uWSGI configuration file contains redundant options. By default, uWSGI init.d script adds some options automatically. ‘–master’, ‘–uid www-data’, ‘–gid www-data’, ‘–socket’ are among them. So you can remove , , , from uwsgi.xml. Socket will be placed at /var/run/uwsgi-python//socket (in your case — at /var/run/uwsgi-python/uwsgi/socket).

  2. BOYPT说道:

    @Leonid Borisenko Thank you Leonid, i updated my post with your code to avoid misleading others.

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*