← pag

Commit 8f077c9

0
plików
+0
dodanych
-0
usuniętych
@@ -1,7 +1,7 @@
1 1 #!/usr/bin/env python3
2 2 """
3 3 ╔══════════════════════════════════════════════════════════════════════════════╗
4 -║ PAG - Pagan Linux Package Manager v3.3.18 ║
4 +║ PAG - Pagan Linux Package Manager v3.3.19 ║
5 5 ║ Produkcyjny menedżer pakietów – atomowy, bezpieczny, i18n ║
6 6 ╚══════════════════════════════════════════════════════════════════════════════╝
7 7
@@ -55,7 +55,7 @@ import threading, itertools
55 55 import uuid # serialNumber SBOM (CycloneDX)
56 56
57 57 # Wersja klienta – do porównania z repo.json["pag_version"] (self-update)
58 -PAG_VERSION = "3.3.18"
58 +PAG_VERSION = "3.3.19"
59 59 from urllib.error import URLError, HTTPError
60 60
61 61 # =============================================================================
@@ -166,6 +166,19 @@ class DownloadBar:
166 166 GPG_BINARY = shutil.which("gpg2") or shutil.which("gpg") or "gpg"
167 167 GPG_HOME = "/etc/pag/gpg" # izolowany keyring (działa z keyboxd GPG 2.4+)
168 168
169 +def _ssl_ok() -> bool:
170 + """Czy Python ma działający moduł ssl (HTTPS w urllib).
171 +
172 + Bez niego urllib nie zna schematu https i każda operacja sieciowa kończy
173 + się „unknown url type: https” – myląco, bo wygląda jak błąd adresu.
174 + """
175 + try:
176 + import ssl # noqa: F401
177 + return True
178 + except Exception:
179 + return False
180 +
181 +
169 182 def _gpg_run(*args, timeout: int = 30, **kwargs) -> subprocess.CompletedProcess:
170 183 """
171 184 Bezpieczne wywołanie GPG – przechwytuje FileNotFoundError,
@@ -300,10 +313,14 @@ T = {
300 313 "flatpak_info_desc": "Description",
301 314 "flatpak_updated": "Flatpaks updated.",
302 315 "flatpak_usage": "Usage: pag flatpak <search|install|remove|list|update|info> [args]",
316 + "flatpak_scope": "Installation",
317 + "flatpak_menu_hint": "Installed and verified, but this session may not show it in the menu: Flatpak exports are missing from XDG_DATA_DIRS.",
318 + "flatpak_menu_fix": "Add /var/lib/flatpak/exports/share to XDG_DATA_DIRS (and .../exports/bin to PATH) in your session, then re-login – PaganDE does this in session/pagande-session.",
303 319 "key_imported": "Key imported successfully.",
304 320 "key_removed": "Key removed: {}",
305 321 "no_keys": "No trusted GPG keys.",
306 322 "gpg_missing": "GNUPG MISSING – install gnupg and retry",
323 + "ssl_broken": "Python has no working ssl module (HTTPS impossible) – fix the python/openssl packages (e.g. sudo pag install -f python).",
307 324 "key_add_failed": "Key import failed (gpg error) – key was NOT added.",
308 325 "verify_ok": "All {} files intact.",
309 326 "verify_errors": "{} problems found:",
@@ -395,10 +412,14 @@ T = {
395 412 "flatpak_info_desc": "Opis",
396 413 "flatpak_updated": "Flapaki zaktualizowane.",
397 414 "flatpak_usage": "Użycie: pag flatpak <search|install|remove|list|update|info> [args]",
415 + "flatpak_scope": "Instalacja",
416 + "flatpak_menu_hint": "Zainstalowano i zweryfikowano, ale sesja może jej nie pokazać w menu: brak eksportów Flatpaka w XDG_DATA_DIRS.",
417 + "flatpak_menu_fix": "Dodaj /var/lib/flatpak/exports/share do XDG_DATA_DIRS (a .../exports/bin do PATH) w sesji i zaloguj się ponownie – PaganDE robi to w session/pagande-session.",
398 418 "key_imported": "Klucz zaimportowany pomyślnie.",
399 419 "key_removed": "Klucz usunięty: {}",
400 420 "no_keys": "Brak zaufanych kluczy GPG.",
401 421 "gpg_missing": "BRAK GNUPG – zainstaluj gnupg i spróbuj ponownie",
422 + "ssl_broken": "Python bez działającego modułu ssl (HTTPS niemożliwe) – napraw pakiety python/openssl (np. sudo pag install -f python).",
402 423 "key_add_failed": "Import klucza nie powiódł się (błąd gpg) – klucz NIE został dodany.",
403 424 "verify_ok": "Wszystkie {} plików sprawne.",
404 425 "verify_errors": "Znaleziono {} problemów:",
@@ -2805,16 +2826,27 @@ def cmd_self_update():
2805 2826 """Aktualizuje samego klienta pag z repo (podpisany /stable/pag).
2806 2827
2807 2828 Kolejność: pobierz → weryfikacja GPG (+ fingerprint repo) → SHA256 →
2808 - kontrola składni (compile) → backup → atomowe os.replace. Nowa wersja
2809 - idzie do tego samego katalogu (/usr/local/bin/.pag.new), dzięki czemu
2810 - podmiana jest atomowa; jeśli system padnie w trakcie, stary pag zostaje.
2811 - """
2829 + kontrola składni (compile) → backup → atomowe os.replace.
2830 +
2831 + Podmieniamy plik, z którego pag ZOSTAŁ URUCHOMIONY (a nie hardkodowane
2832 + /usr/local/bin/pag): gdy pakiet instaluje /usr/bin/pag, a self-update
2833 + pisał do /usr/local/bin/pag, powstawały DWIE kopie o różnych wersjach
2834 + i zależnie od PATH `pag --version` pokazywał raz jedną, raz drugą."""
2812 2835 repos = get_repos()
2813 2836 if not repos:
2814 2837 print("❌ Brak repozytoriów w konfiguracji.")
2815 2838 return 1
2816 - base = repos[0]
2839 + # Aktualizuj bieżący plik (Python ≥3.9 ustawia __file__ jako ścieżkę
2840 + # bezwzględną). Poza /usr|/usr/local (np. uruchomienie z checkoutu)
2841 + # nie nadpisujemy niczego – wracamy do domyślnej lokalizacji instalacji.
2817 2842 dst = "/usr/local/bin/pag"
2843 + try:
2844 + cand = os.path.realpath(__file__)
2845 + if cand.startswith(("/usr/", "/usr/local/")):
2846 + dst = cand
2847 + except Exception:
2848 + pass
2849 + base = repos[0]
2818 2850 dst_new = dst + ".new"
2819 2851 dst_bak = dst + ".bak"
2820 2852 print(f"🔄 Sprawdzam aktualizację pag z {base}...")
@@ -2915,6 +2947,12 @@ def cmd_self_update():
2915 2947 except OSError:
2916 2948 pass
2917 2949 print(f" ✅ Zainstalowano nowy pag. Stary zachowany jako {dst_bak}")
2950 + # Ostrzeż, gdy w PATH pierwsza jest INNA kopia `pag` – inaczej
2951 + # `pag --version` dalej pokaże starą wersję i wygląda to jak brak update.
2952 + other = shutil.which("pag")
2953 + if other and os.path.realpath(other) != os.path.realpath(dst):
2954 + print(f" ⚠ W PATH pierwszy jest inny pag: {other} (zaktualizowano {dst})")
2955 + print(f" Usuń starą kopię: sudo rm {other}")
2918 2956 print(" Uruchom ponownie pag, aby użyć nowej wersji.")
2919 2957 return 0
2920 2958
@@ -3572,16 +3610,22 @@ def cmd_repo_add(url, name=None):
3572 3610 def cmd_repo_list():
3573 3611 for i, url in enumerate(get_repos(), 1): print(f" {i}. {url}")
3574 3612
3613 +FLATPAK_REMOTE_URL = "https://flathub.org/repo/flathub.flatpakrepo"
3614 +
3575 3615 def _check_flatpak(quiet: bool = False):
3576 3616 if not shutil.which("flatpak"):
3577 3617 if not quiet:
3578 3618 print(f"❌ {_('flatpak_missing')}")
3579 3619 return False
3580 - r = subprocess.run(["flatpak","remotes"], capture_output=True, text=True)
3620 + r = subprocess.run(["flatpak", "remotes"], capture_output=True, text=True)
3581 3621 if "flathub" not in r.stdout:
3582 3622 print(f"⚠ {_('flatpak_adding')}")
3583 - subprocess.run(["flatpak","remote-add","--if-not-exists","flathub",
3584 - "https://flathub.org/repo/flathub.flatpakrepo"], check=False)
3623 + # Jako root dodajemy remote systemowy; bez roota – w instalacji użytkownika,
3624 + # żeby remote i miejsce instalacji były spójne (patrz _flatpak_do_install).
3625 + add = ["flatpak", "remote-add", "--if-not-exists"]
3626 + if os.geteuid() != 0:
3627 + add.append("--user")
3628 + subprocess.run(add + ["flathub", FLATPAK_REMOTE_URL], check=False)
3585 3629 return True
3586 3630
3587 3631 def _spinner(msg: str):
@@ -3832,19 +3876,84 @@ def _flatpak_smart_install(names: list) -> int:
3832 3876 failed += 1
3833 3877 return 1 if failed else 0
3834 3878
3879 +def _flatpak_export_dirs() -> List[str]:
3880 + """Katalogi eksportów Flatpaka (system + użytkownika) obecne na dysku.
3881 +
3882 + Instalacja użytkownika roota (/root/.local/share/flatpak) jest świadomie
3883 + POMIJANA – to wewnętrzny artefakt roota, którego sesja użytkownika nigdy
3884 + nie zobaczy (patrz _flatpak_do_install)."""
3885 + dirs = ["/var/lib/flatpak/exports"]
3886 + home = os.path.expanduser("~")
3887 + if home and home not in ("/root", "/"):
3888 + dirs.append(os.path.join(home, ".local", "share", "flatpak", "exports"))
3889 + return [d for d in dirs if os.path.isdir(d)]
3890 +
3891 +
3892 +def _flatpak_refresh_caches() -> None:
3893 + """Odświeża cache pulpitu i ikon po zmianie w Flatpaku (best-effort).
3894 +
3895 + Flatpak robi to sam, ale tylko gdy `update-desktop-database` jest w PATH.
3896 + Bez tego nowo zainstalowana aplikacja bywa nieobecna w menu."""
3897 + for exports in _flatpak_export_dirs():
3898 + apps = os.path.join(exports, "share", "applications")
3899 + if os.path.isdir(apps) and shutil.which("update-desktop-database"):
3900 + subprocess.run(["update-desktop-database", apps],
3901 + capture_output=True, check=False, timeout=60)
3902 + icons = os.path.join(exports, "share", "icons", "hicolor")
3903 + if os.path.isdir(icons) and shutil.which("gtk-update-icon-cache"):
3904 + subprocess.run(["gtk-update-icon-cache", "-q", "-t", "-f", icons],
3905 + capture_output=True, check=False, timeout=120)
3906 +
3907 +
3908 +def _flatpak_session_sees_exports() -> bool:
3909 + """Czy bieżąca sesja ma eksporty Flatpaka w XDG_DATA_DIRS."""
3910 + raw = os.environ.get("XDG_DATA_DIRS", "")
3911 + dirs = {d for d in raw.split(":") if d} or {"/usr/local/share", "/usr/share"}
3912 + return any(os.path.join(e, "share") in dirs for e in _flatpak_export_dirs())
3913 +
3914 +
3915 +def _flatpak_warn_if_invisible() -> None:
3916 + """Ostrzega, gdy sesja nie widzi eksportów – inaczej wygląda to jak
3917 + „zainstalowało się, ale nie ma go w menu / na PC”."""
3918 + if not _flatpak_session_sees_exports():
3919 + print(f" ⚠ {_('flatpak_menu_hint')}")
3920 + print(f" {_('flatpak_menu_fix')}")
3921 +
3922 +
3835 3923 def _flatpak_do_install(app_id: str) -> int:
3836 - """Wykonuje właściwą instalację flatpaka."""
3924 + """Wykonuje właściwą instalację flatpaka.
3925 +
3926 + Jako root wymuszamy instalację SYSTEMOWĄ (--system). Bez tego `flatpak`
3927 + uruchomiony jako root potrafi zainstalować aplikację w instalacji
3928 + użytkownika roota (/root/.local/share/flatpak) – widocznej dla roota, ale
3929 + nie dla zalogowanego użytkownika. To dokładnie efekt „instaluje, ale nie
3930 + mam tego na PC”. Bez roota próbujemy instalacji systemowej (polkit),
3931 + a w razie niepowodzenia cofamy się do instalacji użytkownika (--user)."""
3837 3932 print(f" {_('flatpak_installing', app_id)}")
3838 - result = subprocess.run(
3839 - ["flatpak", "install", "-y", "flathub", app_id],
3840 - check=False, timeout=600
3841 - )
3842 - if result.returncode == 0:
3843 - print(f" ✅ {_('flatpak_installed', app_id)}")
3844 - return 0
3933 + if os.geteuid() == 0:
3934 + result = subprocess.run(
3935 + ["flatpak", "install", "-y", "--system", "flathub", app_id],
3936 + check=False, timeout=600)
3845 3937 else:
3938 + result = subprocess.run(
3939 + ["flatpak", "install", "-y", "flathub", app_id],
3940 + check=False, timeout=600)
3941 + if result.returncode != 0:
3942 + # Flathub tylko dla użytkownika / brak agenta polkit – instalacja
3943 + # lokalna użytkownika jest lepsza niż twardy błąd.
3944 + subprocess.run(
3945 + ["flatpak", "remote-add", "--if-not-exists", "--user",
3946 + "flathub", FLATPAK_REMOTE_URL], check=False, timeout=60)
3947 + result = subprocess.run(
3948 + ["flatpak", "install", "-y", "--user", "flathub", app_id],
3949 + check=False, timeout=600)
3950 + if result.returncode != 0:
3846 3951 print(f" ❌ {_('download_fail')}: {app_id}")
3847 3952 return 1
3953 + _flatpak_refresh_caches()
3954 + print(f" ✅ {_('flatpak_installed', app_id)}")
3955 + _flatpak_warn_if_invisible()
3956 + return 0
3848 3957
3849 3958 def _flatpak_smart_remove(names: list) -> int:
3850 3959 """Usuwa flatpaki – obsługuje nazwy częściowe."""
@@ -3903,6 +4012,8 @@ def _flatpak_smart_remove(names: list) -> int:
3903 4012 else:
3904 4013 print("❌")
3905 4014 failed += 1
4015 + if not failed:
4016 + _flatpak_refresh_caches()
3906 4017 return 1 if failed else 0
3907 4018
3908 4019 def cmd_flatpak_search(q: str):
@@ -3946,7 +4057,7 @@ def cmd_flatpak_list():
3946 4057 if not _check_flatpak():
3947 4058 return 1
3948 4059 r = subprocess.run(
3949 - ["flatpak", "list", "--columns=application,name,version,origin,installed-size"],
4060 + ["flatpak", "list", "--columns=application,name,version,origin,installation,installed-size"],
3950 4061 capture_output=True, text=True, timeout=10
3951 4062 )
3952 4063 lines = [l for l in r.stdout.strip().split("\n") if l.strip()]
@@ -3958,9 +4069,11 @@ def cmd_flatpak_list():
3958 4069 parts = line.split("\t")
3959 4070 if len(parts) >= 3:
3960 4071 app_id, name, version = parts[0], parts[1], parts[2]
3961 - size = parts[4] if len(parts) > 4 else ""
4072 + scope = parts[4] if len(parts) > 4 else ""
4073 + size = parts[5] if len(parts) > 5 else ""
3962 4074 size_str = f" ({size})" if size else ""
3963 - print(f" 📦 {_c('bold', name)} {version}{size_str}")
4075 + scope_str = f" [{scope}]" if scope else ""
4076 + print(f" 📦 {_c('bold', name)} {version}{scope_str}{size_str}")
3964 4077 print(f" {_c('dim', app_id)}")
3965 4078 return 0
3966 4079
@@ -3971,6 +4084,7 @@ def cmd_flatpak_update():
3971 4084 print(" 🔄 Aktualizacja flatpaków...")
3972 4085 result = subprocess.run(["flatpak", "update", "-y"], check=False, timeout=600)
3973 4086 if result.returncode == 0:
4087 + _flatpak_refresh_caches()
3974 4088 print(f" ✅ {_('flatpak_updated')}")
3975 4089 return result.returncode
3976 4090
@@ -4441,16 +4555,39 @@ def cmd_deploy_cleanup(keep: int = 3):
4441 4555 # POMOCNICZE
4442 4556 # =============================================================================
4443 4557
4558 +# Pakiety dostarczane przez bazowy system (zawsze "zainstalowane"). Wchodzą do
4559 +# rootfs z ISO przez data.tar.xz, więc NIE ma ich w repo.json ani w bazie pag –
4560 +# każda zależność od nich (glibc, bash, python3…) wyglądałaby na "brakującą".
4561 +# Współdzielone przez rozwijanie i weryfikację zależności.
4562 +SYSTEM_BASE = {
4563 + "glibc", "libc", "gcc", "g++", "make", "binutils", "coreutils", "bash",
4564 + "linux-api-headers", "kernel-headers", "zlib", "pkg-config", "pkgconf",
4565 + "tar", "gzip", "xz", "bzip2", "findutils", "grep", "sed", "gawk", "awk",
4566 + "diffutils", "patch", "file", "m4", "perl", "python3", "sh",
4567 +}
4568 +
4569 +
4444 4570 def _resolve_deps(names, repo, installed):
4445 4571 resolved, visited = [], set()
4446 4572 missing = [] # zależności których nie ma ani w repo ani zainstalowane
4447 4573
4448 - def visit(name):
4574 + def _is_base(dep):
4575 + """True, gdy zależność dostarcza bazowy system (także przez provides)."""
4576 + return dep in SYSTEM_BASE \
4577 + or _resolve_provides(dep, repo, installed) in SYSTEM_BASE
4578 +
4579 + def visit(name, explicit=False):
4449 4580 if name in visited: return
4450 4581
4451 4582 # Rozwijanie wirtualnych zależności przez provides
4452 4583 target = _resolve_provides(name, repo, installed)
4453 4584
4585 + # Zależność dostarczana przez bazowy system – nie ma jej w repo ani w
4586 + # bazie pag, więc nie "brakuje" jej i nie próbujemy jej instalować.
4587 + # Wyjątek: nazwa podana wprost przez użytkownika (explicit=True).
4588 + if not explicit and target in SYSTEM_BASE:
4589 + return
4590 +
4454 4591 if target in visited: return
4455 4592 visited.add(target)
4456 4593 if target in repo:
@@ -4458,6 +4595,9 @@ def _resolve_deps(names, repo, installed):
4458 4595 real_dep = _resolve_provides(dep, repo, installed)
4459 4596 real_target = real_dep if real_dep in repo else dep
4460 4597
4598 + if _is_base(dep):
4599 + continue
4600 +
4461 4601 # Sprawdź czy zależność jest dostępna
4462 4602 if real_target not in installed and real_target not in repo:
4463 4603 if dep not in missing:
@@ -4474,7 +4614,7 @@ def _resolve_deps(names, repo, installed):
4474 4614 resolved.append(target)
4475 4615
4476 4616 for name in names:
4477 - visit(name)
4617 + visit(name, explicit=True)
4478 4618
4479 4619 # Zwróć brakujące (do sprawdzenia przez wywołującego)
4480 4620 return resolved, missing
@@ -4484,13 +4624,6 @@ def _verify_dependencies(to_install: list, repo: dict, installed: dict) -> int:
4484 4624 Sprawdza czy wszystkie zależności pakietów do instalacji są spełnione.
4485 4625 Zwraca liczbę brakujących zależności.
4486 4626 """
4487 - # Pakiety dostarczane przez bazowy system (zawsze "zainstalowane")
4488 - SYSTEM_BASE = {
4489 - "glibc", "libc", "gcc", "g++", "make", "binutils", "coreutils", "bash",
4490 - "linux-api-headers", "kernel-headers", "zlib", "pkg-config", "pkgconf",
4491 - "tar", "gzip", "xz", "bzip2", "findutils", "grep", "sed", "gawk", "awk",
4492 - "diffutils", "patch", "file", "m4", "perl", "python3", "sh",
4493 - }
4494 4627 all_missing = []
4495 4628 all_warnings = []
4496 4629
@@ -4918,6 +5051,11 @@ def main():
4918 5051 cmd = sys.argv[1]
4919 5052 args = sys.argv[2:]
4920 5053
5054 + # Python bez modułu ssl = brak HTTPS w urllib. Powiedz to wprost, zamiast
5055 + # pokazywać mylące „unknown url type: https” przy każdej operacji sieciowej.
5056 + if not _ssl_ok():
5057 + print(f" ⚠ {_('ssl_broken')}", file=sys.stderr)
5058 +
4921 5059 # --- Komendy TYLKO DO ODCZYTU (nie wymagają roota) ---
4922 5060 READ_ONLY = {
4923 5061 "list": lambda: cmd_list("--installed" in args),