Compare commits
	
		
			16 Commits
		
	
	
		
			03-05-2025
			...
			09-05-2025
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6051ad1546 | |||
| f682858051 | |||
| 68580d0ded | |||
| b764547d16 | |||
| 7f4fabe856 | |||
| 79f6fc0265 | |||
| 5ac37ad9ad | |||
| 4cdcfadbac | |||
| 55f2119bc3 | |||
| d6c20b81f9 | |||
| 0c4d000d96 | |||
| 3da54642a0 | |||
| fb0158d1cd | |||
| 6604650adf | |||
| a903666a26 | |||
| d0adaa76e4 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,5 +1,6 @@
 | 
			
		||||
debug.log
 | 
			
		||||
.venv
 | 
			
		||||
.venv.bak
 | 
			
		||||
.idea
 | 
			
		||||
.vscode
 | 
			
		||||
__pycache__
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								Changelog
									
									
									
									
									
								
							
							
						
						@@ -7,23 +7,28 @@ My standard System: Linux Mint 22 Cinnamon
 | 
			
		||||
 - If Wire-Py already runs, prevent further start
 | 
			
		||||
 - for loops with lists replaced by List Comprehensions
 | 
			
		||||
 | 
			
		||||
   ### Added
 | 
			
		||||
04-06-2025
 | 
			
		||||
 | 
			
		||||
 - large update in the module were outsourced and can now be used as
 | 
			
		||||
   dependencies to wirepy us other apps from git.ilunix.de
 | 
			
		||||
 | 
			
		||||
   ### Added
 | 
			
		||||
13-04-0725
 | 
			
		||||
13-04-20255
 | 
			
		||||
 | 
			
		||||
 - Installer update for Open Suse Tumbleweed and Leap
 | 
			
		||||
 - add symbolic link wirepy.py 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   ### Added
 | 
			
		||||
09-04-0725
 | 
			
		||||
09-04-2025
 | 
			
		||||
 | 
			
		||||
 - Installer now with query and remove
 | 
			
		||||
 - Icons merged
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   ### Added
 | 
			
		||||
07-04-0725
 | 
			
		||||
07-04-2025
 | 
			
		||||
 | 
			
		||||
 - Installers will support other systems again
 | 
			
		||||
 - Installer is now finished clean with wrong password
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										858
									
								
								common_tools.py
									
									
									
									
									
								
							
							
						
						@@ -1,858 +0,0 @@
 | 
			
		||||
""" Classes Method and Functions for lx Apps """
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
import signal
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
import tkinter as tk
 | 
			
		||||
from typing import Optional, Dict, Any, NoReturn, TextIO, Tuple, List
 | 
			
		||||
import zipfile
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from subprocess import check_call, CompletedProcess
 | 
			
		||||
from tkinter import ttk, Toplevel
 | 
			
		||||
from wp_app_config import AppConfig, Msg
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
# Translate
 | 
			
		||||
_ = AppConfig.setup_translations()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Create:
 | 
			
		||||
    """
 | 
			
		||||
    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 dir_and_files() -> None:
 | 
			
		||||
        """
 | 
			
		||||
        check and create folders and files if not present
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        pth: Path = Path.home() / ".config/wire_py"
 | 
			
		||||
        pth.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
        sett: Path = Path.home() / ".config/wire_py/settings"
 | 
			
		||||
        AppConfig.KEYS_FILE
 | 
			
		||||
 | 
			
		||||
        if sett.exists():
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            sett.touch()
 | 
			
		||||
            sett.write_text(
 | 
			
		||||
                "[UPDATES]\non\n[THEME]\nlight\n[TOOLTIP]\nTrue\n[AUTOSTART ON]\noff\n"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if AppConfig.KEYS_FILE.exists():
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            AppConfig.KEYS_FILE.touch()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def files_for_autostart() -> None:
 | 
			
		||||
        """
 | 
			
		||||
        check and create a file for auto start if not present and enable the service
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        pth2: Path = Path.home() / ".config/systemd/user"
 | 
			
		||||
        pth2.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
        wg_ser: Path = Path.home() / ".config/systemd/user/wg_start.service"
 | 
			
		||||
 | 
			
		||||
        if wg_ser.exists():
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            wg_ser.touch()
 | 
			
		||||
            wg_ser.write_text(
 | 
			
		||||
                "[Unit]\nDescription=Automatic Tunnel Start\nAfter=network-online.target\n\n[Service]\n"
 | 
			
		||||
                "Type=oneshot\nExecStartPre=/bin/sleep 5\nExecStart=/usr/local/bin/start_wg.py\n[Install]"
 | 
			
		||||
                "\nWantedBy=default.target"
 | 
			
		||||
            )
 | 
			
		||||
            check_call(["systemctl", "--user", "enable", "wg_start.service"])
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def make_dir() -> None:
 | 
			
		||||
        """Folder Name "tlecdewg" = Tunnel Encrypt Decrypt Wireguard"""
 | 
			
		||||
 | 
			
		||||
        if AppConfig.TEMP_DIR.exists():
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            AppConfig.TEMP_DIR.mkdir()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def decrypt() -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Starts SSL dencrypt
 | 
			
		||||
        """
 | 
			
		||||
        process: CompletedProcess[str] = subprocess.run(
 | 
			
		||||
            ["pkexec", "/usr/local/bin/ssl_decrypt.py"],
 | 
			
		||||
            stdout=subprocess.PIPE,
 | 
			
		||||
            text=True,
 | 
			
		||||
            check=True,
 | 
			
		||||
        )
 | 
			
		||||
        path: Path = Path.home() / ".config/wire_py/"
 | 
			
		||||
        file_in_path: list[Path] = list(path.rglob("*.dat"))
 | 
			
		||||
        if file_in_path:
 | 
			
		||||
            if process.returncode == 0:
 | 
			
		||||
                print("File successfully decrypted...")
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                print(f"Error with the following code... {process.returncode}")
 | 
			
		||||
        else:
 | 
			
		||||
            print(_("Ready for import"))
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def encrypt() -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Starts SSL encryption
 | 
			
		||||
        """
 | 
			
		||||
        process: CompletedProcess[str] = subprocess.run(
 | 
			
		||||
            ["pkexec", "/usr/local/bin/ssl_encrypt.py"],
 | 
			
		||||
            stdout=subprocess.PIPE,
 | 
			
		||||
            text=True,
 | 
			
		||||
            check=True,
 | 
			
		||||
        )
 | 
			
		||||
        print(process.stdout)
 | 
			
		||||
        if process.returncode == 0:
 | 
			
		||||
            print("All Files successfully encrypted...")
 | 
			
		||||
        else:
 | 
			
		||||
            print(f"Error with the following code... {process.returncode}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LxTools(tk.Tk):
 | 
			
		||||
    """
 | 
			
		||||
    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 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: WIDTHxHEIGHT+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 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 (subprocess.SubprocessError, 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 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 get_file_name(path: Path, i: int = 5) -> List[str]:
 | 
			
		||||
        """
 | 
			
		||||
        Recursively searches the specified path for files and returns a list of filenames,
 | 
			
		||||
        with the last 'i' characters of each filename removed.
 | 
			
		||||
 | 
			
		||||
        This method is useful for obtaining filenames without specific file extensions,
 | 
			
		||||
        e.g., to remove '.conf' from Wireguard configuration files.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            path (Path): The directory path to search
 | 
			
		||||
            i (int, optional): Number of characters to remove from the end of each filename.
 | 
			
		||||
                            Default is 5, which typically corresponds to the length of '.conf'.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            List[str]: A list of filenames without the last 'i' characters
 | 
			
		||||
 | 
			
		||||
        Example:
 | 
			
		||||
            If path contains files like 'tunnel1.conf', 'tunnel2.conf' and i=5,
 | 
			
		||||
            the method returns ['tunnel1', 'tunnel2'].
 | 
			
		||||
        """
 | 
			
		||||
        lists_file = list(path.rglob("*"))
 | 
			
		||||
        lists_file = [conf_file.name[:-i] for conf_file in lists_file]
 | 
			
		||||
        return lists_file
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_username() -> str:
 | 
			
		||||
        """
 | 
			
		||||
        Returns the username of the logged-in user,
 | 
			
		||||
        even if the script is running with root privileges.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            result = subprocess.run(
 | 
			
		||||
                ["logname"],
 | 
			
		||||
                stdout=subprocess.PIPE,
 | 
			
		||||
                text=True,
 | 
			
		||||
                check=True,
 | 
			
		||||
            )
 | 
			
		||||
            if result.returncode != 0:
 | 
			
		||||
                exit(1)
 | 
			
		||||
            else:
 | 
			
		||||
                print(result.stdout.strip())
 | 
			
		||||
                return result.stdout.strip()
 | 
			
		||||
 | 
			
		||||
        except subprocess.CalledProcessError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def clean_files(TEMP_DIR: Path = None, file: Path = None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        method that can be added after need to delete a folder and a file when quitting.
 | 
			
		||||
        Args:
 | 
			
		||||
            :param file: default None
 | 
			
		||||
            :param AppConfig.TEMP_DIR: default None
 | 
			
		||||
        """
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        :argument AppConfig.IMAGE_PATHS["icon_info"] = Image for TK window which is displayed to the left of the text
 | 
			
		||||
        :argument 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)
 | 
			
		||||
 | 
			
		||||
        # Lade das erste Bild für das Fenster
 | 
			
		||||
        try:
 | 
			
		||||
            msg.img = tk.PhotoImage(file=image_path)
 | 
			
		||||
            msg.i_window = tk.Label(msg, image=msg.img)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"Fehler beim Laden des Fensterbildes: {e}")
 | 
			
		||||
            msg.i_window = tk.Label(msg, text="Bild nicht gefunden")
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
        # Lade das Icon für das Fenster
 | 
			
		||||
        try:
 | 
			
		||||
            icon = tk.PhotoImage(file=image_path2)
 | 
			
		||||
            msg.iconphoto(True, icon)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"Fehler beim Laden des Fenstericons: {e}")
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
                print(
 | 
			
		||||
                    f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}."
 | 
			
		||||
                )
 | 
			
		||||
                LxTools.clean_files(file_path, file)
 | 
			
		||||
                print("Breakdown by user...")
 | 
			
		||||
                sys.exit(exit_code)
 | 
			
		||||
            else:
 | 
			
		||||
                print(f"Signal {signum} received and ignored.")
 | 
			
		||||
                LxTools.clean_files(file_path, file)
 | 
			
		||||
                print("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() -> Dict[str, List[str]]:
 | 
			
		||||
        data = {}
 | 
			
		||||
 | 
			
		||||
        if not AppConfig.TEMP_DIR.exists() or not AppConfig.TEMP_DIR.is_dir():
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        # Get a list of all files in the directorys
 | 
			
		||||
        files = [file for file in AppConfig.TEMP_DIR.iterdir() if file.is_file()]
 | 
			
		||||
        if not files:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        # Search for the string in the files
 | 
			
		||||
        for file in files:
 | 
			
		||||
            try:
 | 
			
		||||
                with open(file, "r") as f:
 | 
			
		||||
                    content = f.read()
 | 
			
		||||
                    # Hier parsen wir die relevanten Zeilen aus dem Inhalt
 | 
			
		||||
                    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")
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    # Extrahiere die Werte
 | 
			
		||||
                    address = address_line.split("=")[1].strip()
 | 
			
		||||
                    dns = dns_line.split("=")[1].strip()
 | 
			
		||||
                    endpoint = endpoint_line.split("=")[1].strip()
 | 
			
		||||
 | 
			
		||||
                    # Speichere im Dictionary
 | 
			
		||||
                    data[file.stem] = {
 | 
			
		||||
                        "Address": address,
 | 
			
		||||
                        "DNS": dns,
 | 
			
		||||
                        "Endpoint": endpoint,
 | 
			
		||||
                    }
 | 
			
		||||
            except Exception:
 | 
			
		||||
                # Ignore errors and continue to the next file
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
        return data
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def con_to_dict(cls, file: TextIO) -> Tuple[str, str, str, Optional[str]]:
 | 
			
		||||
        """
 | 
			
		||||
        Returns tuple of (address, dns, endpoint, pre_key)
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        dictlist: List[str] = []
 | 
			
		||||
        for lines in file.readlines():
 | 
			
		||||
            line_plit: List[str] = lines.split()
 | 
			
		||||
            dictlist = dictlist + line_plit
 | 
			
		||||
        dictlist.remove("[Interface]")
 | 
			
		||||
        dictlist.remove("[Peer]")
 | 
			
		||||
        for items in dictlist:
 | 
			
		||||
            if items == "=":
 | 
			
		||||
                dictlist.remove(items)
 | 
			
		||||
            if items == "::/0":
 | 
			
		||||
                dictlist.remove(items)
 | 
			
		||||
 | 
			
		||||
        # Here is the beginning (Loop) of convert List to Dictionary
 | 
			
		||||
        for _ in dictlist:
 | 
			
		||||
            a: List[str] = [dictlist[0], dictlist[1]]
 | 
			
		||||
            b: List[str] = [dictlist[2], dictlist[3]]
 | 
			
		||||
            c: List[str] = [dictlist[4], dictlist[5]]
 | 
			
		||||
            d: List[str] = [dictlist[6], dictlist[7]]
 | 
			
		||||
            e: List[str] = [dictlist[8], dictlist[9]]
 | 
			
		||||
            f: List[str] = [dictlist[10], dictlist[11]]
 | 
			
		||||
            g: List[str] = [dictlist[12], dictlist[13]]
 | 
			
		||||
            h: List[str] = [dictlist[14], dictlist[15]]
 | 
			
		||||
            new_list: List[List[str]] = [a, b, c, d, e, f, g, h]
 | 
			
		||||
            final_dict: Dict[str, str] = {}
 | 
			
		||||
            for elements in new_list:
 | 
			
		||||
                final_dict[elements[0]] = elements[1]
 | 
			
		||||
 | 
			
		||||
            # end... result a Dictionary
 | 
			
		||||
 | 
			
		||||
        address: str = final_dict["Address"]
 | 
			
		||||
        dns: str = final_dict["DNS"]
 | 
			
		||||
        if "," in dns:
 | 
			
		||||
            dns = dns[:-1]
 | 
			
		||||
        endpoint: str = final_dict["Endpoint"]
 | 
			
		||||
        pre_key: Optional[str] = final_dict.get("PresharedKey")
 | 
			
		||||
        if pre_key is None:
 | 
			
		||||
            pre_key: Optional[str] = final_dict.get("PreSharedKey")
 | 
			
		||||
        return address, dns, endpoint, pre_key
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def active() -> str:
 | 
			
		||||
        """
 | 
			
		||||
        Shows the Active Tunnel
 | 
			
		||||
        """
 | 
			
		||||
        active = (
 | 
			
		||||
            os.popen('nmcli con show --active | grep -iPo "(.*)(wireguard)"')
 | 
			
		||||
            .read()
 | 
			
		||||
            .split()
 | 
			
		||||
        )
 | 
			
		||||
        if not active:
 | 
			
		||||
            active = ""
 | 
			
		||||
        else:
 | 
			
		||||
            active = active[0]
 | 
			
		||||
 | 
			
		||||
        return active
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def list() -> List[str]:
 | 
			
		||||
        """
 | 
			
		||||
        Returns a list of Wireguard tunnel names
 | 
			
		||||
        """
 | 
			
		||||
        AppConfig.TEMP_DIR: Path = Path("/tmp/tlecdcwg/")
 | 
			
		||||
        wg_s: List[str] = os.listdir(AppConfig.TEMP_DIR)
 | 
			
		||||
 | 
			
		||||
        return wg_s
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def export(
 | 
			
		||||
        image_path: Path = None,
 | 
			
		||||
        image_path2: Path = None,
 | 
			
		||||
        image_path3: Path = None,
 | 
			
		||||
        image_path4: Path = None,
 | 
			
		||||
        title: Dict = None,
 | 
			
		||||
        window_msg: Dict = None,
 | 
			
		||||
    ) -> 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
 | 
			
		||||
        Args:
 | 
			
		||||
            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
 | 
			
		||||
            AppConfig.IMAGE_PATHS["icon_error"]: Image for TK window which is displayed to the left of the text
 | 
			
		||||
            AppConfig.IMAGE_PATHS["icon_msg"]: Image for Task Icon
 | 
			
		||||
        """
 | 
			
		||||
        now_time: datetime = datetime.now()
 | 
			
		||||
        now_datetime: str = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M")
 | 
			
		||||
        tl: List[str] = Tunnel.list()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            if len(tl) != 0:
 | 
			
		||||
                wg_tar: str = f"{Path.home()}/{now_datetime}"
 | 
			
		||||
                shutil.copytree("/tmp/tlecdcwg/", "/tmp/wire_py", dirs_exist_ok=True)
 | 
			
		||||
                source: Path = Path("/tmp/wire_py")
 | 
			
		||||
                shutil.make_archive(wg_tar, "zip", source)
 | 
			
		||||
                shutil.rmtree(source)
 | 
			
		||||
                with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf:
 | 
			
		||||
                    if len(zf.namelist()) != 0:
 | 
			
		||||
 | 
			
		||||
                        LxTools.msg_window(
 | 
			
		||||
                            AppConfig.IMAGE_PATHS["icon_info"],
 | 
			
		||||
                            AppConfig.IMAGE_PATHS["icon_vpn"],
 | 
			
		||||
                            Msg.STR["exp_succ"],
 | 
			
		||||
                            Msg.STR["exp_in_home"],
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
                    else:
 | 
			
		||||
 | 
			
		||||
                        LxTools.msg_window(
 | 
			
		||||
                            AppConfig.IMAGE_PATHS["icon_error"],
 | 
			
		||||
                            AppConfig.IMAGE_PATHS["icon_msg"],
 | 
			
		||||
                            Msg.STR["exp_err"],
 | 
			
		||||
                            Msg.STR["exp_try"],
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
 | 
			
		||||
                LxTools.msg_window(
 | 
			
		||||
                    AppConfig.IMAGE_PATHS["icon_info"],
 | 
			
		||||
                    AppConfig.IMAGE_PATHS["icon_msg"],
 | 
			
		||||
                    Msg.STR["sel_tl"],
 | 
			
		||||
                    Msg.STR["tl_first"],
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        except TypeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ConfigManager with caching
 | 
			
		||||
class ConfigManager:
 | 
			
		||||
    """
 | 
			
		||||
    Universal class for managing configuration files with caching.
 | 
			
		||||
    Can be reused in different projects.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    _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:
 | 
			
		||||
    @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
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            urld: Download URL
 | 
			
		||||
            res: Result filename
 | 
			
		||||
            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
 | 
			
		||||
            AppConfig.IMAGE_PATHS["icon_error"]: Image for TK window which is displayed to the left of the text
 | 
			
		||||
            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 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
 | 
			
		||||
							
								
								
									
										228
									
								
								install
									
									
									
									
									
								
							
							
						
						@@ -1,228 +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 -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 -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 -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 -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 
 | 
			
		||||
    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  | 
							
								
								
									
										61
									
								
								match_found.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						@@ -0,0 +1,61 @@
 | 
			
		||||
#!/usr/bin/python3
 | 
			
		||||
 | 
			
		||||
import argparse
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
directorys: list[str] = [
 | 
			
		||||
    "/etc/netplan/",
 | 
			
		||||
    "/etc/NetworkManager/system-connections/",
 | 
			
		||||
    "/var/lib/NetworkManager/user-connections/",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def search_string_in_directory(
 | 
			
		||||
    directories: list[str] = directorys,  # Use the predefined list as default
 | 
			
		||||
    search_string: str = "",  # Default is empty string
 | 
			
		||||
) -> bool:
 | 
			
		||||
 | 
			
		||||
    if len(search_string) == 0:
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    result = False
 | 
			
		||||
    for directory in directories:
 | 
			
		||||
        in_paths = Path(directory)
 | 
			
		||||
        if not in_paths.exists() or not in_paths.is_dir():
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        files = [file for file in in_paths.iterdir() if file.is_file()]
 | 
			
		||||
        if not files:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        # Search for the string in each file
 | 
			
		||||
        for file in files:
 | 
			
		||||
            try:
 | 
			
		||||
                with open(file, "r", errors="ignore") as f:
 | 
			
		||||
                    for line in f:
 | 
			
		||||
                        if search_string in line:
 | 
			
		||||
                            result = True  # String found
 | 
			
		||||
                            break
 | 
			
		||||
                    if result:
 | 
			
		||||
                        break  # No need to check further
 | 
			
		||||
            except Exception:
 | 
			
		||||
                continue  # Skip files that cause errors
 | 
			
		||||
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main() -> None:
 | 
			
		||||
    parser = argparse.ArgumentParser(
 | 
			
		||||
        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")
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    result = search_string_in_directory(search_string=args.search_string)
 | 
			
		||||
    print(result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
@@ -25,6 +25,7 @@ License along with this library.  If not, see
 | 
			
		||||
  <action id="org.ssl_encrypt">
 | 
			
		||||
    <defaults>
 | 
			
		||||
      <allow_any>auth_admin_keep</allow_any>
 | 
			
		||||
      <allow_inactive>auth_admin_keep</allow_inactive>
 | 
			
		||||
      <allow_active>yes</allow_active>
 | 
			
		||||
    </defaults>
 | 
			
		||||
    <annotate key="org.freedesktop.policykit.exec.path">/usr/local/bin/ssl_encrypt.py</annotate>
 | 
			
		||||
@@ -37,6 +38,14 @@ License along with this library.  If not, see
 | 
			
		||||
      <allow_active>yes</allow_active>
 | 
			
		||||
    </defaults>
 | 
			
		||||
    <annotate key="org.freedesktop.policykit.exec.path">/usr/local/bin/ssl_decrypt.py</annotate>
 | 
			
		||||
    
 | 
			
		||||
  </action>
 | 
			
		||||
 | 
			
		||||
  <action id="org.match_found">
 | 
			
		||||
    <defaults>
 | 
			
		||||
      <allow_any>auth_admin_keep</allow_any>
 | 
			
		||||
      <allow_inactive>auth_admin_keep</allow_inactive>
 | 
			
		||||
      <allow_active>yes</allow_active>
 | 
			
		||||
    </defaults>
 | 
			
		||||
    <annotate key="org.freedesktop.policykit.exec.path">/usr/local/bin/match_found.py</annotate>
 | 
			
		||||
  </action>
 | 
			
		||||
</policyconfig>
 | 
			
		||||
@@ -1,26 +1,30 @@
 | 
			
		||||
#!/usr/bin/python3
 | 
			
		||||
""" This Script decrypt Wireguard files for Wirepy users """
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
import argparse
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from subprocess import check_call
 | 
			
		||||
from wp_app_config import AppConfig
 | 
			
		||||
import getpass
 | 
			
		||||
import pwd
 | 
			
		||||
import shutil
 | 
			
		||||
from subprocess import CompletedProcess, run
 | 
			
		||||
from shared_libs.wp_app_config import AppConfig, logging
 | 
			
		||||
 | 
			
		||||
log_name: str = getpass.getuser()
 | 
			
		||||
if log_name == "root":
 | 
			
		||||
parser = argparse.ArgumentParser()
 | 
			
		||||
parser.add_argument("--user", required=True, help="Username of the target file system")
 | 
			
		||||
args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    from common_tools import LxTools
 | 
			
		||||
try:
 | 
			
		||||
    # Retrieve UID and GID
 | 
			
		||||
    user_info = pwd.getpwnam(args.user)
 | 
			
		||||
    uid = user_info.pw_uid  # User ID (e.g., 1000)
 | 
			
		||||
    gid = user_info.pw_gid  # Group ID (e.g., 1000)
 | 
			
		||||
except KeyError:
 | 
			
		||||
    logging.error(f"User '{args.user}' not found.", exc_info=True)
 | 
			
		||||
    exit(1)
 | 
			
		||||
 | 
			
		||||
    log_name: str = LxTools.get_username()
 | 
			
		||||
    print("replacement method applied")
 | 
			
		||||
 | 
			
		||||
keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem")
 | 
			
		||||
keyfile: Path = Path(f"/home/{args.user}/.config/wire_py/pbwgk.pem")
 | 
			
		||||
path_of_crypted_tunnel: Path = Path(f"/home/{args.user}/.config/wire_py")
 | 
			
		||||
 | 
			
		||||
if not keyfile.is_file():
 | 
			
		||||
 | 
			
		||||
    check_call(
 | 
			
		||||
    process: CompletedProcess[str] = run(
 | 
			
		||||
        [
 | 
			
		||||
            "openssl",
 | 
			
		||||
            "rsa",
 | 
			
		||||
@@ -31,21 +35,29 @@ if not keyfile.is_file():
 | 
			
		||||
            "-outform",
 | 
			
		||||
            "PEM",
 | 
			
		||||
            "-pubout",
 | 
			
		||||
        ]
 | 
			
		||||
        ],
 | 
			
		||||
        capture_output=True,
 | 
			
		||||
        text=True,
 | 
			
		||||
        check=False,
 | 
			
		||||
    )
 | 
			
		||||
    shutil.chown(keyfile, 1000, 1000)
 | 
			
		||||
 | 
			
		||||
AppConfig.TEMP_DIR2 = f"/home/{log_name}/.config/wire_py/"
 | 
			
		||||
detl: list[str] = os.listdir(AppConfig.TEMP_DIR2)
 | 
			
		||||
os.chdir(AppConfig.TEMP_DIR2)
 | 
			
		||||
detl.remove("keys")
 | 
			
		||||
detl.remove("settings")
 | 
			
		||||
if os.path.exists(f"{AppConfig.TEMP_DIR2}pbwgk.pem"):
 | 
			
		||||
    detl.remove("pbwgk.pem")
 | 
			
		||||
    for detunnels in detl:
 | 
			
		||||
        tlname2 = f"{detunnels[:-4]}.conf"
 | 
			
		||||
        extpath = f"{AppConfig.TEMP_DIR}/{tlname2}"
 | 
			
		||||
        check_call(
 | 
			
		||||
    if process.returncode == 0:
 | 
			
		||||
        logging.info("Public key generated successfully.", exc_info=True)
 | 
			
		||||
    else:
 | 
			
		||||
        logging.error(
 | 
			
		||||
            f"Error with the following code... {process.returncode}", exc_info=True
 | 
			
		||||
        )
 | 
			
		||||
    shutil.chown(keyfile, uid, gid)
 | 
			
		||||
 | 
			
		||||
if AppConfig.PUBLICKEY.exists():
 | 
			
		||||
 | 
			
		||||
    crypted__tunnel = [str(file) for file in path_of_crypted_tunnel.glob("*.dat")]
 | 
			
		||||
 | 
			
		||||
    for tunnel_path in crypted__tunnel:
 | 
			
		||||
 | 
			
		||||
        base_name = Path(tunnel_path).stem
 | 
			
		||||
 | 
			
		||||
        process: CompletedProcess[str] = run(
 | 
			
		||||
            [
 | 
			
		||||
                "openssl",
 | 
			
		||||
                "pkeyutl",
 | 
			
		||||
@@ -53,9 +65,20 @@ if os.path.exists(f"{AppConfig.TEMP_DIR2}pbwgk.pem"):
 | 
			
		||||
                "-inkey",
 | 
			
		||||
                AppConfig.SYSTEM_PATHS["pkey_path"],
 | 
			
		||||
                "-in",
 | 
			
		||||
                detunnels,
 | 
			
		||||
                tunnel_path,  # full path to the file
 | 
			
		||||
                "-out",
 | 
			
		||||
                extpath,
 | 
			
		||||
            ]
 | 
			
		||||
                f"{AppConfig.TEMP_DIR}/{base_name}.conf",
 | 
			
		||||
            ],
 | 
			
		||||
            capture_output=True,
 | 
			
		||||
            text=True,
 | 
			
		||||
            check=False,
 | 
			
		||||
        )
 | 
			
		||||
        shutil.chown(extpath, 1000, 1000)
 | 
			
		||||
        shutil.chown(f"{AppConfig.TEMP_DIR}/{base_name}.conf", uid, gid)
 | 
			
		||||
        logging.info(f"Processing of the file: {tunnel_path}", exc_info=True)
 | 
			
		||||
 | 
			
		||||
        # Output from Openssl Error
 | 
			
		||||
        if process.stderr:
 | 
			
		||||
            logging.error(
 | 
			
		||||
                f"{process.stderr} Error by [{tunnel_path}] Code: {process.returncode}",
 | 
			
		||||
                exc_info=True,
 | 
			
		||||
            )
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										112
									
								
								ssl_encrypt.py
									
									
									
									
									
								
							
							
						
						@@ -1,18 +1,33 @@
 | 
			
		||||
#!/usr/bin/python3
 | 
			
		||||
""" This Script encrypt Wireguardfiles for Wirepy users for more Security """
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
import argparse
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from subprocess import check_call
 | 
			
		||||
from wp_app_config import AppConfig
 | 
			
		||||
from common_tools import LxTools
 | 
			
		||||
import pwd
 | 
			
		||||
import shutil
 | 
			
		||||
from subprocess import CompletedProcess, run
 | 
			
		||||
from shared_libs.wp_app_config import AppConfig, logging
 | 
			
		||||
 | 
			
		||||
keyfile: Path = AppConfig.PUBLICKEY
 | 
			
		||||
parser = argparse.ArgumentParser()
 | 
			
		||||
parser.add_argument("--user", required=True, help="Username of the target file system")
 | 
			
		||||
args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    # Retrieve UID and GID
 | 
			
		||||
    user_info = pwd.getpwnam(args.user)
 | 
			
		||||
    uid = user_info.pw_uid  # User ID (e.g., 1000)
 | 
			
		||||
    gid = user_info.pw_gid  # Group ID (e.g., 1000)
 | 
			
		||||
except KeyError:
 | 
			
		||||
    logging.error(f"User '{args.user}' not found.", exc_info=True)
 | 
			
		||||
    exit(1)
 | 
			
		||||
 | 
			
		||||
keyfile: Path = Path(f"/home/{args.user}/.config/wire_py/pbwgk.pem")
 | 
			
		||||
 | 
			
		||||
target: Path = Path(f"/home/{args.user}/.config/wire_py/")
 | 
			
		||||
 | 
			
		||||
if not keyfile.is_file():
 | 
			
		||||
 | 
			
		||||
    check_call(
 | 
			
		||||
    process: CompletedProcess[str] = run(
 | 
			
		||||
        [
 | 
			
		||||
            "openssl",
 | 
			
		||||
            "rsa",
 | 
			
		||||
@@ -23,56 +38,45 @@ if not keyfile.is_file():
 | 
			
		||||
            "-outform",
 | 
			
		||||
            "PEM",
 | 
			
		||||
            "-pubout",
 | 
			
		||||
        ]
 | 
			
		||||
        ],
 | 
			
		||||
        capture_output=True,
 | 
			
		||||
        text=True,
 | 
			
		||||
        check=False,
 | 
			
		||||
    )
 | 
			
		||||
    shutil.chown(keyfile, 1000, 1000)
 | 
			
		||||
 | 
			
		||||
    if AppConfig.TEMP_DIR.exists():
 | 
			
		||||
        tl = LxTools.get_file_name(AppConfig.TEMP_DIR)
 | 
			
		||||
        CPTH: str = f"{keyfile}"
 | 
			
		||||
        CRYPTFILES: str = CPTH[:-9]
 | 
			
		||||
    # Output from Openssl Error
 | 
			
		||||
    if process.stderr:
 | 
			
		||||
        logging.error(f"{process.stderr} Code: {process.returncode}", exc_info=True)
 | 
			
		||||
 | 
			
		||||
        if keyfile.exists() and len(tl) != 0:
 | 
			
		||||
            for tunnels in tl:
 | 
			
		||||
                sourcetl: str = f"{AppConfig.TEMP_DIR}/{tunnels}"
 | 
			
		||||
                tlname: str = f"{CRYPTFILES}{tunnels[:-5]}.dat"
 | 
			
		||||
                check_call(
 | 
			
		||||
                    [
 | 
			
		||||
                        "openssl",
 | 
			
		||||
                        "pkeyutl",
 | 
			
		||||
                        "-encrypt",
 | 
			
		||||
                        "-inkey",
 | 
			
		||||
                        keyfile,
 | 
			
		||||
                        "-pubin",
 | 
			
		||||
                        "-in",
 | 
			
		||||
                        sourcetl,
 | 
			
		||||
                        "-out",
 | 
			
		||||
                        tlname,
 | 
			
		||||
                    ]
 | 
			
		||||
                )
 | 
			
		||||
    if process.returncode == 0:
 | 
			
		||||
        logging.info("Public key generated successfully.", exc_info=True)
 | 
			
		||||
 | 
			
		||||
else:
 | 
			
		||||
    shutil.chown(keyfile, uid, gid)
 | 
			
		||||
 | 
			
		||||
    if AppConfig.TEMP_DIR.exists():
 | 
			
		||||
        tl: list[str] = os.listdir(f"{AppConfig.TEMP_DIR}")
 | 
			
		||||
        CPTH: str = f"{keyfile}"
 | 
			
		||||
        CRYPTFILES: str = CPTH[:-9]
 | 
			
		||||
# any() get True when directory is not empty
 | 
			
		||||
if AppConfig.TEMP_DIR.exists() and any(AppConfig.TEMP_DIR.iterdir()):
 | 
			
		||||
    clear_files = [str(file) for file in AppConfig.TEMP_DIR.glob("*.conf")]
 | 
			
		||||
 | 
			
		||||
        if keyfile.exists() and len(tl) != 0:
 | 
			
		||||
            for tunnels in tl:
 | 
			
		||||
                sourcetl: str = f"{AppConfig.TEMP_DIR}/{tunnels}"
 | 
			
		||||
                tlname: str = f"{CRYPTFILES}{tunnels[:-5]}.dat"
 | 
			
		||||
                check_call(
 | 
			
		||||
                    [
 | 
			
		||||
                        "openssl",
 | 
			
		||||
                        "pkeyutl",
 | 
			
		||||
                        "-encrypt",
 | 
			
		||||
                        "-inkey",
 | 
			
		||||
                        keyfile,
 | 
			
		||||
                        "-pubin",
 | 
			
		||||
                        "-in",
 | 
			
		||||
                        sourcetl,
 | 
			
		||||
                        "-out",
 | 
			
		||||
                        tlname,
 | 
			
		||||
                    ]
 | 
			
		||||
                )
 | 
			
		||||
    for config_file in clear_files:
 | 
			
		||||
        base_name = Path(config_file).stem
 | 
			
		||||
        process: CompletedProcess[str] = run(
 | 
			
		||||
            [
 | 
			
		||||
                "openssl",
 | 
			
		||||
                "pkeyutl",
 | 
			
		||||
                "-encrypt",
 | 
			
		||||
                "-inkey",
 | 
			
		||||
                keyfile,
 | 
			
		||||
                "-pubin",
 | 
			
		||||
                "-in",
 | 
			
		||||
                config_file,
 | 
			
		||||
                "-out",
 | 
			
		||||
                f"{target}/{base_name}.dat",
 | 
			
		||||
            ],
 | 
			
		||||
            capture_output=True,
 | 
			
		||||
            text=True,
 | 
			
		||||
            check=False,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Output from Openssl Error
 | 
			
		||||
        if process.stderr:
 | 
			
		||||
            logging.error(process.stderr, exc_info=True)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								start_wg.py
									
									
									
									
									
								
							
							
						
						@@ -2,15 +2,23 @@
 | 
			
		||||
"""
 | 
			
		||||
 This script belongs to wirepy and is for the auto start of the tunnel
 | 
			
		||||
"""
 | 
			
		||||
import logging
 | 
			
		||||
from subprocess import CompletedProcess, run
 | 
			
		||||
from shared_libs.wp_app_config import AppConfig
 | 
			
		||||
from shared_libs.common_tools import ConfigManager, LogConfig
 | 
			
		||||
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from subprocess import check_call
 | 
			
		||||
ConfigManager.init(AppConfig.SETTINGS_FILE)
 | 
			
		||||
LogConfig.logger(ConfigManager.get("logfile"))
 | 
			
		||||
if ConfigManager.get("autostart") != "off":
 | 
			
		||||
    process: CompletedProcess[str] = run(
 | 
			
		||||
        ["nmcli", "connection", "up", ConfigManager.get("autostart")],
 | 
			
		||||
        capture_output=True,
 | 
			
		||||
        text=True,
 | 
			
		||||
        check=False,
 | 
			
		||||
    )
 | 
			
		||||
    # Output from start_wg error
 | 
			
		||||
    if process.stderr:
 | 
			
		||||
        logging.error(process.stderr, exc_info=True)
 | 
			
		||||
 | 
			
		||||
path_to_file = Path(Path.home() / ".config/wire_py/settings")
 | 
			
		||||
 | 
			
		||||
a_con = Path(path_to_file).read_text(encoding="utf-8").splitlines(keepends=True)
 | 
			
		||||
a_con = a_con[7].strip()
 | 
			
		||||
if a_con != "off":
 | 
			
		||||
    check_call(["nmcli", "connection", "up", a_con])
 | 
			
		||||
else:
 | 
			
		||||
    pass
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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,10 +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
 | 
			
		||||
							
								
								
									
										555
									
								
								wirepy.py
									
									
									
									
									
								
							
							
						
						@@ -2,30 +2,28 @@
 | 
			
		||||
"""
 | 
			
		||||
this script is a simple GUI for managing Wireguard Tunnels
 | 
			
		||||
"""
 | 
			
		||||
import os
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
import shutil
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
import tkinter as tk
 | 
			
		||||
import webbrowser
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from subprocess import check_call
 | 
			
		||||
from subprocess import CompletedProcess, run
 | 
			
		||||
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,
 | 
			
		||||
    ThemeManager,
 | 
			
		||||
    Create,
 | 
			
		||||
    GiteaUpdate,
 | 
			
		||||
    Tunnel,
 | 
			
		||||
    Tooltip,
 | 
			
		||||
    LxTools,
 | 
			
		||||
)
 | 
			
		||||
from wp_app_config import AppConfig, Msg
 | 
			
		||||
 | 
			
		||||
Create.dir_and_files()
 | 
			
		||||
Create.make_dir()
 | 
			
		||||
Create.decrypt()
 | 
			
		||||
from shared_libs.wp_app_config import AppConfig, Msg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Wirepy(tk.Tk):
 | 
			
		||||
@@ -169,7 +167,11 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        self.settings.add_command(
 | 
			
		||||
            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
 | 
			
		||||
        self.about_btn = ttk.Button(
 | 
			
		||||
            self.menu_frame, text=_("About"), style="Toolbutton", command=self.about
 | 
			
		||||
@@ -177,7 +179,7 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        self.about_btn.grid(column=2, columnspan=2, row=0)
 | 
			
		||||
        self.readme = tk.Menu(self)
 | 
			
		||||
 | 
			
		||||
        self.a = Tunnel.active()
 | 
			
		||||
        self.a = Tunnel.get_active()
 | 
			
		||||
 | 
			
		||||
        # Label Frame 1
 | 
			
		||||
        self.lb_frame_btn_lbox = ttk.Frame(self)
 | 
			
		||||
@@ -239,15 +241,19 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        self.l_box.configure(yscrollcommand=self.scrollbar.set)
 | 
			
		||||
 | 
			
		||||
        # Tunnel List
 | 
			
		||||
        self.tl = LxTools.get_file_name(AppConfig.TEMP_DIR)
 | 
			
		||||
        for tunnels in self.tl:
 | 
			
		||||
        self.tl = Tunnel.parse_files_to_dictionary(directory=AppConfig.TEMP_DIR)
 | 
			
		||||
        LxTools.clean_files(AppConfig.TEMP_DIR, file=None)
 | 
			
		||||
        AppConfig.ensure_directories()
 | 
			
		||||
 | 
			
		||||
        for tunnels, values in self.tl.items():
 | 
			
		||||
            self.l_box.insert("end", tunnels)
 | 
			
		||||
            self.l_box.update()
 | 
			
		||||
 | 
			
		||||
        # Button Vpn
 | 
			
		||||
        if self.a != "":
 | 
			
		||||
            self.stop()
 | 
			
		||||
            data = self.handle_tunnel_data(self.a)
 | 
			
		||||
            self.handle_tunnel_data(self.a, self.tl)
 | 
			
		||||
            self.show_data()
 | 
			
		||||
        else:
 | 
			
		||||
            self.start()
 | 
			
		||||
 | 
			
		||||
@@ -288,14 +294,7 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        self.btn_exp = ttk.Button(
 | 
			
		||||
            self.lb_frame_btn_lbox,
 | 
			
		||||
            image=self.exp_pic,
 | 
			
		||||
            command=lambda: Tunnel.export(
 | 
			
		||||
                AppConfig.IMAGE_PATHS["icon_info"],
 | 
			
		||||
                AppConfig.IMAGE_PATHS["icon_vpn"],
 | 
			
		||||
                AppConfig.IMAGE_PATHS["icon_error"],
 | 
			
		||||
                AppConfig.IMAGE_PATHS["icon_msg"],
 | 
			
		||||
                Msg.STR["sel_tl"],
 | 
			
		||||
                Msg.STR["tl_first"],
 | 
			
		||||
            ),
 | 
			
		||||
            command=lambda: Tunnel.export(),
 | 
			
		||||
            padding=0,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@@ -403,7 +402,7 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
 | 
			
		||||
    # Update the labels based on the result
 | 
			
		||||
    def update_ui_for_update(self, res):
 | 
			
		||||
        """Update UI elements based on update check result"""
 | 
			
		||||
        """Update UI elements based on an update check result"""
 | 
			
		||||
        # First, remove the update button if it exists to avoid conflicts
 | 
			
		||||
        if hasattr(self, "update_btn"):
 | 
			
		||||
            self.update_btn.grid_forget()
 | 
			
		||||
@@ -415,13 +414,13 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
            self.update_tooltip.set(_("Updates you have disabled"))
 | 
			
		||||
            # Clear the foreground color as requested
 | 
			
		||||
            self.update_foreground.set("")
 | 
			
		||||
            # Set tooltip for the label
 | 
			
		||||
            # Set the tooltip for the label
 | 
			
		||||
            Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state)
 | 
			
		||||
 | 
			
		||||
        elif res == "No Internet Connection!":
 | 
			
		||||
            self.update_label.set(_("No Server Connection!"))
 | 
			
		||||
            self.update_foreground.set("red")
 | 
			
		||||
            # Set tooltip for "No Server Connection"
 | 
			
		||||
            # Set the tooltip for "No Server Connection"
 | 
			
		||||
            Tooltip(
 | 
			
		||||
                self.updates_lb,
 | 
			
		||||
                _("Could not connect to update server"),
 | 
			
		||||
@@ -432,7 +431,7 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
            self.update_label.set(_("No Updates"))
 | 
			
		||||
            self.update_tooltip.set(_("Congratulations! Wire-Py is up to date"))
 | 
			
		||||
            self.update_foreground.set("")
 | 
			
		||||
            # Set tooltip for the label
 | 
			
		||||
            # Set the tooltip for the label
 | 
			
		||||
            Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state)
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
@@ -454,12 +453,7 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
            self.download.add_command(
 | 
			
		||||
                label=_("Download"),
 | 
			
		||||
                command=lambda: GiteaUpdate.download(
 | 
			
		||||
                    f"{AppConfig.DOWNLOAD_URL}/{res}.zip",
 | 
			
		||||
                    res,
 | 
			
		||||
                    AppConfig.IMAGE_PATHS["icon_info"],
 | 
			
		||||
                    AppConfig.IMAGE_PATHS["icon_vpn"],
 | 
			
		||||
                    AppConfig.IMAGE_PATHS["icon_error"],
 | 
			
		||||
                    AppConfig.IMAGE_PATHS["icon_msg"],
 | 
			
		||||
                    f"{AppConfig.DOWNLOAD_URL}/{res}.zip", res
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
@@ -469,7 +463,7 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        a tk.Toplevel window
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        def link_btn() -> str | None:
 | 
			
		||||
        def link_btn() -> None:
 | 
			
		||||
            webbrowser.open("https://git.ilunix.de/punix/Wire-Py")
 | 
			
		||||
 | 
			
		||||
        msg_t = _(
 | 
			
		||||
@@ -508,7 +502,7 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
                    AppConfig.UPDATE_URL, AppConfig.VERSION, "on"
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                # Make sure UI is updated regardless of previous state
 | 
			
		||||
                # Make sure the UI is updated regardless of the previous state
 | 
			
		||||
                if hasattr(self, "update_btn"):
 | 
			
		||||
                    self.update_btn.grid_forget()
 | 
			
		||||
                if hasattr(self, "updates_lb"):
 | 
			
		||||
@@ -517,7 +511,7 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
                # Now update the UI with the fresh result
 | 
			
		||||
                self.update_ui_for_update(res)
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                print(f"Error checking for updates: {e}")
 | 
			
		||||
                logging.error(f"Error checking for updates: {e}", exc_info=True)
 | 
			
		||||
                # Fallback to a default message if there's an error
 | 
			
		||||
                self.update_ui_for_update("No Internet Connection!")
 | 
			
		||||
 | 
			
		||||
@@ -532,7 +526,13 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
            self.tooltip_label.set(_("Enable Tooltips"))
 | 
			
		||||
 | 
			
		||||
    def tooltips_toggle(self):
 | 
			
		||||
        """Toggles tooltips on/off and updates the menu label"""
 | 
			
		||||
        """
 | 
			
		||||
        Toggles the visibility of tooltips (on/off) and updates
 | 
			
		||||
        the corresponding menu label. Inverts the current tooltip state
 | 
			
		||||
        (`self.tooltip_state`), saves the new value in the configuration,
 | 
			
		||||
        and applies the change immediately. Updates the menu entry's label to
 | 
			
		||||
        reflect the new tooltip status (e.g., "Tooltips: On" or "Tooltips: Off").
 | 
			
		||||
        """
 | 
			
		||||
        # Toggle the boolean state
 | 
			
		||||
        new_bool_state = not self.tooltip_state.get()
 | 
			
		||||
        # Save the converted value in the configuration
 | 
			
		||||
@@ -547,8 +547,8 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        # This assumes it's the third item (index 2) in your menu
 | 
			
		||||
        self.settings.entryconfigure(1, label=self.tooltip_label.get())
 | 
			
		||||
 | 
			
		||||
    def update_theme_label(self) -> str:
 | 
			
		||||
        """Update the theme label based on current theme"""
 | 
			
		||||
    def update_theme_label(self) -> None:
 | 
			
		||||
        """Update the theme label based on the current theme"""
 | 
			
		||||
        current_theme = ConfigManager.get("theme")
 | 
			
		||||
        if current_theme == "light":
 | 
			
		||||
            self.theme_label.set(_("Dark"))
 | 
			
		||||
@@ -577,28 +577,11 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        )
 | 
			
		||||
        self.btn_stst.grid(column=0, row=0, padx=5, pady=8)
 | 
			
		||||
 | 
			
		||||
        tl = LxTools.get_file_name(AppConfig.TEMP_DIR)
 | 
			
		||||
        if len(self.tl) == 0:
 | 
			
		||||
        if self.l_box.size() == 0:
 | 
			
		||||
            Tooltip(self.btn_stst, Msg.TTIP["empty_list"], self.tooltip_state)
 | 
			
		||||
        else:
 | 
			
		||||
            Tooltip(self.btn_stst, Msg.TTIP["start_tl"], self.tooltip_state)
 | 
			
		||||
 | 
			
		||||
    def handle_tunnel_data(self, tunnel_name: str) -> tuple[str, str, str, str | None]:
 | 
			
		||||
        """_summary_
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            tunnel_name (str): name of a tunnel
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            tuple[str, str]: tuple with tunnel data
 | 
			
		||||
        """
 | 
			
		||||
        wg_read = f"/tmp/tlecdcwg/{tunnel_name}.conf"
 | 
			
		||||
        with open(wg_read, "r", encoding="utf-8") as file:
 | 
			
		||||
            data = Tunnel.con_to_dict(file)
 | 
			
		||||
            self.init_and_report(data)
 | 
			
		||||
            self.show_data()
 | 
			
		||||
            return data
 | 
			
		||||
 | 
			
		||||
    def color_label(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        View activ Tunnel in the color green or yellow
 | 
			
		||||
@@ -642,149 +625,133 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
    def import_sl(self) -> None:
 | 
			
		||||
        """validity check of wireguard config files"""
 | 
			
		||||
 | 
			
		||||
        Create.dir_and_files()
 | 
			
		||||
        AppConfig.ensure_directories()
 | 
			
		||||
        try:
 | 
			
		||||
            filepath = filedialog.askopenfilename(
 | 
			
		||||
                initialdir=f"{Path.home()}",
 | 
			
		||||
                title=_("Select Wireguard config File"),
 | 
			
		||||
                filetypes=[(_("WG config files"), "*.conf")],
 | 
			
		||||
                title="Select Wireguard config File",
 | 
			
		||||
                filetypes=[("WG config files", "*.conf")],
 | 
			
		||||
            )
 | 
			
		||||
            data_import, key_name = Tunnel.parse_files_to_dictionary(filepath=filepath)
 | 
			
		||||
 | 
			
		||||
            # Überprüfe, ob der Benutzer den Dialog abgebrochen hat
 | 
			
		||||
            if not filepath:
 | 
			
		||||
                print("File import: abort by user...")
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            with open(filepath, "r", encoding="utf-8") as file:
 | 
			
		||||
                read = file.read()
 | 
			
		||||
 | 
			
		||||
            path_split = filepath.split("/")
 | 
			
		||||
            path_split1 = path_split[-1]
 | 
			
		||||
 | 
			
		||||
            if (
 | 
			
		||||
                "PrivateKey = " in read
 | 
			
		||||
                and "PublicKey = " in read
 | 
			
		||||
                and "Endpoint =" in read
 | 
			
		||||
            ):
 | 
			
		||||
                with open(filepath, "r", encoding="utf-8") as file:
 | 
			
		||||
                    key = Tunnel.con_to_dict(file)
 | 
			
		||||
                pre_key = key[3]
 | 
			
		||||
 | 
			
		||||
                if len(pre_key) != 0:
 | 
			
		||||
                    p_key = AppConfig.KEYS_FILE.read_text(encoding="utf-8")
 | 
			
		||||
 | 
			
		||||
                    if pre_key in p_key or f"{pre_key}\n" in p_key:
 | 
			
		||||
                        LxTools.msg_window(
 | 
			
		||||
                            AppConfig.IMAGE_PATHS["icon_error"],
 | 
			
		||||
                            AppConfig.IMAGE_PATHS["icon_msg"],
 | 
			
		||||
                            Msg.STR["imp_err"],
 | 
			
		||||
                            Msg.STR["tl_exist"],
 | 
			
		||||
                        )
 | 
			
		||||
                    else:
 | 
			
		||||
                        with open(
 | 
			
		||||
                            AppConfig.KEYS_FILE, "a", encoding="utf-8"
 | 
			
		||||
                        ) as keyfile:
 | 
			
		||||
                            keyfile.write(f"{pre_key}\r")
 | 
			
		||||
 | 
			
		||||
                        if len(path_split1) > 17:
 | 
			
		||||
                            p1 = shutil.copy(filepath, AppConfig.TEMP_DIR)
 | 
			
		||||
                            path_split = path_split1[len(path_split1) - 17 :]
 | 
			
		||||
                            os.rename(p1, f"{AppConfig.TEMP_DIR}/{path_split}")
 | 
			
		||||
                            new_conf = f"{AppConfig.TEMP_DIR}/{path_split}"
 | 
			
		||||
 | 
			
		||||
                            if self.a != "":
 | 
			
		||||
                                check_call(["nmcli", "connection", "down", self.a])
 | 
			
		||||
                                self.reset_fields()
 | 
			
		||||
 | 
			
		||||
                            subprocess.check_output(
 | 
			
		||||
                                [
 | 
			
		||||
                                    "nmcli",
 | 
			
		||||
                                    "connection",
 | 
			
		||||
                                    "import",
 | 
			
		||||
                                    "type",
 | 
			
		||||
                                    "wireguard",
 | 
			
		||||
                                    "file",
 | 
			
		||||
                                    new_conf,
 | 
			
		||||
                                ],
 | 
			
		||||
                                text=True,
 | 
			
		||||
                            )
 | 
			
		||||
                            Create.encrypt()
 | 
			
		||||
                        else:
 | 
			
		||||
                            shutil.copy(filepath, f"{AppConfig.TEMP_DIR}/")
 | 
			
		||||
 | 
			
		||||
                            if self.a != "":
 | 
			
		||||
                                check_call(["nmcli", "connection", "down", self.a])
 | 
			
		||||
                                self.reset_fields()
 | 
			
		||||
 | 
			
		||||
                            subprocess.check_output(
 | 
			
		||||
                                [
 | 
			
		||||
                                    "nmcli",
 | 
			
		||||
                                    "connection",
 | 
			
		||||
                                    "import",
 | 
			
		||||
                                    "type",
 | 
			
		||||
                                    "wireguard",
 | 
			
		||||
                                    "file",
 | 
			
		||||
                                    filepath,
 | 
			
		||||
                                ],
 | 
			
		||||
                                text=True,
 | 
			
		||||
                            )
 | 
			
		||||
                            Create.encrypt()
 | 
			
		||||
 | 
			
		||||
                        self.str_var.set("")
 | 
			
		||||
                        self.a = Tunnel.active()
 | 
			
		||||
                        self.l_box.insert(0, self.a)
 | 
			
		||||
                        self.wg_autostart.configure(state="normal")
 | 
			
		||||
                        self.l_box.selection_clear(0, tk.END)
 | 
			
		||||
                        self.l_box.update()
 | 
			
		||||
                        self.l_box.selection_set(0)
 | 
			
		||||
 | 
			
		||||
                        Tooltip(
 | 
			
		||||
                            self.wg_autostart,
 | 
			
		||||
                            Msg.TTIP["autostart"],
 | 
			
		||||
                            self.tooltip_state,
 | 
			
		||||
                            x_offset=-10,
 | 
			
		||||
                            y_offset=-40,
 | 
			
		||||
                        )
 | 
			
		||||
                        Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], self.tooltip_state)
 | 
			
		||||
                        Tooltip(self.btn_exp, Msg.TTIP["export_tl"], self.tooltip_state)
 | 
			
		||||
                        Tooltip(
 | 
			
		||||
                            self.btn_rename, Msg.TTIP["rename_tl"], self.tooltip_state
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
                        self.lb_rename.insert(0, "Max. 12 characters!")
 | 
			
		||||
                        self.str_var = tk.StringVar()
 | 
			
		||||
                        self.str_var.set(self.a)
 | 
			
		||||
                        self.color_label()
 | 
			
		||||
                        self.stop()
 | 
			
		||||
                        data = self.handle_tunnel_data(self.a)
 | 
			
		||||
                        check_call(
 | 
			
		||||
                            [
 | 
			
		||||
                                "nmcli",
 | 
			
		||||
                                "con",
 | 
			
		||||
                                "mod",
 | 
			
		||||
                                self.a,
 | 
			
		||||
                                "connection.autoconnect",
 | 
			
		||||
                                "no",
 | 
			
		||||
                            ]
 | 
			
		||||
                        )
 | 
			
		||||
            elif ("PrivateKey = " in read) and ("Endpoint = " in read):
 | 
			
		||||
                pass
 | 
			
		||||
            else:
 | 
			
		||||
            if CryptoUtil.find_key(f"{data_import[key_name]["PrivateKey"]}="):
 | 
			
		||||
                LxTools.msg_window(
 | 
			
		||||
                    AppConfig.IMAGE_PATHS["icon_error"],
 | 
			
		||||
                    AppConfig.IMAGE_PATHS["icon_msg"],
 | 
			
		||||
                    Msg.STR["imp_err"],
 | 
			
		||||
                    Msg.STR["no_valid_file"],
 | 
			
		||||
                    Msg.STR["tl_exist"],
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            elif not CryptoUtil.is_valid_base64(
 | 
			
		||||
                f"{data_import[key_name]["PrivateKey"]}="
 | 
			
		||||
            ):  # 2. Second check: Is it valid Base64?
 | 
			
		||||
                LxTools.msg_window(
 | 
			
		||||
                    AppConfig.IMAGE_PATHS["icon_error"],
 | 
			
		||||
                    AppConfig.IMAGE_PATHS["icon_msg"],
 | 
			
		||||
                    Msg.STR["imp_err"],
 | 
			
		||||
                    Msg.STR["invalid_base64"],
 | 
			
		||||
                )
 | 
			
		||||
            else:
 | 
			
		||||
                filepath = Path(filepath)
 | 
			
		||||
                # Shorten the tunnel name to the maximum allowed length if it exceeds 12 characters.
 | 
			
		||||
                original_name = filepath.name
 | 
			
		||||
                truncated_name = (
 | 
			
		||||
                    original_name[-17:] if len(original_name) > 17 else original_name
 | 
			
		||||
                )
 | 
			
		||||
                import_file = shutil.copy2(
 | 
			
		||||
                    filepath, AppConfig.TEMP_DIR / truncated_name
 | 
			
		||||
                )
 | 
			
		||||
                import_file = Path(import_file)
 | 
			
		||||
 | 
			
		||||
                del data_import[key_name]["PrivateKey"]
 | 
			
		||||
                self.tl.update(data_import)
 | 
			
		||||
 | 
			
		||||
                if self.a != "":
 | 
			
		||||
                    process: CompletedProcess[str] = run(
 | 
			
		||||
                        ["nmcli", "connection", "down", self.a],
 | 
			
		||||
                        capture_output=True,
 | 
			
		||||
                        text=True,
 | 
			
		||||
                        check=False,
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    if process.stderr:
 | 
			
		||||
                        logging.error(f"{process.stderr}: Code {process.returncode}")
 | 
			
		||||
 | 
			
		||||
                    self.reset_fields()
 | 
			
		||||
 | 
			
		||||
                process: CompletedProcess[str] = run(
 | 
			
		||||
                    [
 | 
			
		||||
                        "nmcli",
 | 
			
		||||
                        "connection",
 | 
			
		||||
                        "import",
 | 
			
		||||
                        "type",
 | 
			
		||||
                        "wireguard",
 | 
			
		||||
                        "file",
 | 
			
		||||
                        import_file,
 | 
			
		||||
                    ],
 | 
			
		||||
                    capture_output=True,
 | 
			
		||||
                    text=True,
 | 
			
		||||
                    check=False,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                if process.stderr:
 | 
			
		||||
                    logging.error(
 | 
			
		||||
                        f"{process.stderr} Code: {process.returncode}", exc_info=True
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                CryptoUtil.encrypt(getpass.getuser())
 | 
			
		||||
                LxTools.clean_files(AppConfig.TEMP_DIR, file=None)
 | 
			
		||||
                AppConfig.ensure_directories()
 | 
			
		||||
                self.str_var.set("")
 | 
			
		||||
                self.a = Tunnel.get_active()
 | 
			
		||||
                self.l_box.insert(0, self.a)
 | 
			
		||||
                self.wg_autostart.configure(state="normal")
 | 
			
		||||
                self.l_box.selection_clear(0, tk.END)
 | 
			
		||||
                self.l_box.update()
 | 
			
		||||
                self.l_box.selection_set(0)
 | 
			
		||||
 | 
			
		||||
                Tooltip(
 | 
			
		||||
                    self.wg_autostart,
 | 
			
		||||
                    Msg.TTIP["autostart"],
 | 
			
		||||
                    self.tooltip_state,
 | 
			
		||||
                    x_offset=-10,
 | 
			
		||||
                    y_offset=-40,
 | 
			
		||||
                )
 | 
			
		||||
                Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], self.tooltip_state)
 | 
			
		||||
                Tooltip(self.btn_exp, Msg.TTIP["export_tl"], self.tooltip_state)
 | 
			
		||||
                Tooltip(self.btn_rename, Msg.TTIP["rename_tl"], self.tooltip_state)
 | 
			
		||||
 | 
			
		||||
                self.lb_rename.insert(0, "Max. 12 characters!")
 | 
			
		||||
                self.str_var = tk.StringVar()
 | 
			
		||||
                self.str_var.set(self.a)
 | 
			
		||||
                self.color_label()
 | 
			
		||||
                self.stop()
 | 
			
		||||
                self.handle_tunnel_data(self.a, self.tl)
 | 
			
		||||
                self.show_data()
 | 
			
		||||
                process: CompletedProcess[str] = run(
 | 
			
		||||
                    ["nmcli", "con", "mod", self.a, "connection.autoconnect", "no"],
 | 
			
		||||
                    capture_output=True,
 | 
			
		||||
                    text=True,
 | 
			
		||||
                    check=False,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                if process.stderr:
 | 
			
		||||
                    logging.error(process.stderr, exc_info=True)
 | 
			
		||||
 | 
			
		||||
                if process.returncode == 0:
 | 
			
		||||
                    print(f">> {import_file.stem} << autostart is disabled by default")
 | 
			
		||||
 | 
			
		||||
        except UnboundLocalError:
 | 
			
		||||
            LxTools.msg_window(
 | 
			
		||||
                AppConfig.IMAGE_PATHS["icon_error"],
 | 
			
		||||
                AppConfig.IMAGE_PATHS["icon_msg"],
 | 
			
		||||
                Msg.STR["imp_err"],
 | 
			
		||||
                Msg.STR["no_valid_file"],
 | 
			
		||||
            )
 | 
			
		||||
        except (IsADirectoryError, TypeError, FileNotFoundError):
 | 
			
		||||
            print("File import: abort by user...")
 | 
			
		||||
        except EOFError as e:
 | 
			
		||||
            print(e)
 | 
			
		||||
        except TypeError:
 | 
			
		||||
            print("File import: abort by user...")
 | 
			
		||||
        except FileNotFoundError:
 | 
			
		||||
            print("File import: abort by user...")
 | 
			
		||||
        except subprocess.CalledProcessError:
 | 
			
		||||
            print("Tunnel exist!")
 | 
			
		||||
 | 
			
		||||
    def delete(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
@@ -793,35 +760,27 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        try:
 | 
			
		||||
            self.select_tunnel = self.l_box.curselection()
 | 
			
		||||
            select_tl = self.l_box.get(self.select_tunnel[0])
 | 
			
		||||
            with open(
 | 
			
		||||
                f"/tmp/tlecdcwg/{select_tl}.conf", "r+", encoding="utf-8"
 | 
			
		||||
            ) as file2:
 | 
			
		||||
                key = Tunnel.con_to_dict(file2)
 | 
			
		||||
                pre_key = key[3]
 | 
			
		||||
            check_call(["nmcli", "connection", "delete", select_tl])
 | 
			
		||||
 | 
			
		||||
            process: CompletedProcess[str] = run(
 | 
			
		||||
                ["nmcli", "connection", "delete", select_tl],
 | 
			
		||||
                capture_output=True,
 | 
			
		||||
                text=True,
 | 
			
		||||
                check=False,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            if process.stderr:
 | 
			
		||||
                logging.error(
 | 
			
		||||
                    f"{process.stderr} Code: {process.returncode}", exc_info=True
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            self.l_box.delete(self.select_tunnel[0])
 | 
			
		||||
            with open(AppConfig.SETTINGS_FILE, "r", encoding="utf-8") as set_f6:
 | 
			
		||||
                lines6 = set_f6.readlines()
 | 
			
		||||
                if select_tl == lines6[7].strip() and "off\n" not in lines6[7].strip():
 | 
			
		||||
                    lines6[7] = "off\n"
 | 
			
		||||
                    with open(AppConfig.SETTINGS_FILE, "w", encoding="utf-8") as set_f7:
 | 
			
		||||
                        set_f7.writelines(lines6)
 | 
			
		||||
                    self.selected_option.set(0)
 | 
			
		||||
                    self.autoconnect_var.set(_("no Autoconnect"))
 | 
			
		||||
            is_encrypt = Path.home() / f".config/wire_py/{select_tl}.dat"
 | 
			
		||||
            if is_encrypt.is_file():
 | 
			
		||||
                Path.unlink(f"{Path.home()}/.config/wire_py/{select_tl}.dat")
 | 
			
		||||
            Path.unlink(f"/tmp/tlecdcwg/{select_tl}.conf")
 | 
			
		||||
            with open(AppConfig.KEYS_FILE, "r", encoding="utf-8") as readfile:
 | 
			
		||||
                with open(
 | 
			
		||||
                    f"{Path.home()}/.config/wire_py/keys2", "w", encoding="utf-8"
 | 
			
		||||
                ) as writefile:
 | 
			
		||||
                    for line in readfile:
 | 
			
		||||
                        if pre_key not in line.strip("\n"):
 | 
			
		||||
                            writefile.write(line)
 | 
			
		||||
            file_one = Path(f"{Path.home()}/.config/wire_py/keys2")
 | 
			
		||||
            file_two = file_one.with_name("keys")
 | 
			
		||||
            file_one.replace(file_two)
 | 
			
		||||
            Path.unlink(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat")
 | 
			
		||||
 | 
			
		||||
            if select_tl == ConfigManager.get("autostart"):
 | 
			
		||||
                ConfigManager.set("autostart", "off")
 | 
			
		||||
                self.selected_option.set(0)
 | 
			
		||||
                self.autoconnect_var.set(_("no Autoconnect"))
 | 
			
		||||
 | 
			
		||||
            self.wg_autostart.configure(state="disabled")
 | 
			
		||||
 | 
			
		||||
            # for disabling checkbox when Listbox empty
 | 
			
		||||
@@ -871,7 +830,7 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        """
 | 
			
		||||
        checkbox for enable autostart Tunnel
 | 
			
		||||
        """
 | 
			
		||||
        Create.files_for_autostart()
 | 
			
		||||
        AppConfig.get_autostart_content()
 | 
			
		||||
        if self.l_box.size() != 0:
 | 
			
		||||
            self.wg_autostart.configure(state="normal")
 | 
			
		||||
            self.lb_rename.config(state="normal")
 | 
			
		||||
@@ -884,22 +843,15 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        Set (on), the selected tunnel is displayed in the label.
 | 
			
		||||
        At (off) the label is first emptied then filled with No Autoconnect
 | 
			
		||||
        """
 | 
			
		||||
        lines = (
 | 
			
		||||
            Path(AppConfig.SETTINGS_FILE)
 | 
			
		||||
            .read_text(encoding="utf-8")
 | 
			
		||||
            .splitlines(keepends=True)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if lines[7] != "off\n":
 | 
			
		||||
            print(f"{lines[7]} starts automatically when the system starts.")
 | 
			
		||||
        if ConfigManager.get("autostart") != "off":
 | 
			
		||||
            self.selected_option.set(1)
 | 
			
		||||
            self.autoconnect_var.set("")
 | 
			
		||||
            self.auto_con = lines[7]
 | 
			
		||||
            self.auto_con = ConfigManager.get("autostart")
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            self.selected_option.set(0)
 | 
			
		||||
            self.auto_con = _("no Autoconnect")
 | 
			
		||||
            print("Autostart disabled.")
 | 
			
		||||
        self.autoconnect_var.set("")
 | 
			
		||||
        self.autoconnect_var = tk.StringVar()
 | 
			
		||||
        self.autoconnect_var.set(self.auto_con)
 | 
			
		||||
@@ -929,31 +881,13 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
            select_tl = self.l_box.get(select_tunnel[0])
 | 
			
		||||
 | 
			
		||||
            if self.selected_option.get() == 0:
 | 
			
		||||
                lines = (
 | 
			
		||||
                    Path(AppConfig.SETTINGS_FILE)
 | 
			
		||||
                    .read_text(encoding="utf-8")
 | 
			
		||||
                    .splitlines(keepends=True)
 | 
			
		||||
                )
 | 
			
		||||
                lines[7] = "off\n"
 | 
			
		||||
                Path(AppConfig.SETTINGS_FILE).write_text(
 | 
			
		||||
                    "".join(lines), encoding="utf-8"
 | 
			
		||||
                )
 | 
			
		||||
                ConfigManager.set("autostart", "off")
 | 
			
		||||
 | 
			
		||||
                tl = LxTools.get_file_name(AppConfig.TEMP_DIR)
 | 
			
		||||
 | 
			
		||||
                if len(tl) == 0:
 | 
			
		||||
                if self.l_box.size() == 0:
 | 
			
		||||
                    self.wg_autostart.configure(state="disabled")
 | 
			
		||||
 | 
			
		||||
            if self.selected_option.get() >= 1:
 | 
			
		||||
                lines = (
 | 
			
		||||
                    Path(AppConfig.SETTINGS_FILE)
 | 
			
		||||
                    .read_text(encoding="utf-8")
 | 
			
		||||
                    .splitlines(keepends=True)
 | 
			
		||||
                )
 | 
			
		||||
                lines[7] = select_tl
 | 
			
		||||
                Path(AppConfig.SETTINGS_FILE).write_text(
 | 
			
		||||
                    "".join(lines), encoding="utf-8"
 | 
			
		||||
                )
 | 
			
		||||
                ConfigManager.set("autostart", select_tl)
 | 
			
		||||
 | 
			
		||||
        except IndexError:
 | 
			
		||||
            self.selected_option.set(1)
 | 
			
		||||
@@ -962,9 +896,12 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
 | 
			
		||||
    def tl_rename(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        method to rename a tunnel
 | 
			
		||||
        Method to rename a tunnel. Validates input for length,
 | 
			
		||||
        special characters, and duplicate names,
 | 
			
		||||
        performs the renaming via `nmcli` if valid, updates
 | 
			
		||||
        the configuration file in the directory,
 | 
			
		||||
        and adjusts UI elements such as listboxes and labels.
 | 
			
		||||
        """
 | 
			
		||||
        name_of_file = LxTools.get_file_name(AppConfig.TEMP_DIR)
 | 
			
		||||
        special_characters = ["\\", "/", "{", "}", " "]
 | 
			
		||||
 | 
			
		||||
        if len(self.lb_rename.get()) > 12:
 | 
			
		||||
@@ -994,7 +931,9 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
                Msg.STR["false_signs"],
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        elif self.lb_rename.get() in name_of_file:
 | 
			
		||||
        elif self.lb_rename.get() in [
 | 
			
		||||
            file.stem for file in AppConfig.CONFIG_DIR.glob("*.dat")
 | 
			
		||||
        ]:
 | 
			
		||||
 | 
			
		||||
            LxTools.msg_window(
 | 
			
		||||
                AppConfig.IMAGE_PATHS["icon_info"],
 | 
			
		||||
@@ -1010,7 +949,7 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
                select_tl = self.l_box.get(self.select_tunnel[0])
 | 
			
		||||
 | 
			
		||||
                # nmcli connection modify old connection.id iphone
 | 
			
		||||
                subprocess.check_output(
 | 
			
		||||
                process: CompletedProcess[str] = run(
 | 
			
		||||
                    [
 | 
			
		||||
                        "nmcli",
 | 
			
		||||
                        "connection",
 | 
			
		||||
@@ -1019,30 +958,27 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
                        "connection.id",
 | 
			
		||||
                        self.lb_rename.get(),
 | 
			
		||||
                    ],
 | 
			
		||||
                    capture_output=True,
 | 
			
		||||
                    text=True,
 | 
			
		||||
                    check=False,
 | 
			
		||||
                )
 | 
			
		||||
                source = Path(f"/tmp/tlecdcwg/{select_tl}.conf")
 | 
			
		||||
                destination = source.with_name(f"{self.lb_rename.get()}.conf")
 | 
			
		||||
                source.replace(destination)
 | 
			
		||||
                Path.unlink(f"{Path.home()}/.config/wire_py/{select_tl}.dat")
 | 
			
		||||
                if process.stderr:
 | 
			
		||||
                    logging.error(
 | 
			
		||||
                        f"{process.stderr} Code: {process.returncode}", exc_info=True
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                source = Path(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat")
 | 
			
		||||
                destination = AppConfig.CONFIG_DIR / f"{self.lb_rename.get()}.dat"
 | 
			
		||||
                source.replace(destination)
 | 
			
		||||
                self.tl[self.lb_rename.get()] = self.tl.pop(select_tl)
 | 
			
		||||
                if select_tl == ConfigManager.get("autostart"):
 | 
			
		||||
                    ConfigManager.set("autostart", self.lb_rename.get())
 | 
			
		||||
                    self.autoconnect_var.set(value=self.lb_rename.get())
 | 
			
		||||
                self.l_box.delete(self.select_tunnel[0])
 | 
			
		||||
                self.l_box.insert("end", self.lb_rename.get())
 | 
			
		||||
                self.l_box.update()
 | 
			
		||||
                new_a_connect = self.lb_rename.get()
 | 
			
		||||
                self.lb_rename.delete(0, tk.END)
 | 
			
		||||
 | 
			
		||||
                with open(AppConfig.SETTINGS_FILE, "r", encoding="utf-8") as set_f5:
 | 
			
		||||
                    lines5 = set_f5.readlines()
 | 
			
		||||
                if select_tl == lines5[7].strip() and "off\n" not in lines5[7].strip():
 | 
			
		||||
                    lines5[7] = new_a_connect
 | 
			
		||||
                    with open(
 | 
			
		||||
                        AppConfig.SETTINGS_FILE, "w", encoding="utf-8"
 | 
			
		||||
                    ) as theme_set5:
 | 
			
		||||
                        theme_set5.writelines(lines5)
 | 
			
		||||
                    self.autoconnect_var.set(value=new_a_connect)
 | 
			
		||||
                self.update_connection_display()
 | 
			
		||||
                Create.encrypt()
 | 
			
		||||
 | 
			
		||||
            except IndexError:
 | 
			
		||||
 | 
			
		||||
@@ -1053,28 +989,32 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
                    Msg.STR["sel_list"],
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            except subprocess.CalledProcessError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            except EOFError as e:
 | 
			
		||||
                print(e)
 | 
			
		||||
                logging.error(e, exc_info=True)
 | 
			
		||||
 | 
			
		||||
    def init_and_report(self, data=None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Displays the value address, DNS and peer in the labels
 | 
			
		||||
        or empty it again
 | 
			
		||||
    def handle_tunnel_data(self, active=None, data=None) -> None:
 | 
			
		||||
        """Processes tunnel data from an active connection and updates
 | 
			
		||||
        UI elements like labels with information about address, DNS, and endpoint.
 | 
			
		||||
        """
 | 
			
		||||
        tunnel = active
 | 
			
		||||
        values = data[tunnel]
 | 
			
		||||
        # Address Label
 | 
			
		||||
        self.add = tk.StringVar()
 | 
			
		||||
        self.add.set(f"{_("Address:    ")}{data[0]}")
 | 
			
		||||
        self.add.set(f"Address:  {values['Address']}")
 | 
			
		||||
        self.DNS = tk.StringVar()
 | 
			
		||||
        self.DNS.set(f"       DNS:   {data[1]}")
 | 
			
		||||
        self.DNS.set(f"      DNS:   {values['DNS']}")
 | 
			
		||||
        self.enp = tk.StringVar()
 | 
			
		||||
        self.enp.set(f"{_("Endpoint:    ")}{data[2]}")
 | 
			
		||||
        self.enp.set(f"Endpoint: {values['Endpoint']}")
 | 
			
		||||
 | 
			
		||||
    def show_data(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        shows data in the label
 | 
			
		||||
        Displays network-related data (address, DNS, endpoint)
 | 
			
		||||
        in the UI using ttk.Label widgets.
 | 
			
		||||
        Creates three labels for address, DNS, and endpoint with
 | 
			
		||||
        specific styling (color, font), positioning them in a
 | 
			
		||||
         grid layout (`lb_frame` and `lb_frame2`).
 | 
			
		||||
        Each label is linked to a corresponding text variable
 | 
			
		||||
        (`self.add`, `self.DNS`, `self.enp`) for dynamic data updates.
 | 
			
		||||
        """
 | 
			
		||||
        # Address Label
 | 
			
		||||
        self.address = ttk.Label(
 | 
			
		||||
@@ -1097,7 +1037,13 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
 | 
			
		||||
    def wg_switch(self, event=None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Deals with switching the VPN connection
 | 
			
		||||
        Manages switching between active and inactiveVPN connections.
 | 
			
		||||
        If no tunnel is selected (`self.a == ""`), it starts a new connection
 | 
			
		||||
        with the selected tunnel from the listbox (`l_box`).
 | 
			
		||||
        Otherwise, it stops the current connection and updates
 | 
			
		||||
        tunnel data using `handle_tunnel_data`.
 | 
			
		||||
        Handles errors like `IndexError` by displaying appropriate
 | 
			
		||||
        messages if no items are selected or the listbox is empty.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            if self.a == "":
 | 
			
		||||
@@ -1107,10 +1053,8 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
 | 
			
		||||
                data = self.handle_tunnel_data(self.a)
 | 
			
		||||
                if data:
 | 
			
		||||
 | 
			
		||||
                    self.handle_connection_state("stop")
 | 
			
		||||
                self.handle_tunnel_data(self.a, self.tl)
 | 
			
		||||
                self.handle_connection_state("stop")
 | 
			
		||||
 | 
			
		||||
        except IndexError:
 | 
			
		||||
 | 
			
		||||
@@ -1142,7 +1086,18 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        """
 | 
			
		||||
        if action == "stop":
 | 
			
		||||
            if self.a:
 | 
			
		||||
                check_call(["nmcli", "connection", "down", self.a])
 | 
			
		||||
                process: CompletedProcess[str] = run(
 | 
			
		||||
                    ["nmcli", "connection", "down", self.a],
 | 
			
		||||
                    capture_output=True,
 | 
			
		||||
                    text=True,
 | 
			
		||||
                    check=False,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                if process.stderr:
 | 
			
		||||
                    logging.error(
 | 
			
		||||
                        f"{process.stderr} Code: {process.returncode}", exc_info=True
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                self.update_connection_display()
 | 
			
		||||
                self.reset_fields()
 | 
			
		||||
                self.start()
 | 
			
		||||
@@ -1150,10 +1105,20 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        elif action == "start":
 | 
			
		||||
            if tunnel_name or self.a:
 | 
			
		||||
                target_tunnel = tunnel_name or self.a
 | 
			
		||||
                check_call(["nmcli", "connection", "up", target_tunnel])
 | 
			
		||||
                process: CompletedProcess[str] = run(
 | 
			
		||||
                    ["nmcli", "connection", "up", target_tunnel],
 | 
			
		||||
                    capture_output=True,
 | 
			
		||||
                    text=True,
 | 
			
		||||
                    check=False,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                if process.stderr:
 | 
			
		||||
                    logging.error(
 | 
			
		||||
                        f"{process.stderr} Code: {process.returncode}", exc_info=True
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                self.update_connection_display()
 | 
			
		||||
                data = self.handle_tunnel_data(self.a)
 | 
			
		||||
                self.init_and_report(data)
 | 
			
		||||
                self.handle_tunnel_data(self.a, self.tl)
 | 
			
		||||
                self.show_data()
 | 
			
		||||
                self.color_label()
 | 
			
		||||
                self.stop()
 | 
			
		||||
@@ -1168,7 +1133,7 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
        """
 | 
			
		||||
        Updated the display after connection changes
 | 
			
		||||
        """
 | 
			
		||||
        self.a = Tunnel.active()
 | 
			
		||||
        self.a = Tunnel.get_active()
 | 
			
		||||
        if not hasattr(self, "str_var"):
 | 
			
		||||
            self.str_var = tk.StringVar()
 | 
			
		||||
        self.str_var.set(self.a)
 | 
			
		||||
@@ -1177,10 +1142,14 @@ class FrameWidgets(ttk.Frame):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
 | 
			
		||||
    AppConfig.ensure_directories()
 | 
			
		||||
    AppConfig.create_default_settings()
 | 
			
		||||
    CryptoUtil.decrypt(getpass.getuser(), AppConfig.CONFIG_DIR)
 | 
			
		||||
    _ = AppConfig.setup_translations()
 | 
			
		||||
    LxTools.sigi(AppConfig.TEMP_DIR, AppConfig.USER_FILE)
 | 
			
		||||
    LxTools.sigi(AppConfig.TEMP_DIR)
 | 
			
		||||
 | 
			
		||||
    window = Wirepy()
 | 
			
		||||
    LogConfig.logger(ConfigManager.get("logfile"))
 | 
			
		||||
    """
 | 
			
		||||
    the hidden files are hidden in Filedialog
 | 
			
		||||
    """
 | 
			
		||||
@@ -1192,5 +1161,5 @@ if __name__ == "__main__":
 | 
			
		||||
    window.tk.call("set", "::tk::dialog::file::showHiddenVar", "0")
 | 
			
		||||
    window.mainloop()
 | 
			
		||||
 | 
			
		||||
LxTools.clean_files(AppConfig.TEMP_DIR, AppConfig.USER_FILE)
 | 
			
		||||
LxTools.clean_files(AppConfig.TEMP_DIR)
 | 
			
		||||
sys.exit(0)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										116
									
								
								wp_app_config.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						@@ -1,14 +1,37 @@
 | 
			
		||||
#!/usr/bin/python3
 | 
			
		||||
"""App configuration for Wire-Py"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import gettext
 | 
			
		||||
import locale
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from subprocess import CompletedProcess, run
 | 
			
		||||
from typing import Dict, Any
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AppConfig:
 | 
			
		||||
    """Central configuration class for Wire-Py application"""
 | 
			
		||||
    """Central configuration and system setup manager for the Wire-Py application.
 | 
			
		||||
 | 
			
		||||
    This class serves as a singleton-like container for all global configuration data,
 | 
			
		||||
    including paths, UI settings, localization, versioning, and system-specific resources.
 | 
			
		||||
    It ensures that required directories, files, and services are created and configured
 | 
			
		||||
    before the application starts. Additionally, it provides tools for managing translations,
 | 
			
		||||
    default settings, and autostart functionality to maintain a consistent user experience.
 | 
			
		||||
 | 
			
		||||
    Key Responsibilities:
 | 
			
		||||
    - Centralizes all configuration values (paths, UI preferences, localization).
 | 
			
		||||
    - Ensures required directories and files exist on startup.
 | 
			
		||||
    - Handles translation setup via `gettext` for multilingual support.
 | 
			
		||||
    - Manages default settings file generation.
 | 
			
		||||
    - Configures autostart services using systemd for user-specific launch behavior.
 | 
			
		||||
 | 
			
		||||
    This class is used globally across the application to access configuration data
 | 
			
		||||
    consistently and perform system-level setup tasks.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # Logging
 | 
			
		||||
    LOG_DIR = Path.home() / ".local/share/lxlogs"
 | 
			
		||||
    Path(LOG_DIR).mkdir(parents=True, exist_ok=True)
 | 
			
		||||
    LOG_FILE_PATH = LOG_DIR / "wirepy.log"
 | 
			
		||||
 | 
			
		||||
    # Localization
 | 
			
		||||
    APP_NAME: str = "wirepy"
 | 
			
		||||
@@ -18,23 +41,30 @@ class AppConfig:
 | 
			
		||||
    BASE_DIR: Path = Path.home()
 | 
			
		||||
    CONFIG_DIR: Path = BASE_DIR / ".config/wire_py"
 | 
			
		||||
    TEMP_DIR: Path = Path("/tmp/tlecdcwg")
 | 
			
		||||
    USER_FILE: Path = Path("/tmp/.log_user")
 | 
			
		||||
    PUBLICKEY: Path = CONFIG_DIR / "pbwgk.pem"
 | 
			
		||||
 | 
			
		||||
    # Configuration files
 | 
			
		||||
    SETTINGS_FILE: Path = CONFIG_DIR / "settings"
 | 
			
		||||
    KEYS_FILE: Path = CONFIG_DIR / "keys"
 | 
			
		||||
    SYSTEMD_USER_FOLDER: Path = Path.home() / ".config/systemd/user"
 | 
			
		||||
    AUTOSTART_SERVICE: Path = Path.home() / ".config/systemd/user/wg_start.service"
 | 
			
		||||
    DEFAULT_SETTINGS: Dict[str, str] = {
 | 
			
		||||
        "# Configuration": "on",
 | 
			
		||||
        "# Theme": "dark",
 | 
			
		||||
        "# Tooltips": True,
 | 
			
		||||
        "# Autostart": "off",
 | 
			
		||||
        "# Logfile": LOG_FILE_PATH,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # Updates
 | 
			
		||||
    # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year
 | 
			
		||||
    VERSION: str = "v. 2.04.1725"
 | 
			
		||||
    VERSION: str = "v. 2.06.0425"
 | 
			
		||||
    UPDATE_URL: str = "https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases"
 | 
			
		||||
    DOWNLOAD_URL: str = "https://git.ilunix.de/punix/Wire-Py/archive"
 | 
			
		||||
 | 
			
		||||
    # UI configuration
 | 
			
		||||
    UI_CONFIG: Dict[str, Any] = {
 | 
			
		||||
        "window_title": "Wire-Py",
 | 
			
		||||
        "window_title2": "LogViewer",
 | 
			
		||||
        "window_size": (600, 383),
 | 
			
		||||
        "font_family": "Ubuntu",
 | 
			
		||||
        "font_size": 11,
 | 
			
		||||
@@ -42,7 +72,7 @@ class AppConfig:
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # System-dependent paths
 | 
			
		||||
    SYSTEM_PATHS: Dict[str, str] = {
 | 
			
		||||
    SYSTEM_PATHS: Dict[str, Path] = {
 | 
			
		||||
        "ssl_decrypt": "/usr/local/bin/ssl_decrypt.py",
 | 
			
		||||
        "ssl_encrypt": "/usr/local/bin/ssl_encrypt.py",
 | 
			
		||||
        "tcl_path": "/usr/share/TK-Themes",
 | 
			
		||||
@@ -50,7 +80,7 @@ class AppConfig:
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # Images and icons paths
 | 
			
		||||
    IMAGE_PATHS: Dict[str, str] = {
 | 
			
		||||
    IMAGE_PATHS: Dict[str, Path] = {
 | 
			
		||||
        "icon_vpn": "/usr/share/icons/lx-icons/48/wg_vpn.png",
 | 
			
		||||
        "icon_msg": "/usr/share/icons/lx-icons/48/wg_msg.png",
 | 
			
		||||
        "icon_import": "/usr/share/icons/lx-icons/48/wg_import.png",
 | 
			
		||||
@@ -60,6 +90,7 @@ class AppConfig:
 | 
			
		||||
        "icon_stop": "/usr/share/icons/lx-icons/48/wg_vpn-stop.png",
 | 
			
		||||
        "icon_info": "/usr/share/icons/lx-icons/64/info.png",
 | 
			
		||||
        "icon_error": "/usr/share/icons/lx-icons/64/error.png",
 | 
			
		||||
        "icon_log": "/usr/share/icons/lx-icons/48/log.png",
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
@@ -79,7 +110,8 @@ class AppConfig:
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def ensure_directories(cls) -> None:
 | 
			
		||||
        """Ensures that all required directories exist"""
 | 
			
		||||
        cls.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
        if not cls.CONFIG_DIR.exists():
 | 
			
		||||
            cls.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
        cls.TEMP_DIR.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
@@ -92,31 +124,47 @@ class AppConfig:
 | 
			
		||||
            cls.SETTINGS_FILE.write_text(content)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_image_paths(cls) -> Dict[str, Path]:
 | 
			
		||||
        """Returns paths to UI images"""
 | 
			
		||||
        return {
 | 
			
		||||
            "main_icon": cls.SYSTEM_PATHS["image_path"] / "48/wg_vpn.png",
 | 
			
		||||
            "warning": cls.CONFIG_DIR / "images/warning.png",
 | 
			
		||||
            "success": cls.CONFIG_DIR / "images/success.png",
 | 
			
		||||
            "error": cls.CONFIG_DIR / "images/error.png",
 | 
			
		||||
        }
 | 
			
		||||
    def get_autostart_content(cls) -> None:
 | 
			
		||||
        """Returns the content for an autostart service file"""
 | 
			
		||||
        systemd_file: list[str] = [
 | 
			
		||||
            "[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",
 | 
			
		||||
        ]
 | 
			
		||||
        if not cls.SYSTEMD_USER_FOLDER.exists():
 | 
			
		||||
            cls.SYSTEMD_USER_FOLDER.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
        if not cls.AUTOSTART_SERVICE.is_file():
 | 
			
		||||
 | 
			
		||||
            content = "\n".join([line for line in systemd_file])
 | 
			
		||||
            cls.AUTOSTART_SERVICE.write_text(content)
 | 
			
		||||
 | 
			
		||||
        process: CompletedProcess[str] = run(
 | 
			
		||||
            ["systemctl", "--user", "enable", "wg_start.service"],
 | 
			
		||||
            capture_output=True,
 | 
			
		||||
            text=True,
 | 
			
		||||
            check=False,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if process.stderr:
 | 
			
		||||
            logging.error(f"{process.stderr} Code: {process.returncode}", exc_info=True)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_autostart_content(cls) -> str:
 | 
			
		||||
        """Returns the content for the autostart service file"""
 | 
			
		||||
 | 
			
		||||
        return """[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"""
 | 
			
		||||
    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 inizialize the class for translate strrings
 | 
			
		||||
# here is initializing the class for translation strings
 | 
			
		||||
_ = AppConfig.setup_translations()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -150,11 +198,16 @@ class Msg:
 | 
			
		||||
        "imp_err": _("Import Error"),
 | 
			
		||||
        "exp_err": _("Export Error"),
 | 
			
		||||
        "exp_try": _("Export failed! Please try again"),
 | 
			
		||||
        "exp_zip": _(
 | 
			
		||||
            "The error occurs because the zipfile module encountered an issue.\n"
 | 
			
		||||
            "Please verify that you have the latest version of WirePy installed.\n"
 | 
			
		||||
            "You can also contact the WirePy developer team to resolve this issue quickly.\n"
 | 
			
		||||
        ),
 | 
			
		||||
        "tl_first": _("Please first import tunnel"),
 | 
			
		||||
        "sel_list": _("Please select a tunnel from the list"),
 | 
			
		||||
        "sign_len": _("The new name may contain only 12 characters"),
 | 
			
		||||
        "zero_signs": _("At least one character must be entered"),
 | 
			
		||||
        "false signs": _(
 | 
			
		||||
        "false_signs": _(
 | 
			
		||||
            "No valid sign. These must not be used.\nBlank, Slash, Backslash and { }\n"
 | 
			
		||||
        ),
 | 
			
		||||
        "is_in_use": _("The tunnel is already in use"),
 | 
			
		||||
@@ -162,6 +215,9 @@ class Msg:
 | 
			
		||||
            "Oh... no valid Wireguard File!\nPlease select a valid Wireguard File"
 | 
			
		||||
        ),
 | 
			
		||||
        "tl_exist": _("Tunnel already available!\nPlease use another file for import"),
 | 
			
		||||
        "invalid_base64": _(
 | 
			
		||||
            "Invalid base64 format!\nPlease use a Config file with valid key."
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
    TTIP: Dict[str, str] = {
 | 
			
		||||
        # Strings for Tooltips
 | 
			
		||||
 
 | 
			
		||||