#!/bin/bash # ============================================================================= # build-repo.sh – budowa pakietów do repo PAG na PC (w chrootcie PaganOS) # ----------------------------------------------------------------------------- # DLACZEGO NA PC, A NIE NA VPS: # VPS buduje przez builder-chroot.sh w chrootcie DEBIANA (suite trixie), więc # pakiety linkują się z Debianowymi bibliotekami. To właśnie stąd brały się # rozjazdy ABI w repo: `libgsf` z odwołaniem do xmlNanoHTTPClose (w PaganLinux # nie ma – libxml2 2.16 tego nie eksportuje) i `libavcodec` z # rsvg_handle_render_document (nasze librsvg to 2.40.21, Debian ma 2.46+). # Budowa w rootfsie PaganOS daje pakiety spójne z dystrybucją – a PC jest # szybszy, więc i tak wygrywa. # # Użycie (wymaga roota): # sudo ./build-repo.sh --prepare # utwórz rootfs buildera i zbuduj domyślne pakiety # sudo ./build-repo.sh kernel nvidia-kernel # zbuduj wybrane pakiety # sudo ./build-repo.sh --rootfs /mnt/pagan-build --out ./repo-out kernel # sudo ./build-repo.sh --sign --gpg-key ABCD1234 kernel # sudo ./build-repo.sh --upload user@vps:/var/www/repo.paganlinux.eu/stable kernel # # Kolejność ma znaczenie przy NVIDIA: `kernel` musi pójść pierwszy (skrypt sam # instaluje go w rootfsie buildera, bo z niego korzysta `nvidia-kernel`). # # Czego ten skrypt NIE robi: nie publikuje repo.json (to robi `pagsync --gen-repo` # na VPS – patrz instrukcja wypisywana na końcu). # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOTFS="/mnt/pagan-build" # rootfs buildera (chroot) PAGAN_SRC_ROOT="${PAGAN_ROOT:-/mnt/pagan}" # źródłowy rootfs (obraz) do skopiowania OUT_DIR="$SCRIPT_DIR/repo-out" SRC_CACHE="/var/cache/pagbuild/sources" DO_PREPARE=0 DO_SIGN=0 GPG_KEY="" UPLOAD="" PKGS=() # Domyślnie: rolling kernel + gotowe moduły NVIDIA (to, czego brakuje w repo). DEFAULT_PKGS=(kernel nvidia-kernel) usage() { sed -n '3,/^set -euo pipefail$/p' "$0" | grep -v '^set -euo pipefail$'; exit "${1:-0}"; } while [[ $# -gt 0 ]]; do case "$1" in --rootfs) ROOTFS="$2"; shift 2 ;; --out) OUT_DIR="$2"; shift 2 ;; --pagansrc) PAGAN_SRC_ROOT="$2"; shift 2 ;; --prepare) DO_PREPARE=1; shift ;; --sign) DO_SIGN=1; shift ;; --gpg-key) GPG_KEY="$2"; shift 2 ;; --upload) UPLOAD="$2"; shift 2 ;; -h|--help) usage 0 ;; -*) echo "Nieznana opcja: $1" >&2; usage 2 ;; *) PKGS+=("$1"); shift ;; esac done [[ $EUID -eq 0 ]] || { echo "❌ Wymagane uprawnienia root (chroot)." >&2; exit 1; } [[ -x "$SCRIPT_DIR/pagbuild" ]] || { echo "❌ Brak wykonywalnego $SCRIPT_DIR/pagbuild" >&2; exit 1; } [[ ${#PKGS[@]} -gt 0 ]] || PKGS=("${DEFAULT_PKGS[@]}") info() { printf '\033[1;36m▸ %s\033[0m\n' "$*"; } ok() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; } warn() { printf '\033[1;33m! %s\033[0m\n' "$*"; } die() { printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2; exit 1; } ABI_FAILED=() # ── Montowanie systemowych katalogów w chrootcie (dla `pag install`) ── _mounted=0 _mount_chroot() { local r="$1" for d in dev proc sys; do mountpoint -q "$r/$d" 2>/dev/null || mount --bind "/$d" "$r/$d" 2>/dev/null || true done _mounted=1 } _umount_chroot() { [[ $_mounted -eq 1 ]] || return 0 local r="$1" for d in sys proc dev; do mountpoint -q "$r/$d" 2>/dev/null && umount -l "$r/$d" 2>/dev/null || true done _mounted=0 } # ── (1) Przygotowanie rootfsu buildera ─────────────────────────────────────── # Kopiujemy GOTOWY rootfs PaganOS (ma już gcc/make/bison/flex/perl) zamiast # stawiać od zera rootfs.sh (godziny). Wyłączamy sources/ – to 10+ GB, a pagbuild # sam ściąga źródła do /var/cache/pagbuild/work wewnątrz chroota. if [[ $DO_PREPARE -eq 1 && ! -d "$ROOTFS" ]]; then [[ -d "$PAGAN_SRC_ROOT/usr/bin" ]] || die "Brak $PAGAN_SRC_ROOT – podaj --pagansrc" info "Tworzę rootfs buildera: $ROOTFS (z $PAGAN_SRC_ROOT, bez sources/)" df -h "$(dirname "$ROOTFS")" | tail -1 command -v rsync >/dev/null || die "Brak rsync (potrzebny do kopii rootfsu)" mkdir -p "$ROOTFS" rsync -aHAX --info=progress2 \ --exclude='/sources/' --exclude='/tmp/' --exclude='/var/cache/pagbuild/' \ --exclude='/proc/' --exclude='/sys/' --exclude='/dev/' --exclude='/run/' \ "$PAGAN_SRC_ROOT/" "$ROOTFS/" || die "Kopiowanie rootfsu nieudane" ok "Rootfs buildera gotowy: $ROOTFS" fi [[ -d "$ROOTFS/usr/bin" ]] || die "Brak rootfsu buildera ($ROOTFS). Użyj --prepare." # ── (2) Narzędzia budowy, których brakuje w obrazie ──────────────────────── # binutils: ISO ma tylko binutils-pass1 w /tools – w /usr/bin NIE MA `ld`, więc # nic się nie zlinkuje (dotyczy też DKMS na docelowym systemie). # libelf: jądro robi `pkg-config --exists libelf` (objtool dla # CONFIG_UNWINDER_ORC=y). Obraz ma libelf.so, ale BEZ libelf.pc → w chrootcie # budowa jądra pada na „Cannot generate ORC metadata … please install # libelf-dev”. W buildzie ISO to przechodziło, bo objtool buduje się # narzędziami HOSTA (a host ma libelf), nie rootfsu. # bc/bison/flex: wymagane przez Kbuild; w obrazie są, ale gdy kiedyś znikną… _pag_install_in_rootfs() { local r="$1"; shift [[ $# -gt 0 ]] || return 0 cp -L /etc/resolv.conf "$r/etc/resolv.conf" 2>/dev/null || true _mount_chroot "$r" trap '_umount_chroot "$ROOTFS"' EXIT chroot "$r" /usr/bin/env PAG_YES=1 /usr/bin/pag install -y "$@" \ || warn " nie udało się doinstalować: $*" _umount_chroot "$r" trap - EXIT } for _spec in "binutils:/usr/bin/ld" \ "libelf:/usr/lib/pkgconfig/libelf.pc" \ "bc:/usr/bin/bc" "bison:/usr/bin/bison" "flex:/usr/bin/flex" \ "meson:/usr/bin/meson" "ninja:/usr/bin/ninja" \ "cmake:/usr/bin/cmake" "pkgconf:/usr/bin/pkgconf" \ "autoconf:/usr/bin/autoconf" "automake:/usr/bin/automake" \ "libtool:/usr/bin/libtool" "m4:/usr/bin/m4"; do _dep="${_spec%%:*}"; _sent="${_spec#*:}" if [[ ! -e "$ROOTFS$_sent" ]]; then info "Doinstalowuję $_dep w rootfsie buildera (brak $_sent)" _pag_install_in_rootfs "$ROOTFS" "$_dep" fi done [[ -x "$ROOTFS/usr/bin/ld" ]] || warn "Nadal brak $ROOTFS/usr/bin/ld – linker zostanie wzięty z hosta? (nie zadziała w chrootcie)" # meson OSOBNO i z twardą kontrolą: obraz ISO go nie zawiera (nie jest potrzebny # w systemie uruchomieniowym), a KAŻDA receptura mesonowa (mesa, libglvnd, libva, # xf86-video-amdgpu…) padnie na "meson: command not found" (exit 127). Pre-flight # w pagbuild pomija meson jako „zawsze obecny w builderze”, więc bez tego wpisu # brak meson nie jest w ogóle raportowany przed buildem. if [[ ! -x "$ROOTFS/usr/bin/meson" ]]; then warn "Brak meson w rootfsie buildera – receptury mesonowe padną na 'meson: command not found'" fi # cmake w tym rootfsie bywa zbudowany pod systemowy rhash (NEEDED librhash.so.1), # a sam pakiet rhash nie jest wciągany jako zależność – po podmianie cmake przez # `pag install` (np. jako zależności glslang/llvm) KAŻDY build cmake'owy pada na # „cmake: error while loading shared libraries: librhash.so.1”. Wykrywamy to # działaniem, a nie obecnością pliku (cmake JEST, tylko zepsuty). if [[ -x "$ROOTFS/usr/bin/cmake" ]]; then if ! grep -qa 'librhash\.so' "$ROOTFS/usr/bin/cmake" 2>/dev/null; then : # cmake bez librhash – OK (self-contained) elif [[ ! -e "$ROOTFS/usr/lib/librhash.so.1" && ! -e "$ROOTFS/usr/lib64/librhash.so.1" ]]; then info "cmake wymaga librhash.so.1, którego nie ma – doinstalowuję rhash" _pag_install_in_rootfs "$ROOTFS" rhash fi fi # ── (3) Cache źródeł: nie ściągaj ponownie 160 MB kernela ─────────────────── # pagbuild trzyma źródła w /var/cache/pagbuild/sources (nazwa = basename URL-a, # chyba że to „tagowa” nazwa typu 610.57.04.tar.gz – wtedy z hashem; kernel ma # nazwę linux-.tar.xz, więc trafia do cache pod tą samą nazwą). # Wersję bierzemy z receptury, żeby nie rozjechała się z tym, co budujemy. mkdir -p "$SRC_CACHE" "$OUT_DIR" _kernel_ver="$(python3 -c "import yaml,sys;print(yaml.safe_load(open(sys.argv[1]))['pkgver'])" \ "$SCRIPT_DIR/recipes/utils/kernel/PAGBUILD.yaml" 2>/dev/null || echo 7.2.5)" for f in "$PAGAN_SRC_ROOT"/sources/linux-"$_kernel_ver".tar.xz; do [[ -f "$f" ]] || continue b="$(basename "$f")" [[ -f "$SRC_CACHE/$b" ]] || { cp -f "$f" "$SRC_CACHE/$b" && ok "Cache źródeł: $b (z $PAGAN_SRC_ROOT/sources)"; } done # ── (4) Budowa ── # Kolejność ma znaczenie: nvidia-kernel buduje się POD ŹRÓDŁA z pakietu kernel # (/usr/src/kernel), więc po zbudowaniu kernela instalujemy go w rootfsie # buildera – inaczej Kbuild nie znajdzie drzewa jądra. # # Przed każdą recepturą doinstalowujemy jej `makedepends` w rootfsie buildera # (jedno `pag install` na recepturę). Bez tego ciężkie buildy (mesa z # -Dvalgrind=enabled, gvfs z libsecret…) padają na konfiguracji, mimo że pakiet # JEST w repo. _install_recipe_deps() { local recipe="$1" deps deps="$(python3 - "$recipe" <<'PY' import sys, yaml d = yaml.safe_load(open(sys.argv[1])) or {} md = d.get("makedepends") or [] if isinstance(md, str): md = [md] # pomijamy wpisy wirtualne (pkgconfig(x), libfoo.so) – to nie nazwy pakietów print(" ".join(x for x in md if x and not any(c in x for c in "()"))) PY )" [[ -n "$deps" ]] || return 0 info " makedepends: $deps" _pag_install_in_rootfs "$ROOTFS" $deps } # ── Auto-zapis configu jądra po udanym buildzie ────────────────────────────── # Receptura w fazie build robi `make olddefconfig`, więc w pakiecie leży config # dopasowany do bieżącego Kconfig (boot/config-). Zapisujemy go z # powrotem do receptury (+ świeży SHA wpisu `config`). Na PC nie ma pagsync, # dlatego robimy to tutaj; zmiany w recipes/ trzeba zacommitować. _save_kernel_config() { local recipe="$1" pag="$2" [[ -f "$recipe" && -f "$pag" ]] || return 0 python3 - "$recipe" "$pag" <<'PY' import io, os, sys, tarfile, hashlib import yaml recipe, pag = sys.argv[1:3] cfg = os.path.join(os.path.dirname(os.path.abspath(recipe)), "config") if not os.path.isfile(cfg): raise SystemExit def extract(p): with tarfile.open(p, "r:xz") as tf: fh = tf.extractfile("data.tar.xz") if fh is None: return None, None data = fh.read() with tarfile.open(fileobj=io.BytesIO(data), mode="r:xz") as df: for ti in df.getmembers(): nm = ti.name[2:] if ti.name.startswith("./") else ti.name parts = nm.split("/") if len(parts) == 2 and parts[0] == "boot" and parts[1].startswith("config-"): fh = df.extractfile(ti) return (fh.read().decode("utf-8", "replace"), parts[1][len("config-"):]) if fh else (None, None) return None, None content, rel = extract(pag) if not content: print(" \u23ed save-kernel-config: brak boot/config-* w " + os.path.basename(pag)) raise SystemExit if not content.endswith("\n"): content += "\n" old = open(cfg, encoding="utf-8").read() if content == old: print(" \u2705 config kernela aktualny (" + rel + ")") raise SystemExit open(cfg, "w", encoding="utf-8").write(content) sha = hashlib.sha256(open(cfg, "rb").read()).hexdigest() txt = open(recipe, encoding="utf-8").read() d = yaml.safe_load(txt) or {} srcs = d.get("source") or [] if isinstance(srcs, str): srcs = [srcs] if "config" in srcs: idx = srcs.index("config") lines = txt.split("\n"); start = None for i, l in enumerate(lines): if l.startswith("sha256sums:"): start = i; break if start is not None: n = -1 for j in range(start + 1, len(lines)): l = lines[j] if l.startswith("- "): n += 1 if n == idx: lines[j] = "- " + sha; break elif l and not l.startswith((" ", "\t")): break open(recipe, "w", encoding="utf-8").write("\n".join(lines)) print(" \U0001f4be config kernela zaktualizowany (" + rel + ", sha " + sha[:12] + "…) – zacommituj recipes/") PY } # ── Instalacja zbudowanego pakietu w rootfsie buildera ────────────────────── # PO CO: łańcuchy zależności w JEDNEJ komendzie (libva → intel-media-driver, # mesa → xf86-video-*). Bez tego drugi pakiet nie widzi nagłówków/.pc pierwszego # i pada już na konfiguracji („Package 'libva' not found”), mimo że receptura ma # go w makedepends – pagbuild instaluje makedepends TYLKO z repo, a świeżo # zbudowanego pakietu w repo jeszcze nie ma. # Podmieniamy pliki WŁASNYM pakietem zbudowanym w tym samym rootfsie, więc # builder pozostaje spójny z targetem (żadnego Debiana tu nie mieszamy). # Wyłączenie: PAGAN_BUILDER_NO_INSTALL=1 _install_in_builder() { local pag_file="$1" pkgname="$2" in_chroot_dir _s [[ "${PAGAN_BUILDER_NO_INSTALL:-0}" == "1" ]] && return 0 [[ -f "$pag_file" ]] || return 0 info " → instaluję $pkgname w rootfsie buildera (widoczność dla kolejnych buildów)" in_chroot_dir="$ROOTFS/var/cache/pagbuild/out" mkdir -p "$in_chroot_dir" cp -f "$pag_file" "$in_chroot_dir/" # pag weryfikuje lokalny plik FAIL-CLOSED – podpis (jeśli jest) musi trafić do chroot razem z pakietem. for _s in "$pag_file".asc "$pag_file".sig; do [[ -f "$_s" ]] && cp -f "$_s" "$in_chroot_dir/" done _mount_chroot "$ROOTFS" trap '_umount_chroot "$ROOTFS"' EXIT # PAG_INSECURE=1: pakiety budowane TU (na PC) nie są podpisane – nie ma tu # klucza repo (celowo trzyma go VPS). `pag` jest FAIL-CLOSED dla lokalnych # plików, więc bez tego instalacja w builderze kończyła się „Brak podpisu dla # pakietu”. To NASZE, świeżo zbudowane pakiety w NASZYM rootfsie budowy, więc # pominięcie weryfikacji jest tu bezpieczne i celowe; na systemie docelowym # podpisy nadal obowiązują (tam instalacja idzie z podpisanego repo). chroot "$ROOTFS" /usr/bin/env PAG_YES=1 PAG_INSECURE=1 /usr/bin/pag install -y \ "/var/cache/pagbuild/out/$(basename "$pag_file")" \ || warn " instalacja $pkgname w builderze nieudana – kolejne buildy mogą go nie widzieć" _umount_chroot "$ROOTFS" trap - EXIT } _built_kernel_pag="" for pkg in "${PKGS[@]}"; do info "Budowanie: $pkg" # UWAGA: NIE pisz tego jako `_recipe="$SCRIPT_DIR/recipes"/*/"$pkg"/PAGBUILD.yaml`! # Bash NIE rozwija globów w zwykłym przypisaniu (tylko w `arr=( ... )`), więc # zmienna zostawała LITERALNYM wzorcem z gwiazdką, `[[ -f $_recipe ]]` był # zawsze fałszywy i makedepends NIGDY nie trafiały do rootfsu buildera – buildy # padały (meson/cmake/pkg-config nie widziały zależności), a komunikat mówił # „brak receptury” mimo że receptura leżała na dysku. `find` jest odporne. _recipe="$(find "$SCRIPT_DIR/recipes" -type f -path "*/$pkg/PAGBUILD.yaml" -print -quit 2>/dev/null || true)" if [[ -n "$_recipe" && -f "$_recipe" ]]; then info " receptura: ${_recipe#"$SCRIPT_DIR/"}" _install_recipe_deps "$_recipe" else warn " brak receptury dla '$pkg' w recipes/*/ – pagbuild i tak spróbuje" _recipe="" # nie przekazuj śmieciowego wzorca do _save_kernel_config fi args=( "$pkg" --rootfs "$ROOTFS" --out "$OUT_DIR" ) [[ $DO_SIGN -eq 1 ]] && args+=( --sign ) [[ -n "$GPG_KEY" ]] && args+=( --gpg-key "$GPG_KEY" ) if ! "$SCRIPT_DIR/pagbuild" "${args[@]}"; then warn "Budowa $pkg NIEUDANA – pomijam" continue fi pag_file="$(ls -1t "$OUT_DIR/$pkg"-*.pag 2>/dev/null | head -n1 || true)" if [[ -z "$pag_file" ]]; then warn "$pkg: pagbuild zwrócił 0, ale nie ma $OUT_DIR/$pkg-*.pag" continue fi ok "$pkg → $pag_file ($(du -h "$pag_file" | cut -f1))" printf ' sha256: %s\n' "$(sha256sum "$pag_file" | cut -d' ' -f1)" # Auto-zapis configu jądra (olddefconfig) z powrotem do receptury. case "$pkg" in kernel*) _save_kernel_config "$_recipe" "$pag_file" ;; esac # ── Bramka ABI ────────────────────────────────────────────────────────── # Pakiet NIE MOŻE odwoływać się do symboli, których nie ma w docelowym # PaganLinux. To wyłapuje pakiety zbudowane w chrootcie Debiana na VPS # (tak wyszły zepsute libgsf z xmlNanoHTTPClose i libavcodec z # rsvg_handle_render_document). Indeks symboli jest cache'owany. # # TRYB ROZJAZDU (--build-rootfs), a NIE „celu”: pytamy wyłącznie o symbole, # które SĄ w środowisku budowy, a których NIE MA w docelowym systemie – dokładnie # to znaczy „zbudowano pod inne biblioteki”. Tryb „celu” (bez --build-rootfs) # raportuje KAŻDĄ zależność spoza obrazu i kończy się kodem 0 (bez --strict), # więc jako bramka był bezużyteczny: zawsze mówił „ABI zgodne”, mimo że dla # pakietów repo zależności spoza ISO są normalne (dociąga je `pag`). # UWAGA do nazwy cache: „-v2” celowo. Cache v1 (/var/cache/pagbuild/target-symbols.txt) # powstał przy skanie z -maxdepth, więc NIE zawiera symboli modułów z głębszych # katalogów (np. /usr/lib/xorg/modules/libglamoregl.so → glamor_init) i dawał # fałszywe „rozjazdy ABI” dla sterowników Xorg. Nowa wersja skanuje całe drzewo. if [[ -x "$SCRIPT_DIR/check-pag-abi.sh" ]]; then if PAGAN_ABI_INDEX="${PAGAN_ABI_INDEX:-/var/cache/pagbuild/target-symbols-v2.txt}" \ "$SCRIPT_DIR/check-pag-abi.sh" --rootfs "$PAGAN_SRC_ROOT" \ --build-rootfs "$ROOTFS" --quiet "$pag_file" >/dev/null 2>&1; then ok " $pkg: brak rozjazdu ABI względem $PAGAN_SRC_ROOT" else warn " $pkg: ROZJAZD ABI z $PAGAN_SRC_ROOT – NIE publikuj tego pakietu!" warn " szczegóły: ./check-pag-abi.sh --rootfs $PAGAN_SRC_ROOT --build-rootfs $ROOTFS $pag_file" ABI_FAILED+=("$pkg") fi fi # Każdy zbudowany pakiet ląduje w rootfsie buildera, żeby KOLEJNE buildy z tej # samej komendy widziały jego nagłówki/.pc (np. libva → intel-media-driver). _install_in_builder "$pag_file" "$pkg" # Kernel dodatkowo: gwarancja /usr/src/kernel (SYSSRC dla nvidia-kernel/dkms), # nawet gdy `pag install` odmówi (podpis / konflikt symlinku w /usr/lib/modules). if [[ "$pkg" == "kernel" ]]; then _built_kernel_pag="$pag_file" in_chroot_dir="$ROOTFS/var/cache/pagbuild/out" mkdir -p "$in_chroot_dir" # Gwarancja obecności źródeł jądra (SYSSRC dla nvidia-kernel/dkms). # Bezpośrednie rozpakowanie omija bramkę podpisu pag oraz konflikt # symlinku w /usr/lib/modules (który nas tu nie interesuje) – bierzemy # tylko /usr/src i /boot. if [[ ! -d "$ROOTFS/usr/src/kernel" ]]; then if tar -xJf "$pag_file" -C "$in_chroot_dir" data.tar.xz 2>/dev/null; then tar -xJf "$in_chroot_dir/data.tar.xz" -C "$ROOTFS" ./usr/src ./boot 2>/dev/null \ || warn " niepełne rozpakowanie źródeł jądra" rm -f "$in_chroot_dir/data.tar.xz" fi fi [[ -d "$ROOTFS/usr/src/kernel" ]] \ || warn " brak $ROOTFS/usr/src/kernel – nvidia-kernel może się nie zbudować" fi done echo info "Zbudowane pakiety w: $OUT_DIR" ls -1sh "$OUT_DIR"/*.pag 2>/dev/null || warn "Brak .pag w $OUT_DIR" # Bramka ABI: czy któryś pakiet odwołuje się do symboli, których nie ma w obrazie? if [[ ${#ABI_FAILED[@]} -gt 0 ]]; then echo warn "UWAGA: ${#ABI_FAILED[@]} pakiet(ów) z niezgodnym ABI: ${ABI_FAILED[*]}" warn "Takie pakiety padają na starcie z „undefined symbol”. NIE wgrywaj ich do repo –" warn "przebuduj natywnie na PC (ten skrypt) albo popraw zależności w recepturze." _abi_rc=1 else _abi_rc=0 fi echo cat <.pag.asc – jeśli budujesz z --sign, rsync powyżej zabierze je razem z pakietami. ──────────────────────────────────────────────────────────────────────────── EOF # Kod wyjścia: 1, gdy którykolwiek pakiet ma niezgodne ABI (łatwe do wykrycia # w automatyzacji: `./build-repo.sh ... || echo "NIE PUBLIKUJ"`). exit "$_abi_rc"