Re: Re: PyDS as a service

Rudy Schockaert <[email protected]> Mon, 29 Mar 2004 22:36:32 +0200
Newsgroups gmane.comp.pythin.pyds.devel
Message-ID <[email protected]>
I've got it working :-)

I attached the script to install and start the service.

I simply added the Environment variable
PYDS_HOME = G:\PyDS\.PyDS
to the system environment variables (requires a restart before it is 
seen by the system)
The service start fine now.

I will check out the other options also (USERPROFILE and 
HOMEDRIVE+HOMEPATH, but I'll have to reboot again first.
I'm not sure all people will like it that PyDS is located in their 
proile. An oversized PyDS wil slow down people having a roaming profile 
since it will be copied over to and from te server each time they log on 
or log off.

>Ah. Ok, you need some settings for PyDS to run - they might not be
>available if you run PyDS as a service under the Administrator account.
>Try to choose your normal user account, as that one should have all
>needed settings.
>
>But it might be the same problem as what I wrote just recently, that the
>environment settings for users are actually not the right ones as they
>are currently used by PyDS. The problem is, the current windows version
>is started by a command file that set's the home variable. This command
>file isn't used when you directly start it as a service.
>
>Hmm. We could switch to using %USERPROFILE% directly in
>PyDS/DefaultConfig.py - currently it uses this code:
>
># compute some special filenames and paths
>try:
>  HOMEDIR = os.environ['PYDS_HOME']
>except KeyError:
>  if sys.platform == 'win32':
>    try:
>      home = os.environ['HOME'] # for custom environments
>    except KeyError: # more likely default NT/2K/XP install
>      home = os.environ['HOMEDRIVE'] + os.environ['HOMEPATH']
>  else:
>    home = os.environ['HOME']
>  HOMEDIR = constructPath(home, '.PyDS')
>
>It could be changed like this:
>
># compute some special filenames and paths
>try:
>  HOMEDIR = os.environ['PYDS_HOME']
>except KeyError:
>  if sys.platform == 'win32':
>    try:
>      home = os.environ['HOME'] # for custom environments
>    except KeyError: # more likely default NT/2K/XP install
>      try:
>        home = os.environ['USERPROFILE']
>      except KeyError:
>        home = os.environ['HOMEDRIVE'] + os.environ['HOMEPATH']
>  else:
>    home = os.environ['HOME']
>  HOMEDIR = constructPath(home, '.PyDS')
>
>This would prefer USERPROFILE over HOMEDRIVE+HOMEPATH - so your PyDS
>installation stuff for your user would be in
>c:\System\Profiles\USERACCOUNT\.Toolserver\ - you might need to move
>your current installation there.
>
>It would be cool if you could check that out. I will put that into CVS
>if it works (including your patched pyds-win32.py, if you send that to
>the list).
>
>  
>
>>This is what I get in \PyDS\.PyDS\log\errors:
>>    
>>
>[...]
>  
>
>>  File "C:\Python23\lib\site-packages\PyDS\EventsTool.py", line 102,
>>    
>>
>in 
>  
>
>>getPref
>>    return getattr(self.prefs[0], pref, '')
>>AttributeError: EventsTool instance has no attribute 'prefs'
>>    
>>
>
>Hmmm. A rather weird one - looks like PyDS wanted to log an error before
>it was actually initialized. This might be due to the fact of PyDS not
>initializing correctly because of missing environment settings.
>
>bye, Georg
>
>
>
>  
>
PyDS-win32.py (text/plain, 10.7 KB)
##############################################################################
# 
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
# 
# Copyright (c) Digital Creations.  All rights reserved.
# 
# This license has been certified as Open Source(tm).
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
# 1. Redistributions in source code must retain the above copyright
#    notice, this list of conditions, and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions, and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 
# 3. Digital Creations requests that attribution be given to Zope
#    in any manner possible. Zope includes a "Powered by Zope"
#    button that is installed by default. While it is not a license
#    violation to remove this button, it is requested that the
#    attribution remain. A significant investment has been put
#    into Zope, and this effort will continue if the Zope community
#    continues to grow. This is one way to assure that growth.
# 
# 4. All advertising materials and documentation mentioning
#    features derived from or use of this software must display
#    the following acknowledgement:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    In the event that the product being advertised includes an
#    intact Zope distribution (with copyright and license included)
#    then this clause is waived.
# 
# 5. Names associated with Zope or Digital Creations must not be used to
#    endorse or promote products derived from this software without
#    prior written permission from Digital Creations.
# 
# 6. Modified redistributions of any form whatsoever must retain
#    the following acknowledgment:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    Intact (re-)distributions of any official Zope release do not
#    require an external acknowledgement.
# 
# 7. Modifications are encouraged but must be packaged separately as
#    patches to official Zope releases.  Distributions that do not
#    clearly separate the patches from the original work must be clearly
#    labeled as unofficial distributions.  Modifications which do not
#    carry the name Zope may be packaged in any form, as long as they
#    conform to all of the clauses above.
# 
# 
# Disclaimer
# 
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
#   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
#   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
#   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#   SUCH DAMAGE.
# 
# 
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations.  Specific
# attributions are listed in the accompanying credits file.
# 
##############################################################################
"""
PyDS as a NT service.

This script is based on the ZService.py script from the Zope
distribution. Thanks to the Zope Corp for this little gem.

The service starts up and monitors a PyDS process.

Features:
  
  * When you start the service it starts PyDS
  * When you stop the serivice it stops PyDS
  * It monitors PyDS and restarts it if it exits abnormally
  * If PyDS is shutdown from the web, the service stops.
  * If PyDS cannot be restarted, the service stops.

Usage:

  Installation
  
    You can manually install, uninstall the service from
    the commandline.
    
      PyDS-win32.py [options] install|update|remove|start [...]
          |stop|restart [...]|debug [...]

    Options for 'install' and 'update' commands only:
    
     --username domain\username : The Username the service is to run
                                  under
     
     --password password : The password for the username
     
     --startup [manual|auto|disabled] : How the service starts, 
                                        default = manual
  
    Commands
           
      install : Installs the service
     
      update : Updates the service, use this when you update
               PyDS
     
      remove : Removes the service
     
      start : Starts the service, this can also be done from the
              services control panel
     
      stop : Stops the service, this can also be done from the
             services control panel
     
      restart : Restarts the service
     
      debug : Runs the service in debug mode
  
  Starting PyDS
  
    Start PyDS by clicking the 'start' button in the services control
    panel. You can set PyDS to automatically start at boot time by
    choosing 'Auto' startup by clicking the 'statup' button.
  
  Stopping PyDS
  
    Stop PyDS by clicking the 'stop' button in the services control
    panel. You can also stop PyDS through the web by going to the
    PyDS control panel and by clicking 'Shutdown'. 
  
  Event logging
  
    Service events are logged to the NT application event log. Use the
    event viewer to keep track of service events.

  Registry Settings
  
    You can change how the service starts PyDS by editing a registry
    key.
    
      HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\
        <Service Name>\Parameters\start

    The value of this key is the command which the service uses to
    start PyDS. For example:
    
      "C:\Program Files\Python\python.exe"
        "C:\Program Files\Python\bin\pyds-start" -v

    
This script does for NT the same sort of thing zdaemon.py does for UNIX.
Requires Python win32api extensions.
"""
import sys, os, string, time, imp

# Some fancy path footwork is required here because we
# may be run from python.exe or lib/win32/PythonService.exe

home=os.path.split(os.path.split(sys.executable)[0])[0]
if sys.executable[-10:]!='python.exe':
    home=os.path.split(home)[0]
    home=os.path.split(home)[0]
sys.path.append(os.path.join(home, 'bin'))
sys.path.append(os.path.join(home, 'bin', 'lib', 'win32'))
sys.path.append(os.path.join(home, 'bin', 'lib', 'win32', 'lib'))


# pythoncom and pywintypes are special, and require these hacks when
# we dont have a standard Python installation around.

import win32api
def magic_import(modulename, filename):
    # by Mark Hammond
    try:
        # See if it does import first!
        return __import__(modulename)
    except ImportError:
        pass
    # win32 can find the DLL name.
    h = win32api.LoadLibrary(filename)
    found = win32api.GetModuleFileName(h)
    # Python can load the module
    mod = imp.load_module(modulename, None, found, ('.dll', 'rb', imp.C_EXTENSION))
    # inject it into the global module list.
    sys.modules[modulename] = mod
    # And finally inject it into the namespace.
    globals()[modulename] = mod
    win32api.FreeLibrary(h)

magic_import('pywintypes','pywintypes21.dll')

import win32serviceutil, win32service, win32event, win32process
try: import servicemanager
except: pass

class PyDSService(win32serviceutil.ServiceFramework):

    # Some trickery to determine the service name. The WISE
    # installer will write an svcname.txt to the PyDS dir
    # that we can use to figure out our service name.

    _svc_name_ = 'PyDS'
    _svc_display_name_ = "Python Desktop Server (%s)" % _svc_name_
    
    restart_min_time=5 # if the server restarts before this many
                       # seconds then we have a problem, and
                       # need to stop the service.
    
    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
    
    def SvcDoRun(self):
        self.start_zserver()
        while 1:
            rc=win32event.WaitForMultipleObjects(
                    (self.hWaitStop, self.hPyDS), 0, win32event.INFINITE)
            if rc - win32event.WAIT_OBJECT_0 == 0:
                break
            else:
                self.restart_zserver()
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING, 5000)   

    def SvcStop(self):
        servicemanager.LogInfoMsg('Stopping PyDS.') 
        try:
            self.stop_zserver()              
        except:
            pass
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)

    def start_zserver(self):
        sc=self.get_start_command()
        result=win32process.CreateProcess(None, self.get_start_command(),
                None, None, 0, 0, None, None, win32process.STARTUPINFO())
        self.hPyDS=result[0]
        self.last_start_time=time.time()
        servicemanager.LogInfoMsg('Starting PyDS.')
        
    def stop_zserver(self):
        win32process.TerminateProcess(self.hPyDS,0)
        
    def restart_zserver(self):
        if time.time() - self.last_start_time < self.restart_min_time:
            servicemanager.LogErrorMsg('PyDS died and could not be restarted.')
            self.SvcStop()
        code=win32process.GetExitCodeProcess(self.hPyDS)
        if code == 0:
            # Exited with a normal status code,
            # assume that shutdown is intentional.
            self.SvcStop()
        else:
            servicemanager.LogWarningMsg('Restarting PyDS.')
            self.start_zserver()

    def get_start_command(self):
        return win32serviceutil.GetServiceCustomOption(self,'start')
        
        
def set_start_command(value):
    "sets the PyDS start command if the start command is not already set"
    current=win32serviceutil.GetServiceCustomOption(PyDSService,
                                                    'start', None)
    if current is None:
        win32serviceutil.SetServiceCustomOption(PyDSService,'start',value)
        return 1
    return 0
            

if __name__=='__main__':
    win32serviceutil.HandleCommandLine(PyDSService)
    if 'install' in sys.argv:
        command='"%s" "%s"' % (sys.executable, os.path.join(os.path.dirname(sys.executable), 'scripts', 'pyds-start'))
        if set_start_command(command):
            print "Setting PyDS start command to:", command