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