Files
void-installer/iso/_inner-build-unified.sh
Giancarmine Salucci 56dfe11039 refactor: unified multi-profile build system
Add 4 configurable profiles (stable-cinnamon, stable-niri,
mainline-cinnamon, mainline-niri) with a single unified build
entry point.

- iso/build.sh: replaces build-iso.sh / build-live-iso.sh /
  build-niri-live-iso.sh; accepts --profile and --type flags
- iso/_inner-build-unified.sh: replaces the three _inner-build-*.sh
  scripts; branches on BUILD_TYPE / DESKTOP / KERNEL_PKG env vars
- config/profiles/stable-niri/: new — linux (k6) + niri/Wayland/noctalia
- config/profiles/mainline-cinnamon/: new — linux-mainline (k7) + Cinnamon/X11
- config/profiles/mainline-niri/packages.live.list: symlink added
- config/profiles/stable-cinnamon/packages.live.list: symlink added
- Makefile: PROFILE variable (default stable-cinnamon), shellcheck updated
- installer/install.sh: respects DEFAULT_PROFILE env (set by live ISO)
- tests/run-qemu-test.sh: passes PROFILE through to build and overlay

Live ISOs embed the installer pre-configured for the same profile
they were built with (DEFAULT_PROFILE in /etc/profile.d/).
2026-04-26 12:42:11 +02:00

308 lines
15 KiB
Bash
Executable File

#!/bin/bash
# Unified inner-build script — runs INSIDE the docker container (as root).
# Invoked by iso/build.sh via 'docker run'.
#
# Replaces: _inner-build.sh, _inner-build-live.sh, _inner-build-niri-live.sh
#
# Required env:
# ARCH REPO_URL KEYMAP LOCALE ISO_PKGS ISO_TITLE OUT_ISO_REL INCLUDE_DIR_REL
# BUILD_TYPE — installer | live
# DESKTOP — cinnamon | niri
# KERNEL_PKG — linux | linux-mainline
#
# Optional env:
# NOCTALIA_REPO — CDN URL for noctalia-shell/noctalia-qs (niri only)
# NIX_PACKAGES_PREBAKE — space-separated nix package attrs (live only)
# BOOT_CMDLINE — extra kernel command-line
# HOST_UID / HOST_GID — fix output ownership for host user (default 1000)
set -Eeuo pipefail
# ── validate required env ─────────────────────────────────────────────────
: "${ARCH:?}"; : "${REPO_URL:?}"; : "${KEYMAP:?}"; : "${LOCALE:?}"
: "${ISO_PKGS:?}"; : "${ISO_TITLE:?}";
: "${OUT_ISO_REL:?}"; : "${INCLUDE_DIR_REL:?}"
: "${BUILD_TYPE:?}"; : "${DESKTOP:?}"; : "${KERNEL_PKG:?}"
# ── paths ─────────────────────────────────────────────────────────────────
CACHE_DIR=/cache
PROJECT_DIR=/work
INCLUDE_DIR="$PROJECT_DIR/$INCLUDE_DIR_REL"
OUT_ISO="$PROJECT_DIR/$OUT_ISO_REL"
# Niri profiles use a separate mklive clone to avoid races on parallel builds.
case "$DESKTOP" in
niri) MKLIVE_DIR="$CACHE_DIR/void-mklive-niri" ;;
*) MKLIVE_DIR="$CACHE_DIR/void-mklive" ;;
esac
# xbps package cache dir (per-desktop, same reason as above)
case "$DESKTOP" in
niri) XBPS_CACHE="$CACHE_DIR/xbps-niri-pkgs" ;;
*) XBPS_CACHE="$CACHE_DIR/xbps-live-pkgs" ;;
esac
export PATH="$CACHE_DIR/xbps-static/usr/bin:$PATH"
# ── sanity checks ─────────────────────────────────────────────────────────
[[ -d "$MKLIVE_DIR" ]] || { echo "ERROR: $MKLIVE_DIR missing (should have been cloned on host)"; exit 1; }
[[ -d "$INCLUDE_DIR" ]] || { echo "ERROR: $INCLUDE_DIR missing (staging failed on host)"; exit 1; }
command -v xbps-install.static >/dev/null \
|| { echo "ERROR: xbps-install.static not on PATH"; exit 1; }
mkdir -p "$(dirname "$OUT_ISO")" "$XBPS_CACHE"
# ═════════════════════════════════════════════════════════════════════════════
# 1) NIX PREBAKE (live ISOs only)
# ═════════════════════════════════════════════════════════════════════════════
if [[ "$BUILD_TYPE" == "live" && -n "${NIX_PACKAGES_PREBAKE:-}" ]]; then
echo ">>> pre-baking nix packages"
read -r -a _NIX_PKGS <<< "$NIX_PACKAGES_PREBAKE"
_NIX_CACHE="$CACHE_DIR/nix-prebake"
_CACHE_KEY="$_NIX_CACHE/.done.$(printf '%s\n' "${_NIX_PKGS[@]}" | sort | md5sum | cut -c1-8)"
mkdir -p "$_NIX_CACHE"
if [[ -f "$_CACHE_KEY" && -d "$_NIX_CACHE/store" && -f "$_NIX_CACHE/.profile-path" ]]; then
echo " restoring cached nix store ($(du -sh "$_NIX_CACHE/store" 2>/dev/null | cut -f1))"
mkdir -p /nix
rsync -a "$_NIX_CACHE/" /nix/ 2>&1 | tail -1
else
echo " installing nix (single-user, no-daemon) ..."
rm -rf /nix ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
mkdir -m 0755 -p /nix
export NIX_CONFIG="build-users-group = "
curl -fsSL https://nixos.org/nix/install | \
NIX_INSTALLER_TRUST_INSTALLER=1 sh -s -- --no-daemon --no-channel-add
# shellcheck disable=SC1091
. /root/.nix-profile/etc/profile.d/nix.sh 2>/dev/null || true
export PATH="/root/.nix-profile/bin:/nix/var/nix/profiles/default/bin:$PATH"
export NIXPKGS_ALLOW_UNFREE=1
echo " nix profile install: ${_NIX_PKGS[*]}"
nix profile add --extra-experimental-features "nix-command flakes" \
--impure "${_NIX_PKGS[@]}" 2>&1
readlink -f /root/.nix-profile > "$_NIX_CACHE/.profile-path"
rsync -a /nix/ "$_NIX_CACHE/" 2>&1 | tail -1
touch "$_CACHE_KEY"
echo " cached nix store: $(du -sh "$_NIX_CACHE/store" 2>/dev/null | cut -f1)"
fi
echo " staging /nix into overlay ($(du -sh /nix/store 2>/dev/null | cut -f1))"
mkdir -p "$INCLUDE_DIR/nix"
rsync -a /nix/ "$INCLUDE_DIR/nix/" 2>&1 | tail -1
# Single-user nix: live user (uid 1000) owns the store.
chown -R 1000:1000 "$INCLUDE_DIR/nix"
_STORE_PROFILE=$(cat "$_NIX_CACHE/.profile-path" 2>/dev/null \
|| readlink -f /root/.nix-profile 2>/dev/null || echo "")
if [[ -n "$_STORE_PROFILE" && -d "$_STORE_PROFILE" ]]; then
mkdir -p "$INCLUDE_DIR/etc/skel"
ln -sf "$_STORE_PROFILE" "$INCLUDE_DIR/etc/skel/.nix-profile"
echo " skel/.nix-profile → $_STORE_PROFILE"
else
echo " WARNING: could not determine nix store profile path"
fi
fi
# ═════════════════════════════════════════════════════════════════════════════
# 2) NOCTALIA LOCAL SIGNED REPO (niri profiles only)
#
# noctalia-qs has a broken .sig2 on the CDN; noctalia-shell's CDN key import
# also fails intermittently (EAGAIN). Workaround: download both .xbps archives
# directly (curl skips sig checks), create a LOCAL SIGNED repo with a fresh RSA
# keypair, and register the public key in mklive/keys/ so copy_void_keys
# pre-trusts it in the rootfs. Our local repo is passed last to mklive.sh
# (-r last = HIGHEST priority) so xbps resolves and verifies both packages
# against our trusted key.
# ═════════════════════════════════════════════════════════════════════════════
_NOC_LOCAL=""
if [[ "$DESKTOP" == "niri" && -n "${NOCTALIA_REPO:-}" ]]; then
echo ">>> building local signed noctalia XBPS repo (CDN .sig2 workaround)"
_NOC_LOCAL="/tmp/noctalia-local"
_NOC_HOME="/tmp/noc-sign-home"
mkdir -p "$_NOC_LOCAL" "$_NOC_HOME"
export HOME="$_NOC_HOME"
# Discover exact package versions from CDN repodata
_NOC_VERS=$(python3 - <<'PYEOF' 2>/dev/null
import urllib.request, plistlib, tarfile, io, os
repo = os.environ.get("NOCTALIA_REPO", "https://universalrepo.r1xelelo.workers.dev/void")
arch = os.environ.get("ARCH", "x86_64")
want = {"noctalia-qs", "noctalia-shell"}
found = {}
for ua in ("curl/8.0", "xbps/0.59.2", "Mozilla/5.0 (X11; Linux x86_64)"):
try:
req = urllib.request.Request(f"{repo}/{arch}-repodata", headers={"User-Agent": ua})
data = urllib.request.urlopen(req, timeout=15).read()
tf = tarfile.open(fileobj=io.BytesIO(data))
idx = plistlib.loads(tf.extractfile("index.plist").read())
for pkgname, meta in idx.items():
if isinstance(meta, dict) and pkgname in want:
found[pkgname] = meta.get("pkgver", pkgname)
if len(found) >= len(want):
break
except Exception:
pass
# Fallback to versions confirmed by previous builds
defaults = {"noctalia-qs": "noctalia-qs-0.0.12_0", "noctalia-shell": "noctalia-shell-4.7.6_1"}
for pkg, ver in defaults.items():
if pkg not in found:
found[pkg] = ver
for ver in found.values():
print(ver)
PYEOF
)
for _ver in $_NOC_VERS; do
_fname="${_ver}.${ARCH}.xbps"
# Clear any cached bad sig2 from a previous failed build
rm -f "$CACHE_DIR/xbps-niri-pkgs/${_ver}"* 2>/dev/null || true
if [[ ! -f "$_NOC_LOCAL/$_fname" ]]; then
echo " downloading $_fname ..."
curl -fsSL "$NOCTALIA_REPO/$_fname" -o "$_NOC_LOCAL/$_fname" \
|| { echo " WARNING: failed to download $_fname"; rm -f "$_NOC_LOCAL/$_fname"; }
else
echo " cached: $_fname"
fi
done
xbps-rindex.static -a "$_NOC_LOCAL"/*.xbps
mkdir -p "$_NOC_HOME/.xbps-sign"
openssl genrsa -out "$_NOC_HOME/.xbps-sign/privkey.pem" 4096 2>/dev/null
[[ -s "$_NOC_HOME/.xbps-sign/privkey.pem" ]] \
|| { echo "ERROR: openssl genrsa failed"; exit 1; }
_NOC_PRIVKEY="$_NOC_HOME/.xbps-sign/privkey.pem"
xbps-rindex.static --sign --privkey "$_NOC_PRIVKEY" \
--signedby "noctalia-local" "$_NOC_LOCAL"
for _pkg in "$_NOC_LOCAL"/*.xbps; do
xbps-rindex.static --sign-pkg --privkey "$_NOC_PRIVKEY" \
--signedby "noctalia-local" "$_pkg"
done
openssl rsa -in "$_NOC_PRIVKEY" \
-pubout -outform DER -out "$_NOC_HOME/pubkey.der" 2>/dev/null
_FINGERPRINT=$(md5sum "$_NOC_HOME/pubkey.der" \
| cut -d' ' -f1 | sed 's/../&:/g; s/:$//')
_PUBKEY_B64=$(openssl rsa -in "$_NOC_PRIVKEY" \
-pubout 2>/dev/null | base64 -w 0)
cat > "$MKLIVE_DIR/keys/$_FINGERPRINT.plist" <<KEOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>public-key</key>
<data>$_PUBKEY_B64</data>
<key>public-key-size</key>
<integer>4096</integer>
<key>signature-by</key>
<string>noctalia-local</string>
</dict>
</plist>
KEOF
echo " local signed noctalia repo ready — key: $_FINGERPRINT"
unset HOME
fi
# ═════════════════════════════════════════════════════════════════════════════
# 3) DCONF POSTSETUP SCRIPT (live ISOs only — both cinnamon and niri)
#
# Compile system-db AND the skel user dconf db using Void's own binary inside
# the mklive rootfs chroot. No cross-distro format mismatch possible.
# ═════════════════════════════════════════════════════════════════════════════
_DCONF_POSTSETUP=""
if [[ "$BUILD_TYPE" == "live" ]]; then
_DCONF_POSTSETUP="$(mktemp -p "$MKLIVE_DIR" postsetup-dconf.XXXXX.sh)"
cat > "$_DCONF_POSTSETUP" <<'PSEOF'
#!/bin/bash
ROOTFS="$1"
if [[ -x "$ROOTFS/usr/bin/dconf" ]] && [[ -d "$ROOTFS/etc/dconf/db/local.d" ]]; then
chroot "$ROOTFS" dconf compile /etc/dconf/db/local /etc/dconf/db/local.d \
&& echo "postsetup-dconf: system-db compiled ($(chroot "$ROOTFS" dconf --version 2>/dev/null))" \
|| echo "postsetup-dconf: system-db compile failed (non-fatal)"
mkdir -p "$ROOTFS/etc/skel/.config/dconf"
chroot "$ROOTFS" dconf compile /etc/skel/.config/dconf/user /etc/dconf/db/local.d \
&& echo "postsetup-dconf: skel user dconf db compiled" \
|| echo "postsetup-dconf: skel user dconf db compile failed (non-fatal)"
else
echo "postsetup-dconf: dconf or keyfile dir not found in rootfs — skipping"
fi
PSEOF
chmod +x "$_DCONF_POSTSETUP"
fi
# ═════════════════════════════════════════════════════════════════════════════
# 4) CLEANUP TRAP
# ═════════════════════════════════════════════════════════════════════════════
cd "$MKLIVE_DIR"
_cleanup_mklive_builds() {
local d sub
for d in "$MKLIVE_DIR"/mklive-build.*/; do
[[ -d "$d" ]] || continue
for sub in tmp-rootfs/sys tmp-rootfs/proc tmp-rootfs/dev tmp-rootfs/run \
image/rootfs/sys image/rootfs/proc image/rootfs/dev image/rootfs/run; do
[[ -d "$d$sub" ]] && umount -R --lazy "$d$sub" 2>/dev/null || true
done
rm -rf "$d" 2>/dev/null || true
done
}
trap _cleanup_mklive_builds EXIT
# ═════════════════════════════════════════════════════════════════════════════
# 5) BUILD MKLIVE ARGS
# ═════════════════════════════════════════════════════════════════════════════
_MKLIVE_ARGS=(
-a "$ARCH"
-r "$REPO_URL"
-r "${REPO_URL%/current}/current/nonfree"
-c "$XBPS_CACHE"
-H "$CACHE_DIR/xbps-host-pkgs"
-k "$KEYMAP"
-l "$LOCALE"
-T "$ISO_TITLE"
-p "$ISO_PKGS"
-I "$INCLUDE_DIR"
-C "${BOOT_CMDLINE:-}"
-o "$OUT_ISO"
)
# Mainline kernel: pass as primary kernel so mklive builds the right initramfs.
# On dual-kernel ISOs the patched mklive loop also builds an entry for 'linux'.
[[ "$KERNEL_PKG" == "linux-mainline" ]] && _MKLIVE_ARGS+=( -v linux-mainline )
# Noctalia repos (niri only).
# Order matters: LAST -r = HIGHEST priority.
# We add CDN first, then our locally-signed repo so xbps resolves noctalia-*
# from the trusted local copy.
if [[ "$DESKTOP" == "niri" && -n "${NOCTALIA_REPO:-}" ]]; then
_MKLIVE_ARGS+=( -r "$NOCTALIA_REPO" )
[[ -n "$_NOC_LOCAL" ]] && _MKLIVE_ARGS+=( -r "$_NOC_LOCAL" )
fi
# NVIDIA postsetup (all builds)
_MKLIVE_ARGS+=( -x "$PROJECT_DIR/iso/postsetup-nvidia.sh" )
# dconf postsetup (live ISOs only)
[[ -n "$_DCONF_POSTSETUP" ]] && _MKLIVE_ARGS+=( -x "$_DCONF_POSTSETUP" )
# ═════════════════════════════════════════════════════════════════════════════
# 6) RUN MKLIVE
# ═════════════════════════════════════════════════════════════════════════════
echo ">>> running mklive.sh"
echo " type : $BUILD_TYPE"
echo " desktop : $DESKTOP"
echo " kernel : $KERNEL_PKG"
echo " output : $OUT_ISO"
./mklive.sh "${_MKLIVE_ARGS[@]}"
# Fix ownership so the host user can clean up without sudo.
chmod -R u+rwX "$INCLUDE_DIR" 2>/dev/null || true
chown -R "${HOST_UID:-1000}:${HOST_GID:-1000}" "$INCLUDE_DIR" 2>/dev/null || true
chown -R "${HOST_UID:-1000}:${HOST_GID:-1000}" "$OUT_ISO" "${OUT_ISO}".* 2>/dev/null || true