mirror of
https://github.com/SystemRage/py-kms.git
synced 2024-11-22 08:15:38 +01:00
Added daemonization option
improved options parsing
This commit is contained in:
parent
cbfe1511a2
commit
a497d8ddb2
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,5 +1,9 @@
|
|||||||
# App files
|
# App files
|
||||||
pykms_logserver.log*
|
pykms_logserver.log*
|
||||||
|
pykms_logclient.log*
|
||||||
|
pykms_newlines.txt*
|
||||||
|
pykms_config.pickle*
|
||||||
|
etrigan.log*
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
610
py-kms/Etrigan.py
Normal file
610
py-kms/Etrigan.py
Normal file
@ -0,0 +1,610 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import atexit
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import signal
|
||||||
|
import logging
|
||||||
|
import argparse
|
||||||
|
from collections import Sequence
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
__license__ = "MIT License"
|
||||||
|
__author__ = u"Matteo ℱan <SystemRage@protonmail.com>"
|
||||||
|
__copyright__ = "© Copyright 2020"
|
||||||
|
__url__ = "https://github.com/SystemRage/Etrigan"
|
||||||
|
__description__ = "Etrigan: a python daemonizer that rocks."
|
||||||
|
|
||||||
|
path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
class Etrigan(object):
|
||||||
|
"""
|
||||||
|
Daemonizer based on double-fork method
|
||||||
|
--------------------------------------
|
||||||
|
Each option can be passed as a keyword argument or modified by assigning
|
||||||
|
to an attribute on the instance:
|
||||||
|
|
||||||
|
jasonblood = Etrigan(pidfile,
|
||||||
|
argument_example_1 = foo,
|
||||||
|
argument_example_2 = bar)
|
||||||
|
|
||||||
|
that is equivalent to:
|
||||||
|
|
||||||
|
jasonblood = Etrigan(pidfile)
|
||||||
|
jasonblood.argument_example_1 = foo
|
||||||
|
jasonblood.argument_example_2 = bar
|
||||||
|
|
||||||
|
Object constructor expects always `pidfile` argument.
|
||||||
|
`pidfile`
|
||||||
|
Path to the pidfile.
|
||||||
|
|
||||||
|
The following other options are defined:
|
||||||
|
`stdin`
|
||||||
|
`stdout`
|
||||||
|
`stderr`
|
||||||
|
:Default: `os.devnull`
|
||||||
|
File objects used as the new file for the standard I/O streams
|
||||||
|
`sys.stdin`, `sys.stdout`, and `sys.stderr` respectively.
|
||||||
|
|
||||||
|
`funcs_to_daemonize`
|
||||||
|
:Default: `[]`
|
||||||
|
Define a list of your custom functions
|
||||||
|
which will be executed after daemonization.
|
||||||
|
If None, you have to subclass Etrigan `run` method.
|
||||||
|
Note that these functions can return elements that will be
|
||||||
|
added to Etrigan object (`etrigan_add` list) so the other subsequent
|
||||||
|
ones can reuse them for further processing.
|
||||||
|
You only have to provide indexes of `etrigan_add` list,
|
||||||
|
(an int (example: 2) for single index or a string (example: '1:4') for slices)
|
||||||
|
as first returning element.
|
||||||
|
|
||||||
|
`want_quit`
|
||||||
|
:Default: `False`
|
||||||
|
If `True`, runs Etrigan `quit_on_start` or `quit_on_stop`
|
||||||
|
lists of your custom functions at the end of `start` or `stop` operations.
|
||||||
|
These can return elements as `funcs_to_daemonize`.
|
||||||
|
|
||||||
|
`logfile`
|
||||||
|
:Default: `None`
|
||||||
|
Path to the output log file.
|
||||||
|
|
||||||
|
`loglevel`
|
||||||
|
:Default: `None`
|
||||||
|
Set the log level of logging messages.
|
||||||
|
|
||||||
|
`mute`
|
||||||
|
:Default: `False`
|
||||||
|
Disable all stdout and stderr messages (before double forking).
|
||||||
|
|
||||||
|
`pause_loop`
|
||||||
|
:Default: `None`
|
||||||
|
Seconds of pause between the calling, in an infinite loop,
|
||||||
|
of every function in `funcs_to_daemonize` list.
|
||||||
|
If `-1`, no pause between the calling, in an infinite loop,
|
||||||
|
of every function in `funcs_to_daemonize` list.
|
||||||
|
If `None`, only one run (no infinite loop) of functions in
|
||||||
|
`funcs_to_daemonize` list, without pause.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, pidfile,
|
||||||
|
stdin = os.devnull, stdout = os.devnull, stderr = os.devnull,
|
||||||
|
funcs_to_daemonize = [], want_quit = False,
|
||||||
|
logfile = None, loglevel = None,
|
||||||
|
mute = False, pause_loop = None):
|
||||||
|
|
||||||
|
self.pidfile = pidfile
|
||||||
|
self.funcs_to_daemonize = funcs_to_daemonize
|
||||||
|
self.stdin = stdin
|
||||||
|
self.stdout = stdout
|
||||||
|
self.stderr = stderr
|
||||||
|
self.logfile = logfile
|
||||||
|
self.loglevel = loglevel
|
||||||
|
self.mute = mute
|
||||||
|
self.want_quit = want_quit
|
||||||
|
self.pause_loop = pause_loop
|
||||||
|
# internal only.
|
||||||
|
self.homedir = '/'
|
||||||
|
self.umask = 0o22
|
||||||
|
self.etrigan_restart, self.etrigan_reload = (False for _ in range(2))
|
||||||
|
self.etrigan_alive = True
|
||||||
|
self.etrigan_add = []
|
||||||
|
self.etrigan_index = None
|
||||||
|
# seconds of pause between stop and start during the restart of the daemon.
|
||||||
|
self.pause_restart = 5
|
||||||
|
# when terminate a process, seconds to wait until kill the process with signal.
|
||||||
|
# self.pause_kill = 3
|
||||||
|
|
||||||
|
# create logfile.
|
||||||
|
self.setup_files()
|
||||||
|
|
||||||
|
def handle_terminate(self, signum, frame):
|
||||||
|
if os.path.exists(self.pidfile):
|
||||||
|
self.etrigan_alive = False
|
||||||
|
# eventually run quit (on stop) function/s.
|
||||||
|
if self.want_quit:
|
||||||
|
if not isinstance(self.quit_on_stop, (list, tuple)):
|
||||||
|
self.quit_on_stop = [self.quit_on_stop]
|
||||||
|
self.execute(self.quit_on_stop)
|
||||||
|
# then always run quit standard.
|
||||||
|
self.quit_standard()
|
||||||
|
else:
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, "Failed to stop the daemon process: can't find PIDFILE '%s'" %self.pidfile)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def handle_reload(self, signum, frame):
|
||||||
|
self.etrigan_reload = True
|
||||||
|
|
||||||
|
def setup_files(self):
|
||||||
|
self.pidfile = os.path.abspath(self.pidfile)
|
||||||
|
|
||||||
|
if self.logfile is not None:
|
||||||
|
self.logdaemon = logging.getLogger('logdaemon')
|
||||||
|
self.logdaemon.setLevel(self.loglevel)
|
||||||
|
|
||||||
|
filehandler = logging.FileHandler(self.logfile)
|
||||||
|
filehandler.setLevel(self.loglevel)
|
||||||
|
formatter = logging.Formatter(fmt = '[%(asctime)s] [%(levelname)8s] --- %(message)s',
|
||||||
|
datefmt = '%Y-%m-%d %H:%M:%S')
|
||||||
|
filehandler.setFormatter(formatter)
|
||||||
|
self.logdaemon.addHandler(filehandler)
|
||||||
|
else:
|
||||||
|
nullhandler = logging.NullHandler()
|
||||||
|
self.logdaemon.addHandler(nullhandler)
|
||||||
|
|
||||||
|
def emit_error(self, message, to_exit = True):
|
||||||
|
""" Print an error message to STDERR. """
|
||||||
|
if not self.mute:
|
||||||
|
sys.stderr.write(message + '\n')
|
||||||
|
sys.stderr.flush()
|
||||||
|
if to_exit:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def emit_message(self, message, to_exit = False):
|
||||||
|
""" Print a message to STDOUT. """
|
||||||
|
if not self.mute:
|
||||||
|
sys.stdout.write(message + '\n')
|
||||||
|
sys.stdout.flush()
|
||||||
|
if to_exit:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def view(self, logobj, emitobj, msg, **kwargs):
|
||||||
|
options = {'to_exit' : False,
|
||||||
|
'silent' : False
|
||||||
|
}
|
||||||
|
options.update(kwargs)
|
||||||
|
|
||||||
|
if logobj:
|
||||||
|
logobj(msg)
|
||||||
|
if emitobj:
|
||||||
|
if not options['silent']:
|
||||||
|
emitobj(msg, to_exit = options['to_exit'])
|
||||||
|
|
||||||
|
def daemonize(self):
|
||||||
|
"""
|
||||||
|
Double-forks the process to daemonize the script.
|
||||||
|
see Stevens' "Advanced Programming in the UNIX Environment" for details (ISBN 0201563177)
|
||||||
|
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
|
||||||
|
"""
|
||||||
|
self.view(self.logdaemon.debug, None, "Attempting to daemonize the process...")
|
||||||
|
|
||||||
|
# First fork.
|
||||||
|
self.fork(msg = "First fork")
|
||||||
|
# Decouple from parent environment.
|
||||||
|
self.detach()
|
||||||
|
# Second fork.
|
||||||
|
self.fork(msg = "Second fork")
|
||||||
|
# Write the PID file.
|
||||||
|
self.create_pidfile()
|
||||||
|
self.view(self.logdaemon.info, self.emit_message, "The daemon process has started.")
|
||||||
|
# Redirect standard file descriptors.
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
self.attach('stdin', mode = 'r')
|
||||||
|
self.attach('stdout', mode = 'a+')
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.attach('stderr', mode = 'a+', buffering = 0)
|
||||||
|
except ValueError:
|
||||||
|
# Python 3 can't have unbuffered text I/O.
|
||||||
|
self.attach('stderr', mode = 'a+', buffering = 1)
|
||||||
|
|
||||||
|
# Handle signals.
|
||||||
|
signal.signal(signal.SIGINT, self.handle_terminate)
|
||||||
|
signal.signal(signal.SIGTERM, self.handle_terminate)
|
||||||
|
signal.signal(signal.SIGHUP, self.handle_reload)
|
||||||
|
#signal.signal(signal.SIGKILL....)
|
||||||
|
|
||||||
|
def fork(self, msg):
|
||||||
|
try:
|
||||||
|
pid = os.fork()
|
||||||
|
if pid > 0:
|
||||||
|
self.view(self.logdaemon.debug, None, msg + " success with PID %d." %pid)
|
||||||
|
# Exit from parent.
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception as e:
|
||||||
|
msg += " failed: %s." %str(e)
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, msg)
|
||||||
|
|
||||||
|
def detach(self):
|
||||||
|
# cd to root for a guarenteed working dir.
|
||||||
|
try:
|
||||||
|
os.chdir(self.homedir)
|
||||||
|
except Exception as e:
|
||||||
|
msg = "Unable to change working directory: %s." %str(e)
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, msg)
|
||||||
|
|
||||||
|
# clear the session id to clear the controlling tty.
|
||||||
|
pid = os.setsid()
|
||||||
|
if pid == -1:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# set the umask so we have access to all files created by the daemon.
|
||||||
|
try:
|
||||||
|
os.umask(self.umask)
|
||||||
|
except Exception as e:
|
||||||
|
msg = "Unable to change file creation mask: %s." %str(e)
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, msg)
|
||||||
|
|
||||||
|
def attach(self, name, mode, buffering = -1):
|
||||||
|
with open(getattr(self, name), mode, buffering) as stream:
|
||||||
|
os.dup2(stream.fileno(), getattr(sys, name).fileno())
|
||||||
|
|
||||||
|
def checkfile(self, path, typearg, typefile):
|
||||||
|
filename = os.path.basename(path)
|
||||||
|
pathname = os.path.dirname(path)
|
||||||
|
if not os.path.isdir(pathname):
|
||||||
|
msg = "argument %s: invalid directory: '%s'. Exiting..." %(typearg, pathname)
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, msg)
|
||||||
|
elif not filename.lower().endswith(typefile):
|
||||||
|
msg = "argument %s: not a %s file, invalid extension: '%s'. Exiting..." %(typearg, typefile, filename)
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, msg)
|
||||||
|
|
||||||
|
def create_pidfile(self):
|
||||||
|
atexit.register(self.delete_pidfile)
|
||||||
|
pid = os.getpid()
|
||||||
|
try:
|
||||||
|
with open(self.pidfile, 'w+') as pf:
|
||||||
|
pf.write("%s\n" %pid)
|
||||||
|
self.view(self.logdaemon.debug, None, "PID %d written to '%s'." %(pid, self.pidfile))
|
||||||
|
except Exception as e:
|
||||||
|
msg = "Unable to write PID to PIDFILE '%s': %s" %(self.pidfile, str(e))
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, msg)
|
||||||
|
|
||||||
|
def delete_pidfile(self, pid):
|
||||||
|
# Remove the PID file.
|
||||||
|
try:
|
||||||
|
os.remove(self.pidfile)
|
||||||
|
self.view(self.logdaemon.debug, None, "Removing PIDFILE '%s' with PID %d." %(self.pidfile, pid))
|
||||||
|
except Exception as e:
|
||||||
|
if e.errno != errno.ENOENT:
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, str(e))
|
||||||
|
|
||||||
|
def get_pidfile(self):
|
||||||
|
# Get the PID from the PID file.
|
||||||
|
if self.pidfile is None:
|
||||||
|
return None
|
||||||
|
if not os.path.isfile(self.pidfile):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.pidfile, 'r') as pf:
|
||||||
|
pid = int(pf.read().strip())
|
||||||
|
self.view(self.logdaemon.debug, None, "Found PID %d in PIDFILE '%s'" %(pid, self.pidfile))
|
||||||
|
except Exception as e:
|
||||||
|
self.view(self.logdaemon.warning, None, "Empty or broken PIDFILE")
|
||||||
|
pid = None
|
||||||
|
|
||||||
|
def pid_exists(pid):
|
||||||
|
# psutil _psposix.py.
|
||||||
|
if pid == 0:
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
os.kill(pid, 0)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ESRCH:
|
||||||
|
return False
|
||||||
|
elif e.errno == errno.EPERM:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, str(e))
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if pid is not None and pid_exists(pid):
|
||||||
|
return pid
|
||||||
|
else:
|
||||||
|
# Remove the stale PID file.
|
||||||
|
self.delete_pidfile(pid)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
""" Start the daemon. """
|
||||||
|
self.view(self.logdaemon.info, self.emit_message, "Starting the daemon process...", silent = self.etrigan_restart)
|
||||||
|
|
||||||
|
# Check for a PID file to see if the Daemon is already running.
|
||||||
|
pid = self.get_pidfile()
|
||||||
|
if pid is not None:
|
||||||
|
msg = "A previous daemon process with PIDFILE '%s' already exists. Daemon already running ?" %self.pidfile
|
||||||
|
self.view(self.logdaemon.warning, self.emit_error, msg, to_exit = False)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Daemonize the main process.
|
||||||
|
self.daemonize()
|
||||||
|
# Start a infinitive loop that periodically runs `funcs_to_daemonize`.
|
||||||
|
self.loop()
|
||||||
|
# eventualy run quit (on start) function/s.
|
||||||
|
if self.want_quit:
|
||||||
|
if not isinstance(self.quit_on_start, (list, tuple)):
|
||||||
|
self.quit_on_start = [self.quit_on_start]
|
||||||
|
self.execute(self.quit_on_start)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
""" Stop the daemon. """
|
||||||
|
self.view(None, self.emit_message, "Stopping the daemon process...", silent = self.etrigan_restart)
|
||||||
|
|
||||||
|
self.logdaemon.disabled = True
|
||||||
|
pid = self.get_pidfile()
|
||||||
|
self.logdaemon.disabled = False
|
||||||
|
if not pid:
|
||||||
|
# Just to be sure. A ValueError might occur
|
||||||
|
# if the PIDFILE is empty but does actually exist.
|
||||||
|
if os.path.exists(self.pidfile):
|
||||||
|
self.delete_pidfile(pid)
|
||||||
|
|
||||||
|
msg = "Can't find the daemon process with PIDFILE '%s'. Daemon not running ?" %self.pidfile
|
||||||
|
self.view(self.logdaemon.warning, self.emit_error, msg, to_exit = False)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Try to kill the daemon process.
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
os.kill(pid, signal.SIGTERM)
|
||||||
|
time.sleep(0.1)
|
||||||
|
except Exception as e:
|
||||||
|
if (e.errno != errno.ESRCH):
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, "Failed to stop the daemon process: %s" %str(e))
|
||||||
|
else:
|
||||||
|
self.view(None, self.emit_message, "The daemon process has ended correctly.", silent = self.etrigan_restart)
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
""" Restart the daemon. """
|
||||||
|
self.view(self.logdaemon.info, self.emit_message, "Restarting the daemon process...")
|
||||||
|
self.etrigan_restart = True
|
||||||
|
self.stop()
|
||||||
|
if self.pause_restart:
|
||||||
|
time.sleep(self.pause_restart)
|
||||||
|
self.etrigan_alive = True
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def status(self):
|
||||||
|
""" Get status of the daemon. """
|
||||||
|
self.view(self.logdaemon.info, self.emit_message, "Viewing the daemon process status...")
|
||||||
|
|
||||||
|
if self.pidfile is None:
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, "Cannot get the status of daemon without PIDFILE.")
|
||||||
|
|
||||||
|
pid = self.get_pidfile()
|
||||||
|
if pid is None:
|
||||||
|
self.view(self.logdaemon.info, self.emit_message, "The daemon process is not running.", to_exit = True)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
with open("/proc/%d/status" %pid, 'r') as pf:
|
||||||
|
pass
|
||||||
|
self.view(self.logdaemon.info, self.emit_message, "The daemon process is running.", to_exit = True)
|
||||||
|
except Exception as e:
|
||||||
|
msg = "There is not a process with the PIDFILE '%s': %s" %(self.pidfile, str(e))
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, msg)
|
||||||
|
|
||||||
|
def flatten(self, alistoflists, ltypes = Sequence):
|
||||||
|
# https://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists/2158532#2158532
|
||||||
|
alistoflists = list(alistoflists)
|
||||||
|
while alistoflists:
|
||||||
|
while alistoflists and isinstance(alistoflists[0], ltypes):
|
||||||
|
alistoflists[0:1] = alistoflists[0]
|
||||||
|
if alistoflists: yield alistoflists.pop(0)
|
||||||
|
|
||||||
|
def exclude(self, func):
|
||||||
|
from inspect import getargspec
|
||||||
|
args = getargspec(func)
|
||||||
|
if callable(func):
|
||||||
|
try:
|
||||||
|
args[0].pop(0)
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
return args
|
||||||
|
else:
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, "Not a function.")
|
||||||
|
return
|
||||||
|
|
||||||
|
def execute(self, some_functions):
|
||||||
|
returned = None
|
||||||
|
if isinstance(some_functions, (list, tuple)):
|
||||||
|
for func in some_functions:
|
||||||
|
l_req = len(self.exclude(func)[0])
|
||||||
|
|
||||||
|
if l_req == 0:
|
||||||
|
returned = func()
|
||||||
|
else:
|
||||||
|
l_add = len(self.etrigan_add)
|
||||||
|
if l_req > l_add:
|
||||||
|
self.view(self.logdaemon.error, self.emit_error,
|
||||||
|
"Can't evaluate function: given %s, required %s." %(l_add, l_req))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
arguments = self.etrigan_add[self.etrigan_index]
|
||||||
|
l_args = (len(arguments) if isinstance(arguments, list) else 1)
|
||||||
|
if (l_args > l_req) or (l_args < l_req):
|
||||||
|
self.view(self.logdaemon.error, self.emit_error,
|
||||||
|
"Can't evaluate function: given %s, required %s." %(l_args, l_req))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if isinstance(arguments, list):
|
||||||
|
returned = func(*arguments)
|
||||||
|
else:
|
||||||
|
returned = func(arguments)
|
||||||
|
|
||||||
|
if returned:
|
||||||
|
if isinstance(returned, (list, tuple)):
|
||||||
|
if isinstance(returned[0], int):
|
||||||
|
self.etrigan_index = returned[0]
|
||||||
|
else:
|
||||||
|
self.etrigan_index = slice(*map(int, returned[0].split(':')))
|
||||||
|
if returned[1:] != []:
|
||||||
|
self.etrigan_add.append(returned[1:])
|
||||||
|
self.etrigan_add = list(self.flatten(self.etrigan_add))
|
||||||
|
else:
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, "Function should return list or tuple.")
|
||||||
|
returned = None
|
||||||
|
else:
|
||||||
|
if some_functions is None:
|
||||||
|
self.run()
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
try:
|
||||||
|
if self.pause_loop is None:
|
||||||
|
# one-shot.
|
||||||
|
self.execute(self.funcs_to_daemonize)
|
||||||
|
else:
|
||||||
|
if self.pause_loop >= 0:
|
||||||
|
# infinite with pause.
|
||||||
|
time.sleep(self.pause_loop)
|
||||||
|
while self.etrigan_alive:
|
||||||
|
self.execute(self.funcs_to_daemonize)
|
||||||
|
time.sleep(self.pause_loop)
|
||||||
|
elif self.pause_loop == -1:
|
||||||
|
# infinite without pause.
|
||||||
|
while self.etrigan_alive:
|
||||||
|
self.execute(self.funcs_to_daemonize)
|
||||||
|
except Exception as e:
|
||||||
|
msg = "The daemon process start method failed: %s" %str(e)
|
||||||
|
self.view(self.logdaemon.error, self.emit_error, msg)
|
||||||
|
|
||||||
|
def quit_standard(self):
|
||||||
|
self.view(self.logdaemon.info, None, "Stopping the daemon process...")
|
||||||
|
self.delete_pidfile(self.get_pidfile())
|
||||||
|
self.view(self.logdaemon.info, None, "The daemon process has ended correctly.")
|
||||||
|
|
||||||
|
def quit_on_start(self):
|
||||||
|
"""
|
||||||
|
Override this method when you subclass Daemon.
|
||||||
|
"""
|
||||||
|
self.quit_standard()
|
||||||
|
|
||||||
|
def quit_on_stop(self):
|
||||||
|
"""
|
||||||
|
Override this method when you subclass Daemon.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
Override this method when you subclass Daemon.
|
||||||
|
It will be called after the process has been
|
||||||
|
daemonized by start() or restart().
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class JasonBlood(Etrigan):
|
||||||
|
def run(self):
|
||||||
|
jasonblood_func()
|
||||||
|
|
||||||
|
def jasonblood_func():
|
||||||
|
with open(os.path.join(path, 'etrigan_test.txt'), 'a') as file:
|
||||||
|
file.write("Yarva Demonicus Etrigan " + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()) + '\n')
|
||||||
|
|
||||||
|
def Etrigan_parser(parser = None):
|
||||||
|
if parser is None:
|
||||||
|
# create a new parser.
|
||||||
|
parser = argparse.ArgumentParser(description = __description__, epilog = __version__)
|
||||||
|
if not parser.add_help:
|
||||||
|
# create help argument.
|
||||||
|
parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
|
||||||
|
|
||||||
|
# attach to an existent parser.
|
||||||
|
parser.add_argument("operation", action = "store", choices = ["start", "stop", "restart", "status", "reload"],
|
||||||
|
help = "Select an operation for daemon.", type = str)
|
||||||
|
parser.add_argument("--etrigan-pid",
|
||||||
|
action = "store", dest = "etriganpid", default = "/tmp/etrigan.pid",
|
||||||
|
help = "Choose a pidfile path. Default is \"/tmp/etrigan.pid\".", type = str) #'/var/run/etrigan.pid'
|
||||||
|
parser.add_argument("--etrigan-log",
|
||||||
|
action = "store", dest = "etriganlog", default = os.path.join(path, "etrigan.log"),
|
||||||
|
help = "Use this option to choose an output log file; for not logging don't select it. Default is \"etrigan.log\".", type = str)
|
||||||
|
parser.add_argument("--etrigan-lev",
|
||||||
|
action = "store", dest = "etriganlev", default = "DEBUG",
|
||||||
|
choices = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
|
||||||
|
help = "Use this option to set a log level. Default is \"DEBUG\".", type = str)
|
||||||
|
parser.add_argument("--etrigan-mute",
|
||||||
|
action = "store_const", dest = 'etriganmute', const = True, default = False,
|
||||||
|
help = "Disable all stdout and stderr messages.")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
class Etrigan_check(object):
|
||||||
|
def emit_opt_err(self, msg):
|
||||||
|
print(msg)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def checkfile(self, path, typearg, typefile):
|
||||||
|
filename, extension = os.path.splitext(path)
|
||||||
|
pathname = os.path.dirname(path)
|
||||||
|
if not os.path.isdir(pathname):
|
||||||
|
msg = "argument `%s`: invalid directory: '%s'. Exiting..." %(typearg, pathname)
|
||||||
|
self.emit_opt_err(msg)
|
||||||
|
elif not extension == typefile:
|
||||||
|
msg = "argument `%s`: not a %s file, invalid extension: '%s'. Exiting..." %(typearg, typefile, extension)
|
||||||
|
self.emit_opt_err(msg)
|
||||||
|
|
||||||
|
def checkfunction(self, funcs, booleans):
|
||||||
|
if not isinstance(funcs, (list, tuple)):
|
||||||
|
if funcs is not None:
|
||||||
|
msg = "argument `funcs_to_daemonize`: provide list, tuple or None"
|
||||||
|
self.emit_opt_err(msg)
|
||||||
|
|
||||||
|
for elem in booleans:
|
||||||
|
if not type(elem) == bool:
|
||||||
|
msg = "argument `want_quit`: not a boolean."
|
||||||
|
self.emit_opt_err(msg)
|
||||||
|
|
||||||
|
def Etrigan_job(type_oper, daemon_obj):
|
||||||
|
Etrigan_check().checkfunction(daemon_obj.funcs_to_daemonize,
|
||||||
|
[daemon_obj.want_quit])
|
||||||
|
if type_oper == "start":
|
||||||
|
daemon_obj.start()
|
||||||
|
elif type_oper == "stop":
|
||||||
|
daemon_obj.stop()
|
||||||
|
elif type_oper == "restart":
|
||||||
|
daemon_obj.restart()
|
||||||
|
elif type_oper == "status":
|
||||||
|
daemon_obj.status()
|
||||||
|
elif type_oper == "reload":
|
||||||
|
daemon_obj.reload()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Parse arguments.
|
||||||
|
parser = Etrigan_parser()
|
||||||
|
args = vars(parser.parse_args())
|
||||||
|
# Check arguments.
|
||||||
|
Etrigan_check().checkfile(args['etriganpid'], 'pidfile', '.pid')
|
||||||
|
Etrigan_check().checkfile(args['etriganlog'], 'pidfile', '.log')
|
||||||
|
|
||||||
|
# Setup daemon.
|
||||||
|
jasonblood_1 = Etrigan(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'],
|
||||||
|
mute = args['etriganmute'],
|
||||||
|
funcs_to_daemonize = [jasonblood_func], pause_loop = 5)
|
||||||
|
|
||||||
|
## jasonblood_2 = JasonBlood(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'],
|
||||||
|
## mute = args['etriganmute'],
|
||||||
|
## funcs_to_daemonize = None, pause_loop = 5)
|
||||||
|
# Do job.
|
||||||
|
Etrigan_job(args['operation'], jasonblood_1)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -25,8 +25,11 @@ from pykms_Misc import logger_create, check_logfile
|
|||||||
from pykms_Misc import KmsParser, KmsException
|
from pykms_Misc import KmsParser, KmsException
|
||||||
from pykms_Format import justify, byterize, enco, deco, ShellMessage, pretty_printer
|
from pykms_Format import justify, byterize, enco, deco, ShellMessage, pretty_printer
|
||||||
|
|
||||||
clt_description = 'KMS Client Emulator written in Python'
|
clt_version = "py-kms_2020-02-02"
|
||||||
clt_version = 'py-kms_2019-05-15'
|
__license__ = "The Unlicense"
|
||||||
|
__author__ = u"Matteo ℱan <SystemRage@protonmail.com>"
|
||||||
|
__url__ = "https://github.com/SystemRage/py-kms"
|
||||||
|
clt_description = "py-kms: KMS Client Emulator written in Python"
|
||||||
clt_config = {}
|
clt_config = {}
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@ -58,7 +61,7 @@ will be generated.', 'def' : None, 'des' : "machineName"},
|
|||||||
'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MINI"]},
|
'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MINI"]},
|
||||||
'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logclient.log\". Type \"STDOUT\" to view \
|
'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logclient.log\". Type \"STDOUT\" to view \
|
||||||
log info on stdout. Type \"FILESTDOUT\" to combine previous actions.',
|
log info on stdout. Type \"FILESTDOUT\" to combine previous actions.',
|
||||||
'def' : os.path.dirname(os.path.abspath( __file__ )) + "/pykms_logclient.log", 'des' : "logfile"},
|
'def' : os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pykms_logclient.log'), 'des' : "logfile"},
|
||||||
'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Desactivated by default.', 'def' : 0, 'des': "logsize"},
|
'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Desactivated by default.', 'def' : 0, 'des': "logsize"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ class ShellMessage(object):
|
|||||||
self.put_text = put_text
|
self.put_text = put_text
|
||||||
self.where = where
|
self.where = where
|
||||||
self.plaintext = []
|
self.plaintext = []
|
||||||
self.path = os.path.dirname(os.path.abspath( __file__ )) + '/newlines.txt'
|
self.path = os.path.dirname(os.path.abspath( __file__ )) + '/pykms_newlines.txt'
|
||||||
self.print_queue = Queue.Queue()
|
self.print_queue = Queue.Queue()
|
||||||
|
|
||||||
def formatter(self, msgtofrmt):
|
def formatter(self, msgtofrmt):
|
||||||
|
@ -20,12 +20,15 @@ except ImportError:
|
|||||||
from tkinter import filedialog
|
from tkinter import filedialog
|
||||||
import tkinter.font as tkFont
|
import tkinter.font as tkFont
|
||||||
|
|
||||||
from pykms_Server import srv_options, srv_version, srv_config, srv_terminate, serverqueue, serverthread
|
from pykms_Server import srv_options, srv_version, srv_config, server_terminate, serverqueue, serverthread
|
||||||
from pykms_GuiMisc import ToolTip, TextDoubleScroll, TextRedirect, custom_background
|
from pykms_GuiMisc import ToolTip, TextDoubleScroll, TextRedirect, custom_background
|
||||||
from pykms_Client import clt_options, clt_version, clt_config, client_thread
|
from pykms_Client import clt_options, clt_version, clt_config, client_thread
|
||||||
|
|
||||||
gui_description = 'py-kms GUI'
|
gui_version = "py-kms_gui_v2.0"
|
||||||
gui_version = 'v1.0'
|
__license__ = "The Unlicense"
|
||||||
|
__author__ = u"Matteo ℱan <SystemRage@protonmail.com>"
|
||||||
|
__url__ = "https://github.com/SystemRage/py-kms"
|
||||||
|
gui_description = "A GUI for py-kms."
|
||||||
|
|
||||||
##---------------------------------------------------------------------------------------------------------------------------------------------------------
|
##---------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
def get_ip_address():
|
def get_ip_address():
|
||||||
@ -452,7 +455,7 @@ class KmsGui(tk.Tk):
|
|||||||
def srv_actions_stop(self):
|
def srv_actions_stop(self):
|
||||||
if serverthread.is_running_server:
|
if serverthread.is_running_server:
|
||||||
if serverthread.server is not None:
|
if serverthread.server is not None:
|
||||||
srv_terminate(exit_server = True)
|
server_terminate(serverthread, exit_server = True)
|
||||||
# wait for switch.
|
# wait for switch.
|
||||||
while serverthread.is_running_server:
|
while serverthread.is_running_server:
|
||||||
pass
|
pass
|
||||||
@ -522,10 +525,10 @@ class KmsGui(tk.Tk):
|
|||||||
def on_exit(self):
|
def on_exit(self):
|
||||||
if serverthread.is_running_server:
|
if serverthread.is_running_server:
|
||||||
if serverthread.server is not None:
|
if serverthread.server is not None:
|
||||||
srv_terminate(exit_server = True)
|
server_terminate(serverthread, exit_server = True)
|
||||||
else:
|
else:
|
||||||
serverthread.is_running_server = False
|
serverthread.is_running_server = False
|
||||||
srv_terminate(exit_thread = True)
|
server_terminate(serverthread, exit_thread = True)
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def on_clear(self, widgetlist):
|
def on_clear(self, widgetlist):
|
||||||
|
@ -221,6 +221,31 @@ class KmsParser(argparse.ArgumentParser):
|
|||||||
def error(self, message):
|
def error(self, message):
|
||||||
raise KmsException(message)
|
raise KmsException(message)
|
||||||
|
|
||||||
|
class KmsHelper(object):
|
||||||
|
def replace(self, parser):
|
||||||
|
text = parser.format_help().splitlines()
|
||||||
|
help_list = []
|
||||||
|
for line in text:
|
||||||
|
if line == parser.description:
|
||||||
|
continue
|
||||||
|
if line == parser.epilog:
|
||||||
|
line = 80 * '*' + '\n'
|
||||||
|
help_list.append(line)
|
||||||
|
return help_list
|
||||||
|
|
||||||
|
def print(self, parsers):
|
||||||
|
parser_base, parser_adj, parser_sub = parsers
|
||||||
|
print('\n' + parser_base.description)
|
||||||
|
print(len(parser_base.description) * '-' + '\n')
|
||||||
|
for line in self.replace(parser_base):
|
||||||
|
print(line)
|
||||||
|
print(parser_adj.description + '\n')
|
||||||
|
for line in self.replace(parser_sub):
|
||||||
|
print(line)
|
||||||
|
print('\n' + len(parser_base.epilog) * '-')
|
||||||
|
print(parser_base.epilog + '\n')
|
||||||
|
parser_base.exit()
|
||||||
|
|
||||||
#----------------------------------------------------------------------------------------------------------------------------------------------------------
|
#----------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
# http://joshpoley.blogspot.com/2011/09/hresults-user-0x004.html (slerror.h)
|
# http://joshpoley.blogspot.com/2011/09/hresults-user-0x004.html (slerror.h)
|
||||||
|
@ -9,6 +9,7 @@ import uuid
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
import pickle
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 2 import.
|
# Python 2 import.
|
||||||
@ -27,11 +28,15 @@ import pykms_RpcBind, pykms_RpcRequest
|
|||||||
from pykms_RpcBase import rpcBase
|
from pykms_RpcBase import rpcBase
|
||||||
from pykms_Dcerpc import MSRPCHeader
|
from pykms_Dcerpc import MSRPCHeader
|
||||||
from pykms_Misc import logger_create, check_logfile, check_lcid
|
from pykms_Misc import logger_create, check_logfile, check_lcid
|
||||||
from pykms_Misc import KmsParser, KmsException
|
from pykms_Misc import KmsParser, KmsException, KmsHelper
|
||||||
from pykms_Format import enco, deco, ShellMessage, pretty_printer
|
from pykms_Format import enco, deco, ShellMessage, pretty_printer
|
||||||
|
from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job
|
||||||
|
|
||||||
srv_description = 'KMS Server Emulator written in Python'
|
srv_version = "py-kms_2020-02-02"
|
||||||
srv_version = 'py-kms_2019-05-15'
|
__license__ = "The Unlicense"
|
||||||
|
__author__ = u"Matteo ℱan <SystemRage@protonmail.com>"
|
||||||
|
__url__ = "https://github.com/SystemRage/py-kms"
|
||||||
|
srv_description = "py-kms: KMS Server Emulator written in Python"
|
||||||
srv_config = {}
|
srv_config = {}
|
||||||
|
|
||||||
##---------------------------------------------------------------------------------------------------------------------------------------------------------
|
##---------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@ -110,12 +115,14 @@ class server_thread(threading.Thread):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.server = None
|
self.server = None
|
||||||
self.is_running_server, self.with_gui = [False for _ in range(2)]
|
self.is_running_server, self.with_gui, self.checked = [False for _ in range(3)]
|
||||||
self.is_running_thread = threading.Event()
|
self.is_running_thread = threading.Event()
|
||||||
|
|
||||||
def terminate_serve(self):
|
def terminate_serve(self):
|
||||||
self.server.shutdown()
|
self.server.shutdown()
|
||||||
self.server.server_close()
|
self.server.server_close()
|
||||||
|
self.server = None
|
||||||
|
self.is_running_server = False
|
||||||
|
|
||||||
def terminate_thread(self):
|
def terminate_thread(self):
|
||||||
self.is_running_thread.set()
|
self.is_running_thread.set()
|
||||||
@ -136,15 +143,11 @@ class server_thread(threading.Thread):
|
|||||||
self.eject = False
|
self.eject = False
|
||||||
self.is_running_server = True
|
self.is_running_server = True
|
||||||
# Check options.
|
# Check options.
|
||||||
server_check()
|
if not self.checked:
|
||||||
|
server_check()
|
||||||
# Create and run server.
|
# Create and run server.
|
||||||
self.server = server_create()
|
self.server = server_create()
|
||||||
self.server.pykms_serve()
|
self.server.pykms_serve()
|
||||||
elif item == 'stop':
|
|
||||||
self.server = None
|
|
||||||
self.is_running_server = False
|
|
||||||
elif item == 'exit':
|
|
||||||
self.terminate_thread()
|
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
self.eject = True
|
self.eject = True
|
||||||
if not self.with_gui:
|
if not self.with_gui:
|
||||||
@ -179,46 +182,126 @@ The default is \"364F463A8863D35F\" or type \"RANDOM\" to auto generate the HWID
|
|||||||
'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MINI"]},
|
'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MINI"]},
|
||||||
'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logserver.log\". Type \"STDOUT\" to view \
|
'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logserver.log\". Type \"STDOUT\" to view \
|
||||||
log info on stdout. Type \"FILESTDOUT\" to combine previous actions.',
|
log info on stdout. Type \"FILESTDOUT\" to combine previous actions.',
|
||||||
'def' : os.path.dirname(os.path.abspath( __file__ )) + "/pykms_logserver.log", 'des' : "logfile"},
|
'def' : os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pykms_logserver.log'), 'des' : "logfile"},
|
||||||
'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Desactivated by default.', 'def' : 0, 'des': "logsize"},
|
'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Desactivated by default.', 'def' : 0, 'des': "logsize"},
|
||||||
}
|
}
|
||||||
|
|
||||||
def server_options():
|
def server_options():
|
||||||
parser = KmsParser(description = srv_description, epilog = 'version: ' + srv_version)
|
main_parser = KmsParser(description = srv_description, epilog = 'version: ' + srv_version, add_help = False, allow_abbrev = False)
|
||||||
parser.add_argument("ip", nargs = "?", action = "store", default = srv_options['ip']['def'], help = srv_options['ip']['help'], type = str)
|
main_parser.add_argument("ip", nargs = "?", action = "store", default = srv_options['ip']['def'], help = srv_options['ip']['help'], type = str)
|
||||||
parser.add_argument("port", nargs = "?", action = "store", default = srv_options['port']['def'], help = srv_options['port']['help'], type = int)
|
main_parser.add_argument("port", nargs = "?", action = "store", default = srv_options['port']['def'], help = srv_options['port']['help'], type = int)
|
||||||
parser.add_argument("-e", "--epid", dest = srv_options['epid']['des'], default = srv_options['epid']['def'], help = srv_options['epid']['help'], type = str)
|
main_parser.add_argument("-e", "--epid", action = "store", dest = srv_options['epid']['des'], default = srv_options['epid']['def'],
|
||||||
parser.add_argument("-l", "--lcid", dest = srv_options['lcid']['des'], default = srv_options['lcid']['def'], help = srv_options['lcid']['help'], type = int)
|
help = srv_options['epid']['help'], type = str)
|
||||||
parser.add_argument("-c", "--client-count", dest = srv_options['count']['des'] , default = srv_options['count']['def'],
|
main_parser.add_argument("-l", "--lcid", action = "store", dest = srv_options['lcid']['des'], default = srv_options['lcid']['def'],
|
||||||
help = srv_options['count']['help'], type = int)
|
help = srv_options['lcid']['help'], type = int)
|
||||||
parser.add_argument("-a", "--activation-interval", dest = srv_options['activation']['des'], default = srv_options['activation']['def'],
|
main_parser.add_argument("-c", "--client-count", action = "store", dest = srv_options['count']['des'] , default = srv_options['count']['def'],
|
||||||
help = srv_options['activation']['help'], type = int)
|
help = srv_options['count']['help'], type = int)
|
||||||
parser.add_argument("-r", "--renewal-interval", dest = srv_options['renewal']['des'], default = srv_options['renewal']['def'],
|
main_parser.add_argument("-a", "--activation-interval", action = "store", dest = srv_options['activation']['des'],
|
||||||
help = srv_options['renewal']['help'], type = int)
|
default = srv_options['activation']['def'], help = srv_options['activation']['help'], type = int)
|
||||||
parser.add_argument("-s", "--sqlite", dest = srv_options['sql']['des'], action = "store_const", const = True, default = srv_options['sql']['def'],
|
main_parser.add_argument("-r", "--renewal-interval", action = "store", dest = srv_options['renewal']['des'], default = srv_options['renewal']['def'],
|
||||||
help = srv_options['sql']['help'])
|
help = srv_options['renewal']['help'], type = int)
|
||||||
parser.add_argument("-w", "--hwid", dest = srv_options['hwid']['des'], action = "store", default = srv_options['hwid']['def'],
|
main_parser.add_argument("-s", "--sqlite", action = "store_const", dest = srv_options['sql']['des'], const = True, default = srv_options['sql']['def'],
|
||||||
help = srv_options['hwid']['help'], type = str)
|
help = srv_options['sql']['help'])
|
||||||
parser.add_argument("-t", "--timeout", dest = srv_options['time']['des'], action = "store", default = srv_options['time']['def'],
|
main_parser.add_argument("-w", "--hwid", action = "store", dest = srv_options['hwid']['des'], default = srv_options['hwid']['def'],
|
||||||
help = srv_options['time']['help'], type = int)
|
help = srv_options['hwid']['help'], type = str)
|
||||||
parser.add_argument("-V", "--loglevel", dest = srv_options['llevel']['des'], action = "store", choices = srv_options['llevel']['choi'],
|
main_parser.add_argument("-t", "--timeout", action = "store", dest = srv_options['time']['des'], default = srv_options['time']['def'],
|
||||||
default = srv_options['llevel']['def'], help = srv_options['llevel']['help'], type = str)
|
help = srv_options['time']['help'], type = int)
|
||||||
parser.add_argument("-F", "--logfile", nargs = "+", action = "store", dest = srv_options['lfile']['des'], default = srv_options['lfile']['def'],
|
main_parser.add_argument("-V", "--loglevel", action = "store", dest = srv_options['llevel']['des'], choices = srv_options['llevel']['choi'],
|
||||||
help = srv_options['lfile']['help'], type = str)
|
default = srv_options['llevel']['def'], help = srv_options['llevel']['help'], type = str)
|
||||||
parser.add_argument("-S", "--logsize", dest = srv_options['lsize']['des'], action = "store", default = srv_options['lsize']['def'],
|
main_parser.add_argument("-F", "--logfile", nargs = "+", action = "store", dest = srv_options['lfile']['des'], default = srv_options['lfile']['def'],
|
||||||
help = srv_options['lsize']['help'], type = float)
|
help = srv_options['lfile']['help'], type = str)
|
||||||
|
main_parser.add_argument("-S", "--logsize", action = "store", dest = srv_options['lsize']['des'], default = srv_options['lsize']['def'],
|
||||||
|
help = srv_options['lsize']['help'], type = float)
|
||||||
|
main_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
|
||||||
|
|
||||||
|
daemon_parser = KmsParser(description = "daemon options inherited from Etrigan", add_help = False, allow_abbrev = False)
|
||||||
|
daemon_subparser = daemon_parser.add_subparsers(dest = "mode")
|
||||||
|
etrigan_parser = daemon_subparser.add_parser("etrigan", add_help = False, allow_abbrev = False)
|
||||||
|
etrigan_parser.add_argument("-g", "--gui", action = "store_const", dest = 'gui', const = True, default = False,
|
||||||
|
help = "Enable py-kms GUI usage.")
|
||||||
|
etrigan_parser = Etrigan_parser(parser = etrigan_parser)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
srv_config.update(vars(parser.parse_args()))
|
if "-h" in sys.argv[1:]:
|
||||||
# Check logfile.
|
KmsHelper().print(parsers = [main_parser, daemon_parser, etrigan_parser])
|
||||||
srv_config['logfile'] = check_logfile(srv_config['logfile'], srv_options['lfile']['def'], where = "srv")
|
|
||||||
|
# Set defaults for config.
|
||||||
|
# case: python3 pykms_Server.py
|
||||||
|
srv_config.update(vars(main_parser.parse_args([])))
|
||||||
|
# Eventually set daemon values for config.
|
||||||
|
if 'etrigan' in sys.argv[1:]:
|
||||||
|
if 'etrigan' == sys.argv[1]:
|
||||||
|
# case: python3 pykms_Server.py etrigan start --daemon_optionals
|
||||||
|
srv_config.update(vars(daemon_parser.parse_args(sys.argv[1:])))
|
||||||
|
elif 'etrigan' == sys.argv[2]:
|
||||||
|
# case: python3 pykms_Server.py 1.2.3.4 etrigan start --daemon_optionals
|
||||||
|
srv_config['ip'] = sys.argv[1]
|
||||||
|
srv_config.update(vars(daemon_parser.parse_args(sys.argv[2:])))
|
||||||
|
else:
|
||||||
|
# case: python3 pykms_Server.py 1.2.3.4 1234 --main_optionals etrigan start --daemon_optionals
|
||||||
|
knw_args, knw_extras = main_parser.parse_known_args()
|
||||||
|
# fix for logfile option (at the end) that catchs etrigan parser options.
|
||||||
|
if 'etrigan' in knw_args.logfile:
|
||||||
|
indx = knw_args.logfile.index('etrigan')
|
||||||
|
for num, elem in enumerate(knw_args.logfile[indx:]):
|
||||||
|
knw_extras.insert(num, elem)
|
||||||
|
knw_args.logfile = knw_args.logfile[:indx]
|
||||||
|
|
||||||
|
# continue parsing.
|
||||||
|
if len(knw_extras) > 0 and knw_extras[0] in ['etrigan']:
|
||||||
|
daemon_parser.parse_args(knw_extras, namespace = knw_args)
|
||||||
|
srv_config.update(vars(knw_args))
|
||||||
|
else:
|
||||||
|
# Update dict config.
|
||||||
|
# case: python3 pykms_Server.py 1.2.3.4 1234 --main_optionals
|
||||||
|
knw_args, knw_extras = main_parser.parse_known_args()
|
||||||
|
if knw_extras != []:
|
||||||
|
raise KmsException("unrecognized arguments: %s" %' '.join(knw_extras))
|
||||||
|
else:
|
||||||
|
srv_config.update(vars(knw_args))
|
||||||
|
|
||||||
except KmsException as e:
|
except KmsException as e:
|
||||||
pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True)
|
pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True)
|
||||||
|
|
||||||
|
|
||||||
|
def server_daemon():
|
||||||
|
if 'etrigan' in srv_config.values():
|
||||||
|
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pykms_config.pickle')
|
||||||
|
|
||||||
|
if srv_config['operation'] in ['stop', 'restart', 'status'] and len(sys.argv[1:]) > 2:
|
||||||
|
pretty_printer(put_text = "{reverse}{red}{bold}too much arguments. Exiting...{end}", to_exit = True)
|
||||||
|
|
||||||
|
if srv_config['gui']:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if srv_config['operation'] == 'start':
|
||||||
|
with open(path, 'wb') as file:
|
||||||
|
pickle.dump(srv_config, file, protocol = pickle.HIGHEST_PROTOCOL)
|
||||||
|
elif srv_config['operation'] in ['stop', 'status', 'restart']:
|
||||||
|
with open(path, 'rb') as file:
|
||||||
|
old_srv_config = pickle.load(file)
|
||||||
|
old_srv_config = {x: old_srv_config[x] for x in old_srv_config if x not in ['operation']}
|
||||||
|
srv_config.update(old_srv_config)
|
||||||
|
|
||||||
|
serverdaemon = Etrigan(srv_config['etriganpid'],
|
||||||
|
logfile = srv_config['etriganlog'], loglevel = srv_config['etriganlev'],
|
||||||
|
mute = srv_config['etriganmute'], pause_loop = None)
|
||||||
|
|
||||||
|
if srv_config['operation'] == 'start':
|
||||||
|
serverdaemon.want_quit = True
|
||||||
|
if srv_config['gui']:
|
||||||
|
serverdaemon.funcs_to_daemonize = [server_with_gui]
|
||||||
|
else:
|
||||||
|
server_without_gui = ServerWithoutGui()
|
||||||
|
serverdaemon.funcs_to_daemonize = [server_without_gui.start, server_without_gui.join]
|
||||||
|
indx_for_clean = lambda: (0, )
|
||||||
|
serverdaemon.quit_on_stop = [indx_for_clean, server_without_gui.clean]
|
||||||
|
|
||||||
|
Etrigan_job(srv_config['operation'], serverdaemon)
|
||||||
|
|
||||||
def server_check():
|
def server_check():
|
||||||
# Check logfile (only for GUI).
|
# Check logfile.
|
||||||
if serverthread.with_gui:
|
srv_config['logfile'] = check_logfile(srv_config['logfile'], srv_options['lfile']['def'], where = "srv")
|
||||||
srv_config['logfile'] = check_logfile(srv_config['logfile'], srv_options['lfile']['def'], where = "srv")
|
|
||||||
|
|
||||||
# Setup hidden or not messages.
|
# Setup hidden or not messages.
|
||||||
ShellMessage.view = ( False if any(i in ['STDOUT', 'FILESTDOUT'] for i in srv_config['logfile']) else True )
|
ShellMessage.view = ( False if any(i in ['STDOUT', 'FILESTDOUT'] for i in srv_config['logfile']) else True )
|
||||||
@ -279,28 +362,59 @@ def server_create():
|
|||||||
loggersrv.info("HWID: %s" % deco(binascii.b2a_hex(srv_config['hwid']), 'utf-8').upper())
|
loggersrv.info("HWID: %s" % deco(binascii.b2a_hex(srv_config['hwid']), 'utf-8').upper())
|
||||||
return server
|
return server
|
||||||
|
|
||||||
def srv_terminate(exit_server = False, exit_thread = False):
|
def server_terminate(generic_srv, exit_server = False, exit_thread = False):
|
||||||
if exit_server:
|
if exit_server:
|
||||||
serverthread.terminate_serve()
|
generic_srv.terminate_serve()
|
||||||
serverqueue.put('stop')
|
|
||||||
if exit_thread:
|
if exit_thread:
|
||||||
serverqueue.put('exit')
|
generic_srv.terminate_thread()
|
||||||
|
|
||||||
def srv_main_without_gui():
|
class ServerWithoutGui(object):
|
||||||
|
def start(self):
|
||||||
|
import queue as Queue
|
||||||
|
daemon_queue = Queue.Queue(maxsize = 0)
|
||||||
|
daemon_serverthread = server_thread(daemon_queue, name = "Thread-Srv-Daemon")
|
||||||
|
daemon_serverthread.setDaemon(True)
|
||||||
|
# options already checked in `server_main_terminal`.
|
||||||
|
daemon_serverthread.checked = True
|
||||||
|
daemon_serverthread.start()
|
||||||
|
daemon_queue.put('start')
|
||||||
|
return 0, daemon_serverthread
|
||||||
|
|
||||||
|
def join(self, daemon_serverthread):
|
||||||
|
while daemon_serverthread.is_alive():
|
||||||
|
daemon_serverthread.join(timeout = 0.5)
|
||||||
|
|
||||||
|
def clean(self, daemon_serverthread):
|
||||||
|
server_terminate(daemon_serverthread, exit_server = True, exit_thread = True)
|
||||||
|
|
||||||
|
def server_main_terminal():
|
||||||
# Parse options.
|
# Parse options.
|
||||||
server_options()
|
server_options()
|
||||||
# Run threaded server.
|
# Check options.
|
||||||
serverqueue.put('start')
|
server_check()
|
||||||
# Wait to finish.
|
serverthread.checked = True
|
||||||
try:
|
|
||||||
while serverthread.is_alive():
|
|
||||||
serverthread.join(timeout = 0.5)
|
|
||||||
except (KeyboardInterrupt, SystemExit):
|
|
||||||
srv_terminate(exit_server = True, exit_thread = True)
|
|
||||||
|
|
||||||
def srv_main_with_gui(width = 950, height = 660):
|
if 'etrigan' not in srv_config.values():
|
||||||
|
# (without GUI) and (without daemon).
|
||||||
|
# Run threaded server.
|
||||||
|
serverqueue.put('start')
|
||||||
|
# Wait to finish.
|
||||||
|
try:
|
||||||
|
while serverthread.is_alive():
|
||||||
|
serverthread.join(timeout = 0.5)
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
server_terminate(serverthread, exit_server = True, exit_thread = True)
|
||||||
|
else:
|
||||||
|
# (with or without GUI) and (with daemon)
|
||||||
|
# Setup daemon (eventually).
|
||||||
|
server_daemon()
|
||||||
|
|
||||||
|
def server_with_gui():
|
||||||
import pykms_GuiBase
|
import pykms_GuiBase
|
||||||
|
|
||||||
|
width = 950
|
||||||
|
height = 660
|
||||||
|
|
||||||
root = pykms_GuiBase.KmsGui()
|
root = pykms_GuiBase.KmsGui()
|
||||||
root.title(pykms_GuiBase.gui_description + ' ' + pykms_GuiBase.gui_version)
|
root.title(pykms_GuiBase.gui_description + ' ' + pykms_GuiBase.gui_version)
|
||||||
# Main window initial position.
|
# Main window initial position.
|
||||||
@ -314,6 +428,11 @@ def srv_main_with_gui(width = 950, height = 660):
|
|||||||
root.resizable(0, 0)
|
root.resizable(0, 0)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
|
||||||
|
def server_main_no_terminal():
|
||||||
|
# Run tkinter GUI.
|
||||||
|
# (with GUI) and (without daemon).
|
||||||
|
server_with_gui()
|
||||||
|
|
||||||
class kmsServerHandler(socketserver.BaseRequestHandler):
|
class kmsServerHandler(socketserver.BaseRequestHandler):
|
||||||
def setup(self):
|
def setup(self):
|
||||||
loggersrv.info("Connection accepted: %s:%d" % (self.client_address[0], self.client_address[1]))
|
loggersrv.info("Connection accepted: %s:%d" % (self.client_address[0], self.client_address[1]))
|
||||||
@ -376,9 +495,9 @@ serverthread.start()
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if sys.stdout.isatty():
|
if sys.stdout.isatty():
|
||||||
srv_main_without_gui()
|
server_main_terminal()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
srv_main_with_gui()
|
server_main_no_terminal()
|
||||||
except:
|
except:
|
||||||
srv_main_without_gui()
|
server_main_terminal()
|
||||||
|
Loading…
Reference in New Issue
Block a user