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/).
308 lines
15 KiB
Bash
Executable File
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
|