diff --git a/Changelog b/Changelog index 0384163..f5c3d3e 100644 --- a/Changelog +++ b/Changelog @@ -41,7 +41,7 @@ My standard System: Linux Mint 22 Cinnamon - Fix ipv6 in Config File on import - Wirepy run now as user - - settings, keys and Config Files now in ~/.config/wire_py + - settings, AppConfig.KEYS_FILE and Config Files now in ~/.config/wire_py - For new users, the required files are created and autostart service is started. - Tunnels are now read from the directory to view them in the list. To display only own tunnels, and read errors are minimized. diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 8c9b363..a8e736c 100644 Binary files a/__pycache__/cls_mth_fc.cpython-312.pyc and b/__pycache__/cls_mth_fc.cpython-312.pyc differ diff --git a/__pycache__/wp_app_config.cpython-312.pyc b/__pycache__/wp_app_config.cpython-312.pyc new file mode 100644 index 0000000..e4df3cb Binary files /dev/null and b/__pycache__/wp_app_config.cpython-312.pyc differ diff --git a/cls_mth_fc.py b/cls_mth_fc.py index cffc669..a184d87 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -14,14 +14,14 @@ 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 import requests -APP = "wirepy" -LOCALE_DIR = "/usr/share/locale/" -locale.bindtextdomain(APP, LOCALE_DIR) -gettext.bindtextdomain(APP, LOCALE_DIR) -gettext.textdomain(APP) +# Translate +AppConfig.APP_NAME +locale.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR) +gettext.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR) +gettext.textdomain(AppConfig.APP_NAME) _ = gettext.gettext @@ -41,7 +41,7 @@ class Create: pth: Path = Path.home() / ".config/wire_py" pth.mkdir(parents=True, exist_ok=True) sett: Path = Path.home() / ".config/wire_py/settings" - ks: Path = Path.home() / ".config/wire_py/keys" + AppConfig.KEYS_FILE if sett.exists(): pass @@ -50,11 +50,11 @@ class Create: sett.touch() sett.write_text("[UPDATES]\non\n[THEME]\nlight\n[TOOLTIP]\nTrue\n[AUTOSTART ON]\noff\n") - if ks.exists(): + if AppConfig.KEYS_FILE.exists(): pass else: - ks.touch() + AppConfig.KEYS_FILE.touch() @staticmethod def files_for_autostart() -> None: @@ -80,11 +80,11 @@ class Create: def make_dir() -> None: """Folder Name "tlecdewg" = Tunnel Encrypt Decrypt Wireguard""" - folder_path: Path = Path("/tmp/tlecdcwg/") - if folder_path.exists(): + AppConfig.TEMP_DIR: Path = Path("/tmp/tlecdcwg/") + if AppConfig.TEMP_DIR.exists(): pass else: - folder_path.mkdir() + AppConfig.TEMP_DIR.mkdir() @staticmethod def decrypt() -> None: @@ -138,15 +138,15 @@ class LxTools(tk.Tk): Path(file).write_text(log_name, encoding="utf-8") @staticmethod - def clean_files(folder_path: Path = None, file: Path = None) -> None: + 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 folder_path: default None + :param AppConfig.TEMP_DIR: default None """ - if folder_path is not None: - shutil.rmtree(folder_path) + if AppConfig.TEMP_DIR is not None: + shutil.rmtree(AppConfig.TEMP_DIR) if file is not None: Path.unlink(file) @@ -162,6 +162,15 @@ class LxTools(tk.Tk): tip = True return tip + + def theme_change(self) -> None: + + lines = AppConfig.SETTINGS_FILE.read_text() + if "light\n" in lines: + self.tk.call("set_theme", "light") + else: + self.tk.call("set_theme", "dark") + @staticmethod def msg_window(img_w: str, img_i: str, w_title: str, w_txt: str, txt2: Optional[str] = None, com: Optional[str] = None) -> None: @@ -392,8 +401,8 @@ class Tunnel: """ Returns a list of Wireguard tunnel names """ - folder_path: Path = Path("/tmp/tlecdcwg/") - wg_s: List[str] = os.listdir(folder_path) + AppConfig.TEMP_DIR: Path = Path("/tmp/tlecdcwg/") + wg_s: List[str] = os.listdir(AppConfig.TEMP_DIR) return wg_s diff --git a/install b/install index 1b88d2b..8ca2607 100755 --- a/install +++ b/install @@ -17,7 +17,7 @@ install_file_with(){ exit 0 else sudo apt install python3-tk && \ - sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.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/ && \ @@ -43,7 +43,7 @@ install_arch_d(){ exit 0 else sudo pacman -S --noconfirm tk python3 python-requests && \ - sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.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/ && \ @@ -120,7 +120,7 @@ install(){ exit 0 else sudo dnf install python3-tkinter -y - sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.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/ && \ @@ -145,7 +145,7 @@ install(){ rm -r ~/.config/wire_py && rm -r ~/.config/systemd exit 0 else - sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.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/ && \ @@ -181,7 +181,7 @@ install(){ remove(){ sudo rm -f /usr/local/bin/wirepy /usr/local/bin/wirepy.py /usr/local/bin/start_wg.py \ - /usr/local/bin/cls_mth_fc.py /usr/local/bin/ssl_encrypt.py /usr/local/bin/ssl_decrypt.py + /usr/local/bin/wp_app_config.py cls_mth_fc.py /usr/local/bin/ssl_encrypt.py /usr/local/bin/ssl_decrypt.py if [ $? -ne 0 ] then exit 0 diff --git a/ssl_decrypt.py b/ssl_decrypt.py index e9fda31..42015fd 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -5,13 +5,12 @@ import os import shutil from pathlib import Path from subprocess import check_call +from wp_app_config import AppConfig uname: Path = Path("/tmp/.log_user") log_name = Path(uname).read_text(encoding="utf-8") -# Folder Name "tlecdewg" = Tunnel Encrypt Decrypt Wireguard -folder_path: Path = Path("/tmp/tlecdcwg/") keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") PKEYFILE: Path = "/usr/local/etc/ssl/pwgk.pem" @@ -20,16 +19,16 @@ if not keyfile.is_file(): check_call(["openssl", "rsa", "-in", PKEYFILE, "-out", keyfile, "-outform", "PEM", "-pubout"]) shutil.chown(keyfile, 1000, 1000) -folder_path2 = f"/home/{log_name}/.config/wire_py/" -detl: list[str] = os.listdir(folder_path2) -os.chdir(folder_path2) +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"{folder_path2}pbwgk.pem"): +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"{folder_path}/{tlname2}" + extpath = f"{AppConfig.TEMP_DIR}/{tlname2}" check_call(["openssl", "pkeyutl", "-decrypt", "-inkey", PKEYFILE, "-in", detunnels, "-out", extpath]) shutil.chown(extpath, 1000, 1000) diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 7ab4fa7..2505a59 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -6,12 +6,13 @@ import shutil from pathlib import Path from subprocess import check_call +from wp_app_config import AppConfig + uname: Path = Path("/tmp/.log_user") log_name = Path(uname).read_text(encoding="utf-8") keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") -folder_path: Path = Path("/tmp/tlecdcwg/") PKEYFILE = "/usr/local/etc/ssl/pwgk.pem" if not keyfile.is_file(): @@ -19,28 +20,28 @@ if not keyfile.is_file(): check_call(["openssl", "rsa", "-in", PKEYFILE, "-out", keyfile, "-outform", "PEM", "-pubout"]) shutil.chown(keyfile, 1000, 1000) - if folder_path.exists(): - tl = os.listdir(f"{folder_path}") + if AppConfig.TEMP_DIR.exists(): + tl = os.listdir(f"{AppConfig.TEMP_DIR}") CPTH: str = f"{keyfile}" CRYPTFILES: str = CPTH[:-9] if keyfile.exists() and len(tl) != 0: for tunnels in tl: - sourcetl: str = f"{folder_path}/{tunnels}" + 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,]) else: - if folder_path.exists(): - tl: list[str] = os.listdir(f"{folder_path}") + if AppConfig.TEMP_DIR.exists(): + tl: list[str] = os.listdir(f"{AppConfig.TEMP_DIR}") CPTH: str = f"{keyfile}" CRYPTFILES: str = CPTH[:-9] if keyfile.exists() and len(tl) != 0: for tunnels in tl: - sourcetl: str = f"{folder_path}/{tunnels}" + 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]) diff --git a/wirepy.py b/wirepy.py index a33df56..3ece856 100755 --- a/wirepy.py +++ b/wirepy.py @@ -15,31 +15,26 @@ from subprocess import check_call from tkinter import TclError, filedialog, ttk from cls_mth_fc import (Create, GiteaUpdate, Tooltip, Tunnel, LxTools) +from wp_app_config import AppConfig LxTools.uos() Create.dir_and_files() Create.make_dir() Create.decrypt() - -tcl_path: Path = Path("/usr/share/TK-Themes") -set_file: Path = Path(Path.home() / ".config/wire_py/settings") -keys: Path = Path(Path.home() / ".config/wire_py/keys") -tips = LxTools.if_tip(set_file) -folder_path: Path = Path("/tmp/tlecdcwg/") -user_file = Path("/tmp/.log_user") +tips = LxTools.if_tip(AppConfig.SETTINGS_FILE) + # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year VERSION: str = "v. 2.04.1725" -res = GiteaUpdate.api_down("https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases", VERSION, set_file) +res = GiteaUpdate.api_down("https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases", VERSION, AppConfig.SETTINGS_FILE) # Translate -APP = "wirepy" -LOCALE_DIR = "/usr/share/locale/" -locale.bindtextdomain(APP, LOCALE_DIR) -gettext.bindtextdomain(APP, LOCALE_DIR) -gettext.textdomain(APP) +AppConfig.APP_NAME +locale.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR) +gettext.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR) +gettext.textdomain(AppConfig.APP_NAME) _ = gettext.gettext img_w: str = r"/usr/share/icons/lx-icons/64/info.png" @@ -52,7 +47,7 @@ ie:str = _("Import Error") pfit: str = _("Please first import tunnel") pstl: str = _("Please select a tunnel from the list") -LxTools.sigi(folder_path, user_file) +LxTools.sigi(AppConfig.TEMP_DIR, AppConfig.USER_FILE) class Wirepy(tk.Tk): """ @@ -62,24 +57,18 @@ class Wirepy(tk.Tk): super().__init__(*args, **kwargs) self.my_tool_tip = None - self.x_width = 600 - self.y_height = 383 + self.x_width = AppConfig.UI_CONFIG["window_size"][0] + self.y_height = AppConfig.UI_CONFIG["window_size"][1] self.monitor_center_x = int(self.winfo_screenwidth() / 2 - (self.x_width / 2)) self.monitor_center_y = int(self.winfo_screenheight() / 2 - (self.y_height / 2)) - self.resizable(width=False, height=False) - self.title("Wire-Py") + self.resizable(AppConfig.UI_CONFIG["resizable_window"][0], AppConfig.UI_CONFIG["resizable_window"][1]) + self.title(AppConfig.UI_CONFIG["window_title"]) self.geometry(f"{self.x_width}x{self.y_height}+{self.monitor_center_x}+{self.monitor_center_y}") self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) - self.style = ttk.Style(self) - self.tk.call("source", f"{tcl_path}/water.tcl") - - lines = set_file.read_text() - if "light\n" in lines: - self.tk.call("set_theme", "light") - else: - self.tk.call("set_theme", "dark") + self.tk.call("source", f"{AppConfig.SYSTEM_PATHS["tcl_path"]}/water.tcl") + LxTools.theme_change(self) # Load the image file from the disk self.wg_icon = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_vpn.png") @@ -352,9 +341,9 @@ class FrameWidgets(ttk.Frame): """ if self.tk.call("ttk::style", "theme", "use") == "water-dark": self.tk.call("set_theme", "light") - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) # (keepends=True) = not changed + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) # (keepends=True) = not changed lines[3] = 'light\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") self.color_label() def theme_change_dark(self) -> None: @@ -363,9 +352,9 @@ class FrameWidgets(ttk.Frame): """ if not self.tk.call("ttk::style", "theme", "use") == "water-dark": self.tk.call("set_theme", "dark") - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[3] = 'dark\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") self.color_label() @staticmethod @@ -376,14 +365,14 @@ class FrameWidgets(ttk.Frame): update_res (int): argument that is passed contains 0 or 1 """ if update_res == 1: - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[1] = 'off\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") else: - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[1] = 'on\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") @staticmethod def tooltip(tip) -> None: @@ -393,14 +382,14 @@ class FrameWidgets(ttk.Frame): tip (bool): argument that is passed contains True or False """ if tip: - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[5] = 'False\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") else: - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[5] = 'True\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") def enable_check_box(self, _) -> None: """ @@ -425,12 +414,12 @@ class FrameWidgets(ttk.Frame): pre_key = key[3] check_call(["nmcli", "connection", "delete", select_tl]) self.l_box.delete(self.select_tunnel[0]) - with open(set_file, "r", encoding="utf-8") as set_f6: + 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(set_file, "w", encoding="utf-8") as set_f7: + 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")) @@ -516,11 +505,11 @@ class FrameWidgets(ttk.Frame): if self.a != "" and self.a == select_tl: self.a = Tunnel.active() self.str_var.set(value=self.a) - with open(set_file, "r", encoding="utf-8") as set_f5: + 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(set_file, "w", encoding="utf-8") as theme_set5: + with open(AppConfig.SETTINGS_FILE, "w", encoding="utf-8") as theme_set5: theme_set5.writelines(lines5) self.autoconnect_var.set(value=new_a_connect) @@ -658,9 +647,9 @@ class FrameWidgets(ttk.Frame): select_tl = self.l_box.get(select_tunnel[0]) if self.selected_option.get() == 0: - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[7] = 'off\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") tl = Tunnel.list() @@ -668,9 +657,9 @@ class FrameWidgets(ttk.Frame): self.wg_autostart.configure(state="disabled") if self.selected_option.get() >= 1: - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[7] = select_tl - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") except IndexError: self.selected_option.set(1) @@ -683,7 +672,7 @@ 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(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + 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.") @@ -771,7 +760,7 @@ class FrameWidgets(ttk.Frame): """ View activ Tunnel in the color green or yellow """ - lines = set_file.read_text() + lines = AppConfig.SETTINGS_FILE.read_text() if "light\n" in lines: self.lb_tunnel = ttk.Label(self, textvariable=self.str_var, foreground="green") @@ -865,5 +854,5 @@ if __name__ == "__main__": window.tk.call("set", "::tk::dialog::file::showHiddenVar", "0") window.mainloop() -LxTools.clean_files(folder_path, user_file) +LxTools.clean_files(AppConfig.TEMP_DIR, AppConfig.USER_FILE) sys.exit(0) diff --git a/app_config.py b/wp_app_config.py similarity index 90% rename from app_config.py rename to wp_app_config.py index 54f97a7..3485c59 100644 --- a/app_config.py +++ b/wp_app_config.py @@ -8,7 +8,8 @@ class AppConfig: BASE_DIR = Path.home() CONFIG_DIR = BASE_DIR / ".config/wire_py" TEMP_DIR = Path("/tmp/tlecdcwg") - + USER_FILE = Path("/tmp/.log_user") + # Configuration files SETTINGS_FILE = CONFIG_DIR / "settings" KEYS_FILE = CONFIG_DIR / "keys" @@ -29,15 +30,17 @@ class AppConfig: # UI configuration UI_CONFIG = { "window_title": "Wire-Py", - "window_size": (800, 600), + "window_size": (600, 383), "font_family": "Ubuntu", - "font_size": 11 + "font_size": 11, + "resizable_window": (False, False) } # System-dependent paths SYSTEM_PATHS = { "ssl_decrypt": "/usr/local/bin/ssl_decrypt.py", - "ssl_encrypt": "/usr/local/bin/ssl_encrypt.py" + "ssl_encrypt": "/usr/local/bin/ssl_encrypt.py", + "tcl_path": "/usr/share/TK-Themes" } @classmethod