szaydel
1/27/2016 - 9:41 PM

BrickstorOS Installer

BrickstorOS Installer Utility for hosted installs both virtual and physical.

#!/bin/sh
# Copyright 2009-2018 RackTop Systems Inc. and/or its affiliates.
# http://www.racktopsystems.com
#
# The methods and techniques utilized herein are considered TRADE SECRETS
# and/or CONFIDENTIAL unless otherwise noted. REPRODUCTION or DISTRIBUTION
# is FORBIDDEN, in whole and/or in part, except by express written permission
# of RackTop Systems.
#
# @@DESCRIPTION@@  Script used to install BrickstorOS to disk device
# @@NAME@@         osinstall
# @@STABILITY@@    unstable
# @@VERSION@@      1.1.10

# Run this script as follows. To set device via an environment variable, prefix
# this command with ENV_BSROS_BOOT_DEV=c2t1d0, assuming device is c2t1d0.
# Otherwise if the device path is not same as default c2t1d0, add a `-b` (boot)
# argument to the command.
#
# Example with environment variable:
# ENV_BSROS_BOOT_DEV=c10t0d0 \
#   /path/to/osinstall /rpool/images/tmp/<some guid>.rap
#
# or, if environment variable has already been set previously:
# /path/to/osinstall /rpool/images/tmp/<some guid>.rap
#
# Example with -b:
# /path/to/osinstall -b c10t0d0 /rpool/images/tmp/<some guid>.rap
#
# Keep DEBUG at 0, otherwise set to 1 or above to get much more output.
DEBUG=0

[[ "${DEBUG}" -gt 0 ]] && set -o xtrace
PROGNAME=`basename $0`
PREFIX=/dev/rdsk
OSVER=`uname -s`
# Override device below with $ENV_BSROS_BOOT_DEV variable
DEFAULT_DEV=c2t1d0
ROOT=bp/ROOT

# These variables may be overriden by environment variables, when set on same
# line as running the command, or set as part of the environment, or
# via config file, which is nothing more than a one-pair per line of:
# <VAR>=<VALUE> mappings. Comments are perfectly fine as well.
# A config file may look like this:
# *cfg*
# BD=c1t10d0
# TMP_WORK_AREA=/var/tmp
# SRCDIR=/bp/images
#
# First line of the config should have __cfg__ commented out.
# This is a string that we use in effect as a magic number or shabang
# to confirm that this is a valid config, which otherwise could be anything.
# The goal is to avoid blindly sourcing just anything.
#
SRCDIR="${ENV_SRCDIR:-/rpool/images}"
# When we are booting a live CD/DVD, we won't have a mounted pool at all.
# In those instances we should assume that we will create a volatile dir.
# in /tmp. It will of course be gone after reboot.
NOPERSIST_DIR="/tmp/nopersist"
OSINSTALL_CONF="${ENV_OSINSTALL_CONF:-/root/osi.conf}"
BD="${ENV_BSROS_BOOT_DEV:-$DEFAULT_DEV}"
MNT_DIR="${ENV_MNT_DIR:-/mnt}"
OS_IMAGE=
SRC_RAP_PKG=
TMP_WORK_AREA="${ENV_TMP_WORK_AREA:-/tmp/.osinstall}"
ALT_ROOT="${ENV_ALT_ROOT:-/t}"
FORMAT_PARAMS_FILE="${TMP_WORK_AREA}/format.params"

# Grub files need to come from somewhere. At the moment we cheat and we place
# correct files on the host system doing installation. We could just as well
# use host-symstem supplied versions of these files.
SRC_GRUB_FILES="${ENV_SRC_GRUB_FILES:-${TMP_WORK_AREA}/boot/grub}"
GRUB_MENU_FILE="${ALT_ROOT}/bp/boot/grub/menu.lst"

#
# Global variables
#
remove_rap_pkg=

#
# process_config_file: If there is a config file present, use it to alter
# values that are otherwise already set to some sane default.
#
function process_config_file {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace

  # A lousy check, but it is good enough. We don't want to blindly
  # source just anything. If the first line of the config does not have a
  # commented out __cfg__ entry, this file won't be sourced.
  if [[ -f "${OSINSTALL_CONF}" && \
    `awk '{if(NR==1) {if (match($0, "\\\B.__(cfg)__")) { print 1 } } }' \
    < "${OSINSTALL_CONF}"` -eq 1 ]]; then
    . "${OSINSTALL_CONF}"
  else
    return
  fi

  # Because these will not dynamically change if $TMP_WORK_AREA or $ALT_ROOT
  # are modified here, we want to make sure that they are reset here.
  # Environment variable, if exists, still has priority, with $TMP_WORK_AREA
  # as fallback.
  SRC_GRUB_FILES="${ENV_SRC_GRUB_FILES:-${TMP_WORK_AREA}/boot/grub}"
  GRUB_MENU_FILE="${ALT_ROOT}/bp/boot/grub/menu.lst"
}

function usage {
  printf "%s\n" "${PROGNAME} BrickstorOS Portable Installer Utility"
  printf "%s\n" \
    "" \
    "Description:" \
    "Utility for installation of BrickstorOS to physical media." \
    "" \
    "The tool is meant to be run on a host system with a temporarily attached" \
    "disk drive or more likey a SSD or SATADOM." \
    "" \
    "This device is prepared by being made bootable first." \
    "Pool named {bp} is created and configured as required by BrickstorOS." \
    "" \
    "Contents of RAP package are extracted and sent to temporarily attached" \
    "device using zfs send/recv method, which makes ZFS a pre-requisite " \
    "on the host system." \
    "" \
    "Usage: ${PROGNAME} [-b <c#t#d#>] <${SRCDIR}/guid/guid.rap>" >&2
  printf "%s\n" \
    "Command requires only one positional parameter, path to RAP package." \
    "If target device is not given as argument: -b <dev>," \
    "and no config file <${OSINSTALL_CONF}> is found," \
    "we use ${DEFAULT_DEV} automatically." \
    "" >&2
  printf "%s\n" "Optional Arguments:" >&2
  printf "  %-24s        %s\n" \
    "-b | --bootdev <dev>" \
    "Name of device as reported by iostat, i.e.: ${DEFAULT_DEV}, no slices" \
    "-r | --remove-rap" \
    "If set, remove the RAP package after installation" \
    "" >&2
  printf "%s\n" "Environment Variables:" >&2
  printf "\t%s\n" "ENV_BSROS_BOOT_DEV - equivalent to -b|--bootdev" >&2
  exit 1
}

function write_err {
  printf "[ERROR] %s\n" "$@" >&2
  exit 1
}

function write_info {
  printf "[INFO] %s\n" "$@"
}

function write_warn {
  printf "[WARN] %s\n" "$@" >&2
}

function bp_is_mounted {
  zpool list -Honame bp >/dev/null 2>&1 ; return $?
}

function device_not_found {
  write_err "Unable to validate this disk: ${BD} please confirm disk exists."
}
function preinstall_tasks {
  if ! `mkdir -p "${TMP_WORK_AREA}"` ; then
    write_err "Failed to create temporary work area ${TMP_WORK_AREA}!"
    return 1
  fi
}
#
# postinstall_tasks: At the end of the script before any housekeeping is taking
# place we may want to add tasks that require newly built pool to still be
# imported and online. This function is meant for these tasks.
#
function postinstall_tasks {
  return
}
#
# cleanup: At the end of the script do necessary housekeeping
#
function cleanup {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace

  write_info "Exporting pool and cleaning-up host system."

  if ! zpool export bp; then
    zpool export -f bp
    write_warn "Normal export did not succeed, used force to export bp."
  fi

  # Drop temporary work area
  rm -rf "${TMP_WORK_AREA}"

  # Drop RAP package if asked, otherwise it is left alone.
  if [ "${remove_rap_pkg}" -eq 1 ]; then
    rm "${SRC_RAP_PKG}"
  fi
}
#
# zero_out_pt: Remove any partitions found on the device before building new
# layout on it. This assures consistent state of a device being built.
#
function zero_out_pt {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace

  write_info "Zeroing out existing partitions on ${BD}."
  device="${PREFIX}/${BD}p0"
  for partition in \
  `fdisk -W- "${device}" | \
    awk '!/\*/ { if($1 > 0 && $10 > 0) {
    printf("%d:%d:%d:%d:%d:%d:%d:%d:%d:%d\n", $1,$2,$3,$4,$5,$6,$7,$8,$9,$10)}}'` ; do
    fdisk -D "${partition}" "${device}"
    if [[ $? -ne 0 ]]; then
      write_err "Failed to delete already existing partition. Please check!"
    else
      write_info "Deleted partition ${partition} on device ${device}."
    fi
  done
}

#
# validate_prereqs: Check that basic system requirements are met,
# such as existence of required tools, which may or may not be there.
#
function validate_prereqs {
  reqs_unmet=0
  typeset -a missing
  [ ! -x `which fdisk` ] && \
    { reqs_unmet=$((++reqs_unmet)) ; missing+=("fdisk") ; }
  [ ! -x `which installgrub` ] && \
    { reqs_unmet=$((++reqs_unmet)) ; missing+=("installgrub") ; }
  [ ! -x `which cp` ] && \
    { reqs_unmet=$((++reqs_unmet)) ; missing+=("cp") ; }
  [ ! -x `which zfs` ] && \
    { reqs_unmet=$((++reqs_unmet)) ; missing+=("zfs") ; }
  [ ! -x `which zpool` ] && \
    { reqs_unmet=$((++reqs_unmet)) ; missing+=("zpool") ; }
  [ ! -x `which prtvtoc` ] && \
    { reqs_unmet=$((++reqs_unmet)) ; missing+=("prtvtoc") ; }
  if [ "${OSVER}" != "BrickstorOS" ]; then
    [ ! -x `which unzip` ] && \
      { reqs_unmet=$((++reqs_unmet)) ; missing+=("unzip") ; }
  fi

  if [ "${reqs_unmet}" -gt 0 ] ; then
    printf "%s\n" "Cannot continue. Identified ${reqs_unmet} missing tools."
    printf "required: %s\n" "${missing[@]}"
    return 1
  fi
  return 0
}

#
# prepare_new_disk: Low-level interaction with the device to which we are
# installing BrickstorOS. We create a partition table, slices and such.
#
function prepare_new_disk {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace

  write_info "Preparing Disk for Installation of BrickstorOS."
  if [ "${ENV_SKIP_DISK_PREP}" -eq 1 ] ; then return ; fi
  device="${PREFIX}/${BD}p0"
  zeroed=0
  # Does this disk need to be cleared?
  result=`fdisk -W- "${device}" | \
    awk '{
      if($1 != "*" && $1 != "")
      {
        if($1 != 0) {print 1}
      }
    }'`

  if [[ "${result}" -eq "1" ]]; then
    zero_out_pt && zeroed=1
  else
    zeroed=1
  fi

  if [[ $? -ne 0 ]]; then
    write_err "Failed to zero out device ${BD}, please do so manually."
  else
    write_info "Successfully zeroed out device ${BD}."
  fi

  # Apply partition table over the now hopefully clean disk.
  [[ "${zeroed}" -eq 1 ]] && fdisk -B "${device}"
  end=$(( `prtvtoc "${PREFIX}/${BD}s0" \
        | awk '/accessible cylinders/ {print $2}'` + -1 ))
  cat > "${FORMAT_PARAMS_FILE}" <<EOF
  p
  0
  root
  wm
  1
  ${end}c
  label
EOF

  # Make sure we had a chance to sync things and update device links.
  sync

  # Setup slice information and save it. Format is messing with us, so we
  # return 0 here, ignoring format's return code. This should be addressed.
  if [ "${DEBUG}" -gt 0 ] ; then
    format -d "${BD}" -f "${FORMAT_PARAMS_FILE}"
  else
    format -d "${BD}" -f "${FORMAT_PARAMS_FILE}" > /dev/null 2>&1
  fi
  return 0
}

function create_pool {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace

  write_info "Preparing boot pool for Installation of BrickstorOS."
  # Create pool and setup necessary bits for next step, installation.
  zpool create -f -oaltroot=/t bp "${BD}s0" \
    && zfs create -p "${ROOT}" \
    && zfs set mountpoint=legacy "${ROOT}"

  if [[ $? -ne 0 ]]; then
    write_err "All or part of pool setup failed. Please inspect manually!"
  fi
  return $?
}
#
# prepare_persistent_datasets: Creates necessary datasets and stuffs
# them with initial seed data from the boot archive.
#
function prepare_persistent_datasets {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace

  mkdir -p "${TMP_WORK_AREA}" "${MNT_DIR}/bp" "${MNT_DIR}/boot_archive" || \
    {
      write_err "Unable to create work area or mountpoints."
    }

  # Mount filesystem extracted from RAP package, then mount boot archive
  # and extract data out of `/etc` and `/var`, populating newly created
  # `bp/etc` and `bp/var` in the process.
  mount -F zfs -o ro "${BOOTFS}" "${MNT_DIR}/bp"
  mount -F ufs -r \
    "${MNT_DIR}/bp/platform/i86pc/amd64/boot_archive" \
    "${MNT_DIR}/boot_archive"
  for ds in etc var ; do
    zfs create \
      -o compression=lz4 \
      -o logbias=throughput \
      -o mountpoint="/${ds}" "bp/${ds}"
    if [ "${DEBUG}" -gt 0 ]; then
      /usr/bin/cp -a \
        ${MNT_DIR}/boot_archive/${ds}/* "${ALT_ROOT}/${ds}/"
    else
      /usr/bin/cp -a \
        ${MNT_DIR}/boot_archive/${ds}/* "${ALT_ROOT}/${ds}/" > /dev/null
    fi
  done

  # Before we umount stuff, we copy grub files from the boot archive. We
  # will use these files later when we install grub making device bootable.
  mkdir -p "${TMP_WORK_AREA}/boot"

  if [ "${DEBUG}" -gt 0 ]; then
    /usr/bin/cp -r ${MNT_DIR}/bp/boot ${TMP_WORK_AREA}
  else
    /usr/bin/cp -r ${MNT_DIR}/bp/boot ${TMP_WORK_AREA} > /dev/null 2>&1
  fi

  # Drop mounted filesystems, we are done with them.
  umount "${MNT_DIR}/boot_archive" ; umount "${MNT_DIR}/bp"
}

function install_image {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace

  img="${SRCDIR}/${OS_IMAGE}/${OS_IMAGE}"
  write_info "Writing contents of package ${OS_IMAGE} to ${BOOTFS} on ${BD}"

  # If file exists, write contents of the image over the boot device,
  # otherwise fail with a message about missing image.
  if [[ ! -f "${img}" ]]; then
    write_err \
      "Unable to locate ${img}! Please, confirm this file was staged."
  else
    dd if="${img}" bs=1M 2>/dev/null | zfs recv "${BOOTFS}"
    if [[ $? -eq 0 ]]; then
      # Set bootfs prop on bp, otherwise pool will not boot correctly.
      zpool set bootfs="${BOOTFS}" bp
      write_info "Successfully wrote OS image to ${BOOTFS}."
    else
      write_err "Failed to write OS image to device ${BD}!"
    fi
  fi
}

#
# copy_boot_files: Transfer necessary files for device to bootstrap
# correctly from the temporary work area to the device.
#
function copy_boot_files {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace

  bproot="${ALT_ROOT}/bp"
  grubdir="${ALT_ROOT}/bp/boot/grub"
  # We use altroot to mount pool under /t, which makes it easier to manage
  # `/etc` and `/var` creation.
  if [ ${DEBUG} -gt 0 ]; then
    /usr/bin/cp -r ${TMP_WORK_AREA}/boot "${bproot}/"
  else
    /usr/bin/cp -r ${TMP_WORK_AREA}/boot "${bproot}/" > /dev/null 2>&1
  fi

  umask 0222 # file under bootsign is ready-only, which says little to root. ;)
  mkdir "${grubdir}/bootsign/" && touch "${grubdir}/bootsign/pool_bp"
}
#
# check_grub_files_exist: Checks for existence of grub stage files.
#
function check_grub_files_exist {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace
  [[ -f "${SRC_GRUB_FILES}/stage1" &&
    -f "${SRC_GRUB_FILES}/stage2" ]] || return 1
}

#
# install_grub: Writes grub bootlader code into the MBR area of the device.
#
function install_grub {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace

  device="${PREFIX}/${BD}s0"
  check_grub_files_exist || return 1
  write_info "Installing GRUB bootloader to ${BD}."
  # Need to make sure disk is actually bootable.
  if [ "${DEBUG}" -gt 0 ]; then
    installgrub "${SRC_GRUB_FILES}/stage1" "${SRC_GRUB_FILES}/stage2" \
      "${device}"
  else
    installgrub "${SRC_GRUB_FILES}/stage1" "${SRC_GRUB_FILES}/stage2" \
      "${device}" >/dev/null 2>&1
  fi

  return $?
}
#
# build_menu: Build grub boot menu, initially containing two entries for
# the installed OS, one for normal boot and another for SAFE MODE boot,
# which effectively means the boot pool `bp` is not imported.
#
function build_menu {
  grub_title=`zfs get -H -o value brickstor:title "${BOOTFS}"`
  grub_safemode_title=`sed -e 's/\[/\[SAFE MODE\//' <<< "${grub_title}"`

  cat > "${GRUB_MENU_FILE}" <<EOF
#
# Automatically generated.  Do not edit!
#

timeout 5
splashimage /boot/grub/splash.xpm.gz
default 0

title ${grub_title}
findroot (pool_bp,0,a)
bootfs ${BOOTFS}
kernel\$ /platform/i86pc/kernel/amd64/unix -B \$ZFS-BOOTFS
module /platform/i86pc/amd64/boot_archive type=rootfs
module /platform/i86pc/amd64/boot_archive.hash type=hash

title ${grub_safemode_title}
findroot (pool_bp,0,a)
bootfs ${BOOTFS}
kernel /platform/i86pc/kernel/amd64/unix
module /platform/i86pc/amd64/boot_archive type=rootfs
module /platform/i86pc/amd64/boot_archive.hash type=hash
EOF
}
#
# unpack_rap_package: Extract the stream from the zip file using proper tool,
# depending on the version of OS the script is running on. We should be able to
# run this on OmniOS and BrickstorOS. At the moment we are not targeting any
# other operating systems.
#
function unpack_rap_package {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace

  rap="${SRCDIR}/${OS_IMAGE}.rap" # Absolute path to the rap package.
  case "${OSVER}" in
    "BrickstorOS")
      (
        cd "${SRCDIR}/${OS_IMAGE}" ;
        if [[ ! -f "./${OS_IMAGE}.rap" || ! -f "./HEADER" ]]; then
            write_info "Unpacking RAP with GUID ${OS_IMAGE}"
            /usr/racktop/bin/unpackrap "./${OS_IMAGE}.rap" > /dev/null
        fi
      )
      return $?
    ;;

    "SunOS")
    (
      cd "${SRCDIR}/${OS_IMAGE}" ;
      if [[ ! -f "./${OS_IMAGE}.rap" || ! -f "./HEADER" ]]; then
          write_info "Unpacking RAP with GUID ${OS_IMAGE}"
          unzip -nq "./${OS_IMAGE}.rap"
      fi
    )
    return $?
    ;;

    *) return 1
      write_err "This is not a known and supported operating system!"
    ;;
  esac
}

#
# stage_rap_package: We want to keep all RAP packages in one area and for each
# package we want to create a separate directory, which promotes better
# organization.
#
function stage_rap_package {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace
  echo "SRCDIR=${SRCDIR}"
  needcopy=1
  # Drop leading and trailing (if any) slashes from the path to location of
  # images. This path, if it were /rpool/images/ is transformed into a
  # digestable ZFS-friendly form: rpool/images and used as argument to ZFS
  # commands.
  rapds=`
    awk '{ a[0]="^/" ; a[1]="/$"
          for (i in a) { gsub(a[i], "") }
        } END {print $1}' <<< "${SRCDIR}"`
  if ! `zpool list ${rapds%%/*}` >/dev/null 2>&1 ; then
    write_warn "Pool ${rapds%%/*} does not exist, falling back to ${NOPERSIST_DIR}."
    SRCDIR="${NOPERSIST_DIR}"
    # There may be cases where we are running this multiple times and the
    # directory is already there. In these cases we don't want to fail on
    # creating an already-existing directory.
      mkdir -p ${SRCDIR} || \
        write_err "Failed to stage package, check capacity of ${NOPERSIST_DIR}."
  else
    if ! `zfs list -Honame "${rapds}" >/dev/null 2>&1`; then
        zfs create "${rapds}"
    fi
  fi

  # If the image was already placed into correct location on the host system
  # we don't need to do anything more, otherwise we should move the file
  # to location expected by this tool.
  if [[ `dirname "${SRC_RAP_PKG}"` == "${SRCDIR}/${OS_IMAGE}" ]]; then
    if [[ -f "${SRC_RAP_PKG}" ]]; then
      needcopy=0
    fi
  elif [[ -f "${SRCDIR}/${OS_IMAGE}/${OS_IMAGE}.rap" ]]; then
    needcopy=0
  fi
  # Copy RAP package to proper destination first, remove it from temporary
  # location after copy is done.
  if [[ "${needcopy}" -eq 1 ]]; then
    write_info \
      "Moving file to work area before extraction ${SRCDIR}/${OS_IMAGE}"

    # Prepare work area and copy file there, don't unlink original file unless
    # explicitly told to do so.
    mkdir -p "${SRCDIR}/${OS_IMAGE}"
    cp -rp "${SRC_RAP_PKG}" "${SRCDIR}/${OS_IMAGE}/${OS_IMAGE}.rap"
  else
    write_info \
      "Skip copy of ${OS_IMAGE} RAP to workarea, package already there."
  fi
  # Unpack the rap package, extracting ZFS stream of BrickstorOS.
  unpack_rap_package || write_err "Failed to extract data from RAP package."
}
#
# parse_cmdline_args: Process arguments passed by caller and setup variables
# used by rest of the script.
#
function parse_cmdline_args {
  [[ "${DEBUG}" -gt 0 ]] && set -o xtrace


  [ ${#@} -eq 0 ] && { usage ; }
  # Reset all variables that might be set
  fn=
  bd=
  verbose=0

  while :; do
    case $1 in
      -h|-\?|--help)
          usage
          ;;

      -b|--bootdev) # Takes an option argument, argument is required.
          if [ -n "$2" ]; then
            bd=$2
            shift
          else
            write_err "-b|--bootdev requires an option argument."
            exit 1
          fi
          ;;
      --bootdev=?*)
          bd=${1#*=} # Delete everything up to "=" and assign the remainder.
          ;;
      --bootdev=) # Handle the case of an empty --bootdev=
          write_err "-b|--bootdev requires an option argument."
          exit 1
          ;;
      -r|--remove-rap)
          remove_rap_pkg=1
          ;;
      -v|--verbose)
          verbose=$((verbose + 1)) # Each -v argument adds 1 to verbosity.
          ;;
      */* | [a-zA-Z0-9]*) # This should be path to RAP package.
          # If path is a valid file, set ${fn} to this value.
          if [[ -f "$1" ]]; then fn=$1; fi
          ;;
      --) # End of all options.
          shift
          break
          ;;
      -?*)
          printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2
          ;;
      *) # Default case: If no more options then break out of the loop.
          break
    esac
    shift
  done

  # If we have values, which we should, after processing command line args,
  # we update global variables used by other parts of the tool.
  # After we are done, if "${BD}" or "${OS_IMAGE}" are still null strings,
  # we bail, because there is nothing more we can do.
  # We may have set "${BD}" to a value from exported environment variable, and
  # if so, nothing further is necessary.
  if [[ -n "${bd}" ]] ; then
    BD="${bd}"
  fi

  [[ -n "${fn}" ]] && \
    {
      SRC_RAP_PKG="${fn}" ;
      tmpv=`basename "${fn}"` ;
      OS_IMAGE="${tmpv%.rap}" ;
      BOOTFS="${ROOT}/${OS_IMAGE}" ;
    }
}

#
# Process config file, if exists...
#
process_config_file

#
# Process command line arguments
#
# We need to make sure we have minimum required information from user
# in order to continue. Determine that now and break out if we don't.
#
parse_cmdline_args $@

#
# Validate basic system compatibility. If we have missing tools, we should
# catch that here and report.
#
validate_prereqs || exit 1

if bp_is_mounted; then
  write_err "Cannot continue. Pool bp is already mounted!"
fi

# If these don't have values, we are done and should print out usage.
# We should not ever have a case of null "${BD}".
if [[ -z "${BD}" || -z "${OS_IMAGE}" ]]; then
  [[ -z "${OS_IMAGE}" ]] && write_err \
    "Path to RAP package may not be valid."
fi

preinstall_tasks || exit 1

#
# Move RAP into workarea if necessary
#
if ! stage_rap_package; then
  write_err "Failed to prepare RAP package for installation to device ${BD}!"
fi

#
# Validate and prepare disk
#
# Check device exists. We should have at least the p0 partition if
# device is functional.
#
if [[ ! -h "${PREFIX}/${BD}p0" ]]; then
  device_not_found
fi

#
# Make disk ready for bp
#
if ! prepare_new_disk; then
  write_err "Failed to prepare disk for installation of BrickstorOS!"
else
  write_info "Device is ready for BrickstorOS installation."
fi

#
# Install the OS to disk
#
if ! create_pool; then
  write_err "Failed to complete setup of new pool, cannot continue!"
fi

if ! install_image; then
  write_err "Failed to install image ${OS_IMAGE} to device ${BD}!"
fi

prepare_persistent_datasets

# Copy what is in /bp/boot, could be a tarball or whatever.
if ! copy_boot_files; then
  write_err "Failed to copy boot files to device ${BD}!"
fi

#
# Make disk bootable
#
if ! install_grub; then
  write_err "Failed to install grub. Device ${BD} will not be bootable!"
fi

#
# Build initial grub menu file based on just installed OS.
#
build_menu

#
# Cleanup before exiting
#
if ! cleanup; then
  write_err \
    "Post-install cleanup did not complete. Export bp manually if necessary."
fi