build.sh 11.92 KiB
#!/bin/bash
. tools/functions.sh
# Default variables
export SQUASHFS="output/squashfs"
export ROOTFS_DIR="output/rootfs"
export ISO_DIR="output/iso"
export BOOT_FOLDER="$ISO_DIR/boot"
export SQUASHFS_IMG="squash.rootfs"
export VERBOSE=false
export OUTPUT="build"
export ISO_NAME="nexus.iso"
export UBUNTU_VERSION="noble"
export CHALLENGE="default_challenge"
export ARCH="amd64"
TMP_KERNEL=$(mktemp /tmp/vmlinuz.XXXXXX)
TMP_INITRD=$(mktemp /tmp/initrd.XXXXXX)
TMP_SQUASHFS_IMG=$(mktemp /tmp/squashfs_img.XXXXXX)
helper() {
echo "USAGE: $(basename "$0") [options]"
echo
echo "Options:"
echo " -v, --verbose Set verbose mode"
echo " --env Path to an env file with all described parameters"
echo " --output Set the output folder (default: .)"
echo " --luks-passphrase Enable LUKS encryption with passphrase"
echo " --luks-keys List of additional keys"
echo " --ram Using the system in RAM"
echo " --challenge List of additional keys"
echo " --pxe Set the url of the pxe and generate an initramfs, kernel and squashfs for the pxe to use
(use the url of the folder where the squashfs is stored, not the direct link to the squashfs)"
echo " --cache Path to a folder with a clean fs already downloaded"
echo " --version Version of Ubuntu (e.g., Noble)"
echo
echo "Environment Setup Parameters:"
echo " SQUASHFS Path to the SquashFS file (e.g., out/squash.rootfs)"
echo " ROOTFS_DIR Path to the temporary filesystem directory (e.g., out/rootfs)"
echo " ISO_DIR Path to the temporary ISO directory (e.g., out/iso)"
echo " SERVER IP of the nexus server (e.g., 127.0.0.1:1077)"
echo " CERT Path to the Certificate file (e.g., ca-cert.pem)"
echo " EXAM_USER Default user for exam client (e.g., user)"
echo " EXAM_PWD Default password for exam client (e.g., password)"
echo
echo "Example Usage:"
echo " $(basename "$0") --env .env"
echo " $(basename "$0") --luks-passphrase mypassphrase --cache cache -v"
exit 1
}
while [ "$#" -gt 0 ]; do
case "$1" in
--env)
if [ -f "$2" ]; then
source "$2" > /dev/null
else
echo "Error: File '$2' not found." >&2
helper
fi
shift 2
;;
--output)
if [ -n "$2" ]; then
export OUTPUT="$2"
shift 2
else
echo "Error: --output requires an argument" >&2
helper
fi
;;
--luks-passphrase)
if [ -n "$2" ]; then
LUKS_PASSPHRASE="$2"
shift 2
else
echo "Error: --luks-passphrase requires an argument" >&2
helper
fi
;;
--luks-keys)
if [ -n "$2" ]; then
ADDITIONAL_KEYS="$2"
shift 2
else
echo "Error: --luks-keys requires an argument" >&2
helper
fi
;;
--challenge)
if [ -n "$2" ]; then
CHALLENGE="$2"
shift 2
else
echo "Error: --challenge requires an argument" >&2
helper
fi
;;
--pxe)
if [ -n "$2" ]; then
export PXE_URL="$2"
shift 2
else
echo "Error: --pxe requires an argument" >&2
helper
fi
;;
--ram)
export RAM="squashfs_tmpfs"
shift
;;
-v|--verbose)
export VERBOSE=true
shift
;;
--cache)
if [ -n "$2" ]; then
export CACHE_FS="$2"
shift 2
else
echo "Error: --cache requires an argument" >&2
helper
fi
;;
--version)
if [ -n "$2" ]; then
export UBUNTU_VERSION="$2"
shift 2
else
echo "Error: --version requires an argument" >&2
helper
fi
;;
*)
echo "Unknown option: $1" >&2
helper
;;
esac
done
check_inside_container
check_environment_var
check_dependencies
CMDLINE="boot=nexus quiet splash $RAM modprobe.blacklist=floppy"
echo "[Cleanup $ROOTFS_DIR]"
ROOTFS_PATH="${ROOTFS_DIR%/}"
child_mounts=$(findmnt -rn -o TARGET | grep -E "^${ROOTFS_PATH}/[^/]+$")
if [ -n "$child_mounts" ]; then
echo "WARNING: The following mount points are directly under $ROOTFS_DIR:"
echo "$child_mounts"
echo
read -p "Do you want to unmount these mount points? [y/N]: " confirmation
case "$confirmation" in
[yY][eE][sS]|[yY])
while IFS= read -r mount_point; do
echo "Unmounting $mount_point..."
if ! umount "$mount_point"; then
echo "Error: Failed to unmount $mount_point. Please unmount manually or reboot the system to avoid issues."
exit 1
fi
done <<< "$child_mounts"
;;
*)
echo "Cleanup aborted by user."
exit 1
;;
esac
fi
run_command rm -rf "$ROOTFS_DIR"
echo "[Cleanup $OUTPUT]"
run_command rm -rf "$OUTPUT"
echo "[Create filesystem...]"
mkdir -p "$ROOTFS_DIR"
if [ -z "$CACHE_FS" ]; then
run_command debootstrap --arch="$ARCH" "$UBUNTU_VERSION" "$ROOTFS_DIR" http://archive.ubuntu.com/ubuntu/
run_command mount -t proc /proc "$ROOTFS_DIR/proc"
run_command mount --rbind /sys "$ROOTFS_DIR/sys"
run_command mount --rbind /dev "$ROOTFS_DIR/dev"
run_command mount --rbind /run "$ROOTFS_DIR/run"
run_command mount -t tmpfs tmpfs "$ROOTFS_DIR/tmp"
echo "[Installing packages...]"
packages=$(tr '\n' ' ' < config/01-packages_install/packages)
echo "deb http://archive.ubuntu.com/ubuntu $UBUNTU_VERSION main universe" > "$ROOTFS_DIR/etc/apt/sources.list"
run_command_chroot apt update
run_command_chroot apt install -y --no-install-recommends "$packages"
else
if [ ! -d "$CACHE_FS" ]; then
mkdir "$CACHE_FS"
run_command debootstrap --arch="$ARCH" "$UBUNTU_VERSION" "$CACHE_FS" http://archive.ubuntu.com/ubuntu/
run_command mount -t proc /proc "$CACHE_FS/proc"
run_command mount --rbind /sys "$CACHE_FS/sys"
run_command mount --rbind /dev "$CACHE_FS/dev"
run_command mount --rbind /run "$CACHE_FS/run"
run_command mount -t tmpfs tmpfs "$CACHE_FS/tmp"
echo "[Installing packages...]"
packages=$(tr '\n' ' ' < config/01-packages_install/packages)
echo "deb http://archive.ubuntu.com/ubuntu $UBUNTU_VERSION main universe" > "$CACHE_FS/etc/apt/sources.list"
run_command chroot "$CACHE_FS" apt update
run_command chroot "$CACHE_FS" apt install -y --no-install-recommends "$packages"
run_command umount -l "$CACHE_FS/proc"
run_command umount -l "$CACHE_FS/sys"
run_command umount -l "$CACHE_FS/dev"
run_command umount -l "$CACHE_FS/run"
run_command umount -l "$CACHE_FS/tmp"
else
echo " [Skip through cache]"
fi
run_command cp -r "$CACHE_FS"/. "$ROOTFS_DIR"/
run_command mount -t proc /proc "$ROOTFS_DIR/proc"
run_command mount --rbind /sys "$ROOTFS_DIR/sys"
run_command mount --rbind /dev "$ROOTFS_DIR/dev"
run_command mount --rbind /run "$ROOTFS_DIR/run"
run_command mount -t tmpfs tmpfs "$ROOTFS_DIR/tmp"
fi
echo "[Uploading configuration file...]"
run_command cp -rf config/02-customisation/* "$ROOTFS_DIR"
echo "[Checking for required dependencies...]"
if [ ! -d "/opt/limine" ]; then
echo "Error: /opt/limine not found. Please install the Limine bootloader files in /opt/limine."
exit 1
fi
if [ ! -f "$ROOTFS_DIR/usr/lib/shim/shimx64.efi.signed.latest" ] || [ ! -f "$ROOTFS_DIR/usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed" ]; then
echo "Error: Required EFI files not found in $ROOTFS_DIR."
exit 1
fi
if [ -z "$PXE_URL" ]; then
EFIBOOT=$(mktemp /tmp/efiboot.img.XXXXXX)
EFIBOOT_MOUNT=$(mktemp -d /tmp/efiboot.XXXXXX)
echo "[EFI img preparation]"
run_command mkdir -p "$ISO_DIR/boot/grub"
run_command "sed 's/CMDLINE/$CMDLINE/g' config/00-bootloader/grub.cfg > $ISO_DIR/boot/grub/grub.cfg"
SIZE_EFIBOOT=$(get_total_size "$ROOTFS_DIR/usr/lib/shim/shimx64.efi.signed.latest" "$ROOTFS_DIR/usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed")
OFFSET=$(( 150 * 1024 )) # Some space for fat header and grub.cfg file
run_command fallocate -l $(( $SIZE_EFIBOOT + $OFFSET)) "$EFIBOOT"
run_command mkfs.vfat "$EFIBOOT"
run_command mount -o loop "$EFIBOOT" "$EFIBOOT_MOUNT"
run_command mkdir -p "$EFIBOOT_MOUNT/EFI/BOOT"
run_command cp config/00-bootloader/grub.efi.cfg "$EFIBOOT_MOUNT/EFI/BOOT/grub.cfg"
run_command cp "$ROOTFS_DIR/usr/lib/shim/shimx64.efi.signed.latest" "$EFIBOOT_MOUNT/EFI/BOOT/BOOTx64.EFI"
run_command cp "$ROOTFS_DIR/usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed" "$EFIBOOT_MOUNT/EFI/BOOT/grubx64.efi"
run_command umount "$EFIBOOT_MOUNT"
run_command rmdir "$EFIBOOT_MOUNT"
fi
echo "[Post-install...]"
for script in config/03-post_install/*.sh; do
echo " [Running $script...]"
run_command "$script"
done
echo "[Moving kernel...]"
KERNEL_FILE=$(ls -1 "$ROOTFS_DIR/boot/vmlinuz-"* 2>/dev/null | head -n 1)
if [ -z "$KERNEL_FILE" ]; then
echo "Error: No kernel file found in $ROOTFS_DIR/boot"
exit 1
fi
run_command cp "$KERNEL_FILE" "$TMP_KERNEL"
run_command cp "$ROOTFS_DIR/boot/initrd.img" "$TMP_INITRD"
run_command rm -rf "$ROOTFS_DIR/boot"
echo "[Unmount subsystem...]"
run_command umount -l "$ROOTFS_DIR/proc"
run_command umount -l "$ROOTFS_DIR/sys"
run_command umount -l "$ROOTFS_DIR/dev"
run_command umount -l "$ROOTFS_DIR/run"
run_command umount -l "$ROOTFS_DIR/tmp"
echo "[Create squash.rootfs...]"
run_command mksquashfs "$ROOTFS_DIR" "$SQUASHFS"
if [ ! -z "$LUKS_PASSPHRASE" ]; then
echo "[Encrypt squash.rootfs...]"
SQUASHFS_SIZE=$(stat -c %s "$SQUASHFS")
LUKS_HEADER_SIZE=$((1024 * 1024 * 16)) # 16 MiB header LUKS2
TOTAL_SIZE=$((SQUASHFS_SIZE + LUKS_HEADER_SIZE))
SECTOR_SIZE=512
run_command fallocate -l "$TOTAL_SIZE" "$TMP_SQUASHFS_IMG"
run_command bash -c "cat <<EOF | cryptsetup luksFormat '$TMP_SQUASHFS_IMG' --batch-mode
$LUKS_PASSPHRASE
EOF"
run_command bash -c "cat <<EOF | cryptsetup luksOpen '$TMP_SQUASHFS_IMG' container
$LUKS_PASSPHRASE
EOF"
run_command dd if="$SQUASHFS" of=/dev/mapper/container bs=4M
run_command cryptsetup luksClose container
if [ ! -z "$ADDITIONAL_KEYS" ]; then
for key in $ADDITIONAL_KEYS; do
key_file=$(mktemp)
echo "$key" > "$key_file"
run_command bash -c "cat <<EOF | cryptsetup luksAddKey '$TMP_SQUASHFS_IMG'
$LUKS_PASSPHRASE
$key
EOF"
rm -f "$key_file"
done
fi
else
cp "$SQUASHFS" "$TMP_SQUASHFS_IMG"
fi
mkdir "$OUTPUT"
if [ ! -z "$PXE_URL" ]; then
run_command cp "$TMP_KERNEL" "$OUTPUT/vmlinuz"
run_command cp "$TMP_INITRD" "$OUTPUT/initrd"
run_command cp "$TMP_SQUASHFS_IMG" "$OUTPUT/$SQUASHFS_IMG"
run_command cp "$ROOTFS_DIR/usr/lib/shim/shimx64.efi.signed.latest" "$OUTPUT/BOOTx64.EFI"
run_command cp "$ROOTFS_DIR/usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed" "$OUTPUT/grubx64.efi"
CMDLINE="boot=pxe quiet splash $RAM modprobe.blacklist=floppy"
run_command "sed 's/CMDLINE/$CMDLINE/g' config/00-bootloader/grub.cfg > $OUTPUT/grub.cfg"
else
echo "[Moving kernel && squashfs from rootfs to iso/boot...]"
run_command mkdir -p "$BOOT_FOLDER"
run_command cp "$TMP_KERNEL" "$BOOT_FOLDER/vmlinuz"
run_command cp "$TMP_INITRD" "$BOOT_FOLDER/initrd"
echo "[Create iso...]"
run_command cp "$TMP_SQUASHFS_IMG" "$ISO_DIR/$SQUASHFS_IMG"
run_command cp /opt/limine/*.bin "$ISO_DIR/"
run_command cp /opt/limine/*.sys "$ISO_DIR/"
run_command cp "$EFIBOOT" "$ISO_DIR/efiboot.img"
run_command "sed 's/CMDLINE/$CMDLINE/g' config/00-bootloader/limine.conf > $ISO_DIR/boot/limine.conf"
run_command xorriso -as mkisofs -R -r -J -b "limine-bios-cd.bin" \
-no-emul-boot -boot-load-size 4 -boot-info-table -hfsplus \
-apm-block-size 2048 --efi-boot "efiboot.img" \
-efi-boot-part --efi-boot-image --protective-msdos-label \
-volid "NEXUSCLIENTISO" \
"$ISO_DIR" -o "$OUTPUT/$ISO_NAME"
run_command limine bios-install "$OUTPUT/$ISO_NAME"
fi
chown -R 1000:1000 "$OUTPUT"
echo "[Cleanup temporary files...]"
rm -f "$TMP_KERNEL" "$TMP_INITRD" "$TMP_SQUASHFS_IMG" "$EFIBOOT"