#!/bin/bash # # Copyright (c) 2022 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # # Utility to convert a StarlingX installation iso into a # prestaged subcloud installation iso. # # Docker images can also be added to the iso ,removing the need # for each subcloud to download the images independently. # Docker images must be in 'docker save' format. Multiple container # images can be captured in a single archive. No single archive may # exceed 4 GB. Multiple archives can be provided. All archives # must have the suffix 'tar.gz'. # Error log print function log_fatal { echo "ERROR: $@" >&2 && exit 1 } function log_error { echo "ERROR: $@" >&2 } # Info log function log { echo "INFO: $@" >&2 } # Usage manual. function usage { cat < --output [ --images ] [ --patch ] [ --kickstart-patch ] [ --addon ] [ --param = ] [ --default-boot ] [ --timeout ] [ --force-install ] --input : Specify input ISO file --output : Specify output ISO file --images : Specify a collection of docker images in 'docker save' format. This option can be specified more than once, or a comma separated list can be used. Multiple images can be captured in a single archive. No single archive may exceed 4 GB. --patch : Specify software patch file(s). Can be specified more than once, or provide a comma separated list. --kickstart-patch : A patch to replace the prestaged installer kickstart. Not to be included in the runtime patches. --setup : Specify ks-setup.cfg file. --addon : Specify ks-addon.cfg file. --param : Specify boot parameter(s). Can be specified more than once, or provide a comma separated list. Examples: --param rootfs_device=nvme0n1,boot_device=nvme0n1 --param rootfs_device=/dev/disk/by-path/pci-0000:00:0d.0-ata-1.0 --param boot_device=/dev/disk/by-path/pci-0000:00:0d.0-ata-1.0 --default-boot : Specify default boot menu option: 0 - Serial Console 1 - Graphical Console (default) --timeout : Specify boot menu timeout, in seconds. (default 30) A value of -1 will wait forever. --force-install: Force install the prestaged content even if there is already an installation on the target. ENDUSAGE } function cleanup { # This is invoked from the trap handler. common_cleanup } function check_requirements { common_check_requirements mkisofs isohybrid cpio cp find } function mkdir_on_iso { local dir="${1}" local final_dir="${BUILDDIR}/${dir}" mkdir -p "${final_dir}" if [ $? -ne 0 ]; then log_error "Error: mkdir_on_iso: Failed to create directory '${dir}'" exit 1 fi } function normalized_path { local path="${1}" local default_fn="${2}" local path_name="$(basename "${path}")" local path_dir="$(dirname "${path}")" # If 'path' ends in '/' then path was intended to be a directory if [ "${path:(-1):1}" == "/" ]; then # Drop the trailing '/' path_dir="${path:0:(-1)}" path_name="${default_fn}" fi # drop leading '.' from path_dir if [ "${path_dir:0:1}" == "." ]; then path_dir="${path_dir:1}" fi # drop leading '/' from path_dir if [ "${path_dir:0:1}" == "/" ]; then path_dir="${path_dir:1}" fi if [ -z "${path_dir}" ]; then echo "${path_name}" else echo "${path_dir}/${path_name}" fi } function copy_to_iso { local src="${1}" local dest="${2}" local md5="${3}" local overwrite="${4}" local default_dest= local final_dest= local final_dest_dir= local final_md5= local final_md5_dir= if [ -z "${src}" ] || [ -z "${dest}" ]; then log_error "Error: copy_to_iso: missing argument" exit 1 fi if [ ! -f "${src}" ]; then log_error "Error: copy_to_iso: source file doesn't exist '${src}'" exit 1 fi default_dest="$(basename "${src}")" dest="$(normalized_path "${dest}" "${default_dest}")" final_dest="${BUILDDIR}/${dest}" final_dest_dir="$(dirname "${final_dest}")" if [ ! -z "${md5}" ]; then case "${md5}" in y | Y | yes | YES ) # Use a default name, in same dir as dest md5="$(dirname "${dest}")" ;; esac final_md5="${BUILDDIR}/${md5}" fi if [ -z "${overwrite}" ] || [ "${overwrite}" == 'n' ]; then if [ -f "${final_dest}" ]; then log_error "Error: copy_to_iso: destination already exists '${final_dest}'" exit 1 fi fi if [ ! -d "${final_dest_dir}" ]; then log_error "Error: copy_to_iso: destination directory does not exist '${final_dest_dir}'" exit 1 fi cp -f "${src}" "${final_dest}" if [ $? -ne 0 ]; then log_error "Error: Failed to copy '${src}' to '${final_dest}'" exit 1 fi if [ ! -z "${final_md5}" ]; then pushd ${final_dest_dir} > /dev/null md5sum "$(basename "${final_dest}")" >> "${final_md5}" popd > /dev/null fi } function generate_boot_cfg { local isodir=$1 if [ -z "${EFI_MOUNT}" ]; then mount_efiboot_img ${isodir} fi local PARAM_LIST= log "Generating prestage.iso from params: ${PARAMS[*]}" # Set/update boot parameters if [ ${#PARAMS[@]} -gt 0 ]; then for p in "${PARAMS[@]}"; do param=${p%%=*} value=${p#*=} # Pull the boot device out of PARAMS and convert to instdev if [ "$param" = "boot_device" ]; then log "Setting instdev=$value from boot_device param" instdev=$value fi PARAM_LIST="${PARAM_LIST} ${param}=${value}" done fi log "Parameters: ${PARAM_LIST}" COMMON_ARGS="initrd=/initrd instdate=@$(date +%s) instw=60 instiso=instboot" COMMON_ARGS="${COMMON_ARGS} biosplusefi=1 instnet=0" COMMON_ARGS="${COMMON_ARGS} ks=file:///kickstart/kickstart.cfg" COMMON_ARGS="${COMMON_ARGS} rdinit=/install instname=debian instbr=starlingx instab=0" COMMON_ARGS="${COMMON_ARGS} insturl=file://NOT_SET prestage ip=${BOOT_IP_ARG}" COMMON_ARGS="${COMMON_ARGS} BLM=2506 FSZ=32 BSZ=512 RSZ=20480 VSZ=20480 instl=/ostree_repo instdev=${instdev}" COMMON_ARGS="${COMMON_ARGS} defaultkernel=vmlinuz*[!t]-amd64" if [[ -n "${FORCE_INSTALL}" ]]; then COMMON_ARGS="${COMMON_ARGS} force_install" fi # Uncomment for LAT debugging: #COMMON_ARGS="${COMMON_ARGS} instsh=2" COMMON_ARGS="${COMMON_ARGS} ${PARAM_LIST}" log "COMMON_ARGS: $COMMON_ARGS" for f in ${isodir}/isolinux/isolinux.cfg; do cat < "${f}" prompt 0 timeout ${TIMEOUT} allowoptions 1 serial 0 115200 ui vesamenu.c32 menu background #ff555555 menu title Debian Local Install : Select kernel options and boot kernel menu tabmsg Press [Tab] to edit, [Return] to select DEFAULT ${DEFAULT_SYSLINUX_ENTRY} LABEL 0 menu label Serial Console kernel /bzImage-std ipappend 2 append ${COMMON_ARGS} traits=controller console=ttyS0,115200 console=tty0 LABEL 1 menu label Graphical Console kernel /bzImage-std ipappend 2 append ${COMMON_ARGS} traits=controller console=tty0 EOF done for f in ${isodir}/EFI/BOOT/grub.cfg ${EFI_MOUNT}/EFI/BOOT/grub.cfg; do cat < "${f}" default=${DEFAULT_GRUB_ENTRY} timeout=${GRUB_TIMEOUT} search --no-floppy --set=root -l 'instboot' menuentry 'Serial Console' --id=serial { linux /bzImage-std ${COMMON_ARGS} traits=controller console=ttyS0,115200 serial initrd /initrd } menuentry 'Graphical Console' --id=graphical { linux /bzImage-std ${COMMON_ARGS} traits=controller console=tty0 initrd /initrd } EOF done unmount_efiboot_img } # Constants DIR_NAME=$(dirname "$0") if [[ ! -e "${DIR_NAME}"/stx-iso-utils.sh ]]; then log_fatal "${DIR_NAME}/stx-iso-utils.sh does not exist" else source "${DIR_NAME}"/stx-iso-utils.sh fi # Required variables declare INPUT_ISO= declare OUTPUT_ISO= declare -a IMAGES declare ORIG_PWD=$PWD declare KS_SETUP= declare KS_ADDON= declare -a PARAMS declare -a PATCHES declare -a KICKSTART_PATCHES declare DEFAULT_LABEL=0 declare DEFAULT_SYSLINUX_ENTRY=1 declare DEFAULT_GRUB_ENTRY="graphical" declare UPDATE_TIMEOUT="no" declare FORCE_INSTALL= declare PLATFORM_ROOT="opt/platform-backup" declare MD5_FILE="container-image.tar.gz.md5" ############################################################################### # Get the command line arguments. ############################################################################### SHORTOPTS=""; LONGOPTS="" SHORTOPTS+="i:"; LONGOPTS+="input:," SHORTOPTS+="o:"; LONGOPTS+="output:," SHORTOPTS+="s:"; LONGOPTS+="setup:," SHORTOPTS+="a:"; LONGOPTS+="addon:," SHORTOPTS+="p:"; LONGOPTS+="param:," SHORTOPTS+="P:"; LONGOPTS+="patch:," SHORTOPTS+="K:"; LONGOPTS+="kickstart-patch:," SHORTOPTS+="d:"; LONGOPTS+="default-boot:," SHORTOPTS+="t:"; LONGOPTS+="timeout:," SHORTOPTS+="I:"; LONGOPTS+="images:," SHORTOPTS+="f"; LONGOPTS+="force-install," SHORTOPTS+="h"; LONGOPTS+="help" OPTS=$(getopt -o "${SHORTOPTS}" --long "${LONGOPTS}" --name "$0" -- "$@") if [[ "$?" -ne 0 ]]; then usage log_fatal "Options to $0 not properly parsed" fi eval set -- "${OPTS}" if [[ $# == 1 ]]; then usage log_fatal "No arguments were provided" fi while :; do case $1 in -i | --input) INPUT_ISO="$2" shift 2 ;; -o | --output) OUTPUT_ISO=$2 shift 2 ;; -s | --setup) KS_SETUP=$2 shift 2 ;; -a | --addon) KS_ADDON=$2 shift 2 ;; -p | --param) PARAMS+=(${2//,/ }) shift 2 ;; -P | --patch) PATCHES+=(${2//,/ }) shift 2 ;; -K | --kickstart-patch) KICKSTART_PATCHES+=(${2//,/ }) shift 2 ;; -I | --images) IMAGES+=(${2//,/ }) shift 2 ;; -d | --default-boot) DEFAULT_LABEL=$2 case ${DEFAULT_LABEL} in 0) DEFAULT_SYSLINUX_ENTRY=0 DEFAULT_GRUB_ENTRY="serial" ;; 1) DEFAULT_SYSLINUX_ENTRY=1 DEFAULT_GRUB_ENTRY="graphical" ;; *) usage log_fatal "Invalid default boot menu option: ${DEFAULT_LABEL}" ;; esac shift 2 ;; -t | --timeout) let -i timeout_arg=$2 if [[ "${timeout_arg}" -gt 0 ]]; then let -i "TIMEOUT=${timeout_arg}*10" GRUB_TIMEOUT="${timeout_arg}" elif [[ "${timeout_arg}" -eq 0 ]]; then TIMEOUT=0 GRUB_TIMEOUT=0.001 elif [[ "${timeout_arg}" -lt 0 ]]; then TIMEOUT=0 GRUB_TIMEOUT=${FOREVER_GRUB_TIMEOUT} fi UPDATE_TIMEOUT="yes" shift 2 ;; -f | --force-install) FORCE_INSTALL="true" shift ;; --) break ;; *) shift break ;; esac done ############################################################################### # Generate prestage iso. # ############################################################################### check_requirements ## Check for mandatory parameters check_required_param "--input" "${INPUT_ISO}" check_required_param "--output" "${OUTPUT_ISO}" check_files_exist ${INPUT_ISO} ${PATCHES[@]} ${IMAGES[@]} ${KS_SETUP} ${KS_ADDON} check_files_size ${INPUT_ISO} ${PATCHES[@]} ${IMAGES[@]} ${KS_SETUP} ${KS_ADDON} if [[ -e "${OUTPUT_ISO}" ]]; then log_fatal "${OUTPUT_ISO} exists. Delete before you execute this script." fi ## Catch Control-C and handle. trap cleanup EXIT # Create a temporary build directory. BUILDDIR=$(mktemp -d -p $PWD updateiso_build_XXXXXX) if [ -z "${BUILDDIR}" -o ! -d ${BUILDDIR} ]; then log_fatal "Failed to create builddir. Aborting..." fi echo ${BUILDDIR} mount_iso "${INPUT_ISO}" # # Determine release version from ISO # if [ ! -f ${MNTDIR}/upgrades/version ]; then log_error "Version info not found on ${INPUT_ISO}" exit 1 fi ISO_VERSION=$(source ${MNTDIR}/upgrades/version && echo ${VERSION}) if [ -z "${ISO_VERSION}" ]; then log_error "Failed to determine version of installation ISO" exit 1 fi # Copy the contents of the input iso to the build directory. # This ensures that the ostree, kernel and the initramfs are all copied over # to the prestage iso. rsync -a --exclude "pxeboot" "${MNTDIR}/" "${BUILDDIR}/" rc=$? if [[ "${rc}" -ne 0 ]]; then unmount_iso log_fatal "Unable to rsync content from the ISO: Error rc=${rc}" fi unmount_iso # # Copy ISO, patches, and docker image bundles to /opt on the iso. # These will be processed by the prestaged installer kickstart. # RPM has no role in the installation of these files. # PLATFORM_PATH="${PLATFORM_ROOT}/${ISO_VERSION}" mkdir_on_iso "${PLATFORM_PATH}" for PATCH in ${PATCHES[@]}; do copy_to_iso "${PATCH}" "${PLATFORM_PATH}/" done for IMAGE in ${IMAGES[@]}; do copy_to_iso "${IMAGE}" "${PLATFORM_PATH}/" "${PLATFORM_PATH}/${MD5_FILE}" done # generate the grub and isolinux cmd line parameters generate_boot_cfg "${BUILDDIR}" # copy the addon and setup files to the BUILDDIR if [[ -e "${KS_SETUP}" ]]; then cp "${KS_SETUP}" "${BUILDDIR}" fi if [[ -e "${KS_ADDON}" ]]; then cp "${KS_ADDON}" "${BUILDDIR}" fi # we are ready to create the prestage iso. mkisofs -o "${OUTPUT_ISO}" \ -A 'instboot' -V 'instboot' \ -quiet -U -J -joliet-long -r -iso-level 2 \ -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot \ -boot-load-size 4 -boot-info-table \ -eltorito-alt-boot \ -e efi.img \ -no-emul-boot \ "${BUILDDIR}" isohybrid --uefi "${OUTPUT_ISO}"