#!/bin/bash PROGNAME=$(basename "$0") FORCE=0 AUTO_MODE= IMG_SIZE= BOOTIMAGE_ISO= GRAPHICAL_SUFFIX= IMG_FILE= AUTO_ISO= DHCPV6C=yes OAM_DEV=ens3 IPV4_GW_ADDR= IPV6_GW_ADDR= declare -A PASSWORDS : KVM= KVM_OPTS=() TEMPFILES_DIR= SUDO=0 GRAPHICAL=0 TTY_SETTINGS= # Print out the help message usage() { echo "\ Usage: $0 OPTIONS... Create a QCOW2/QEMU image with StarlingX pre-installed -f,--force overwrite output file if it exists -m,--mode={controller|aio|aio_lowlatency} create a controller or an all-in-one/low latency system (default: aio) --sudo Use sudo to mount the ISO, rather than udisks -s,--size=nnnG image file size, must end with "G" (default: 500G) -g,--graphical create a graphical installation, rather than console -e,--oam-dev=OAM_DEV OAM network device (default: ens3) -4,--ipv4 don't configure IPv6 in the generated image -w,--ipv4-default-gateway=GW_IPV4_ADDR Add a default IPv4 route via this gateway address -W,--ipv6-default-gateway=GW_IPV6_ADDR Add a default IPv6 route via this gateway address -p,--password=USER:PASSWORD Unlock USER account and set its password in the generated image. USER must exist -- e.g., root, sysadmin. This option may be repeated. WARNING: this option is not recommended because the password will be visible to anyone listing the processes. Use \`--passwords-from' instead. -P,--passwords-from=PASSWORD_FILE Unlock and set passwords of each user account from PASSWORD_FILE, which must contain one or more lines of the form USER:PASSWORD USERs must exist -- e.g., root, sysadmin. -S,--passwords-from-stdin Same as \`--passwords-from=/dev/stdin' -i,--iso=BOOTIMAGE_ISO use this iso file as input, it must have been generated by build-iso with default options (default: \$MY_WORKSPACE/export/bootimage.iso) -o,--output=IMG_FILE output image file name Default: \$MY_WORKSPACE/export/stx_\${MODE}.qcow2) Default with --graphical: \$MY_WORKSPACE/export/stx_\${MODE}_graphical.qcow2) ENVIRONMENT MY_REPO source repo directory MY_WORKSPACE build workspace directory KVM path to kvm executable (default: auto) " } # Delete temporary files cleanup() { # QEMU changes terminal settings, restore them before exiting [[ -z $TTY_SETTINGS ]] || stty "$TTY_SETTINGS" <&1 # remove temporary files rm -rf "$TEMPFILES_DIR" rm -f "$IMG_FILE.tmp" } # Clean up before exiting due to a signal handle_sig() { trap - EXIT cleanup exit 1 } # Clean up before normal exit handle_exit() { local rv="$?" trap - EXIT cleanup exit $rv } # Print out an error message error() { echo "$PROGNAME: error: $*" >&2 } # Print out an error message and exit die() { error "$*" exit 1 } # Print out a command-line error message and exit cmdline_error() { if [ "$#" -gt 0 ] ; then error "$*" fi echo "Type \`$0 --help' for more info." >&2 exit 2 } # Encrypt a password for /etc/passwd encrypt_password() { export ARG="$1" python -c ' import crypt, os, binascii, sys salt = binascii.b2a_hex(os.urandom (8)).decode("ascii") encrypted = crypt.crypt (os.environ["ARG"], "$5$" + salt + "$") print (encrypted) ' "$1" local status="$?" unset ARG [[ $status -eq 0 ]] || exit 1 } # Save username/password to $PASSWORDS save_password() { local passwd_str="$1" local error_prefix="$2" if [[ ! $passwd_str =~ : ]] ; then error "${error_prefix}expecting USER:PASSWORD" return 1 fi local user="${passwd_str%%:*}" local passwd="${passwd_str#*:}" if [[ -z $user || -z $passwd ]] ; then error "${error_prefix}expecting USER:PASSWORD" return 1 fi if [[ $user =~ [^a-zA-Z0-9._-] ]] ; then error "${error_prefix}username must only contain characters [a-zA-Z0-9._-]" return 1 fi PASSWORDS[$user]="$passwd" return 0 } # Read passwords from file or STDIN read_passwords() { local filename="$1" local -i lineno=0 local numchar="#" # Open password file or STDIN as file descriptor 3 if [[ -z $filename || $filename == - ]] ; then filename=STDIN exec 3<&0 || exit 1 else exec 3<"$filename" || exit 1 fi while read line <&3 ; do let lineno++ # skip empty lines and comments # ${numchar} is "#" to avoid tripping up VI's syntax highlighting if [[ ! $line =~ ^[[:space:]]*(${numchar}.*)?*$ ]] ; then save_password "$line" "$filename:$lineno: " || exit 1 fi done # close file descriptor 3 exec 3<&- } # Check if an IPv4 address is valid is_ipv4_addr() { # make sure we have python python -c 'import socket' || exit 1 # parse the address via python python -c 'import socket,sys;socket.inet_aton(sys.argv[1])' "$1" >/dev/null 2>&1 } # Check if an IPv6 address is valid is_ipv6_addr() { # make sure we have python python -c 'import socket' || exit 1 # parse the address via python python -c 'import socket,sys;socket.inet_pton(socket.AF_INET6,sys.argv[1])' "$1" >/dev/null 2>&1 } # find QEMU/KVM find_kvm() { local kvm if [[ -n "$KVM" ]] ; then kvm=$(which "$KVM") [[ -n $kvm ]] || exit 1 else for kvm_basename in qemu-kvm kvm ; do kvm=$(export PATH=$PATH:/usr/bin:/usr/libexec ; which $kvm_basename 2>/dev/null || :) [[ -n $kvm ]] && break || : done [[ -n $kvm ]] || die "unable to find kvm executable" fi KVM="$kvm" if [[ -c /dev/kvm ]] ; then KVM_OPTS+=("-enable-kvm") fi } # Process command line init() { local temp temp=$(getopt -o hf4w:W:e:p:P:Sm:gs:i:o: --long help,force,ipv4,ipv4-default-gateway:,ipv6-default-gateway:,oam-dev:,password:,passwords-from:,passwords-from-stdin,mode:,graphical,sudo,size:,iso:,output: -n "$PROGNAME" -- "$@") || cmdline_error eval set -- "$temp" while true ; do case "$1" in -h|--help) usage exit 0 ;; -f|--force) FORCE=1 shift ;; -4|--ipv4) DHCPV6C=no shift ;; -w|--ipv4-default-gateway) is_ipv4_addr "$2" || cmdline_error "invalid IP address \`$2'" IPV4_GW_ADDR="$2" shift 2 ;; -W|--ipv6-default-gateway) is_ipv6_addr "$2" || cmdline_error "invalid IP address \`$2'" IPV6_GW_ADDR="$2" shift 2 ;; -e|--oam-dev) OAM_DEV="$2" shift 2 ;; -P|--passwords-from) read_passwords "$2" shift 2 ;; -S|--passwords-from-stdin) read_passwords - shift ;; -p|--password) save_password "$2" "invalid $1: " || cmdline_error shift 2 ;; -m|--mode) [[ "$2" =~ ^(controller|aio|aio_lowlatency)$ ]] || cmdline_error "invalid --mode" AUTO_MODE="$2" shift 2 ;; -g|--graphical) GRAPHICAL=1 GRAPHICAL_SUFFIX=_graphical shift ;; --sudo) SUDO=1 shift ;; -s|--size) [[ $2 =~ ^[0-9]{1,5}G$ ]] || cmdline_error "invalid --size" IMG_SIZE="$2" shift 2 ;; -i|--iso) BOOTIMAGE_ISO="$2" shift 2 ;; -o|--output) IMG_FILE="$2" shift 2 ;; --) shift break ;; -?*) cmdline_error ;; *) break ;; esac done [[ $# -le 0 ]] || cmdline_error "too many arguments" # These are required [[ -n $MY_WORKSPACE ]] || die "MY_WORKSPACE is not set" [[ -n $MY_REPO ]] || die "MY_REPO is not set" # Defaults : ${AUTO_MODE:=aio} : ${IMG_SIZE:=500G} : ${BOOTIMAGE_ISO:=$MY_WORKSPACE/export/bootimage.iso} : ${IMG_FILE:=$MY_WORKSPACE/export/stx_${AUTO_MODE}${GRAPHICAL_SUFFIX}.qcow2} } # main init "$@" # make sure we clean up before exiting trap handle_sig INT TERM PIPE HUP trap handle_exit EXIT # make sure update-iso.sh exists UPDATE_ISO=$MY_REPO/stx/utilities/utilities/platform-util/scripts/update-iso.sh : <"$UPDATE_ISO" || exit 1 # make sure input ISO file exists : <"$BOOTIMAGE_ISO" || exit 1 # find QEMU/KVM find_kvm # find qemu-img which qemu-img >/dev/null || exit 1 # refuse to overwrite existing output file if [[ -e "$IMG_FILE" ]] && [[ $FORCE -ne 1 ]] ; then die "output file $IMG_FILE already exist, delete it first or use --force" fi # which menu item to use? menu_item= case "$AUTO_MODE" in controller) menu_item=0 ;; aio) menu_item=2 ;; aio_lowlatency) menu_item=4 ;; *) die "internal error" ;; esac # create a directory for temporary files TEMPFILES_DIR=$(mktemp -d -t build_img.XXXXXXXX) || exit 1 # create an updated iso with the menu item pre-selected auto_iso="$TEMPFILES_DIR/bootimage_${AUTO_MODE}${GRAPHICAL_SUFFIX}.iso" rm -f "$auto_iso" cmd=() if [[ $SUDO == 1 ]] ; then cmd+=(sudo) fi cmd+=("$UPDATE_ISO" -i "$BOOTIMAGE_ISO" -o "$auto_iso" -d "$menu_item" -t 3) # generate a kickstart add-on ks_addon="$TEMPFILES_DIR/ks_addon.sh" echo "#### start ks-addon.cfg" >"$ks_addon" # configure $OAM_DEV cat >>"$ks_addon" <<_END # configure $OAM_DEV uuid=\$(uuidgen) cat >/etc/sysconfig/network-scripts/ifcfg-$OAM_DEV <>"$ks_addon" <<_END # Add a default IPv4 route echo "default via $IPV4_GW_ADDR dev $OAM_DEV metric 1" >/etc/sysconfig/network-scripts/route-$OAM_DEV _END fi if [[ -n "$IPV6_GW_ADDR" ]] ; then cat >>"$ks_addon" <<_END # Add a default IPv6 route echo "default via $IPV6_GW_ADDR dev $OAM_DEV metric 1" >/etc/sysconfig/network-scripts/route6-$OAM_DEV _END fi # Set passwords for user in "${!PASSWORDS[@]}" ; do encrypted=$(encrypt_password "${PASSWORDS[$user]}") [[ $? -eq 0 ]] || exit 1 cat >>"$ks_addon" <<_END # set ${user}'s password usermod -e '' -p '$encrypted' '$user' || exit 1 chage --inactive -1 --maxdays -1 --lastday \$(date '+%Y-%m-%d') '$user' || exit 1 _END done # Comment-out global_filter in lvm.conf # The installer normally sets it to the installer hard drive's bus address, # and LVM doesn't come up when booted in different emulation environment. cat >>"$ks_addon" <<'_END' # Comment-out global_filter in lvm.conf sed -r -i 's!^(\s*)global_filter\s*=.*!\1# global_filter = [ "a|.*/|" ]!' /etc/lvm/lvm.conf _END # Change grub parameters to boot to graphical console. # The installer sets these to use the serial port when we install # in text mode. if [[ $GRAPHICAL -eq 1 ]] ; then cat >>"$ks_addon" <<'_END' # Boot in graphical mode sed -r -i \ -e '/^\s*GRUB_SERIAL_COMMAND=/ d' \ -e '/^\s*GRUB_TERMINAL(_OUTPUT)?=/ s/=.*/="console"/' \ -e '/^\s*GRUB_CMDLINE_LINUX=/ s/\bconsole=ttyS0,\S+/console=tty0/' \ /etc/default/grub if [ -d /sys/firmware/efi ] ; then grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg else grub2-mkconfig -o /boot/grub2/grub.cfg fi _END fi echo "#### end ks-addon.cfg" >>"$ks_addon" cmd+=(-a "$ks_addon") # execute update_iso.sh echo "${cmd[@]}" "${cmd[@]}" || exit 1 # create a blank image file rm -f "$IMG_FILE.tmp" cmd=(qemu-img create "$IMG_FILE.tmp" -f qcow2 "$IMG_SIZE") echo "${cmd[@]}" "${cmd[@]}" || exit 1 # run the installer in QEMU cmd=( "$KVM" "${KVM_OPTS[@]}" -m 8192 -drive file="$IMG_FILE.tmp",if=ide -cdrom "$auto_iso" -boot d -no-reboot -nographic -smp 4 ) # if STDOUT is a terminal, save current terminal settings # so that we can restore them later if [[ -t 1 ]] ; then TTY_SETTINGS=$(stty -g <&1) # otherwise, disable QEMU's terminal features else cmd+=(-serial file:/dev/stdout) fi # execute qemu echo "${cmd[@]}" "${cmd[@]}" 2>&1 | tee $TEMPFILES_DIR/kvm.log if [[ ${PIPESTATUS[0]} -ne 0 || ${PIPESTATUS[1]} -ne 0 ]] ; then die "qemu: installation failed" fi # QEMU exits with status=0 even when killed by a signal. Check its output # for a known message to detect this case if tail "$TEMPFILES_DIR/kvm.log" | grep -q -E "(qemu|kvm).*: terminating on signal" ; then die "qemu terminated by a signal" fi # rename tmp image file to the final name mv -f "$IMG_FILE.tmp" "$IMG_FILE" || exit 1 # done echo " Created $IMG_FILE To use this image, type: " if [[ $GRAPHICAL -eq 1 ]] ; then echo " $KVM ${KVM_OPTS[@]} -m 16384 -drive file=$IMG_FILE,if=ide -boot c -smp 4" echo echo "(requires a graphical console)" else echo " $KVM ${KVM_OPTS[@]} -m 16384 -drive file=$IMG_FILE,if=ide -boot c -nographic -smp 4" fi