Ability to run as daemon.

pull/7/head
Ferry Boender 10 years ago
parent 336dd0c541
commit f28fd0aa34
  1. 186
      src/scriptform.py

@ -24,6 +24,11 @@ import base64
import tempfile
import hashlib
import urlparse
import atexit
import signal
import time
import errno
import logging
html_header = '''<html>
@ -159,6 +164,7 @@ class ScriptForm:
else:
self.callbacks = {}
self.basepath = os.path.realpath(os.path.dirname(config_file))
self.log = logging.getLogger('SCRIPTFORM')
def get_form_config(self):
"""
@ -498,7 +504,9 @@ class WebSrv:
Very basic web server.
"""
def __init__(self, request_handler, listen_addr='', listen_port=80):
self.log = logging.getLogger('WEBSRV')
httpd = ThreadedHTTPServer((listen_addr, listen_port), request_handler)
self.log.info("Listening on {0}:{1}".format(listen_addr, listen_port))
httpd.serve_forever()
@ -839,25 +847,141 @@ class ScriptFormWebApp(WebAppHandler):
os.unlink(file_name)
def main_generate_pw(parser, options, args):
import getpass
plain_pw = getpass.getpass()
if not plain_pw == getpass.getpass('Repeat password: '):
sys.stderr.write("Passwords do not match.\n")
sys.exit(1)
print hashlib.sha256(plain_pw).hexdigest()
sys.exit(0)
class DaemonError(BaseException):
pass
def main_serve(parser, options, args):
if len(args) < 1:
parser.error("Insufficient number of arguments")
sf = ScriptForm(args[0])
sf.run(listen_port=options.port)
class Daemon:
"""
Daemonize the current process (detach it from the console).
"""
def __init__(self, pid_file, log_file=None, log_level=logging.INFO, foreground=False):
if pid_file is None:
self.pid_file = '{0}.pid'.format(os.path.basename(sys.argv[0]))
else:
self.pid_file = pid_file
if log_file is None:
self.log_file = '{0}.log'.format(os.path.basename(sys.argv[0]))
else:
self.log_file = log_file
self.foreground = foreground
logging.basicConfig(level=log_level,
format='%(asctime)s:%(name)s:%(levelname)s:%(message)s',
filename=self.log_file,
filemode='a')
self.log = logging.getLogger('DAEMON')
def start(self):
self.log.info("Starting")
if self.is_running():
self.log.error('Already running')
raise DaemonError("Already running")
if not self.foreground:
self._fork()
def stop(self):
if not self.is_running():
raise DaemonError("Not running")
pid = self.get_pid()
# Kill the daemon and wait until the process is gone
os.kill(pid, signal.SIGTERM)
for timeout in range(25): # 5 seconds
time.sleep(0.2)
if not self._pid_running(pid):
break
else:
self.log.error("Couldn't stop the daemon.")
def is_running(self):
"""
Check if the daemon is already running by looking at the PID file
"""
if self.get_pid() is None:
return False
else:
return True
def get_pid(self):
"""
Returns the PID of this daemon. If the daemon is not running (the PID
file does not exist or the PID in the PID file does not exist), returns
None.
"""
if not os.path.exists(self.pid_file):
return None
try:
pid = int(file(self.pid_file, 'r').read().strip())
except ValueError:
return None
if os.path.isdir('/proc/{0}/'.format(pid)):
return pid
else:
os.unlink(self.pid_file)
return None
def _pid_running(self, pid):
"""
Returns True if the PID is running, False otherwise
"""
try:
os.kill(pid, 0)
except OSError as err:
if err.errno == errno.ESRCH:
return False
return True
def _fork(self):
# Fork a child and end the parent (detach from parent)
pid = os.fork()
if pid > 0:
sys.exit(0) # End parent
# Change some defaults so the daemon doesn't tie up dirs, etc.
os.setsid()
os.umask(0)
# Fork a child and end parent (so init now owns process)
pid = os.fork()
if pid > 0:
self.log.info("PID = {0}".format(pid))
f = file(self.pid_file, 'w')
f.write(str(pid))
f.close()
sys.exit(0) # End parent
atexit.register(self._cleanup)
signal.signal(signal.SIGTERM, self._cleanup)
# Close STDIN, STDOUT and STDERR so we don't tie up the controlling
# terminal
for fd in (0, 1, 2):
try:
os.close(fd)
except OSError:
pass
# Reopen the closed file descriptors so other os.open() calls don't
# accidentally get tied to the stdin etc.
os.open("/dev/null", os.O_RDWR) # standard input (0)
os.dup2(0, 1) # standard output (1)
os.dup2(0, 2) # standard error (2)
return pid
def _cleanup(self, signal=None, frame=None):
self.log.info("Received signal {0}".format(signal))
if os.path.exists(self.pid_file):
os.unlink(self.pid_file)
if __name__ == "__main__":
usage = [
sys.argv[0] + " [option] <form_definition.json>",
sys.argv[0] + " [option] (--start|--stop) <form_definition.json>",
" " + sys.argv[0] + " --generate-pw",
]
parser = optparse.OptionParser()
@ -865,9 +989,39 @@ if __name__ == "__main__":
parser.add_option("-g", "--generate-pw", dest="generate_pw", action="store_true", default=False, help="Generate password")
parser.add_option("-p", "--port", dest="port", action="store", type="int", default=80, help="Port to listen on")
parser.add_option("-f", "--foreground", dest="foreground", action="store_true", default=False, help="Run in foreground (debugging)")
parser.add_option( "--pid-file", dest="pid_file", action="store", default=None, help="Pid file")
parser.add_option( "--log-file", dest="log_file", action="store", default=None, help="Log file")
parser.add_option( "--start", dest="action_start", action="store_true", default=None, help="Start daemon")
parser.add_option( "--stop", dest="action_stop", action="store_true", default=None, help="Stop daemon")
(options, args) = parser.parse_args()
if options.generate_pw:
main_generate_pw(parser, options, args)
# Generate a password for use in the `users` section
import getpass
plain_pw = getpass.getpass()
if not plain_pw == getpass.getpass('Repeat password: '):
sys.stderr.write("Passwords do not match.\n")
sys.exit(1)
print hashlib.sha256(plain_pw).hexdigest()
sys.exit(0)
else:
main_serve(parser, options, args)
if not options.action_stop and len(args) < 1:
parser.error("Insufficient number of arguments")
if not options.action_stop and not options.action_start:
options.action_start = True
daemon = Daemon(options.pid_file, options.log_file, foreground=options.foreground)
log = logging.getLogger('MAIN')
try:
if options.action_start:
sf = ScriptForm(args[0])
daemon.start()
#sf.run(listen_port=options.port)
time.sleep(10000)
elif options.action_stop:
daemon.stop()
sys.exit(0)
except Exception, e:
log.exception(e)
raise

Loading…
Cancel
Save