From c9877d82304f27a9c734556fae79a219b43d99cb Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Fri, 24 Nov 2023 11:28:09 +0100 Subject: [PATCH 01/11] chore: Release 0.11.0 --- src/revpipyload/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/revpipyload/__init__.py b/src/revpipyload/__init__.py index 743751b..47f8af9 100644 --- a/src/revpipyload/__init__.py +++ b/src/revpipyload/__init__.py @@ -4,4 +4,4 @@ __author__ = "Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager" __license__ = "GPLv2" __package__ = "revpipyload" -__version__ = "0.11.0rc2" +__version__ = "0.11.0" From 286b146b5dace321a2068b24c2ed89686101d2a9 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Mon, 22 Jun 2026 11:02:24 +0200 Subject: [PATCH 02/11] fix: Ensure file operations are restricted to PLC working directory - Added checks to validate if file paths reside within the PLC working directory. - Logs a warning and prevents operations when paths fall outside the permissible directory. Signed-off-by: Sven Sager Signed-off-by: Sven Sager --- src/revpipyload/revpipyload.py | 48 ++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/revpipyload/revpipyload.py b/src/revpipyload/revpipyload.py index 87aac87..10baa30 100644 --- a/src/revpipyload/revpipyload.py +++ b/src/revpipyload/revpipyload.py @@ -1064,11 +1064,19 @@ class RevPiPyLoad: :param file_name: File with full path relative to work directory :return: True on success """ - file_name = os.path.join(self.plcworkdir, file_name) - if os.path.exists(file_name): - os.remove(file_name) - dirname = os.path.dirname(file_name) - if dirname != self.plcworkdir: + plcworkdir = os.path.realpath(self.plcworkdir) + file_path = os.path.realpath(os.path.join(plcworkdir, file_name)) + + if os.path.commonpath([plcworkdir, file_path]) != plcworkdir: + proginit.logger.warning( + "file path is not in plc working directory" + ) + return False + + if os.path.exists(file_path): + os.remove(file_path) + dirname = os.path.dirname(file_path) + if dirname != plcworkdir: try: # Try to remove directory, which will work if it is empty os.rmdir(dirname) @@ -1104,9 +1112,17 @@ class RevPiPyLoad: :param file_name: File with full path relative to work directory :return: Binary object in gzip format """ - file_name = os.path.join(self.plcworkdir, file_name) - if os.path.exists(file_name): - with open(file_name, "rb") as fh: + plcworkdir = os.path.realpath(self.plcworkdir) + file_path = os.path.realpath(os.path.join(plcworkdir, file_name)) + + if os.path.commonpath([plcworkdir, file_path]) != plcworkdir: + proginit.logger.warning( + "file path is not in plc working directory" + ) + return Binary() + + if os.path.exists(file_path): + with open(file_path, "rb") as fh: xmldata = Binary(gzip.compress(fh.read())) return xmldata return Binary() @@ -1169,7 +1185,7 @@ class RevPiPyLoad: else: return -1 - def xml_plcupload(self, filedata, filename): + def xml_plcupload(self, filedata: Binary, filename:str): """Empfaengt Dateien fuer das PLC Programm einzeln. @param filedata GZIP Binary data der Datei @@ -1185,14 +1201,18 @@ class RevPiPyLoad: # Windowszeichen prüfen filename = filename.replace("\\", "/") - # Build absolut path, join will return last element, if absolute - dirname = os.path.join(self.plcworkdir, os.path.dirname(filename)) - if os.path.abspath(dirname).find(self.plcworkdir) != 0: + + plcworkdir = os.path.realpath(self.plcworkdir) + file_path = os.path.realpath(os.path.join(plcworkdir, filename)) + + if os.path.commonpath([plcworkdir, file_path]) != plcworkdir: proginit.logger.warning( "file path is not in plc working directory" ) return False + dirname = os.path.dirname(file_path) + set_uid = self.plcuid if self.plcworkdir_set_uid else 0 set_gid = self.plcgid if self.plcworkdir_set_uid else 0 @@ -1210,9 +1230,9 @@ class RevPiPyLoad: # Datei erzeugen try: - with open(filename, "wb") as fh: + with open(file_path, "wb") as fh: fh.write(gzip.decompress(filedata.data)) - os.chown(filename, set_uid, set_gid) + os.chown(file_path, set_uid, set_gid) return True except Exception: return False From 9f7f8262bb7ebb6c22f54c958af8a5718daf84cd Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Mon, 22 Jun 2026 11:11:24 +0200 Subject: [PATCH 03/11] chore: Release 0.11.1 Signed-off-by: Sven Sager --- src/revpipyload/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/revpipyload/__init__.py b/src/revpipyload/__init__.py index 47f8af9..7b4b92d 100644 --- a/src/revpipyload/__init__.py +++ b/src/revpipyload/__init__.py @@ -4,4 +4,4 @@ __author__ = "Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager" __license__ = "GPLv2" __package__ = "revpipyload" -__version__ = "0.11.0" +__version__ = "0.11.1" From 835c3a1b61100e803168ff5e5507b1b7acde9b9b Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Mon, 27 Jul 2026 12:59:20 +0200 Subject: [PATCH 04/11] refactor: Replace `_exit` flag with `_evt_exit` event Replaced the `_exit` boolean flag with an `Event` object (`_evt_exit`) to improve thread-safe control of the program's shutdown process. Signed-off-by: Sven Sager --- src/revpipyload/revpipyload.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/revpipyload/revpipyload.py b/src/revpipyload/revpipyload.py index 10baa30..bb663bd 100644 --- a/src/revpipyload/revpipyload.py +++ b/src/revpipyload/revpipyload.py @@ -69,7 +69,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 @@ -343,7 +344,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 +353,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 +373,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() @@ -485,7 +486,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 +763,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 +787,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 +888,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): From aad540593d7a118fe86d7b690a6751107a770345 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Fri, 24 Jul 2026 10:51:42 +0200 Subject: [PATCH 05/11] feat: Add option to use unix socket for xml rpc server --- data/etc/revpipyload/revpipyload.conf | 2 +- src/revpipyload/revpipyload.py | 65 ++++++++++++++++++++++----- src/revpipyload/xrpcserver.py | 42 +++++++++++++++++ 3 files changed, 97 insertions(+), 12 deletions(-) diff --git a/data/etc/revpipyload/revpipyload.conf b/data/etc/revpipyload/revpipyload.conf index 52f0d8f..37e1eaa 100644 --- a/data/etc/revpipyload/revpipyload.conf +++ b/data/etc/revpipyload/revpipyload.conf @@ -27,7 +27,7 @@ watchdog = 1 [XMLRPC] xmlrpc = 1 aclfile = /etc/revpipyload/aclxmlrpc.conf -bindip = 127.0.0.1 +bindip = socket [MQTT] mqtt = 0 diff --git a/src/revpipyload/revpipyload.py b/src/revpipyload/revpipyload.py index bb663bd..ff762de 100644 --- a/src/revpipyload/revpipyload.py +++ b/src/revpipyload/revpipyload.py @@ -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" @@ -314,10 +315,20 @@ 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) @@ -388,12 +399,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, + requestHandler=UnixStreamXMLRPCRequestHandler, + logRequests=False, + allow_none=True, + ) + + 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() @@ -936,8 +970,17 @@ class RevPiPyLoad: if self.xsrv is not None: proginit.logger.info("close xmlrpc-server") + self.xsrv.shutdown() 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): diff --git a/src/revpipyload/xrpcserver.py b/src/revpipyload/xrpcserver.py index 9f9a616..583ad64 100644 --- a/src/revpipyload/xrpcserver.py +++ b/src/revpipyload/xrpcserver.py @@ -4,6 +4,7 @@ __author__ = "Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager" __license__ = "GPLv2" +import socket from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer from . import proginit @@ -102,3 +103,44 @@ 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 _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) From 565ecf5d8bd9c89db3f5e32cf5fa5e6fb506c4bc Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Mon, 27 Jul 2026 10:34:53 +0200 Subject: [PATCH 06/11] feat: Add unix group support for XML-RPC server socket permissions - Introduced `unixgroup` configuration for XML-RPC in `revpipyload.conf`. - Updated `UnixStreamXMLRPCServer` to set socket group ownership and permissions. Signed-off-by: Sven Sager --- data/etc/revpipyload/revpipyload.conf | 9 ++--- src/revpipyload/revpipyload.py | 3 +- src/revpipyload/xrpcserver.py | 50 +++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/data/etc/revpipyload/revpipyload.conf b/data/etc/revpipyload/revpipyload.conf index 37e1eaa..e38ce76 100644 --- a/data/etc/revpipyload/revpipyload.conf +++ b/data/etc/revpipyload/revpipyload.conf @@ -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 @@ -28,6 +28,7 @@ watchdog = 1 xmlrpc = 1 aclfile = /etc/revpipyload/aclxmlrpc.conf 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 = diff --git a/src/revpipyload/revpipyload.py b/src/revpipyload/revpipyload.py index ff762de..175a551 100644 --- a/src/revpipyload/revpipyload.py +++ b/src/revpipyload/revpipyload.py @@ -331,6 +331,7 @@ class RevPiPyLoad: 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): @@ -414,9 +415,9 @@ class RevPiPyLoad: self.xsrv = UnixStreamXMLRPCServer( self.xmlrpcbindip, - requestHandler=UnixStreamXMLRPCRequestHandler, logRequests=False, allow_none=True, + unixgroup=self.xmlrpcunixgroup ) else: diff --git a/src/revpipyload/xrpcserver.py b/src/revpipyload/xrpcserver.py index 583ad64..7d51ce7 100644 --- a/src/revpipyload/xrpcserver.py +++ b/src/revpipyload/xrpcserver.py @@ -4,6 +4,8 @@ __author__ = "Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager" __license__ = "GPLv2" +import grp +import os import socket from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer @@ -127,6 +129,54 @@ 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.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. From 8017ebd854196d3304dcdb37d0d4eadfac38732a Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Mon, 27 Jul 2026 12:57:59 +0200 Subject: [PATCH 07/11] fix: Remove shutdown, this resolves a dead lock --- src/revpipyload/revpipyload.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/revpipyload/revpipyload.py b/src/revpipyload/revpipyload.py index 175a551..89b19c1 100644 --- a/src/revpipyload/revpipyload.py +++ b/src/revpipyload/revpipyload.py @@ -971,7 +971,6 @@ class RevPiPyLoad: if self.xsrv is not None: proginit.logger.info("close xmlrpc-server") - self.xsrv.shutdown() self.xsrv.server_close() # Unix Socket aufräumen From a3b18b22eedb74e2e13836a952cda8fd53a1456c Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Tue, 4 Aug 2026 14:42:27 +0200 Subject: [PATCH 08/11] feat: Enhance secure-installation command with Unix socket support Signed-off-by: Sven Sager --- src/revpipyload/secure_installation.py | 34 ++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/revpipyload/secure_installation.py b/src/revpipyload/secure_installation.py index 070650c..35e185c 100644 --- a/src/revpipyload/secure_installation.py +++ b/src/revpipyload/secure_installation.py @@ -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,8 +103,14 @@ 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") + xmlrpcacl.acl = "" + save_xmlrpcacls() + + elif input("\nDo you want to allow connections from remote hosts? (y/N) ").lower() == "y": conf.set("XMLRPC", "xmlrpc", "1") conf.set("XMLRPC", "bindip", "*") @@ -169,15 +179,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 From 22491195d505d2e0cb67b68d7399f5658693e1b1 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Wed, 5 Aug 2026 07:09:14 +0200 Subject: [PATCH 09/11] chore: Log process ID during initialization for debugging purposes Signed-off-by: Sven Sager --- src/revpipyload/proginit.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/revpipyload/proginit.py b/src/revpipyload/proginit.py index d508627..8ce2f06 100644 --- a/src/revpipyload/proginit.py +++ b/src/revpipyload/proginit.py @@ -9,6 +9,7 @@ import os import sys from argparse import ArgumentParser from configparser import ConfigParser +from os import getpid from . import __version__ @@ -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())) From a9dcc82bddf6dcb0ab6758c71601c5328484af1b Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Thu, 6 Aug 2026 08:58:34 +0200 Subject: [PATCH 10/11] fix: Set default config path and raise error for inaccessible file Set default config path to `/etc/revpipyload/revpipyload.conf` and raise error for inaccessible config files. Signed-off-by: Sven Sager --- src/revpipyload/proginit.py | 2 +- src/revpipyload/revpipyload.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/revpipyload/proginit.py b/src/revpipyload/proginit.py index 8ce2f06..71f3679 100644 --- a/src/revpipyload/proginit.py +++ b/src/revpipyload/proginit.py @@ -56,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( diff --git a/src/revpipyload/revpipyload.py b/src/revpipyload/revpipyload.py index 89b19c1..74ab252 100644 --- a/src/revpipyload/revpipyload.py +++ b/src/revpipyload/revpipyload.py @@ -211,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 From 302bb0f8ad4c618444f3cf57183236a96546d903 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Thu, 6 Aug 2026 11:46:31 +0200 Subject: [PATCH 11/11] chore: Release 0.12.0 Signed-off-by: Sven Sager --- src/revpipyload/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/revpipyload/__init__.py b/src/revpipyload/__init__.py index 7b4b92d..afe436f 100644 --- a/src/revpipyload/__init__.py +++ b/src/revpipyload/__init__.py @@ -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"