Commit 49cfe15
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.3 ║
4
+║ PAG - Pagan Linux Package Manager v3.3.4 ║
5
5
║ Produkcyjny menedżer pakietów – atomowy, bezpieczny, i18n ║
6
6
╚══════════════════════════════════════════════════════════════════════════════╝
7
7
@@ -33,7 +33,7 @@ from urllib.request import urlopen, Request
33
33
import threading, itertools
34
34
35
35
# Wersja klienta – do porównania z repo.json["pag_version"] (self-update)
36
-PAG_VERSION = "3.3.3"
36
+PAG_VERSION = "3.3.4"
37
37
from urllib.error import URLError, HTTPError
38
38
39
39
# =============================================================================
@@ -384,6 +384,9 @@ GPG_KEYRING = "/etc/pag/trusted-keys.gpg"
384
384
STAGING_DIR = "/.pag_staging" # na tej samej partycji co / (unikamy EXDEV)
385
385
PKG_EXT = ".pkg.tar.xz"
386
386
REPO_CACHE_TTL = 3600
387
+MAX_PKG_SIZE = 2 * 1024 * 1024 * 1024 # 2 GB – maksymalny rozmiar paczki
388
+ALLOWED_PKG_RE = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9._+@-]*$')
389
+MAX_PKG_SIZE = 2 * 1024 * 1024 * 1024 # 2 GB – maksymalny rozmiar paczki
387
390
388
391
# =============================================================================
389
392
# IMMUTABLE OS – DEPLOYMENTY
@@ -659,32 +662,45 @@ def _db_count_files() -> int:
659
662
# =============================================================================
660
663
661
664
class DatabaseLock:
665
+ """Blokada oparta na PID-file – niezawodna, bez flock."""
662
666
def __init__(self):
663
- self._fd = None
667
+ self._acquired = False
664
668
def __enter__(self):
665
- self._fd = open(LOCK_FILE, "w")
666
- try:
667
- fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
668
- except (IOError, OSError):
669
- print(f"❌ {_('db_locked')}", file=sys.stderr)
670
- print(f" {_('db_lock_hint', LOCK_FILE)}", file=sys.stderr)
671
- sys.exit(1)
669
+ os.makedirs(os.path.dirname(LOCK_FILE), exist_ok=True)
670
+ if os.path.exists(LOCK_FILE):
671
+ try:
672
+ old_pid = int(open(LOCK_FILE).read().strip())
673
+ os.kill(old_pid, 0) # sygnał 0 = sprawdź czy proces żyje
674
+ except (ValueError, OSError, ProcessLookupError):
675
+ # Stary PID nie żyje – usuwamy nieświeżą blokadę
676
+ try:
677
+ os.remove(LOCK_FILE)
678
+ except OSError:
679
+ pass
680
+ else:
681
+ print(f"❌ {_('db_locked')}", file=sys.stderr)
682
+ print(f" {_('db_lock_hint', LOCK_FILE)}", file=sys.stderr)
683
+ sys.exit(1)
684
+ with open(LOCK_FILE, "w") as f:
685
+ f.write(str(os.getpid()))
686
+ self._acquired = True
672
687
return self
673
688
def __exit__(self, *args):
674
- if self._fd:
675
- fcntl.flock(self._fd, fcntl.LOCK_UN)
676
- self._fd.close()
689
+ if self._acquired:
677
690
try:
678
691
os.remove(LOCK_FILE)
679
692
except OSError:
680
- pass # plik mógł zostać usunięty przez inny proces
693
+ pass
681
694
682
695
# =============================================================================
683
696
# POMOCNICZE
684
697
# =============================================================================
685
698
686
699
687
-_ALLOWED_PREFIXES = ("/usr/", "/etc/", "/var/", "/opt/")
700
+_ALLOWED_PREFIXES = ("/usr/", "/etc/", "/var/", "/opt/",
701
+ # Pliki wewnętrzne paczki .pkg.tar.xz
702
+ "metadata.json", "data.tar.xz", "hooks/",
703
+ "sums.json")
688
704
689
705
def _check_path_safety(name: str) -> bool:
690
706
for prefix in _ALLOWED_PREFIXES:
@@ -696,16 +712,7 @@ def _check_path_safety(name: str) -> bool:
696
712
def _validate_pkg_name(name):
697
713
return bool(ALLOWED_PKG_RE.match(name))
698
714
699
-def _acquire_lock():
700
- import fcntl
701
- os.makedirs(os.path.dirname(LOCK_FILE), exist_ok=True)
702
- lf = open(LOCK_FILE, "w")
703
- try:
704
- fcntl.flock(lf, fcntl.LOCK_EX | fcntl.LOCK_NB)
705
- return lf
706
- except (IOError, OSError):
707
- print(f" {_("sec_locked")}")
708
- sys.exit(1)
715
+
709
716
710
717
def _audit(msg):
711
718
from datetime import datetime, timezone
@@ -1366,7 +1373,6 @@ def cmd_rollback():
1366
1373
# =============================================================================
1367
1374
1368
1375
def cmd_install(package_names, as_dep=False):
1369
- _lock = _acquire_lock()
1370
1376
ensure_dirs()
1371
1377
installed_db = load_json(INSTALLED_DB)
1372
1378
world = load_world()
@@ -1670,11 +1676,33 @@ def cmd_self_update():
1670
1676
f.write(sig)
1671
1677
1672
1678
# Weryfikacja podpisu GPG – bez tego nie instalujemy
1679
+ insecure = os.environ.get("PAG_INSECURE", "") == "1"
1673
1680
res = _gpg_run("--no-default-keyring", "--keyring", GPG_KEYRING,
1674
1681
"--verify", tmp_sig, tmp_pag, capture_output=True, text=True)
1675
1682
if res.returncode != 0:
1676
- print(" ❌ Nieprawidłowy podpis aktualizacji – nie aktualizuję.")
1677
- return 1
1683
+ # Automatyczny import klucza (TOFU) – jak w _verify_repo_sig
1684
+ _stderr = (res.stderr or "")
1685
+ if "public key" in _stderr.lower() and "no public key" in _stderr.lower():
1686
+ try:
1687
+ with urlopen(Request(f"{base}/paganos.asc", headers={"User-Agent": "pag/3.0"}), timeout=20) as r:
1688
+ keydata = r.read()
1689
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".asc") as tmp:
1690
+ tmp.write(keydata)
1691
+ tmp.flush()
1692
+ _gpg_run("--no-default-keyring", "--keyring", GPG_KEYRING,
1693
+ "--import", tmp.name, capture_output=True, timeout=30)
1694
+ os.unlink(tmp.name)
1695
+ print(f" 🔑 Importowano klucz repo z {base}/paganos.asc")
1696
+ res = _gpg_run("--no-default-keyring", "--keyring", GPG_KEYRING,
1697
+ "--verify", tmp_sig, tmp_pag, capture_output=True, text=True)
1698
+ except Exception:
1699
+ pass
1700
+ if res.returncode != 0:
1701
+ if insecure:
1702
+ print(" ⚠ Nieprawidłowy podpis aktualizacji (PAG_INSECURE – ignoruję)")
1703
+ else:
1704
+ print(" ❌ Nieprawidłowy podpis aktualizacji – nie aktualizuję.")
1705
+ return 1
1678
1706
1679
1707
m = re.search(rb"v\d+\.\d+\.\d+", data[:3000])
1680
1708
new_ver = m.group(0).decode().lstrip("v") if m else "?"