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

PaganLinux/build-native-builder-rootfs.sh main

425 linii Raw ← Powrót
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
#!/usr/bin/env bash
# =============================================================================
# build-native-builder-rootfs.sh
#
# Buduje natywną warstwę BUILD-DEPS PaganOS w rootfs utworzonym przez rootfs.sh.
# Nie używa Debianowego ABI: pagbuild uruchamiany jest przez chroot PaganOS.
#
# Użycie:
#   sudo ./build-native-builder-rootfs.sh \
#       --rootfs /mnt/pagan-native-rootfs \
#       --recipes ./recipes \
#       --rebuild-base
# =============================================================================
set -Eeuo pipefail
# Nigdy nie pozwól, aby biblioteki PaganOS zastąpiły biblioteki hosta
# podczas wywołań mount/umount/chroot.
unset LD_LIBRARY_PATH

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOTFS="${NATIVE_ROOTFS:-/mnt/pagan-native-rootfs}"
RECIPES="${NATIVE_RECIPES:-$SCRIPT_DIR/recipes}"
REBUILD_BASE=0
KEEP_MOUNTS=0
LOG="/var/log/pagan-native-builder.log"

usage() { sed -n '1,20p' "$0"; exit "${1:-0}"; }
while [[ $# -gt 0 ]]; do
    case "$1" in
        --rootfs) ROOTFS="$2"; shift 2 ;;
        --recipes) RECIPES="$2"; shift 2 ;;
        --rebuild-base) REBUILD_BASE=1; shift ;;
        --keep-mounts) KEEP_MOUNTS=1; shift ;;
        -h|--help) usage ;;
        *) echo "Nieznany argument: $1" >&2; usage 2 ;;
    esac
done

[[ $EUID -eq 0 ]] || { echo "Uruchom jako root" >&2; exit 1; }
[[ "$ROOTFS" != "/" && -n "$ROOTFS" ]] || { echo "Niebezpieczny ROOTFS" >&2; exit 1; }
[[ -d "$RECIPES" ]] || { echo "Brak recipes: $RECIPES" >&2; exit 1; }
mkdir -p "$(dirname "$LOG")"
exec > >(tee -a "$LOG") 2>&1

log() { printf '[native-rootfs] %s\n' "$*"; }
die() { log "BŁĄD: $*"; exit 1; }

# Etap 1: bazowy, natywny PaganOS rootfs/toolchain.
if [[ $REBUILD_BASE -eq 1 || ! -x "$ROOTFS/usr/bin/gcc" ]]; then
    log "Buduję bazowy PaganOS rootfs przez rootfs.sh"
    PAGAN_ROOT="$ROOTFS" REBUILD_FROM_SCRATCH=1 "$SCRIPT_DIR/rootfs.sh" --rebuild
fi

for x in bash gcc g++ make tar curl python3; do
    [[ -x "$ROOTFS/usr/bin/$x" || -x "$ROOTFS/bin/$x" ]] || die "Brak natywnego narzędzia w rootfs: $x"
done
[[ -x "$ROOTFS/usr/bin/pagbuild" ]] || die "Brak /usr/bin/pagbuild w rootfs"

mkdir -p "$ROOTFS/dev" "$ROOTFS/proc" "$ROOTFS/sys" "$ROOTFS/run"

MOUNTS=()
unmount_all() { for m in "${MOUNTS[@]:-}"; do umount -l "$m" 2>/dev/null || true; done; MOUNTS=(); }
cleanup() { local rc=$?; [[ $KEEP_MOUNTS -eq 0 ]] && unmount_all; exit "$rc"; }
trap cleanup EXIT INT TERM

mount -t proc proc "$ROOTFS/proc"; MOUNTS=("$ROOTFS/proc" "${MOUNTS[@]}" )
mount --rbind /sys "$ROOTFS/sys"; mount --make-rslave "$ROOTFS/sys"; MOUNTS=("$ROOTFS/sys" "${MOUNTS[@]}" )
mount --rbind /dev "$ROOTFS/dev"; mount --make-rslave "$ROOTFS/dev"; MOUNTS=("$ROOTFS/dev" "${MOUNTS[@]}" )
mount --rbind /run "$ROOTFS/run"; mount --make-rslave "$ROOTFS/run"; MOUNTS=("$ROOTFS/run" "${MOUNTS[@]}" )
# Autoconf intensywnie korzysta z /dev/null podczas generowania config.status.
# Zwykły plik zamiast urządzenia znakowego wstrzykuje wyjście diff do
# config.status i psuje build. Upewnij się, że /dev/null jest urządzeniem.
if [[ ! -c "$ROOTFS/dev/null" ]]; then
    log "Naprawiam nieprawidłowe $ROOTFS/dev/null"
    rm -f "$ROOTFS/dev/null"
    mknod -m 666 "$ROOTFS/dev/null" c 1 3
fi
# Niektóre bazowe nagłówki glibc (np. regex.h) mogą nie być zachowane podczas
# bootstrapu, mimo że libc udostępnia funkcje (regcomp/regexec).
if [[ ! -f "$ROOTFS/usr/include/regex.h" && -f /usr/include/regex.h ]]; then
    install -m644 /usr/include/regex.h "$ROOTFS/usr/include/regex.h"
    log "Uzupełniono brakujący nagłówek glibc: /usr/include/regex.h"
fi
# Loader musi znać /usr/lib64 (tam trafia wiele pakietów GTK/Qt). Bez tego
# custom meson commands nie ładują świeżo zbudowanych bibliotek.
if [[ ! -f "$ROOTFS/etc/ld.so.conf" ]] || ! grep -q '/usr/lib64' "$ROOTFS/etc/ld.so.conf" 2>/dev/null; then
    printf '/usr/lib\n/usr/lib64\n/lib\n/lib64\n' > "$ROOTFS/etc/ld.so.conf"
    chroot "$ROOTFS" /sbin/ldconfig >/dev/null 2>&1 || true
    log "Ustawiono /etc/ld.so.conf z /usr/lib64 i przebudowano cache ld.so"
fi
rm -f "$ROOTFS/etc/resolv.conf"; cp -L /etc/resolv.conf "$ROOTFS/etc/resolv.conf"

mkdir -p "$ROOTFS/var/lib/pagan-sync/recipes" "$ROOTFS/var/cache/pagbuild/output" "$ROOTFS/var/cache/pagbuild/sources"
rsync -a --delete "$RECIPES/" "$ROOTFS/var/lib/pagan-sync/recipes/"

# Zawsze synchronizuj AKTUALNY builder do rootfs przed budowaniem.
# Stary/niekompletny pagbuild w rootfs potrafił cicho nie wygenerować
# build.sh ('chmod: No such file'), co przerywało całą natywną warstwę
# i pozostawiało mylący log. Wymuszamy świeżą kopię, identyczną z repozytorium.
if [[ -f "$SCRIPT_DIR/pagbuild" ]]; then
    cp -f "$SCRIPT_DIR/pagbuild" "$ROOTFS/usr/bin/pagbuild"
    chmod +x "$ROOTFS/usr/bin/pagbuild"
    log "Zsynchronizowano aktualny pagbuild ($(stat -c%s "$ROOTFS/usr/bin/pagbuild") B) do rootfs"
else
    die "Brak $SCRIPT_DIR/pagbuild – nie można zagwarantować aktualnego buildera"
fi
if [[ -f "$SCRIPT_DIR/pagsync" ]]; then
    install -m755 "$SCRIPT_DIR/pagsync" "$ROOTFS/opt/pagan/pagsync"
fi


# Wyznacz kolejność zależności. Uwzględniamy również makedepends, ponieważ
# biblioteki developerskie muszą być zainstalowane przed konsumentem.
ORDER_FILE=$(mktemp)
python3 - "$RECIPES" > "$ORDER_FILE" <<'PY'
import os, sys, yaml
from pathlib import Path
base=Path(sys.argv[1]); items={}; deps={}
for p in sorted(base.rglob("PAGBUILD.yaml")):
    try: d=yaml.safe_load(p.read_text()) or {}
    except Exception: continue
    n=str(d.get("pkgname") or p.parent.name)
    items[n]=p
    all_deps=d.get("depends",[])+d.get("makedepends",[])
    if isinstance(all_deps,str): all_deps=[all_deps]
    # Uwzględnij tylko zależności, które są też recepturami w tym repo.
    # Zależności bazowe (glibc, systemd, bash...), które już są w rootfs,
    # nie są budowane tutaj – ich obecność w deps nigdy by nie pozwoliła
    # węzłowi stać się "ready" (zmuszały algorytm do fallbacku na
    # sorted(deps)[0], co psuło kolejność budowania).
    deps[n]={str(x) for x in all_deps if str(x) in items}
for n in items: deps.setdefault(n,set())
order=[]
while deps:
    ready=sorted(n for n,v in deps.items() if not (v & items.keys()))
    if not ready: ready=[sorted(deps)[0]]
    for n in ready:
        order.append(items[n]); deps.pop(n,None)
        for v in deps.values(): v.discard(n)
for p in order: print(p)
PY

TOTAL=$(wc -l < "$ORDER_FILE")
N=0
SKIPPED=0
# Katalog, do którego pagbuild składa gotowe pakiety (wewnątrz rootfs).
PKG_OUT="$ROOTFS/var/cache/pagbuild/output"

# Zwraca nazwę pliku pakietu ("pkgname-pkgver-pkgrel.pag") albo pusty ciąg.
pkg_file_for() {
    python3 - "$1" <<'PY'
import sys, yaml
try:
    with open(sys.argv[1], encoding="utf-8") as f:
        d = yaml.safe_load(f) or {}
    name = d.get("pkgname"); ver = d.get("pkgver"); rel = d.get("pkgrel", 1)
    if name and ver:
        print(f"{name}-{ver}-{rel}.pag")
except Exception:
    pass
PY
}
# Zwraca pkgname receptury albo pusty ciąg.
pkg_name_for() {
    python3 - "$1" <<'PY'
import sys, yaml
try:
    with open(sys.argv[1], encoding="utf-8") as f:
        d = yaml.safe_load(f) or {}
    print(d.get("pkgname", "") or "")
except Exception:
    pass
PY
}
# Wypisuje nazwy depends+makedepends receptury (po jednej na linię).
deps_of() {
    python3 - "$1" <<'PY'
import sys, yaml
try:
    with open(sys.argv[1], encoding="utf-8") as f:
        d = yaml.safe_load(f) or {}
except Exception:
    raise SystemExit
out = (d.get("depends") or []) + (d.get("makedepends") or [])
for n in out:
    print(str(n))
PY
}

# Mapa pkgname → ścieżka receptury (do wyszukiwania zależności po nazwie).
declare -A RECIPE_BY_NAME
while IFS= read -r rr; do
    nn="$(pkg_name_for "$rr")"
    [[ -n "$nn" ]] && RECIPE_BY_NAME["$nn"]="$rr"
done < <(find "$RECIPES" -name PAGBUILD.yaml)

# Instaluje zbudowany pakiet .pag do działającego rootfs (pliki + ldconfig +
# hooki post-install). pagbuild tylko PAKUJE do output/ – bez tego kroku
# nagłówki/biblioteki zbudowane w tej sesji nie byłyby widoczne dla kolejnych
# receptur, przez co zależne pakiety padały na etapie configure/kompilacji.
install_pkg_to_rootfs() { # $1 = ścieżka receptury
    local recipe="$1" pf tmp mark
    pf="$(pkg_file_for "$recipe")"
    [[ -n "$pf" && -s "$PKG_OUT/$pf" ]] || return 1
    mark="$PKG_OUT/.installed"
    if grep -qxF "$pf" "$mark" 2>/dev/null; then
        return 0
    fi

    tmp=$(mktemp -d)
    tar -xJf "$PKG_OUT/$pf" -C "$tmp" data.tar.xz 2>/dev/null || { rm -rf "$tmp"; return 1; }
    tar -xJf "$tmp/data.tar.xz" -C "$ROOTFS" 2>/dev/null || { rm -rf "$tmp"; return 1; }

    # Hooki post-install (np. make-ca, update-mime-database) – w chroot, cwd=/.
    if [[ -f "$tmp/hooks/post-install" ]]; then
        local name
        name="$(pkg_name_for "$recipe")"
        install -m755 "$tmp/hooks/post-install" "$ROOTFS/tmp/.pag-hook-post-install"
        chroot "$ROOTFS" /bin/bash -c "cd / && timeout 120 /bin/bash /tmp/.pag-hook-post-install" \
            || log "Uwaga: hook post-install $name zwrócił błąd (kontynuuję)"
        rm -f "$ROOTFS/tmp/.pag-hook-post-install"
    fi
    rm -rf "$tmp"

    chroot "$ROOTFS" /sbin/ldconfig >/dev/null 2>&1 || true
    echo "$pf" >> "$mark"
    log "[inst] Zainstalowano w rootfs: $pf"
}

# Pakiety "bazowe" rdzenia systemu, które rootfs.sh buduje już w LFS (Ch. 6).
# Instalowanie ich ponownie do działającego rootfs tworzy drugą, niespójną kopię
# (np. glibc w /lib64 vs /usr/lib) i psuje cały system. Lista przez BASE_KEEP.
BASE_KEEP="${BASE_KEEP:-glibc}"
IFS=' ' read -r -a BASE_KEEP_ARR <<< "$BASE_KEEP"

# Wykonuje faktyczny build pakietu w chroot (jako root, w tym chroot).
run_pagbuild() { # $1 = rel path w chroot (np. /var/lib/pagan-sync/...)
    chroot "$ROOTFS" /usr/bin/env -i \
        HOME=/root TERM=linux LANG=C LC_ALL=C \
        PATH=/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin \
        CC=/usr/bin/gcc CXX=/usr/bin/g++ AR=/usr/bin/ar RANLIB=/usr/bin/ranlib \
        PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/lib64/pkgconfig:/usr/share/pkgconfig \
        PKG_CONFIG_LIBDIR=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/lib64/pkgconfig:/usr/share/pkgconfig \
        PKG_CONFIG_SYSROOT_DIR=/ CMAKE_PREFIX_PATH=/usr:/ \
        FORCE_UNSAFE_CONFIGURE=1 CFLAGS=-D_GNU_SOURCE CPPFLAGS=-D_GNU_SOURCE \
        PAGBUILD_WORK=/var/cache/pagbuild/work \
        /usr/bin/pagbuild "$1" --rootfs / --out /var/cache/pagbuild/output
}

# REKURENCYJNA RESOLUCJA ZALEŻNOŚCI.
# Buduje depends/makedepends (szukając receptur po nazwie we wszystkich
# katalogach recipes: core/gui/utils), instaluje je do rootfs i dopiero wtedy
# buduje pakiet docelowy. Zwraca 0 = gotowe, 1 = nie udało się (błąd budowy
# pakietu lub którejś zależności).
declare -A DEP_VISIT
FAILED_PKGS=()

build_with_deps() { # $1 = hostowa ścieżka receptury
    local recipe="$1"
    [[ -f "$recipe" ]] || return 0

    local pkg_name pkg_file
    pkg_name="$(pkg_name_for "$recipe")"
    # Pomijamy pakiety bazowe (stay w rootfs).
    if [[ -n "$pkg_name" ]] && printf '%s\n' "${BASE_KEEP_ARR[@]}" | grep -qx -- "$pkg_name"; then
        return 0
    fi
    pkg_file="$(pkg_file_for "$recipe")"
    # Już zbudowany → nic nie robimy (ale dopilnuj instalacji w rootfs).
    if [[ -n "$pkg_file" && -s "$PKG_OUT/$pkg_file" ]]; then
        install_pkg_to_rootfs "$recipe" || true
        return 0
    fi
    # Chronimy przed cyklami / wielokrotnym wejściem.
    if [[ ${DEP_VISIT["$recipe"]+x} ]]; then
        return 0
    fi
    DEP_VISIT["$recipe"]=1

    local rel="/var/lib/pagan-sync/recipes/${recipe#"$RECIPES"/}"
    local dep dep_rec
    # 1) Rekurencyjnie buduj zależności (szukane po nazwie w core/gui/utils).
    while IFS= read -r dep; do
        [[ -z "$dep" ]] && continue
        dep_rec="${RECIPE_BY_NAME[$dep]:-}"
        if [[ -n "$dep_rec" ]] && ! build_with_deps "$dep_rec"; then
            log "[fail] Zależność '$dep' dla $rel nie powstała – pomijam"
            FAILED_PKGS+=("$rel (zależność: $dep)")
            return 1
        fi
    done < <(deps_of "$recipe")

    # 2) Zbuduj pakiet (jeśli nadal brak .pag).
    if [[ -n "$pkg_file" && -s "$PKG_OUT/$pkg_file" ]]; then
        install_pkg_to_rootfs "$recipe" || true
        return 0
    fi
    log "[depe] Buduję zależność: $rel"
    if ! run_pagbuild "$rel"; then
        log "[fail] Nie udało się zbudować: $rel"
        FAILED_PKGS+=("$rel")
        return 1
    fi
    if ! install_pkg_to_rootfs "$recipe"; then
        log "[fail] Nie udało się zainstalować do rootfs: $rel"
        FAILED_PKGS+=("$rel (instalacja)")
        return 1
    fi
    return 0
}

while IFS= read -r recipe; do
    [[ -f "$recipe" ]] || continue
    N=$((N+1)); rel="/var/lib/pagan-sync/recipes/${recipe#"$RECIPES"/}"
    pkg_name="$(pkg_name_for "$recipe")"
    # Pomiń pakiety bazowe (bez nadpisywania działającego systemu).
    if [[ -n "$pkg_name" ]] && printf '%s\n' "${BASE_KEEP_ARR[@]}" | grep -qx -- "$pkg_name"; then
        SKIPPED=$((SKIPPED+1))
        log "[$N/$TOTAL] Pomijam bazowy (już w rootfs, nie nadpisuję): $rel"
        continue
    fi
    pkg_file="$(pkg_file_for "$recipe")"
    if [[ -n "$pkg_file" && -s "$PKG_OUT/$pkg_file" ]]; then
        SKIPPED=$((SKIPPED+1))
        log "[$N/$TOTAL] Pomijam (już zbudowany): $rel"
        install_pkg_to_rootfs "$recipe" || true
        continue
    fi
    log "[$N/$TOTAL] Natywny build: $rel"
    # Build z rekurencyjną kontrolą depends/makedepends (zależności są
    # wyszukiwane po nazwie w core/gui/utils, budowane i instalowane).
    if ! build_with_deps "$recipe"; then
        log "[$N/$TOTAL] ❌ POMINIĘTO (błąd powyżej): $rel"
        continue
    fi
    # Po budowie sprawdź, czy pakiet faktycznie powstał.
    if [[ -n "$pkg_file" && -s "$PKG_OUT/$pkg_file" ]]; then
        install_pkg_to_rootfs "$recipe" || log "Uwaga: nie udało się zainstalować $rel do rootfs"
        log "[$N/$TOTAL] OK: $rel"
    else
        log "[$N/$TOTAL] ❌ Brak pakietu po buildzie: $rel"
        FAILED_PKGS+=("$rel (brak pakietu)")
    fi
done < "$ORDER_FILE"
rm -f "$ORDER_FILE"

if [[ $SKIPPED -gt 0 ]]; then
    log "Pominięto $SKIPPED już zbudowanych/bazowych pakietów (usuń ich .pag, aby przebudować)"
fi

# ── RAPORT: pakiety, których nie udało się zbudować ──
if [[ ${#FAILED_PKGS[@]} -gt 0 ]]; then
    log "❌ NIE ZBUDOWANO (${#FAILED_PKGS[@]}):"
    printf '%s\n' "${FAILED_PKGS[@]}" | sort -u | sed 's/^/    - /' | tee -a "$LOG"
    log "Wznów skrypt, aby ponowić próbę – zbudowane pakiety zostaną pominięte."
else
    log "✅ Wszystkie pakiety zbudowane pomyślnie."
fi

log "Walidacja natywnego sysroota"
chroot "$ROOTFS" /usr/bin/env -i PATH=/usr/local/bin:/usr/bin:/bin HOME=/root \
    bash -lc '
set -e
for x in gcc g++ make cmake meson ninja pkg-config python3 perl xsltproc xmllint gperf pagbuild; do command -v "$x" >/dev/null; done
pkg-config --exists glib-2.0
pkg-config --exists dbus-1
pkg-config --exists gtk+-3.0
printf "native-builder-rootfs:ok\\n"
' || log "⚠️ Walidacja sysroota niekompletna (nie wszystko się zbudowało – patrz raport powyżej)"

# ── Gotowe repo: tylko aktualna wersja per-pakiet + podpisy + repo.json ──
REPO_DIR="${NATIVE_REPO_DIR:-$ROOTFS/var/www/repo.paganlinux.eu/stable}"
if [[ -d "$PKG_OUT" ]] && compgen -G "$PKG_OUT"/*.pag >/dev/null 2>&1; then
    log "Buduję gotowe repo: $REPO_DIR"
    mkdir -p "$REPO_DIR"
    python3 - "$PKG_OUT" "$REPO_DIR" <<'PY'
import os, sys, glob, re, shutil
out, repo = sys.argv[1], sys.argv[2]

def vkey(v):
    parts = re.split(r"[.\-_+]", v.lstrip("vV"))
    k = []
    for p in parts:
        m = re.match(r"^(\d+)(.*)$", p)
        k.append((1, int(m.group(1)), m.group(2)) if m else (0, p, ""))
    return tuple(k)

best = {}
for f in glob.glob(os.path.join(out, "*.pag")):
    base = os.path.basename(f)
    m = re.match(r"^(.+)-(\d[\w.\-]*)-(\d+)\.pag$", base)
    if not m:
        continue
    name, ver, rel = m.group(1), m.group(2), int(m.group(3))
    cur = best.get(name)
    if cur is None or (vkey(ver), rel) > (vkey(cur[1]), cur[2]):
        best[name] = (base, ver, rel)

for name, (base, ver, rel) in best.items():
    for old in glob.glob(os.path.join(repo, name + "-*.pag")):
        if os.path.basename(old) == base:
            continue
        os.remove(old)
        for sfx in (".asc", ".sig"):
            if os.path.exists(old + sfx):
                os.remove(old + sfx)
    shutil.copy2(os.path.join(out, base), os.path.join(repo, base))
print(f"[native-rootfs] Repo: {len(best)} pakietów (tylko aktualne wersje)")
PY
    IDX_ARGS=(--out "$REPO_DIR" --repo "$REPO_DIR")
    if [[ -n "${PAGAN_GPG_KEY:-}" ]]; then
        IDX_ARGS+=(--gpg-key "$PAGAN_GPG_KEY" --sign-all)
        log "Podpisywanie pakietów kluczem $PAGAN_GPG_KEY"
    fi
    if [[ -x "$SCRIPT_DIR/repo-index.sh" ]]; then
        "$SCRIPT_DIR/repo-index.sh" "${IDX_ARGS[@]}" || log "Uwaga: repo-index.sh zwrócił błąd"
    else
        log "Brak $SCRIPT_DIR/repo-index.sh – pomijam indeks repo"
    fi
else
    log "Brak pakietów w $PKG_OUT – pomijam generowanie repo"
fi

log "Natywny rootfs jest gotowy: $ROOTFS"
log "Nie podmieniam automatycznie VPS. Spakuj po akceptacji:"
log "tar -cJf ${ROOTFS%/}.tar.xz -C $ROOTFS --exclude=./proc --exclude=./sys --exclude=./dev --exclude=./run --exclude=./tmp ."