From c2178aa9e7af2bff38af7f62df07b0858b66abeb Mon Sep 17 00:00:00 2001 From: David Timber Date: Mon, 14 Apr 2025 19:37:42 +0200 Subject: Change command invocation semantics to ... `python -m palhm` --- src/palhm.py | 220 ------------------------------- src/palhm/__main__.py | 220 +++++++++++++++++++++++++++++++ src/palhm/conf/crontab | 4 +- src/palhm/conf/palhm-boot-report.service | 2 +- 4 files changed, 223 insertions(+), 223 deletions(-) delete mode 100755 src/palhm.py create mode 100755 src/palhm/__main__.py (limited to 'src') diff --git a/src/palhm.py b/src/palhm.py deleted file mode 100755 index b0ce2d4..0000000 --- a/src/palhm.py +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2022 David Timber -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -import importlib -import logging -import os -import sys -from abc import ABC, abstractmethod -from getopt import getopt - -import palhm -from palhm.exceptions import InvalidConfigError - - -class ProgConf: - conf = "/etc/palhm/palhm.jsonc" - cmd = None - override_vl = None - ctx = None - - def alloc_ctx (): - ProgConf.ctx = palhm.setup_conf(palhm.load_conf(ProgConf.conf)) - if not ProgConf.override_vl is None: - ProgConf.ctx.l.setLevel(ProgConf.override_vl) - -def err_unknown_cmd (): - sys.stderr.write("Unknown command. Run '" + sys.argv[0] + " help' for usage.\n") - exit(2) - -class Cmd (ABC): - @abstractmethod - def do_cmd (self): - ... - -class ConfigCmd (Cmd): - def __init__ (self, *args, **kwargs): - pass - - def do_cmd (self): - ProgConf.alloc_ctx() - print(ProgConf.ctx) - return 0 - - def print_help (): - print( -"Usage: " + sys.argv[0] + " config" + ''' -Load and parse config. Print the structure to stdout.''') - -class RunCmd (Cmd): - def __init__ (self, optlist, args): - self.optlist = optlist - self.args = args - - def do_cmd (self): - ProgConf.alloc_ctx() - - if self.args and self.args[0]: # empty string as "default" - task = self.args[0] - else: - task = palhm.DEFAULT.RUN_TASK.value - - ProgConf.ctx.task_map[task].run(ProgConf.ctx) - - return 0 - - def print_help (): - print( -"Usage: " + sys.argv[0] + " run [TASK]" + ''' -Run a task in config. Run the "''' + palhm.DEFAULT.RUN_TASK.value + -'''" task if [TASK] is not specified.''') - -class ModsCmd (Cmd): - def __init__ (self, *args, **kwargs): - pass - - def _walk_mods (self, path: str): - def is_mod_dir (path: str) -> bool: - try: - for i in os.scandir(path): - if i.name.startswith("__init__.py"): - return True - except NotADirectoryError: - pass - return False - - def is_mod_file (path: str) -> str: - if not os.path.isfile(path): - return None - - try: - pos = path.rindex(".") - if path[pos + 1:].startswith("py"): - return os.path.basename(path[:pos]) - except ValueError: - pass - - for i in os.scandir(path): - if i.name.startswith("_"): - continue - elif is_mod_dir(i.path): - print(i.name) - self._walk_mods(i.path) - else: - name = is_mod_file(i.path) - if name: - print(name) - - def do_cmd (self): - for i in importlib.util.find_spec("palhm.mod").submodule_search_locations: - self._walk_mods(i) - - return 0 - - def print_help (): - print( -"Usage: " + sys.argv[0] + " mods" + ''' -Prints the available modules to stdout.''') - -class BootReportCmd (Cmd): - def __init__ (self, *args, **kwargs): - pass - - def do_cmd (self): - ProgConf.alloc_ctx() - - if ProgConf.ctx.boot_report is None: - raise InvalidConfigError("'boot-report' not configured") - - return ProgConf.ctx.boot_report.do_send(ProgConf.ctx) - - def print_help (): - print( -"Usage: " + sys.argv[0] + " boot-report" + ''' -Send mail of boot report to recipients configured.''') - -class HelpCmd (Cmd): - def __init__ (self, optlist, args): - self.optlist = optlist - self.args = args - - def do_cmd (self): - if len(self.args) >= 2: - if not args[0] in CmdMap: - err_unknown_cmd() - else: - CmdMap[self.args[0]].print_help() - else: - HelpCmd.print_help() - - return 0 - - def print_help (): - print( -"Usage: " + sys.argv[0] + " [options] CMD [command options ...]" + ''' -Options: - -q Set the verbosity level to 0(CRITIAL). Overrides config - -v Increase the verbosity level by 1. Overrides config - -f FILE Load config from FILE instead of the hard-coded default -Config: ''' + ProgConf.conf + ''' -Commands: - run run a task - config load config and print the contents - help [CMD] print this message and exit normally if [CMD] is not specified. - Print usage of [CMD] otherwise - mods list available modules - boot-report mail boot report''') - - return 0 - -CmdMap = { - "config": ConfigCmd, - "run": RunCmd, - "help": HelpCmd, - "mods": ModsCmd, - "boot-report": BootReportCmd -} - -optlist, args = getopt(sys.argv[1:], "qvf:") -optkset = set() -for p in optlist: - optkset.add(p[0]) - -if "-v" in optkset and "-q" in optkset: - sys.stderr.write("Options -v and -q cannot not used together.\n") - exit(2) - -if not args or not args[0] in CmdMap: - err_unknown_cmd() - -for p in optlist: - if p[0] == "-q": ProgConf.override_vl = logging.ERROR - elif p[0] == "-v": - if ProgConf.override_vl is None: - ProgConf.override_vl = palhm.DEFAULT.VL.value - 10 - else: - ProgConf.override_vl -= 10 - elif p[0] == "-f": ProgConf.conf = p[1] - -logging.basicConfig(format = "%(name)s %(message)s") - -ProgConf.cmd = CmdMap[args[0]](optlist, args) -del args[0] -exit(ProgConf.cmd.do_cmd()) diff --git a/src/palhm/__main__.py b/src/palhm/__main__.py new file mode 100755 index 0000000..cdb21b7 --- /dev/null +++ b/src/palhm/__main__.py @@ -0,0 +1,220 @@ +# Copyright (c) 2022 David Timber +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import importlib +import logging +import os +import sys +from abc import ABC, abstractmethod +from getopt import getopt + +import palhm +from palhm.exceptions import InvalidConfigError + + +class ProgConf: + conf = "/etc/palhm/palhm.jsonc" + cmd = None + override_vl = None + ctx = None + + def alloc_ctx (): + ProgConf.ctx = palhm.setup_conf(palhm.load_conf(ProgConf.conf)) + if not ProgConf.override_vl is None: + ProgConf.ctx.l.setLevel(ProgConf.override_vl) + +def err_unknown_cmd (): + sys.stderr.write( + "Unknown command. Run '" + sys.executable + " -m palhm help' for usage.\n") + exit(2) + +class Cmd (ABC): + @abstractmethod + def do_cmd (self): + ... + +class ConfigCmd (Cmd): + def __init__ (self, *args, **kwargs): + pass + + def do_cmd (self): + ProgConf.alloc_ctx() + print(ProgConf.ctx) + return 0 + + def print_help (): + print( +"Usage: " + sys.executable + " -m palhm config" + ''' +Load and parse config. Print the structure to stdout.''') + +class RunCmd (Cmd): + def __init__ (self, optlist, args): + self.optlist = optlist + self.args = args + + def do_cmd (self): + ProgConf.alloc_ctx() + + if self.args and self.args[0]: # empty string as "default" + task = self.args[0] + else: + task = palhm.DEFAULT.RUN_TASK.value + + ProgConf.ctx.task_map[task].run(ProgConf.ctx) + + return 0 + + def print_help (): + print( +"Usage: " + sys.executable + " -m palhm run [TASK]" + ''' +Run a task in config. Run the "''' + palhm.DEFAULT.RUN_TASK.value + +'''" task if [TASK] is not specified.''') + +class ModsCmd (Cmd): + def __init__ (self, *args, **kwargs): + pass + + def _walk_mods (self, path: str): + def is_mod_dir (path: str) -> bool: + try: + for i in os.scandir(path): + if i.name.startswith("__init__.py"): + return True + except NotADirectoryError: + pass + return False + + def is_mod_file (path: str) -> str: + if not os.path.isfile(path): + return None + + try: + pos = path.rindex(".") + if path[pos + 1:].startswith("py"): + return os.path.basename(path[:pos]) + except ValueError: + pass + + for i in os.scandir(path): + if i.name.startswith("_"): + continue + elif is_mod_dir(i.path): + print(i.name) + self._walk_mods(i.path) + else: + name = is_mod_file(i.path) + if name: + print(name) + + def do_cmd (self): + for i in importlib.util.find_spec("palhm.mod").submodule_search_locations: + self._walk_mods(i) + + return 0 + + def print_help (): + print( +"Usage: " + sys.executable + " -m palhm mods" + ''' +Prints the available modules to stdout.''') + +class BootReportCmd (Cmd): + def __init__ (self, *args, **kwargs): + pass + + def do_cmd (self): + ProgConf.alloc_ctx() + + if ProgConf.ctx.boot_report is None: + raise InvalidConfigError("'boot-report' not configured") + + return ProgConf.ctx.boot_report.do_send(ProgConf.ctx) + + def print_help (): + print( +"Usage: " + sys.executable + " -m palhm boot-report" + ''' +Send mail of boot report to recipients configured.''') + +class HelpCmd (Cmd): + def __init__ (self, optlist, args): + self.optlist = optlist + self.args = args + + def do_cmd (self): + if len(self.args) >= 2: + if not args[0] in CmdMap: + err_unknown_cmd() + else: + CmdMap[self.args[0]].print_help() + else: + HelpCmd.print_help() + + return 0 + + def print_help (): + print( +"Usage: " + sys.executable + " -m palhm [options] CMD [command options ...]" + ''' +Options: + -q Set the verbosity level to 0(CRITIAL). Overrides config + -v Increase the verbosity level by 1. Overrides config + -f FILE Load config from FILE instead of the hard-coded default +Config: ''' + ProgConf.conf + ''' +Commands: + run run a task + config load config and print the contents + help [CMD] print this message and exit normally if [CMD] is not specified. + Print usage of [CMD] otherwise + mods list available modules + boot-report mail boot report''') + + return 0 + +CmdMap = { + "config": ConfigCmd, + "run": RunCmd, + "help": HelpCmd, + "mods": ModsCmd, + "boot-report": BootReportCmd +} + +optlist, args = getopt(sys.argv[1:], "qvf:") +optkset = set() +for p in optlist: + optkset.add(p[0]) + +if "-v" in optkset and "-q" in optkset: + sys.stderr.write("Options -v and -q cannot not used together.\n") + exit(2) + +if not args or not args[0] in CmdMap: + err_unknown_cmd() + +for p in optlist: + if p[0] == "-q": ProgConf.override_vl = logging.ERROR + elif p[0] == "-v": + if ProgConf.override_vl is None: + ProgConf.override_vl = palhm.DEFAULT.VL.value - 10 + else: + ProgConf.override_vl -= 10 + elif p[0] == "-f": ProgConf.conf = p[1] + +logging.basicConfig(format = "%(name)s %(message)s") + +ProgConf.cmd = CmdMap[args[0]](optlist, args) +del args[0] +exit(ProgConf.cmd.do_cmd()) diff --git a/src/palhm/conf/crontab b/src/palhm/conf/crontab index d0eeda6..9e59a16 100644 --- a/src/palhm/conf/crontab +++ b/src/palhm/conf/crontab @@ -1,6 +1,6 @@ # PALHM will produce stderr on exception. Mail the output to root MAILTO="root" # Run default task every Sunday at midnight -0 0 * * sun root /var/lib/PALHM/src/palhm.py -q run +0 0 * * sun root python -m palhm -q run # Check dnssec validity every hour -# 0 * * * * root systemd-run -qP -p User=palhm -p Nice=15 -p ProtectSystem=strict -p ReadOnlyPaths=/ -p PrivateDevices=true --wait /var/lib/PALHM/src/palhm.py -q run check-dnssec +# 0 * * * * root systemd-run -qP -p User=palhm -p Nice=15 -p ProtectSystem=strict -p ReadOnlyPaths=/ -p PrivateDevices=true --wait python -m palhm -q run check-dnssec diff --git a/src/palhm/conf/palhm-boot-report.service b/src/palhm/conf/palhm-boot-report.service index 0b4ba67..16a7c6d 100644 --- a/src/palhm/conf/palhm-boot-report.service +++ b/src/palhm/conf/palhm-boot-report.service @@ -4,7 +4,7 @@ After=postfix.service sendmail.service exim.service dovecot.service network-onli [Service] Type=oneshot -ExecStart=/var/lib/PALHM/src/palhm.py -q boot-report +ExecStart=/usr/bin/python -m palhm -q boot-report Nice=10 [Install] -- cgit v1.2.3-70-g09d2