add gpg public_key ipmort

This commit is contained in:
2025-07-09 08:49:49 +02:00
parent 12904e843c
commit 44e75fa1b0
13 changed files with 1639 additions and 150 deletions

View File

@ -4,6 +4,8 @@ from tkinter import ttk
import os
import sys
import subprocess
from typing import Optional, List
import hashlib
import getpass
from datetime import datetime
import tempfile
@ -18,8 +20,9 @@ from manager import (
Image,
AppManager,
LxTools,
Locale,
)
from network import NetworkChecker
from network import NetworkChecker, GiteaUpdater, GPGManager
from message import MessageDialog
@ -34,6 +37,173 @@ class InstallationManager:
self.system_manager = System()
self.download_manager = DownloadManager()
def check_gpg(self) -> bool:
if GPGManager.is_key_already_imported(
LXToolsAppConfig.EXPECTED_FINGERPRINT[-8:]
):
return True
all_keys_valid: bool = True
# 1. Check reachability of OpenPGP keyserver and process key if reachable
openpgp_reachable: bool = GPGManager.is_url_reachable(
LXToolsAppConfig.KEY_URL_OPENPGP
)
if openpgp_reachable:
self.log(LocaleStrings.MSGGPG["keyserver_reachable"])
self.update_progress(LocaleStrings.MSGGPG["keyserver_reachable"])
if not GPGManager.import_key_from_url(
key_url=LXToolsAppConfig.KEY_URL_OPENPGP,
expected_fingerprint=LXToolsAppConfig.EXPECTED_FINGERPRINT,
filename="public_key1.asc",
):
all_keys_valid = False
else:
self.log(LocaleStrings.MSGGPG["keyserver_unreachable"])
self.update_progress(LocaleStrings.MSGGPG["keyserver_unreachable"])
# 2. Always download and verify Git.ilunix public key
if not GPGManager.import_key_from_url(
key_url=LXToolsAppConfig.KEY_URL_GITILUNIX,
expected_fingerprint=LXToolsAppConfig.EXPECTED_FINGERPRINT,
filename="public_key2.asc",
):
all_keys_valid = False
# Final step: Proceed only if all fingerprints match or OpenPGP was unreachable
if all_keys_valid:
self.log(LocaleStrings.MSGGPG["all_keys_valid"])
self.update_progress(LocaleStrings.MSGGPG["all_keys_valid"])
key_id: str = LXToolsAppConfig.EXPECTED_FINGERPRINT[-8:]
if not GPGManager.update_gpg_trust_level(key_id, trust_level=5):
self.log(LocaleStrings.MSGGPG["error_updating_trust_level"], key_id)
self.update_progress(
LocaleStrings.MSGGPG["error_updating_trust_level"], key_id
)
return False
else:
self.log(f"{LocaleStrings.MSGGPG['set_trust_level']}{key_id}.")
self.update_progress(
f"{LocaleStrings.MSGGPG['set_trust_level']}{key_id}."
)
return True
else:
self.log(LocaleStrings.MSGGPG["not_all_keys_are_valid"])
self.update_progress(LocaleStrings.MSGGPG["not_all_keys_are_valid"])
def check_appimage(self) -> bool | None | str:
"""
Checks the current version of the lxtools_installer AppImage by:
1. Downloading it from Gitea (with checksum and signature verification).
2. Renaming and making it executable.
3. Comparing its SHA-256 hash with an existing reference file at `/usr/local/bin/lxtools_installer`.
4. Returns `True` if the hashes match, otherwise `False`.
Returns:
bool: True if AppImages are identical; False if they differ or an error occurred.
"""
appimage_path: Optional[str] = GiteaUpdater.download_appimage(
verify_checksum=True, verify_signature=True
)
tests: List[str] = [
"Checksum file not found",
"Signature file not found",
"Checksum mismatch",
"Signature verification failed",
]
if appimage_path is None or appimage_path in tests:
print(f"{LocaleStrings.MSGA['error']}{appimage_path}")
return None
else:
try:
if os.path.isfile(appimage_path):
new_filename = "/tmp/portinstaller/lxtools_installer"
result = subprocess.run(
["mv", appimage_path, new_filename],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
self.update_progress(LocaleStrings.MSGA["appimage_renamed"])
self.log(LocaleStrings.MSGA["appimage_renamed"])
else:
self.update_progress(
f"{LocaleStrings.MSGA['appimage_rename_error']}{result.stderr}"
)
self.log(
f"{LocaleStrings.MSGA['appimage_rename_error']}{result.stderr}"
)
result = subprocess.run(
["chmod", "+x", new_filename],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
self.update_progress(
LocaleStrings.MSGA["appimage_executable_success"]
)
self.log(LocaleStrings.MSGA["appimage_executable_success"])
else:
self.update_progress(
f"{LocaleStrings.MSGA['appimage_executable_error']}{result.stderr}"
)
self.log(
f"{LocaleStrings.MSGA['appimage_executable_error']}{result.stderr}"
)
# Define reference path
reference_appimage_path = "/usr/local/bin/lxtools_installer"
if os.path.isfile(reference_appimage_path):
def calculate_sha256(file_path: str) -> str:
"""
Calculates the SHA-256 hash of a file.
Args:
file_path (str): Path to the file.
Returns:
str: Hexdigest of the SHA-256 hash.
"""
hash_obj = hashlib.sha256()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_obj.update(chunk)
return hash_obj.hexdigest()
# Calculate hashes
new_checksum = calculate_sha256(new_filename)
reference_checksum = calculate_sha256(reference_appimage_path)
# Compare and decide action
if new_checksum == reference_checksum:
return "True" # String as a compatibility solution to network.py
else:
return False
else:
return False
else:
self.update_progress(LocaleStrings.MSGA["appimage_not_exist"])
self.log(LocaleStrings.MSGA["appimage_not_exist"])
return False
except Exception as e:
self.update_progress(f"Exception occurred: {str(e)}")
self.log(f"Exception occurred: {str(e)}")
return None
def install_project(self, project_key):
"""Install or update project"""
project_info = self.app_manager.get_project_info(project_key)
@ -77,6 +247,13 @@ class InstallationManager:
raise Exception(f"{LocaleStrings.MSGI['unknow_project']}{project_key}")
def _create_wirepy_install_script(self):
gpg_checker = self.check_gpg()
result_appimage = self.check_appimage()
if not gpg_checker:
print(gpg_checker)
return
if result_appimage is True or result_appimage is None:
return
detected_os = Detector.get_os()
if detected_os == "Arch Linux":
result_unzip = Detector.get_unzip()
@ -90,13 +267,14 @@ class InstallationManager:
"""Create Wire-Py installation script"""
script = f"""#!/bin/bash
set -e
echo "=== Wire-Py Installation ==="
if [ "{result_appimage}" = "False" ]; then
cp -f /tmp/portinstaller/lxtools_installer /usr/local/bin/lxtools_installer
fi
if [ "{detected_os}" = "Arch Linux" ]; then
if [ "{result_unzip}" = "False" ]; then
pacman -S --noconfirm unzip
fi
if [ "{result_wget}" = "False" ]; then
pacman -S --noconfirm wget
fi
@ -215,13 +393,21 @@ fi
# Cleanup
cd /tmp
rm -rf wirepy_install
rm -rf wirepy_install portinstaller
echo "Wire-Py installation completed!"
"""
return script
def _create_logviewer_install_script(self):
gpg_checker = self.check_gpg()
result_appimage = self.check_appimage()
if not gpg_checker:
print(gpg_checker)
return
if result_appimage is True or result_appimage is None:
return
detected_os = Detector.get_os()
if detected_os == "Arch Linux":
result_unzip = Detector.get_unzip()
@ -235,7 +421,10 @@ echo "Wire-Py installation completed!"
script = f"""#!/bin/bash
set -e
{LXToolsAppConfig.TKINTER_INSTALL_COMMANDS[detected_os]} 2>&1 | grep -v "apt does not have a stable CLI interface"
echo "=== LogViewer Installation ==="
if [ "{result_appimage}" = "False" ]; then
cp -f /tmp/portinstaller/lxtools_installer /usr/local/bin/lxtools_installer
fi
if [ "{detected_os}" = "Arch Linux" ]; then
if [ "{result_unzip}" = "False" ]; then
pacman -S --noconfirm unzip
@ -273,12 +462,12 @@ if [ ! -d "/usr/share/TK-Themes" ] || [ -z "$(ls -A /usr/share/TK-Themes 2>/dev/
wget -q "{LXToolsAppConfig.WIREPY_URL}" -O wirepy.zip
unzip -q wirepy.zip
WIREPY_DIR=$(find . -name "Wire-Py" -type d | head -1)
if [ -d "$WIREPY_DIR/TK-Themes" ]; then
echo "Installing TK-Themes..."
cp -rf "$WIREPY_DIR/TK-Themes"/* /usr/share/TK-Themes/
fi
# Also install icons from Wire-Py if not present
if [ -d "$WIREPY_DIR/lx-icons" ] && [ ! -d "/usr/share/icons/lx-icons" ]; then
echo "Installing icons from Wire-Py..."
@ -315,7 +504,7 @@ fi
# Cleanup
cd /tmp
rm -rf logviewer_install
rm -rf logviewer_install portinstaller
echo "LogViewer installation completed!"
"""
@ -344,7 +533,7 @@ echo "LogViewer installation completed!"
# Log output
if result.stdout:
self.log(f"STDOUT: {result.stdout}")
self.log((result.stdout).strip("Log\n"))
if result.stderr:
self.log(f"STDERR: {result.stderr}")
@ -358,6 +547,7 @@ echo "LogViewer installation completed!"
except subprocess.TimeoutExpired:
raise Exception(LocaleStrings.MSGI["install_timeout"])
except subprocess.CalledProcessError as e:
raise Exception(f"{LocaleStrings.MSGI['install_script_failed']}{e}")
@ -476,12 +666,13 @@ if [ ! -f /usr/local/bin/logviewer ] || [ ! -L /usr/local/bin/logviewer ]; then
echo "No other LX apps found, removing shared resources..."
rm -rf /usr/share/icons/lx-icons
rm -rf /usr/share/TK-Themes
rm -rf /usr/local/bin/lxtools_installer
# Remove shared libs
for file in common_tools.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py logviewer.py; do
rm -f {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/$file
done
# Try to remove shared_libs directory if empty
rmdir {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]} 2>/dev/null || true
else
@ -523,15 +714,16 @@ if [ ! -f /usr/local/bin/wirepy ] || [ ! -L /usr/local/bin/wirepy ]; then
echo "No other LX apps found, removing shared resources..."
rm -rf /usr/share/icons/lx-icons
rm -rf /usr/share/TK-Themes
rm -rf /usr/local/bin/lxtools_installer
# Remove shared libs (but keep logviewer.py if we're only uninstalling logviewer)
for file in common_tools.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py; do
rm -f {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/$file
done
# Remove logviewer.py last
rm -f {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/logviewer.py
# Try to remove shared_libs directory if empty
rmdir LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os] 2>/dev/null || true
else
@ -568,7 +760,7 @@ echo "LogViewer uninstallation completed!"
# Log output
if result.stdout:
self.log(f"STDOUT: {result.stdout}")
self.log(result.stdout)
if result.stderr:
self.log(f"STDERR: {result.stderr}")
@ -628,6 +820,8 @@ class LXToolsGUI:
self.download_icon_label = None
self.log_text = None
self.selected_project = None
self.polkit_ok = Detector.get_polkit()
self.networkmanager_ok = Detector.get_networkmanager()
self.project_frames = {}
self.status_labels = {}
self.version_labels = {}
@ -661,7 +855,7 @@ class LXToolsGUI:
def create_gui(self):
"""Create the main GUI"""
self.root = tk.Tk()
self.root.title(f"{LXToolsAppConfig.APP_NAME} v{LXToolsAppConfig.VERSION}")
self.root.title(f"{Locale.APP_NAME} v{LXToolsAppConfig.VERSION}")
self.root.geometry(
f"{LXToolsAppConfig.WINDOW_WIDTH}x{LXToolsAppConfig.WINDOW_HEIGHT}+100+100"
)
@ -677,6 +871,7 @@ class LXToolsGUI:
self.root.iconphoto(False, icon)
except:
pass
self.root.minsize(LXToolsAppConfig.WINDOW_WIDTH, LXToolsAppConfig.WINDOW_HEIGHT)
Theme.apply_light_theme(self.root)
# Create header
@ -719,10 +914,25 @@ class LXToolsGUI:
icon_text_frame = tk.Frame(left_side, bg="#2c3e50")
icon_text_frame.pack(anchor="w")
# Tool-Icon
tk.Label(
icon_text_frame, text="🔧", font=("Helvetica", 18), bg="#2c3e50", fg="white"
).pack(side="left", padx=(0, 8))
# Tool-Icon (versuche echtes Icon, dann Fallback)
try:
icon = self.image_manager.load_image("header_image")
if icon:
# Resize icon für Header
icon_label = tk.Label(icon_text_frame, image=icon, bg="#2c3e50")
icon_label.image = icon # Referenz behalten
icon_label.pack(side="left", padx=(0, 8))
else:
raise Exception("Kein Icon gefunden")
except:
# Fallback: Unicode-Symbol statt Emoji
tk.Label(
icon_text_frame,
text="",
font=("Helvetica", 18),
bg="#2c3e50",
fg="white",
).pack(side="left", padx=(0, 8))
# App Name and Version
text_frame = tk.Frame(icon_text_frame, bg="#2c3e50")
@ -774,40 +984,51 @@ class LXToolsGUI:
def check_ready_status(self):
"""Check if system is ready for installation"""
# Checks:
polkit_ok = Detector.get_polkit()
networkmanager_ok = Detector.get_networkmanager()
# Führe alle Checks durch
internet_ok = NetworkChecker.check_internet_connection()
repo_ok = NetworkChecker.check_repository_access()
result = Detector.get_host_python_version()
python_result = Detector.get_host_python_version()
if not polkit_ok:
self.update_header_status(
LocaleStrings.MSGO["polkit_check"], "#e74c3c"
) # Red
if not networkmanager_ok:
self.update_header_status(
LocaleStrings.MSGO["networkmanager_check"], "#e74c3c"
) # Red
# Sammle alle Probleme
issues = []
if not self.polkit_ok:
issues.append(("PolicyKit", "#e74c3c", LocaleStrings.MSGO["polkit_check"]))
if not self.networkmanager_ok:
issues.append(
(
"NetworkManager",
"#e74c3c",
LocaleStrings.MSGO["networkmanager_check"],
)
)
if not internet_ok:
issues.append(("Internet", "#e74c3c", LocaleStrings.MSGO["no_internet"]))
if not repo_ok:
issues.append(
("Repository", "#f39c12", LocaleStrings.MSGO["repo_unavailable"])
)
if python_result is None:
issues.append(("Python", "#e74c3c", LocaleStrings.MSGO["python_check"]))
if internet_ok and repo_ok and result is not None:
self.update_header_status(LocaleStrings.MSGO["ready"], "#1abc9c") # Green
elif not internet_ok:
self.update_header_status(
LocaleStrings.MSGO["no_internet"], "#e74c3c"
) # Red
elif not repo_ok:
self.update_header_status(
LocaleStrings.MSGO["repo_unavailable"], "#f39c12"
) # Orange
elif result is None:
self.update_header_status(
LocaleStrings.MSGO["python_check"], "#e74c3c"
) # Red
# Bestimme Hauptstatus basierend auf Priorität
if issues:
# Zeige das wichtigste Problem (Internet > Python > Repository > Services)
for priority_check in [
"Internet",
"Python",
"Repository",
"PolicyKit",
"NetworkManager",
]:
for issue_name, color, message in issues:
if issue_name == priority_check:
print(f"DEBUG: Zeige Problem: {issue_name} - {message}")
self.update_header_status(message, color)
return
else:
self.update_header_status(
LocaleStrings.MSGO["system_check"], "#3498db"
) # Blue
self.update_header_status(LocaleStrings.MSGO["ready"], "#1abc9c") # Green
def _create_projects_tab(self):
"""Create projects tab with project cards"""
@ -1046,14 +1267,16 @@ class LXToolsGUI:
clear_log_btn.pack(side="right", pady=(0, 10))
# Initial log message
self.log_message(
f"=== {LXToolsAppConfig.APP_NAME} v {LXToolsAppConfig.VERSION} ==="
)
self.log_message(f"=== {Locale.APP_NAME} v {LXToolsAppConfig.VERSION} ===")
self.log_message(f"{LocaleStrings.MSGL['work_dir']}{LXToolsAppConfig.WORK_DIR}")
self.log_message(
f"{LocaleStrings.MSGL['icons_dir']}{LXToolsAppConfig.ICONS_DIR}"
)
self.log_message(f"{LocaleStrings.MSGL['detected_os']}{self.detected_os}")
self.log_message(f"{LocaleStrings.MSGO['polkit_check_log']}{self.polkit_ok}")
self.log_message(
f"{LocaleStrings.MSGO['networkmanager_check_log']}{self.networkmanager_ok}"
)
self.log_message(f"{LocaleStrings.MSGO['ready']}...")
def _create_progress_section(self):
@ -1089,58 +1312,87 @@ class LXToolsGUI:
button_frame = tk.Frame(self.root, bg=self.colors["bg"])
button_frame.pack(fill="x", padx=15, pady=(5, 10))
# Button style configuration
style = ttk.Style()
# Button style configuration (mit Error-Handling für Linux Mint)
try:
style = ttk.Style()
# Install button (green)
style.configure("Install.TButton", foreground="#27ae60", font=("Helvetica", 11))
style.map(
"Install.TButton",
foreground=[("active", "#14542f"), ("pressed", "#1e8449")],
)
# Install button (green)
style.configure(
"Install.TButton", foreground="#27ae60", font=("Helvetica", 11)
)
style.map(
"Install.TButton",
foreground=[("active", "#14542f"), ("pressed", "#1e8449")],
)
# Uninstall button (red)
style.configure(
"Uninstall.TButton", foreground="#e74c3c", font=("Helvetica", 11)
)
style.map(
"Uninstall.TButton",
foreground=[("active", "#7d3b34"), ("pressed", "#c0392b")],
)
# Uninstall button (red)
style.configure(
"Uninstall.TButton", foreground="#e74c3c", font=("Helvetica", 11)
)
style.map(
"Uninstall.TButton",
foreground=[("active", "#7d3b34"), ("pressed", "#c0392b")],
)
# Refresh button (blue)
style.configure(
"Refresh.TButton", foreground="#3498db", font=("Helvetica", 11)
)
style.map(
"Refresh.TButton",
foreground=[("active", "#1e3747"), ("pressed", "#2980b9")],
)
except Exception as e:
print(f"Style-Konfiguration fehlgeschlagen: {e}")
# Fallback: verwende Standard-Styles
# Refresh button (blue)
style.configure("Refresh.TButton", foreground="#3498db", font=("Helvetica", 11))
style.map(
"Refresh.TButton",
foreground=[("active", "#1e3747"), ("pressed", "#2980b9")],
)
# Create buttons
install_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["install"],
command=self.install_selected,
style="Install.TButton",
padding=8,
)
# Create buttons (mit Fallback für Style-Probleme)
try:
install_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["install"],
command=self.install_selected,
style="Install.TButton",
padding=8,
)
except:
install_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["install"],
command=self.install_selected,
)
install_btn.pack(side="left", padx=(0, 10))
uninstall_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["uninstall"],
command=self.uninstall_selected,
style="Uninstall.TButton",
padding=8,
)
try:
uninstall_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["uninstall"],
command=self.uninstall_selected,
style="Uninstall.TButton",
padding=8,
)
except:
uninstall_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["uninstall"],
command=self.uninstall_selected,
)
uninstall_btn.pack(side="left", padx=(0, 10))
refresh_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["refresh"],
command=self.refresh_status,
style="Refresh.TButton",
padding=8,
)
try:
refresh_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["refresh"],
command=self.refresh_status,
style="Refresh.TButton",
width=30,
padding=8,
)
except:
refresh_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["refresh"],
command=self.refresh_status,
)
refresh_btn.pack(side="right")
def update_download_icon(self, status):
@ -1211,16 +1463,16 @@ class LXToolsGUI:
if latest_version != "Unknown":
if installed_version != f"v. {latest_version}":
version_label.config(
text=f"{LocaleStrings.MSGC['latest']}(v. {latest_version}) {LocaleStrings.MSGC['update_available']}",
text=f"{LocaleStrings.MSGC['update_on']}(v. {latest_version}) {LocaleStrings.MSGC['available_lower']}",
fg="orange",
)
self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGC['update_available']}(v. {latest_version})"
f"{project_info['name']}: {LocaleStrings.MSGC['update_on']}(v. {latest_version} {LocaleStrings.MSGC['available_lower']})"
)
else:
version_label.config(
text=f"{LocaleStrings.MSGC['latest']}: (v. {latest_version}) {LocaleStrings.MSGC['up_to_date']}",
text=f"(v. {latest_version}) {LocaleStrings.MSGC['up_to_date']}",
fg="green",
)
self.log_message(
@ -1379,7 +1631,7 @@ class LXToolsGUI:
def main():
"""Main function to start the application"""
print(f"=== {LXToolsAppConfig.APP_NAME} v {LXToolsAppConfig.VERSION} ===")
print(f"=== {Locale.APP_NAME} v {LXToolsAppConfig.VERSION} ===")
print(f"{LocaleStrings.MSGL['working_dir']}{os.getcwd()}")
try: