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),