large update
							
								
								
									
										11
									
								
								Changelog
									
									
									
									
									
								
							
							
						
						@@ -7,23 +7,26 @@ My standard System: Linux Mint 22 Cinnamon
 | 
				
			|||||||
 - If Wire-Py already runs, prevent further start
 | 
					 - If Wire-Py already runs, prevent further start
 | 
				
			||||||
 - for loops with lists replaced by List Comprehensions
 | 
					 - for loops with lists replaced by List Comprehensions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
   ### Added
 | 
					   ### Added
 | 
				
			||||||
13-04-0725
 | 
					03-06-2025
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 -  
 | 
				
			||||||
 | 
					   ### Added
 | 
				
			||||||
 | 
					13-04-20255
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 - Installer update for Open Suse Tumbleweed and Leap
 | 
					 - Installer update for Open Suse Tumbleweed and Leap
 | 
				
			||||||
 - add symbolic link wirepy.py 
 | 
					 - add symbolic link wirepy.py 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   ### Added
 | 
					   ### Added
 | 
				
			||||||
09-04-0725
 | 
					09-04-2025
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 - Installer now with query and remove
 | 
					 - Installer now with query and remove
 | 
				
			||||||
 - Icons merged
 | 
					 - Icons merged
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   ### Added
 | 
					   ### Added
 | 
				
			||||||
07-04-0725
 | 
					07-04-2025
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 - Installers will support other systems again
 | 
					 - Installers will support other systems again
 | 
				
			||||||
 - Installer is now finished clean with wrong password
 | 
					 - Installer is now finished clean with wrong password
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										883
									
								
								common_tools.py
									
									
									
									
									
								
							
							
						
						@@ -1,883 +0,0 @@
 | 
				
			|||||||
""" Classes Method and Functions for lx Apps """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import getpass
 | 
					 | 
				
			||||||
import shutil
 | 
					 | 
				
			||||||
import signal
 | 
					 | 
				
			||||||
import base64
 | 
					 | 
				
			||||||
import secrets
 | 
					 | 
				
			||||||
import subprocess
 | 
					 | 
				
			||||||
from subprocess import CompletedProcess, run
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import tkinter as tk
 | 
					 | 
				
			||||||
from typing import Optional, Dict, Any, NoReturn
 | 
					 | 
				
			||||||
import zipfile
 | 
					 | 
				
			||||||
from datetime import datetime
 | 
					 | 
				
			||||||
from pathlib import Path
 | 
					 | 
				
			||||||
from tkinter import ttk, Toplevel
 | 
					 | 
				
			||||||
from wp_app_config import AppConfig, Msg, logging
 | 
					 | 
				
			||||||
import requests
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Translate
 | 
					 | 
				
			||||||
_ = AppConfig.setup_translations()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CryptoUtil:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    This class is for the creation of the folders and files
 | 
					 | 
				
			||||||
    required by Wire-Py, as well as for decryption
 | 
					 | 
				
			||||||
    the tunnel from the user's home directory
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def decrypt(user) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Starts SSL dencrypt
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if len([file.stem for file in AppConfig.CONFIG_DIR.glob("*.dat")]) == 0:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            process: CompletedProcess[str] = run(
 | 
					 | 
				
			||||||
                ["pkexec", "/usr/local/bin/ssl_decrypt.py", "--user", user],
 | 
					 | 
				
			||||||
                capture_output=True,
 | 
					 | 
				
			||||||
                text=True,
 | 
					 | 
				
			||||||
                check=False,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Output from Openssl Error
 | 
					 | 
				
			||||||
            if process.stderr:
 | 
					 | 
				
			||||||
                logging.error(process.stderr, exc_info=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if process.returncode == 0:
 | 
					 | 
				
			||||||
                logging.info("Files successfully decrypted...", exc_info=True)
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                logging.error(
 | 
					 | 
				
			||||||
                    f"Error process decrypt: Code {process.returncode}", exc_info=True
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def encrypt(user) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Starts SSL encryption
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        process: CompletedProcess[str] = run(
 | 
					 | 
				
			||||||
            ["pkexec", "/usr/local/bin/ssl_encrypt.py", "--user", user],
 | 
					 | 
				
			||||||
            capture_output=True,
 | 
					 | 
				
			||||||
            text=True,
 | 
					 | 
				
			||||||
            check=False,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Output from Openssl Error
 | 
					 | 
				
			||||||
        if process.stderr:
 | 
					 | 
				
			||||||
            logging.error(process.stderr, exc_info=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if process.returncode == 0:
 | 
					 | 
				
			||||||
            logging.info("Files successfully encrypted...", exc_info=True)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            logging.error(
 | 
					 | 
				
			||||||
                f"Error process encrypt: Code {process.returncode}", exc_info=True
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def find_key(key: str = "") -> bool:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Checks if the private key already exists in the system using an external script.
 | 
					 | 
				
			||||||
        Returns True only if the full key is found exactly (no partial match).
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        process: CompletedProcess[str] = run(
 | 
					 | 
				
			||||||
            ["pkexec", "/usr/local/bin/match_found.py", key],
 | 
					 | 
				
			||||||
            capture_output=True,
 | 
					 | 
				
			||||||
            text=True,
 | 
					 | 
				
			||||||
            check=False,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        if "True" in process.stdout:
 | 
					 | 
				
			||||||
            return True
 | 
					 | 
				
			||||||
        elif "False" in process.stdout:
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
        logging.error(
 | 
					 | 
				
			||||||
            f"Unexpected output from the external script:\nSTDOUT: {process.stdout}\nSTDERR: {process.stderr}",
 | 
					 | 
				
			||||||
            exc_info=True,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def is_valid_base64(key: str) -> bool:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Validates if the input is a valid Base64 string (WireGuard private key format).
 | 
					 | 
				
			||||||
        Returns True only for non-empty strings that match the expected length.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        # Check for empty string
 | 
					 | 
				
			||||||
        if not key or key.strip() == "":
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Regex pattern to validate Base64: [A-Za-z0-9+/]+={0,2}
 | 
					 | 
				
			||||||
        base64_pattern = r"^[A-Za-z0-9+/]+={0,2}$"
 | 
					 | 
				
			||||||
        if not re.match(base64_pattern, key):
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            # Decode and check length (WireGuard private keys are 32 bytes long)
 | 
					 | 
				
			||||||
            decoded = base64.b64decode(key)
 | 
					 | 
				
			||||||
            if len(decoded) != 32:  # 32 bytes = 256 bits
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            logging.error(f"Error on decode Base64: {e}", exc_info=True)
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class LxTools:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Class LinuxTools methods that can also be used for other apps
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, *args: Any, **kwargs: Any) -> None:
 | 
					 | 
				
			||||||
        super().__init__(*args, **kwargs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def center_window_cross_platform(window, width, height):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Centers a window on the primary monitor in a way that works on both X11 and Wayland
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Args:
 | 
					 | 
				
			||||||
            window: The tkinter window to center
 | 
					 | 
				
			||||||
            width: Window width
 | 
					 | 
				
			||||||
            height: Window height
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        # Calculate the position before showing the window
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # First attempt: Try to use GDK if available (works on both X11 and Wayland)
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            import gi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            gi.require_version("Gdk", "3.0")
 | 
					 | 
				
			||||||
            from gi.repository import Gdk
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            display = Gdk.Display.get_default()
 | 
					 | 
				
			||||||
            monitor = display.get_primary_monitor() or display.get_monitor(0)
 | 
					 | 
				
			||||||
            geometry = monitor.get_geometry()
 | 
					 | 
				
			||||||
            scale_factor = monitor.get_scale_factor()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Calculate center position on the primary monitor
 | 
					 | 
				
			||||||
            x = geometry.x + (geometry.width - width // scale_factor) // 2
 | 
					 | 
				
			||||||
            y = geometry.y + (geometry.height - height // scale_factor) // 2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Set window geometry
 | 
					 | 
				
			||||||
            window.geometry(f"{width}x{height}+{x}+{y}")
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        except (ImportError, AttributeError):
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Second attempt: Try xrandr for X11
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            import subprocess
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            output = subprocess.check_output(
 | 
					 | 
				
			||||||
                ["xrandr", "--query"], universal_newlines=True
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Parse the output to find the primary monitor
 | 
					 | 
				
			||||||
            primary_info = None
 | 
					 | 
				
			||||||
            for line in output.splitlines():
 | 
					 | 
				
			||||||
                if "primary" in line:
 | 
					 | 
				
			||||||
                    parts = line.split()
 | 
					 | 
				
			||||||
                    for part in parts:
 | 
					 | 
				
			||||||
                        if "x" in part and "+" in part:
 | 
					 | 
				
			||||||
                            primary_info = part
 | 
					 | 
				
			||||||
                            break
 | 
					 | 
				
			||||||
                    break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if primary_info:
 | 
					 | 
				
			||||||
                # Parse the geometry: WIDTH x HEIGHT+X+Y
 | 
					 | 
				
			||||||
                geometry = primary_info.split("+")
 | 
					 | 
				
			||||||
                dimensions = geometry[0].split("x")
 | 
					 | 
				
			||||||
                primary_width = int(dimensions[0])
 | 
					 | 
				
			||||||
                primary_height = int(dimensions[1])
 | 
					 | 
				
			||||||
                primary_x = int(geometry[1])
 | 
					 | 
				
			||||||
                primary_y = int(geometry[2])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # Calculate center position on the primary monitor
 | 
					 | 
				
			||||||
                x = primary_x + (primary_width - width) // 2
 | 
					 | 
				
			||||||
                y = primary_y + (primary_height - height) // 2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # Set window geometry
 | 
					 | 
				
			||||||
                window.geometry(f"{width}x{height}+{x}+{y}")
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
        except (ImportError, IndexError, ValueError):
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Final fallback: Use standard Tkinter method
 | 
					 | 
				
			||||||
        screen_width = window.winfo_screenwidth()
 | 
					 | 
				
			||||||
        screen_height = window.winfo_screenheight()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Try to make an educated guess for multi-monitor setups
 | 
					 | 
				
			||||||
        # If screen width is much larger than height, assume multiple monitors side by side
 | 
					 | 
				
			||||||
        if (
 | 
					 | 
				
			||||||
            screen_width > screen_height * 1.8
 | 
					 | 
				
			||||||
        ):  # Heuristic for detecting multiple monitors
 | 
					 | 
				
			||||||
            # Assume the primary monitor is on the left half
 | 
					 | 
				
			||||||
            screen_width = screen_width // 2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        x = (screen_width - width) // 2
 | 
					 | 
				
			||||||
        y = (screen_height - height) // 2
 | 
					 | 
				
			||||||
        window.geometry(f"{width}x{height}+{x}+{y}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def clean_files(tmp_dir: Path = AppConfig.TEMP_DIR, file: Path = None) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Deletes temporary files and directories for cleanup when exiting the application.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        This method safely removes an optional directory defined by `AppConfig.TEMP_DIR`
 | 
					 | 
				
			||||||
        and a single file to free up resources at the end of the program's execution.
 | 
					 | 
				
			||||||
        All operations are performed securely, and errors such as `FileNotFoundError`
 | 
					 | 
				
			||||||
        are ignored if the target files or directories do not exist.
 | 
					 | 
				
			||||||
        :param tmp_dir: (Path, optional): Path to the temporary directory that should be deleted.
 | 
					 | 
				
			||||||
                If `None`, the value of `AppConfig.TEMP_DIR` is used.
 | 
					 | 
				
			||||||
        :param file: (Path, optional): Path to the file that should be deleted.
 | 
					 | 
				
			||||||
                If `None`, no additional file will be deleted.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Returns:
 | 
					 | 
				
			||||||
            None: The method does not return any value.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if AppConfig.TEMP_DIR is not None:
 | 
					 | 
				
			||||||
            shutil.rmtree(AppConfig.TEMP_DIR)
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            if file is not None:
 | 
					 | 
				
			||||||
                Path.unlink(file)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        except FileNotFoundError:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def msg_window(
 | 
					 | 
				
			||||||
        image_path: Path,
 | 
					 | 
				
			||||||
        image_path2: Path,
 | 
					 | 
				
			||||||
        w_title: str,
 | 
					 | 
				
			||||||
        w_txt: str,
 | 
					 | 
				
			||||||
        txt2: Optional[str] = None,
 | 
					 | 
				
			||||||
        com: Optional[str] = None,
 | 
					 | 
				
			||||||
    ) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Creates message windows
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param image_path2:
 | 
					 | 
				
			||||||
        :param image_path:
 | 
					 | 
				
			||||||
         AppConfig.IMAGE_PATHS["icon_info"] = Image for TK window which is displayed to the left of the text
 | 
					 | 
				
			||||||
         AppConfig.IMAGE_PATHS["icon_vpn"] = Image for Task Icon
 | 
					 | 
				
			||||||
        :argument w_title = Windows Title
 | 
					 | 
				
			||||||
        :argument w_txt = Text for Tk Window
 | 
					 | 
				
			||||||
        :argument txt2 = Text for Button two
 | 
					 | 
				
			||||||
        :argument com = function for Button command
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        msg: tk.Toplevel = tk.Toplevel()
 | 
					 | 
				
			||||||
        msg.resizable(width=False, height=False)
 | 
					 | 
				
			||||||
        msg.title(w_title)
 | 
					 | 
				
			||||||
        msg.configure(pady=15, padx=15)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # load first image for a window
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            msg.img = tk.PhotoImage(file=image_path)
 | 
					 | 
				
			||||||
            msg.i_window = tk.Label(msg, image=msg.img)
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            logging.error(f"Error on load Window Image: {e}", exc_info=True)
 | 
					 | 
				
			||||||
            msg.i_window = tk.Label(msg, text="Image not found")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        label: tk.Label = tk.Label(msg, text=w_txt)
 | 
					 | 
				
			||||||
        label.grid(column=1, row=0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if txt2 is not None and com is not None:
 | 
					 | 
				
			||||||
            label.config(font=("Ubuntu", 11), padx=15, justify="left")
 | 
					 | 
				
			||||||
            msg.i_window.grid(column=0, row=0, sticky="nw")
 | 
					 | 
				
			||||||
            button2: ttk.Button = ttk.Button(
 | 
					 | 
				
			||||||
                msg, text=f"{txt2}", command=com, padding=4
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            button2.grid(column=0, row=1, sticky="e", columnspan=2)
 | 
					 | 
				
			||||||
            button: ttk.Button = ttk.Button(
 | 
					 | 
				
			||||||
                msg, text="OK", command=msg.destroy, padding=4
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            button.grid(column=0, row=1, sticky="w", columnspan=2)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            label.config(font=("Ubuntu", 11), padx=15)
 | 
					 | 
				
			||||||
            msg.i_window.grid(column=0, row=0)
 | 
					 | 
				
			||||||
            button: ttk.Button = ttk.Button(
 | 
					 | 
				
			||||||
                msg, text="OK", command=msg.destroy, padding=4
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            button.grid(column=0, columnspan=2, row=1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            icon = tk.PhotoImage(file=image_path2)
 | 
					 | 
				
			||||||
            msg.iconphoto(True, icon)
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            logging.error(f"Error loading the window icon: {e}", exc_info=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        msg.columnconfigure(0, weight=1)
 | 
					 | 
				
			||||||
        msg.rowconfigure(0, weight=1)
 | 
					 | 
				
			||||||
        msg.winfo_toplevel()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def sigi(file_path: Optional[Path] = None, file: Optional[Path] = None) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Function for cleanup after a program interruption
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param file: Optional - File to be deleted
 | 
					 | 
				
			||||||
        :param file_path: Optional - Directory to be deleted
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def signal_handler(signum: int, frame: Any) -> NoReturn:
 | 
					 | 
				
			||||||
            """
 | 
					 | 
				
			||||||
            Determines clear text names for signal numbers and handles signals
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Args:
 | 
					 | 
				
			||||||
                signum: The signal number
 | 
					 | 
				
			||||||
                frame: The current stack frame
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Returns:
 | 
					 | 
				
			||||||
                NoReturn since the function either exits the program or continues execution
 | 
					 | 
				
			||||||
            """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            signals_to_names_dict: Dict[int, str] = dict(
 | 
					 | 
				
			||||||
                (getattr(signal, n), n)
 | 
					 | 
				
			||||||
                for n in dir(signal)
 | 
					 | 
				
			||||||
                if n.startswith("SIG") and "_" not in n
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            signal_name: str = signals_to_names_dict.get(
 | 
					 | 
				
			||||||
                signum, f"Unnamed signal: {signum}"
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # End program for certain signals, report to others only reception
 | 
					 | 
				
			||||||
            if signum in (signal.SIGINT, signal.SIGTERM):
 | 
					 | 
				
			||||||
                exit_code: int = 1
 | 
					 | 
				
			||||||
                logging.error(
 | 
					 | 
				
			||||||
                    f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}.",
 | 
					 | 
				
			||||||
                    exc_info=True,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                LxTools.clean_files(file_path, file)
 | 
					 | 
				
			||||||
                logging.info("Breakdown by user...")
 | 
					 | 
				
			||||||
                sys.exit(exit_code)
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                logging.info(f"Signal {signum} received and ignored.")
 | 
					 | 
				
			||||||
                LxTools.clean_files(file_path, file)
 | 
					 | 
				
			||||||
                logging.error("Process unexpectedly ended...")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Register signal handlers for various signals
 | 
					 | 
				
			||||||
        signal.signal(signal.SIGINT, signal_handler)
 | 
					 | 
				
			||||||
        signal.signal(signal.SIGTERM, signal_handler)
 | 
					 | 
				
			||||||
        signal.signal(signal.SIGHUP, signal_handler)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Tunnel:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Class of Methods for Wire-Py
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def parse_files_to_dictionary(
 | 
					 | 
				
			||||||
        directory: Path = None, filepath: str = None, content: str = None
 | 
					 | 
				
			||||||
    ) -> tuple[dict, str] | dict | None:
 | 
					 | 
				
			||||||
        data = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if filepath is not None:
 | 
					 | 
				
			||||||
            filepath = Path(filepath)
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                content = filepath.read_text()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # parse the content
 | 
					 | 
				
			||||||
                address_line = next(
 | 
					 | 
				
			||||||
                    line for line in content.splitlines() if line.startswith("Address")
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                dns_line = next(
 | 
					 | 
				
			||||||
                    line for line in content.splitlines() if line.startswith("DNS")
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                endpoint_line = next(
 | 
					 | 
				
			||||||
                    line for line in content.splitlines() if line.startswith("Endpoint")
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                private_key_line = next(
 | 
					 | 
				
			||||||
                    line
 | 
					 | 
				
			||||||
                    for line in content.splitlines()
 | 
					 | 
				
			||||||
                    if line.startswith("PrivateKey")
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                content = secrets.token_bytes(len(content))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # extract the values
 | 
					 | 
				
			||||||
                address = address_line.split("=")[1].strip()
 | 
					 | 
				
			||||||
                dns = dns_line.split("=")[1].strip()
 | 
					 | 
				
			||||||
                endpoint = endpoint_line.split("=")[1].strip()
 | 
					 | 
				
			||||||
                private_key = private_key_line.split("=")[1].strip()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # Shorten the tunnel name to the maximum allowed length if it exceeds 12 characters.
 | 
					 | 
				
			||||||
                original_stem = filepath.stem
 | 
					 | 
				
			||||||
                truncated_stem = (
 | 
					 | 
				
			||||||
                    original_stem[-12:] if len(original_stem) > 12 else original_stem
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # save in the dictionary
 | 
					 | 
				
			||||||
                data[truncated_stem] = {
 | 
					 | 
				
			||||||
                    "Address": address,
 | 
					 | 
				
			||||||
                    "DNS": dns,
 | 
					 | 
				
			||||||
                    "Endpoint": endpoint,
 | 
					 | 
				
			||||||
                    "PrivateKey": private_key,
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                content = secrets.token_bytes(len(content))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            except StopIteration:
 | 
					 | 
				
			||||||
                pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        elif directory is not None:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if not directory.exists() or not directory.is_dir():
 | 
					 | 
				
			||||||
                logging.error(
 | 
					 | 
				
			||||||
                    "Temp directory does not exist or is not a directory.",
 | 
					 | 
				
			||||||
                    exc_info=True,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Get a list of all files in the directory
 | 
					 | 
				
			||||||
            files = [file for file in AppConfig.TEMP_DIR.iterdir() if file.is_file()]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Search for the string in the files
 | 
					 | 
				
			||||||
            for file in files:
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    content = file.read_text()
 | 
					 | 
				
			||||||
                    # parse the content
 | 
					 | 
				
			||||||
                    address_line = next(
 | 
					 | 
				
			||||||
                        line
 | 
					 | 
				
			||||||
                        for line in content.splitlines()
 | 
					 | 
				
			||||||
                        if line.startswith("Address")
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    dns_line = next(
 | 
					 | 
				
			||||||
                        line for line in content.splitlines() if line.startswith("DNS")
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    endpoint_line = next(
 | 
					 | 
				
			||||||
                        line
 | 
					 | 
				
			||||||
                        for line in content.splitlines()
 | 
					 | 
				
			||||||
                        if line.startswith("Endpoint")
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    # extract values
 | 
					 | 
				
			||||||
                    address = address_line.split("=")[1].strip()
 | 
					 | 
				
			||||||
                    dns = dns_line.split("=")[1].strip()
 | 
					 | 
				
			||||||
                    endpoint = endpoint_line.split("=")[1].strip()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    # save values to dictionary
 | 
					 | 
				
			||||||
                    data[file.stem] = {
 | 
					 | 
				
			||||||
                        "Address": address,
 | 
					 | 
				
			||||||
                        "DNS": dns,
 | 
					 | 
				
			||||||
                        "Endpoint": endpoint,
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                except Exception:
 | 
					 | 
				
			||||||
                    # Ignore errors and continue to the next file
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
            if content is not None:
 | 
					 | 
				
			||||||
                content = secrets.token_bytes(len(content))
 | 
					 | 
				
			||||||
        if filepath is not None:
 | 
					 | 
				
			||||||
            return data, truncated_stem
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            return data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def get_active() -> str:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Shows the Active Tunnel
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        active = None
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            process: CompletedProcess[str] = run(
 | 
					 | 
				
			||||||
                ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show", "--active"],
 | 
					 | 
				
			||||||
                capture_output=True,
 | 
					 | 
				
			||||||
                text=True,
 | 
					 | 
				
			||||||
                check=False,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            active = next(
 | 
					 | 
				
			||||||
                line.split(":")[0].strip()
 | 
					 | 
				
			||||||
                for line in process.stdout.splitlines()
 | 
					 | 
				
			||||||
                if line.endswith("wireguard")
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if process.stderr and "error" in process.stderr.lower():
 | 
					 | 
				
			||||||
                logging.error(f"Error output on nmcli: {process.stderr}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        except StopIteration:
 | 
					 | 
				
			||||||
            active = None
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            logging.error(f"Error on nmcli: {e}")
 | 
					 | 
				
			||||||
            active = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return active if active is not None else ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def export() -> bool | None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        This will export the tunnels.
 | 
					 | 
				
			||||||
        A zipfile with the current date and time is created
 | 
					 | 
				
			||||||
        in the user's home directory with the correct right
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        now_time: datetime = datetime.now()
 | 
					 | 
				
			||||||
        now_datetime: str = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            AppConfig.ensure_directories()
 | 
					 | 
				
			||||||
            CryptoUtil.decrypt(getpass.getuser())
 | 
					 | 
				
			||||||
            if len([file.name for file in AppConfig.TEMP_DIR.glob("*.conf")]) == 0:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                LxTools.msg_window(
 | 
					 | 
				
			||||||
                    AppConfig.IMAGE_PATHS["icon_info"],
 | 
					 | 
				
			||||||
                    AppConfig.IMAGE_PATHS["icon_msg"],
 | 
					 | 
				
			||||||
                    Msg.STR["sel_tl"],
 | 
					 | 
				
			||||||
                    Msg.STR["tl_first"],
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                wg_tar: str = f"{AppConfig.BASE_DIR}/{now_datetime}"
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    shutil.make_archive(wg_tar, "zip", AppConfig.TEMP_DIR)
 | 
					 | 
				
			||||||
                    with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf:
 | 
					 | 
				
			||||||
                        if zf.namelist():
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            LxTools.msg_window(
 | 
					 | 
				
			||||||
                                AppConfig.IMAGE_PATHS["icon_info"],
 | 
					 | 
				
			||||||
                                AppConfig.IMAGE_PATHS["icon_vpn"],
 | 
					 | 
				
			||||||
                                Msg.STR["exp_succ"],
 | 
					 | 
				
			||||||
                                Msg.STR["exp_in_home"],
 | 
					 | 
				
			||||||
                            )
 | 
					 | 
				
			||||||
                        else:
 | 
					 | 
				
			||||||
                            logging.error(
 | 
					 | 
				
			||||||
                                "There was a mistake at creating the Zip file. File is empty."
 | 
					 | 
				
			||||||
                            )
 | 
					 | 
				
			||||||
                            LxTools.msg_window(
 | 
					 | 
				
			||||||
                                AppConfig.IMAGE_PATHS["icon_error"],
 | 
					 | 
				
			||||||
                                AppConfig.IMAGE_PATHS["icon_msg"],
 | 
					 | 
				
			||||||
                                Msg.STR["exp_err"],
 | 
					 | 
				
			||||||
                                Msg.STR["exp_zip"],
 | 
					 | 
				
			||||||
                            )
 | 
					 | 
				
			||||||
                            return False
 | 
					 | 
				
			||||||
                        return True
 | 
					 | 
				
			||||||
                except PermissionError:
 | 
					 | 
				
			||||||
                    logging.error(
 | 
					 | 
				
			||||||
                        f"Permission denied when creating archive in {wg_tar}"
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        except zipfile.BadZipFile as e:
 | 
					 | 
				
			||||||
            logging.error(f"Invalid ZIP file: {e}")
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
        except TypeError:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            logging.error(f"Export failed: {str(e)}")
 | 
					 | 
				
			||||||
            LxTools.msg_window(
 | 
					 | 
				
			||||||
                AppConfig.IMAGE_PATHS["icon_error"],
 | 
					 | 
				
			||||||
                AppConfig.IMAGE_PATHS["icon_msg"],
 | 
					 | 
				
			||||||
                Msg.STR["exp_err"],
 | 
					 | 
				
			||||||
                Msg.STR["exp_try"],
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        finally:
 | 
					 | 
				
			||||||
            LxTools.clean_files(AppConfig.TEMP_DIR)
 | 
					 | 
				
			||||||
            AppConfig.ensure_directories()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# ConfigManager with caching
 | 
					 | 
				
			||||||
class ConfigManager:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Universal class for managing configuration files with caching support.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    This class provides a general solution to load, save, and manage configuration
 | 
					 | 
				
			||||||
    files across different projects. It uses a caching system to optimize access efficiency.
 | 
					 | 
				
			||||||
    The `init()` method initializes the configuration file path, while `load()` and `save()`
 | 
					 | 
				
			||||||
    synchronize data between the file and internal memory structures.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Key Features:
 | 
					 | 
				
			||||||
    - Caching to minimize I/O operations.
 | 
					 | 
				
			||||||
    - Default values for missing or corrupted configuration files.
 | 
					 | 
				
			||||||
    - Reusability across different projects and use cases.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    The class is designed for central application configuration management, working closely
 | 
					 | 
				
			||||||
    with `ThemeManager` to dynamically manage themes or other settings.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _config = None
 | 
					 | 
				
			||||||
    _config_file = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def init(cls, config_file):
 | 
					 | 
				
			||||||
        """Initial the Configmanager with the given config file"""
 | 
					 | 
				
			||||||
        cls._config_file = config_file
 | 
					 | 
				
			||||||
        cls._config = None  # Reset the cache
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def load(cls):
 | 
					 | 
				
			||||||
        """Load the config file and return the config as dict"""
 | 
					 | 
				
			||||||
        if not cls._config:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                lines = Path(cls._config_file).read_text(encoding="utf-8").splitlines()
 | 
					 | 
				
			||||||
                cls._config = {
 | 
					 | 
				
			||||||
                    "updates": lines[1].strip(),
 | 
					 | 
				
			||||||
                    "theme": lines[3].strip(),
 | 
					 | 
				
			||||||
                    "tooltips": lines[5].strip()
 | 
					 | 
				
			||||||
                    == "True",  # is converted here to boolean!!!
 | 
					 | 
				
			||||||
                    "autostart": lines[7].strip() if len(lines) > 7 else "off",
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            except (IndexError, FileNotFoundError):
 | 
					 | 
				
			||||||
                # DeDefault values in case of error
 | 
					 | 
				
			||||||
                cls._config = {
 | 
					 | 
				
			||||||
                    "updates": "on",
 | 
					 | 
				
			||||||
                    "theme": "light",
 | 
					 | 
				
			||||||
                    "tooltips": "True",  # Default Value as string!
 | 
					 | 
				
			||||||
                    "autostart": "off",
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
        return cls._config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def save(cls):
 | 
					 | 
				
			||||||
        """Save the config to the config file"""
 | 
					 | 
				
			||||||
        if cls._config:
 | 
					 | 
				
			||||||
            lines = [
 | 
					 | 
				
			||||||
                "# Configuration\n",
 | 
					 | 
				
			||||||
                f"{cls._config['updates']}\n",
 | 
					 | 
				
			||||||
                "# Theme\n",
 | 
					 | 
				
			||||||
                f"{cls._config['theme']}\n",
 | 
					 | 
				
			||||||
                "# Tooltips\n",
 | 
					 | 
				
			||||||
                f"{str(cls._config['tooltips'])}\n",
 | 
					 | 
				
			||||||
                "# Autostart\n",
 | 
					 | 
				
			||||||
                f"{cls._config['autostart']}\n",
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
            Path(cls._config_file).write_text("".join(lines), encoding="utf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def set(cls, key, value):
 | 
					 | 
				
			||||||
        """Sets a configuration value and saves the change"""
 | 
					 | 
				
			||||||
        cls.load()
 | 
					 | 
				
			||||||
        cls._config[key] = value
 | 
					 | 
				
			||||||
        cls.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def get(cls, key, default=None):
 | 
					 | 
				
			||||||
        """Returns a configuration value"""
 | 
					 | 
				
			||||||
        config = cls.load()
 | 
					 | 
				
			||||||
        return config.get(key, default)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ThemeManager:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Class for central theme management and UI customization.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    This static class allows dynamic adjustment of the application's appearance.
 | 
					 | 
				
			||||||
    The method `change_theme()` updates the current theme and saves
 | 
					 | 
				
			||||||
    the selection in the configuration file via `ConfigManager`.
 | 
					 | 
				
			||||||
    It ensures a consistent visual design across the entire project.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Key Features:
 | 
					 | 
				
			||||||
    - Central control over themes.
 | 
					 | 
				
			||||||
    - Automatic saving of theme settings to the configuration file.
 | 
					 | 
				
			||||||
    - Tight integration with `ConfigManager` for persistent storage of preferences.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    The class is designed to apply themes consistently throughout the application,
 | 
					 | 
				
			||||||
    ensuring that changes are traceable and uniform across all parts of the project.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def change_theme(root, theme_in_use, theme_name=None):
 | 
					 | 
				
			||||||
        """Change application theme centrally"""
 | 
					 | 
				
			||||||
        root.tk.call("set_theme", theme_in_use)
 | 
					 | 
				
			||||||
        if theme_in_use == theme_name:
 | 
					 | 
				
			||||||
            ConfigManager.set("theme", theme_in_use)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class GiteaUpdate:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Calling download requests the download URL of the running script,
 | 
					 | 
				
			||||||
    the taskbar image for the “Download OK” window, the taskbar image for the
 | 
					 | 
				
			||||||
    “Download error” window, and the variable res
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def api_down(update_api_url: str, version: str, update_setting: str = None) -> str:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Checks for updates via API
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Args:
 | 
					 | 
				
			||||||
            update_api_url: Update API URL
 | 
					 | 
				
			||||||
            version: Current version
 | 
					 | 
				
			||||||
            update_setting: Update setting from ConfigManager (on/off)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Returns:
 | 
					 | 
				
			||||||
            New version or status message
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        # If updates are disabled, return immediately
 | 
					 | 
				
			||||||
        if update_setting != "on":
 | 
					 | 
				
			||||||
            return "False"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            response: requests.Response = requests.get(update_api_url, timeout=10)
 | 
					 | 
				
			||||||
            response.raise_for_status()  # Raise exception for HTTP errors
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            response_data = response.json()
 | 
					 | 
				
			||||||
            if not response_data:
 | 
					 | 
				
			||||||
                return "No Updates"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            latest_version = response_data[0].get("tag_name")
 | 
					 | 
				
			||||||
            if not latest_version:
 | 
					 | 
				
			||||||
                return "Invalid API Response"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Compare versions (strip 'v. ' prefix if present)
 | 
					 | 
				
			||||||
            current_version = version[3:] if version.startswith("v. ") else version
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if current_version != latest_version:
 | 
					 | 
				
			||||||
                return latest_version
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                return "No Updates"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        except requests.exceptions.RequestException:
 | 
					 | 
				
			||||||
            return "No Internet Connection!"
 | 
					 | 
				
			||||||
        except (ValueError, KeyError, IndexError):
 | 
					 | 
				
			||||||
            return "Invalid API Response"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def download(
 | 
					 | 
				
			||||||
        urld: str,
 | 
					 | 
				
			||||||
        res: str,
 | 
					 | 
				
			||||||
        image_path: Path = None,
 | 
					 | 
				
			||||||
        image_path2: Path = None,
 | 
					 | 
				
			||||||
        image_path3: Path = None,
 | 
					 | 
				
			||||||
        image_path4: Path = None,
 | 
					 | 
				
			||||||
    ) -> None:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Downloads new version of wirepy
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param urld: Download URL
 | 
					 | 
				
			||||||
        :param res: Result filename
 | 
					 | 
				
			||||||
        :param image_path: AppConfig.IMAGE_PATHS["icon_info"]: Image for TK window which is displayed to the left of the text
 | 
					 | 
				
			||||||
        :param image_path2: AppConfig.IMAGE_PATHS["icon_vpn"]: Image for Task Icon
 | 
					 | 
				
			||||||
        :param image_path3: AppConfig.IMAGE_PATHS["icon_error"]: Image for TK window which is displayed to the left of the text
 | 
					 | 
				
			||||||
        :param image_path4: AppConfig.IMAGE_PATHS["icon_msg"]: Image for Task Icon
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            to_down: str = f"wget -qP {Path.home()} {" "} {urld}"
 | 
					 | 
				
			||||||
            result: int = subprocess.call(to_down, shell=True)
 | 
					 | 
				
			||||||
            if result == 0:
 | 
					 | 
				
			||||||
                shutil.chown(f"{Path.home()}/{res}.zip", 1000, 1000)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                wt: str = _("Download Successful")
 | 
					 | 
				
			||||||
                msg_t: str = _("Your zip file is in home directory")
 | 
					 | 
				
			||||||
                LxTools.msg_window(
 | 
					 | 
				
			||||||
                    AppConfig.IMAGE_PATHS["icon_info"],
 | 
					 | 
				
			||||||
                    AppConfig.IMAGE_PATHS["icon_vpn"],
 | 
					 | 
				
			||||||
                    wt,
 | 
					 | 
				
			||||||
                    msg_t,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                wt: str = _("Download error")
 | 
					 | 
				
			||||||
                msg_t: str = _("Download failed! Please try again")
 | 
					 | 
				
			||||||
                LxTools.msg_window(
 | 
					 | 
				
			||||||
                    AppConfig.IMAGE_PATHS["icon_error"],
 | 
					 | 
				
			||||||
                    AppConfig.IMAGE_PATHS["icon_msg"],
 | 
					 | 
				
			||||||
                    wt,
 | 
					 | 
				
			||||||
                    msg_t,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        except subprocess.CalledProcessError:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            wt: str = _("Download error")
 | 
					 | 
				
			||||||
            msg_t: str = _("Download failed! No internet connection!")
 | 
					 | 
				
			||||||
            LxTools.msg_window(
 | 
					 | 
				
			||||||
                AppConfig.IMAGE_PATHS["icon_error"],
 | 
					 | 
				
			||||||
                AppConfig.IMAGE_PATHS["icon_msg"],
 | 
					 | 
				
			||||||
                wt,
 | 
					 | 
				
			||||||
                msg_t,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Tooltip:
 | 
					 | 
				
			||||||
    """Class for Tooltip
 | 
					 | 
				
			||||||
    from common_tools.py import Tooltip
 | 
					 | 
				
			||||||
    example: Tooltip(label, "Show tooltip on label")
 | 
					 | 
				
			||||||
    example: Tooltip(button, "Show tooltip on button")
 | 
					 | 
				
			||||||
    example: Tooltip(widget, "Text", state_var=tk.BooleanVar())
 | 
					 | 
				
			||||||
    example: Tooltip(widget, "Text", state_var=tk.BooleanVar(), x_offset=20, y_offset=10)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    info: label and button are parent widgets.
 | 
					 | 
				
			||||||
    NOTE: When using with state_var, pass the tk.BooleanVar object directly,
 | 
					 | 
				
			||||||
    NOT its value. For example: use state_var=my_bool_var, NOT state_var=my_bool_var.get()
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(
 | 
					 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        widget: Any,
 | 
					 | 
				
			||||||
        text: str,
 | 
					 | 
				
			||||||
        state_var: Optional[tk.BooleanVar] = None,
 | 
					 | 
				
			||||||
        x_offset: int = 65,
 | 
					 | 
				
			||||||
        y_offset: int = 40,
 | 
					 | 
				
			||||||
    ) -> None:
 | 
					 | 
				
			||||||
        """Tooltip Class"""
 | 
					 | 
				
			||||||
        self.widget: Any = widget
 | 
					 | 
				
			||||||
        self.text: str = text
 | 
					 | 
				
			||||||
        self.tooltip_window: Optional[Toplevel] = None
 | 
					 | 
				
			||||||
        self.state_var = state_var
 | 
					 | 
				
			||||||
        self.x_offset = x_offset
 | 
					 | 
				
			||||||
        self.y_offset = y_offset
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Initial binding based on the current state
 | 
					 | 
				
			||||||
        self.update_bindings()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Add trace to the state_var if provided
 | 
					 | 
				
			||||||
        if self.state_var is not None:
 | 
					 | 
				
			||||||
            self.state_var.trace_add("write", self.update_bindings)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def update_bindings(self, *args) -> None:
 | 
					 | 
				
			||||||
        """Updates the bindings based on the current state"""
 | 
					 | 
				
			||||||
        # Remove existing bindings first
 | 
					 | 
				
			||||||
        self.widget.unbind("<Enter>")
 | 
					 | 
				
			||||||
        self.widget.unbind("<Leave>")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Add new bindings if tooltips are enabled
 | 
					 | 
				
			||||||
        if self.state_var is None or self.state_var.get():
 | 
					 | 
				
			||||||
            self.widget.bind("<Enter>", self.show_tooltip)
 | 
					 | 
				
			||||||
            self.widget.bind("<Leave>", self.hide_tooltip)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def show_tooltip(self, event: Optional[Any] = None) -> None:
 | 
					 | 
				
			||||||
        """Shows the tooltip"""
 | 
					 | 
				
			||||||
        if self.tooltip_window or not self.text:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        x: int
 | 
					 | 
				
			||||||
        y: int
 | 
					 | 
				
			||||||
        cx: int
 | 
					 | 
				
			||||||
        cy: int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        x, y, cx, cy = self.widget.bbox("insert")
 | 
					 | 
				
			||||||
        x += self.widget.winfo_rootx() + self.x_offset
 | 
					 | 
				
			||||||
        y += self.widget.winfo_rooty() + self.y_offset
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.tooltip_window = tw = tk.Toplevel(self.widget)
 | 
					 | 
				
			||||||
        tw.wm_overrideredirect(True)
 | 
					 | 
				
			||||||
        tw.wm_geometry(f"+{x}+{y}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        label: tk.Label = tk.Label(
 | 
					 | 
				
			||||||
            tw,
 | 
					 | 
				
			||||||
            text=self.text,
 | 
					 | 
				
			||||||
            background="lightgreen",
 | 
					 | 
				
			||||||
            foreground="black",
 | 
					 | 
				
			||||||
            relief="solid",
 | 
					 | 
				
			||||||
            borderwidth=1,
 | 
					 | 
				
			||||||
            padx=5,
 | 
					 | 
				
			||||||
            pady=5,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        label.grid()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.tooltip_window.after(2200, lambda: tw.destroy())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def hide_tooltip(self, event: Optional[Any] = None) -> None:
 | 
					 | 
				
			||||||
        """Hides the tooltip"""
 | 
					 | 
				
			||||||
        if self.tooltip_window:
 | 
					 | 
				
			||||||
            self.tooltip_window.destroy()
 | 
					 | 
				
			||||||
            self.tooltip_window = None
 | 
					 | 
				
			||||||
							
								
								
									
										233
									
								
								install
									
									
									
									
									
								
							
							
						
						@@ -1,233 +0,0 @@
 | 
				
			|||||||
#!/bin/bash
 | 
					 | 
				
			||||||
NORMAL='\033[0m'
 | 
					 | 
				
			||||||
GREEN='\033[1;32m'
 | 
					 | 
				
			||||||
RED='\033[31;1;42m'
 | 
					 | 
				
			||||||
BLUE='\033[30;1;34m'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
install_file_with(){
 | 
					 | 
				
			||||||
  clear
 | 
					 | 
				
			||||||
  mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
 | 
					 | 
				
			||||||
  mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
 | 
					 | 
				
			||||||
  systemctl --user enable wg_start.service >/dev/null 2>&1
 | 
					 | 
				
			||||||
  sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/ 
 | 
					 | 
				
			||||||
  if [ $? -ne 0 ]
 | 
					 | 
				
			||||||
    then 
 | 
					 | 
				
			||||||
      systemctl --user disable wg_start.service
 | 
					 | 
				
			||||||
      rm -r ~/.config/wire_py && rm -r ~/.config/systemd
 | 
					 | 
				
			||||||
      exit 0
 | 
					 | 
				
			||||||
  else 
 | 
					 | 
				
			||||||
    sudo apt install python3-tk && \
 | 
					 | 
				
			||||||
    sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
 | 
					 | 
				
			||||||
    sudo cp -fv match_found.py /usr/local/bin/ && \
 | 
					 | 
				
			||||||
    sudo cp -uR lx-icons /usr/share/icons/ &&  sudo cp -uR TK-Themes /usr/share/ && \
 | 
					 | 
				
			||||||
    sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
 | 
					 | 
				
			||||||
    sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \
 | 
					 | 
				
			||||||
    sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
 | 
					 | 
				
			||||||
    sudo mkdir -p /usr/local/etc/ssl
 | 
					 | 
				
			||||||
    if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
 | 
					 | 
				
			||||||
      then
 | 
					 | 
				
			||||||
        sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
 | 
					 | 
				
			||||||
    fi
 | 
					 | 
				
			||||||
  fi  
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
install_arch_d(){
 | 
					 | 
				
			||||||
  clear
 | 
					 | 
				
			||||||
  mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
 | 
					 | 
				
			||||||
  mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
 | 
					 | 
				
			||||||
  systemctl --user enable wg_start.service >/dev/null 2>&1
 | 
					 | 
				
			||||||
  sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/
 | 
					 | 
				
			||||||
  if [ $? -ne 0 ]
 | 
					 | 
				
			||||||
    then 
 | 
					 | 
				
			||||||
        systemctl --user disable wg_start.service
 | 
					 | 
				
			||||||
        rm -r ~/.config/wire_py && rm -r ~/.config/systemd
 | 
					 | 
				
			||||||
        exit 0
 | 
					 | 
				
			||||||
  else 
 | 
					 | 
				
			||||||
    sudo pacman -S --noconfirm tk python3 python-requests && \
 | 
					 | 
				
			||||||
    sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
 | 
					 | 
				
			||||||
    sudo cp -fv match_found.py /usr/local/bin/ && \
 | 
					 | 
				
			||||||
    sudo cp -uR lx-icons /usr/share/icons/ &&  sudo cp -uR TK-Themes /usr/share/ && \
 | 
					 | 
				
			||||||
    sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
 | 
					 | 
				
			||||||
    sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \
 | 
					 | 
				
			||||||
    sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy 
 | 
					 | 
				
			||||||
    sudo mkdir -p /usr/local/etc/ssl
 | 
					 | 
				
			||||||
    if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
 | 
					 | 
				
			||||||
      then
 | 
					 | 
				
			||||||
        sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
 | 
					 | 
				
			||||||
    fi   
 | 
					 | 
				
			||||||
   
 | 
					 | 
				
			||||||
  fi 
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
install(){
 | 
					 | 
				
			||||||
  if grep -i 'debian' /etc/os-release > /dev/null 2>&1
 | 
					 | 
				
			||||||
    then
 | 
					 | 
				
			||||||
      groups > /tmp/isgroup
 | 
					 | 
				
			||||||
      if grep 'sudo' /tmp/isgroup
 | 
					 | 
				
			||||||
        then
 | 
					 | 
				
			||||||
          install_file_with
 | 
					 | 
				
			||||||
      else
 | 
					 | 
				
			||||||
        echo -e "$BLUE"The installer found that they are not in the group sudo.""
 | 
					 | 
				
			||||||
        echo -e "with "$RED"su -"$BLUE" "they can enter the root shell in which they then""
 | 
					 | 
				
			||||||
        echo -e "enter "$GREEN""usermod -aG sudo $USER.""$BLUE""
 | 
					 | 
				
			||||||
        echo -e ""after logging in from the system, they can then run Wire-Py install again." $NORMAL"
 | 
					 | 
				
			||||||
        read -n 1 -s -r -p $"Press Enter to exit"
 | 
					 | 
				
			||||||
        clear
 | 
					 | 
				
			||||||
        exit 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  elif grep -i 'mint\|ubuntu\|pop|' /etc/os-release > /dev/null 2>&1
 | 
					 | 
				
			||||||
    then
 | 
					 | 
				
			||||||
        install_file_with
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  elif grep -i 'arch' /etc/os-release > /dev/null 2>&1
 | 
					 | 
				
			||||||
    then
 | 
					 | 
				
			||||||
      groups > /tmp/isgroup
 | 
					 | 
				
			||||||
      clear
 | 
					 | 
				
			||||||
      if grep 'wheel' /tmp/isgroup
 | 
					 | 
				
			||||||
        then
 | 
					 | 
				
			||||||
            install_arch_d
 | 
					 | 
				
			||||||
      else
 | 
					 | 
				
			||||||
          echo "The installer found that they are not in the group sudo."
 | 
					 | 
				
			||||||
          echo "The sudoers file must be edited with"
 | 
					 | 
				
			||||||
          echo -e "$RED""su -""$NORMAL"
 | 
					 | 
				
			||||||
          echo -e "$GREEN"""EDITOR=nano visudo"""$NORMAL"
 | 
					 | 
				
			||||||
          echo "Find the line:"
 | 
					 | 
				
			||||||
          echo "## Uncomment to allow members of group wheel to execute any command"
 | 
					 | 
				
			||||||
          echo "remove '#' on  # %wheel ALL=(ALL) ALL and save the file"
 | 
					 | 
				
			||||||
          echo -e "then enter "$GREEN"gpasswd -a $USER wheel.""$NORMAL"
 | 
					 | 
				
			||||||
          echo "after logging in from the system, they can then run Wire-Py install again."
 | 
					 | 
				
			||||||
          read -n 1 -s -r -p $"Press Enter to exit"
 | 
					 | 
				
			||||||
          clear
 | 
					 | 
				
			||||||
          exit 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  elif grep -i '|manjaro\|garuda\|endeavour|' /etc/os-release > /dev/null 2>&1
 | 
					 | 
				
			||||||
      then
 | 
					 | 
				
			||||||
          install_arch_d
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  elif grep -i 'fedora' /etc/os-release > /dev/null 2>&1
 | 
					 | 
				
			||||||
    then
 | 
					 | 
				
			||||||
      clear
 | 
					 | 
				
			||||||
      mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
 | 
					 | 
				
			||||||
      mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
 | 
					 | 
				
			||||||
      systemctl --user enable wg_start.service >/dev/null 2>&1
 | 
					 | 
				
			||||||
      sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/
 | 
					 | 
				
			||||||
      if [ $? -ne 0 ]
 | 
					 | 
				
			||||||
        then 
 | 
					 | 
				
			||||||
            systemctl --user disable wg_start.service
 | 
					 | 
				
			||||||
            rm -r ~/.config/wire_py && rm -r ~/.config/systemd
 | 
					 | 
				
			||||||
            exit 0
 | 
					 | 
				
			||||||
      else
 | 
					 | 
				
			||||||
        sudo dnf install python3-tkinter -y
 | 
					 | 
				
			||||||
        sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
 | 
					 | 
				
			||||||
        sudo cp -fv match_found.py /usr/local/bin/ && \
 | 
					 | 
				
			||||||
        sudo cp -uR lx-icons /usr/share/icons/ &&  sudo cp -uR TK-Themes /usr/share/ && \
 | 
					 | 
				
			||||||
        sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
 | 
					 | 
				
			||||||
        sudo cp -fv Wire-Py.desktop /usr/share/applications/  && \
 | 
					 | 
				
			||||||
        sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
 | 
					 | 
				
			||||||
        sudo mkdir -p /usr/local/etc/ssl
 | 
					 | 
				
			||||||
        if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
 | 
					 | 
				
			||||||
          then
 | 
					 | 
				
			||||||
            sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
 | 
					 | 
				
			||||||
        fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      fi
 | 
					 | 
				
			||||||
  elif grep -i 'suse' /etc/os-release > /dev/null 2>&1
 | 
					 | 
				
			||||||
    then
 | 
					 | 
				
			||||||
      clear
 | 
					 | 
				
			||||||
      mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
 | 
					 | 
				
			||||||
      mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
 | 
					 | 
				
			||||||
      systemctl --user enable wg_start.service >/dev/null 2>&1
 | 
					 | 
				
			||||||
      sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/
 | 
					 | 
				
			||||||
      if [ $? -ne 0 ]
 | 
					 | 
				
			||||||
        then 
 | 
					 | 
				
			||||||
          systemctl --user disable wg_start.service
 | 
					 | 
				
			||||||
          rm -r ~/.config/wire_py && rm -r ~/.config/systemd
 | 
					 | 
				
			||||||
          exit 0
 | 
					 | 
				
			||||||
      else 
 | 
					 | 
				
			||||||
        sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
 | 
					 | 
				
			||||||
        sudo cp -fv match_found.py /usr/local/bin/ && \
 | 
					 | 
				
			||||||
        sudo cp -uR lx-icons /usr/share/icons/ &&  sudo cp -uR TK-Themes /usr/share/ && \
 | 
					 | 
				
			||||||
        sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
 | 
					 | 
				
			||||||
        sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \
 | 
					 | 
				
			||||||
        sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy 
 | 
					 | 
				
			||||||
        sudo mkdir -p /usr/local/etc/ssl
 | 
					 | 
				
			||||||
        if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
 | 
					 | 
				
			||||||
          then
 | 
					 | 
				
			||||||
            sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
 | 
					 | 
				
			||||||
        fi
 | 
					 | 
				
			||||||
        if grep -i 'Tumbleweed' /etc/os-release > /dev/null 2>&1
 | 
					 | 
				
			||||||
          then
 | 
					 | 
				
			||||||
            sudo zypper install python313-tk
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
          sudo zypper install python36-tk
 | 
					 | 
				
			||||||
        fi
 | 
					 | 
				
			||||||
              
 | 
					 | 
				
			||||||
      fi
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
  else
 | 
					 | 
				
			||||||
      clear
 | 
					 | 
				
			||||||
      echo $"Your System could not be determined."
 | 
					 | 
				
			||||||
      echo
 | 
					 | 
				
			||||||
      read -n 1 -s -r -p $"Press Enter to exit"
 | 
					 | 
				
			||||||
      clear
 | 
					 | 
				
			||||||
      exit 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  fi
 | 
					 | 
				
			||||||
  #clear
 | 
					 | 
				
			||||||
  read -n 1 -s -r -p $"Press Enter to exit"
 | 
					 | 
				
			||||||
  clear
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
remove(){
 | 
					 | 
				
			||||||
    sudo rm -f /usr/local/bin/wirepy /usr/local/bin/wirepy.py /usr/local/bin/start_wg.py \
 | 
					 | 
				
			||||||
    /usr/local/bin/wp_app_config.py common_tools.py /usr/local/bin/ssl_encrypt.py \
 | 
					 | 
				
			||||||
     /usr/local/bin/ssl_decrypt.py /usr/local/bin/match_found.py
 | 
					 | 
				
			||||||
    if [ $? -ne 0 ]
 | 
					 | 
				
			||||||
      then
 | 
					 | 
				
			||||||
        exit 0
 | 
					 | 
				
			||||||
    else 
 | 
					 | 
				
			||||||
      systemctl --user disable wg_start.service
 | 
					 | 
				
			||||||
      rm -r ~/.config/wire_py && rm -r ~/.config/systemd
 | 
					 | 
				
			||||||
      sudo rm /usr/share/applications/Wire-Py.desktop
 | 
					 | 
				
			||||||
      sudo rm /usr/share/locale/de/LC_MESSAGES/languages/de/wirepy.mo
 | 
					 | 
				
			||||||
      sudo rm -r /usr/local/etc/ssl
 | 
					 | 
				
			||||||
      which syncpy >/dev/null
 | 
					 | 
				
			||||||
      if [ $? -ne 0 ]
 | 
					 | 
				
			||||||
        then 
 | 
					 | 
				
			||||||
          sudo rm -r /usr/share/icons/lx-icons &&  sudo rm -r /usr/share/TK-Themes  
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      echo
 | 
					 | 
				
			||||||
      read -p "Press Enter to exit..."
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
which wirepy >/dev/null
 | 
					 | 
				
			||||||
if [ $? -eq 0 ]
 | 
					 | 
				
			||||||
    then
 | 
					 | 
				
			||||||
        echo "Do you want to update/reinstall or uninstall wirepy?"
 | 
					 | 
				
			||||||
        echo
 | 
					 | 
				
			||||||
        echo "Update/reinstall: press y, uninstall press r"
 | 
					 | 
				
			||||||
        echo
 | 
					 | 
				
			||||||
        read -n 1 -s -r -p "Cancel with any other key..."  result
 | 
					 | 
				
			||||||
        case $result in
 | 
					 | 
				
			||||||
            [y]* ) clear; install; exit;;
 | 
					 | 
				
			||||||
            [Y]* ) clear; install; exit;;
 | 
					 | 
				
			||||||
            [j]* ) clear; install; exit;;
 | 
					 | 
				
			||||||
            [J]* ) clear; install; exit;;
 | 
					 | 
				
			||||||
            [r]* ) clear; remove; exit;;
 | 
					 | 
				
			||||||
            [R]* ) clear; remove; exit;;
 | 
					 | 
				
			||||||
        esac
 | 
					 | 
				
			||||||
        clear
 | 
					 | 
				
			||||||
else
 | 
					 | 
				
			||||||
  install       
 | 
					 | 
				
			||||||
   
 | 
					 | 
				
			||||||
fi        
 | 
					 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								lx-icons/128/download.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/128/download_error.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/128/log.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/256/download.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 7.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/256/download_error.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/256/log.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 10 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/32/download.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 984 B  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/32/download_error.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 657 B  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/32/log.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 906 B  | 
| 
		 Before Width: | Height: | Size: 5.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/48/download.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/48/download_error.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 906 B  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/48/log.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/64/download.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/64/download_error.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								lx-icons/64/log.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.0 KiB  | 
@@ -47,7 +47,8 @@ def search_string_in_directory(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def main() -> None:
 | 
					def main() -> None:
 | 
				
			||||||
    parser = argparse.ArgumentParser(
 | 
					    parser = argparse.ArgumentParser(
 | 
				
			||||||
        description="Script only for use to compare the private key in the Network configurations to avoid errors with the network manager."
 | 
					        description="Script only for use to compare the private key in the"
 | 
				
			||||||
 | 
					        "Network configurations to avoid errors with the network manager."
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    parser.add_argument("search_string", help="Search string")
 | 
					    parser.add_argument("search_string", help="Search string")
 | 
				
			||||||
    args = parser.parse_args()
 | 
					    args = parser.parse_args()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								settings
									
									
									
									
									
								
							
							
						
						@@ -1,9 +0,0 @@
 | 
				
			|||||||
# Configuration
 | 
					 | 
				
			||||||
on
 | 
					 | 
				
			||||||
# Theme
 | 
					 | 
				
			||||||
dark
 | 
					 | 
				
			||||||
# Tooltips
 | 
					 | 
				
			||||||
True
 | 
					 | 
				
			||||||
# Autostart
 | 
					 | 
				
			||||||
off
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@@ -5,7 +5,7 @@ from pathlib import Path
 | 
				
			|||||||
import pwd
 | 
					import pwd
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
from subprocess import CompletedProcess, run
 | 
					from subprocess import CompletedProcess, run
 | 
				
			||||||
from wp_app_config import AppConfig, logging
 | 
					from shared_libs.wp_app_config import AppConfig, logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
parser = argparse.ArgumentParser()
 | 
					parser = argparse.ArgumentParser()
 | 
				
			||||||
parser.add_argument("--user", required=True, help="Username of the target file system")
 | 
					parser.add_argument("--user", required=True, help="Username of the target file system")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ from pathlib import Path
 | 
				
			|||||||
import pwd
 | 
					import pwd
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
from subprocess import CompletedProcess, run
 | 
					from subprocess import CompletedProcess, run
 | 
				
			||||||
from wp_app_config import AppConfig, logging
 | 
					from shared_libs.wp_app_config import AppConfig, logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
parser = argparse.ArgumentParser()
 | 
					parser = argparse.ArgumentParser()
 | 
				
			||||||
parser.add_argument("--user", required=True, help="Username of the target file system")
 | 
					parser.add_argument("--user", required=True, help="Username of the target file system")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,13 +2,13 @@
 | 
				
			|||||||
"""
 | 
					"""
 | 
				
			||||||
 This script belongs to wirepy and is for the auto start of the tunnel
 | 
					 This script belongs to wirepy and is for the auto start of the tunnel
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
from subprocess import CompletedProcess, run
 | 
					from subprocess import CompletedProcess, run
 | 
				
			||||||
from wp_app_config import AppConfig, logging
 | 
					from shared_libs.wp_app_config import AppConfig
 | 
				
			||||||
from common_tools import ConfigManager
 | 
					from shared_libs.common_tools import ConfigManager, LogConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ConfigManager.init(AppConfig.SETTINGS_FILE)
 | 
					ConfigManager.init(AppConfig.SETTINGS_FILE)
 | 
				
			||||||
 | 
					LogConfig.logger(ConfigManager.get("logfile"))
 | 
				
			||||||
if ConfigManager.get("autostart") != "off":
 | 
					if ConfigManager.get("autostart") != "off":
 | 
				
			||||||
    process: CompletedProcess[str] = run(
 | 
					    process: CompletedProcess[str] = run(
 | 
				
			||||||
        ["nmcli", "connection", "up", ConfigManager.get("autostart")],
 | 
					        ["nmcli", "connection", "up", ConfigManager.get("autostart")],
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										230
									
								
								tunnel.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,230 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/python3
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					import zipfile
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					from subprocess import run, CompletedProcess
 | 
				
			||||||
 | 
					import secrets
 | 
				
			||||||
 | 
					from shared_libs.wp_app_config import AppConfig, Msg
 | 
				
			||||||
 | 
					from shared_libs.common_tools import LxTools, CryptoUtil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Translate
 | 
				
			||||||
 | 
					_ = AppConfig.setup_translations()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Tunnel:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Class of Methods for Wire-Py
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def parse_files_to_dictionary(
 | 
				
			||||||
 | 
					        directory: Path = None, filepath: str = None, content: str = None
 | 
				
			||||||
 | 
					    ) -> tuple[dict, str] | dict | None:
 | 
				
			||||||
 | 
					        data = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if filepath is not None:
 | 
				
			||||||
 | 
					            filepath = Path(filepath)
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                content = filepath.read_text()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # parse the content
 | 
				
			||||||
 | 
					                address_line = next(
 | 
				
			||||||
 | 
					                    line for line in content.splitlines() if line.startswith("Address")
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                dns_line = next(
 | 
				
			||||||
 | 
					                    line for line in content.splitlines() if line.startswith("DNS")
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                endpoint_line = next(
 | 
				
			||||||
 | 
					                    line for line in content.splitlines() if line.startswith("Endpoint")
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                private_key_line = next(
 | 
				
			||||||
 | 
					                    line
 | 
				
			||||||
 | 
					                    for line in content.splitlines()
 | 
				
			||||||
 | 
					                    if line.startswith("PrivateKey")
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                content = secrets.token_bytes(len(content))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # extract the values
 | 
				
			||||||
 | 
					                address = address_line.split("=")[1].strip()
 | 
				
			||||||
 | 
					                dns = dns_line.split("=")[1].strip()
 | 
				
			||||||
 | 
					                endpoint = endpoint_line.split("=")[1].strip()
 | 
				
			||||||
 | 
					                private_key = private_key_line.split("=")[1].strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Shorten the tunnel name to the maximum allowed length if it exceeds 12 characters.
 | 
				
			||||||
 | 
					                original_stem = filepath.stem
 | 
				
			||||||
 | 
					                truncated_stem = (
 | 
				
			||||||
 | 
					                    original_stem[-12:] if len(original_stem) > 12 else original_stem
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # save in the dictionary
 | 
				
			||||||
 | 
					                data[truncated_stem] = {
 | 
				
			||||||
 | 
					                    "Address": address,
 | 
				
			||||||
 | 
					                    "DNS": dns,
 | 
				
			||||||
 | 
					                    "Endpoint": endpoint,
 | 
				
			||||||
 | 
					                    "PrivateKey": private_key,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                content = secrets.token_bytes(len(content))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            except StopIteration:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif directory is not None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not directory.exists() or not directory.is_dir():
 | 
				
			||||||
 | 
					                logging.error(
 | 
				
			||||||
 | 
					                    "Temp directory does not exist or is not a directory.",
 | 
				
			||||||
 | 
					                    exc_info=True,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Get a list of all files in the directory
 | 
				
			||||||
 | 
					            files = [file for file in AppConfig.TEMP_DIR.iterdir() if file.is_file()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Search for the string in the files
 | 
				
			||||||
 | 
					            for file in files:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    content = file.read_text()
 | 
				
			||||||
 | 
					                    # parse the content
 | 
				
			||||||
 | 
					                    address_line = next(
 | 
				
			||||||
 | 
					                        line
 | 
				
			||||||
 | 
					                        for line in content.splitlines()
 | 
				
			||||||
 | 
					                        if line.startswith("Address")
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    dns_line = next(
 | 
				
			||||||
 | 
					                        line for line in content.splitlines() if line.startswith("DNS")
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    endpoint_line = next(
 | 
				
			||||||
 | 
					                        line
 | 
				
			||||||
 | 
					                        for line in content.splitlines()
 | 
				
			||||||
 | 
					                        if line.startswith("Endpoint")
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # extract values
 | 
				
			||||||
 | 
					                    address = address_line.split("=")[1].strip()
 | 
				
			||||||
 | 
					                    dns = dns_line.split("=")[1].strip()
 | 
				
			||||||
 | 
					                    endpoint = endpoint_line.split("=")[1].strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # save values to dictionary
 | 
				
			||||||
 | 
					                    data[file.stem] = {
 | 
				
			||||||
 | 
					                        "Address": address,
 | 
				
			||||||
 | 
					                        "DNS": dns,
 | 
				
			||||||
 | 
					                        "Endpoint": endpoint,
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                except Exception:
 | 
				
			||||||
 | 
					                    # Ignore errors and continue to the next file
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					            if content is not None:
 | 
				
			||||||
 | 
					                content = secrets.token_bytes(len(content))
 | 
				
			||||||
 | 
					        if filepath is not None:
 | 
				
			||||||
 | 
					            return data, truncated_stem
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def get_active() -> str:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Shows the Active Tunnel
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        active = None
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            process: CompletedProcess[str] = run(
 | 
				
			||||||
 | 
					                ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show", "--active"],
 | 
				
			||||||
 | 
					                capture_output=True,
 | 
				
			||||||
 | 
					                text=True,
 | 
				
			||||||
 | 
					                check=False,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            active = next(
 | 
				
			||||||
 | 
					                line.split(":")[0].strip()
 | 
				
			||||||
 | 
					                for line in process.stdout.splitlines()
 | 
				
			||||||
 | 
					                if line.endswith("wireguard")
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if process.stderr and "error" in process.stderr.lower():
 | 
				
			||||||
 | 
					                logging.error(f"Error output on nmcli: {process.stderr}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        except StopIteration:
 | 
				
			||||||
 | 
					            active = None
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            logging.error(f"Error on nmcli: {e}")
 | 
				
			||||||
 | 
					            active = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return active if active is not None else ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def export() -> bool | None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        This will export the tunnels.
 | 
				
			||||||
 | 
					        A zipfile with the current date and time is created
 | 
				
			||||||
 | 
					        in the user's home directory with the correct right
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        now_time: datetime = datetime.now()
 | 
				
			||||||
 | 
					        now_datetime: str = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            AppConfig.ensure_directories()
 | 
				
			||||||
 | 
					            CryptoUtil.decrypt(getpass.getuser())
 | 
				
			||||||
 | 
					            if len([file.name for file in AppConfig.TEMP_DIR.glob("*.conf")]) == 0:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                LxTools.msg_window(
 | 
				
			||||||
 | 
					                    AppConfig.IMAGE_PATHS["icon_info"],
 | 
				
			||||||
 | 
					                    AppConfig.IMAGE_PATHS["icon_msg"],
 | 
				
			||||||
 | 
					                    Msg.STR["sel_tl"],
 | 
				
			||||||
 | 
					                    Msg.STR["tl_first"],
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                wg_tar: str = f"{AppConfig.BASE_DIR}/{now_datetime}"
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    shutil.make_archive(wg_tar, "zip", AppConfig.TEMP_DIR)
 | 
				
			||||||
 | 
					                    with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf:
 | 
				
			||||||
 | 
					                        if zf.namelist():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            LxTools.msg_window(
 | 
				
			||||||
 | 
					                                AppConfig.IMAGE_PATHS["icon_info"],
 | 
				
			||||||
 | 
					                                AppConfig.IMAGE_PATHS["icon_vpn"],
 | 
				
			||||||
 | 
					                                Msg.STR["exp_succ"],
 | 
				
			||||||
 | 
					                                Msg.STR["exp_in_home"],
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            logging.error(
 | 
				
			||||||
 | 
					                                "There was a mistake at creating the Zip file. File is empty."
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                            LxTools.msg_window(
 | 
				
			||||||
 | 
					                                AppConfig.IMAGE_PATHS["icon_error"],
 | 
				
			||||||
 | 
					                                AppConfig.IMAGE_PATHS["icon_msg"],
 | 
				
			||||||
 | 
					                                Msg.STR["exp_err"],
 | 
				
			||||||
 | 
					                                Msg.STR["exp_zip"],
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                            return False
 | 
				
			||||||
 | 
					                        return True
 | 
				
			||||||
 | 
					                except PermissionError:
 | 
				
			||||||
 | 
					                    logging.error(
 | 
				
			||||||
 | 
					                        f"Permission denied when creating archive in {wg_tar}"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        except zipfile.BadZipFile as e:
 | 
				
			||||||
 | 
					            logging.error(f"Invalid ZIP file: {e}")
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        except TypeError:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            logging.error(f"Export failed: {str(e)}")
 | 
				
			||||||
 | 
					            LxTools.msg_window(
 | 
				
			||||||
 | 
					                AppConfig.IMAGE_PATHS["icon_error"],
 | 
				
			||||||
 | 
					                AppConfig.IMAGE_PATHS["icon_msg"],
 | 
				
			||||||
 | 
					                Msg.STR["exp_err"],
 | 
				
			||||||
 | 
					                Msg.STR["exp_try"],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            LxTools.clean_files(AppConfig.TEMP_DIR)
 | 
				
			||||||
 | 
					            AppConfig.ensure_directories()
 | 
				
			||||||
@@ -1,11 +0,0 @@
 | 
				
			|||||||
[Unit]
 | 
					 | 
				
			||||||
Description=Automatic Tunnel Start
 | 
					 | 
				
			||||||
After=network-online.target 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Service]
 | 
					 | 
				
			||||||
Type=oneshot
 | 
					 | 
				
			||||||
ExecStartPre=/bin/sleep 5
 | 
					 | 
				
			||||||
ExecStart=/usr/local/bin/start_wg.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Install]
 | 
					 | 
				
			||||||
WantedBy=default.target
 | 
					 | 
				
			||||||
							
								
								
									
										37
									
								
								wirepy.py
									
									
									
									
									
								
							
							
						
						@@ -2,7 +2,7 @@
 | 
				
			|||||||
"""
 | 
					"""
 | 
				
			||||||
this script is a simple GUI for managing Wireguard Tunnels
 | 
					this script is a simple GUI for managing Wireguard Tunnels
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
import getpass
 | 
					import getpass
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
@@ -11,21 +11,19 @@ import webbrowser
 | 
				
			|||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
from subprocess import CompletedProcess, run
 | 
					from subprocess import CompletedProcess, run
 | 
				
			||||||
from tkinter import TclError, filedialog, ttk
 | 
					from tkinter import TclError, filedialog, ttk
 | 
				
			||||||
 | 
					from tunnel import Tunnel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from common_tools import (
 | 
					from shared_libs.gitea import GiteaUpdate
 | 
				
			||||||
 | 
					from shared_libs.common_tools import (
 | 
				
			||||||
 | 
					    LxTools,
 | 
				
			||||||
 | 
					    CryptoUtil,
 | 
				
			||||||
 | 
					    LogConfig,
 | 
				
			||||||
    ConfigManager,
 | 
					    ConfigManager,
 | 
				
			||||||
    ThemeManager,
 | 
					    ThemeManager,
 | 
				
			||||||
    CryptoUtil,
 | 
					 | 
				
			||||||
    GiteaUpdate,
 | 
					 | 
				
			||||||
    Tunnel,
 | 
					 | 
				
			||||||
    Tooltip,
 | 
					    Tooltip,
 | 
				
			||||||
    LxTools,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from wp_app_config import AppConfig, Msg, logging
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
AppConfig.ensure_directories()
 | 
					from shared_libs.wp_app_config import AppConfig, Msg
 | 
				
			||||||
AppConfig.create_default_settings()
 | 
					 | 
				
			||||||
CryptoUtil.decrypt(getpass.getuser())
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Wirepy(tk.Tk):
 | 
					class Wirepy(tk.Tk):
 | 
				
			||||||
@@ -169,7 +167,11 @@ class FrameWidgets(ttk.Frame):
 | 
				
			|||||||
        self.settings.add_command(
 | 
					        self.settings.add_command(
 | 
				
			||||||
            label=self.theme_label.get(), command=self.on_theme_toggle
 | 
					            label=self.theme_label.get(), command=self.on_theme_toggle
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        # Logviewer Menu
 | 
				
			||||||
 | 
					        self.settings.add_command(
 | 
				
			||||||
 | 
					            label="Log Viewer",
 | 
				
			||||||
 | 
					            command=lambda: run(["logviewer", "--modul=wp_app_config"]),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        # About BTN Menu / Label
 | 
					        # About BTN Menu / Label
 | 
				
			||||||
        self.about_btn = ttk.Button(
 | 
					        self.about_btn = ttk.Button(
 | 
				
			||||||
            self.menu_frame, text=_("About"), style="Toolbutton", command=self.about
 | 
					            self.menu_frame, text=_("About"), style="Toolbutton", command=self.about
 | 
				
			||||||
@@ -451,12 +453,7 @@ class FrameWidgets(ttk.Frame):
 | 
				
			|||||||
            self.download.add_command(
 | 
					            self.download.add_command(
 | 
				
			||||||
                label=_("Download"),
 | 
					                label=_("Download"),
 | 
				
			||||||
                command=lambda: GiteaUpdate.download(
 | 
					                command=lambda: GiteaUpdate.download(
 | 
				
			||||||
                    f"{AppConfig.DOWNLOAD_URL}/{res}.zip",
 | 
					                    f"{AppConfig.DOWNLOAD_URL}/{res}.zip", res
 | 
				
			||||||
                    res,
 | 
					 | 
				
			||||||
                    AppConfig.IMAGE_PATHS["icon_info"],
 | 
					 | 
				
			||||||
                    AppConfig.IMAGE_PATHS["icon_vpn"],
 | 
					 | 
				
			||||||
                    AppConfig.IMAGE_PATHS["icon_error"],
 | 
					 | 
				
			||||||
                    AppConfig.IMAGE_PATHS["icon_msg"],
 | 
					 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1145,10 +1142,14 @@ class FrameWidgets(ttk.Frame):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    AppConfig.ensure_directories()
 | 
				
			||||||
 | 
					    AppConfig.create_default_settings()
 | 
				
			||||||
 | 
					    CryptoUtil.decrypt(getpass.getuser(), AppConfig.CONFIG_DIR)
 | 
				
			||||||
    _ = AppConfig.setup_translations()
 | 
					    _ = AppConfig.setup_translations()
 | 
				
			||||||
    LxTools.sigi(AppConfig.TEMP_DIR)
 | 
					    LxTools.sigi(AppConfig.TEMP_DIR)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    window = Wirepy()
 | 
					    window = Wirepy()
 | 
				
			||||||
 | 
					    LogConfig.logger(ConfigManager.get("logfile"))
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    the hidden files are hidden in Filedialog
 | 
					    the hidden files are hidden in Filedialog
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								wp_app_config.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						@@ -29,16 +29,10 @@ class AppConfig:
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Logging
 | 
					    # Logging
 | 
				
			||||||
    LOG_DIR = Path.home() / ".local/share/wirepy"
 | 
					    LOG_DIR = Path.home() / ".local/share/lxlogs"
 | 
				
			||||||
    Path(LOG_DIR).mkdir(parents=True, exist_ok=True)
 | 
					    Path(LOG_DIR).mkdir(parents=True, exist_ok=True)
 | 
				
			||||||
    LOG_FILE_PATH = LOG_DIR / "wirepy.log"
 | 
					    LOG_FILE_PATH = LOG_DIR / "wirepy.log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logging.basicConfig(
 | 
					 | 
				
			||||||
        filename=f"{LOG_FILE_PATH}",
 | 
					 | 
				
			||||||
        level=logging.ERROR,
 | 
					 | 
				
			||||||
        format="%(asctime)s - %(levelname)s - %(message)s",
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Localization
 | 
					    # Localization
 | 
				
			||||||
    APP_NAME: str = "wirepy"
 | 
					    APP_NAME: str = "wirepy"
 | 
				
			||||||
    LOCALE_DIR: Path = Path("/usr/share/locale/")
 | 
					    LOCALE_DIR: Path = Path("/usr/share/locale/")
 | 
				
			||||||
@@ -58,6 +52,7 @@ class AppConfig:
 | 
				
			|||||||
        "# Theme": "dark",
 | 
					        "# Theme": "dark",
 | 
				
			||||||
        "# Tooltips": True,
 | 
					        "# Tooltips": True,
 | 
				
			||||||
        "# Autostart": "off",
 | 
					        "# Autostart": "off",
 | 
				
			||||||
 | 
					        "# Logfile": LOG_FILE_PATH,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Updates
 | 
					    # Updates
 | 
				
			||||||
@@ -69,6 +64,7 @@ class AppConfig:
 | 
				
			|||||||
    # UI configuration
 | 
					    # UI configuration
 | 
				
			||||||
    UI_CONFIG: Dict[str, Any] = {
 | 
					    UI_CONFIG: Dict[str, Any] = {
 | 
				
			||||||
        "window_title": "Wire-Py",
 | 
					        "window_title": "Wire-Py",
 | 
				
			||||||
 | 
					        "window_title2": "LogViewer",
 | 
				
			||||||
        "window_size": (600, 383),
 | 
					        "window_size": (600, 383),
 | 
				
			||||||
        "font_family": "Ubuntu",
 | 
					        "font_family": "Ubuntu",
 | 
				
			||||||
        "font_size": 11,
 | 
					        "font_size": 11,
 | 
				
			||||||
@@ -94,6 +90,7 @@ class AppConfig:
 | 
				
			|||||||
        "icon_stop": "/usr/share/icons/lx-icons/48/wg_vpn-stop.png",
 | 
					        "icon_stop": "/usr/share/icons/lx-icons/48/wg_vpn-stop.png",
 | 
				
			||||||
        "icon_info": "/usr/share/icons/lx-icons/64/info.png",
 | 
					        "icon_info": "/usr/share/icons/lx-icons/64/info.png",
 | 
				
			||||||
        "icon_error": "/usr/share/icons/lx-icons/64/error.png",
 | 
					        "icon_error": "/usr/share/icons/lx-icons/64/error.png",
 | 
				
			||||||
 | 
					        "icon_log": "/usr/share/icons/lx-icons/48/log.png",
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
@@ -160,6 +157,12 @@ class AppConfig:
 | 
				
			|||||||
        if process.stderr:
 | 
					        if process.stderr:
 | 
				
			||||||
            logging.error(f"{process.stderr} Code: {process.returncode}", exc_info=True)
 | 
					            logging.error(f"{process.stderr} Code: {process.returncode}", exc_info=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def ensure_log(cls) -> None:
 | 
				
			||||||
 | 
					        """Ensures that the log file exists"""
 | 
				
			||||||
 | 
					        if not cls.LOG_FILE_PATH.exists():
 | 
				
			||||||
 | 
					            cls.LOG_FILE_PATH.touch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# here is initializing the class for translation strings
 | 
					# here is initializing the class for translation strings
 | 
				
			||||||
_ = AppConfig.setup_translations()
 | 
					_ = AppConfig.setup_translations()
 | 
				
			||||||
 
 | 
				
			|||||||