119 lines
3.9 KiB
Python
119 lines
3.9 KiB
Python
"""Gestion des mises à jour AUR.
|
|
|
|
Les paquets AUR sont toujours installés immédiatement (jamais reportés à
|
|
l'extinction) car le build requiert un utilisateur actif et un accès réseau.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import pwd
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
from package_manager import Package, InstallResult
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_AUR_HELPERS = ["paru", "yay"]
|
|
|
|
|
|
def detect_aur_helper() -> str | None:
|
|
for helper in _AUR_HELPERS:
|
|
if shutil.which(helper):
|
|
return helper
|
|
return None
|
|
|
|
|
|
def detect_active_user() -> str | None:
|
|
"""Retourne le nom de l'utilisateur avec une session graphique active."""
|
|
try:
|
|
result = subprocess.run(
|
|
["loginctl", "list-sessions", "--no-legend"],
|
|
capture_output=True, text=True, timeout=5,
|
|
)
|
|
for line in result.stdout.strip().splitlines():
|
|
parts = line.split()
|
|
if len(parts) < 3:
|
|
continue
|
|
session_id, _, user = parts[0], parts[1], parts[2]
|
|
if user == "root":
|
|
continue
|
|
session_type = subprocess.run(
|
|
["loginctl", "show-session", session_id, "-p", "Type", "--value"],
|
|
capture_output=True, text=True, timeout=3,
|
|
).stdout.strip()
|
|
if session_type in ("x11", "wayland", "mir"):
|
|
return user
|
|
except Exception as e:
|
|
logger.debug(f"Impossible de détecter l'utilisateur actif : {e}")
|
|
return None
|
|
|
|
|
|
def _user_env(user: str) -> dict:
|
|
pw = pwd.getpwnam(user)
|
|
uid = pw.pw_uid
|
|
env = {
|
|
"HOME": pw.pw_dir,
|
|
"USER": user,
|
|
"LOGNAME": user,
|
|
"XDG_RUNTIME_DIR": f"/run/user/{uid}",
|
|
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
# Évite que sudo bloque sans TTY si le helper essaie d'appeler sudo pacman
|
|
"SUDO_ASKPASS": "/bin/false",
|
|
}
|
|
# Socket DBUS de session (requis par paru/yay pour le build)
|
|
dbus_socket = Path(f"/run/user/{uid}/bus")
|
|
if dbus_socket.exists():
|
|
env["DBUS_SESSION_BUS_ADDRESS"] = f"unix:path={dbus_socket}"
|
|
return env
|
|
|
|
|
|
def check_aur_updates(user: str, helper: str) -> list[Package]:
|
|
"""Liste les mises à jour AUR disponibles sans rien installer."""
|
|
try:
|
|
result = subprocess.run(
|
|
["sudo", "-u", user, "-H", helper, "-Qua", "--color", "never"],
|
|
capture_output=True, text=True, timeout=120,
|
|
env=_user_env(user),
|
|
)
|
|
packages = []
|
|
for line in result.stdout.strip().splitlines():
|
|
parts = line.split()
|
|
if len(parts) >= 4 and parts[2] == "->":
|
|
packages.append(Package(
|
|
name=parts[0],
|
|
old_version=parts[1],
|
|
new_version=parts[3],
|
|
))
|
|
return packages
|
|
except Exception as e:
|
|
logger.error(f"Échec de la vérification AUR : {e}")
|
|
return []
|
|
|
|
|
|
def install_aur_packages(user: str, helper: str, names: list[str]) -> InstallResult:
|
|
"""Installe des paquets AUR spécifiques en tant qu'utilisateur actif."""
|
|
if not names:
|
|
return InstallResult(success=True)
|
|
try:
|
|
result = subprocess.run(
|
|
[
|
|
"sudo", "-u", user, "-H",
|
|
helper, "-S", "--noconfirm", "--noprogressbar",
|
|
"--color", "never",
|
|
] + names,
|
|
capture_output=True, text=True, timeout=900,
|
|
env=_user_env(user),
|
|
)
|
|
if result.returncode == 0:
|
|
return InstallResult(success=True, installed=names)
|
|
return InstallResult(
|
|
success=False,
|
|
failed=names,
|
|
error=result.stderr[-800:],
|
|
)
|
|
except Exception as e:
|
|
logger.exception(f"Erreur lors de l'installation AUR : {e}")
|
|
return InstallResult(success=False, failed=names, error=str(e))
|