mirror of
https://github.com/jgyates/genmon.git
synced 2026-08-12 15:32:00 -04:00
1650 lines
65 KiB
Python
1650 lines
65 KiB
Python
# -------------------------------------------------------------------------------
|
|
# FILE: genloader.py
|
|
# PURPOSE: app for loading specific moduels for genmon
|
|
#
|
|
# AUTHOR: Jason G Yates
|
|
# DATE: 12-Sept-2018
|
|
#
|
|
# MODIFICATIONS:
|
|
# -------------------------------------------------------------------------------
|
|
|
|
import getopt
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from shutil import copyfile, move
|
|
from subprocess import PIPE, Popen
|
|
|
|
try:
|
|
from genmonlib.myconfig import MyConfig
|
|
from genmonlib.mylog import SetupLogger
|
|
from genmonlib.mysupport import MySupport
|
|
from genmonlib.program_defaults import ProgramDefaults
|
|
|
|
except Exception as e1:
|
|
print(
|
|
"\n\nThis program requires the modules located in the genmonlib directory in the github repository.\n"
|
|
)
|
|
print(
|
|
"Please see the project documentation at https://github.com/jgyates/genmon.\n"
|
|
)
|
|
print("Error: " + str(e1))
|
|
sys.exit(2)
|
|
|
|
# ------------ Loader class -----------------------------------------------------
|
|
class Loader(MySupport):
|
|
def __init__(
|
|
self,
|
|
start=False,
|
|
stop=False,
|
|
hardstop=False,
|
|
loglocation=ProgramDefaults.LogPath,
|
|
log=None,
|
|
localinit=False,
|
|
ConfigFilePath=ProgramDefaults.ConfPath,
|
|
):
|
|
|
|
self.Start = start
|
|
self.Stop = stop
|
|
self.HardStop = hardstop
|
|
self.PipChecked = False
|
|
self.AptUpdated = False
|
|
self.NewInstall = False
|
|
self.Upgrade = False
|
|
self.version = None
|
|
|
|
if sys.version_info[0] < 3:
|
|
self.pipProgram = "pip2"
|
|
else:
|
|
self.pipProgram = "pip3"
|
|
|
|
self.ConfigFilePath = ConfigFilePath
|
|
|
|
self.ConfigFileName = "genloader.conf"
|
|
# log errors in this module to a file
|
|
if localinit == True:
|
|
self.configfile = self.ConfigFileName
|
|
else:
|
|
self.configfile = os.path.join(self.ConfigFilePath, self.ConfigFileName)
|
|
|
|
self.ModulePath = os.path.dirname(os.path.realpath(__file__))
|
|
self.ConfPath = os.path.join(
|
|
os.path.dirname(os.path.realpath(__file__)), "conf"
|
|
)
|
|
|
|
self.bSystemIsNotLinux = False
|
|
if not "linux" in sys.platform:
|
|
self.bSystemIsNotLinux = True
|
|
self.bSysetmIsWindows = False
|
|
if "win" in sys.platform:
|
|
self.bSysetmIsWindows = True
|
|
|
|
# log errors in this module to a file
|
|
if log == None:
|
|
self.log = SetupLogger(
|
|
"genloader", os.path.join(loglocation, "genloader.log")
|
|
)
|
|
else:
|
|
self.log = log
|
|
|
|
self.console = SetupLogger("genloader_console", log_file="", stream=True)
|
|
|
|
try:
|
|
self.CachedConfig = {}
|
|
|
|
if not os.path.isdir(self.ConfigFilePath):
|
|
try:
|
|
os.mkdir(self.ConfigFilePath)
|
|
except Exception as e1:
|
|
self.LogInfo(
|
|
"Error creating target config directory: " + str(e1),
|
|
LogLine=True,
|
|
)
|
|
|
|
# check to see if genloader.conf is present, if not copy it from genmon directory
|
|
if not os.path.isfile(self.configfile):
|
|
self.LogInfo(
|
|
"Warning: unable to find config file: "
|
|
+ self.configfile
|
|
+ " Copying file to "
|
|
+ self.ConfigFilePath
|
|
+ " directory."
|
|
)
|
|
if not self.CopyConfFile():
|
|
sys.exit(2)
|
|
|
|
self.config = MyConfig(
|
|
filename=self.configfile, section="genmon", log=self.log
|
|
)
|
|
|
|
if not self.GetConfig():
|
|
self.CopyConfFile()
|
|
self.LogInfo("Error validating config. Retrying..")
|
|
self.config = MyConfig(
|
|
filename=self.configfile, section="genmon", log=self.log
|
|
)
|
|
if not self.GetConfig():
|
|
self.LogInfo("Error reading config file, 2nd attempt (1), Exiting")
|
|
sys.exit(2)
|
|
|
|
if not self.ValidateConfig():
|
|
self.CopyConfFile()
|
|
self.LogInfo("Error validating config. Retrying..")
|
|
self.config = MyConfig(
|
|
filename=self.configfile, section="genmon", log=self.log
|
|
)
|
|
if not self.GetConfig():
|
|
self.LogInfo("Error reading config file, 2nd attempt (2), Exiting")
|
|
sys.exit(2)
|
|
if not self.ValidateConfig():
|
|
self.LogInfo("Error validating config file, Exiting")
|
|
sys.exit(2)
|
|
|
|
if self.Start:
|
|
if not self.CheckSystem():
|
|
self.LogInfo("Error check system readiness. Exiting")
|
|
sys.exit(2)
|
|
|
|
self.LoadOrder = self.GetLoadOrder()
|
|
|
|
if self.Stop:
|
|
self.StopModules()
|
|
time.sleep(2)
|
|
|
|
self.Maintenance()
|
|
|
|
if self.Start:
|
|
self.StartModules()
|
|
except Exception as e1:
|
|
self.LogErrorLine("Error in init: " + str(e1))
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def CopyConfFile(self):
|
|
|
|
if os.path.isfile(os.path.join(self.ConfPath, self.ConfigFileName)):
|
|
copyfile(os.path.join(self.ConfPath, self.ConfigFileName), self.configfile)
|
|
return True
|
|
else:
|
|
self.LogInfo("Unable to find config file.")
|
|
return False
|
|
sys.exit(2)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def IsModuleEnabled(self, ModuleList):
|
|
|
|
try:
|
|
if ModuleList == None or not len(ModuleList):
|
|
return True
|
|
for Module in ModuleList:
|
|
if Module in self.CachedConfig and self.CachedConfig[Module]["enable"]:
|
|
return True
|
|
return False
|
|
except Exception as e1:
|
|
self.LogInfo("Error in IsModuleEnabled: " + str(e1), LogLine=True)
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def IsConfigOptionEnabled(self, ConfigFile, Section, Option, default=False):
|
|
|
|
try:
|
|
ConfigPath = os.path.join(self.ConfigFilePath, ConfigFile)
|
|
if not os.path.isfile(ConfigPath):
|
|
ConfigPath = os.path.join(self.ConfPath, ConfigFile)
|
|
if not os.path.isfile(ConfigPath):
|
|
return default
|
|
config = MyConfig(filename=ConfigPath, section=Section, log=self.log)
|
|
return config.ReadValue(Option, return_type=bool, default=default)
|
|
except Exception as e1:
|
|
self.LogInfo("Error in IsConfigOptionEnabled: " + str(e1), LogLine=True)
|
|
return default
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def LibraryDependency(
|
|
self,
|
|
ImportName,
|
|
InstallName=None,
|
|
Version=None,
|
|
LinuxOnly=False,
|
|
MinPython=None,
|
|
):
|
|
|
|
# ImportName is the module name used by importlib. InstallName is the
|
|
# package name used by pip, which can differ (e.g. serial -> pyserial).
|
|
return {
|
|
"import": ImportName,
|
|
"install": InstallName if InstallName != None else ImportName,
|
|
"version": Version,
|
|
"linuxonly": LinuxOnly,
|
|
"minpython": MinPython,
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def GetDependencyRegistry(self):
|
|
|
|
# Dependency groups:
|
|
# base - always required for normal genmon/genserv startup
|
|
# addons - required only when at least one listed add-on is enabled
|
|
# features - required only when a config option is enabled; a feature
|
|
# may also list modules when both an add-on and an option
|
|
# must be enabled before installing the dependency.
|
|
#
|
|
# Keep optional or unusually large packages out of base. For example,
|
|
# fluids is only needed by genmopeka, and zeroconf is only needed when
|
|
# genhalink mDNS discovery is enabled.
|
|
#
|
|
# we will not use the check for configparser as this look like it is in backports on 2.7
|
|
# and our myconfig modules uses the default so this generates an error that is not warranted
|
|
# ['configparser','configparser',None], # reading config files
|
|
return {
|
|
"base": [
|
|
self.LibraryDependency("flask"), # Web server
|
|
self.LibraryDependency("serial", "pyserial"), # Serial
|
|
self.LibraryDependency("crcmod"), # Modbus CRC
|
|
self.LibraryDependency("pyowm"), # Open Weather API
|
|
self.LibraryDependency("pytz"), # Time zone support
|
|
self.LibraryDependency("pyotp", Version="2.3.0"), # 2FA support
|
|
self.LibraryDependency("psutil"), # process utilities
|
|
self.LibraryDependency("OpenSSL", "pyopenssl"), # SSL
|
|
self.LibraryDependency("ldap3"), # LDAP
|
|
],
|
|
"addons": [
|
|
{
|
|
"modules": ["gensnmp"],
|
|
"dependencies": [
|
|
self.LibraryDependency("pysnmp", Version="5.1.0"),
|
|
],
|
|
},
|
|
{
|
|
"modules": ["gentankdiy"],
|
|
"dependencies": [
|
|
self.LibraryDependency("smbus", LinuxOnly=True),
|
|
],
|
|
},
|
|
{
|
|
"modules": ["genpushover"],
|
|
"dependencies": [
|
|
self.LibraryDependency("chump"),
|
|
],
|
|
},
|
|
{
|
|
"modules": ["gensms"],
|
|
"dependencies": [
|
|
self.LibraryDependency("twilio"),
|
|
],
|
|
},
|
|
{
|
|
"modules": ["genmqtt", "genmqttin", "genhomeassistant"],
|
|
"dependencies": [
|
|
self.LibraryDependency(
|
|
"paho.mqtt.client", "paho-mqtt", Version="1.6.1"
|
|
),
|
|
],
|
|
},
|
|
{
|
|
"modules": ["gencthat"],
|
|
"dependencies": [
|
|
self.LibraryDependency("spidev", LinuxOnly=True),
|
|
],
|
|
},
|
|
{
|
|
"modules": ["genhalink"],
|
|
"dependencies": [
|
|
self.LibraryDependency("aiohttp"),
|
|
],
|
|
},
|
|
{
|
|
"modules": ["gensms_voip"],
|
|
"dependencies": [
|
|
self.LibraryDependency("voipms", Version="0.2.5"),
|
|
],
|
|
},
|
|
{
|
|
"modules": ["genmopeka"],
|
|
"dependencies": [
|
|
self.LibraryDependency("fluids", MinPython=(3, 6)),
|
|
],
|
|
},
|
|
{
|
|
"modules": ["genotodata"],
|
|
"dependencies": [
|
|
self.LibraryDependency("bleak"),
|
|
],
|
|
},
|
|
{
|
|
"modules": ["genhubitat"],
|
|
"dependencies": [
|
|
self.LibraryDependency("aiohttp"),
|
|
],
|
|
},
|
|
],
|
|
"features": [
|
|
{
|
|
# zeroconf is optional within genhalink. Do not install it
|
|
# unless the add-on is enabled and mDNS discovery is on.
|
|
"modules": ["genhalink"],
|
|
"config": {
|
|
"file": "genhalink.conf",
|
|
"section": "genhalink",
|
|
"option": "zeroconf_enabled",
|
|
"default": True,
|
|
},
|
|
"dependencies": [
|
|
self.LibraryDependency("zeroconf"),
|
|
],
|
|
},
|
|
{
|
|
# zeroconf is optional within genhubitat. Do not install it
|
|
# unless the add-on is enabled and mDNS discovery is on.
|
|
"modules": ["genhubitat"],
|
|
"config": {
|
|
"file": "genhubitat.conf",
|
|
"section": "genhubitat",
|
|
"option": "zeroconf_enabled",
|
|
"default": True,
|
|
},
|
|
"dependencies": [
|
|
self.LibraryDependency("zeroconf"),
|
|
],
|
|
},
|
|
{
|
|
# webauthn is used for passkeys, which are only available
|
|
# when MFA is enabled.
|
|
"config": {
|
|
"file": "genmon.conf",
|
|
"section": "GenMon",
|
|
"option": "usemfa",
|
|
"default": False,
|
|
},
|
|
"dependencies": [
|
|
self.LibraryDependency("webauthn", Version="2.7.0"),
|
|
],
|
|
},
|
|
],
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def GetModuleList(self):
|
|
|
|
ModuleList = []
|
|
Seen = set()
|
|
DependencyRegistry = self.GetDependencyRegistry()
|
|
|
|
try:
|
|
# Start with the unconditional core dependencies.
|
|
for Module in DependencyRegistry["base"]:
|
|
ModuleList.append(Module)
|
|
|
|
# Add dependencies for enabled add-ons only. This keeps disabled
|
|
# add-ons from triggering pip installs during every genloader start.
|
|
for AddOn in DependencyRegistry["addons"]:
|
|
if not self.IsModuleEnabled(AddOn["modules"]):
|
|
continue
|
|
for Module in AddOn["dependencies"]:
|
|
Module = Module.copy()
|
|
Module["modules"] = AddOn["modules"]
|
|
ModuleList.append(Module)
|
|
|
|
# Add dependencies for enabled feature flags. Some features also
|
|
# have an add-on owner, so both gates must pass before inclusion.
|
|
for Feature in DependencyRegistry["features"]:
|
|
if "modules" in Feature:
|
|
if not self.IsModuleEnabled(Feature["modules"]):
|
|
continue
|
|
if not self.IsConfigOptionEnabled(
|
|
Feature["config"]["file"],
|
|
Feature["config"]["section"],
|
|
Feature["config"]["option"],
|
|
default=Feature["config"].get("default", False),
|
|
):
|
|
continue
|
|
for Module in Feature["dependencies"]:
|
|
Module = Module.copy()
|
|
if "modules" in Feature:
|
|
Module["modules"] = Feature["modules"]
|
|
Module["config"] = Feature["config"]
|
|
ModuleList.append(Module)
|
|
|
|
# A package can be required through more than one path. Check it
|
|
# once so shared dependencies do not produce duplicate log/install
|
|
# attempts.
|
|
DedupedList = []
|
|
for Module in ModuleList:
|
|
Key = (Module["import"], Module["install"], Module["version"])
|
|
if Key in Seen:
|
|
continue
|
|
Seen.add(Key)
|
|
DedupedList.append(Module)
|
|
return DedupedList
|
|
except Exception as e1:
|
|
self.LogInfo("Error in GetModuleList: " + str(e1), LogLine=True)
|
|
return ModuleList
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def ShouldCheckLibrary(self, Module):
|
|
|
|
try:
|
|
# Final platform/version/config guard before importing or installing.
|
|
# GetModuleList already filters add-ons and features, but keeping
|
|
# this here preserves one validation path for any direct callers.
|
|
if "linuxonly" in Module and self.bSystemIsNotLinux and Module["linuxonly"]:
|
|
return False
|
|
if "minpython" in Module and Module["minpython"] != None:
|
|
if sys.version_info < Module["minpython"]:
|
|
return False
|
|
if not self.IsModuleEnabled(Module.get("modules", None)):
|
|
return False
|
|
if "config" in Module:
|
|
Config = Module["config"]
|
|
return self.IsConfigOptionEnabled(
|
|
Config["file"],
|
|
Config["section"],
|
|
Config["option"],
|
|
default=Config.get("default", False),
|
|
)
|
|
return True
|
|
except Exception as e1:
|
|
self.LogInfo("Error in ShouldCheckLibrary: " + str(e1), LogLine=True)
|
|
return True
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def CheckSystem(self):
|
|
|
|
# this function checks the system to see if the required libraries are
|
|
# installed. If they are not then an attempt is made to install them.
|
|
|
|
ModuleList = self.GetModuleList()
|
|
try:
|
|
ErrorOccured = False
|
|
|
|
for Module in ModuleList:
|
|
|
|
if not self.ShouldCheckLibrary(Module):
|
|
continue
|
|
if not self.LibraryIsInstalled(Module["import"]):
|
|
self.LogInfo(
|
|
"Warning: required library "
|
|
+ Module["install"]
|
|
+ " not installed. Attempting to install...."
|
|
)
|
|
if not self.bSystemIsNotLinux:
|
|
self.CheckToolsNeeded()
|
|
if not self.InstallLibrary(Module["install"], version=Module["version"]):
|
|
self.LogInfo("Error: unable to install library " + Module["install"])
|
|
ErrorOccured = True
|
|
if Module["import"] == "ldap3":
|
|
# This will correct and issue with the ldap3 modbule not being recogonized in LibrayIsInstalled
|
|
self.InstallLibrary("pyasn1", update=True)
|
|
|
|
return not ErrorOccured
|
|
except Exception as e1:
|
|
self.LogInfo("Error in CheckSystem: " + str(e1), LogLine=True)
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def ExecuteCommandList(self, execute_list, env=None):
|
|
|
|
try:
|
|
process = Popen(execute_list, stdout=PIPE, stderr=PIPE, env=env)
|
|
output, _error = process.communicate()
|
|
|
|
if _error:
|
|
self.LogInfo("Error in ExecuteCommandList : " + str(_error))
|
|
return False
|
|
rc = process.returncode
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# check for other tools that are needed by pip libaries
|
|
def CheckToolsNeeded(self):
|
|
try:
|
|
|
|
command_list = ["cmake", "--version"]
|
|
if not self.ExecuteCommandList(command_list):
|
|
if not self.AptUpdated:
|
|
command_list = ["sudo", "apt-get", "-yqq", "--allow-releaseinfo-change","update"]
|
|
if not self.ExecuteCommandList(command_list):
|
|
self.LogInfo("Error: Unable to run apt-get update. Retrying...")
|
|
command_list = ["sudo", "apt-get", "-yqq", "update"]
|
|
if not self.ExecuteCommandList(command_list):
|
|
self.LogInfo("Error: Unable to run apt-get update. ")
|
|
self.AptUpdated = True
|
|
self.LogInfo("Installing cmake...")
|
|
command_list = [
|
|
"sudo",
|
|
"DEBIAN_FRONTEND=noninteractive",
|
|
"apt-get",
|
|
"-yqq",
|
|
"install",
|
|
"cmake",
|
|
]
|
|
if not self.ExecuteCommandList(command_list):
|
|
self.LogInfo("Error: Unable to install cmake.")
|
|
|
|
return True
|
|
except Exception as e1:
|
|
self.LogInfo("Error in CheckToolsNeeded: " + str(e1), LogLine=True)
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def CheckBaseSoftware(self):
|
|
|
|
try:
|
|
if self.PipChecked:
|
|
return True
|
|
|
|
command_list = [sys.executable, "-m", "pip", "-V"]
|
|
#command_list = [self.pipProgram, "-V"]
|
|
if not self.ExecuteCommandList(command_list):
|
|
self.InstallBaseSoftware()
|
|
|
|
self.PipChecked = True
|
|
return True
|
|
except Exception as e1:
|
|
self.LogInfo("Error in CheckBaseSoftware: " + str(e1), LogLine=True)
|
|
self.InstallBaseSoftware()
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def InstallBaseSoftware(self):
|
|
|
|
try:
|
|
if sys.version_info[0] < 3:
|
|
pipInstallProgram = "python-pip"
|
|
else:
|
|
pipInstallProgram = "python3-pip"
|
|
|
|
self.LogInfo("Installing " + pipInstallProgram)
|
|
|
|
if not self.AptUpdated:
|
|
command_list = ["sudo", "apt-get", "-yqq", "--allow-releaseinfo-change", "update"]
|
|
if not self.ExecuteCommandList(command_list):
|
|
self.LogInfo("Error: Unable to run apt-get update. Retrying..")
|
|
command_list = ["sudo", "apt-get", "-yqq", "update"]
|
|
if not self.ExecuteCommandList(command_list):
|
|
self.LogInfo("Error: Unable to run apt-get update. Retrying..")
|
|
self.AptUpdated = True
|
|
command_list = ["sudo", "apt-get", "-yqq", "install", pipInstallProgram]
|
|
if not self.ExecuteCommandList(command_list):
|
|
self.LogInfo("Error: Unable to install " + pipInstallProgram)
|
|
|
|
return True
|
|
except Exception as e1:
|
|
self.LogInfo("Error in InstallBaseSoftware: " + str(e1), LogLine=True)
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
@staticmethod
|
|
def OneTimeMaint(ConfigFilePath, log):
|
|
|
|
FileList = {
|
|
"feedback.json": os.path.dirname(os.path.realpath(__file__)) + "/",
|
|
"outage.txt": os.path.dirname(os.path.realpath(__file__)) + "/",
|
|
"kwlog.txt": os.path.dirname(os.path.realpath(__file__)) + "/",
|
|
"maintlog.json": os.path.dirname(os.path.realpath(__file__)) + "/",
|
|
"Feedback_dat": os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/",
|
|
"Message_dat": os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/",
|
|
"genmon.conf": "/etc/",
|
|
"genserv.conf": "/etc/",
|
|
"gengpio.conf": "/etc/",
|
|
"gengpioin.conf": "/etc/",
|
|
"genlog.conf": "/etc/",
|
|
"gensms.conf": "/etc/",
|
|
"gensms_modem.conf": "/etc/",
|
|
"genpushover.conf": "/etc/",
|
|
"gensyslog.conf": "/etc/",
|
|
"genmqtt.conf": "/etc/",
|
|
"genmqttin.conf": "/etc/",
|
|
"genslack.conf": "/etc/",
|
|
"gencallmebot.conf": "/etc/",
|
|
"genexercise.conf": "/etc/",
|
|
"genemail2sms.conf": "/etc/",
|
|
"genloader.conf": "/etc/",
|
|
"mymail.conf": "/etc/",
|
|
"mymodem.conf": "/etc/",
|
|
}
|
|
try:
|
|
# Check to see if we have done this already by checking files in the genmon source directory
|
|
if (
|
|
not os.path.isfile(
|
|
os.path.dirname(os.path.realpath(__file__))
|
|
+ "/genmonlib/Message_dat"
|
|
)
|
|
and not os.path.isfile(
|
|
os.path.dirname(os.path.realpath(__file__)) + "/maintlog.json"
|
|
)
|
|
and not os.path.isfile(
|
|
os.path.dirname(os.path.realpath(__file__)) + "/outage.txt"
|
|
)
|
|
and not os.path.isfile(
|
|
os.path.dirname(os.path.realpath(__file__)) + "/kwlog.txt"
|
|
)
|
|
and not os.path.isfile("/etc/genmon.conf")
|
|
):
|
|
return False
|
|
# validate target directory
|
|
if not os.path.isdir(ConfigFilePath):
|
|
try:
|
|
os.mkdir(ConfigFilePath)
|
|
if not os.access(ConfigFilePath + File, os.R_OK):
|
|
pass
|
|
except Exception as e1:
|
|
log.error(
|
|
"Error validating target directory: " + str(e1), LogLine=True
|
|
)
|
|
|
|
# move files
|
|
for File, Path in FileList.items():
|
|
try:
|
|
SourceFile = Path + File
|
|
if os.path.isfile(SourceFile):
|
|
log.error("Moving " + SourceFile + " to " + ConfigFilePath)
|
|
if not MySupport.CopyFile(
|
|
SourceFile, ConfigFilePath + File, move=True, log=log
|
|
):
|
|
log.error("Error: using alternate move method")
|
|
move(SourceFile, ConfigFilePath + File)
|
|
if not os.access(ConfigFilePath + File, os.R_OK):
|
|
pass
|
|
except Exception as e1:
|
|
log.error("Error moving " + SourceFile)
|
|
except Exception as e1:
|
|
log.error("Error moving files: " + str(e1), LogLine=True)
|
|
return True
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def FixPyOWMMaintIssues(self):
|
|
try:
|
|
# check version of pyowm
|
|
import pyowm
|
|
|
|
if sys.version_info[0] < 3:
|
|
required_version = "2.9.0"
|
|
else:
|
|
required_version = "2.10.0"
|
|
|
|
if not self.LibraryIsInstalled("pyowm"):
|
|
self.LogError("Error in FixPyOWMMaintIssues: pyowm not installed")
|
|
return False
|
|
|
|
installed_version = self.GetLibararyVersion("pyowm")
|
|
|
|
if installed_version == None:
|
|
self.LogError("Error in FixPyOWMMaintIssues: pyowm version not found")
|
|
return None
|
|
|
|
if self.VersionTuple(installed_version) <= self.VersionTuple(
|
|
required_version
|
|
):
|
|
return True
|
|
|
|
self.LogInfo(
|
|
"Found wrong version of pyowm, uninstalling and installing the correct version."
|
|
)
|
|
|
|
self.InstallLibrary("pyowm", uninstall=True)
|
|
|
|
self.InstallLibrary("pyowm", version=required_version)
|
|
|
|
return True
|
|
except Exception as e1:
|
|
self.LogErrorLine("Error in FixPyOWMMaintIssues: " + str(e1))
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def GetLibararyVersion(self, libraryname, importonly=False):
|
|
|
|
try:
|
|
|
|
try:
|
|
import importlib
|
|
|
|
my_module = importlib.import_module(libraryname)
|
|
return my_module.__version__
|
|
except:
|
|
|
|
if importonly:
|
|
return None
|
|
# if we get here then the libarary does not support a __version__ attribute
|
|
# lets use pip to get the version
|
|
try:
|
|
# This will check if pip is installed
|
|
if "linux" in sys.platform:
|
|
self.CheckBaseSoftware()
|
|
|
|
install_list = [sys.executable, "-m", "pip", "freeze", libraryname]
|
|
|
|
process = Popen(install_list, stdout=PIPE, stderr=PIPE)
|
|
output, _error = process.communicate()
|
|
|
|
if _error:
|
|
self.LogInfo(
|
|
"Error in GetLibararyVersion using pip : "
|
|
+ libraryname
|
|
+ ": "
|
|
+ str(_error)
|
|
)
|
|
rc = process.returncode
|
|
|
|
# process output of pip freeze
|
|
lines = output.splitlines()
|
|
|
|
for line in lines:
|
|
line = line.decode("utf-8")
|
|
line = line.strip()
|
|
if line.startswith(libraryname):
|
|
items = line.split("==")
|
|
if len(items) <= 2:
|
|
return items[1]
|
|
return None
|
|
|
|
except Exception as e1:
|
|
self.LogInfo(
|
|
"Error getting version of module: "
|
|
+ libraryname
|
|
+ ": "
|
|
+ str(e1),
|
|
LogLine=True,
|
|
)
|
|
return None
|
|
except Exception as e1:
|
|
self.LogErrorLine("Error in GetLibararyVersion: " + str(e1))
|
|
return None
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def LibraryIsInstalled(self, libraryname):
|
|
|
|
try:
|
|
import importlib
|
|
|
|
my_module = importlib.import_module(libraryname)
|
|
return True
|
|
except Exception as e1:
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def InstallLibrary(self, libraryname, update=False, version=None, uninstall=False):
|
|
|
|
try:
|
|
if version != None and uninstall == False:
|
|
libraryname = libraryname + "==" + version
|
|
|
|
# This will check if pip is installed
|
|
if "linux" in sys.platform:
|
|
self.CheckBaseSoftware()
|
|
|
|
if update:
|
|
install_list = [sys.executable, "-m", "pip", "install", libraryname, "-U"]
|
|
elif uninstall:
|
|
install_list = [sys.executable, "-m", "pip", "uninstall", "-y", libraryname]
|
|
else:
|
|
install_list = [sys.executable, "-m", "pip", "install", libraryname]
|
|
|
|
process = Popen(install_list, stdout=PIPE, stderr=PIPE)
|
|
output, _error = process.communicate()
|
|
|
|
if _error:
|
|
self.LogInfo(
|
|
"Error in InstallLibrary using pip : "
|
|
+ str(install_list)
|
|
+ ": "
|
|
+ str(_error)
|
|
)
|
|
rc = process.returncode
|
|
return True
|
|
|
|
except Exception as e1:
|
|
self.LogInfo(
|
|
"Error installing module: "
|
|
+ str(install_list)
|
|
+ ": "
|
|
+ str(e1),
|
|
LogLine=True,
|
|
)
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def ValidateConfig(self):
|
|
|
|
ErrorOccured = False
|
|
if not len(self.CachedConfig):
|
|
self.LogInfo("Error: Empty configruation found.")
|
|
return False
|
|
|
|
for Module, Settiings in self.CachedConfig.items():
|
|
try:
|
|
if self.CachedConfig[Module]["enable"]:
|
|
modulepath = self.GetModulePath(
|
|
self.ModulePath, self.CachedConfig[Module]["module"]
|
|
)
|
|
if modulepath == None:
|
|
self.LogInfo(
|
|
"Enable to find file " + self.CachedConfig[Module]["module"]
|
|
)
|
|
ErrorOccured = True
|
|
|
|
# validate config file and if it is not there then copy it.
|
|
if not self.CachedConfig[Module]["conffile"] == None and len(
|
|
self.CachedConfig[Module]["conffile"]
|
|
):
|
|
ConfFileList = self.CachedConfig[Module]["conffile"].split(",")
|
|
for ConfigFile in ConfFileList:
|
|
ConfigFile = ConfigFile.strip()
|
|
if not os.path.isfile(
|
|
os.path.join(self.ConfigFilePath, ConfigFile)
|
|
):
|
|
if os.path.isfile(os.path.join(self.ConfPath, ConfigFile)):
|
|
self.LogInfo(
|
|
"Copying "
|
|
+ ConfigFile
|
|
+ " to "
|
|
+ self.ConfigFilePath
|
|
)
|
|
copyfile(
|
|
os.path.join(self.ConfPath, ConfigFile),
|
|
os.path.join(self.ConfigFilePath, ConfigFile),
|
|
)
|
|
else:
|
|
self.LogInfo(
|
|
"Enable to find config file "
|
|
+ os.path.join(self.ConfPath, ConfigFile)
|
|
)
|
|
ErrorOccured = True
|
|
except Exception as e1:
|
|
self.LogInfo(
|
|
"Error validating config for " + Module + " : " + str(e1),
|
|
LogLine=True,
|
|
)
|
|
return False
|
|
|
|
try:
|
|
if not self.CachedConfig["genmon"]["enable"]:
|
|
self.LogError("Warning: Genmon is not enabled, assume corrupt file.")
|
|
ErrorOccured = True
|
|
if not self.CachedConfig["genserv"]["enable"]:
|
|
self.LogError("Warning: Genserv is not enabled")
|
|
|
|
except Exception as e1:
|
|
self.LogErrorLine(
|
|
"Error in ValidateConfig, possible corrupt file. " + str(e1)
|
|
)
|
|
ErrorOccured = True
|
|
return not ErrorOccured
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def AddEntry(self, section=None, module=None, conffile="", args="", priority="2"):
|
|
|
|
try:
|
|
if section == None or module == None:
|
|
return
|
|
self.config.WriteSection(section)
|
|
self.config.WriteValue("module", module, section=section)
|
|
self.config.WriteValue("enable", "False", section=section)
|
|
self.config.WriteValue("hardstop", "False", section=section)
|
|
self.config.WriteValue("conffile", conffile, section=section)
|
|
self.config.WriteValue("args", args, section=section)
|
|
self.config.WriteValue("priority", priority, section=section)
|
|
except Exception as e1:
|
|
self.LogInfo("Error in AddEntry: " + str(e1), LogLine=True)
|
|
return
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def UpdateIfNeeded(self):
|
|
|
|
try:
|
|
self.config.SetSection("gengpioin")
|
|
if not self.config.HasOption("conffile"):
|
|
self.config.WriteValue(
|
|
"conffile", "gengpioin.conf", section="gengpioin"
|
|
)
|
|
self.LogError("Updated entry gengpioin.conf")
|
|
else:
|
|
defValue = self.config.ReadValue("conffile", default="")
|
|
if not len(defValue):
|
|
self.config.WriteValue(
|
|
"conffile", "gengpioin.conf", section="gengpioin"
|
|
)
|
|
self.LogError("Updated entry gengpioin.conf")
|
|
|
|
self.config.SetSection("gengpio")
|
|
if not self.config.HasOption("conffile"):
|
|
self.config.WriteValue("conffile", "gengpio.conf", section="gengpio")
|
|
self.LogError("Updated entry gengpio.conf")
|
|
else:
|
|
defValue = self.config.ReadValue("conffile", default="")
|
|
if not len(defValue):
|
|
self.config.WriteValue(
|
|
"conffile", "gengpio.conf", section="gengpio"
|
|
)
|
|
self.LogError("Updated entry gengpio.conf")
|
|
|
|
# check version info
|
|
self.config.SetSection("genloader")
|
|
self.version = self.config.ReadValue("version", default="0.0.0")
|
|
if self.version == "0.0.0" or not len(self.version):
|
|
self.version = "0.0.0"
|
|
self.NewInstall = True
|
|
|
|
if self.VersionTuple(self.version) < self.VersionTuple(
|
|
ProgramDefaults.GENMON_VERSION
|
|
):
|
|
self.Upgrade = True
|
|
|
|
if self.NewInstall or self.Upgrade:
|
|
self.config.WriteValue(
|
|
"version", ProgramDefaults.GENMON_VERSION, section="genloader"
|
|
)
|
|
if self.NewInstall:
|
|
pass
|
|
#self.LogInfo("Running one time maintenance check")
|
|
#self.FixPyOWMMaintIssues()
|
|
|
|
# TODO other version checks can be added here
|
|
|
|
self.version = ProgramDefaults.GENMON_VERSION
|
|
|
|
except Exception as e1:
|
|
self.LogInfo("Error in UpdateIfNeeded: " + str(e1), LogLine=True)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Misc maint to perform when genmon is stopped
|
|
def Maintenance(self):
|
|
|
|
# rename templogs folder to sensordata
|
|
self.OldSensorLogPath = os.path.join(ConfigFilePath, "templogs")
|
|
if os.path.isdir(self.OldSensorLogPath):
|
|
self.NewSensorLogPath = os.path.join(ConfigFilePath, "sensordata")
|
|
os.rename(self.OldSensorLogPath, self.NewSensorLogPath)
|
|
try:
|
|
from pathlib import Path
|
|
directory = Path(self.NewSensorLogPath)
|
|
|
|
# 2. Use wildcard to find files and rename them in a loop
|
|
old_prefix = "templog"
|
|
new_prefix = "sensor"
|
|
for file_path in directory.glob(f"{old_prefix}*"):
|
|
if file_path.is_file():
|
|
new_name = file_path.name.removeprefix(old_prefix)
|
|
new_file_path = file_path.with_name(f"{new_prefix}{new_name}")
|
|
|
|
# Rename the file
|
|
file_path.rename(new_file_path)
|
|
self.LogError(f"Renamed: {file_path.name} -> {new_file_path.name}")
|
|
|
|
except Exception as e1:
|
|
self.LogError("Error renaming sensor data files: " + str(e1))
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def GetConfig(self):
|
|
|
|
try:
|
|
|
|
Sections = self.config.GetSections()
|
|
ValidSections = [
|
|
"genmon",
|
|
"genserv",
|
|
"gengpio",
|
|
"gengpioin",
|
|
"genlog",
|
|
"gensms",
|
|
"gensms_modem",
|
|
"genpushover",
|
|
"gensyslog",
|
|
"genmqtt",
|
|
"genmqttin",
|
|
"genslack",
|
|
"gencallmebot",
|
|
"genexercise",
|
|
"genemail2sms",
|
|
"gentankutil",
|
|
"gencentriconnect",
|
|
"gentankdiy",
|
|
"genalexa",
|
|
"gensnmp",
|
|
"gentemp",
|
|
"gengpioledblink",
|
|
"gencthat",
|
|
"genmopeka",
|
|
"genotodata",
|
|
"genhubitat",
|
|
"gencustomgpio",
|
|
"gensms_voip",
|
|
"genloader",
|
|
"genhomeassistant",
|
|
"genhalink",
|
|
]
|
|
for entry in ValidSections:
|
|
if not entry in Sections:
|
|
if entry == "genmon" or entry == "genserv":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , file corruption. "
|
|
)
|
|
return False
|
|
if entry == "genslack":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="genslack.py",
|
|
conffile="genslack.conf",
|
|
)
|
|
if entry == "gencallmebot":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="gencallmebot.py",
|
|
conffile="gencallmebot.conf",
|
|
)
|
|
if entry == "genexercise":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="genexercise.py",
|
|
conffile="genexercise.conf",
|
|
)
|
|
if entry == "genemail2sms":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="genemail2sms.py",
|
|
conffile="genemail2sms.conf",
|
|
)
|
|
if entry == "gencentriconnect":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="gencentriconnect.py",
|
|
conffile="gencentriconnect.conf",
|
|
)
|
|
if entry == "genhomeassistant":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="genhomeassistant.py",
|
|
conffile="genhomeassistant.conf",
|
|
)
|
|
if entry == "genhalink":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="genhalink.py",
|
|
conffile="genhalink.conf",
|
|
)
|
|
if entry == "gentankutil":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="gentankutil.py",
|
|
conffile="gentankutil.conf",
|
|
)
|
|
if entry == "genalexa":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="genalexa.py",
|
|
conffile="genalexa.conf",
|
|
)
|
|
if entry == "gensnmp":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry, module="gensnmp.py", conffile="gensnmp.conf"
|
|
)
|
|
if entry == "gentemp":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry, module="gentemp.py", conffile="gentemp.conf"
|
|
)
|
|
if entry == "gentankdiy":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="gentankdiy.py",
|
|
conffile="gentankdiy.conf",
|
|
)
|
|
if entry == "gengpioledblink":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="gengpioledblink.py",
|
|
conffile="gengpioledblink.conf",
|
|
)
|
|
if entry == "gencthat":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="gencthat.py",
|
|
conffile="gencthat.conf",
|
|
)
|
|
if entry == "genmopeka":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="genmopeka.py",
|
|
conffile="genmopeka.conf",
|
|
)
|
|
if entry == "genotodata":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="genotodata.py",
|
|
conffile="genotodata.conf",
|
|
)
|
|
if entry == "genhubitat":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="genhubitat.py",
|
|
conffile="genhubitat.conf",
|
|
)
|
|
if entry == "gensms_voip":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="gensms_voip.py",
|
|
conffile="gensms_voip.conf",
|
|
)
|
|
if entry == "genmqttin":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="genmqttin.py",
|
|
conffile="genmqttin.conf",
|
|
)
|
|
if entry == "gencustomgpio":
|
|
self.LogError(
|
|
"Warning: Missing entry: " + entry + " , adding entry"
|
|
)
|
|
self.AddEntry(
|
|
section=entry,
|
|
module="gencustomgpio.py",
|
|
conffile="gencustomgpio.conf",
|
|
)
|
|
if entry == "genloader":
|
|
self.LogError("Adding entry: " + entry)
|
|
self.config.WriteSection(entry)
|
|
else:
|
|
self.LogError("Warning: Missing entry: " + entry)
|
|
|
|
self.UpdateIfNeeded()
|
|
|
|
Sections = self.config.GetSections()
|
|
for SectionName in Sections:
|
|
if SectionName == "genloader":
|
|
continue
|
|
TempDict = {}
|
|
self.config.SetSection(SectionName)
|
|
if self.config.HasOption("module"):
|
|
TempDict["module"] = self.config.ReadValue("module")
|
|
else:
|
|
self.LogError(
|
|
"Error in GetConfig: expcting module in section "
|
|
+ str(SectionName)
|
|
)
|
|
TempDict["module"] = None
|
|
|
|
if self.config.HasOption("enable"):
|
|
TempDict["enable"] = self.config.ReadValue(
|
|
"enable", return_type=bool
|
|
)
|
|
else:
|
|
self.LogError(
|
|
"Error in GetConfig: expcting enable in section "
|
|
+ str(SectionName)
|
|
)
|
|
TempDict["enable"] = False
|
|
|
|
if self.config.HasOption("hardstop"):
|
|
TempDict["hardstop"] = self.config.ReadValue(
|
|
"hardstop", return_type=bool
|
|
)
|
|
else:
|
|
self.LogError(
|
|
"Error in GetConfig: expcting hardstop in section "
|
|
+ str(SectionName)
|
|
)
|
|
TempDict["hardstop"] = False
|
|
|
|
if self.config.HasOption("conffile"):
|
|
TempDict["conffile"] = self.config.ReadValue("conffile")
|
|
else:
|
|
self.LogError(
|
|
"Error in GetConfig: expcting confile in section "
|
|
+ str(SectionName)
|
|
)
|
|
TempDict["conffile"] = None
|
|
|
|
if self.config.HasOption("args"):
|
|
TempDict["args"] = self.config.ReadValue("args")
|
|
else:
|
|
self.LogError(
|
|
"Error in GetConfig: expcting args in section "
|
|
+ str(SectionName)
|
|
)
|
|
TempDict["args"] = None
|
|
|
|
if self.config.HasOption("priority"):
|
|
TempDict["priority"] = self.config.ReadValue(
|
|
"priority", return_type=int, default=None
|
|
)
|
|
else:
|
|
self.LogError(
|
|
"Error in GetConfig: expcting priority in section "
|
|
+ str(SectionName)
|
|
)
|
|
TempDict["priority"] = None
|
|
|
|
if self.config.HasOption("postloaddelay"):
|
|
TempDict["postloaddelay"] = self.config.ReadValue(
|
|
"postloaddelay", return_type=int, default=0
|
|
)
|
|
else:
|
|
TempDict["postloaddelay"] = 0
|
|
|
|
if self.config.HasOption("pid"):
|
|
TempDict["pid"] = self.config.ReadValue(
|
|
"pid", return_type=int, default=0, NoLog=True
|
|
)
|
|
else:
|
|
TempDict["pid"] = 0
|
|
|
|
self.CachedConfig[SectionName] = TempDict
|
|
return True
|
|
|
|
except Exception as e1:
|
|
self.LogInfo("Error parsing config file: " + str(e1), LogLine=True)
|
|
return False
|
|
return True
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def ConvertToInt(self, value, default=None):
|
|
|
|
try:
|
|
return int(str(value))
|
|
except:
|
|
return default
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def GetLoadOrder(self):
|
|
|
|
LoadOrder = []
|
|
LoadDict = {}
|
|
try:
|
|
for Module, Settiings in self.CachedConfig.items():
|
|
# get the load order of all modules, even if they are disabled
|
|
# since we need to stop all modules (even disabled ones) if the
|
|
# conf file changed
|
|
try:
|
|
if self.CachedConfig[Module]["priority"] == None:
|
|
LoadDict[Module] = 99
|
|
elif self.CachedConfig[Module]["priority"] >= 0:
|
|
LoadDict[Module] = self.CachedConfig[Module]["priority"]
|
|
else:
|
|
LoadDict[Module] = 99
|
|
except Exception as e1:
|
|
self.LogInfo(
|
|
"Error reading load order (retrying): " + str(e1), LogLine=True
|
|
)
|
|
# lambda kv: (-kv[1], kv[0])
|
|
for key, value in sorted(LoadDict.items(), key=lambda kv: (-kv[1], kv[0])):
|
|
# for key, value in sorted(LoadDict.iteritems(), key=lambda (k,v): (v,k)):
|
|
LoadOrder.append(key)
|
|
except Exception as e1:
|
|
self.LogInfo("Error reading load order: " + str(e1), LogLine=True)
|
|
|
|
return LoadOrder
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def StopModules(self):
|
|
|
|
self.LogConsole("Stopping....")
|
|
if not len(self.LoadOrder):
|
|
self.LogInfo("Error, nothing to stop.")
|
|
return False
|
|
ErrorOccured = False
|
|
for Module in self.LoadOrder:
|
|
try:
|
|
if not self.UnloadModule(
|
|
self.CachedConfig[Module]["module"],
|
|
pid=self.CachedConfig[Module]["pid"],
|
|
HardStop=self.CachedConfig[Module]["hardstop"],
|
|
UsePID=True,
|
|
):
|
|
self.LogInfo("Error stopping " + Module)
|
|
ErrorOccured = True
|
|
except Exception as e1:
|
|
self.LogInfo(
|
|
"Error stopping module " + Module + " : " + str(e1), LogLine=True
|
|
)
|
|
return False
|
|
return not ErrorOccured
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def GetModulePath(self, modulepath, modulename):
|
|
try:
|
|
fullpath = os.path.join(modulepath, modulename)
|
|
|
|
if os.path.isfile(fullpath):
|
|
return modulepath
|
|
|
|
fullpath = os.path.join(modulepath, "addon", modulename)
|
|
|
|
if os.path.isfile(fullpath):
|
|
return os.path.join(modulepath, "addon")
|
|
|
|
return None
|
|
except Exception as e1:
|
|
self.LogInfo(
|
|
"Error in GetModulePath :" + modulename + " : " + str(e1), LogLine=True
|
|
)
|
|
return None
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def StartModules(self):
|
|
|
|
self.LogConsole("Starting....")
|
|
|
|
if not len(self.LoadOrder):
|
|
self.LogInfo("Error, nothing to start.")
|
|
return False
|
|
ErrorOccured = False
|
|
for Module in reversed(self.LoadOrder):
|
|
try:
|
|
if self.CachedConfig[Module]["enable"]:
|
|
modulepath = self.GetModulePath(
|
|
self.ModulePath, self.CachedConfig[Module]["module"]
|
|
)
|
|
if modulepath == None:
|
|
continue
|
|
|
|
if not multi_instance:
|
|
# check that module is not loaded already, if it is then force it (hard) to unload
|
|
attempts = 0
|
|
while True:
|
|
if MySupport.IsRunning(
|
|
prog_name=self.CachedConfig[Module]["module"],
|
|
log=self.log,
|
|
multi_instance=multi_instance,
|
|
):
|
|
# if loaded then kill it
|
|
if attempts >= 4:
|
|
# kill it
|
|
if not self.UnloadModule(
|
|
self.CachedConfig[Module]["module"],
|
|
pid=None,
|
|
HardStop=True,
|
|
UsePID=False,
|
|
):
|
|
self.LogInfo(
|
|
"Error killing "
|
|
+ self.CachedConfig[Module]["module"]
|
|
)
|
|
else:
|
|
attempts += 1
|
|
time.sleep(1)
|
|
else:
|
|
break
|
|
|
|
if not self.LoadModule(
|
|
modulepath,
|
|
self.CachedConfig[Module]["module"],
|
|
args=self.CachedConfig[Module]["args"],
|
|
):
|
|
self.LogInfo("Error starting " + Module)
|
|
ErrorOccured = True
|
|
if (
|
|
not self.CachedConfig[Module]["postloaddelay"] == None
|
|
and self.CachedConfig[Module]["postloaddelay"] > 0
|
|
):
|
|
time.sleep(self.CachedConfig[Module]["postloaddelay"])
|
|
except Exception as e1:
|
|
self.LogInfo(
|
|
"Error starting module " + Module + " : " + str(e1), LogLine=True
|
|
)
|
|
return False
|
|
return not ErrorOccured
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def LoadModuleAlt(self, modulename, args=None):
|
|
try:
|
|
self.LogConsole("Starting " + modulename)
|
|
# to load as a background process we just use os.system since Popen
|
|
# is problematic in doing this
|
|
CommandString = sys.executable + " " + modulename
|
|
if args != None and len(args):
|
|
CommandString += " " + args
|
|
CommandString += " &"
|
|
os.system(CommandString)
|
|
return True
|
|
|
|
except Exception as e1:
|
|
self.LogInfo("Error loading module: " + str(e1), LogLine=True)
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def LoadModule(self, path, modulename, args=None):
|
|
try:
|
|
try:
|
|
import os
|
|
|
|
fullmodulename = os.path.join(path, modulename)
|
|
except Exception as e1:
|
|
fullmodulename = path + "/" + modulename
|
|
|
|
if args != None:
|
|
self.LogConsole("Starting " + fullmodulename + " " + args)
|
|
else:
|
|
self.LogConsole("Starting " + fullmodulename)
|
|
try:
|
|
from subprocess import DEVNULL # py3k
|
|
except ImportError:
|
|
import os
|
|
|
|
DEVNULL = open(os.devnull, "wb")
|
|
|
|
if not len(args):
|
|
args = None
|
|
|
|
if "genserv.py" in modulename:
|
|
OutputStream = DEVNULL
|
|
else:
|
|
OutputStream = subprocess.PIPE
|
|
|
|
executelist = [sys.executable, fullmodulename]
|
|
if args != None:
|
|
executelist.extend(args.split(" "))
|
|
# This will make all the programs use the same config files
|
|
executelist.extend(["-c", self.ConfigFilePath])
|
|
# close_fds=True
|
|
pid = subprocess.Popen(
|
|
executelist,
|
|
stdout=OutputStream,
|
|
stderr=OutputStream,
|
|
stdin=OutputStream,
|
|
)
|
|
return self.UpdatePID(modulename, pid.pid)
|
|
|
|
except Exception as e1:
|
|
self.LogInfo(
|
|
"Error loading module " + path + ": " + modulename + ": " + str(e1),
|
|
LogLine=True,
|
|
)
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def UnloadModule(self, modulename, pid=None, HardStop=False, UsePID=False):
|
|
try:
|
|
LoadInfo = []
|
|
if UsePID:
|
|
if pid == None or pid == "" or pid == 0:
|
|
return True
|
|
if self.bSystemIsNotLinux:
|
|
if self.bSysetmIsWindows:
|
|
LoadInfo.append("taskkill")
|
|
LoadInfo.append("/PID")
|
|
LoadInfo.append(str(pid))
|
|
LoadInfo.append("/T")
|
|
LoadInfo.append("/F")
|
|
else:
|
|
self.LogError("ERROR in UnloadModule: This OS is not support at this time")
|
|
return False
|
|
else:
|
|
LoadInfo.append("kill")
|
|
if HardStop or self.HardStop:
|
|
LoadInfo.append("-9")
|
|
LoadInfo.append(str(pid))
|
|
else:
|
|
if self.bSystemIsNotLinux:
|
|
self.LogError("HARDKILL is not support under non linux platforms at this time")
|
|
else:
|
|
LoadInfo.append("pkill")
|
|
if HardStop or self.HardStop:
|
|
LoadInfo.append("-9")
|
|
LoadInfo.append("-u")
|
|
LoadInfo.append("root")
|
|
LoadInfo.append("-f")
|
|
LoadInfo.append(modulename)
|
|
|
|
self.LogConsole("Stopping " + modulename)
|
|
process = Popen(LoadInfo, stdout=PIPE)
|
|
output, _error = process.communicate()
|
|
rc = process.returncode
|
|
return self.UpdatePID(modulename, "")
|
|
|
|
except Exception as e1:
|
|
self.LogInfo("Error unoading module: (" + str(modulename) +"): " + str(e1), LogLine=True)
|
|
self.LogInfo(f"Module: {modulename} PID: {pid}, UsePID: {UsePID} Hardstop: {HardStop}")
|
|
return False
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def UpdatePID(self, modulename, pid=None):
|
|
|
|
try:
|
|
filename = os.path.splitext(modulename)[0] # remove extension
|
|
if not self.config.SetSection(filename):
|
|
self.LogError(
|
|
"Error settting section name in UpdatePID: " + str(filename)
|
|
)
|
|
return False
|
|
self.config.WriteValue("pid", str(pid))
|
|
return True
|
|
except Exception as e1:
|
|
self.LogInfo("Error writing PID for " + modulename + " : " + str(e1))
|
|
return False
|
|
return True
|
|
|
|
|
|
# ------------------main---------------------------------------------------------
|
|
if __name__ == "__main__":
|
|
|
|
HelpStr = "\npython genloader.py [-s -r -x -z -c configfilepath]\n"
|
|
HelpStr += " Example: python genloader.py -s\n"
|
|
HelpStr += " python genloader.py -r\n"
|
|
HelpStr += "\n -s Start Genmon modules"
|
|
HelpStr += "\n -r Restart Genmon moduels"
|
|
HelpStr += "\n -x Stop Genmon modules"
|
|
HelpStr += "\n -z Hard stop Genmon modules"
|
|
HelpStr += "\n -c Path of genmon.conf file i.e. /etc/"
|
|
HelpStr += "\n \n"
|
|
|
|
if not MySupport.PermissionsOK():
|
|
print(
|
|
"You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting."
|
|
)
|
|
sys.exit(2)
|
|
|
|
try:
|
|
ConfigFilePath = ProgramDefaults.ConfPath
|
|
opts, args = getopt.getopt(
|
|
sys.argv[1:],
|
|
"hsrxzc:",
|
|
["help", "start", "restart", "exit", "hardstop", "configpath="],
|
|
)
|
|
except getopt.GetoptError:
|
|
print(HelpStr)
|
|
sys.exit(2)
|
|
|
|
StopModules = False
|
|
StartModules = False
|
|
HardStop = False
|
|
|
|
for opt, arg in opts:
|
|
if opt == "-h":
|
|
print(HelpStr)
|
|
sys.exit()
|
|
elif opt in ("-s", "--start"):
|
|
StartModules = True
|
|
elif opt in ("-r", "--restart"):
|
|
StopModules = True
|
|
StartModules = True
|
|
elif opt in ("-x", "--exit"):
|
|
StopModules = True
|
|
elif opt in ("-z", "--hardstop"):
|
|
HardStop = True
|
|
StopModules = True
|
|
elif opt in ("-c", "--configpath"):
|
|
ConfigFilePath = arg
|
|
ConfigFilePath = ConfigFilePath.strip()
|
|
|
|
if not StartModules and not StopModules:
|
|
print("\nNo option selected.\n")
|
|
print(HelpStr)
|
|
sys.exit(2)
|
|
|
|
if not "win" in sys.platform:
|
|
tmplog = SetupLogger("genloader", "/var/log/" + "genloader.log")
|
|
if Loader.OneTimeMaint(ConfigFilePath, tmplog):
|
|
time.sleep(1.5)
|
|
port, loglocation, multi_instance = MySupport.GetGenmonInitInfo(
|
|
ConfigFilePath, log=None
|
|
)
|
|
|
|
if MySupport.IsRunning(os.path.basename(__file__), multi_instance=multi_instance):
|
|
print("\ngenloader already running.")
|
|
sys.exit(2)
|
|
|
|
LoaderObject = Loader(
|
|
start=StartModules,
|
|
stop=StopModules,
|
|
hardstop=HardStop,
|
|
ConfigFilePath=ConfigFilePath,
|
|
loglocation=loglocation,
|
|
)
|