🔒 Repository is read-only – file editing is disabled.

PaganLinux/install-pag-fix.sh main

231 linii Raw ← Powrót
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
#!/bin/bash
# =============================================================================
# install-pag-fix.sh – bootstrap klienta `pag` na systemie, gdzie pag jest
# zepsuty lub stary (brak HTTPS przez brak modułu ssl, wersja bez poprawek).
#
# Co robi (w tej kolejności):
#   1. instaluje plik TAM, GDZIE ROBI TO PAKIET (/usr/bin/pag) – bez drugiej
#      kopii w /usr/local/bin/pag, bo „cień” powoduje, że `pag --version`
#      pokazuje inną wersję niż pakiet, a `pag upgrade` nie ma czego ruszyć;
#   2. normalizuje końce linii (CRLF → LF) i twardo sprawdza shebang – inaczej
#      kernel przekazuje „python3\r” i dostajemy: env: 'python3\r': No such file;
#   3. REJESTRUJE zainstalowaną wersję w bazie pakietów (/var/lib/pag), żeby
#      pag był zarządzany tak jak każdy inny pakiet i dał się aktualizować
#      później przez:   sudo pag update    →  sudo pag upgrade
#      Rejestracja jest OFFLINE (bez repo, bez sieci) i ustawia wersję na tę,
#      którą naprawdę wgraliśmy – dzięki temu `pag upgrade` nie zrobi
#      downgrade'u do starszego paga z repo, a podciągnie go, gdy wyjdzie nowszy.
#
# Użycie:
#   sudo ./install-pag-fix.sh [--src ŚCIEŻKA] [--keep-local] [--no-register]
#                             [--from-repo]
#
#   --src PATH      źródłowy skrypt pag (domyślnie: ./pag/pag obok tego skryptu)
#   --keep-local    świadomie NIE usuwaj cienia /usr/local/bin/pag
#   --no-register   nie dotykaj bazy pakietów (tylko podmiana pliku)
#   --from-repo     zamiast rejestracji offline uruchom `pag update` +
#                   `pag install -f pag` (pełna ścieżka pakietowa: files.db,
#                   hooki, podpis). Wymaga repo z pag >= wgranej wersji,
#                   bo `-f` zainstaluje to, co jest w repo (możliwy downgrade).
# =============================================================================
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SRC="$SCRIPT_DIR/pag/pag"
DEST="/usr/bin/pag"          # ścieżka zarządzana przez pakiet (receptura: install -Dm755 pag /usr/bin/pag)
KEEP_LOCAL=0
REGISTER=1
FROM_REPO=0
PAG_DB_DIR="${PAG_DB_DIR:-/var/lib/pag}"

usage() { sed -n '2,34p' "$0"; }

while [[ $# -gt 0 ]]; do
    case "$1" in
        --src) SRC="$2"; shift 2 ;;
        --keep-local) KEEP_LOCAL=1; shift ;;
        --no-register) REGISTER=0; shift ;;
        --from-repo) FROM_REPO=1; shift ;;
        -h|--help) usage; exit 0 ;;
        *) echo "Nieznany argument: $1" >&2; exit 2 ;;
    esac
done

[[ $EUID -eq 0 ]] || { echo "❌ Wymagane uprawnienia root (sudo)." >&2; exit 1; }
[[ -f "$SRC" ]] || { echo "❌ Nie znaleziono źródła: $SRC (użyj --src /ścieżka/pag)" >&2; exit 1; }
grep -q 'PAG_VERSION = ' "$SRC" || { echo "❌ $SRC nie wygląda na skrypt pag." >&2; exit 1; }

VER_SRC="$(grep -m1 'PAG_VERSION = ' "$SRC" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo '?')"
VER_OLD=""
[[ -x "$DEST" ]] && VER_OLD="$("$DEST" --version 2>/dev/null || true)"

# ── 1. Normalizacja końców linii ────────────────────────────────────────────
# Źródło z CRLF psuje shebang: kernel przekazuje „python3\r” i dostajemy
#   env: 'python3\r': No such file or directory
# Zawsze instalujemy plik z LF, niezależnie od tego, jak zapisano źródło.
_tmp="$(mktemp)"; chmod 600 "$_tmp"
trap 'rm -f "$_tmp"' EXIT
if grep -q $'\r' "$SRC"; then
    echo "⚠ Źródło ma końce linii CRLF – normalizuję do LF"
fi
sed 's/\r$//' "$SRC" > "$_tmp"
head -1 "$_tmp" | grep -q '^#!/usr/bin/env python3$' \
    || { echo "❌ Po normalizacji shebang nieprawidłowy: $(head -1 "$_tmp" | cat -A)" >&2; exit 1; }

# ── 2. Kontrola składni PRZED podmianą ──────────────────────────────────────
python3 -m py_compile "$_tmp" \
    || { echo "❌ Błąd składni w źródle – nie instaluję uszkodzonego pliku." >&2; exit 1; }
rm -rf "$(dirname "$_tmp")/__pycache__" 2>/dev/null || true

# ── 3. Instalacja atomowa do ścieżki pakietu ────────────────────────────────
echo "→ Instaluję pag $VER_SRC$DEST${VER_OLD:+ (poprzednio: $VER_OLD)}"
install -Dm755 "$_tmp" "$DEST.new"
mv -f "$DEST.new" "$DEST"
hash -r 2>/dev/null || true

# ── 4. Zdejmij cień innej kopii `pag` z PATH ────────────────────────────────
# Każda INNA kopia przesłania wersję pakietową (typowy efekt
# „pag --version pokazuje inną wersję niż zainstalowany pakiet”).
DEST_REAL="$(readlink -f "$DEST")"
if [[ $KEEP_LOCAL -eq 0 ]]; then
    for cand in /usr/local/bin/pag /usr/local/sbin/pag /usr/sbin/pag /bin/pag; do
        [[ -e "$cand" ]] || continue
        [[ "$(readlink -f "$cand" 2>/dev/null)" == "$DEST_REAL" ]] && continue
        rm -f "$cand"
        echo "   ✓ Usunięto cień: $cand"
    done
    while read -r p; do
        [[ -n "$p" ]] || continue
        [[ "$(readlink -f "$p" 2>/dev/null)" == "$DEST_REAL" ]] && continue
        rm -f "$p"
        echo "   ✓ Usunięto cień: $p"
    done < <(type -a -p pag 2>/dev/null | sort -u || true)
fi

# ── 5. Rejestracja w bazie pakietów (offline) ───────────────────────────────
# Ustawiamy w installed.json wersję zgodną z faktycznie wgranym plikiem.
# `pag upgrade` bierze listę do aktualizacji z installed.json, więc bez tego
# pag nie byłby „widziany” przez aktualizacje (albo cofałby się do starszej
# wersji z repo). Bez sieci, bez GPG – czysty, lokalny wpis.
if [[ $REGISTER -eq 1 && $FROM_REPO -eq 0 ]]; then
    python3 - "$DEST" "$VER_SRC" "$PAG_DB_DIR" <<'PY'
import datetime, hashlib, json, os, shutil, sys

dest, ver, db_dir = sys.argv[1], sys.argv[2], sys.argv[3]
db_path = os.path.join(db_dir, "installed.json")
world_path = os.path.join(db_dir, "world")

with open(dest, "rb") as fh:
    sha = hashlib.sha256(fh.read()).hexdigest()
size = os.path.getsize(dest)

os.makedirs(db_dir, exist_ok=True)
db = {}
if os.path.exists(db_path):
    try:
        with open(db_path, encoding="utf-8") as fh:
            db = json.load(fh)
        if not isinstance(db, dict):
            db = {}
    except (OSError, ValueError):
        db = {}
    # Kopia bezpieczeństwa przed edycją (baza jest krytyczna dla paga).
    try:
        shutil.copy2(db_path, db_path + ".bak")
    except OSError:
        pass

entry = dict(db.get("pag") or {})
entry.update({
    "version": ver,
    "release": entry.get("release", 1),
    "description": entry.get("description") or
        "PaganOS package manager – atomowa instalacja, weryfikacja GPG, i18n (PL/EN), rollback",
    "dependencies": ["python3", "gnupg"],
    "size_bytes": size,
    "sha256": sha,
    "installed_at": datetime.datetime.now().isoformat(),
})
entry.setdefault("repo", "local")
entry.setdefault("provides", [])
entry.setdefault("provides_so", [])
entry.setdefault("requires_so", [])
db["pag"] = entry

new = db_path + ".new"
with open(new, "w", encoding="utf-8") as fh:
    json.dump(db, fh, indent=2)
os.replace(new, db_path)

# `world` = pakiety zainstalowane świadomie; pag ma tam być, żeby nie został
# uznany za sierotę przy `pag autoremove`.
names = set()
if os.path.exists(world_path):
    try:
        names = {l.strip() for l in open(world_path, encoding="utf-8") if l.strip()}
    except OSError:
        names = set()
if "pag" not in names:
    names.add("pag")
    with open(world_path, "w", encoding="utf-8") as fh:
        fh.write("\n".join(sorted(names)) + "\n")

print(f"   ✓ Zarejestrowano pag {ver} w bazie pakietów ({db_path})")
PY
elif [[ $FROM_REPO -eq 1 ]]; then
    echo "→ Rejestruję przez repo (pag update + pag install -f pag)..."
    "$DEST" update || true
    "$DEST" install -f pag
elif [[ $REGISTER -eq 0 ]]; then
    echo "   (pominięto rejestrację w bazie pakietów – --no-register)"
fi

# ── 6. Weryfikacja ──────────────────────────────────────────────────────────
echo
echo "✅ Zainstalowano: $DEST  ($("$DEST" --version))"
if python3 -c 'import ssl' 2>/dev/null; then
    echo "   ✓ HTTPS (moduł ssl) działa"
else
    echo "   ⚠ Python bez działającego modułu ssl – HTTPS w pag nie zadziała."
    echo "     Odtwórz openssl: sudo pag install -f openssl"
fi

other="$(type -a -p pag 2>/dev/null | head -1 || true)"
if [[ -n "$other" && "$(readlink -f "$other")" != "$DEST_REAL" ]]; then
    echo "   ⚠ W PATH pierwszy jest inny pag: $other  →  usuń go (sudo rm $other)"
fi

echo
echo "Test: składnia + symulacja braku GPG..."
python3 -m py_compile "$DEST" && echo "   ✓ py_compile OK"
GNUPGHOME=/tmp/pag-gpg-test PAG_INSECURE=1 python3 - "$DEST" <<'PY'
import os, sys, tempfile
src = open(sys.argv[1], encoding="utf-8").read()
idx = src.find('if __name__ == "__main__":')
assert idx != -1, "nie znaleziono bloku __main__"
# __file__ musi być w przestrzeni exec – _load_lang_files() używa go przy imporcie
ns = {"__file__": os.path.abspath(sys.argv[1]), "__name__": "pag"}
exec(compile(src[:idx], "pag", "exec"), ns)
ns["GPG_BINARY"] = "/nonexistent/gpg"
ns["GPG_HOME"] = tempfile.mkdtemp()
res = ns["_gpg_run"]("--verify", "a.asc", "b", capture_output=True, text=True, timeout=10)
assert isinstance(res.stderr, str), "stderr nie jest str!"
ok, fp = ns["_gpg_verify_fp"]("a.asc", "b")
assert ok is False
print("   ✓ OK – brak TypeError przy braku gpg")
PY

# ── 7. Co dalej ─────────────────────────────────────────────────────────────
echo
if [[ $REGISTER -eq 1 && $FROM_REPO -eq 0 ]]; then
    echo "pag jest teraz zarządzany przez menedżera pakietów. Gdy repo opublikuje"
    echo "nowszą wersję paga, wystarczy:"
    echo "    sudo pag update      # pokaże: pag X → Y"
    echo "    sudo pag upgrade     # podciągnie paga razem z resztą systemu"
    echo
    echo "Jeśli chcesz od razu pełną ścieżkę pakietową (files.db + hooki + podpis),"
    echo "po wypchnięciu 3.3.20 do repo uruchom ten skrypt z --from-repo."
else
    echo "Aby pag był zarządzany przez menedżera pakietów, uruchom bez --no-register."
fi
echo "W tej powłoce odśwież cache poleceń:  hash -r   (albo otwórz nowy terminal)."