diff --git a/VERSION b/VERSION index c87a46e0..74ae29e3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 1.99.4 (2012-01-16 23:25:02) stable +Version 1.99.4 (2012-01-17 21:50:01) stable diff --git a/scripts/service/linux.py b/scripts/service/linux.py new file mode 100644 index 00000000..eece4801 --- /dev/null +++ b/scripts/service/linux.py @@ -0,0 +1,221 @@ +from service import ServiceBase +import os, sys, time, subprocess, atexit +from signal import SIGTERM + +class LinuxService(ServiceBase): + def __init__(self, name, label, stdout='/dev/null', stderr='/dev/null'): + ServiceBase.__init__(self, name, label, stdout, stderr) + self.pidfile = '/tmp/%s.pid' % name + self.config_file = '/etc/%s.conf' % name + + def daemonize(self): + """ + do the UNIX double-fork magic, see Stevens' "Advanced + Programming in the UNIX Environment" for details (ISBN 0201563177) + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + """ + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError, e: + sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) + return + + # decouple from parent environment + os.chdir("/") + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent + sys.exit(0) + except OSError, e: + sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) + return + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = file('/dev/null', 'r') + so = file(self.stdout or '/dev/null', 'a+') + se = file(self.stderr or '/dev/null', 'a+') + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + def getpid(self): + # Check for a pidfile to see if the daemon already runs + try: + pf = file(self.pidfile,'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + return pid + + def status(self): + pid = self.getpid() + if pid: + return 'Service running with PID %s.' % pid + else: + return 'Service is not running.' + + def check_permissions(self): + if not os.geteuid() == 0: + return (False, 'This script must be run with root permissions.') + else: + return (True, '') + + def start(self): + """ + Start the daemon + """ + pid = self.getpid() + + if pid: + message = "Service already running under PID %s\n" + sys.stderr.write(message % self.pidfile) + return + + # Start the daemon + self.daemonize() + self.run() + + def stop(self): + """ + Stop the daemon + """ + pid = self.getpid() + + if not pid: + message = "Service is not running\n" + sys.stderr.write(message) + return # not an error in a restart + + # Try killing the daemon process + try: + while 1: + os.kill(pid, SIGTERM) + time.sleep(0.5) + except OSError, err: + err = str(err) + if err.find("No such process") > 0: + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + else: + print str(err) + return + + def restart(self): + """ + Restart the daemon + """ + self.stop() + self.start() + + def run(self): + atexit.register(self.terminate) + + args = self.load_configuration()[0] + stdout = open(self.stdout, 'a+') + stderr = open(self.stderr, 'a+') + process = subprocess.Popen(args, stdout=stdout, stderr=stderr) + file(self.pidfile,'w+').write("%s\n" % process.pid) + process.wait() + + self.terminate() + + def terminate(self): + try: + os.remove(self.pidfile) + except: + pass + + def install(self): + env = self.detect_environment() + src_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'service.py') + + # make sure this script is executable + self.run_command('chmod', '+x', src_path) + + # link this daemon to the service directory + dest_path = env['rc.d-path'] + self.name + os.symlink(src_path, dest_path) + + # start the service at boot + install_command = self.get_service_installer_command(env) + result = self.run_command(*install_command) + self.start() + + def uninstall(self): + self.stop() + env = self.detect_environment() + + # stop the service from autostarting + uninstall_command = self.get_service_uninstaller_command(env) + result = self.run_command(*uninstall_command) + + # remove link to the script from the service directory + path = env['rc.d-path'] + self.name + os.remove(path) + + def detect_environment(self): + """ + Returns a dictionary of command/path to the required command-line applications. + One key is 'dist' which will either be 'debian' or 'redhat', which is the best + guess as to which Linux distribution the current system is based on. + """ + check_for = [ + 'chkconfig', + 'service', + 'update-rc.d', + 'rpm', + 'dpkg', + ] + + env = dict() + for cmd in check_for: + result = self.run_command('which', cmd) + if result[0]: + env[cmd] = result[0].replace('\n', '') + + if 'rpm' in env: + env['dist'] = 'redhat' + env['rc.d-path'] = '/etc/rc.d/init.d/' + elif 'dpkg' in env: + env['dist'] = 'debian' + env['rc.d-path'] = '/etc/init.d/' + else: + env['dist'] = 'unknown' + env['rc.d-path'] = '/dev/null/' + + return env + + def get_service_installer_command(self, env): + """ + Returns list of args required to set a service to run on boot. + """ + if env['dist'] == 'redhat': + cmd = env['chkconfig'] + return [cmd, self.name, 'on'] + else: + cmd = env['update-rc.d'] + return [cmd, self.name, 'defaults'] + + def get_service_uninstaller_command(self, env): + """ + Returns list of arge required to stop a service from running at boot. + """ + if env['dist'] == 'redhat': + cmd = env['chkconfig'] + return [cmd, self.name, 'off'] + else: + cmd = env['update-rc.d'] + return [cmd, self.name, 'remove'] + diff --git a/scripts/service/service.py b/scripts/service/service.py new file mode 100644 index 00000000..e97eeab3 --- /dev/null +++ b/scripts/service/service.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python + +import sys, os, time, subprocess + +class Base: + def run_command(self, *args): + """ + Returns the output of a command as a tuple (output, error). + """ + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return p.communicate() + +class ServiceBase(Base): + def __init__(self, name, label, stdout=None, stderr=None): + self.name = name + self.label = label + self.stdout = stdout + self.stderr = stderr + self.config_file = None + + def load_configuration(self): + """ + Loads the configuration required to build the command-line string + for running web2py. Returns a tuple (command_args, config_dict). + """ + s = os.path.sep + + default = dict( + python = 'python', + web2py = os.path.join(s.join(__file__.split(s)[:-3]), 'web2py.py'), + http_enabled = True, + http_ip = '0.0.0.0', + http_port = 8000, + https_enabled = True, + https_ip = '0.0.0.0', + https_port = 8001, + https_key = '', + https_cert = '', + password = '', + ) + + config = default + if self.config_file: + try: + f = open(self.config_file, 'r') + lines = f.readlines() + f.close() + + for line in lines: + fields = line.split('=', 1) + if len(fields) == 2: + key, value = fields + key = key.strip() + value = value.strip() + config[key] = value + except: + pass + + web2py_path = os.path.dirname(config['web2py']) + os.chdir(web2py_path) + + args = [config['python'], config['web2py']] + interfaces = [] + ports = [] + + if config['http_enabled']: + ip = config['http_ip'] + port = config['http_port'] + interfaces.append('%s:%s' % (ip, port)) + ports.append(port) + if config['https_enabled']: + ip = config['https_ip'] + port = config['https_port'] + key = config['https_key'] + cert = config['https_cert'] + if key != '' and cert != '': + interfaces.append('%s:%s:%s:%s' % (ip, port, cert, key)) + ports.append(ports) + if len(interfaces) == 0: + sys.exit('Configuration error. Must have settings for http and/or https') + + password = config['password'] + if not password == '': + from gluon import main + for port in ports: + main.save_password(password, port) + + password = '' + + args.append('-a "%s"' % password) + + interfaces = ';'.join(interfaces) + args.append('--interfaces=%s' % interfaces) + + if 'log_filename' in config.key(): + log_filename = config['log_filename'] + args.append('--log_filename=%s' % log_filename) + + return (args, config) + + def start(self): + pass + + def stop(self): + pass + + def restart(self): + pass + + def status(self): + pass + + def run(self): + pass + + def install(self): + pass + + def uninstall(self): + pass + + def check_permissions(self): + """ + Does the script have permissions to install, uninstall, start, and stop services? + Return value must be a tuple (True/False, error_message_if_False). + """ + return (False, 'Permissions check not implemented') + +class WebServerBase(Base): + def install(self): + pass + + def uninstall(self): + pass + + +def get_service(): + service_name = 'web2py' + service_label = 'web2py Service' + + if sys.platform == 'linux2': + from linux import LinuxService as Service + elif sys.platform == 'darwin': + # from mac import MacService as Service + sys.exit('Mac OS X is not yet supported.\n') + elif sys.platform == 'win32': + # from windows import WindowsService as Service + sys.exit('Windows is not yet supported.\n') + else: + sys.exit('The following platform is not supported: %s.\n' % sys.platform) + + service = Service(service_name, service_label) + return service + +if __name__ == '__main__': + service = get_service() + is_root, error_message = service.check_permissions() + if not is_root: + sys.exit(error_message) + + if len(sys.argv) >= 2: + command = sys.argv[1] + if command == 'start': + service.start() + elif command == 'stop': + service.stop() + elif command == 'restart': + service.restart() + elif command == 'status': + print service.status() + '\n' + elif command == 'run': + service.run() + elif command == 'install': + service.install() + elif command == 'uninstall': + service.uninstall() + elif command == 'install-apache': + # from apache import Apache + # server = Apache() + # server.install() + sys.exit('Configuring Apache is not yet supported.\n') + elif command == 'uninstall-apache': + # from apache import Apache + # server = Apache() + # server.uninstall() + sys.exit('Configuring Apache is not yet supported.\n') + else: + sys.exit('Unknown command: %s' % command) + else: + print 'Usage: %s [command] \n' % sys.argv[0] + \ + '\tCommands:\n' + \ + '\t\tstart Starts the service\n' + \ + '\t\tstop Stop the service\n' + \ + '\t\trestart Restart the service\n' + \ + '\t\tstatus Check if the service is running\n' + \ + '\t\trun Run service is blocking mode\n' + \ + '\t\t (Press Ctrl + C to exit)\n' + \ + '\t\tinstall Install the service\n' + \ + '\t\tuninstall Uninstall the service\n' + \ + '\t\tinstall-apache Install as an Apache site\n' + \ + '\t\tuninstall-apache Uninstall from Apache\n'