chore: Update proginit to 1.4.0
This commit is contained in:
@@ -5,19 +5,20 @@
|
|||||||
__author__ = "Sven Sager"
|
__author__ = "Sven Sager"
|
||||||
__copyright__ = "Copyright (C) 2018-2023 Sven Sager"
|
__copyright__ = "Copyright (C) 2018-2023 Sven Sager"
|
||||||
__license__ = "LGPL-2.0-or-later"
|
__license__ = "LGPL-2.0-or-later"
|
||||||
__version__ = "1.3.2"
|
__version__ = "1.4.0"
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser, Namespace
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
|
from enum import Enum
|
||||||
from os import R_OK, W_OK, access, environ, getpid, remove
|
from os import R_OK, W_OK, access, environ, getpid, remove
|
||||||
from os.path import abspath, dirname, exists, join
|
from os.path import abspath, dirname, exists, join
|
||||||
from shutil import copy, move
|
from shutil import copy, move
|
||||||
from threading import Event
|
from threading import Event
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Import program version from meta data module of your program
|
# Import the program version from the meta-data module of your program
|
||||||
from .__about__ import __version__ as external_version
|
from .__about__ import __version__ as external_version
|
||||||
except Exception:
|
except Exception:
|
||||||
external_version = None
|
external_version = None
|
||||||
@@ -26,11 +27,12 @@ programname = "revpi-middleware" # Program name
|
|||||||
program_version = external_version or "a.b.c"
|
program_version = external_version or "a.b.c"
|
||||||
|
|
||||||
conf_rw = False # If you want so save the configuration with .save_conf() set to True
|
conf_rw = False # If you want so save the configuration with .save_conf() set to True
|
||||||
conf_rw_save = False # Create new conf file in same directory and move to old one
|
conf_rw_save = False # Create a new conf file in the same directory and move to the old one
|
||||||
conf_rw_backup = False # Keep a backup of old conf file [filename].bak
|
conf_rw_backup = False # Keep a backup of an old conf file [filename].bak
|
||||||
_extend_daemon_startup_timeout = 0.0 # Default startup timeout is 90 seconds
|
_extend_daemon_startup_timeout = 0.0 # The default startup timeout is 90 seconds
|
||||||
|
|
||||||
conf = ConfigParser()
|
conf = ConfigParser()
|
||||||
|
pargs = Namespace(daemon=False, verbose=0)
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
pidfile = "/var/run/{0}.pid".format(programname)
|
pidfile = "/var/run/{0}.pid".format(programname)
|
||||||
_daemon_started_up = Event()
|
_daemon_started_up = Event()
|
||||||
@@ -90,11 +92,19 @@ def cleanup():
|
|||||||
sys.stdout.close()
|
sys.stdout.close()
|
||||||
|
|
||||||
|
|
||||||
def reconfigure_logger():
|
class StdLogOutput(Enum):
|
||||||
|
"""Enum for the different output streams of the logger."""
|
||||||
|
|
||||||
|
NONE = ""
|
||||||
|
STDOUT = "stdout"
|
||||||
|
STDERR = "stderr"
|
||||||
|
|
||||||
|
|
||||||
|
def reconfigure_logger(std_output: StdLogOutput = StdLogOutput.STDOUT):
|
||||||
"""Configure logging module of program."""
|
"""Configure logging module of program."""
|
||||||
|
|
||||||
class FilterDebug(logging.Filter):
|
class FilterDebug(logging.Filter):
|
||||||
"""Set this filter to log handler if verbose level is > 1."""
|
"""Set this filter to log handler if the verbose level is > 1."""
|
||||||
|
|
||||||
def filter(self, record: logging.LogRecord) -> bool:
|
def filter(self, record: logging.LogRecord) -> bool:
|
||||||
remove_record = False
|
remove_record = False
|
||||||
@@ -104,29 +114,33 @@ def reconfigure_logger():
|
|||||||
|
|
||||||
return not remove_record
|
return not remove_record
|
||||||
|
|
||||||
# Clear all log handler
|
# Clear all log handlers
|
||||||
for lhandler in logger.handlers.copy():
|
for lhandler in logger.handlers.copy():
|
||||||
lhandler.close()
|
lhandler.close()
|
||||||
logger.removeHandler(lhandler)
|
logger.removeHandler(lhandler)
|
||||||
|
|
||||||
if pargs.daemon:
|
if pargs.daemon:
|
||||||
# Create daemon log file
|
# Create the daemon log file
|
||||||
fh_logfile = open("/var/log/{0}.log".format(programname), "a")
|
fh_logfile = open("/var/log/{0}.log".format(programname), "a")
|
||||||
|
|
||||||
# Close stdout and use logfile
|
# Close stdout and use the logfile
|
||||||
sys.stdout.close()
|
sys.stdout.close()
|
||||||
sys.stdout = fh_logfile
|
sys.stdout = fh_logfile
|
||||||
sys.stderr = sys.stdout
|
sys.stderr = sys.stdout
|
||||||
|
|
||||||
# Create new log handler
|
# Create the new log handler
|
||||||
if pargs.verbose > 2:
|
if pargs.verbose > 2:
|
||||||
log_frm = "{asctime} [{levelname:8}] {name} {message}"
|
log_frm = "{asctime} [{levelname:8}] {name} {message}"
|
||||||
else:
|
else:
|
||||||
log_frm = "{asctime} [{levelname:8}] {message}"
|
log_frm = "{asctime} [{levelname:8}] {message}"
|
||||||
logformat = logging.Formatter(log_frm, datefmt="%Y-%m-%d %H:%M:%S", style="{")
|
logformat = logging.Formatter(log_frm, datefmt="%Y-%m-%d %H:%M:%S", style="{")
|
||||||
lhandler = logging.StreamHandler(sys.stdout)
|
|
||||||
lhandler.setFormatter(logformat)
|
if std_output is not StdLogOutput.NONE:
|
||||||
logger.addHandler(lhandler)
|
lhandler = logging.StreamHandler(
|
||||||
|
sys.stdout if std_output is StdLogOutput.STDOUT else sys.stderr
|
||||||
|
)
|
||||||
|
lhandler.setFormatter(logformat)
|
||||||
|
logger.addHandler(lhandler)
|
||||||
|
|
||||||
if "logfile" in pargs and pargs.logfile is not None:
|
if "logfile" in pargs and pargs.logfile is not None:
|
||||||
# Write logs to a logfile
|
# Write logs to a logfile
|
||||||
@@ -147,13 +161,13 @@ def reconfigure_logger():
|
|||||||
|
|
||||||
def reload_conf(clear_load=False) -> None:
|
def reload_conf(clear_load=False) -> None:
|
||||||
"""
|
"""
|
||||||
Reload config file.
|
Reload the config file.
|
||||||
|
|
||||||
After successful reload, call set_startup_complete() function to inform
|
After successful reload, call the set_startup_complete() function to inform
|
||||||
systemd that all functions are available again.
|
systemd that all functions are available again.
|
||||||
|
|
||||||
If keys are commented out in conf file, they will still be in the conf file.
|
If keys are commented out in the conf file, they will still be in the conf file.
|
||||||
To remove not existing keys set clear_load to True.
|
To remove not existing keys, set clear_load to True.
|
||||||
|
|
||||||
:param clear_load: Clear conf before reload
|
:param clear_load: Clear conf before reload
|
||||||
"""
|
"""
|
||||||
@@ -161,11 +175,11 @@ def reload_conf(clear_load=False) -> None:
|
|||||||
# Inform systemd about reloading configuration
|
# Inform systemd about reloading configuration
|
||||||
_systemd_socket.sendto(b"RELOADING=1\n", _systemd_notify)
|
_systemd_socket.sendto(b"RELOADING=1\n", _systemd_notify)
|
||||||
|
|
||||||
# Reset started up event for the set_startup_complete function
|
# Reset started-up event for the set_startup_complete function
|
||||||
_daemon_started_up.clear()
|
_daemon_started_up.clear()
|
||||||
|
|
||||||
if "conffile" in pargs:
|
if "conffile" in pargs:
|
||||||
# Check config file
|
# Check the config file
|
||||||
if not access(pargs.conffile, R_OK):
|
if not access(pargs.conffile, R_OK):
|
||||||
raise RuntimeError("can not access config file '{0}'".format(pargs.conffile))
|
raise RuntimeError("can not access config file '{0}'".format(pargs.conffile))
|
||||||
if conf_rw:
|
if conf_rw:
|
||||||
@@ -212,7 +226,7 @@ def startup_complete():
|
|||||||
this daemon are available so that the starts of further daemons can be
|
this daemon are available so that the starts of further daemons can be
|
||||||
properly timed.
|
properly timed.
|
||||||
|
|
||||||
The systemd unit file that is supposed to start this demon must be set
|
The systemd unit file supposed to start this demon must be set
|
||||||
to 'Type=notify'. If the daemon supports reloading the settings,
|
to 'Type=notify'. If the daemon supports reloading the settings,
|
||||||
'ExecReload=/bin/kill -HUP $MAINPID' must also be set. The daemon must
|
'ExecReload=/bin/kill -HUP $MAINPID' must also be set. The daemon must
|
||||||
call this function again after the reload in order to signal systemd the
|
call this function again after the reload in order to signal systemd the
|
||||||
@@ -231,13 +245,13 @@ def startup_complete():
|
|||||||
return
|
return
|
||||||
|
|
||||||
if _systemd_notify:
|
if _systemd_notify:
|
||||||
# Inform systemd about complete startup of daemon process
|
# Inform systemd about the complete startup of the daemon process
|
||||||
_systemd_socket.sendto(b"READY=1\n", _systemd_notify)
|
_systemd_socket.sendto(b"READY=1\n", _systemd_notify)
|
||||||
|
|
||||||
if pargs.daemon:
|
if pargs.daemon:
|
||||||
from os import kill
|
from os import kill
|
||||||
|
|
||||||
# Send SIGTERM signal to main process
|
# Send SIGTERM signal to the main process
|
||||||
kill(_daemon_main_pid, 15)
|
kill(_daemon_main_pid, 15)
|
||||||
|
|
||||||
_daemon_started_up.set()
|
_daemon_started_up.set()
|
||||||
@@ -246,35 +260,15 @@ def startup_complete():
|
|||||||
# Generate command arguments of the program
|
# Generate command arguments of the program
|
||||||
parser = ArgumentParser(
|
parser = ArgumentParser(
|
||||||
prog=programname,
|
prog=programname,
|
||||||
# todo: Add program description for help
|
|
||||||
description="Program description",
|
description="Program description",
|
||||||
)
|
)
|
||||||
parser.add_argument("--version", action="version", version=f"%(prog)s {program_version}")
|
parser.add_argument("--version", action="version", version=f"%(prog)s {program_version}")
|
||||||
if can_be_forked():
|
|
||||||
# Show the parameter only on systems that support fork call
|
|
||||||
parser.add_argument(
|
|
||||||
"-d",
|
|
||||||
"--daemon",
|
|
||||||
action="store_true",
|
|
||||||
dest="daemon",
|
|
||||||
help="run program as a daemon in background",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-c",
|
|
||||||
"--conffile",
|
|
||||||
dest="conffile",
|
|
||||||
default="/etc/{0}/{0}.conf".format(programname),
|
|
||||||
help="application configuration file",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-f",
|
"-f",
|
||||||
"--logfile",
|
"--logfile",
|
||||||
dest="logfile",
|
dest="logfile",
|
||||||
help="save log entries to this file",
|
help="save log entries to this file",
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Insert more arguments
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-v",
|
"-v",
|
||||||
"--verbose",
|
"--verbose",
|
||||||
@@ -292,68 +286,75 @@ if exists(open_source_licenses):
|
|||||||
dest="oss_licenses",
|
dest="oss_licenses",
|
||||||
help="print packed open-source-licenses and exit",
|
help="print packed open-source-licenses and exit",
|
||||||
)
|
)
|
||||||
pargs = parser.parse_args()
|
|
||||||
|
|
||||||
# Process open-source-licenses argument, if set (only affects bundled apps)
|
|
||||||
if "oss_licenses" in pargs and pargs.oss_licenses:
|
|
||||||
with open(open_source_licenses, "r") as fh:
|
|
||||||
sys.stdout.write(fh.read())
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# Check important objects and set to default if they do not exist
|
def init_app(logger_std_output: StdLogOutput = StdLogOutput.STDOUT):
|
||||||
if "daemon" not in pargs:
|
global pargs
|
||||||
pargs.daemon = False
|
pargs = parser.parse_args()
|
||||||
if "verbose" not in pargs:
|
|
||||||
pargs.verbose = 0
|
|
||||||
|
|
||||||
# Check if the program should run as a daemon
|
|
||||||
if pargs.daemon:
|
|
||||||
# Check if daemon is already running
|
|
||||||
if exists(pidfile):
|
|
||||||
logger.error("Program already running as daemon. Check '{0}'".format(pidfile))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Fork to daemon
|
|
||||||
from os import fork
|
|
||||||
|
|
||||||
pid = fork()
|
|
||||||
if pid > 0:
|
|
||||||
# Main process waits for exit till startup is complete
|
|
||||||
from os import kill
|
|
||||||
from signal import SIGKILL, SIGTERM, signal
|
|
||||||
|
|
||||||
# Catch the TERM signal, which will be sent from the forked process after startup_complete
|
|
||||||
signal(SIGTERM, lambda number, frame: _daemon_started_up.set())
|
|
||||||
|
|
||||||
# Use the default timeout of 90 seconds from systemd also for the '--daemon' flag
|
|
||||||
if not _daemon_started_up.wait(90.0 + _extend_daemon_startup_timeout):
|
|
||||||
sys.stderr.write(
|
|
||||||
"Run into startup complete timout! Killing fork and exit main process\n"
|
|
||||||
)
|
|
||||||
kill(pid, SIGKILL)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Main process writes pidfile with pid of forked process
|
|
||||||
with open(pidfile, "w") as f:
|
|
||||||
f.write(str(pid))
|
|
||||||
|
|
||||||
|
# Process open-source-licenses argument, if set (only affects bundled apps)
|
||||||
|
if "oss_licenses" in pargs and pargs.oss_licenses:
|
||||||
|
with open(open_source_licenses, "r") as fh:
|
||||||
|
sys.stdout.write(fh.read())
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Check important objects and set to default if they do not exist
|
||||||
|
if "daemon" not in pargs:
|
||||||
|
pargs.daemon = False
|
||||||
|
if "verbose" not in pargs:
|
||||||
|
pargs.verbose = 0
|
||||||
|
|
||||||
# Get absolute paths
|
# Check if the program should run as a daemon
|
||||||
pwd = abspath(".")
|
if pargs.daemon:
|
||||||
|
# Check if the daemon is already running
|
||||||
|
if exists(pidfile):
|
||||||
|
logger.error("Program already running as daemon. Check '{0}'".format(pidfile))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Configure logger
|
# Fork to daemon
|
||||||
if "logfile" in pargs and pargs.logfile is not None and dirname(pargs.logfile) == "":
|
from os import fork
|
||||||
pargs.logfile = join(pwd, pargs.logfile)
|
|
||||||
reconfigure_logger()
|
|
||||||
|
|
||||||
# Initialize configparser of globalconfig
|
pid = fork()
|
||||||
if "conffile" in pargs and dirname(pargs.conffile) == "":
|
if pid > 0:
|
||||||
pargs.conffile = join(pwd, pargs.conffile)
|
# The main process waits for exit till startup is complete
|
||||||
|
from os import kill
|
||||||
|
from signal import SIGKILL, SIGTERM, signal
|
||||||
|
|
||||||
# Load configuration - Comment out, if you do that in your own program
|
# Catch the TERM signal, which will be sent from the forked process after startup_complete
|
||||||
# reload_conf()
|
signal(SIGTERM, lambda number, frame: _daemon_started_up.set())
|
||||||
|
|
||||||
# Log PID for development purposes
|
# Use the default timeout of 90 seconds from systemd also for the '--daemon' flag
|
||||||
logger.debug("Running with PID {}".format(getpid()))
|
if not _daemon_started_up.wait(90.0 + _extend_daemon_startup_timeout):
|
||||||
|
sys.stderr.write(
|
||||||
|
"Run into startup complete timout! Killing fork and exit main process\n"
|
||||||
|
)
|
||||||
|
kill(pid, SIGKILL)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Main process writes pidfile with pid of the forked process
|
||||||
|
with open(pidfile, "w") as f:
|
||||||
|
f.write(str(pid))
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Get absolute paths
|
||||||
|
pwd = abspath(".")
|
||||||
|
|
||||||
|
# Configure logger
|
||||||
|
if "logfile" in pargs and pargs.logfile is not None and dirname(pargs.logfile) == "":
|
||||||
|
pargs.logfile = join(pwd, pargs.logfile)
|
||||||
|
reconfigure_logger(logger_std_output)
|
||||||
|
|
||||||
|
# Initialize configparser of globalconfig
|
||||||
|
if "conffile" in pargs and dirname(pargs.conffile) == "":
|
||||||
|
pargs.conffile = join(pwd, pargs.conffile)
|
||||||
|
|
||||||
|
# Load configuration - Comment out, if you do that in your own program
|
||||||
|
# reload_conf()
|
||||||
|
|
||||||
|
# Log PID for development purposes
|
||||||
|
logger.debug("Running with PID {}".format(getpid()))
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize global config
|
||||||
|
# init_app()
|
||||||
|
|||||||
Reference in New Issue
Block a user