Files
WGSecure/app/utils/platform_utils.py
T
2026-07-21 10:21:13 +02:00

178 lines
5.6 KiB
Python

import sys
import os
import subprocess
import platform
def is_windows() -> bool:
return sys.platform == "win32"
def is_linux() -> bool:
return sys.platform.startswith("linux")
def get_config_dir() -> str:
if is_windows():
base = os.environ.get("APPDATA", os.path.expanduser("~"))
path = os.path.join(base, "WGSecure")
else:
path = os.path.join(os.path.expanduser("~"), ".wgsecure")
os.makedirs(path, exist_ok=True)
return path
def get_wg_config_dir() -> str:
if is_windows():
base = os.environ.get("PROGRAMDATA", r"C:\ProgramData")
return os.path.join(base, "WireGuard")
return "/etc/wireguard"
def has_root_privileges() -> bool:
if is_windows():
try:
import ctypes
return ctypes.windll.shell32.IsUserAnAdmin() != 0
except Exception:
return False
return os.geteuid() == 0
def run_command(cmd: list[str], timeout: int = 10) -> tuple[int, str, str]:
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
)
return result.returncode, result.stdout.strip(), result.stderr.strip()
except subprocess.TimeoutExpired:
return -1, "", "Timeout"
except FileNotFoundError:
return -1, "", f"Commande introuvable : {cmd[0]}"
except Exception as e:
return -1, "", str(e)
def run_privileged(cmd: list[str], timeout: int = 60) -> tuple[int, str, str]:
if is_windows():
return run_command(cmd, timeout)
if has_root_privileges():
return run_command(cmd, timeout)
# 1. sudo -n (NOPASSWD configuré dans sudoers — sans aucun dialogue)
code, out, err = run_command(["sudo", "-n"] + cmd, timeout)
if code == 0:
return code, out, err
# Si sudo a tourné mais la commande a échoué (pas un problème d'auth), retourner l'erreur
if code != 1 or ("password" not in err.lower() and "passwd" not in err.lower()):
if "sudo:" not in err.lower() and code not in (-1,):
return code, out, err
# 2. sudo avec programme askpass graphique (pas de TTY dans une app Qt)
askpass = _find_askpass()
if askpass:
env = os.environ.copy()
env["SUDO_ASKPASS"] = askpass
try:
result = subprocess.run(
["sudo", "-A"] + cmd,
capture_output=True, text=True,
timeout=timeout, env=env,
)
if result.returncode == 0:
return result.returncode, result.stdout.strip(), result.stderr.strip()
except (subprocess.TimeoutExpired, Exception):
pass
# 3. pkexec — dialogue graphique polkit (GNOME/KDE)
code, out, err = run_command(["pkexec"] + cmd, timeout)
if code != -1 or "introuvable" not in err:
return code, out, err
return (
-1, "",
"Élévation de privilèges impossible.\n"
"Exécutez make setup-sudoers pour configurer wg-quick sans dialogue.\n"
"Ou manuellement :\n"
f" sudo bash -c \"echo '{os.environ.get('USER','<user>')} ALL=(ALL) NOPASSWD: /usr/bin/wg-quick'"
" > /etc/sudoers.d/wgsecure && chmod 440 /etc/sudoers.d/wgsecure\""
)
def _find_askpass() -> str:
"""Cherche un programme askpass graphique disponible."""
candidates = [
os.environ.get("SUDO_ASKPASS", ""),
"/usr/lib/openssh/gnome-ssh-askpass",
"/usr/bin/ssh-askpass",
"/usr/bin/x11-ssh-askpass",
"/usr/libexec/ssh-askpass",
]
for p in candidates:
if p and os.path.isfile(p) and os.access(p, os.X_OK):
return p
# Cherche aussi via shutil
import shutil
for name in ("ssh-askpass", "x11-ssh-askpass", "gnome-ssh-askpass"):
found = shutil.which(name)
if found:
return found
return ""
def wg_available() -> bool:
code, _, _ = run_command(["wg", "--version"])
return code == 0
def wg_quick_available() -> bool:
if is_windows():
code, _, _ = run_command(["wireguard", "--help"])
return code == 0
code, _, _ = run_command(["wg-quick", "--help"])
return code == 0
def install_desktop_entry(icon_png_path: str) -> None:
"""Installe l'entrée .desktop et l'icône PNG pour la barre des tâches GNOME/KDE.
Appelé une fois au démarrage ; sans effet sur Windows."""
if is_windows():
return
try:
# Icône dans le thème hicolor
icon_dir = os.path.expanduser("~/.local/share/icons/hicolor/256x256/apps")
os.makedirs(icon_dir, exist_ok=True)
dest_icon = os.path.join(icon_dir, "wgsecure.png")
import shutil
shutil.copy2(icon_png_path, dest_icon)
# Fichier .desktop
app_dir = os.path.expanduser("~/.local/share/applications")
os.makedirs(app_dir, exist_ok=True)
script = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "..", "main.py")
)
desktop = os.path.join(app_dir, "wgsecure.desktop")
content = (
"[Desktop Entry]\n"
"Type=Application\n"
"Name=WGSecure\n"
"Comment=WireGuard GUI avec MFA TOTP\n"
f"Exec=python3 {script}\n"
"Icon=wgsecure\n"
"Categories=Network;Security;\n"
"StartupWMClass=main\n"
"Terminal=false\n"
)
with open(desktop, "w") as f:
f.write(content)
# Mettre à jour le cache d'icônes si disponible
run_command(["gtk-update-icon-cache", "-f", "-t",
os.path.expanduser("~/.local/share/icons/hicolor")])
except Exception:
pass