= Publishing Repositories Using CherryPy = === Introduction === For a really really simple way of publishing your Mercurial repositories on the Web without the need for Apache or IIS. Some quotes from the !CherryPy Web site: !CherryPy is a pythonic, object-oriented HTTP framework. !CherryPy powered web applications are in fact stand-alone Python applications embedding their own multi-threaded web server. !CherryPy applications run on Windows, Linux, Mac OS X and any other platform supporting Python. !CherryPy can run WSGI compliant applications like ~+`hgweb`+~ and ~+`hgwebdir`+~. === Pre-requisites === Python 2.4 or newer (http://www.python.org/download/) !CherryPy 3.1 or newer (http://www.cherrypy.org/) If you want authentication you will need Paste (http://pypi.python.org/pypi/Paste), I used version 1.7.2 Read PublishingRepositories and HgWebDirStepByStep first to get an understanding of ~+`hgweb`+~, ~+`hgwebdir`+~, the ~+`hgweb.config`+~ file and Mercurial ~+`Lib`+~ folder setup. == Running HgWebDir == Here is my sample code that uses the ~+`hgwebdir`+~ module. Edit this code to suit your needs: {{{ #!python # cphgwebdir.py # Adjust host and port to suit your Web presence: sUrlHost='0.0.0.0' iUrlPort=8080 # Adjust encoding to suit or comment out: import os os.environ['HGENCODING']='UTF-8' import sys # Adjust path to your Mercurial Lib folder: sys.path.append(r'C:\Program Files\Mercurial\Lib') from mercurial.hgweb.hgwebdir_mod import hgwebdir import cherrypy cherrypy.config.update({ # Default is development environment, uncomment below when in production #'environment':'production', 'server.socket_host':sUrlHost, 'server.socket_port':iUrlPort, #'log.access_file':'access.log', 'log.error_file':'error.log', 'log.screen':True }) # Use same hgweb.config file as for hgwebdir.cgi cherrypy.tree.graft(hgwebdir('hgweb.config'),script_name='/') cherrypy.engine.start() cherrypy.engine.block() }}} ~+`sUrlHost`+~ can be IP number or domain name (usually, depending on how your network is set up). Save this code into the same folder as ~+`hgweb.config`+~. Unless you have added a path, then save to the root of that path. Suggested filename is ~+`cphgwebdir.py`+~ From a command line just execute the file, like this from its folder: {{{ /path/to/python cphgwebdir.py }}} That's it. == Running HgWebDir with Digest Authentication == This is a modification of the above code. Edit this code to suit your needs: {{{ #!python # cphgwebdir.py with digest authentication import os import sys import cherrypy from paste.auth.digest import AuthDigestHandler, digest_password # Adjust host and port to suit your Web presence: sUrlHost='0.0.0.0' iUrlPort=8080 # Adjust encoding to suit or comment out: os.environ['HGENCODING']='UTF-8' # Adjust path to your Mercurial Lib folder: sys.path.append(r'C:\Program Files\Mercurial\Lib') from mercurial.hgweb.hgwebdir_mod import hgwebdir # Use same hgweb.config file as for hgwebdir.cgi WsgiApp=hgwebdir('hgweb.config') # Adjust realm to suit your needs Realm='Mercurial Repositories' # This is a sample dictionary of valid users with their digest encrypted password dAuth={} dAuth['Alice']=digest_password(Realm,'Alice','secret') dAuth['Dilbert']=digest_password(Realm,'Dilbert','secret') dAuth['Wally']=digest_password(Realm,'Wally','secret') #dAuth['']=digest_password('','','') def AuthFunc(environ,realm,username): return dAuth.get(username,None) WsgiApp=AuthDigestHandler(WsgiApp,Realm,AuthFunc) cherrypy.config.update({ # Default is development environment, uncomment below when in production #'environment':'production', 'server.socket_host':sUrlHost, 'server.socket_port':iUrlPort, #'log.access_file':'access.log', 'log.error_file':'error.log', 'log.screen':True }) cherrypy.tree.graft(WsgiApp,script_name='/') cherrypy.engine.start() cherrypy.engine.block() }}} Note: do not try ~+`tools.wsgiapp`+~ as it does not work in this case and has been deprecated, and will be removed in Cherrypy 3.2 == Running HgWeb == Use this if you don't want the fancy browser front-end. Here is my sample code that uses the ~+`hgweb`+~ module. Edit this code to suit your needs: {{{ #!python # cphgweb.py # Adjust host and port to suit your Web presence: sUrlHost='0.0.0.0' iUrlPort=8080 # Change this to represent your repository/ies: lRepos=[ #('',''), ('/project1',r'C:\Program Files\DemoRepos\project1'), ('/project2',r'C:\Program Files\DemoRepos\project2'), ('/project3',r'C:\Program Files\DemoRepos\project3') ] # Adjust encoding to suit or comment out: import os os.environ['HGENCODING']='UTF-8' import sys # Adjust path to your Mercurial Lib folder: sys.path.append(r'C:\Program Files\Mercurial\Lib') from mercurial.hgweb.hgweb_mod import hgweb import cherrypy cherrypy.config.update({ # Default is development environment, uncomment below when in production #'environment':'production', 'engine.autoreload.on':True, 'server.socket_host':sUrlHost, 'server.socket_port':iUrlPort, 'log.error_file':'error.log', 'log.screen':True }) for (sName,sPath) in lRepos: cherrypy.tree.graft(hgweb(sPath),script_name=sName) cherrypy.engine.start() cherrypy.engine.block() }}} ~+`sUrlHost`+~ can be IP number or domain name (usually, depending on how your network is set up). Change tuples in ~+`lRepos`+~ list to indicate the repositories that you want published, first element of each tuple must have a leading forward slash. Suggested filename is ~+`cphgweb.py`+~ From a command line just execute the file, like this from its folder: {{{ /path/to/python cphgweb.py }}} You can make changes to the ~+`lRepos`+~ list while it is running, saving changes will initiate a restart. Example command: {{{ hg clone "http://192.168.0.10:8080/project2" }}} == Running HgWebDir As a Windows Service == Here is some example code on how to run hgwebdir and cherrypy as a windows service. It is basically a combination of the above example and the example from cherry's web site: http://tools.cherrypy.org/wiki/WindowsService. Notice the assignments to sys.__stdout__ and sys.__stderr__ without them you'll get a 500 internal error back when running as a service. It will however run fine in debug mode. Took me a while to figure that out ;-) {{{ #!python # cphgwebdir.py URL_HOST = '127.0.0.1' URL_PORT = 4040 import sys import os import cherrypy import win32serviceutil import win32service import win32event os.environ['HGENCODING'] = 'UTF-8' # Change current working dir to the path where hgweb.config is located. os.chdir('D:/Projects/python/cphgweb') class MercurialService(win32serviceutil.ServiceFramework): """Windows Service""" _svc_name_ = "hgservice" _svc_display_name_ = "Mercurial Service" def __init__(self, args): win32serviceutil.ServiceFramework.__init__(self, args) # Create an event that SvcDoRun can wait on and SvcStop # can set. self.stop_event = win32event.CreateEvent(None, 0, 0, None) def SvcDoRun(self): from mercurial.hgweb.hgwebdir_mod import hgwebdir # Set __stdout__ and __stderr__ to avoid mercurial/hook.py from # throwing an OSError in the hook function when attempting to # redirect stdout to stderr. sys.__stdout__ = open('NUL', 'a+') sys.__stderr__ = open('NUL', 'a+') cherrypy.tree.graft(hgwebdir('hgweb.config'), script_name='/') cherrypy.config.update({ 'global':{ #'environment':'production', 'engine.autoreload_on': False, 'server.socket_host':URL_HOST, 'server.socket_port':URL_PORT, 'log.screen': False, 'log.error_file':'D:/Projects/python/cphgweb/error.log', 'engine.SIGHUP': None, 'engine.SIGTERM': None, } }) cherrypy.engine.start() # now, block until our event is set... win32event.WaitForSingleObject(self.stop_event, win32event.INFINITE) def SvcStop(self): self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) cherrypy.server.stop() win32event.SetEvent(self.stop_event) if __name__ == '__main__': win32serviceutil.HandleCommandLine(MercurialService) }}} Run: {{{ python cphgwebdir.py install python cphgwebdir.py start }}} And your service should be running. === SSL Problem with Windows Service === If you modify the above Windows Service code to add SSL support you may receive the following Windows error message after the service starts causing it to quickly fail: Application popup: PythonService.exe - Application Error : The instruction at "0x77fcb333" referenced memory at "0x00000000". The memory could not be "written". I have fixed this by adding the following code after ~+`import cherrypy`+~ : {{{ #!python from cherrypy import _cpwsgi_server# to fix PythonService.exe SSL problem }}} My implementation also includes the authentication code and runs under Win2000. ---- CategoryHowTo