|
|
|
@ -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 |
|
|
|
|