🔒 Repository is read-only – file editing is disabled.
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)."