From d0fd7fcc7beacd95c31cf7dad9157d65b8ec8b5f Mon Sep 17 00:00:00 2001 From: Jan De Landtsheer Date: Tue, 10 Dec 2024 18:37:25 +0100 Subject: [PATCH] Keep netboot entries in efiboot when defining Zero-OS as first boot, we can not get the node back into rescue mode as that is the way Hetzner functions: tne netboot get an ipxe that doe a local reload or, in case of rescue starts a full netboot --- zosinstalluefipxeondisk.sh | 480 ++++++++++++++++++++----------------- 1 file changed, 259 insertions(+), 221 deletions(-) diff --git a/zosinstalluefipxeondisk.sh b/zosinstalluefipxeondisk.sh index 21a404e..2295b6b 100644 --- a/zosinstalluefipxeondisk.sh +++ b/zosinstalluefipxeondisk.sh @@ -5,264 +5,302 @@ set -e # Function to log messages with timestamps log() { - echo "$(date '+%F %T') - $@" + echo "$(date '+%F %T') - $@" } # Function to display usage information usage() { - echo "Usage: $0 [-d DEVICE] [-f FARM] [-e ENVIRONMENT]" - echo " -d, --device DEVICE Specify the block device to use (e.g., sda, nvme0n1)." - echo " -f, --farm FARM Specify the FARM ID." - echo " -e, --environment ENV Specify the environment (dev, qa, test, prod)." - echo " -h, --help Display this help message." - exit 1 + echo "Usage: $0 [-d DEVICE] [-f FARM] [-e ENVIRONMENT]" + echo " -d, --device DEVICE Specify the block device to use (e.g., sda, nvme0n1)." + echo " -f, --farm FARM Specify the FARM ID." + echo " -e, --environment ENV Specify the environment (dev, qa, test, prod)." + echo " -h, --help Display this help message." + exit 1 } # Function to find a suitable block device find_blockdev() { - # Minimum size in bytes, e.g., 10 GiB - MIN_SIZE=$((10 * 1024 * 1024 * 1024)) - - # Search for NVMe devices first - log "Searching for NVMe devices..." - for DEV in /dev/nvme*n1; do - if [ -b "$DEV" ]; then - SIZE_BYTES=$(blockdev --getsize64 "$DEV") - if [ "$SIZE_BYTES" -ge "$MIN_SIZE" ]; then - log "Found NVMe device: $DEV ($(numfmt --to=iec $SIZE_BYTES))" - BLOCKDEV="${DEV##*/}" - return 0 - else - log "$DEV is smaller than $(numfmt --to=iec $MIN_SIZE). Skipping." - fi - fi - done - - # If no NVMe device found, search for SSD devices - log "No suitable NVMe device found. Searching for SSD devices..." - for DEV_PATH in /sys/block/*; do - DEV_NAME=$(basename "$DEV_PATH") - # Exclude unwanted devices - case "$DEV_NAME" in - loop*|ram*|sr*|fd*|md*|dm-*) - continue - ;; - esac - DEVICE="/dev/$DEV_NAME" - if [ -b "$DEVICE" ] && [ -e "/sys/block/$DEV_NAME/queue/rotational" ] && [ "$(cat /sys/block/$DEV_NAME/queue/rotational)" == "0" ]; then - SIZE_BYTES=$(blockdev --getsize64 "$DEVICE") - if [ "$SIZE_BYTES" -ge "$MIN_SIZE" ]; then - log "Found SSD device: $DEVICE ($(numfmt --to=iec $SIZE_BYTES))" - BLOCKDEV="$DEV_NAME" - return 0 - else - log "$DEVICE is smaller than $(numfmt --to=iec $MIN_SIZE). Skipping." - fi - fi - done + # Minimum size in bytes, e.g., 10 GiB + MIN_SIZE=$((10 * 1024 * 1024 * 1024)) + + # Search for NVMe devices first + log "Searching for NVMe devices..." + for DEV in /dev/nvme*n1; do + if [ -b "$DEV" ]; then + SIZE_BYTES=$(blockdev --getsize64 "$DEV") + if [ "$SIZE_BYTES" -ge "$MIN_SIZE" ]; then + log "Found NVMe device: $DEV ($(numfmt --to=iec $SIZE_BYTES))" + BLOCKDEV="${DEV##*/}" + return 0 + else + log "$DEV is smaller than $(numfmt --to=iec $MIN_SIZE). Skipping." + fi + fi + done + + # If no NVMe device found, search for SSD devices + log "No suitable NVMe device found. Searching for SSD devices..." + for DEV_PATH in /sys/block/*; do + DEV_NAME=$(basename "$DEV_PATH") + # Exclude unwanted devices + case "$DEV_NAME" in + loop* | ram* | sr* | fd* | md* | dm-*) + continue + ;; + esac + DEVICE="/dev/$DEV_NAME" + if [ -b "$DEVICE" ] && [ -e "/sys/block/$DEV_NAME/queue/rotational" ] && [ "$(cat /sys/block/$DEV_NAME/queue/rotational)" == "0" ]; then + SIZE_BYTES=$(blockdev --getsize64 "$DEVICE") + if [ "$SIZE_BYTES" -ge "$MIN_SIZE" ]; then + log "Found SSD device: $DEVICE ($(numfmt --to=iec $SIZE_BYTES))" + BLOCKDEV="$DEV_NAME" + return 0 + else + log "$DEVICE is smaller than $(numfmt --to=iec $MIN_SIZE). Skipping." + fi + fi + done - log "No suitable block device found." - return 1 + log "No suitable block device found." + return 1 } # Function to manage UEFI boot entries manage_boot_entries() { - log "Managing UEFI boot entries..." + log "Managing UEFI boot entries..." - # Check if efibootmgr is available - if ! command -v efibootmgr &> /dev/null; then - log "Error: 'efibootmgr' is not installed. Please install it and rerun the script." - exit 1 + # Check if efibootmgr is available + if ! command -v efibootmgr &>/dev/null; then + log "Error: 'efibootmgr' is not installed. Please install it and rerun the script." + exit 1 + fi + + # Get the list of current boot entries + log "Retrieving current boot entries..." + BOOT_ENTRIES_INFO=$(efibootmgr -v | grep -E "^Boot[0-9A-F]{4}") + + # Arrays to hold boot entry numbers + NETBOOT_ENTRIES=() + ENTRIES_TO_DELETE=() + + # Identify netboot entries and entries to delete + while read -r LINE; do + ENTRY_NUM=$(echo "$LINE" | awk '{print $1}' | sed 's/Boot//;s/\*//') + ENTRY_DESC=$(echo "$LINE" | cut -d' ' -f2-) + ENTRY_PATH=$(echo "$LINE" | awk -F'File' '{print $2}') + + # Check if the entry is a netboot entry (e.g., contains "PXE", "Network", or "IPv4"/"IPv6") + if echo "$ENTRY_DESC" | grep -qiE "(PXE|Network|IPV4|IPV6)"; then + NETBOOT_ENTRIES+=("$ENTRY_NUM") + log "Identified netboot entry: Boot$ENTRY_NUM - $ENTRY_DESC" + else + ENTRIES_TO_DELETE+=("$ENTRY_NUM") + log "Marking entry for deletion: Boot$ENTRY_NUM - $ENTRY_DESC" fi - - # Get the list of current boot entries - CURRENT_BOOT_ENTRIES=$(efibootmgr | grep "BootOrder") - log "Current BootOrder: $CURRENT_BOOT_ENTRIES" - - # Delete all existing boot entries except the EFI Shell (if present) - log "Deleting existing boot entries..." - BOOT_ENTRIES=$(efibootmgr | grep -E "^Boot[0-9A-F]{4}" | awk '{print $1}' | sed 's/\*//') - for ENTRY in $BOOT_ENTRIES; do - efibootmgr -b ${ENTRY#Boot} -B - log "Deleted boot entry $ENTRY" + done <<<"$BOOT_ENTRIES_INFO" + + # Delete non-netboot entries + if [ ${#ENTRIES_TO_DELETE[@]} -gt 0 ]; then + log "Deleting non-netboot boot entries..." + for ENTRY_NUM in "${ENTRIES_TO_DELETE[@]}"; do + efibootmgr -b "$ENTRY_NUM" -B + log "Deleted boot entry Boot$ENTRY_NUM" done + else + log "No non-netboot entries to delete." + fi + + # Create a new boot entry pointing to BOOTX64.EFI on the first partition + log "Creating new boot entry for /dev/${PARTPREFIX}1..." + # Adjust the partition number if necessary + ESP_PART_NUM=1 + efibootmgr -c -d /dev/${BLOCKDEV} -p $ESP_PART_NUM -L "Zero-OS" -l "\\EFI\\BOOT\\BOOTX64.EFI" || { + log "Error: Failed to create new boot entry." + exit 1 + } - # Create a new boot entry pointing to BOOTX64.EFI on the first partition - log "Creating new boot entry for /dev/${PARTPREFIX}1..." - # Adjust the partition number if necessary - ESP_PART_NUM=1 - efibootmgr -c -d /dev/${BLOCKDEV} -p $ESP_PART_NUM -L "Zero-OS" -l "\\EFI\\BOOT\\BOOTX64.EFI" || { - log "Error: Failed to create new boot entry." - exit 1 - } - - # Set the boot order to include only the new boot entry - NEW_BOOT_NUM=$(efibootmgr | grep "Zero-OS" | awk '{print $1}' | sed 's/\*//;s/Boot//') - if [ -z "$NEW_BOOT_NUM" ]; then - log "Error: Failed to retrieve new boot entry number." - exit 1 - fi - log "Setting BootOrder to only include Boot$NEW_BOOT_NUM..." - efibootmgr -o $NEW_BOOT_NUM || { - log "Error: Failed to set new BootOrder." - exit 1 - } + # Retrieve the new boot entry number + NEW_BOOT_NUM=$(efibootmgr | grep "Zero-OS" | awk '{print $1}' | sed 's/\*//;s/Boot//') + if [ -z "$NEW_BOOT_NUM" ]; then + log "Error: Failed to retrieve new boot entry number." + exit 1 + fi + + # Set the new boot order: netboot entries first, followed by the new boot entry + log "Setting new BootOrder with netboot entries first..." + BOOT_ORDER="" + for ENTRY_NUM in "${NETBOOT_ENTRIES[@]}"; do + BOOT_ORDER+="$ENTRY_NUM," + done + BOOT_ORDER+="$NEW_BOOT_NUM" + + efibootmgr -o "$BOOT_ORDER" || { + log "Error: Failed to set new BootOrder." + exit 1 + } - log "New BootOrder set successfully." + log "New BootOrder set successfully: $BOOT_ORDER" } # Main script execution main() { - # Default values - BLOCKDEV_SPECIFIED="" - FARM="1234" - ENVIRONMENT="prod" - - # Parse command-line arguments - while [[ $# -gt 0 ]]; do - case "$1" in - -d|--device) - shift - if [ -z "$1" ]; then - log "Error: '--device' requires a non-empty option argument." - usage - fi - BLOCKDEV_SPECIFIED="$1" - shift - ;; - -f|--farm) - shift - if [ -z "$1" ]; then - log "Error: '--farm' requires a non-empty option argument." - usage - fi - FARM="$1" - shift - ;; - -e|--environment) - shift - if [ -z "$1" ]; then - log "Error: '--environment' requires a non-empty option argument." - usage - fi - ENVIRONMENT="$1" - shift - ;; - -h|--help) - usage - ;; - *) - log "Error: Unknown option: $1" - usage - ;; - esac - done - - # Validate environment - case "$ENVIRONMENT" in - dev|qa|test|prod) - log "Using environment: $ENVIRONMENT" - ;; - *) - log "Error: Invalid environment '$ENVIRONMENT'. Valid options are 'dev', 'qa', 'test', 'prod'." - exit 1 - ;; + # Default values + BLOCKDEV_SPECIFIED="" + FARM="1234" + ENVIRONMENT="prod" + + # Parse command-line arguments + while [[ $# -gt 0 ]]; do + case "$1" in + -d | --device) + shift + if [ -z "$1" ]; then + log "Error: '--device' requires a non-empty option argument." + usage + fi + BLOCKDEV_SPECIFIED="$1" + shift + ;; + -f | --farm) + shift + if [ -z "$1" ]; then + log "Error: '--farm' requires a non-empty option argument." + usage + fi + FARM="$1" + shift + ;; + -e | --environment) + shift + if [ -z "$1" ]; then + log "Error: '--environment' requires a non-empty option argument." + usage + fi + ENVIRONMENT="$1" + shift + ;; + -h | --help) + usage + ;; + *) + log "Error: Unknown option: $1" + usage + ;; esac + done + + # Validate environment + case "$ENVIRONMENT" in + dev | qa | test | prod) + log "Using environment: $ENVIRONMENT" + ;; + *) + log "Error: Invalid environment '$ENVIRONMENT'. Valid options are 'dev', 'qa', 'test', 'prod'." + exit 1 + ;; + esac - if [ -n "$BLOCKDEV_SPECIFIED" ]; then - # Use the specified block device - if [[ "$BLOCKDEV_SPECIFIED" != /dev/* ]]; then - BLOCKDEV_SPECIFIED="/dev/$BLOCKDEV_SPECIFIED" - fi - if [ ! -b "$BLOCKDEV_SPECIFIED" ]; then - log "Error: Specified device $BLOCKDEV_SPECIFIED does not exist or is not a block device." - exit 1 - fi - BLOCKDEV="${BLOCKDEV_SPECIFIED##*/}" - log "Using specified block device: $BLOCKDEV_SPECIFIED" - else - # Find a suitable block device - if ! find_blockdev; then - log "Error: No suitable block device found." - exit 1 - fi - log "Selected block device: /dev/${BLOCKDEV}" + if [ -n "$BLOCKDEV_SPECIFIED" ]; then + # Use the specified block device + if [[ "$BLOCKDEV_SPECIFIED" != /dev/* ]]; then + BLOCKDEV_SPECIFIED="/dev/$BLOCKDEV_SPECIFIED" fi - - # Confirm with the user before proceeding - read -p "This will erase all data on /dev/${BLOCKDEV}. Are you sure you want to proceed? (yes/[no]): " CONFIRM - if [ "$CONFIRM" != "yes" ]; then - log "Operation cancelled by user." - exit 0 + if [ ! -b "$BLOCKDEV_SPECIFIED" ]; then + log "Error: Specified device $BLOCKDEV_SPECIFIED does not exist or is not a block device." + exit 1 fi - - # Determine partition naming convention - if [[ $BLOCKDEV =~ [0-9]$ ]]; then - PARTPREFIX="${BLOCKDEV}p" - else - PARTPREFIX="${BLOCKDEV}" + BLOCKDEV="${BLOCKDEV_SPECIFIED##*/}" + log "Using specified block device: $BLOCKDEV_SPECIFIED" + else + # Find a suitable block device + if ! find_blockdev; then + log "Error: No suitable block device found." + exit 1 fi + log "Selected block device: /dev/${BLOCKDEV}" + fi + + # Confirm with the user before proceeding + read -p "This will erase all data on /dev/${BLOCKDEV}. Are you sure you want to proceed? (yes/[no]): " CONFIRM + if [ "$CONFIRM" != "yes" ]; then + log "Operation cancelled by user." + exit 0 + fi + + # Determine partition naming convention + if [[ $BLOCKDEV =~ [0-9]$ ]]; then + PARTPREFIX="${BLOCKDEV}p" + else + PARTPREFIX="${BLOCKDEV}" + fi + + # Wipe filesystem signatures + log "Wiping filesystem signatures on /dev/${BLOCKDEV}..." + wipefs -a -f /dev/${BLOCKDEV} || { + log "Failed to wipe filesystem signatures." + exit 1 + } - # Wipe filesystem signatures - log "Wiping filesystem signatures on /dev/${BLOCKDEV}..." - wipefs -a -f /dev/${BLOCKDEV} || { log "Failed to wipe filesystem signatures."; exit 1; } - - # Create GPT partition table - log "Creating GPT partition table on /dev/${BLOCKDEV}..." - parted -s /dev/${BLOCKDEV} mklabel gpt || { log "Failed to create GPT partition table."; exit 1; } + # Create GPT partition table + log "Creating GPT partition table on /dev/${BLOCKDEV}..." + parted -s /dev/${BLOCKDEV} mklabel gpt || { + log "Failed to create GPT partition table." + exit 1 + } - # Create partitions - log "Creating partitions on /dev/${BLOCKDEV}..." - if ! parted -s /dev/${BLOCKDEV} "mkpart zosboot fat32 1MiB 300MiB set 1 esp on mkpart zoscache btrfs 300MiB 100%"; then - log "Failed to create partitions." - exit 1 - fi + # Create partitions + log "Creating partitions on /dev/${BLOCKDEV}..." + if ! parted -s /dev/${BLOCKDEV} "mkpart zosboot fat16 1MiB 100MiB set 1 esp on mkpart zoscache btrfs 100MiB 100%"; then + log "Failed to create partitions." + exit 1 + fi - # Format partitions - log "Formatting /dev/${PARTPREFIX}1 as FAT32..." - if ! mkfs.vfat -F32 /dev/${PARTPREFIX}1; then - log "Failed to format /dev/${PARTPREFIX}1 as FAT32." - exit 1 - fi + # Format partitions + log "Formatting /dev/${PARTPREFIX}1 as FAT32..." + if ! mkfs.vfat -F32 -nZOSPXE /dev/${PARTPREFIX}1; then + log "Failed to format /dev/${PARTPREFIX}1 as FAT32." + exit 1 + fi - log "Formatting /dev/${PARTPREFIX}2 as BTRFS..." - if ! mkfs.btrfs -f /dev/${PARTPREFIX}2; then - log "Failed to format /dev/${PARTPREFIX}2 as BTRFS." - exit 1 - fi + log "Formatting /dev/${PARTPREFIX}2 as BTRFS..." + if ! mkfs.btrfs -f /dev/${PARTPREFIX}2 -LZOSCACHE; then + log "Failed to format /dev/${PARTPREFIX}2 as BTRFS." + exit 1 + fi + + # Mount and prepare directories + MOUNT_POINT="zospxe" + log "Creating and mounting directory ./${MOUNT_POINT}..." + mkdir -p "${MOUNT_POINT}" + if ! mount /dev/${PARTPREFIX}1 -t vfat "${MOUNT_POINT}"; then + log "Failed to mount /dev/${PARTPREFIX}1 to ${MOUNT_POINT}." + exit 1 + fi - # Mount and prepare directories - MOUNT_POINT="zospxe" - log "Creating and mounting directory ./${MOUNT_POINT}..." - mkdir -p "${MOUNT_POINT}" - if ! mount /dev/${PARTPREFIX}1 -t vfat "${MOUNT_POINT}"; then - log "Failed to mount /dev/${PARTPREFIX}1 to ${MOUNT_POINT}." - exit 1 - fi + log "Creating directory structure in ${MOUNT_POINT}..." + mkdir -p "${MOUNT_POINT}/efi/boot" - log "Creating directory structure in ${MOUNT_POINT}..." - mkdir -p "${MOUNT_POINT}/efi/boot" - - # Download the required BOOTX64.EFI file - # NOTE:: EDIT THIS WHEN APPROPRIATE AND IN PROD - EFI_FILE_URL="https://bootstrap.grid.tf/uefi/${ENVIRONMENT}/${FARM}/debug/zero-os-development-zos-v4-debug-7d2de62033.efi?version=v4" - # EFI_FILE_URL="https://bootstrap.grid.tf/uefi/${ENVIRONMENT}/${FARM}" - log "Downloading BOOTX64.EFI from ${EFI_FILE_URL}..." - if ! wget -q "${EFI_FILE_URL}" -O "${MOUNT_POINT}/efi/boot/BOOTX64.EFI"; then - log "Failed to download BOOTX64.EFI." - exit 1 - fi + # Download the required BOOTX64.EFI file + EFI_FILE_URL="https://bootstrap.grid.tf/uefi/${ENVIRONMENT}/${FARM}" + log "Downloading BOOTX64.EFI from ${EFI_FILE_URL}..." + if ! wget -q "${EFI_FILE_URL}" -O "${MOUNT_POINT}/efi/boot/BOOTX64.EFI"; then + log "Failed to download BOOTX64.EFI." + exit 1 + fi - log "Download successful." + log "Download successful." - # Unmount the partition - log "Unmounting ${MOUNT_POINT}..." - umount -r "${MOUNT_POINT}" || { log "Failed to unmount ${MOUNT_POINT}."; exit 1; } + # Unmount the partition + log "Unmounting ${MOUNT_POINT}..." + umount -r "${MOUNT_POINT}" || { + log "Failed to unmount ${MOUNT_POINT}." + exit 1 + } - # Manage UEFI boot entries - manage_boot_entries + # Manage UEFI boot entries + manage_boot_entries - log "Operation completed successfully." + log "Operation completed successfully." } # Execute the main function