1642 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1642 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env python3
 | 
						|
 | 
						|
import glob
 | 
						|
import importlib
 | 
						|
import math
 | 
						|
import os
 | 
						|
import platform
 | 
						|
import shutil
 | 
						|
import signal
 | 
						|
import socket
 | 
						|
import subprocess
 | 
						|
import sys
 | 
						|
import traceback
 | 
						|
 | 
						|
import seiscomp.shell
 | 
						|
 | 
						|
# Problem: if
 | 
						|
# import seiscomp.config
 | 
						|
# fails, then in any case a sometimes misleading exception
 | 
						|
# ImportError: No module named _config
 | 
						|
# is raised, even if the seiscomp._config module exists but for
 | 
						|
# another reason fails to import. We therefore...
 | 
						|
import seiscomp._config
 | 
						|
 | 
						|
# ...here explicitly to get a meaningful exception if this fails.
 | 
						|
 | 
						|
import seiscomp.config
 | 
						|
import seiscomp.kernel
 | 
						|
 | 
						|
 | 
						|
# request and optionally enforce user input
 | 
						|
# @param question The question to be answered.
 | 
						|
# @param default The default value to use if no input was made
 | 
						|
# @param options List or string of available options. If defined the input must
 | 
						|
#        match one of the options (unless a default is specified). If the input
 | 
						|
#        is invalid the question is repeated.
 | 
						|
def getInput(question, default=None, options=None):
 | 
						|
    def _default(text):
 | 
						|
        # print default value to previous line if no input was made
 | 
						|
        if default:
 | 
						|
            print(f"\033[F\033[{len(text) + 1}G{default}")
 | 
						|
        return default
 | 
						|
 | 
						|
    # no options: accept any type of input
 | 
						|
    if not options:
 | 
						|
        defaultStr = "" if default is None else f" [{default}]"
 | 
						|
        question = f"{question}{defaultStr}: "
 | 
						|
        return input(question) or _default(question)
 | 
						|
 | 
						|
    if default is not None:
 | 
						|
        default = str(default).lower()
 | 
						|
 | 
						|
    # options supplied: check and enforce input
 | 
						|
    opts = [str(o).lower() for o in options]
 | 
						|
    optStr = "/".join(o.upper() if o == default else o for o in opts)
 | 
						|
    question = f"{question} [{optStr}]: "
 | 
						|
    while True:
 | 
						|
        res = input(question)
 | 
						|
        if not res and default:
 | 
						|
            return _default(question)
 | 
						|
 | 
						|
        if res.lower() in opts:
 | 
						|
            return res.lower()
 | 
						|
 | 
						|
 | 
						|
if sys.platform == "darwin":
 | 
						|
    SysLibraryPathVar = "DYLD_FALLBACK_LIBRARY_PATH"
 | 
						|
    SysFrameworkPathVar = "DYLD_FALLBACK_FRAMEWORK_PATH"
 | 
						|
else:
 | 
						|
    SysLibraryPathVar = "LD_LIBRARY_PATH"
 | 
						|
    SysFrameworkPathVar = None
 | 
						|
 | 
						|
 | 
						|
def get_library_path():
 | 
						|
    if sys.platform == "darwin":
 | 
						|
        return LD_LIBRARY_PATH + ":" + DYLD_FALLBACK_FRAMEWORK_PATH
 | 
						|
 | 
						|
    return LD_LIBRARY_PATH
 | 
						|
 | 
						|
 | 
						|
def get_framework_path():
 | 
						|
    return DYLD_FALLBACK_FRAMEWORK_PATH
 | 
						|
 | 
						|
 | 
						|
# Python 3 compatible string check
 | 
						|
def is_string(variable):
 | 
						|
    try:
 | 
						|
        string_class = basestring
 | 
						|
    except NameError:
 | 
						|
        string_class = str
 | 
						|
 | 
						|
    return isinstance(variable, string_class)
 | 
						|
 | 
						|
 | 
						|
# ------------------------------------------------------------------------------
 | 
						|
# Helper functions
 | 
						|
# ------------------------------------------------------------------------------
 | 
						|
SIGTERM_SENT = False
 | 
						|
 | 
						|
 | 
						|
def sigterm_handler(_signum, _):
 | 
						|
    # pylint: disable=W0603
 | 
						|
    global SIGTERM_SENT
 | 
						|
    if not SIGTERM_SENT:
 | 
						|
        SIGTERM_SENT = True
 | 
						|
        os.killpg(0, signal.SIGTERM)
 | 
						|
 | 
						|
    sys.exit()
 | 
						|
 | 
						|
 | 
						|
def system(args):
 | 
						|
    with subprocess.Popen(args, shell=False, env=os.environ) as proc:
 | 
						|
        while True:
 | 
						|
            try:
 | 
						|
                return proc.wait()
 | 
						|
            except KeyboardInterrupt:
 | 
						|
                continue
 | 
						|
            except Exception as e:
 | 
						|
                try:
 | 
						|
                    proc.terminate()
 | 
						|
                except Exception:
 | 
						|
                    pass
 | 
						|
                sys.stderr.write(f"Exception: {e}\n")
 | 
						|
                continue
 | 
						|
 | 
						|
    # return subprocess.call(cmd, shell=True)
 | 
						|
 | 
						|
 | 
						|
def info(msg):
 | 
						|
    sys.stderr.write(f"info: {msg}\n")
 | 
						|
    sys.stderr.flush()
 | 
						|
 | 
						|
 | 
						|
def error(msg):
 | 
						|
    sys.stderr.write(f"error: {msg}\n")
 | 
						|
    sys.stderr.flush()
 | 
						|
 | 
						|
 | 
						|
def warning(msg):
 | 
						|
    sys.stderr.write(f"warning: {msg}\n")
 | 
						|
    sys.stderr.flush()
 | 
						|
 | 
						|
 | 
						|
# Returns a seiscomp.kernel.Module instance
 | 
						|
# from a given path with a given name
 | 
						|
def load_module(path):
 | 
						|
    modname0 = os.path.splitext(os.path.basename(path))[0].replace(".", "_")
 | 
						|
    modname = "__seiscomp_modules_" + modname0
 | 
						|
 | 
						|
    if modname in sys.modules:
 | 
						|
        mod = sys.modules[modname]
 | 
						|
    else:
 | 
						|
        if sys.path[0] != INIT_PATH:
 | 
						|
            sys.path.insert(0, INIT_PATH)
 | 
						|
        mod = importlib.import_module(modname0)
 | 
						|
        mod.__file__ = path
 | 
						|
 | 
						|
        # store it in sys.modules
 | 
						|
        sys.modules[modname] = mod
 | 
						|
 | 
						|
        module = mod.Module
 | 
						|
 | 
						|
    return module
 | 
						|
 | 
						|
 | 
						|
def module_key(module):
 | 
						|
    return (module.order, module.name)
 | 
						|
 | 
						|
 | 
						|
def load_init_modules(path):
 | 
						|
    modules = []
 | 
						|
 | 
						|
    if not os.path.isdir(path):
 | 
						|
        error(f"Failed to load modules, no such directory: {path}")
 | 
						|
        return modules
 | 
						|
 | 
						|
    files = glob.glob(os.path.join(path, "*.py"))
 | 
						|
    for f in files:
 | 
						|
        try:
 | 
						|
            pmod = load_module(f)
 | 
						|
        except Exception as exc:
 | 
						|
            warning(f"Failed to load module from file {f}: {exc}")
 | 
						|
            continue
 | 
						|
 | 
						|
        try:
 | 
						|
            mod = pmod(env)  # .Module(env)
 | 
						|
        except Exception as exc:
 | 
						|
            warning(f"Failed to instantiate module from file {f}: {exc}")
 | 
						|
            continue
 | 
						|
 | 
						|
        modules.append(mod)
 | 
						|
 | 
						|
    # mods = sorted(mods, key=lambda mod: mod.order)
 | 
						|
    modules = sorted(modules, key=module_key)
 | 
						|
 | 
						|
    return modules
 | 
						|
 | 
						|
 | 
						|
def get_module(name):
 | 
						|
    for module in mods:
 | 
						|
        if module.name == name:
 | 
						|
            return module
 | 
						|
    return None
 | 
						|
 | 
						|
 | 
						|
def has_module(name):
 | 
						|
    return get_module(name) is not None
 | 
						|
 | 
						|
 | 
						|
def dump_paths():
 | 
						|
    print("--------------------")
 | 
						|
    print(f'SEISCOMP_ROOT="{SEISCOMP_ROOT}"')
 | 
						|
    print(f"PATH=\"{os.environ['PATH']}\"")
 | 
						|
    print(f'{SysLibraryPathVar}="{os.environ[SysLibraryPathVar]}"')
 | 
						|
    if SysFrameworkPathVar:
 | 
						|
        print(f'{SysFrameworkPathVar}="{os.environ[SysFrameworkPathVar]}"')
 | 
						|
    print(f'PYTHONPATH="{sys.path}"')
 | 
						|
    print(f'CWD="{os.getcwd()}"')
 | 
						|
    print("--------------------")
 | 
						|
 | 
						|
 | 
						|
# Returns whether a module should run or not. It simply returns if its
 | 
						|
# runfile exists.
 | 
						|
def shouldModuleRun(mod_name):
 | 
						|
    return os.path.exists(env.runFile(mod_name))
 | 
						|
 | 
						|
 | 
						|
def touch(filename):
 | 
						|
    try:
 | 
						|
        with open(filename, "w", encoding="utf8") as _:
 | 
						|
            pass
 | 
						|
    except OSError as exc:
 | 
						|
        error(str(exc))
 | 
						|
 | 
						|
 | 
						|
def start_module(mod):
 | 
						|
    try:
 | 
						|
        r = mod.start()
 | 
						|
        if r is None:
 | 
						|
            # Not supported
 | 
						|
            return 1
 | 
						|
        # Create runfile
 | 
						|
        touch(env.runFile(mod.name))
 | 
						|
        return r
 | 
						|
    except Exception as e:
 | 
						|
        error(f"Failed to start {mod.name}: {str(e)}")
 | 
						|
        return 1
 | 
						|
 | 
						|
 | 
						|
def stop_module(mod):
 | 
						|
    returncode = 0
 | 
						|
    try:
 | 
						|
        r = mod.stop()
 | 
						|
        if r is None:
 | 
						|
            # Not supported
 | 
						|
            return 1
 | 
						|
        elif type(r) == type(True):
 | 
						|
            # Boolean type: True = success, False = error
 | 
						|
            if not r:
 | 
						|
                returncode = 1
 | 
						|
        elif type(r) == type(0):
 | 
						|
            # Integer type: 0 = success, error otherwise
 | 
						|
            if r:
 | 
						|
                returncode = 1
 | 
						|
        else:
 | 
						|
            # Any other type: Truthy = success, Falsy = error
 | 
						|
            if not r:
 | 
						|
                returncode = 1
 | 
						|
    except Exception as e:
 | 
						|
        error(f"Failed to stop {mod.name}: {str(e)}")
 | 
						|
        return 1
 | 
						|
 | 
						|
    # Delete runfile and pidfile
 | 
						|
    try:
 | 
						|
        runFile = env.runFile(mod.name)
 | 
						|
        if os.path.isfile(runFile):
 | 
						|
            os.remove(runFile)
 | 
						|
        pidFile = f"{runFile.split('.')[0]}.pid"
 | 
						|
        if os.path.isfile(pidFile):
 | 
						|
            os.remove(pidFile)
 | 
						|
    except BaseException:
 | 
						|
        returncode = 1
 | 
						|
 | 
						|
    return returncode
 | 
						|
 | 
						|
 | 
						|
def start_kernel_modules():
 | 
						|
    for mod in mods:
 | 
						|
        if isinstance(mod, seiscomp.kernel.CoreModule):
 | 
						|
            return start_module(mod)
 | 
						|
 | 
						|
    return 1
 | 
						|
 | 
						|
 | 
						|
def stop_kernel_modules():
 | 
						|
    for mod in reversed(mods):
 | 
						|
        if isinstance(mod, seiscomp.kernel.CoreModule):
 | 
						|
            return stop_module(mod)
 | 
						|
 | 
						|
    return 1
 | 
						|
 | 
						|
 | 
						|
def detectOS():
 | 
						|
    OSReleaseMap = {"centos": "rhel", "rocky": "rhel", "raspbian": "debian"}
 | 
						|
 | 
						|
    try:
 | 
						|
        arch = platform.machine()
 | 
						|
    except BaseException:
 | 
						|
        arch = "x86_64"
 | 
						|
 | 
						|
    data = {}
 | 
						|
    with open("/etc/os-release", "r", encoding="utf8") as f:
 | 
						|
        for line in f:
 | 
						|
            toks = line.split("=")
 | 
						|
            if len(toks) != 2:
 | 
						|
                continue
 | 
						|
 | 
						|
            data[toks[0].strip().upper()] = toks[1].strip()
 | 
						|
 | 
						|
    osID = OSReleaseMap.get(data["ID"].strip('"'))
 | 
						|
    if not osID:
 | 
						|
        osID = data["ID"].strip('"')
 | 
						|
 | 
						|
    version = data["VERSION_ID"].strip('"')
 | 
						|
    if osID == "rhel":
 | 
						|
        try:
 | 
						|
            version = str(math.floor(float(version)))
 | 
						|
        except Exception:
 | 
						|
            pass
 | 
						|
 | 
						|
    name = data["NAME"].strip('"')
 | 
						|
    return name, osID, version, arch
 | 
						|
 | 
						|
 | 
						|
# ------------------------------------------------------------------------------
 | 
						|
# Commandline action handler
 | 
						|
# ------------------------------------------------------------------------------
 | 
						|
def on_setup(args, flags):
 | 
						|
    # pylint: disable=C0415,W0621
 | 
						|
    import seiscomp.setup
 | 
						|
 | 
						|
    if "stdin" in flags:
 | 
						|
        cfg = seiscomp.config.Config()
 | 
						|
        if not cfg.readConfig("-"):
 | 
						|
            error("invalid configuration from stdin")
 | 
						|
            return 1
 | 
						|
    else:
 | 
						|
        setup = seiscomp.setup.Simple(args)
 | 
						|
        cfg = setup.run(env)
 | 
						|
 | 
						|
    retCode = 0
 | 
						|
 | 
						|
    for mod in config_mods:
 | 
						|
        if len(args) == 0 or mod.name in args:
 | 
						|
            try:
 | 
						|
                hasSetupHandler = callable(getattr(mod, "setup"))
 | 
						|
            except BaseException:
 | 
						|
                hasSetupHandler = False
 | 
						|
 | 
						|
            if hasSetupHandler:
 | 
						|
                print(f"* setup {mod.name}")
 | 
						|
                if mod.setup(cfg) != 0:
 | 
						|
                    error(f"module '{mod.name}' failed to setup")
 | 
						|
                    retCode = 1
 | 
						|
 | 
						|
    if retCode == 0:
 | 
						|
        runpath = os.path.join(SEISCOMP_ROOT, "var", "run")
 | 
						|
        if not os.path.exists(runpath):
 | 
						|
            try:
 | 
						|
                os.makedirs(runpath)
 | 
						|
            except BaseException:
 | 
						|
                error(f"failed to create directory: {runpath}")
 | 
						|
 | 
						|
        statfile = os.path.join(runpath, "seiscomp.init")
 | 
						|
        if not os.path.exists(statfile):
 | 
						|
            try:
 | 
						|
                with open(statfile, "w", encoding="utf8") as _:
 | 
						|
                    pass
 | 
						|
            except BaseException:
 | 
						|
                error(f"failed to create status file: {statfile}")
 | 
						|
 | 
						|
    return retCode
 | 
						|
 | 
						|
 | 
						|
def on_setup_help(_):
 | 
						|
    print("Initialize the configuration of all available modules. Each module")
 | 
						|
    print("implements its own setup handler which is called at this point. The")
 | 
						|
    print("initialization takes the installation directory into account and")
 | 
						|
    print("should be repeated when copying the system to another directory.")
 | 
						|
    print("NOTE:")
 | 
						|
    print("Setup might overwrite already made settings with default values.")
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_shell(_args, _):
 | 
						|
    shell = seiscomp.shell.CLI()
 | 
						|
    try:
 | 
						|
        shell.run(env)
 | 
						|
    except Exception as e:
 | 
						|
        error(str(e))
 | 
						|
        return 1
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_shell_help(_):
 | 
						|
    print("Launches the SeisComP shell, a command-line interface which allows")
 | 
						|
    print("to manage modules configurations and bindings.")
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_enable(args, _):
 | 
						|
    if not args:
 | 
						|
        error("module name required")
 | 
						|
        return 1
 | 
						|
 | 
						|
    for name in args:
 | 
						|
        modName = get_module(name)
 | 
						|
        if modName is None:
 | 
						|
            error(f"{name} is not available")
 | 
						|
        elif isinstance(modName, seiscomp.kernel.CoreModule):
 | 
						|
            error(f"{name} is a kernel module and is enabled automatically")
 | 
						|
        else:
 | 
						|
            try:
 | 
						|
                r = modName.enable()
 | 
						|
                if r is None:
 | 
						|
                    # Not supported
 | 
						|
                    return 1
 | 
						|
            except:
 | 
						|
                pass
 | 
						|
            env.enableModule(name)
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_enable_help(_):
 | 
						|
    print("Enables all given modules to be started when 'seiscomp start' is")
 | 
						|
    print("invoked without a module list.")
 | 
						|
    print()
 | 
						|
    print("Examples:")
 | 
						|
    print("seiscomp enable seedlink slarchive")
 | 
						|
 | 
						|
 | 
						|
def on_disable(args, _):
 | 
						|
    if not args:
 | 
						|
        error("module name required")
 | 
						|
        return 1
 | 
						|
 | 
						|
    for name in args:
 | 
						|
        modName = get_module(name)
 | 
						|
        if modName is None:
 | 
						|
            error(f"{modName} is not available")
 | 
						|
        elif isinstance(modName, seiscomp.kernel.CoreModule):
 | 
						|
            error(f"{name} is a kernel module and cannot be disabled")
 | 
						|
        else:
 | 
						|
            env.disableModule(name)
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_disable_help(_):
 | 
						|
    print("Disables all given modules. See 'enable'.")
 | 
						|
    print()
 | 
						|
    print("Examples:")
 | 
						|
    print("seiscomp disable seedlink slarchive")
 | 
						|
 | 
						|
 | 
						|
def on_start(args, _):
 | 
						|
    cntStarted = 0
 | 
						|
    if not args:
 | 
						|
        if start_kernel_modules() == 0:
 | 
						|
            cntStarted += 1
 | 
						|
        for mod in mods:
 | 
						|
            # Kernel modules have been started already
 | 
						|
            if isinstance(mod, seiscomp.kernel.CoreModule):
 | 
						|
                continue
 | 
						|
            # Module in autorun?
 | 
						|
            if env.isModuleEnabled(mod.name):
 | 
						|
                if start_module(mod) == 0:
 | 
						|
                    cntStarted += 1
 | 
						|
    else:
 | 
						|
        for mod in mods:
 | 
						|
            if mod.name in args or len(args) == 0:
 | 
						|
                if start_module(mod) == 0:
 | 
						|
                    cntStarted += 1
 | 
						|
 | 
						|
    if not useCSV:
 | 
						|
        print(f"Summary: {cntStarted} modules started")
 | 
						|
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_start_help(_):
 | 
						|
    print("Starts all enabled modules or a list of modules given.")
 | 
						|
    print()
 | 
						|
    print("Examples:")
 | 
						|
    print("seiscomp start")
 | 
						|
    print("seiscomp start seedlink slarchive")
 | 
						|
 | 
						|
 | 
						|
def on_stop(args, _):
 | 
						|
    cntStopped = 0
 | 
						|
    if not args:
 | 
						|
        for mod in reversed(mods):
 | 
						|
            # Kernel modules will be stopped latter
 | 
						|
            if isinstance(mod, seiscomp.kernel.CoreModule):
 | 
						|
                continue
 | 
						|
            if stop_module(mod) == 0:
 | 
						|
                cntStopped += 1
 | 
						|
 | 
						|
        # Stop all kernel modules
 | 
						|
        if stop_kernel_modules() == 0:
 | 
						|
            cntStopped += 1
 | 
						|
    else:
 | 
						|
        for mod in reversed(mods):
 | 
						|
            if mod.name in args or len(args) == 0:
 | 
						|
                if stop_module(mod) == 0:
 | 
						|
                    cntStopped += 1
 | 
						|
 | 
						|
    if not useCSV:
 | 
						|
        print(f"Summary: {cntStopped} modules stopped")
 | 
						|
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_stop_help(_):
 | 
						|
    print("Stops all enabled modules or a list of modules given.")
 | 
						|
    print()
 | 
						|
    print("Examples:")
 | 
						|
    print("seiscomp stop")
 | 
						|
    print("seiscomp stop seedlink slarchive")
 | 
						|
 | 
						|
 | 
						|
def on_restart(args, flags):
 | 
						|
    on_stop(args, flags)
 | 
						|
    on_start(args, flags)
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_restart_help(_):
 | 
						|
    print("Restarts all enabled modules or a list of modules given.")
 | 
						|
    print("This command is equal to:")
 | 
						|
    print("seiscomp stop {args}")
 | 
						|
    print("seiscomp start {args}")
 | 
						|
    print()
 | 
						|
    print("Examples:")
 | 
						|
    print("seiscomp restart")
 | 
						|
    print("seiscomp restart seedlink slarchive")
 | 
						|
 | 
						|
 | 
						|
def on_reload(args, _):
 | 
						|
    if not args:
 | 
						|
        for mod in mods:
 | 
						|
            # Reload not supported by kernel modules
 | 
						|
            if isinstance(mod, seiscomp.kernel.CoreModule):
 | 
						|
                continue
 | 
						|
 | 
						|
            if shouldModuleRun(mod.name):
 | 
						|
                mod.reload()
 | 
						|
    else:
 | 
						|
        for mod in mods:
 | 
						|
            if mod.name in args or len(args) == 0:
 | 
						|
                mod.reload()
 | 
						|
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_reload_help(_):
 | 
						|
    print("Reloads all enabled modules or a list of modules given.")
 | 
						|
    print("This operation is module specific and implemented only for some")
 | 
						|
    print("modules.")
 | 
						|
    print()
 | 
						|
    print("Examples:")
 | 
						|
    print("seiscomp reload")
 | 
						|
    print("seiscomp reload fdsnws")
 | 
						|
 | 
						|
 | 
						|
def on_check(args, _):
 | 
						|
    cntStarted = 0
 | 
						|
    for mod in mods:
 | 
						|
        if mod.name in args or len(args) == 0:
 | 
						|
            if shouldModuleRun(mod.name):
 | 
						|
                cntStarted += 1
 | 
						|
                mod.check()
 | 
						|
 | 
						|
    if not useCSV:
 | 
						|
        print(f"Summary: {cntStarted} started modules checked")
 | 
						|
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_check_help(_):
 | 
						|
    print("Checks if a started module is still running. If not, it is")
 | 
						|
    print("restarted. If no modules are given, all started modules are")
 | 
						|
    print("checked.")
 | 
						|
    print()
 | 
						|
    print("Examples:")
 | 
						|
    print("$ seiscomp check seedlink")
 | 
						|
    print("seedlink is already running")
 | 
						|
 | 
						|
 | 
						|
def on_exec(args, _):
 | 
						|
    if len(args) < 1:
 | 
						|
        error("no module name given")
 | 
						|
        return False
 | 
						|
 | 
						|
    # Change back into the working dir
 | 
						|
    env.chback()
 | 
						|
    return system(args)
 | 
						|
 | 
						|
 | 
						|
def on_exec_help(_):
 | 
						|
    print("Executes a command like calling a command from command-line.")
 | 
						|
    print("It will setup all paths and execute the command.")
 | 
						|
    print("'seiscomp run' will block until the command terminates.")
 | 
						|
    print("Example:")
 | 
						|
    print("seiscomp exec scolv")
 | 
						|
 | 
						|
 | 
						|
def on_list(args, _):
 | 
						|
    if len(args) < 1:
 | 
						|
        error("expected argument: {modules|aliases|enabled|disabled|started}")
 | 
						|
        return 1
 | 
						|
 | 
						|
    if args[0] == "modules":
 | 
						|
        found = 0
 | 
						|
        for mod in mods:
 | 
						|
            if env.isModuleEnabled(mod.name) or isinstance(
 | 
						|
                mod, seiscomp.kernel.CoreModule
 | 
						|
            ):
 | 
						|
                state = "enabled"
 | 
						|
            else:
 | 
						|
                state = "disabled"
 | 
						|
            found += 1
 | 
						|
            print(f"{mod.name} is {state}")
 | 
						|
 | 
						|
        if not useCSV:
 | 
						|
            print(f"Summary: {found} modules reported")
 | 
						|
 | 
						|
        return 0
 | 
						|
 | 
						|
    if args[0] == "aliases":
 | 
						|
        try:
 | 
						|
            with open(ALIAS_FILE, "r", encoding="utf8") as f:
 | 
						|
                lines = [line.rstrip() for line in f.readlines()]
 | 
						|
        except OSError:
 | 
						|
            print(f"No aliases found: Cannot open file {ALIAS_FILE}", file=sys.stderr)
 | 
						|
            return 1
 | 
						|
 | 
						|
        for line in lines:
 | 
						|
            if line.lstrip().startswith("#") or not line.strip():
 | 
						|
                continue
 | 
						|
            toks = [t.strip() for t in line.split("=")]
 | 
						|
            # Remove invalid lines
 | 
						|
            if len(toks) != 2:
 | 
						|
                continue
 | 
						|
            if useCSV:
 | 
						|
                print(f"{toks[0]};{toks[1]}")
 | 
						|
            else:
 | 
						|
                print(f"{toks[0]} -> {toks[1]}")
 | 
						|
 | 
						|
        return 0
 | 
						|
 | 
						|
    if args[0] == "enabled":
 | 
						|
        found = 0
 | 
						|
        for mod in mods:
 | 
						|
            if env.isModuleEnabled(mod.name) or isinstance(
 | 
						|
                mod, seiscomp.kernel.CoreModule
 | 
						|
            ):
 | 
						|
                print(mod.name)
 | 
						|
                found += 1
 | 
						|
 | 
						|
        if not useCSV:
 | 
						|
            print(f"Summary: {found} modules enabled")
 | 
						|
 | 
						|
        return 0
 | 
						|
 | 
						|
    if args[0] == "disabled":
 | 
						|
        found = 0
 | 
						|
        for mod in mods:
 | 
						|
            if not env.isModuleEnabled(mod.name) and not isinstance(
 | 
						|
                mod, seiscomp.kernel.CoreModule
 | 
						|
            ):
 | 
						|
                print(mod.name)
 | 
						|
                found += 1
 | 
						|
 | 
						|
        if not useCSV:
 | 
						|
            print(f"Summary: {found} modules disabled")
 | 
						|
 | 
						|
        return 0
 | 
						|
 | 
						|
    if args[0] == "started":
 | 
						|
        found = 0
 | 
						|
        for mod in mods:
 | 
						|
            if shouldModuleRun(mod.name):
 | 
						|
                print(mod.name)
 | 
						|
                found += 1
 | 
						|
 | 
						|
        if not useCSV:
 | 
						|
            print(f"Summary: {found} modules started")
 | 
						|
 | 
						|
        return 0
 | 
						|
 | 
						|
    error("wrong argument: {modules|aliases|enabled|disabled|started} expected")
 | 
						|
    return 1
 | 
						|
 | 
						|
 | 
						|
def on_list_help(_):
 | 
						|
    print("Prints the result of a query. 5 queries are currently supported:")
 | 
						|
    print(" modules: lists all existing modules")
 | 
						|
    print(" aliases: lists all existing aliases")
 | 
						|
    print(" enabled: lists all enabled modules")
 | 
						|
    print(" disabled: lists all disabled modules")
 | 
						|
    print(" started: lists all started modules")
 | 
						|
    print()
 | 
						|
    print("Examples:")
 | 
						|
    print("$ seiscomp list aliases")
 | 
						|
    print("l1autopick -> scautopick")
 | 
						|
 | 
						|
 | 
						|
def on_status(args, _):
 | 
						|
    found = 0
 | 
						|
    if len(args) > 0 and args[0] == "enabled":
 | 
						|
        for mod in mods:
 | 
						|
            if env.isModuleEnabled(mod.name) or isinstance(
 | 
						|
                mod, seiscomp.kernel.CoreModule
 | 
						|
            ):
 | 
						|
                mod.status(shouldModuleRun(mod.name))
 | 
						|
                found += 1
 | 
						|
 | 
						|
        if not useCSV:
 | 
						|
            print(f"Summary: {found} modules enabled")
 | 
						|
 | 
						|
        return 0
 | 
						|
 | 
						|
    if len(args) > 0 and args[0] == "started":
 | 
						|
        for mod in mods:
 | 
						|
            if shouldModuleRun(mod.name):
 | 
						|
                mod.status(shouldModuleRun(mod.name))
 | 
						|
                found += 1
 | 
						|
 | 
						|
        if not useCSV:
 | 
						|
            print(f"Summary: {found} modules started")
 | 
						|
 | 
						|
        return 0
 | 
						|
 | 
						|
    for mod in mods:
 | 
						|
        if mod.name in args or len(args) == 0:
 | 
						|
            mod.status(shouldModuleRun(mod.name))
 | 
						|
            found += 1
 | 
						|
 | 
						|
    if not useCSV:
 | 
						|
        print(f"Summary: {found} modules reported")
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_status_help(_):
 | 
						|
    print("Prints the status of ")
 | 
						|
    print(" * all modules")
 | 
						|
    print(" * all enabled modules")
 | 
						|
    print(" * all started modules")
 | 
						|
    print(" * a list of modules")
 | 
						|
    print("and gives a warning if a module should run but doesn't.")
 | 
						|
    print("This command supports csv formatted output via '--csv' switch.")
 | 
						|
    print()
 | 
						|
    print("Examples:")
 | 
						|
    print("$ seiscomp status started")
 | 
						|
    print("$ seiscomp status enabled")
 | 
						|
    print("scmaster             is not running [WARNING]")
 | 
						|
    print("$ seiscomp status scautopick")
 | 
						|
    print("scautopick           is not running")
 | 
						|
    print("$ seiscomp --csv status scautopick")
 | 
						|
    print("scautopick;0;0;0")
 | 
						|
    print()
 | 
						|
    print("CSV format:")
 | 
						|
    print(" column 1: module name")
 | 
						|
    print(" column 2: running flag")
 | 
						|
    print(" column 3: should run flag")
 | 
						|
    print(" column 4: enabled flag")
 | 
						|
 | 
						|
 | 
						|
def on_print(args, _):
 | 
						|
    if len(args) < 1:
 | 
						|
        error("expected argument: {crontab|env|variables}")
 | 
						|
        return 1
 | 
						|
 | 
						|
    if args[0] == "crontab":
 | 
						|
        path = os.path.join(env.SEISCOMP_ROOT, "bin", "seiscomp")
 | 
						|
        print(f"*/3 * * * * {path} check >/dev/null 2>&1")
 | 
						|
        for mod in mods:
 | 
						|
            mod.printCrontab()
 | 
						|
 | 
						|
    elif args[0] == "env":
 | 
						|
        print(f'export SEISCOMP_ROOT="{SEISCOMP_ROOT}"')
 | 
						|
        print(f'export PATH="{BIN_PATH}:$PATH"')
 | 
						|
        print(f'export {SysLibraryPathVar}="{get_library_path()}:${SysLibraryPathVar}"')
 | 
						|
        if sys.platform == "darwin":
 | 
						|
            print(
 | 
						|
                f'export {SysFrameworkPathVar}="{get_framework_path()}:'
 | 
						|
                f'${SysFrameworkPathVar}"'
 | 
						|
            )
 | 
						|
 | 
						|
        print(f'export PYTHONPATH="{PYTHONPATH}:$PYTHONPATH"')
 | 
						|
        print(f'export MANPATH="{MANPATH}:$MANPATH"')
 | 
						|
        print(f'source "{SEISCOMP_ROOT}/share/shell-completion/seiscomp.bash"')
 | 
						|
        hostenv = os.path.join(
 | 
						|
            SEISCOMP_ROOT, "etc", "env", "by-hostname", socket.gethostname()
 | 
						|
        )
 | 
						|
        if os.path.isfile(hostenv):
 | 
						|
            print(f"source {hostenv}")
 | 
						|
 | 
						|
    elif args[0] == "variables":
 | 
						|
        try:
 | 
						|
            # pylint: disable=C0415
 | 
						|
            import seiscomp.system as scsys
 | 
						|
        except ImportError as e:
 | 
						|
            # unavailable without trunk installation, e.g., seedlink-only
 | 
						|
            error(
 | 
						|
                "wrong argument: variables is unavailable without trunk installation\n"
 | 
						|
                f"  + {e}"
 | 
						|
            )
 | 
						|
            return 1
 | 
						|
        sysenv = scsys.Environment.Instance()
 | 
						|
        print(
 | 
						|
            f"""@HOMEDIR@          : {sysenv.homeDir()}
 | 
						|
@ROOTDIR@          : {sysenv.installDir()}
 | 
						|
@SYSTEMCONFIGDIR@  : {sysenv.appConfigDir()}
 | 
						|
@DEFAULTCONFIGDIR@ : {sysenv.globalConfigDir()}
 | 
						|
@KEYDIR@           : {sysenv.appConfigDir()}/key
 | 
						|
@DATADIR@          : {sysenv.shareDir()}
 | 
						|
@CONFIGDIR@        : {sysenv.configDir()}
 | 
						|
@LOGDIR@           : {sysenv.logDir()}"""
 | 
						|
        )
 | 
						|
 | 
						|
    else:
 | 
						|
        error("wrong argument: {crontab|env|variables} expected")
 | 
						|
        return 1
 | 
						|
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_print_help(_):
 | 
						|
    print("seiscomp print {crontab|env|variables}")
 | 
						|
    print(" crontab: suggests crontab entries of all registered or given modules.")
 | 
						|
    print(" env: prints environment variables necessary to run SeisComP modules.")
 | 
						|
    print(" variables: prints internal SeisComP variables.")
 | 
						|
    print()
 | 
						|
    print("Examples:")
 | 
						|
    print("Source SC environment into current bash session")
 | 
						|
    print("$ eval $(seiscomp/bin/seiscomp print env)")
 | 
						|
 | 
						|
 | 
						|
def on_install_deps_linux(args, _):
 | 
						|
    try:
 | 
						|
        name, release, version, arch = detectOS()
 | 
						|
    except BaseException as err:
 | 
						|
        print("*********************************************************************")
 | 
						|
        print("seiscomp was not able to figure out the installed distribution")
 | 
						|
        print("You need to check the documentation for required packages and install")
 | 
						|
        print("them manually.")
 | 
						|
        print(f"Error: {err}")
 | 
						|
        print("*********************************************************************")
 | 
						|
 | 
						|
        return 1
 | 
						|
 | 
						|
    print(f"Distribution: {name}-{version}-{arch}({release}-{version})")
 | 
						|
 | 
						|
    for n in range(version.count(".") + 1):
 | 
						|
        ver = version.rsplit(".", n)[0]
 | 
						|
        script_dir = os.path.join(
 | 
						|
            env.SEISCOMP_ROOT, "share", "deps", release.lower(), ver.lower()
 | 
						|
        )
 | 
						|
        if os.path.exists(script_dir):
 | 
						|
            break
 | 
						|
 | 
						|
    if not os.path.exists(script_dir):
 | 
						|
        print("*********************************************************************")
 | 
						|
        print("Sorry, the installed distribution is not supported.")
 | 
						|
        print("You need to check the documentation for required packages and install")
 | 
						|
        print("them manually.")
 | 
						|
        print("*********************************************************************")
 | 
						|
        return 1
 | 
						|
 | 
						|
    for pkg in args:
 | 
						|
        script = os.path.join(script_dir, "install-" + pkg + ".sh")
 | 
						|
        if not os.path.exists(script):
 | 
						|
            error(f"no handler available for package '{pkg}'")
 | 
						|
            return 1
 | 
						|
 | 
						|
        if system(["sudo", "sh", script]) != 0:
 | 
						|
            error("installation failed")
 | 
						|
            return 1
 | 
						|
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_install_deps(args, flags):
 | 
						|
    if not args:
 | 
						|
        error("expected package list: PKG1 [PKG2 [..]]")
 | 
						|
        print("Example: seiscomp install-deps base gui mariadb-server")
 | 
						|
        print("For a list of available packages issue: seiscomp help install-deps")
 | 
						|
 | 
						|
    if sys.platform.startswith("linux"):
 | 
						|
        return on_install_deps_linux(args, flags)
 | 
						|
 | 
						|
    error("unsupported platform")
 | 
						|
    print("*********************************************************************")
 | 
						|
    print("The platform you are currently running on is not supported to install")
 | 
						|
    print("dependencies automatically.")
 | 
						|
    print("You need to check the documentation for required packages and install")
 | 
						|
    print("them manually.")
 | 
						|
    print("*********************************************************************")
 | 
						|
    return 1
 | 
						|
 | 
						|
 | 
						|
def on_install_deps_help(_):
 | 
						|
    print("seiscomp install-deps PKG1 [PKG2 [..]]")
 | 
						|
    print("Installs OS dependencies to run SeisComP. This requires either a 'sudo'")
 | 
						|
    print("or root account. Available packages are:")
 | 
						|
    print("  base:   basic packages required by all installations")
 | 
						|
    print("  gui:    required by graphical user interfaces, e.g. on workstations")
 | 
						|
    print("  [mariadb,mysql,postgresql]-server:")
 | 
						|
    print("          database management system required by the machine running")
 | 
						|
    print("          the SeisComP messaging system (scmaster)")
 | 
						|
    print("  fdsnws: required for data sharing via the FDSN web services")
 | 
						|
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_update_config(args, _):
 | 
						|
    kernelModsStarted = False
 | 
						|
 | 
						|
    availableMods = {mod.name: mod for mod in config_mods}
 | 
						|
    selectedMods = args if args else list(availableMods.keys())
 | 
						|
    configuredMods = set()
 | 
						|
 | 
						|
    while selectedMods:
 | 
						|
        name = selectedMods.pop(0)
 | 
						|
        if name not in availableMods:
 | 
						|
            print(f"* module {name} not available")
 | 
						|
            continue
 | 
						|
        if name in configuredMods:
 | 
						|
            print(f"* module {name} already configured")
 | 
						|
            continue
 | 
						|
 | 
						|
        mod = availableMods[name]
 | 
						|
        if not kernelModsStarted and mod.requiresKernelModules():
 | 
						|
            print("* starting kernel modules")
 | 
						|
            start_kernel_modules()
 | 
						|
            kernelModsStarted = True
 | 
						|
 | 
						|
        print(f"* configure {name}")
 | 
						|
 | 
						|
        proxy = mod.updateConfigProxy()
 | 
						|
        if proxy:
 | 
						|
            if not is_string(proxy):
 | 
						|
                error(f"module {name} returned invalid proxy of type {type(proxy)}")
 | 
						|
                return 1
 | 
						|
            if proxy not in availableMods:
 | 
						|
                error(
 | 
						|
                    f"module {name} returned proxy {proxy} which is not available "
 | 
						|
                    "for configuration"
 | 
						|
                )
 | 
						|
                return 1
 | 
						|
            if proxy not in configuredMods and proxy not in selectedMods:
 | 
						|
                selectedMods.append(proxy)
 | 
						|
 | 
						|
        result = mod.updateConfig()
 | 
						|
 | 
						|
        try:
 | 
						|
            error_code = int(result)
 | 
						|
        except ValueError:
 | 
						|
            error(f"unexpected return type when updating configuration of {name}")
 | 
						|
            return 1
 | 
						|
 | 
						|
        if error_code:
 | 
						|
            error(f"updating configuration for {name} failed")
 | 
						|
            return 1
 | 
						|
 | 
						|
        configuredMods.add(name)
 | 
						|
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_update_config_help(_):
 | 
						|
    print("Updates the configuration of all available modules. This command")
 | 
						|
    print("will convert the etc/*.cfg to the modules native configuration")
 | 
						|
    print("including its bindings.")
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def on_alias(args, _):
 | 
						|
    if len(args) < 2:
 | 
						|
        error("expected arguments: {create|remove} ALIAS_NAME APP_NAME")
 | 
						|
        return 1
 | 
						|
 | 
						|
    aliasName = args[1]
 | 
						|
 | 
						|
    if args[0] == "create":
 | 
						|
        if len(args) != 3:
 | 
						|
            error("expected two arguments for create: ALIAS_NAME APP_NAME")
 | 
						|
            return 1
 | 
						|
 | 
						|
        mod = None
 | 
						|
        for module in mods:
 | 
						|
            if module.name == args[2]:
 | 
						|
                mod = module
 | 
						|
                break
 | 
						|
 | 
						|
        if not mod:
 | 
						|
            error(f"module '{args[2]}' not found")
 | 
						|
            return 1
 | 
						|
 | 
						|
        supportsAliases = False
 | 
						|
        try:
 | 
						|
            supportsAliases = mod.supportsAliases()
 | 
						|
        except BaseException:
 | 
						|
            pass
 | 
						|
 | 
						|
        if not supportsAliases:
 | 
						|
            error(f"module '{args[2]}' does not support aliases")
 | 
						|
            return 1
 | 
						|
 | 
						|
        if len(aliasName) > 20:
 | 
						|
            error(
 | 
						|
                f"rejecting alias name '{aliasName}' exceeding 20 characters since "
 | 
						|
                "bindings will not be written to database"
 | 
						|
            )
 | 
						|
            return 1
 | 
						|
 | 
						|
        mod2 = args[2]
 | 
						|
        if os.path.exists(os.path.join("bin", mod2)):
 | 
						|
            mod1 = os.path.join("bin", aliasName)
 | 
						|
        elif os.path.exists(os.path.join("sbin", mod2)):
 | 
						|
            mod1 = os.path.join("sbin", aliasName)
 | 
						|
        else:
 | 
						|
            error(f"no {mod2} binary found (neither bin nor sbin)")
 | 
						|
            return 1
 | 
						|
 | 
						|
        # create alias line in etc/descriptions/aliases
 | 
						|
        if not os.path.exists(DESC_PATH):
 | 
						|
            try:
 | 
						|
                os.makedirs(DESC_PATH)
 | 
						|
            except Exception:
 | 
						|
                error(f"failed to create directory: {DESC_PATH}")
 | 
						|
                return 1
 | 
						|
 | 
						|
        has_alias = False
 | 
						|
        lines = []
 | 
						|
        new_lines = []
 | 
						|
        try:
 | 
						|
            with open(ALIAS_FILE, "r", encoding="utf8") as f:
 | 
						|
                lines = [line.rstrip() for line in f.readlines()]
 | 
						|
 | 
						|
            for line in lines:
 | 
						|
                if line.lstrip().startswith("#") or not line.strip():
 | 
						|
                    # Keep comments or empty lines
 | 
						|
                    new_lines.append(line)
 | 
						|
                    continue
 | 
						|
                toks = [t.strip() for t in line.split("=")]
 | 
						|
                # Remove invalid lines
 | 
						|
                if len(toks) != 2:
 | 
						|
                    continue
 | 
						|
                if toks[0] == aliasName:
 | 
						|
                    has_alias = True
 | 
						|
                    break
 | 
						|
 | 
						|
                new_lines.append(line)
 | 
						|
        except BaseException:
 | 
						|
            pass
 | 
						|
 | 
						|
        print(f"Creating alias '{aliasName}' for '{mod2}':")
 | 
						|
        if has_alias:
 | 
						|
            warning(
 | 
						|
                f"  + {aliasName} is already registered as alias for {mod2} in "
 | 
						|
                "$SEISCOMP_ROOT/etc/descriptions/aliases"
 | 
						|
            )
 | 
						|
            warning("    do not register again but trying to link the required files")
 | 
						|
        else:
 | 
						|
            print(
 | 
						|
                f"  + registering alias '{aliasName}' in "
 | 
						|
                "$SEISCOMP_ROOT/etc/descriptions/aliases"
 | 
						|
            )
 | 
						|
 | 
						|
        # Check if target exists already
 | 
						|
        if os.path.exists(os.path.join(SEISCOMP_ROOT, mod1)):
 | 
						|
            warning(
 | 
						|
                f"  + link '{aliasName}' to '{mod2}' exists already in {SEISCOMP_ROOT}"
 | 
						|
                "/bin/"
 | 
						|
            )
 | 
						|
            warning("    do not link again")
 | 
						|
 | 
						|
        try:
 | 
						|
            with open(ALIAS_FILE, "w", encoding="utf8") as f:
 | 
						|
                new_lines.append(f"{aliasName} = {args[2]}")
 | 
						|
                f.write("\n".join(new_lines) + "\n")
 | 
						|
        except BaseException:
 | 
						|
            error(f"  + failed to open/create alias file: {ALIAS_FILE}")
 | 
						|
            return 1
 | 
						|
 | 
						|
        # create symlink of defaults from etc/defaults/mod1.cfg to etc/defaults/mod2.cfg
 | 
						|
        # use relative path to default_cfg2
 | 
						|
        cwdAlias = os.getcwd()
 | 
						|
        os.chdir(os.path.join(SEISCOMP_ROOT, "etc", "defaults"))
 | 
						|
        default_cfg1 = aliasName + ".cfg"
 | 
						|
        default_cfg2 = args[2] + ".cfg"
 | 
						|
        if os.path.exists(default_cfg2):
 | 
						|
            print(
 | 
						|
                f"  + linking default configuration: {default_cfg2} -> {default_cfg1}"
 | 
						|
            )
 | 
						|
            # - first: remove target
 | 
						|
            try:
 | 
						|
                os.remove(default_cfg1)
 | 
						|
            except BaseException:
 | 
						|
                pass
 | 
						|
            # create symlink
 | 
						|
            os.symlink(os.path.relpath(default_cfg2), default_cfg1)
 | 
						|
        else:
 | 
						|
            print("  + no default configuration to link")
 | 
						|
        # return to initial directory
 | 
						|
        os.chdir(cwdAlias)
 | 
						|
 | 
						|
        # create symlink from bin/mod1 to bin/mod2
 | 
						|
        # - first: remove target
 | 
						|
        try:
 | 
						|
            os.remove(os.path.join(SEISCOMP_ROOT, mod1))
 | 
						|
        except BaseException:
 | 
						|
            pass
 | 
						|
        print(f"  + creating alias to application: {mod2} -> {mod1}")
 | 
						|
        os.symlink(mod2, os.path.join(SEISCOMP_ROOT, mod1))
 | 
						|
 | 
						|
        # create symlink from etc/init/mod1.py to etc/init/mod2.py
 | 
						|
        cwdAlias = os.getcwd()
 | 
						|
        os.chdir(os.path.join(SEISCOMP_ROOT, "etc", "init"))
 | 
						|
        init1 = aliasName + ".py"
 | 
						|
        init2 = args[2] + ".py"
 | 
						|
        print(f"  + linking init script: {init2} -> {init1}")
 | 
						|
        # - first: remove target
 | 
						|
        try:
 | 
						|
            os.remove(init1)
 | 
						|
        except BaseException:
 | 
						|
            pass
 | 
						|
        # create symlink with relative path
 | 
						|
        os.symlink(os.path.relpath(init2), init1)
 | 
						|
        # return to initial directory
 | 
						|
        os.chdir(cwdAlias)
 | 
						|
 | 
						|
        return 0
 | 
						|
 | 
						|
    if args[0] == "remove":
 | 
						|
        if len(args) != 2:
 | 
						|
            error("expected one argument for remove: alias-name")
 | 
						|
            return 1
 | 
						|
 | 
						|
        print(f"Removing alias '{aliasName}':")
 | 
						|
        #  check and remove alias line in etc/descriptions/aliases
 | 
						|
        has_alias = False
 | 
						|
        lines = []
 | 
						|
        new_lines = []
 | 
						|
        try:
 | 
						|
            with open(ALIAS_FILE, "r", encoding="utf8") as f:
 | 
						|
                lines = [line.rstrip() for line in f.readlines()]
 | 
						|
 | 
						|
            for line in lines:
 | 
						|
                if line.lstrip().startswith("#") or not line.strip():
 | 
						|
                    # Keep comments or empty lines
 | 
						|
                    new_lines.append(line)
 | 
						|
                    continue
 | 
						|
                toks = [t.strip() for t in line.split("=")]
 | 
						|
                # Remove invalid lines
 | 
						|
                if len(toks) != 2:
 | 
						|
                    continue
 | 
						|
                if toks[0] == aliasName:
 | 
						|
                    has_alias = True
 | 
						|
                else:
 | 
						|
                    new_lines.append(line)
 | 
						|
        except BaseException:
 | 
						|
            pass
 | 
						|
 | 
						|
        if not has_alias:
 | 
						|
            print(f"  + {aliasName} is not defined as an alias")
 | 
						|
            if not interactiveMode:
 | 
						|
                print("  + remove related configuration with '--interactive'")
 | 
						|
                if len(lines) == len(new_lines):
 | 
						|
                    return 1
 | 
						|
 | 
						|
        try:
 | 
						|
            with open(ALIAS_FILE, "w", encoding="utf8") as f:
 | 
						|
                if len(lines) > 0:
 | 
						|
                    f.write("\n".join(new_lines) + "\n")
 | 
						|
        except OSError:
 | 
						|
            error(f"  + failed to open/create alias file: {ALIAS_FILE}")
 | 
						|
            return 1
 | 
						|
 | 
						|
        if not has_alias and not interactiveMode:
 | 
						|
            return 1
 | 
						|
 | 
						|
        # remove symlink from bin/mod1
 | 
						|
        if os.path.exists(os.path.join("bin", aliasName)):
 | 
						|
            sym_link = os.path.join("bin", aliasName)
 | 
						|
        elif os.path.exists(os.path.join("sbin", aliasName)):
 | 
						|
            sym_link = os.path.join("sbin", aliasName)
 | 
						|
        else:
 | 
						|
            sym_link = ""
 | 
						|
 | 
						|
        if sym_link:
 | 
						|
            print(f"  + removing alias application: {sym_link}")
 | 
						|
            try:
 | 
						|
                os.remove(os.path.join(SEISCOMP_ROOT, sym_link))
 | 
						|
            except BaseException:
 | 
						|
                pass
 | 
						|
 | 
						|
        # remove symlink from etc/init/mod1.py
 | 
						|
        init_scr = os.path.join("etc", "init", aliasName + ".py")
 | 
						|
        print(f"  + removing init script: {init_scr}")
 | 
						|
        try:
 | 
						|
            os.remove(os.path.join(SEISCOMP_ROOT, init_scr))
 | 
						|
        except BaseException:
 | 
						|
            pass
 | 
						|
 | 
						|
        #  delete defaults etc/defaults/mod1.cfg
 | 
						|
        default_cfg = os.path.join("etc", "defaults", aliasName + ".cfg")
 | 
						|
        print(f"  + removing default configuration: {SEISCOMP_ROOT}/{default_cfg}")
 | 
						|
        try:
 | 
						|
            os.remove(os.path.join(SEISCOMP_ROOT, default_cfg))
 | 
						|
        except BaseException as e:
 | 
						|
            error(f"    + could not remove {e}")
 | 
						|
 | 
						|
        if not interactiveMode:
 | 
						|
            warning(
 | 
						|
                f"No other configuration removed for '{aliasName}' - interactive"
 | 
						|
                " removal is supported by '--interactive'"
 | 
						|
            )
 | 
						|
            return 0
 | 
						|
 | 
						|
        # test module configuration files
 | 
						|
        # SYSTEMCONFIGDIR
 | 
						|
        cfg = os.path.join("etc", aliasName + ".cfg")
 | 
						|
        if os.path.isfile(cfg):
 | 
						|
            print(f"  + found module configuration file: {SEISCOMP_ROOT}/{cfg}")
 | 
						|
            answer = getInput("    + do you wish to remove it?", "n", "yn")
 | 
						|
            if answer == "y":
 | 
						|
                try:
 | 
						|
                    os.remove(cfg)
 | 
						|
                except Exception as e:
 | 
						|
                    error(f"    + could not remove '{e}' - try manually")
 | 
						|
 | 
						|
        # CONFIGDIR
 | 
						|
        cfg = os.path.join(os.path.expanduser("~"), ".seiscomp", aliasName + ".cfg")
 | 
						|
        if os.path.isfile(cfg):
 | 
						|
            print(f"  + found module configuration file: {cfg}")
 | 
						|
            answer = getInput("    + do you wish to remove it?", "n", "yn")
 | 
						|
            if answer == "y":
 | 
						|
                try:
 | 
						|
                    os.remove(cfg)
 | 
						|
                except Exception as e:
 | 
						|
                    error(f"  + could not remove the file: {e} - try manually")
 | 
						|
 | 
						|
        # test module binding files
 | 
						|
        bindingDir = os.path.join(SEISCOMP_ROOT, "etc", "key", aliasName)
 | 
						|
        if os.path.exists(bindingDir):
 | 
						|
            print(f"  + found binding directory: {bindingDir}")
 | 
						|
            answer = getInput("    + do you wish to remove it?", "n", "yn")
 | 
						|
            if answer == "y":
 | 
						|
                try:
 | 
						|
                    shutil.rmtree(bindingDir)
 | 
						|
                except Exception as e:
 | 
						|
                    error(f"    + could not remove the directory: {e} - try manually")
 | 
						|
 | 
						|
        # test key files
 | 
						|
        keyDir = os.path.join(SEISCOMP_ROOT, "etc", "key")
 | 
						|
        dirContent = os.listdir(keyDir)
 | 
						|
        keyFiles = []
 | 
						|
        print("  + testing key files")
 | 
						|
        for f in dirContent:
 | 
						|
            if not os.path.isfile(os.path.join(keyDir, f)) or not f.startswith(
 | 
						|
                "station_"
 | 
						|
            ):
 | 
						|
                continue
 | 
						|
 | 
						|
            keyFile = os.path.join(keyDir, f)
 | 
						|
            with open(keyFile, "r", encoding="utf8") as fp:
 | 
						|
                # Read all lines in the file one by one
 | 
						|
                for line in fp:
 | 
						|
                    # check if the line starts with the module name
 | 
						|
                    if line.startswith(aliasName):
 | 
						|
                        keyFiles.append(keyFile)
 | 
						|
                        print(f"    + found binding for '{aliasName}' in: {keyFile}")
 | 
						|
 | 
						|
        if keyFiles:
 | 
						|
            print(
 | 
						|
                f"    + found {len(keyFiles)} bindings for '{aliasName}' in key files"
 | 
						|
            )
 | 
						|
            question = f"    + remove all '{aliasName}' bindings from key files?"
 | 
						|
            answer = getInput(question, "n", "yn")
 | 
						|
            if answer == "y":
 | 
						|
                shell = seiscomp.shell.CLI(env)
 | 
						|
                shell.commandRemove(["module", aliasName, "*.*"])
 | 
						|
        else:
 | 
						|
            print("    + found no key files")
 | 
						|
 | 
						|
        return 0
 | 
						|
 | 
						|
    error(f"Wrong command '{args[0]}': expected 'create' or 'remove'")
 | 
						|
    return 1
 | 
						|
 | 
						|
 | 
						|
def on_alias_help(_):
 | 
						|
    print("seiscomp alias {create|remove} ALIAS_NAME APP_NAME")
 | 
						|
    print(
 | 
						|
        "Creates/removes aliases of applications as symlinks allowing to run multiple "
 | 
						|
        "application instances with different parameter sets. Aliases of aliases are "
 | 
						|
        "not allowed."
 | 
						|
    )
 | 
						|
    print()
 | 
						|
    print("Examples:")
 | 
						|
    print("$ seiscomp alias create l1autopick scautopick")
 | 
						|
    print("Creating alias 'l1autopick' for 'scautopick'':")
 | 
						|
    print(
 | 
						|
        "  + registering alias 'l1autopick' in $SEISCOMP_ROOT/etc/descriptions/aliases"
 | 
						|
    )
 | 
						|
    print("  + linking default configuration: scautopick.cfg -> l1autopick.cfg")
 | 
						|
    print("  + creating alias to application: scautopick -> bin/l1autopick")
 | 
						|
    print("  + linking init script: scautopick.py -> l1autopick.py")
 | 
						|
    print()
 | 
						|
    print("$ seiscomp alias remove l1autopick")
 | 
						|
    print("Removing alias 'l1autopick':")
 | 
						|
    print("  + removing alias application: bin/l1autopick")
 | 
						|
    print("  + removing init script: etc/init/l1autopick.py")
 | 
						|
    print("  + removing default configuration: etc/defaults/l1autopick.cfg")
 | 
						|
 | 
						|
 | 
						|
allowed_actions = [
 | 
						|
    "install-deps",
 | 
						|
    "setup",
 | 
						|
    "shell",
 | 
						|
    "enable",
 | 
						|
    "disable",
 | 
						|
    "start",
 | 
						|
    "stop",
 | 
						|
    "restart",
 | 
						|
    "reload",
 | 
						|
    "check",
 | 
						|
    "status",
 | 
						|
    "list",
 | 
						|
    "exec",
 | 
						|
    "update-config",
 | 
						|
    "alias",
 | 
						|
    "print",
 | 
						|
    "help",
 | 
						|
]
 | 
						|
 | 
						|
 | 
						|
# Define all actions that do not need locking of seiscomp
 | 
						|
actions_without_lock = [
 | 
						|
    # "install-deps",
 | 
						|
    "help",
 | 
						|
    "list",
 | 
						|
    "exec",
 | 
						|
    "print",
 | 
						|
]
 | 
						|
 | 
						|
 | 
						|
def on_help(args, _):
 | 
						|
    if not args:
 | 
						|
        print("Name:")
 | 
						|
        print(
 | 
						|
            "  seiscomp - Load the environment of the SeisComP installation from "
 | 
						|
            "where seiscomp is executed and run a command"
 | 
						|
        )
 | 
						|
        print("\nSynopsis:")
 | 
						|
        print("  seiscomp [flags] [commands] [arguments]")
 | 
						|
        print("\nFlags:")
 | 
						|
        print("  -h, [--help]          Produce this help message.")
 | 
						|
        print("  --asroot              Allow running a command as root.")
 | 
						|
        print("  --csv                 Print output as csv in machine-readable format.")
 | 
						|
        print(
 | 
						|
            "  -i, [--interactive]   Interactive mode: Allow deleting configurations "
 | 
						|
            "interactively when removing aliases."
 | 
						|
        )
 | 
						|
        print(
 | 
						|
            "  --invert              Invert the selection of the specified module names "
 | 
						|
            "when using any of the commands: start, stop, check, status, reload, or "
 | 
						|
            "restart."
 | 
						|
        )
 | 
						|
        print(
 | 
						|
            "  --wait arg            Define a timeout in seconds for acquiring the seiscomp "
 | 
						|
            "lock file, e.g. `seiscomp --wait 10 update-config`."
 | 
						|
        )
 | 
						|
        print("\nAvailable commands:")
 | 
						|
        for helpAction in allowed_actions:
 | 
						|
            print(f"  {helpAction}")
 | 
						|
 | 
						|
        print("\nUse 'help [command]' to get more help about a command.")
 | 
						|
        print("\nExamples:")
 | 
						|
        print("  seiscomp help update-config          Show help for update-config")
 | 
						|
        print(
 | 
						|
            "  seiscomp update-config               Run update-config for all modules"
 | 
						|
        )
 | 
						|
        print(
 | 
						|
            "  seiscomp update-config trunk         Run update-config for all trunk modules"
 | 
						|
        )
 | 
						|
        print("  seiscomp update-config scautopick    Run update-config for scautopick")
 | 
						|
        return 0
 | 
						|
 | 
						|
    cmd = args[0]
 | 
						|
    try:
 | 
						|
        func = globals()["on_" + cmd.replace("-", "_") + "_help"]
 | 
						|
    except BaseException:
 | 
						|
        print(f"Sorry, no help available for {cmd}")
 | 
						|
        return 1
 | 
						|
    func(args[1:])
 | 
						|
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
def run_action(runAction, args, flags):
 | 
						|
    try:
 | 
						|
        func = globals()["on_" + runAction.replace("-", "_")]
 | 
						|
        return func(args, flags)
 | 
						|
    except Exception as exc:
 | 
						|
        error(f"command '{runAction}' failed: {str(exc)}")
 | 
						|
        if "debug" in flags:
 | 
						|
            info = traceback.format_exception(*sys.exc_info())
 | 
						|
            for i in info:
 | 
						|
                sys.stderr.write(i)
 | 
						|
        return 2
 | 
						|
 | 
						|
 | 
						|
def on_csv_help(_):
 | 
						|
    print("If --csv is prepended to a usual command the internal output is")
 | 
						|
    print("set to comma separated values. The only command that currently")
 | 
						|
    print("uses this output format is 'status'.")
 | 
						|
    print()
 | 
						|
    print("Example:")
 | 
						|
    print("seiscomp --csv status")
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
# ------------------------------------------------------------------------------
 | 
						|
# Check command line
 | 
						|
# ------------------------------------------------------------------------------
 | 
						|
useCSV = False
 | 
						|
asRoot = False
 | 
						|
lockTimeout = None
 | 
						|
interactiveMode = False
 | 
						|
invert = False
 | 
						|
 | 
						|
argv = sys.argv[1:]
 | 
						|
argflags = []
 | 
						|
 | 
						|
# Check for flags
 | 
						|
while argv:
 | 
						|
    if argv[0] == "--help" or argv[0] == "-h":
 | 
						|
        on_help([], "")
 | 
						|
        sys.exit(1)
 | 
						|
    if argv[0] == "--csv":
 | 
						|
        useCSV = True
 | 
						|
        argv = argv[1:]
 | 
						|
    elif argv[0] == "--asroot":
 | 
						|
        asRoot = True
 | 
						|
        argv = argv[1:]
 | 
						|
    elif argv[0] == "--interactive" or argv[0] == "-i":
 | 
						|
        interactiveMode = True
 | 
						|
        argv = argv[1:]
 | 
						|
    elif argv[0] == "--invert":
 | 
						|
        invert = True
 | 
						|
        argv = argv[1:]
 | 
						|
    elif argv[0] == "--wait":
 | 
						|
        argv = argv[1:]
 | 
						|
        if not argv:
 | 
						|
            print("--wait expects an integer value in seconds")
 | 
						|
            sys.exit(1)
 | 
						|
        try:
 | 
						|
            lockTimeout = int(argv[0])
 | 
						|
        except BaseException:
 | 
						|
            print(f"Wait timeout is not an integer: {argv[0]}")
 | 
						|
            sys.exit(1)
 | 
						|
        if lockTimeout < 0:
 | 
						|
            print(f"Wait timeout must be positive: {argv[0]}")
 | 
						|
            sys.exit(1)
 | 
						|
        argv = argv[1:]
 | 
						|
    elif argv[0].startswith("--"):
 | 
						|
        argflags.append(argv[0][2:])
 | 
						|
        argv = argv[1:]
 | 
						|
    else:
 | 
						|
        break
 | 
						|
 | 
						|
 | 
						|
if len(argv) < 1:
 | 
						|
    print("seiscomp [flags] {%s} [args]" % "|".join(allowed_actions))
 | 
						|
    print("\nUse 'seiscomp help' to get more help")
 | 
						|
    sys.exit(1)
 | 
						|
 | 
						|
action = argv[0]
 | 
						|
arguments = argv[1:]
 | 
						|
inverted = []
 | 
						|
 | 
						|
if action not in allowed_actions:
 | 
						|
    print("seiscomp [flags] {%s} [args]" % "|".join(allowed_actions))
 | 
						|
    sys.exit(1)
 | 
						|
 | 
						|
if os.getuid() == 0 and not asRoot and action != "install-deps":
 | 
						|
    print("Running 'seiscomp' as root is dangerous. Use --asroot only if you")
 | 
						|
    print("know exactly what you are doing!")
 | 
						|
    sys.exit(1)
 | 
						|
 | 
						|
if invert and action in ["start", "stop", "check", "status", "reload", "restart"]:
 | 
						|
    inverted = arguments
 | 
						|
    arguments = []
 | 
						|
else:
 | 
						|
    invert = False
 | 
						|
 | 
						|
# ------------------------------------------------------------------------------
 | 
						|
# Initialize the environment
 | 
						|
# ------------------------------------------------------------------------------
 | 
						|
 | 
						|
# Resolve symlinks to files (if any)
 | 
						|
if os.path.islink(sys.argv[0]):
 | 
						|
    # Read the link target
 | 
						|
    target = os.readlink(sys.argv[0])
 | 
						|
    # Is the target an absolute path then take it as is
 | 
						|
    if os.path.isabs(target):
 | 
						|
        sys.argv[0] = target
 | 
						|
    # Otherwise join the dirname of the script with the target
 | 
						|
    # to get the semi-real path of the seiscomp script. Semi-real
 | 
						|
    # refers to the fact that symlinks are not completely resolved
 | 
						|
    # and why the usage of os.path.realpath is avoided. If the
 | 
						|
    # seiscomp directory itself is a symlink it should be preserved.
 | 
						|
    else:
 | 
						|
        sys.argv[0] = os.path.join(os.path.dirname(sys.argv[0]), target)
 | 
						|
 | 
						|
# Guess SEISCOMP_ROOT from path of called script, directory links are not
 | 
						|
# resolved allowing to create separate SeisComP environments
 | 
						|
if os.path.isabs(sys.argv[0]):
 | 
						|
    root_path = sys.argv[0]
 | 
						|
else:
 | 
						|
    cwd = os.getenv("PWD")
 | 
						|
    if cwd is None:
 | 
						|
        cwd = os.getcwd()
 | 
						|
    root_path = os.path.join(cwd, sys.argv[0])
 | 
						|
 | 
						|
SEISCOMP_ROOT = os.path.dirname(os.path.dirname(os.path.normpath(root_path)))
 | 
						|
INIT_PATH = os.path.join(SEISCOMP_ROOT, "etc", "init")
 | 
						|
DESC_PATH = os.path.join(SEISCOMP_ROOT, "etc", "descriptions")
 | 
						|
ALIAS_FILE = os.path.join(DESC_PATH, "aliases")
 | 
						|
BIN_PATH = os.path.join(SEISCOMP_ROOT, "bin")
 | 
						|
SBIN_PATH = os.path.join(SEISCOMP_ROOT, "sbin")
 | 
						|
PYTHONPATH = os.path.join(SEISCOMP_ROOT, "lib", "python")
 | 
						|
MANPATH = os.path.join(SEISCOMP_ROOT, "share", "man")
 | 
						|
LD_LIBRARY_PATH = os.path.join(SEISCOMP_ROOT, "lib")
 | 
						|
DYLD_FALLBACK_FRAMEWORK_PATH = os.path.join(SEISCOMP_ROOT, "lib", "3rd-party")
 | 
						|
 | 
						|
# Run another process with proper LD_LIBRARY_PATH set otherwise the dynamic
 | 
						|
# linker will not find dependent SC3 libraries
 | 
						|
isWrapped = False
 | 
						|
try:
 | 
						|
    if os.environ["SEISCOMP_WRAP"] == "TRUE":
 | 
						|
        isWrapped = True
 | 
						|
except BaseException:
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
# Setup signal handler
 | 
						|
# signal.signal(signal.SIGTERM, sigterm_handler)
 | 
						|
 | 
						|
if not isWrapped:
 | 
						|
    try:
 | 
						|
        os.environ["PATH"] = BIN_PATH + ":" + os.environ["PATH"]
 | 
						|
    except BaseException:
 | 
						|
        os.environ["PATH"] = BIN_PATH
 | 
						|
 | 
						|
    try:
 | 
						|
        os.environ[SysLibraryPathVar] = (
 | 
						|
            get_library_path() + ":" + os.environ[SysLibraryPathVar]
 | 
						|
        )
 | 
						|
    except BaseException:
 | 
						|
        os.environ[SysLibraryPathVar] = get_library_path()
 | 
						|
 | 
						|
    if sys.platform == "darwin":
 | 
						|
        os.environ[SysFrameworkPathVar] = get_framework_path()
 | 
						|
 | 
						|
    try:
 | 
						|
        os.environ["PYTHONPATH"] = PYTHONPATH + ":" + os.environ["PYTHONPATH"]
 | 
						|
    except BaseException:
 | 
						|
        os.environ["PYTHONPATH"] = PYTHONPATH
 | 
						|
    try:
 | 
						|
        os.environ["MANPATH"] = MANPATH + ":" + os.environ["MANPATH"]
 | 
						|
    except BaseException:
 | 
						|
        os.environ["MANPATH"] = MANPATH
 | 
						|
 | 
						|
    os.environ["SEISCOMP_WRAP"] = "TRUE"
 | 
						|
 | 
						|
    sys.exit(system(sys.argv))
 | 
						|
 | 
						|
# Register local lib/python in SEARCH PATH
 | 
						|
sys.path.insert(0, PYTHONPATH)
 | 
						|
 | 
						|
# Create environment which supports queries for various SeisComP
 | 
						|
# directories and sets PATH, LD_LIBRARY_PATH and PYTHONPATH
 | 
						|
env = seiscomp.kernel.Environment(SEISCOMP_ROOT)
 | 
						|
env.setCSVOutput(useCSV)
 | 
						|
 | 
						|
# Check for lock file
 | 
						|
isChild = False
 | 
						|
 | 
						|
if action in actions_without_lock:
 | 
						|
    isChild = True
 | 
						|
else:
 | 
						|
    try:
 | 
						|
        isChild = os.environ["SEISCOMP_LOCK"] == "TRUE"
 | 
						|
    except KeyError:
 | 
						|
        pass
 | 
						|
 | 
						|
if not isChild:
 | 
						|
    if not env.tryLock("seiscomp", lockTimeout):
 | 
						|
        error(
 | 
						|
            f"Could not get lock {env.lockFile('seiscomp')} - is another process "
 | 
						|
            "using it?"
 | 
						|
        )
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    os.environ["SEISCOMP_LOCK"] = "TRUE"
 | 
						|
    exitcode = system(["run_with_lock", "-q", env.lockFile("seiscomp")] + sys.argv)
 | 
						|
    sys.exit(exitcode)
 | 
						|
 | 
						|
 | 
						|
# Change into SEISCOMP_ROOT directory. The env instance will change
 | 
						|
# back into the current working directory automatically if destroyed.
 | 
						|
env.chroot()
 | 
						|
 | 
						|
simpleCommand = (action == "install-deps") or (action == "print" and arguments == "env")
 | 
						|
 | 
						|
if not simpleCommand:
 | 
						|
    config_mods = load_init_modules(INIT_PATH)
 | 
						|
    mods = []
 | 
						|
    for m in config_mods:
 | 
						|
        if m.isConfigModule:
 | 
						|
            continue
 | 
						|
        if invert and m.name in inverted:
 | 
						|
            continue
 | 
						|
        mods.append(m)
 | 
						|
 | 
						|
sys.exit(run_action(action, arguments, argflags))
 |