376 lines
16 KiB
Bash
376 lines
16 KiB
Bash
#!/bin/bash
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# Alfred Linux v4.0 — Unified ISO Build Script
|
|
# Usage: ./build-unified.sh <build-number>
|
|
# build-number: b1, b2, b3, b4, b5, b6, rc
|
|
#
|
|
# Each build is cumulative:
|
|
# b1: Branding (Plymouth, GRUB, wallpapers, neofetch, LightDM)
|
|
# b2: b1 + Alfred Browser (replace Firefox)
|
|
# b3: b2 + Alfred IDE (code-server + Commander extension)
|
|
# b4: b3 + Voice (Kokoro TTS + welcome greeting)
|
|
# b5: b4 + Meilisearch (local search engine)
|
|
# b6: b5 + Calamares (graphical installer)
|
|
# rc: b6 (release candidate — same as b6, final test)
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
set -uo pipefail
|
|
|
|
BUILD_NUM="${1:-}"
|
|
ENABLE_UEFI=false
|
|
if [[ "${2:-}" == "--uefi" ]]; then
|
|
ENABLE_UEFI=true
|
|
fi
|
|
if [[ -z "$BUILD_NUM" ]]; then
|
|
echo "Usage: $0 <b1|b2|b3|b4|b5|b6|rc>"
|
|
exit 1
|
|
fi
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
BUILD_DIR="$PROJECT_DIR/build"
|
|
OUTPUT_DIR="$PROJECT_DIR/iso-output"
|
|
DATE=$(date +%Y%m%d)
|
|
VERSION="4.0-${BUILD_NUM}"
|
|
ISO_NAME="alfred-linux-${VERSION}-amd64-${DATE}.iso"
|
|
|
|
echo ""
|
|
echo " ╔═══════════════════════════════════════════════════╗"
|
|
echo " ║ Alfred Linux — ISO Build System v2.0 ║"
|
|
echo " ║ Build: ${VERSION} ║"
|
|
echo " ║ ISO: ${ISO_NAME} ║"
|
|
echo " ╚═══════════════════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo "ERROR: Must run as root (sudo)."
|
|
exit 1
|
|
fi
|
|
|
|
for cmd in lb debootstrap mksquashfs xorriso; do
|
|
if ! command -v "$cmd" &>/dev/null; then
|
|
echo "ERROR: '$cmd' not found."
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# ── Determine which hooks and packages to include ──
|
|
declare -a HOOKS_TO_INCLUDE=()
|
|
PKG_LIST="alfred.list.chroot" # default (includes firefox-esr)
|
|
|
|
case $BUILD_NUM in
|
|
b1)
|
|
HOOKS_TO_INCLUDE=("0100-alfred-customize.hook.chroot")
|
|
PKG_LIST="alfred.list.chroot"
|
|
;;
|
|
b2)
|
|
HOOKS_TO_INCLUDE=("0100-alfred-customize.hook.chroot" "0200-alfred-browser.hook.chroot")
|
|
PKG_LIST="alfred-b2.list.chroot"
|
|
;;
|
|
b3)
|
|
HOOKS_TO_INCLUDE=("0100-alfred-customize.hook.chroot" "0200-alfred-browser.hook.chroot" "0300-alfred-ide.hook.chroot")
|
|
PKG_LIST="alfred-b2.list.chroot"
|
|
;;
|
|
b4)
|
|
HOOKS_TO_INCLUDE=("0100-alfred-customize.hook.chroot" "0200-alfred-browser.hook.chroot" "0300-alfred-ide.hook.chroot" "0400-alfred-voice.hook.chroot")
|
|
PKG_LIST="alfred-b2.list.chroot"
|
|
;;
|
|
b5)
|
|
HOOKS_TO_INCLUDE=("0100-alfred-customize.hook.chroot" "0200-alfred-browser.hook.chroot" "0300-alfred-ide.hook.chroot" "0400-alfred-voice.hook.chroot" "0500-alfred-search.hook.chroot")
|
|
PKG_LIST="alfred-b2.list.chroot"
|
|
;;
|
|
b6|rc|rc2|rc3)
|
|
HOOKS_TO_INCLUDE=("0100-alfred-customize.hook.chroot" "0200-alfred-browser.hook.chroot" "0300-alfred-ide.hook.chroot" "0400-alfred-voice.hook.chroot" "0500-alfred-search.hook.chroot" "0600-alfred-installer.hook.chroot")
|
|
PKG_LIST="alfred-b2.list.chroot"
|
|
;;
|
|
rc4|rc5)
|
|
HOOKS_TO_INCLUDE=("0100-alfred-customize.hook.chroot" "0200-alfred-browser.hook.chroot" "0300-alfred-ide.hook.chroot" "0400-alfred-voice.hook.chroot" "0500-alfred-search.hook.chroot" "0600-alfred-installer.hook.chroot" "0700-alfred-welcome.hook.chroot" "0710-alfred-update.hook.chroot" "0800-alfred-store.hook.chroot" "0900-alfred-voice-v2.hook.chroot")
|
|
PKG_LIST="alfred-b2.list.chroot"
|
|
;;
|
|
rc6)
|
|
HOOKS_TO_INCLUDE=("0100-alfred-customize.hook.chroot" "0150-alfred-hardware.hook.chroot" "0200-alfred-browser.hook.chroot" "0300-alfred-ide.hook.chroot" "0400-alfred-voice.hook.chroot" "0500-alfred-search.hook.chroot" "0600-alfred-installer.hook.chroot" "0700-alfred-welcome.hook.chroot" "0710-alfred-update.hook.chroot" "0800-alfred-store.hook.chroot" "0900-alfred-voice-v2.hook.chroot")
|
|
PKG_LIST="alfred-b2.list.chroot"
|
|
;;
|
|
rc7)
|
|
HOOKS_TO_INCLUDE=("0100-alfred-customize.hook.chroot" "0150-alfred-hardware.hook.chroot" "0160-alfred-security.hook.chroot" "0200-alfred-browser.hook.chroot" "0300-alfred-ide.hook.chroot" "0400-alfred-voice.hook.chroot" "0500-alfred-search.hook.chroot" "0600-alfred-installer.hook.chroot" "0700-alfred-welcome.hook.chroot" "0710-alfred-update.hook.chroot" "0800-alfred-store.hook.chroot" "0900-alfred-voice-v2.hook.chroot")
|
|
PKG_LIST="alfred-b2.list.chroot"
|
|
;;
|
|
rc8|rc9|rc10)
|
|
HOOKS_TO_INCLUDE=("0100-alfred-customize.hook.chroot" "0150-alfred-hardware.hook.chroot" "0160-alfred-security.hook.chroot" "0165-alfred-network-hardening.hook.chroot" "0170-alfred-fde.hook.chroot" "0200-alfred-browser.hook.chroot" "0300-alfred-ide.hook.chroot" "0400-alfred-voice.hook.chroot" "0500-alfred-search.hook.chroot" "0600-alfred-installer.hook.chroot" "0700-alfred-welcome.hook.chroot" "0710-alfred-update.hook.chroot" "0800-alfred-store.hook.chroot" "0900-alfred-voice-v2.hook.chroot")
|
|
PKG_LIST="alfred-b2.list.chroot"
|
|
;;
|
|
*)
|
|
echo "ERROR: Unknown build number: $BUILD_NUM"
|
|
echo "Valid: b1, b2, b3, b4, b5, b6, rc, rc6, rc7, rc8"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo "[BUILD] Hooks: ${HOOKS_TO_INCLUDE[*]}"
|
|
echo "[BUILD] Package list: $PKG_LIST"
|
|
|
|
# ── Setup ──
|
|
echo "[BUILD] Setting up build directory..."
|
|
if [[ -d "$BUILD_DIR" ]]; then
|
|
echo "[BUILD] Removing previous build directory..."
|
|
rm -rf "$BUILD_DIR"
|
|
fi
|
|
mkdir -p "$BUILD_DIR"
|
|
cd "$BUILD_DIR"
|
|
|
|
echo "[BUILD] Configuring live-build for Debian Trixie..."
|
|
# NOTE: --security false because live-build v3 (Ubuntu) hardcodes the old
|
|
# "<dist>/updates" URL format which is wrong for Bookworm (needs "trixie-security").
|
|
# We add the correct security repo in our 0010 hook instead.
|
|
lb config \
|
|
--mode debian \
|
|
--distribution trixie \
|
|
--architectures amd64 \
|
|
--binary-images iso-hybrid \
|
|
--bootloader syslinux \
|
|
--apt-indices false \
|
|
--memtest none \
|
|
--firmware-chroot false \
|
|
--security false \
|
|
--initsystem systemd \
|
|
--linux-packages "linux-image" \
|
|
--linux-flavours "amd64" \
|
|
--parent-mirror-bootstrap "http://deb.debian.org/debian" \
|
|
--parent-mirror-chroot "http://deb.debian.org/debian" \
|
|
--parent-mirror-binary "http://deb.debian.org/debian" \
|
|
--mirror-bootstrap "http://deb.debian.org/debian" \
|
|
--mirror-chroot "http://deb.debian.org/debian" \
|
|
--mirror-binary "http://deb.debian.org/debian" \
|
|
--iso-application "Alfred Linux" \
|
|
--iso-publisher "GoSiteMe" \
|
|
--iso-volume "Alfred Linux 3.0"
|
|
|
|
# Copy selected package list
|
|
echo "[BUILD] Installing package list ($PKG_LIST)..."
|
|
cp "$PROJECT_DIR/config/package-lists/$PKG_LIST" config/package-lists/alfred.list.chroot
|
|
|
|
# Copy selected hooks
|
|
# NOTE: live-build v3 (Ubuntu) uses config/hooks/*.chroot, NOT config/hooks/live/
|
|
echo "[BUILD] Installing build hooks..."
|
|
mkdir -p config/hooks
|
|
for hook in "${HOOKS_TO_INCLUDE[@]}"; do
|
|
if [[ -f "$PROJECT_DIR/config/hooks/live/$hook" ]]; then
|
|
cp "$PROJECT_DIR/config/hooks/live/$hook" config/hooks/
|
|
echo " ✓ $hook"
|
|
else
|
|
echo " ✗ MISSING: $hook"
|
|
fi
|
|
done
|
|
chmod +x config/hooks/*.hook.chroot 2>/dev/null || true
|
|
|
|
# Security repo fix hook (always included)
|
|
cat > config/hooks/0010-fix-security-repo.hook.chroot << 'HOOKEOF'
|
|
#!/bin/bash
|
|
# Fix Debian Trixie security repo — live-build v3 generates the wrong URL format
|
|
# It creates "trixie/updates" but Trixie uses "trixie-security"
|
|
apt-get install -y --no-install-recommends gnupg 2>/dev/null || true
|
|
|
|
# Remove any broken security sources that use the old format
|
|
sed -i '/bookworm\/updates/d' /etc/apt/sources.list 2>/dev/null || true
|
|
rm -f /etc/apt/sources.list.d/security.list 2>/dev/null
|
|
|
|
# Write correct security and updates sources
|
|
echo "deb http://deb.debian.org/debian-security trixie-security main" > /etc/apt/sources.list.d/security.list
|
|
echo "deb http://deb.debian.org/debian trixie-updates main" > /etc/apt/sources.list.d/updates.list
|
|
apt-get update -qq 2>/dev/null || apt-get update 2>/dev/null || true
|
|
HOOKEOF
|
|
chmod +x config/hooks/0010-fix-security-repo.hook.chroot
|
|
|
|
# DNS failsafe hook — ensures DNS works for all subsequent hooks
|
|
cat > config/hooks/0011-fix-dns.hook.chroot << 'DNSEOF'
|
|
#!/bin/bash
|
|
# Ensure DNS resolution works inside chroot for package downloads
|
|
# /etc/resolv.conf may be a broken symlink to systemd-resolved
|
|
rm -f /etc/resolv.conf 2>/dev/null || true
|
|
cat > /etc/resolv.conf << 'RESOLV'
|
|
nameserver 9.9.9.9
|
|
nameserver 1.1.1.1
|
|
nameserver 8.8.8.8
|
|
RESOLV
|
|
echo "[DNS-FIX] Wrote resolvers to /etc/resolv.conf"
|
|
# Verify
|
|
getent hosts deb.debian.org && echo "[DNS-FIX] DNS working" || echo "[DNS-FIX] WARNING: DNS still broken"
|
|
DNSEOF
|
|
chmod +x config/hooks/0011-fix-dns.hook.chroot
|
|
|
|
# ── Hook: Force non-interactive dpkg (prevents conffile prompts killing builds) ──
|
|
cat > config/hooks/0012-fix-dpkg-noninteractive.hook.chroot << 'DPKGEOF'
|
|
#!/bin/bash
|
|
set +e
|
|
echo "[DPKG-FIX] Configuring non-interactive dpkg..."
|
|
|
|
# Force dpkg to keep existing config files when packages update them
|
|
cat > /etc/dpkg/dpkg.cfg.d/force-confold << 'EOF'
|
|
force-confold
|
|
force-confdef
|
|
EOF
|
|
|
|
# Set non-interactive frontend for apt/dpkg
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
echo 'DEBIAN_FRONTEND=noninteractive' >> /etc/environment
|
|
|
|
# Fix any half-configured packages now
|
|
DEBIAN_FRONTEND=noninteractive dpkg --configure -a 2>/dev/null || true
|
|
|
|
echo "[DPKG-FIX] COMPLETE"
|
|
DPKGEOF
|
|
chmod +x config/hooks/0012-fix-dpkg-noninteractive.hook.chroot
|
|
|
|
# Stage .deb packages in includes.chroot/tmp/ (for b2+)
|
|
# NOTE: Do NOT use packages.chroot/ — live-build creates a local apt repo from it,
|
|
# which requires the full 'gpg' binary that isn't in the minimal debootstrap chroot.
|
|
# Instead, stage .debs in /tmp/ and let hooks install them with dpkg -i.
|
|
if [[ "$BUILD_NUM" != "b1" ]]; then
|
|
if [[ -d "$PROJECT_DIR/config/packages.chroot" ]]; then
|
|
mkdir -p config/includes.chroot/tmp
|
|
cp "$PROJECT_DIR/config/packages.chroot/"*.deb config/includes.chroot/tmp/ 2>/dev/null || true
|
|
echo "[BUILD] .deb packages staged in /tmp/: $(ls config/includes.chroot/tmp/*.deb 2>/dev/null | wc -l)"
|
|
fi
|
|
fi
|
|
|
|
# Copy includes.chroot (extension tarballs etc for b3+)
|
|
if [[ -d "$PROJECT_DIR/config/includes.chroot" ]]; then
|
|
mkdir -p config/includes.chroot
|
|
cp -r "$PROJECT_DIR/config/includes.chroot/"* config/includes.chroot/ 2>/dev/null || true
|
|
fi
|
|
|
|
# For b3+: stage Alfred Commander extension tarball
|
|
if [[ "$BUILD_NUM" =~ ^(b[3-6]|rc)$ ]]; then
|
|
if [[ -f "$PROJECT_DIR/assets/alfred-commander-1.0.1.tar.gz" ]]; then
|
|
mkdir -p config/includes.chroot/tmp
|
|
cp "$PROJECT_DIR/assets/alfred-commander-1.0.1.tar.gz" config/includes.chroot/tmp/
|
|
echo "[BUILD] Staged Commander extension for b3+ build"
|
|
fi
|
|
fi
|
|
|
|
# Fix bootloader
|
|
echo "[BUILD] Fixing bootloader config (isolinux)..."
|
|
mkdir -p config/bootloaders/isolinux
|
|
for f in install.cfg isolinux.cfg live.cfg.in menu.cfg stdmenu.cfg; do
|
|
if [[ -f /usr/share/live/build/bootloaders/isolinux/$f ]]; then
|
|
cp /usr/share/live/build/bootloaders/isolinux/$f config/bootloaders/isolinux/
|
|
fi
|
|
done
|
|
# NOTE: splash.svg.in deliberately excluded — live-build's sed processing of it
|
|
# breaks when multiple kernel versions exist (@LINUX_VERSIONS@ contains newlines).
|
|
cp /usr/lib/ISOLINUX/isolinux.bin config/bootloaders/isolinux/isolinux.bin
|
|
cp /usr/lib/syslinux/modules/bios/vesamenu.c32 config/bootloaders/isolinux/vesamenu.c32
|
|
for extra in ldlinux.c32 libutil.c32 libcom32.c32; do
|
|
[[ -f /usr/lib/syslinux/modules/bios/$extra ]] && cp /usr/lib/syslinux/modules/bios/$extra config/bootloaders/isolinux/
|
|
done
|
|
|
|
# Create branded live.cfg (overrides the template that produces empty labels)
|
|
# NOTE: We use /live/vmlinuz but ALSO add a chroot hook to ensure symlinks exist.
|
|
# live-build normally renames vmlinuz-X.Y.Z to vmlinuz, but some builds skip this.
|
|
cat > config/bootloaders/isolinux/live.cfg << 'LIVECFG'
|
|
label live-amd64
|
|
menu label ^Alfred Linux 3.0 (Live)
|
|
menu default
|
|
kernel /live/vmlinuz
|
|
append initrd=/live/initrd.img boot=live config quiet splash
|
|
|
|
label live-amd64-failsafe
|
|
menu label ^Alfred Linux 3.0 (Safe Mode)
|
|
kernel /live/vmlinuz
|
|
append initrd=/live/initrd.img boot=live config nomodeset vga=normal
|
|
LIVECFG
|
|
# Remove .in templates so live-build uses our pre-built config (not sed-processed templates)
|
|
rm -f config/bootloaders/isolinux/live.cfg.in
|
|
rm -f config/bootloaders/isolinux/splash.svg.in
|
|
|
|
# ── Safety net: late-stage chroot hook copies kernel to generic names ──
|
|
# Hook numbered 9999 so it runs LAST, after all packages and initramfs updates.
|
|
# Uses cp (not symlinks) so live-build's binary_linux-image reliably finds them.
|
|
cat > config/hooks/9999-fix-kernel-names.hook.chroot << 'KERNHOOK'
|
|
#!/bin/bash
|
|
echo "[Alfred] Creating generic kernel copies in /boot/ (late-stage)..."
|
|
cd /boot
|
|
|
|
# Always overwrite with latest versioned kernel
|
|
KERNEL=$(ls -1 vmlinuz-* 2>/dev/null | sort -V | tail -1)
|
|
if [[ -n "$KERNEL" ]]; then
|
|
rm -f vmlinuz
|
|
cp "$KERNEL" vmlinuz
|
|
echo "[Alfred] /boot/vmlinuz copied from $KERNEL"
|
|
else
|
|
echo "[Alfred] WARNING: No vmlinuz-* found in /boot!"
|
|
fi
|
|
|
|
INITRD=$(ls -1 initrd.img-* 2>/dev/null | sort -V | tail -1)
|
|
if [[ -n "$INITRD" ]]; then
|
|
rm -f initrd.img
|
|
cp "$INITRD" initrd.img
|
|
echo "[Alfred] /boot/initrd.img copied from $INITRD"
|
|
else
|
|
echo "[Alfred] WARNING: No initrd.img-* found in /boot!"
|
|
fi
|
|
|
|
ls -lh /boot/vmlinuz /boot/initrd.img 2>/dev/null
|
|
KERNHOOK
|
|
chmod +x config/hooks/9999-fix-kernel-names.hook.chroot
|
|
|
|
echo "[BUILD] Bootloader fix applied: $(ls config/bootloaders/isolinux/ | wc -l) files"
|
|
|
|
# Copy skeleton files
|
|
echo "[BUILD] Installing skeleton files..."
|
|
if [[ -d "$PROJECT_DIR/config/includes.chroot" ]]; then
|
|
mkdir -p config/includes.chroot
|
|
cp -r "$PROJECT_DIR/config/includes.chroot/"* config/includes.chroot/ 2>/dev/null || true
|
|
fi
|
|
|
|
# Build!
|
|
echo ""
|
|
echo "[BUILD] ════════════════════════════════════"
|
|
echo "[BUILD] Starting ISO build: $VERSION"
|
|
echo "[BUILD] Time: $(date)"
|
|
echo "[BUILD] ════════════════════════════════════"
|
|
echo ""
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
lb build 2>&1 | tee "$BUILD_DIR/build.log"
|
|
BUILD_RC=${PIPESTATUS[0]}
|
|
echo "[BUILD] lb build exited with code: $BUILD_RC"
|
|
if [ $BUILD_RC -ne 0 ]; then
|
|
echo "[BUILD] WARNING: lb build returned non-zero ($BUILD_RC) — checking if ISO was still created..."
|
|
fi
|
|
|
|
# Move output
|
|
ISO_FILE=$(find . -maxdepth 1 -name "*.iso" -o -name "*.hybrid.iso" | head -1)
|
|
if [[ -n "$ISO_FILE" ]]; then
|
|
mkdir -p "$OUTPUT_DIR"
|
|
mv "$ISO_FILE" "$OUTPUT_DIR/$ISO_NAME"
|
|
cd "$OUTPUT_DIR"
|
|
sha256sum "$ISO_NAME" > "${ISO_NAME}.sha256"
|
|
|
|
# Optional: Add UEFI boot support
|
|
if [[ "$ENABLE_UEFI" == "true" ]]; then
|
|
echo "[BUILD] Adding UEFI boot support..."
|
|
"$SCRIPT_DIR/add-uefi.sh" "$OUTPUT_DIR/$ISO_NAME"
|
|
# Regenerate checksum after UEFI modification
|
|
sha256sum "$ISO_NAME" > "${ISO_NAME}.sha256"
|
|
fi
|
|
SIZE=$(du -h "$ISO_NAME" | cut -f1)
|
|
echo ""
|
|
echo " ╔═══════════════════════════════════════════════════╗"
|
|
echo " ║ BUILD COMPLETE — ${VERSION} ║"
|
|
echo " ║ ISO: $ISO_NAME"
|
|
echo " ║ Size: $SIZE"
|
|
echo " ║ SHA256: $(cat "${ISO_NAME}.sha256" | cut -d' ' -f1 | head -c 16)..."
|
|
echo " ║ Time: $(date)"
|
|
echo " ╚═══════════════════════════════════════════════════╝"
|
|
echo ""
|
|
else
|
|
echo ""
|
|
echo " ╔═══════════════════════════════════════════════════╗"
|
|
echo " ║ BUILD FAILED — ${VERSION} ║"
|
|
echo " ║ Check: $BUILD_DIR/build.log ║"
|
|
echo " ╚═══════════════════════════════════════════════════╝"
|
|
echo ""
|
|
tail -30 "$BUILD_DIR/build.log"
|
|
exit 1
|
|
fi
|