From 339a6c9709dead8b835d4fc75d9f6a0d30df5eb8 Mon Sep 17 00:00:00 2001 From: Michal Mikolas Date: Tue, 11 Mar 2025 12:25:55 +0100 Subject: [PATCH 1/9] Split config into *.example files & added config files to .gitignore. --- .gitignore | 2 + index.php.example | 4 + pombo.conf => pombo.conf.example | 300 +++++++++++++++---------------- pombo.php | 5 - 4 files changed, 156 insertions(+), 155 deletions(-) create mode 100644 index.php.example rename pombo.conf => pombo.conf.example (97%) diff --git a/.gitignore b/.gitignore index 5971626..4d6269c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ windows/Output/ htmlcov/ .mypy_cache/ .tox/ +index.php +pombo.conf # GUI gui/bin/ diff --git a/index.php.example b/index.php.example new file mode 100644 index 0000000..188c37c --- /dev/null +++ b/index.php.example @@ -0,0 +1,4 @@ + -; W = Windows -; L = GNU/Linux -; M = Mac OSX -; - -; Complete path of GnuPG binary. Not needed if encryption is not used. -; [W] C:\\pombo\\bin\\gpg.exe -; [L] /usr/bin/gpg -; [M] /usr/local/bin/gpg -gpg_binary=C:\\pombo\\bin\\gpg.exe - -; Get the current TCP/IP network interfaces. -; [W] ipconfig /all -; [L] /sbin/ip a -; [L] /sbin/ifconfig -a (deprecated) -; [M] /sbin/ifconfig -a -network_config=ipconfig /all - -; Get the list of Access Points and Ad-Hoc cells in range, and -; optionally a whole bunch of information about them (ESSID, Qual- -; ity, Frequency, Mode ...). -; [W] wlan-dump.bat (only for Windows XP, installed since Pombo 0.0.10) -; [W] netsh wlan show all (note available on Windows XP) -; [L] /sbin/iwlist scanning -; [M] /System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -s -wifi_access_points=netsh wlan show all - -; Get the route over the network between two systems, listing all the -; intermediate routers a connection must pass through to get to its -; destination. -; [W] tracert -d www.example.org -; [L] /usr/bin/traceroute -q1 www.example.com -; [M] /usr/sbin/traceroute -q1 www.example.com -traceroute=tracert -d www.example.org - -; Get network connections (both incoming and outgoing), routing tables, -; and a number of network interface statistics. -; [W] netstat -n -; [L] /bin/ss -putn -; [L] /bin/netstat -putn (deprecated) -; [M] /usr/sbin/netstat -utn -network_trafic=netstat -n - -; Take screen shot? -; [M,L,W] yes or no -screenshot=yes - -; Take webcam shot? -; will be replaced by a filename, do not customize (required). -; [W] yes or no -; [L] /usr/bin/streamer -q -t 1 -r 2 -o -; [L] /usr/bin/streamer -q -t 1 -r 2 -j 100 -s 640x480 -o -; [L] /usr/bin/streamer -q -w 3 -o -; [L] /usr/bin/streamer -q -j 100 -w 3 -s 640x480 -o -; [L] /usr/bin/gst-launch -q v4l2src num_buffers=1 decimate=70 ! pngenc ! filesink location= -; [M] /usr/local/bin/imagesnap -q -w 3.00 -camshot=yes - -; A try to fix most of webcam shot errors is to specify the picture -; extension to feet with your software/hardware. -; Try yourself into a console to find the good one. -; Few possible extensions are: png, jpeg, ppm, bmp, tiff. -; JPEG has the best ratio compression/quality. -; If PPM is chosen, it will be converted to JPEG using /usr/bin/convert. -; [W] not used -; [L,M] png, jpeg, ppm, bmp or tiff -camshot_filetype=jpeg +; Pombo configuration file + +[General] +; +; General parameters --------------------------------------------------- +; + +; Public keyID. +; [NOT recommended] Set it to "i_dont_wanna_use_encryption_and_i_assume" +; to disable report encryption. +gpgkeyid=BAADF00D + +; Password which must be the same as in pombo.php. +password=mysecret + +; Server URL. If several servers, separate them with a "|". +; Example: http://myserver.com/pombo.php +; Example: http://myserver.com/pombo.php|http://myserver2.com/pombo.php +server_url= + +; File to check on one server to tell pombo the computer was stolen. +; If file exists, pombo will send reports each 5 minutes. +; Must be the same as in pombo.php. +check_file=.stolen + +; Time between each report (in minutes). +; When stolen, time between each report is this option divided by 3: +; if time_limit=15, when stolen it will be 15/3 = 5 min. +; On GNU/Linux, think to adapt /etc/cron/pombo. +; On Mac OSX, think to adapt /etc/crontab. +time_limit=15 + +; Email ID to send report as attached file. +; Leave it blank if you do not want to use this feature. +email_id= + +; If True, pombo will check and send report only when IP is not +; the same as the first run. +; This option does not have effect for stolen computer. +; You could add several IP by using "add" option or use add-ip.bat +; on Windows. +only_on_ip_change=no + +; Enable informations logging (not recommended, only for debug/dev purpose) +enable_log=no + +; Authentification settings -------------------------------------------- +; Proxy +use_proxy=no + +; Prefer environment variables? +use_env=no + +; Proxy URL +; Example: http://proxyurl:proxyport +; Example with auth: http://username:password@proxyurl:proxyport +http_proxy= +https_proxy= + +; .htaccess authentification for one server +; For auth_server, only specify the domain, example: +; if serverurl=http://myserver.com/pombo.php, auth_server=myserver.com +auth_server= +auth_user= +auth_pswd= + + +[Commands] +; +; #################################################################### +; # # +; # /!\ For GNU/Linux & Mac OSX users /!\ # +; # # +; # Use the tool's full path # +; # You can try the 'which' command to know where they are situated. # +; # Example: which ifconfig # +; # # +; #################################################################### +; +; To disable a command, blank it (for example, camshot=). +; +; Examples are formated as: +; [OS] +; W = Windows +; L = GNU/Linux +; M = Mac OSX +; + +; Complete path of GnuPG binary. Not needed if encryption is not used. +; [W] C:\\pombo\\bin\\gpg.exe +; [L] /usr/bin/gpg +; [M] /usr/local/bin/gpg +gpg_binary=C:\\pombo\\bin\\gpg.exe + +; Get the current TCP/IP network interfaces. +; [W] ipconfig /all +; [L] /sbin/ip a +; [L] /sbin/ifconfig -a (deprecated) +; [M] /sbin/ifconfig -a +network_config=ipconfig /all + +; Get the list of Access Points and Ad-Hoc cells in range, and +; optionally a whole bunch of information about them (ESSID, Qual- +; ity, Frequency, Mode ...). +; [W] wlan-dump.bat (only for Windows XP, installed since Pombo 0.0.10) +; [W] netsh wlan show all (note available on Windows XP) +; [L] /sbin/iwlist scanning +; [M] /System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -s +wifi_access_points=netsh wlan show all + +; Get the route over the network between two systems, listing all the +; intermediate routers a connection must pass through to get to its +; destination. +; [W] tracert -d www.example.org +; [L] /usr/bin/traceroute -q1 www.example.com +; [M] /usr/sbin/traceroute -q1 www.example.com +traceroute=tracert -d www.example.org + +; Get network connections (both incoming and outgoing), routing tables, +; and a number of network interface statistics. +; [W] netstat -n +; [L] /bin/ss -putn +; [L] /bin/netstat -putn (deprecated) +; [M] /usr/sbin/netstat -utn +network_trafic=netstat -n + +; Take screen shot? +; [M,L,W] yes or no +screenshot=yes + +; Take webcam shot? +; will be replaced by a filename, do not customize (required). +; [W] yes or no +; [L] /usr/bin/streamer -q -t 1 -r 2 -o +; [L] /usr/bin/streamer -q -t 1 -r 2 -j 100 -s 640x480 -o +; [L] /usr/bin/streamer -q -w 3 -o +; [L] /usr/bin/streamer -q -j 100 -w 3 -s 640x480 -o +; [L] /usr/bin/gst-launch -q v4l2src num_buffers=1 decimate=70 ! pngenc ! filesink location= +; [M] /usr/local/bin/imagesnap -q -w 3.00 +camshot=yes + +; A try to fix most of webcam shot errors is to specify the picture +; extension to feet with your software/hardware. +; Try yourself into a console to find the good one. +; Few possible extensions are: png, jpeg, ppm, bmp, tiff. +; JPEG has the best ratio compression/quality. +; If PPM is chosen, it will be converted to JPEG using /usr/bin/convert. +; [W] not used +; [L,M] png, jpeg, ppm, bmp or tiff +camshot_filetype=jpeg diff --git a/pombo.php b/pombo.php index 8bd56c6..6f62b1b 100644 --- a/pombo.php +++ b/pombo.php @@ -20,11 +20,6 @@ * require '../pombo.php'; * ?> */ - if ( !isset($PASSWORD) ) - $PASSWORD = ''; - if ( !isset($CHECKFILE) ) - $CHECKFILE = ''; - if ( !function_exists('hash_hmac') ) { //Calculate HMAC-SHA1 according to RFC2104 // http://www.ietf.org/rfc/rfc2104.txt From 289ca3bb6caff629fa16fffe71ba6f4df3553605 Mon Sep 17 00:00:00 2001 From: Michal Mikolas Date: Wed, 12 Mar 2025 13:04:43 +0100 Subject: [PATCH 2/9] Added portable mode; Small bugfixes; Hotfixed not being able to install 'VideoCapture' library. --- .gitignore | 6 ++++ pombo.conf.example | 2 +- pombo.py | 15 ++++++---- pombo_portable.py | 67 +++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | Bin 0 -> 58 bytes 5 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 pombo_portable.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 4d6269c..389dbfd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,14 @@ windows/Output/ htmlcov/ .mypy_cache/ .tox/ +.venv index.php pombo.conf +.vscode + +# Pombo Portable (IP file & log file) +pombo +pombo.log # GUI gui/bin/ diff --git a/pombo.conf.example b/pombo.conf.example index 1a4c332..9e1e818 100644 --- a/pombo.conf.example +++ b/pombo.conf.example @@ -130,7 +130,7 @@ screenshot=yes ; Take webcam shot? ; will be replaced by a filename, do not customize (required). -; [W] yes or no +; [W] yes or (blank to disable) ; [L] /usr/bin/streamer -q -t 1 -r 2 -o ; [L] /usr/bin/streamer -q -t 1 -r 2 -j 100 -s 640x480 -o ; [L] /usr/bin/streamer -q -w 3 -o diff --git a/pombo.py b/pombo.py index 6f63b76..2244128 100644 --- a/pombo.py +++ b/pombo.py @@ -71,7 +71,6 @@ if sys.platform == "win32": # pragma: no cover from PIL import Image - from VideoCapture import Device __version__ = "1.1b1" @@ -299,11 +298,11 @@ def get_manufacturer(self): if len(res) < 3: manufacturer = "Unknown" else: - manufacturer = "-".join( + manufacturer = " - ".join( [ - res[1].split("=")[1].strip(), - res[0].split("=")[1].strip(), - res[2].split("=")[1].strip(), + res[1].split("=")[1].strip() if '=' in res[1] else res[1], + res[0].split("=")[1].strip() if '=' in res[0] else res[0], + res[2].split("=")[1].strip() if '=' in res[2] else res[2], ] ) elif self.is_mac(): @@ -653,7 +652,7 @@ def snapshot(self, current_ip): self.log.info("Filename: %s", report_name) self.log.info("Collecting system info") filepath = "{}.txt".format(os.path.join(temp, report_name)) - with open(filepath, "w") as fileh: + with open(filepath, "w", encoding='utf8') as fileh: fileh.write(self.system_report(current_ip)) filestozip = [filepath] @@ -815,6 +814,9 @@ def webcamshot(self, filename): self.log.info("Skipping webcamshot.") return None + if sys.platform == "win32": # pragma: no cover + from VideoCapture import Device + temp = gettempdir() self.log.info("Taking webcamshot") if self.is_windows(): @@ -891,6 +893,7 @@ def work(self): ) return + runtime = 0 if self.is_windows(): # Cron job like for Windows :s while True: diff --git a/pombo_portable.py b/pombo_portable.py new file mode 100644 index 0000000..07ee3df --- /dev/null +++ b/pombo_portable.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# coding: utf-8 +""" +Pombo +Theft-recovery tracking open-source software +https://github.com/BoboTiG/pombo +http://sebsauvage.net/pombo + +This program is distributed under the OSI-certified zlib/libpnglicense . +http://www.opensource.org/licenses/zlib-license.php + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from +the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. +""" + +from pombo import * + + +class PomboPortable(Pombo): + """ Pombo Portable core. """ + + conf = "pombo.conf" if WINDOWS else "pombo.conf" + ip_file = "pombo" if WINDOWS else "pombo" + log_file = "pombo.log" if WINDOWS else "pombo.log" + + +def main_portable(args): + # type: (List[str]) -> int + """ Usage example. """ + + ret = 0 + + try: + if args and args[0] != "check": + parser = PomboArg() + ret = parser.parse(args[0]) + else: + pombo = PomboPortable(testing="check" in args) + pombo.work() + except KeyboardInterrupt: + printerr("*** STOPPING operations ***") + ret = 1 + except Exception as ex: + printerr(str(ex)) + raise + + return ret + + +if __name__ == "__main__": + # pylint: disable=no-value-for-parameter + sys.exit(main_portable(sys.argv[1:])) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..09bdbd1de71a1dab2996feeaba6bc173ee81dbb9 GIT binary patch literal 58 ycmezW&yyj5p^|}@fr}xRp%@53tRjX~hC+r?AX&^%0u>8j$YjU?!hD8uuo?hwI|{M@ literal 0 HcmV?d00001 From a85557026c62f23c783c5974b9e68123de432c15 Mon Sep 17 00:00:00 2001 From: Michal Mikolas Date: Wed, 12 Mar 2025 14:49:00 +0100 Subject: [PATCH 3/9] Fixed terminal windows popping up on Windows platform. --- .gitignore | 5 +++++ pombo.py | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 389dbfd..505a73b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ pombo-*.conf gnu-linux/*.tar.gz mac/*.tar.gz windows/Output/ +output +/dist +/build +/*.spec htmlcov/ .mypy_cache/ .tox/ @@ -14,6 +18,7 @@ htmlcov/ index.php pombo.conf .vscode +auto_py_to_exe*json # Pombo Portable (IP file & log file) pombo diff --git a/pombo.py b/pombo.py index 2244128..d5f3144 100644 --- a/pombo.py +++ b/pombo.py @@ -80,7 +80,7 @@ CURRENT_OS = {"Linux": "Linux", "Darwin": "Mac", "Windows": "Windows"}[ platform.system() ] -ENCODING = sys.stdin.encoding or getdefaultlocale()[1] or "utf-8" +ENCODING = (sys.stdin.encoding if sys.stdin else None) or getdefaultlocale()[1] or "utf-8" VC_VERSION = "0.9.5" URL = "https://github.com/BoboTiG/pombo" UPLINK = "https://raw.github.com/BoboTiG/pombo/master/VERSION" @@ -554,6 +554,7 @@ def runprocess(self, commandline, useshell=False): stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=useshell, + creationflags=subprocess.CREATE_NO_WINDOW if WINDOWS else 0, # hides new terminal window on Windows platform @see https://stackoverflow.com/questions/74048217/hide-popen-in-exe-mode ) sout, serr = myprocess.communicate() myprocess.wait() From 7e654e1da162bb5344ffd66e5957cb03e3f0a2d9 Mon Sep 17 00:00:00 2001 From: Michal Mikolas Date: Wed, 12 Mar 2025 16:30:35 +0100 Subject: [PATCH 4/9] Added 'always_report' mode. --- pombo.conf.example | 5 ++++- pombo.py | 15 +++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pombo.conf.example b/pombo.conf.example index 9e1e818..296a1ea 100644 --- a/pombo.conf.example +++ b/pombo.conf.example @@ -23,7 +23,7 @@ server_url= ; Must be the same as in pombo.php. check_file=.stolen -; Time between each report (in minutes). +; Time between each check (in minutes). ; When stolen, time between each report is this option divided by 3: ; if time_limit=15, when stolen it will be 15/3 = 5 min. ; On GNU/Linux, think to adapt /etc/cron/pombo. @@ -41,6 +41,9 @@ email_id= ; on Windows. only_on_ip_change=no +; If True, pombo will send report on every check. +always_report=no + ; Enable informations logging (not recommended, only for debug/dev purpose) enable_log=no diff --git a/pombo.py b/pombo.py index 2244128..6d7eef0 100644 --- a/pombo.py +++ b/pombo.py @@ -92,6 +92,7 @@ "time_limit": 15, "email_id": "", "only_on_ip_change": False, + "always_report": False, "enable_log": False, "use_proxy": False, "use_env": False, @@ -243,6 +244,7 @@ def config(self): config["only_on_ip_change"] = conf.getboolean( # type: ignore "General", "only_on_ip_change" ) + config["always_report"] = conf.getboolean("General", "always_report") # type: ignore config["enable_log"] = conf.getboolean("General", "enable_log") # type: ignore config["use_proxy"] = conf.getboolean("General", "use_proxy") # type: ignore config["use_env"] = conf.getboolean("General", "use_env") # type: ignore @@ -439,6 +441,9 @@ def need_report(self, current_ip): if is_stolen: return True, True + if self.configuration["always_report"]: + return True, False + if not self.configuration["only_on_ip_change"]: self.log.info("Skipping check based on IP change.") return False, False @@ -879,14 +884,16 @@ def work(self): self.snapshot(current_ip) wait_stolen = self.configuration["time_limit"] // 3 - if self.configuration["only_on_ip_change"]: - complement = "on ip change" + if self.configuration["always_report"]: + complement = "every {} minutes otherwise".format(self.configuration["time_limit"]) + elif self.configuration["only_on_ip_change"]: + complement = "on ip change otherwise" else: - complement = "every {} minutes".format(self.configuration["time_limit"]) + complement = "otherwise no report will be sent" self.log.info( ( "==> In real scenario, Pombo will send a report" - " every %d minutes if stolen, %s otherwise." + " every %d minutes if stolen, %s." ), wait_stolen, complement, From c7ddeabe479ed114c182f9e438fc2f5e7eb5502f Mon Sep 17 00:00:00 2001 From: Michal Mikolas Date: Wed, 12 Mar 2025 16:31:22 +0100 Subject: [PATCH 5/9] Updated .gitignore. --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 389dbfd..505a73b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ pombo-*.conf gnu-linux/*.tar.gz mac/*.tar.gz windows/Output/ +output +/dist +/build +/*.spec htmlcov/ .mypy_cache/ .tox/ @@ -14,6 +18,7 @@ htmlcov/ index.php pombo.conf .vscode +auto_py_to_exe*json # Pombo Portable (IP file & log file) pombo From 96a2ce45a2986ec47bb1146bf681e9fae16bdf0b Mon Sep 17 00:00:00 2001 From: Michal Mikolas Date: Wed, 12 Mar 2025 17:27:18 +0100 Subject: [PATCH 6/9] Added 'if stolen' option for screenshot and webcamshot in config. --- pombo.conf.example | 14 ++++++++++---- pombo.py | 36 ++++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/pombo.conf.example b/pombo.conf.example index 296a1ea..32432b1 100644 --- a/pombo.conf.example +++ b/pombo.conf.example @@ -128,19 +128,25 @@ traceroute=tracert -d www.example.org network_trafic=netstat -n ; Take screen shot? -; [M,L,W] yes or no -screenshot=yes +; [M,L,W] yes, stolen or (blank) to disable +; - yes: screenshot will always be included when sending the report +; - stolen: screenshot will be included in the report only when the computer is stolen +; - (blank): screenshot will never be taken +screenshot=stolen ; Take webcam shot? ; will be replaced by a filename, do not customize (required). -; [W] yes or (blank to disable) +; [W] yes, stolen or (blank) to disable +; [W] - yes: camshot will always be included when sending the report +; [W] - stolen: camshot will be included in the report only when the computer is stolen +; [W] - (blank): camshot will never be taken ; [L] /usr/bin/streamer -q -t 1 -r 2 -o ; [L] /usr/bin/streamer -q -t 1 -r 2 -j 100 -s 640x480 -o ; [L] /usr/bin/streamer -q -w 3 -o ; [L] /usr/bin/streamer -q -j 100 -w 3 -s 640x480 -o ; [L] /usr/bin/gst-launch -q v4l2src num_buffers=1 decimate=70 ! pngenc ! filesink location= ; [M] /usr/local/bin/imagesnap -q -w 3.00 -camshot=yes +camshot= ; A try to fix most of webcam shot errors is to specify the picture ; extension to feet with your software/hardware. diff --git a/pombo.py b/pombo.py index b85ebcf..6c86cb2 100644 --- a/pombo.py +++ b/pombo.py @@ -106,7 +106,7 @@ "wifi_access_points": "", "traceroute": "", "network_trafic": "", - "screenshot": True, + "screenshot": "yes", "camshot": "", "camshot_filetype": "", } @@ -258,7 +258,7 @@ def config(self): config["wifi_access_points"] = conf.get("Commands", "wifi_access_points") config["traceroute"] = conf.get("Commands", "traceroute") config["network_trafic"] = conf.get("Commands", "network_trafic") - config["screenshot"] = conf.getboolean("Commands", "screenshot") # type: ignore + config["screenshot"] = conf.get("Commands", "screenshot") config["camshot"] = conf.get("Commands", "camshot") config["camshot_filetype"] = conf.get("Commands", "camshot_filetype") @@ -589,17 +589,21 @@ def runprocess(self, commandline, useshell=False): return "" - def screenshot(self, filename): - # type: (str) -> List[str] + def screenshot(self, filename, is_stolen=False): + # type: (str, bool) -> List[str] """ Takes a screenshot and returns the path to the saved image (in TMP). None if could not take the screenshot. """ files = [] # type: List[str] - if not self.configuration["screenshot"]: + if not self.configuration["screenshot"] or self.configuration["screenshot"] == 'no': self.log.info("Skipping screenshot.") return files + if self.configuration["screenshot"] == 'stolen' and not is_stolen: + self.log.info("Skipping screenshot, computer not stolen.") + return files + self.log.info("Taking screenshot") if not self.user: self.log.error("Could not determine current user. Cannot take screenshot.") @@ -639,8 +643,8 @@ def snapshot_sendto_server(self, filename, data): self.log.info(txt, sizeof_fmt(len(filedata)), urlsplit(distant).netloc) self.request_url(distant, "post", parameters) - def snapshot(self, current_ip): - # type: (str) -> None + def snapshot(self, current_ip, is_stolen=False): + # type: (str, bool) -> None """ Make a global snapshot of the system (ip, screenshot, webcam...) and sends it to the internet. If not internet connexion is available, will exit. @@ -663,10 +667,10 @@ def snapshot(self, current_ip): filestozip = [filepath] # Take screenshot(s) - filestozip.extend(self.screenshot(report_name)) + filestozip.extend(self.screenshot(report_name, is_stolen)) # Take a webcam snapshot - webcam = self.webcamshot(report_name) + webcam = self.webcamshot(report_name, is_stolen) if webcam: filestozip.append(webcam) @@ -810,16 +814,20 @@ def system_report(self, current_ip): report = report.replace("\r\n", "\n") return report - def webcamshot(self, filename): - # type: (str) -> Union[str, None] + def webcamshot(self, filename, is_stolen=False): + # type: (str, bool) -> Union[str, None] """ Takes a snapshot with the webcam and returns the path to the saved image (in TMP). None if could not take the snapshot. """ - if not self.configuration["camshot"]: + if not self.configuration["camshot"] or self.configuration["camshot"] == 'no': self.log.info("Skipping webcamshot.") return None + if self.configuration["camshot"] == 'stolen' and not is_stolen: + self.log.info("Skipping webcamshot, computer not stolen.") + return None + if sys.platform == "win32": # pragma: no cover from VideoCapture import Device @@ -911,7 +919,7 @@ def work(self): report_needed, is_stolen = self.need_report(current_ip) if current_ip and report_needed: start = time.time() - self.snapshot(current_ip) + self.snapshot(current_ip, is_stolen) runtime = time.time() - start if is_stolen: time.sleep(wait_stolen - runtime) @@ -928,7 +936,7 @@ def work(self): for i in range(1, 4): self.log.info("* Attempt %d/3 *", i) start = time.time() - self.snapshot(current_ip) + self.snapshot(current_ip, is_stolen) runtime = time.time() - start if i < 3: time.sleep(wait - runtime) From 2610d930dd305b61f39b105052c6e25da2eae3ea Mon Sep 17 00:00:00 2001 From: Michal Mikolas Date: Tue, 1 Apr 2025 13:41:41 +0200 Subject: [PATCH 7/9] winservice: tmp --- build.pombo.bat | 3 + build.pombo_portable.bat | 3 + pombo.lnk | Bin 0 -> 999 bytes pombo.php | 304 +++++++++++++++++++++++++++++++++++++-- pombo.py | 118 +++++++++++++-- pombo_portable.lnk | Bin 0 -> 1088 bytes requirements.txt | Bin 58 -> 72 bytes 7 files changed, 409 insertions(+), 19 deletions(-) create mode 100644 build.pombo.bat create mode 100644 build.pombo_portable.bat create mode 100644 pombo.lnk create mode 100644 pombo_portable.lnk diff --git a/build.pombo.bat b/build.pombo.bat new file mode 100644 index 0000000..4833896 --- /dev/null +++ b/build.pombo.bat @@ -0,0 +1,3 @@ +cxfreeze --script pombo.py --target-dir dist --base-name Win32GUI +copy pombo.conf dist +copy pombo.lnk dist \ No newline at end of file diff --git a/build.pombo_portable.bat b/build.pombo_portable.bat new file mode 100644 index 0000000..871bafe --- /dev/null +++ b/build.pombo_portable.bat @@ -0,0 +1,3 @@ +cxfreeze --script pombo_portable.py --target-dir dist --base-name Win32GUI +copy pombo.conf dist +copy pombo_portable.lnk dist \ No newline at end of file diff --git a/pombo.lnk b/pombo.lnk new file mode 100644 index 0000000000000000000000000000000000000000..6d92156ad808e692275e80326d642265fabbb4da GIT binary patch literal 999 zcmah{Ur3Wt6#uO(6bTfU78H&id`O+&oNhWxq)m~~Hk#J>SpT$vnqL!_v1%ASC@eCP zd?@;77FbY(K@=#_LxB;9)Qcsh8NKw9FA+kpe&_qvTo80FzjM#I=iGD7J?Az7Fqzko z121%rhHJ=#h4OI5)ZUd_^QPigFN3mwIP|l|IWliTli_GGwiF*XnNjFl^v+toxjJ*k z<}y*NC^t5v%Y`RjU9%#-*Yb4Yg!BJ!(1K*#Y3*2|bRPD5t9>pyMPIUg2%(o8s3Y&7*3D%{IjoE&atTVQ*-%B;>1ReuGOwmi#})Yc+PXX2f!b7N zPfbi6IVG77>X{vSzE$?x5rq$ZXk+wF@GA1D8*kB6|3w8oVXZBU9m$BA`Ys+*pGGC( z(2|8bk4Puy$1e0B42?0vG?)=&&C+H~y*gkq;#ot{5Io74G4)KpKw{o|wvBv!dO*rW z8z$3YTG^Q>0c;|C#1=vZD0>+pj%fDPbX;fDyTot;CfNWX19a? z$hHw~xM*3OQ}D0OrQeZU>wwV&X@#=Xl}&x@GtMJUHd!_>LUYUJ4~>~(ExX$ z*vWFlYlJRIt29B-_%-rX6!u$AW>;?PLE4SY3! Tp50%6#~&{UJQ=K%^FQ<())dye literal 0 HcmV?d00001 diff --git a/pombo.php b/pombo.php index 6f62b1b..4e4cc61 100644 --- a/pombo.php +++ b/pombo.php @@ -35,24 +35,62 @@ function hash_hmac($hashfunc, $data, $key) { } } + class Pombo + { + private $password; + private $checkfile; + + public function __construct($password, $checkfile) + { + $this->password = $password; + $this->checkfile = $checkfile; + } + + public function isStolen() + { + return is_file($this->checkfile); + } + + public function markAsStolen() + { + if (!$this->isStolen()) { + $fh = fopen($this->checkfile, 'w'); + if ($fh === false) { + return false; + } + fclose($fh); + return true; + } + return true; + } + + public function getClientIP() + { + return !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; + } + } + $pombo = new Pombo($PASSWORD, $CHECKFILE); + /* Stolen! */ if ( !empty($_GET) ) { if ( isset($_GET['check']) && $_GET['check'] == $CHECKFILE ) { - if ( is_file($CHECKFILE) ) + if ( $pombo->isStolen() ) { die('Computer already stolen!'); - if ( ($fh = fopen($CHECKFILE, 'w')) === false ) + } + if ( !$pombo->markAsStolen() ) { die('Could not create file.'); - fclose($fh); + } die('File created, Pombo will see it and check every 5 minutes.'); } - if ( isset($_GET['myip']) ) - die( !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']); + if ( isset($_GET['myip']) ) { + die($pombo->getClientIP()); + } die('Nothing to do ...'); } /* Routine */ - else { - if ( empty($_POST) ) - die('Nothing to do ...'); + elseif ( !empty($_POST) ) { + // if ( empty($_POST) ) + // die('Nothing to do ...'); if ( isset($_POST['verify']) ) if ( $_POST['verify'] != hash_hmac('sha1', $_POST['filedata'].'***'.$_POST['filename'], $PASSWORD) ) die('Wrong password!'); @@ -69,6 +107,252 @@ function hash_hmac($hashfunc, $data, $key) { if ( fwrite($fh, base64_decode($_POST['filedata'])) === false ) die('Could not write file.'); fclose($fh); + echo 'File stored.'; } - echo 'File stored.'; -?> + + + +/** + * Archive Cleaner Script (v3) + * + * Cleans archived zip files in the script's directory based on retention rules. + * Outputs HTML-compatible line breaks. + * + * Filename format: {device}_{YYYYMMDD}_{HHMMSS}.zip + * Example: NOTASC271_20250331_093208.zip + * + * Retention Rules (per device): + * - Keep all files from the current day. + * - Keep the last file for each day in the past week (excluding today). + * - Keep the last file for each week in the past month (excluding the last week). + * - Keep the last file for each month in the past year (excluding the last month). + */ + +function cecho($msg) { + // echo($msg); +} + +// --- Configuration --- +// Set to false to actually delete files, true to only list what would be deleted. +define('DRY_RUN', false); +// Define the target directory (current directory where the script resides) +define('TARGET_DIR', __DIR__); +// Define the filename pattern using regex - UPDATED for YYYYMMDD_HHMMSS +// - Group 1: Device name ([a-zA-Z0-9_-]+) +// - Group 2: Date (YYYYMMDD) (\d{8}) << CORRECTED +// - Group 3: Time (HHMMSS) (\d{6}) +define('FILENAME_REGEX', '/^([a-zA-Z0-9_-]+)_(\d{8})_(\d{6})\.zip$/'); // << CORRECTED Regex +// Define the line break string +define('NL', "
\n"); + +// --- Initialization --- +// Use the timezone appropriate for the file timestamps / server location +// Set based on location context provided +date_default_timezone_set('Europe/Prague'); +$now = new DateTimeImmutable(); // Use immutable for safety +$allFilesData = []; +$filesToDelete = []; +$filesToKeep = []; // For verification/logging if needed + +cecho("Archive Cleaner Started: " . $now->format('Y-m-d H:i:s T') . NL); // Added Timezone abbreviation +cecho("Mode: " . (DRY_RUN ? "DRY RUN (No files will be deleted)" : "LIVE (Files will be deleted!)") . NL); +cecho("Target Directory: " . TARGET_DIR . NL); + +// --- Step 1: Find and Parse All Zip Files --- +$directoryIterator = new DirectoryIterator(TARGET_DIR); + +foreach ($directoryIterator as $fileInfo) { + if ($fileInfo->isFile() && $fileInfo->getExtension() === 'zip') { + $filename = $fileInfo->getFilename(); + if (preg_match(FILENAME_REGEX, $filename, $matches)) { + $deviceName = $matches[1]; + $dateStr = $matches[2]; // YYYYMMDD << CORRECTED interpretation + $timeStr = $matches[3]; // HHMMSS + $dateTimeStr = $dateStr . $timeStr; // Combined: YYYYMMDDHHMMSS + + try { + // UPDATED format string 'YmdHis' for YYYYMMDDHHMMSS << CORRECTED + $fileDateTime = DateTimeImmutable::createFromFormat('YmdHis', $dateTimeStr); + if ($fileDateTime === false) { + throw new Exception("Could not parse date/time using format 'YmdHis': $dateTimeStr"); + } + + // Store file info grouped by device name + if (!isset($allFilesData[$deviceName])) { + $allFilesData[$deviceName] = []; + } + $allFilesData[$deviceName][] = [ + 'path' => $fileInfo->getPathname(), + 'filename' => $filename, + 'datetime' => $fileDateTime, + 'timestamp' => $fileDateTime->getTimestamp(), + ]; + } catch (Exception $e) { + cecho("Skipping file (parse error): $filename - " . $e->getMessage() . NL); + } + } else { + cecho("Skipping file (format mismatch): $filename" . NL); + } + } +} + +// --- Step 2: Process Files Per Device --- +if (empty($allFilesData)) { + cecho("No matching zip files found." . NL); + exit; +} + +// Calculate cutoff dates (using DateTime objects for reliable comparisons) +$todayStart = $now->setTime(0, 0, 0); +// Start of the week for "past week" rule calculation +$startOfPastWeek = $now->modify('monday this week')->setTime(0, 0, 0); +// If today *is* Monday, "past week" should start from *last* Monday +if ($now->format('N') == 1) { // 'N' gives ISO-8601 day number (1=Monday, 7=Sunday) + $startOfPastWeek = $startOfPastWeek->modify('-7 days'); +} +// Use $startOfPastWeek directly for the lower bound of daily retention +$oneWeekAgo = $startOfPastWeek; // Keep daily files from this time up to todayStart +$oneMonthAgo = $now->modify('-1 month')->setTime(0,0,0); +$oneYearAgo = $now->modify('-1 year')->setTime(0,0,0); + + +cecho("Cutoff - Today Start: " . $todayStart->format('Y-m-d H:i:s') . NL); +// Clarify rule boundaries: +cecho("Cutoff - Keep Daily From: " . $oneWeekAgo->format('Y-m-d H:i:s') . " (Up to Today)" . NL); +cecho("Cutoff - Keep Weekly From: " . $oneMonthAgo->format('Y-m-d H:i:s') . " (Up to Start of Past Week: " . $oneWeekAgo->format('Y-m-d H:i:s') . ")" . NL); +cecho("Cutoff - Keep Monthly From: " . $oneYearAgo->format('Y-m-d H:i:s') . " (Up to 1 Month Ago: " . $oneMonthAgo->format('Y-m-d H:i:s') . ")" . NL); + + +foreach ($allFilesData as $deviceName => $files) { + cecho(NL . "--- Processing Device: $deviceName ---" . NL); + + // Sort files by datetime descending (newest first) + usort($files, function ($a, $b) { + return $b['timestamp'] <=> $a['timestamp']; // Newest first + }); + + $keptMarkers = [ + 'daily' => [], // Key: YYYY-MM-DD + 'weekly' => [], // Key: YYYY-WW (ISO-8601 week number, 'o-W' format) + 'monthly' => [], // Key: YYYY-MM + ]; + + $deviceFilesToKeep = []; + $deviceFilesToDelete = []; + + foreach ($files as $file) { + $fileDateTime = $file['datetime']; + $filePath = $file['path']; + $fileIdentifier = $file['filename']; // Use filename for easier debugging + + // Assume deletion unless a rule keeps it + $keepFile = false; + + // Rule 1: Keep all files from today + if ($fileDateTime >= $todayStart) { + // cecho("Keep (Today): $fileIdentifier" . NL); // Uncomment for verbose debugging + $keepFile = true; + } + // Rule 2: Keep last file for each day in the past week (older than today, newer than or equal to start of past week) + elseif ($fileDateTime < $todayStart && $fileDateTime >= $oneWeekAgo) { + $dayKey = $fileDateTime->format('Y-m-d'); + if (!isset($keptMarkers['daily'][$dayKey])) { + // cecho("Keep (Last 7 Days - Day: $dayKey): $fileIdentifier" . NL); // Uncomment for verbose debugging + $keptMarkers['daily'][$dayKey] = true; + $keepFile = true; + } + } + // Rule 3: Keep last file for each week in the past month (older than start of past week, newer than or equal to 1 month ago) + elseif ($fileDateTime < $oneWeekAgo && $fileDateTime >= $oneMonthAgo) { + $weekKey = $fileDateTime->format('o-W'); // ISO-8601 year and week number + if (!isset($keptMarkers['weekly'][$weekKey])) { + // cecho("Keep (Last Month - Week: $weekKey): $fileIdentifier" . NL); // Uncomment for verbose debugging + $keptMarkers['weekly'][$weekKey] = true; + $keepFile = true; + } + } + // Rule 4: Keep last file for each month in the past year (older than 1 month, newer than or equal to 1 year ago) + elseif ($fileDateTime < $oneMonthAgo && $fileDateTime >= $oneYearAgo) { + $monthKey = $fileDateTime->format('Y-m'); + if (!isset($keptMarkers['monthly'][$monthKey])) { + // cecho("Keep (Last Year - Month: $monthKey): $fileIdentifier" . NL); // Uncomment for verbose debugging + $keptMarkers['monthly'][$monthKey] = true; + $keepFile = true; + } + } + + // Add to appropriate list + if ($keepFile) { + $deviceFilesToKeep[$fileIdentifier] = $filePath; + } else { + // Only add for deletion if it's older than 1 year OR not kept by any rule above + if ($fileDateTime < $oneYearAgo || !isset($deviceFilesToKeep[$fileIdentifier])) { + // cecho("Mark for Deletion: $fileIdentifier" . NL); // Uncomment for verbose debugging + $deviceFilesToDelete[$fileIdentifier] = $filePath; + } + } + } // End foreach file in device + + // Add to global lists + $filesToKeep = array_merge($filesToKeep, $deviceFilesToKeep); + $filesToDelete = array_merge($filesToDelete, $deviceFilesToDelete); + + cecho("Summary for $deviceName: " . count($files) . " total files found." . NL); + cecho(" - Files to keep: " . count($deviceFilesToKeep) . NL); + if (!empty($deviceFilesToKeep)) { + cecho("
    " . NL); + foreach(array_keys($deviceFilesToKeep) as $fname) { cecho("
  • $fname
  • " . NL); } + cecho("
" . NL); + } + cecho(" - Files to delete: " . count($deviceFilesToDelete) . NL); + if (!empty($deviceFilesToDelete)) { + cecho("
    " . NL); + foreach(array_keys($deviceFilesToDelete) as $fname) { cecho("
  • $fname
  • " . NL); } + cecho("
" . NL); + } + + +} // End foreach device + +// --- Step 3: Perform Deletion (if not DRY_RUN) --- +cecho(NL . "--- Deletion Phase ---" . NL); +if (DRY_RUN) { + cecho("DRY RUN enabled. No files will be deleted." . NL); + if (empty($filesToDelete)) { + cecho("No files marked for deletion." . NL); + } else { + cecho("Files that would be deleted: " . count($filesToDelete) . NL); + // Optional: List files again if needed for clarity in dry run + // cecho("
    " . NL); + // foreach(array_keys($filesToDelete) as $fname) { cecho("
  • $fname
  • " . NL); } + // cecho("
" . NL); + } +} else { + if (empty($filesToDelete)) { + cecho("No files marked for deletion." . NL); + } else { + cecho("Deleting " . count($filesToDelete) . " files..." . NL); + $deletedCount = 0; + $errorCount = 0; + foreach ($filesToDelete as $filename => $path) { + cecho("Deleting: $filename ... "); + if (file_exists($path)) { + // Use @unlink to suppress potential warnings if deletion fails, handle manually + if (@unlink($path)) { + cecho("Success." . NL); + $deletedCount++; + } else { + $error = error_get_last(); + cecho("FAILED! (Reason: " . ($error['message'] ?? 'Unknown permission or lock issue') . ")" . NL); + $errorCount++; + } + } else { + cecho("Skipped (File not found at path: $path)." . NL); + $errorCount++; // Count as error if it was expected to be there based on initial scan + } + } + cecho("Deletion complete. $deletedCount files deleted, $errorCount errors." . NL); + } +} + +cecho(NL . "Archive Cleaner Finished." . NL); \ No newline at end of file diff --git a/pombo.py b/pombo.py index 6c86cb2..16de250 100644 --- a/pombo.py +++ b/pombo.py @@ -42,6 +42,7 @@ from socket import gaierror import sys import time +import urllib3 import zipfile from base64 import b64encode from datetime import datetime @@ -148,7 +149,10 @@ def hash_string(current_ip): def printerr(string=""): # type: (str) -> None """ Print an error message to STDERR. """ - sys.stderr.write(string + "\n") + try: + sys.stderr.write(string + "\n") + except: + pass def to_bool(value=""): @@ -165,9 +169,9 @@ class Pombo(object): # pylint: disable=too-many-public-methods,too-many-instance-attributes - conf = "c:\\pombo\\pombo.conf" if WINDOWS else "/etc/pombo.conf" - ip_file = "c:\\pombo\\pombo" if WINDOWS else "/var/local/pombo" - log_file = "c:\\pombo\\pombo.log" if WINDOWS else "/var/log/pombo.log" + conf = "C:\\Users\\Public\\pombo\\pombo.conf" if WINDOWS else "/etc/pombo.conf" + ip_file = "C:\\Users\\Public\\pombo\\pombo" if WINDOWS else "/var/local/pombo" + log_file = "C:\\Users\\Public\\pombo\\pombo.log" if WINDOWS else "/var/log/pombo.log" def __init__(self, testing=False): # type: (bool) -> None @@ -369,8 +373,8 @@ def get_serial(self): continue if self.is_windows(): - parts = res.split("=") - if not parts[0].startswith("ERR") and parts[1] != "0": + parts = serial = res.split("=") + if len(parts) >= 2 and not parts[0].startswith("ERR") and parts[1] != "0": serial = parts[1] break elif res != "System Serial Number": @@ -498,7 +502,7 @@ def request_url(self, url, method="get", params=None): ret = "" parts = urlsplit(url) - ssl_cert_verif = parts.scheme == "https" + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # ssl_cert_verif = parts.scheme == "https" auth = None # type: Optional[Tuple[str, str]] if self.configuration["auth_server"] == parts.netloc: @@ -510,7 +514,7 @@ def request_url(self, url, method="get", params=None): url, params=params, proxies=proxies, - verify=ssl_cert_verif, + verify=False, # ssl_cert_verif, auth=auth, timeout=30, ) @@ -519,7 +523,7 @@ def request_url(self, url, method="get", params=None): url, data=params, proxies=proxies, - verify=ssl_cert_verif, + verify=False, # ssl_cert_verif, auth=auth, timeout=30, ) @@ -1066,6 +1070,102 @@ def version(): return 0 + +# ~~~~~~ +# ~~~~~~ +# ~~~~~~ +# --- Windows Service Specific Code --- +_SERVICE_NAME = "PomboService" +_SERVICE_DISPLAY_NAME = "Pombo Application Service" +_SERVICE_DESCRIPTION = "Runs the main Pombo application logic." + +try: + import win32serviceutil, win32service, win32event, servicemanager, threading + _PYWIN32_AVAILABLE = True +except ImportError: + _PYWIN32_AVAILABLE = False + # print("Warning: pywin32 not found, service functionality disabled.") + + +if _PYWIN32_AVAILABLE: + class PomboSvc(win32serviceutil.ServiceFramework): + _svc_name_ = _SERVICE_NAME + _svc_display_name_ = _SERVICE_DISPLAY_NAME + _svc_description_ = _SERVICE_DESCRIPTION + + def __init__(self, args): + win32serviceutil.ServiceFramework.__init__(self, args) + # Event signaled by SCM to stop the service + self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) + # Event used to signal the worker thread to stop + self._worker_stop_event = threading.Event() + self.pombo_instance = None + self.worker_thread = None + + def SvcStop(self): + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + servicemanager.LogInfoMsg(f"{self._svc_name_} - Stop requested.") + # Signal the worker thread to stop + self._worker_stop_event.set() + # Signal the main service thread SvcDoRun can exit its wait + win32event.SetEvent(self.hWaitStop) + + def _run_pombo_work_thread(self): + """Wrapper to run pombo.work in a thread.""" + try: + servicemanager.LogInfoMsg(f"{self._svc_name_} - Worker thread starting Pombo instance.") + # Create Pombo instance within the thread context if needed + # Or reuse self.pombo_instance if created in SvcDoRun + self.pombo_instance = Pombo(testing=False) # Or read config + self.pombo_instance.set_stop_event(self._worker_stop_event) # Pass the stop event + self.pombo_instance.work() + servicemanager.LogInfoMsg(f"{self._svc_name_} - Worker thread finished Pombo work cleanly.") + except Exception as e: + servicemanager.LogErrorMsg(f"{self._svc_name_} - Error in worker thread: {e}") + # Optionally report service stopped here if error is fatal + # self.ReportServiceStatus(win32service.SERVICE_STOPPED) # Careful with thread safety + + def SvcDoRun(self): + servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, + servicemanager.PYS_SERVICE_STARTED, + (self._svc_name_, '')) + self.ReportServiceStatus(win32service.SERVICE_START_PENDING) + servicemanager.LogInfoMsg(f"{self._svc_name_} - Starting.") + + try: + # Start the Pombo logic in a separate thread + self.worker_thread = threading.Thread(target=self._run_pombo_work_thread) + self.worker_thread.start() + servicemanager.LogInfoMsg(f"{self._svc_name_} - Worker thread started.") + + # Initialization assumed complete, report running + self.ReportServiceStatus(win32service.SERVICE_RUNNING) + servicemanager.LogInfoMsg(f"{self._svc_name_} - Running.") + + # Wait until SvcStop signals us + win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE) + + # SvcStop was called, wait for worker thread to finish + servicemanager.LogInfoMsg(f"{self._svc_name_} - Waiting for worker thread to stop...") + self.worker_thread.join(timeout=30) # Wait up to 30s for cleanup + if self.worker_thread.is_alive(): + servicemanager.LogWarningMsg(f"{self._svc_name_} - Worker thread did not stop gracefully.") + else: + servicemanager.LogInfoMsg(f"{self._svc_name_} - Worker thread joined.") + + self.ReportServiceStatus(win32service.SERVICE_STOPPED) + servicemanager.LogInfoMsg(f"{self._svc_name_} - Stopped.") + + except Exception as e: + servicemanager.LogErrorMsg(f"{self._svc_name_} - Error in SvcDoRun: {e}") + # Try to signal worker thread to stop on error + self._worker_stop_event.set() + self.ReportServiceStatus(win32service.SERVICE_STOPPED) +# ~~~~~~/ +# ~~~~~~/ +# ~~~~~~/ + + def main(args): # type: (List[str]) -> int """ Usage example. """ diff --git a/pombo_portable.lnk b/pombo_portable.lnk new file mode 100644 index 0000000000000000000000000000000000000000..5f9fb930d122d76b654c4f1d23eb876b3551156e GIT binary patch literal 1088 zcma)5Ur1A76#uO(oRK(ON>I5Xd`O*})2Um9I42U?21{FGwVd5h@wT~X>>-RE6c!0a zJrr7)1rZcf(327M76c;MLw_hu@TG_HCF&tqzjLomeaiE3&UeoF&i6a#-1FU500#3W zO5mj?(L9b^Sg22!zANtQpA30!_qgmIBfs3W+|jBB8uTqImkef<_!oS$mT#_}f~ohp zD6`pa?L?)=70w2&Dk<1Cqcn7~&4l)(l1SnVjL5-{7ngz$Txw{4uayyH$BHQnvKL*N zcGx(Cb|j%7K?yWacGK$Pa-a%UVu@ak3R-s5ksXXP<3cuXe!B_&!DwGk7jQP4xyh}D zw)RWrQ(9)HmQTb%O*8&}1wRH6C3+8dbwjxYrHJ?Rp4HI1v<>l?8jTTE?^zAarNu9q zM;9%Wh2*u=zh|Ezju=$tj`6_E-m4(vP(wF|E%5u=8hw!=yVV=?0v+pc=QP~=8gAZN zQ0k@Ir?H9~(ewlc(5dyP@LMWKk%3+06DzxnU+hY

?JYZzgBnER@T{d;xIy+HAx6 zmkw)9>oO9LB?da9eTr2{5#7v0;SCkC10J{-yZQja>qfE`MatL$OlC-XS+kV9jrP>} z`S}B{yV}NjJDMJPlf!a$nrAG3Qd$u@7aQnIc2zm+brTaGGMSO17mwU|`epW~>S@d$ z`~297Wz+5Ma!2s0d-;FoGsH0xKh519@COzz2b-U_)_JD?6kaR6q0XrE=SZ#+vb=); zU$329v+|0?a&S3O%@?h6g=X7m*V7{>HfnmLlQ*5H Date: Tue, 1 Apr 2025 13:44:14 +0200 Subject: [PATCH 8/9] tmp --- pombo.py | 96 -------------------------------------------------------- 1 file changed, 96 deletions(-) diff --git a/pombo.py b/pombo.py index 16de250..f17100f 100644 --- a/pombo.py +++ b/pombo.py @@ -1070,102 +1070,6 @@ def version(): return 0 - -# ~~~~~~ -# ~~~~~~ -# ~~~~~~ -# --- Windows Service Specific Code --- -_SERVICE_NAME = "PomboService" -_SERVICE_DISPLAY_NAME = "Pombo Application Service" -_SERVICE_DESCRIPTION = "Runs the main Pombo application logic." - -try: - import win32serviceutil, win32service, win32event, servicemanager, threading - _PYWIN32_AVAILABLE = True -except ImportError: - _PYWIN32_AVAILABLE = False - # print("Warning: pywin32 not found, service functionality disabled.") - - -if _PYWIN32_AVAILABLE: - class PomboSvc(win32serviceutil.ServiceFramework): - _svc_name_ = _SERVICE_NAME - _svc_display_name_ = _SERVICE_DISPLAY_NAME - _svc_description_ = _SERVICE_DESCRIPTION - - def __init__(self, args): - win32serviceutil.ServiceFramework.__init__(self, args) - # Event signaled by SCM to stop the service - self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) - # Event used to signal the worker thread to stop - self._worker_stop_event = threading.Event() - self.pombo_instance = None - self.worker_thread = None - - def SvcStop(self): - self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) - servicemanager.LogInfoMsg(f"{self._svc_name_} - Stop requested.") - # Signal the worker thread to stop - self._worker_stop_event.set() - # Signal the main service thread SvcDoRun can exit its wait - win32event.SetEvent(self.hWaitStop) - - def _run_pombo_work_thread(self): - """Wrapper to run pombo.work in a thread.""" - try: - servicemanager.LogInfoMsg(f"{self._svc_name_} - Worker thread starting Pombo instance.") - # Create Pombo instance within the thread context if needed - # Or reuse self.pombo_instance if created in SvcDoRun - self.pombo_instance = Pombo(testing=False) # Or read config - self.pombo_instance.set_stop_event(self._worker_stop_event) # Pass the stop event - self.pombo_instance.work() - servicemanager.LogInfoMsg(f"{self._svc_name_} - Worker thread finished Pombo work cleanly.") - except Exception as e: - servicemanager.LogErrorMsg(f"{self._svc_name_} - Error in worker thread: {e}") - # Optionally report service stopped here if error is fatal - # self.ReportServiceStatus(win32service.SERVICE_STOPPED) # Careful with thread safety - - def SvcDoRun(self): - servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, - servicemanager.PYS_SERVICE_STARTED, - (self._svc_name_, '')) - self.ReportServiceStatus(win32service.SERVICE_START_PENDING) - servicemanager.LogInfoMsg(f"{self._svc_name_} - Starting.") - - try: - # Start the Pombo logic in a separate thread - self.worker_thread = threading.Thread(target=self._run_pombo_work_thread) - self.worker_thread.start() - servicemanager.LogInfoMsg(f"{self._svc_name_} - Worker thread started.") - - # Initialization assumed complete, report running - self.ReportServiceStatus(win32service.SERVICE_RUNNING) - servicemanager.LogInfoMsg(f"{self._svc_name_} - Running.") - - # Wait until SvcStop signals us - win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE) - - # SvcStop was called, wait for worker thread to finish - servicemanager.LogInfoMsg(f"{self._svc_name_} - Waiting for worker thread to stop...") - self.worker_thread.join(timeout=30) # Wait up to 30s for cleanup - if self.worker_thread.is_alive(): - servicemanager.LogWarningMsg(f"{self._svc_name_} - Worker thread did not stop gracefully.") - else: - servicemanager.LogInfoMsg(f"{self._svc_name_} - Worker thread joined.") - - self.ReportServiceStatus(win32service.SERVICE_STOPPED) - servicemanager.LogInfoMsg(f"{self._svc_name_} - Stopped.") - - except Exception as e: - servicemanager.LogErrorMsg(f"{self._svc_name_} - Error in SvcDoRun: {e}") - # Try to signal worker thread to stop on error - self._worker_stop_event.set() - self.ReportServiceStatus(win32service.SERVICE_STOPPED) -# ~~~~~~/ -# ~~~~~~/ -# ~~~~~~/ - - def main(args): # type: (List[str]) -> int """ Usage example. """ From 4268ffdfad8919b42641aadc692127d5ab2c5739 Mon Sep 17 00:00:00 2001 From: Michal Mikolas Date: Tue, 1 Apr 2025 16:27:39 +0200 Subject: [PATCH 9/9] Added Windows Task Scheduler spec file. --- build.pombo.bat | 3 ++- build.pombo_portable.bat | 3 --- pombo_task.xml | Bin 0 -> 6446 bytes 3 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 build.pombo_portable.bat create mode 100644 pombo_task.xml diff --git a/build.pombo.bat b/build.pombo.bat index 4833896..099e7d9 100644 --- a/build.pombo.bat +++ b/build.pombo.bat @@ -1,3 +1,4 @@ cxfreeze --script pombo.py --target-dir dist --base-name Win32GUI copy pombo.conf dist -copy pombo.lnk dist \ No newline at end of file +copy pombo.lnk dist +copy pombo_task.xml dist \ No newline at end of file diff --git a/build.pombo_portable.bat b/build.pombo_portable.bat deleted file mode 100644 index 871bafe..0000000 --- a/build.pombo_portable.bat +++ /dev/null @@ -1,3 +0,0 @@ -cxfreeze --script pombo_portable.py --target-dir dist --base-name Win32GUI -copy pombo.conf dist -copy pombo_portable.lnk dist \ No newline at end of file diff --git a/pombo_task.xml b/pombo_task.xml new file mode 100644 index 0000000000000000000000000000000000000000..be139d62e3c05c43340f957e0a0cc23bec00bc59 GIT binary patch literal 6446 zcmbuDYfl?T6o%(>rT&L4pIWJ0(j-k4xkOMxKq4usjZme2F)q+*dbtBUiXD?%YjW<%aIYmF`!!=3clBx20#HnbLjM z_&~FzyY#asuIKjMx~P5@%{#a4zIETYjz;_9a=nn_E6>q(Ek(&rS8l4$t$XV>^{KSi zmG&HaP8)g-H8RoumA)6E-0*0|qMdm}1C7r#yXk%s;w|?|Z(Dlb^lPVjPUooB<~R%C z>$pF(%cVF}nxBYbs&}M6)b~PfCKI$qzvJ#ibETPy=8p9oyEFZE#b>IyTk+g2rdeab_!b@8v>| z{6hm1{elvjQr0Z=uM)qB-bRw+Qr}YAMT$j8>++lk`Jr_2m$(LPusV#R)OGv zvu7#Hf9r{yL*c-`(EWwJpxG2Rq`1;Nb^u?{ORyv>OV2g|SZqX2<`28Vb|YkUx;m9n1|38@SHg;9Q(?WA9DQ$9vqjx@co@4hrHNkk zUYh936Ono#Yv9dj7kQg=sr?KamL6&Sz4z&!do6zs9uUS;JML%?Y&KB57VcClhaTN) z{llBc6nGcjoJtktI1`oG6%UBMY1t(wCwNxTcE=rQck|=tSY_rt2)`miuuC&SkP7*7 zC{3SDjjw7AWPBVwRCUQJ*)3SLqr8F7lCQ}yeOWf0H-v2(i$ij8n!VC&8NKbqTX~|m z$Je^P2CU0#s0F`xDi2Rv_Wp8K6Txn`u60{AN8-V zar}&2vMe$z-j1{@^=%})2R@?76ZqYZ#_?bL+~NoQwpXPxVBbnRPdpDYQ^-I>on>+M zVo$Odk%E3N^jqp5|80!C7;P6doO;2%tavV}W4}u!jgd75Ue7tY6zPuE-irFh{|@J9 zqMn!QG{}X=w~?Z!wjV`l^7E-Lc4SZLCYhhEBAqio{V08o{5*B;+}CBx7Wg+7q)J)^ zBztv5i>&X})mT+G&R}^euT+nR-gZGB*wQL>S{=-yeaV48mF`Dr4Xoga)eE%d4fqKh zO%lsD%-AS(fid%#nV#evvSrQ^dma&SG7{a)>%po$$bZ+$U2l7Q5zJ_ATQim&tWG?S zN2;Zrhy4rfLL^Y#ehx$`W+Lzbm`n0a)034>*k=YwdC?`o1gZEY&io_4|l zR(-Kd-I}RK5Zz>?v1Fl343(S?b*dT5ICug)pDKUU?SV8${P+A0^v2YF_N&+Z)mEg^ z#ua#XPh72!&-GO%h2?7^@xn){buuP@{{ z&!I#I#3UVrDM>#`SYJvt^W_TZ$EzQ$Zz+=4mPSCf{Y0DPL+%K)778j;z?u++d(H=M-%6 zrG4aTD^feg71*tPnhJS|8e`eJnD2cj1mGi2=j`{Cem6xFGmJ$8^&xak~3x;Hz0 zlr4+b=a%h+Kc>-34vjjAxN@2AL{`mXYe!nJf7^$eQ5`8$-1YiH1afK~%05^r*sxu7 zQd^=gqAQPian;BF@b$4`oXlpuB%W-U??&@Yx&AM=di>Za;zaul729+z1LcVHjBi~4 z{>Lp2`KDe8p2wk%g7fXQIQOInvv5=D9K~WQ&gT3cw_k`t>}(N2ezKaJiw(M7GA`YR z-DCCi9Bb^zKgq$IBYL_UvlISmD^gj)YAPHzBhfZw6*?v4vR%1T;>~UczJJInSc(%6 zHf25DMRxgNfsfiLgHw-Hk!|g{RDo60~=YEd!9nt z;Uv4|JNP&HZ@M?cec$KMn}@S1GM8F`EY<;VHnpmEzEGxi71Fbq-{*Sa<28I1iF_xE-zh4b#gk8f30ZMaEjBMW`27VJfiM3|r#K$YdJ& zDjbj9i*)>nli17I4PImKeOAZU#Qm*qOv5cnrT1g$hWiKZa?`ui^nF8mA;Q-lw4QQJ zt-UX9RBU`b+@o4e2sOhvS*{2*cd^Y+CBZ~l5U;sZS7H?ijWN#2dv%Gh#N+%@Y0Pdh hU)l{wKvO7}H1XslVc(GNqLmn}+ur6F4}E}*{0IFc6}tcc literal 0 HcmV?d00001