mirror of
https://github.com/naruxde/revpipyload.git
synced 2026-08-16 05:05:52 +02:00
Merge tag '0.12.0' into pkg/debian
Release version 0.12.0
This commit is contained in:
@@ -7,7 +7,7 @@ plcworkdir_set_uid = 1
|
||||
plcprogram = program.py
|
||||
plcprogram_stop_timeout = 5
|
||||
plcprogram_watchdog = 0
|
||||
plcarguments =
|
||||
plcarguments =
|
||||
plcuid = 1000
|
||||
plcgid = 1000
|
||||
pythonversion = 3
|
||||
@@ -27,7 +27,8 @@ watchdog = 1
|
||||
[XMLRPC]
|
||||
xmlrpc = 1
|
||||
aclfile = /etc/revpipyload/aclxmlrpc.conf
|
||||
bindip = 127.0.0.1
|
||||
bindip = socket
|
||||
unixgroup = picontrol
|
||||
|
||||
[MQTT]
|
||||
mqtt = 0
|
||||
@@ -38,6 +39,6 @@ write_outputs = 0
|
||||
broker_address = localhost
|
||||
port = 1883
|
||||
tls_set = 0
|
||||
username =
|
||||
password =
|
||||
client_id =
|
||||
username =
|
||||
password =
|
||||
client_id =
|
||||
|
||||
@@ -4,4 +4,4 @@ __author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv2"
|
||||
__package__ = "revpipyload"
|
||||
__version__ = "0.11.1"
|
||||
__version__ = "0.12.0"
|
||||
|
||||
@@ -9,6 +9,7 @@ import os
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from configparser import ConfigParser
|
||||
from os import getpid
|
||||
|
||||
from . import __version__
|
||||
|
||||
@@ -55,7 +56,7 @@ def configure():
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--conffile", dest="conffile",
|
||||
default="revpipyload.conf",
|
||||
default="/etc/revpipyload/revpipyload.conf",
|
||||
help="Application configuration file"
|
||||
)
|
||||
parser.add_argument(
|
||||
@@ -186,3 +187,6 @@ def configure():
|
||||
else:
|
||||
loglevel = logging.DEBUG
|
||||
logger.setLevel(loglevel)
|
||||
|
||||
# Log PID for development purposes
|
||||
logger.debug("Running with PID {}".format(getpid()))
|
||||
|
||||
@@ -31,6 +31,7 @@ __license__ = "GPLv2"
|
||||
import gzip
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
import tarfile
|
||||
import zipfile
|
||||
from configparser import ConfigParser
|
||||
@@ -51,7 +52,7 @@ from . import proginit
|
||||
from .helper import get_revpiled_address, pi_control_reset, refullmatch
|
||||
from .shared.ipaclmanager import IpAclManager
|
||||
from .watchdogs import ResetDriverWatchdog
|
||||
from .xrpcserver import SaveXMLRPCServer
|
||||
from .xrpcserver import SaveXMLRPCServer, UnixStreamXMLRPCServer
|
||||
|
||||
min_revpimodio = "2.5.0"
|
||||
|
||||
@@ -69,7 +70,8 @@ class RevPiPyLoad:
|
||||
proginit.logger.debug("enter RevPiPyLoad.__init__()")
|
||||
|
||||
# Klassenattribute
|
||||
self._exit = True
|
||||
self._evt_exit = Event()
|
||||
self._evt_exit.set()
|
||||
self.evt_loadconfig = Event()
|
||||
self.globalconfig = ConfigParser()
|
||||
proginit.conf = self.globalconfig
|
||||
@@ -209,7 +211,9 @@ class RevPiPyLoad:
|
||||
proginit.logger.info(
|
||||
"loading config file: {0}".format(proginit.globalconffile)
|
||||
)
|
||||
self.globalconfig.read(proginit.globalconffile)
|
||||
if not self.globalconfig.read(proginit.globalconffile):
|
||||
raise RuntimeError("can not access config file '{0}'".format(proginit.globalconffile))
|
||||
|
||||
self.__translate_config()
|
||||
proginit.conf = self.globalconfig
|
||||
|
||||
@@ -313,12 +317,23 @@ class RevPiPyLoad:
|
||||
# Bind IP lesen und anpassen
|
||||
self.xmlrpcbindip = \
|
||||
self.globalconfig.get("XMLRPC", "bindip", fallback="127.0.0.1")
|
||||
if self.xmlrpcbindip == "*":
|
||||
self.xmlrpcbindip = ""
|
||||
elif self.xmlrpcbindip == "":
|
||||
self.xmlrpcbindip = "127.0.0.1"
|
||||
|
||||
if self.xmlrpcbindip.lower() == "socket":
|
||||
# Unix Domain Socket mit festem Pfad
|
||||
self.xmlrpcbindip = "/run/revpipyload/xmlrpc.socket"
|
||||
self.xmlrpcisunix = True
|
||||
elif self.xmlrpcbindip.startswith("/"):
|
||||
# Unix Domain Socket
|
||||
self.xmlrpcisunix = True
|
||||
else:
|
||||
self.xmlrpcisunix = False
|
||||
if self.xmlrpcbindip == "*":
|
||||
self.xmlrpcbindip = ""
|
||||
elif self.xmlrpcbindip == "":
|
||||
self.xmlrpcbindip = "127.0.0.1"
|
||||
|
||||
self.xmlrpcport = self.globalconfig.getint("XMLRPC", "port", fallback=55123)
|
||||
self.xmlrpcunixgroup = self.globalconfig.get("XMLRPC", "unixgroup", fallback="picontrol")
|
||||
|
||||
# Workdirectory wechseln
|
||||
if not os.access(self.plcworkdir, os.R_OK | os.W_OK | os.X_OK):
|
||||
@@ -343,7 +358,7 @@ class RevPiPyLoad:
|
||||
self.stop_plcmqtt()
|
||||
self.th_plcmqtt = self._plcmqtt()
|
||||
|
||||
if not self._exit and self.th_plcmqtt is not None:
|
||||
if not self._evt_exit.is_set() and self.th_plcmqtt is not None:
|
||||
proginit.logger.info("restart mqtt publisher after reload")
|
||||
self.th_plcmqtt.start()
|
||||
|
||||
@@ -352,7 +367,7 @@ class RevPiPyLoad:
|
||||
self.stop_plcprogram()
|
||||
self.plc = self._plcthread()
|
||||
|
||||
if not self._exit and self.plc is not None and self.autostart:
|
||||
if not self._evt_exit.is_set() and self.plc is not None and self.autostart:
|
||||
proginit.logger.info("restart plc program after reload")
|
||||
self.plc.start()
|
||||
|
||||
@@ -372,7 +387,7 @@ class RevPiPyLoad:
|
||||
self.stop_plcserver()
|
||||
self.th_plcserver = self._plcserver()
|
||||
|
||||
if not self._exit and self.th_plcserver is not None:
|
||||
if not self._evt_exit.is_set() and self.th_plcserver is not None:
|
||||
proginit.logger.info("restart plc server after reload")
|
||||
self.th_plcserver.start()
|
||||
|
||||
@@ -387,12 +402,35 @@ class RevPiPyLoad:
|
||||
self.xsrv = None
|
||||
else:
|
||||
proginit.logger.debug("create xmlrpc server")
|
||||
self.xsrv = SaveXMLRPCServer(
|
||||
(self.xmlrpcbindip, self.xmlrpcport),
|
||||
logRequests=False,
|
||||
allow_none=True,
|
||||
ipacl=self.xmlrpcacl
|
||||
)
|
||||
|
||||
if self.xmlrpcisunix:
|
||||
# Unix Domain Socket Server
|
||||
proginit.logger.info(
|
||||
"starting xmlrpc unix server on {0}".format(self.xmlrpcbindip)
|
||||
)
|
||||
|
||||
# Vorherige Socket-Datei löschen
|
||||
try:
|
||||
os.unlink(self.xmlrpcbindip)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
self.xsrv = UnixStreamXMLRPCServer(
|
||||
self.xmlrpcbindip,
|
||||
logRequests=False,
|
||||
allow_none=True,
|
||||
unixgroup=self.xmlrpcunixgroup
|
||||
)
|
||||
|
||||
else:
|
||||
# Standard IP Server
|
||||
self.xsrv = SaveXMLRPCServer(
|
||||
(self.xmlrpcbindip, self.xmlrpcport),
|
||||
logRequests=False,
|
||||
allow_none=True,
|
||||
ipacl=self.xmlrpcacl
|
||||
)
|
||||
|
||||
self.xsrv.register_introspection_functions()
|
||||
self.xsrv.register_multicall_functions()
|
||||
|
||||
@@ -485,7 +523,7 @@ class RevPiPyLoad:
|
||||
proginit.logger.debug("created xmlrpc server")
|
||||
|
||||
# Neustart bei reload
|
||||
if not self._exit:
|
||||
if not self._evt_exit.is_set():
|
||||
proginit.logger.info("bind xmlrpc-server")
|
||||
self.xsrv.server_bind()
|
||||
self.xsrv.server_activate()
|
||||
@@ -762,7 +800,7 @@ class RevPiPyLoad:
|
||||
proginit.logger.debug("enter RevPiPyLoad.start()")
|
||||
|
||||
proginit.logger.info("starting revpipyload")
|
||||
self._exit = False
|
||||
self._evt_exit.clear()
|
||||
|
||||
if self.xmlrpc and self.xsrv is not None:
|
||||
proginit.logger.info("bind xmlrpc-server")
|
||||
@@ -786,7 +824,7 @@ class RevPiPyLoad:
|
||||
pictory_reset_driver.register_call(self.xml_psstop)
|
||||
|
||||
# mainloop
|
||||
while not self._exit:
|
||||
while not self._evt_exit.is_set():
|
||||
# Neue Konfiguration laden
|
||||
if self.evt_loadconfig.is_set():
|
||||
proginit.logger.info("got reqeust to reload config")
|
||||
@@ -887,7 +925,7 @@ class RevPiPyLoad:
|
||||
def stop(self):
|
||||
"""Stop revpipyload."""
|
||||
proginit.logger.debug("enter RevPiPyLoad.stop()")
|
||||
self._exit = True
|
||||
self._evt_exit.set()
|
||||
proginit.logger.debug("leave RevPiPyLoad.stop()")
|
||||
|
||||
def stop_plcmqtt(self):
|
||||
@@ -937,6 +975,14 @@ class RevPiPyLoad:
|
||||
proginit.logger.info("close xmlrpc-server")
|
||||
self.xsrv.server_close()
|
||||
|
||||
# Unix Socket aufräumen
|
||||
if hasattr(self.xsrv, "address_family") and \
|
||||
self.xsrv.address_family == socket.AF_UNIX:
|
||||
try:
|
||||
os.unlink(self.xsrv.server_address)
|
||||
except (FileNotFoundError, AttributeError):
|
||||
pass
|
||||
|
||||
proginit.logger.debug("leave RevPiPyLoad.stop_xmlrpcserver()")
|
||||
|
||||
def xml_getconfig(self):
|
||||
|
||||
@@ -18,6 +18,7 @@ def main() -> int:
|
||||
"""Secure installation script to use on Revolution Pi."""
|
||||
from configparser import ConfigParser
|
||||
from os import R_OK, access, getuid, system
|
||||
from shutil import which
|
||||
from re import match
|
||||
from sys import stderr, stdout
|
||||
|
||||
@@ -42,7 +43,9 @@ def main() -> int:
|
||||
# Prepare variables
|
||||
xmlrpcacl = IpAclManager(minlevel=0, maxlevel=4)
|
||||
xmlrpcacl.loadaclfile(aclxmlrpc)
|
||||
xmlrpc_only_localhost = xmlrpcbindip.find("127.") == 0 or xmlrpcbindip == ""
|
||||
xmlrpc_unix_socket = xmlrpcbindip == "socket" or xmlrpcbindip.startswith("/")
|
||||
xmlrpc_only_localhost = xmlrpcbindip.find("127.") == 0 or xmlrpcbindip == "" or \
|
||||
xmlrpc_unix_socket
|
||||
|
||||
# ----- Print summary of actual configuration
|
||||
stdout.write("""
|
||||
@@ -58,7 +61,8 @@ def main() -> int:
|
||||
aclxmlrpc=aclxmlrpc,
|
||||
xmlrpc="" if xmlrpc else "NOT ",
|
||||
source="" if not xmlrpc
|
||||
else " from this computer only (localhost)." if xmlrpc_only_localhost
|
||||
else " from this computer / SSH only (unix socket)." if xmlrpcbindip == "socket" or xmlrpcbindip.startswith("/")
|
||||
else " from this computer / SSH only (localhost)." if xmlrpc_only_localhost
|
||||
else " from ACL listed remote computers!"
|
||||
))
|
||||
|
||||
@@ -81,7 +85,7 @@ def main() -> int:
|
||||
stdout.write("{0:15} - Level: {1:2}".format(ip, level))
|
||||
counter += 1
|
||||
stdout.write("\n")
|
||||
else:
|
||||
elif not xmlrpc_unix_socket:
|
||||
stderr.write(
|
||||
"\nWARNING: NO IP addresses defined in ACL!\n You will "
|
||||
"not be able to connect with RevPiPyControl at this moment!\n"
|
||||
@@ -99,10 +103,18 @@ def main() -> int:
|
||||
stderr.write("\nYou need root permissions to change values (sudo).\n")
|
||||
return 4
|
||||
|
||||
cmd = input("\nDo you want to allow connections from remote hosts? (y/N) ").lower()
|
||||
cmd = input("\nDo you want to allow connections ONLY via SSH/Unix-Socket? (y/N) ").lower()
|
||||
if cmd == "y":
|
||||
conf.set("XMLRPC", "xmlrpc", "1")
|
||||
conf.set("XMLRPC", "bindip", "socket")
|
||||
xmlrpc_unix_socket = True
|
||||
xmlrpcacl.acl = ""
|
||||
save_xmlrpcacls()
|
||||
|
||||
elif input("\nDo you want to allow native TCP connections from remote hosts? (y/N) ").lower() == "y":
|
||||
conf.set("XMLRPC", "xmlrpc", "1")
|
||||
conf.set("XMLRPC", "bindip", "*")
|
||||
xmlrpc_unix_socket = False
|
||||
|
||||
cmd = input("Reset the ACL file to allow all private networks? (y/N) ").lower()
|
||||
if cmd == "y":
|
||||
@@ -135,12 +147,13 @@ def main() -> int:
|
||||
stderr.write("Wrong format (0.0.0.0)\n")
|
||||
|
||||
else:
|
||||
cmd = input("Do you want to allow connections from localhost ONLY? (y/N) ").lower()
|
||||
cmd = input("Do you want to allow TCP connections from localhost ONLY? (y/N) ").lower()
|
||||
if cmd == "y":
|
||||
conf.set("XMLRPC", "xmlrpc", "1")
|
||||
conf.set("XMLRPC", "bindip", "127.0.0.1")
|
||||
xmlrpc_unix_socket = False
|
||||
|
||||
cmd = input("Reset the ACL file to allow localhost connections only? (y/N) ").lower()
|
||||
cmd = input("Reset the ACL file to allow localhost TCP connections only? (y/N) ").lower()
|
||||
if cmd == "y":
|
||||
xmlrpcacl.acl = "127.*.*.*,4 "
|
||||
save_xmlrpcacls()
|
||||
@@ -153,6 +166,7 @@ def main() -> int:
|
||||
if cmd == "y":
|
||||
conf.set("XMLRPC", "xmlrpc", "0")
|
||||
conf.set("XMLRPC", "bindip", "127.0.0.1")
|
||||
xmlrpc_unix_socket = False
|
||||
xmlrpcacl.acl = ""
|
||||
save_xmlrpcacls()
|
||||
else:
|
||||
@@ -169,15 +183,25 @@ def main() -> int:
|
||||
stdout.write("\n\nWe did no changes!\n")
|
||||
return 2
|
||||
|
||||
if system("/etc/init.d/revpipyload status > /dev/null") == 0:
|
||||
# Check for service manager
|
||||
if which("systemctl"):
|
||||
cmd_status = "systemctl status revpipyload > /dev/null"
|
||||
cmd_reload = "systemctl reload revpipyload"
|
||||
hint_reload = "sudo systemctl reload revpipyload"
|
||||
else:
|
||||
cmd_status = "/etc/init.d/revpipyload status > /dev/null"
|
||||
cmd_reload = "/etc/init.d/revpipyload reload"
|
||||
hint_reload = "sudo /etc/init.d/revpipyload reload"
|
||||
|
||||
if system(cmd_status) == 0:
|
||||
try:
|
||||
cmd = input("\nDo you want to apply the new settings now? (Y/n) ").lower()
|
||||
if cmd in ("", "y"):
|
||||
system("/etc/init.d/revpipyload reload")
|
||||
system(cmd_reload)
|
||||
else:
|
||||
stderr.write(
|
||||
"\nYou have to activate the new settings for RevPiPyLoad!\n"
|
||||
" sudo /etc/init.d/revpipyload reload\n"
|
||||
" {0}\n".format(hint_reload)
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
@@ -4,6 +4,9 @@ __author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv2"
|
||||
|
||||
import grp
|
||||
import os
|
||||
import socket
|
||||
from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
|
||||
|
||||
from . import proginit
|
||||
@@ -102,3 +105,94 @@ class SaveXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class UnixStreamXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
|
||||
"""XML-RPC Request-Handler fuer Unix Domain Sockets."""
|
||||
|
||||
timeout = 2.0
|
||||
|
||||
def setup(self):
|
||||
"""Initialisiert die Verbindung ohne TCP-spezifische Socket-Optionen."""
|
||||
self.connection = self.request
|
||||
if self.timeout is not None:
|
||||
self.connection.settimeout(self.timeout)
|
||||
self.rfile = self.connection.makefile("rb", self.rbufsize)
|
||||
self.wfile = self.connection.makefile("wb", self.wbufsize)
|
||||
|
||||
def address_string(self):
|
||||
"""Liefert einen Namen fuer Logging bei Unix Domain Sockets."""
|
||||
return "localhost"
|
||||
|
||||
|
||||
class UnixStreamXMLRPCServer(SimpleXMLRPCServer):
|
||||
"""XML-RPC Server fuer Unix Domain Sockets."""
|
||||
address_family = socket.AF_UNIX
|
||||
|
||||
def __init__(
|
||||
self, addr, logRequests=True, allow_none=False, unixgroup="picontrol"):
|
||||
"""Init UnixStreamXMLRPCServer class."""
|
||||
proginit.logger.debug("enter UnixStreamXMLRPCServer.__init__()")
|
||||
|
||||
self.timeout = 0.5
|
||||
self.unixgroup = unixgroup
|
||||
|
||||
# Create subdirectories for Unix Domain Socket
|
||||
socket_dir = os.path.dirname(addr)
|
||||
if socket_dir:
|
||||
os.makedirs(socket_dir, exist_ok=True)
|
||||
|
||||
super().__init__(
|
||||
addr=addr,
|
||||
requestHandler=UnixStreamXMLRPCRequestHandler,
|
||||
logRequests=logRequests,
|
||||
allow_none=allow_none,
|
||||
encoding="utf-8",
|
||||
bind_and_activate=False,
|
||||
)
|
||||
|
||||
proginit.logger.warning("RevPi Commander 0.12.0 or greater is required to connect via Unix Domain Socket")
|
||||
|
||||
proginit.logger.debug("leave UnixStreamXMLRPCServer.__init__()")
|
||||
|
||||
def server_bind(self):
|
||||
"""Ueberschreibt server_bind um Berechtigungen zu setzen."""
|
||||
super().server_bind()
|
||||
|
||||
# Gruppe setzen
|
||||
try:
|
||||
gid = grp.getgrnam(self.unixgroup).gr_gid
|
||||
# -1 will leave the owner unchanged
|
||||
os.chown(self.server_address, -1, gid)
|
||||
except (KeyError, PermissionError):
|
||||
proginit.logger.warning(
|
||||
"can not set group of socket {0} to '{1}'"
|
||||
"".format(self.server_address, self.unixgroup)
|
||||
)
|
||||
|
||||
# Berechtigungen fuer restliche Benutzer entziehen
|
||||
try:
|
||||
os.chmod(self.server_address, 0o660)
|
||||
except PermissionError:
|
||||
proginit.logger.warning(
|
||||
"can not set permissions of socket {0}"
|
||||
"".format(self.server_address)
|
||||
)
|
||||
|
||||
def _dispatch(self, method, params):
|
||||
"""Prueft ACL Level fuer angeforderte Methode.
|
||||
|
||||
@param method Angeforderte Methode
|
||||
@param params Argumente fuer Methode
|
||||
@return Dispatched data
|
||||
|
||||
"""
|
||||
# ACL on a socket is always max level
|
||||
if method == "xmlmodus":
|
||||
params = (4,)
|
||||
|
||||
return super()._dispatch(method, params)
|
||||
|
||||
def register_function(self, acl_level, function, name=None):
|
||||
"""Override register_function to ignore acl_level for unix sockets."""
|
||||
return super().register_function(function, name)
|
||||
|
||||
Reference in New Issue
Block a user