#!/bin/bash
# Author: Steven Shiau <steven _at_ nchc org tw> 
# license: GPL
# Description: this program will create the NBI image for PXE and etherboot
#              client.

# Load DRBL setting and functions
if [ ! -f "/opt/drbl/sbin/drbl-conf-functions" ]; then
  echo "Unable to find /opt/drbl/sbin/drbl-conf-functions! Program terminated!" 
  exit 1
fi
. /opt/drbl/sbin/drbl-conf-functions

# Setting
NETDEV_MOD="/usr/lib/mkpxeinitrd-net/initrd-skel/etc/modules"
NETINITRD_CFG="/usr/lib/mkpxeinitrd-net/initrd-skel/etc/linuxrc.conf"
NETDEV_CFG="/usr/lib/mkpxeinitrd-net/initrd-skel/etc/netdev.conf"
# The retry max times for udhcp in one ethernet port
retry_max_default="3"
# The default pause time after NIC is up.
sleep_time_after_NIC_up_default="0"

# 
usage() {
  echo "Create PXE boot image for DRBL clients."
  echo "Usage: $0 [Options]"
  echo "Options:"
  echo "-a, --all: create PXE boot image for all supported NIC (deprecated! Now this is default option, and you do not have to specify that.)"
  echo "-k, --kernel KERNEL_VER: specify the KERNEL_VER which you want to create PXE boot image for all supported NIC, If you do not specify any KERNEL_VER, it will try to find the latest DRBL kernel"
  echo "-s, --smp: force to find SMP kernel when creating NIC images"
  echo "-i, --archi ARCH: assign the kernel CPU arch of DRBL clients, ARCH could be i386/i586/i686/x86_64"
  echo "-c, --check-server-name [Y/n]: let client to check DHCP server name is drbl or not when it got IP address from some DHCP server."
  echo "-r, --retry-max NO:  The retry max times for udhcp in one ethernet port"
  echo "-m, --modules NET_MOD: assign the network device modules to be loaded in DRBL client initrd. NET_MOD is like 'tg3 bcm4400 bcm5700'. (Note! If more than one arguments, you must put \" \" or ' ' before and after the arguments.)"
  echo "-n, --netdev NET_DEV: assign the priority of network device to request IP address from server in DRBL client, NET_DEV is like 'eth1' (just request from eth1) or 'eth1 eth0' (request from eth1, then eth0) (Note! If more than one arguments, you must put \" \" or ' ' before and after the arguments.)"
  echo "-nu, --no-usb-modules: Do NOT include USB keyboard related modules in the network initrd."
  echo "-p, --pause NO:  Pause NO secs after network card if up"
  echo "--no-modules: Force not to copy the kernel modules to clients"
  echo "-v, --verbose    Prints out verbose information"
  echo 
  echo "Ex:"
  echo "$0 -a -k 2.4.20-30.9drbl"
}

#
check_if_root

# initial setting
SMP_OPTION=""
NIC="all"
check_server_name=""
use_usb_keyboard_modules="yes"
#
while [ $# -gt 0 ]; do
  case "$1" in
    -v|--verbose)
		shift; VERBOSE="-v"
                ;;
    -s|--smp)
		shift; SMP_OPTION="-smp"
                ;;
    -a|--all)
                # This is deprecated option, we already force to set NIC=all.
		# Try to remove this option in the future.
		shift; NIC="all"
                ;;
    -i|--archi)
		shift; 
                # skip the -xx option, in case 
		[ -z "$(echo $1 |grep ^-.)" ] && CLIENT_ARCH="$1"
		shift;;
    --no-modules)
		shift; CP_MODULES="no"
                ;;
    -k|--kernel)
		shift; 
                # skip the -xx option, in case 
		[ -z "$(echo $1 |grep ^-.)" ] && selected_kernel="$1"
		shift;;
    -n|--netdev)
		shift; 
                # skip the -xx option, in case 
		[ -z "$(echo $1 |grep ^-.)" ] && net_dev="$1"
		shift;;
    -m|--modules)
		shift; 
                # skip the -xx option, in case 
		[ -z "$(echo $1 |grep ^-.)" ] && net_modules="$1"
		shift;;
    -c|--check-server-name)
		shift; 
                # skip the -xx option, in case 
		[ -z "$(echo $1 |grep ^-.)" ] && check_server_name="$1"
		shift;;
    -r|--retry-max)
		shift; 
                # skip the -xx option, in case 
		[ -z "$(echo $1 |grep ^-.)" ] && retry_max="$1"
		shift;;
    -nu|--no-usb-modules)
		use_usb_keyboard_modules="no"
		shift;;
    -p|--pause)
		shift; 
                # skip the -xx option, in case 
		[ -z "$(echo $1 |grep ^-.)" ] && sleep_time_after_NIC_up="$1"
		shift;;
    -*)		echo "${0}: ${1}: invalid option" >&2
		usage >& 2
		exit 2 ;;
    *)		break ;;
  esac
done

[ -z "$NIC" ] && usage && exit
# parse the parameter for etc/linuxrc.conf in mkpxeinitrd-net
case "$check_server_name" in
  n|N|[nN][oO])
    check_server_name_flag="no"
    ;;
  *)
    check_server_name_flag="yes"
    ;;
esac
case "$retry_max" in
  [0-9]*)
    retry_max_no="$retry_max"
    ;;
  *)
    retry_max_no="$retry_max_default"
    ;;
esac
case "$sleep_time_after_NIC_up" in
  [0-9]*)
    sleep_time_after_NIC_up_no="$sleep_time_after_NIC_up"
    ;;
  *)
    sleep_time_after_NIC_up_no="$sleep_time_after_NIC_up_default"
    ;;
esac
case "$use_usb_keyboard_modules" in
  yes)
    usb_kb_opt=""
    ;;
  no)
    usb_kb_opt="--no-usb-modules"
    ;;
esac

# create some directories if they do not exist
[ ! -d $drbl_common_root/lib/modules ] && mkdir -p $drbl_common_root/lib/modules
[ ! -d $drbl_common_root/tmp/boot ] && mkdir -p $drbl_common_root/tmp/boot
( 
  cd $drbl_common_root
  [ -L boot ] && rm -f boot
  ln -fs tmp/boot boot
)

# Change the setting in $NETINITRD_CFG
echo "Will client check DHCP server name is \"drbl\" or not: $check_server_name_flag"
echo "The maximum times to try to get IP address for a client: $retry_max_no"
echo "The pause time after network card is up: $sleep_time_after_NIC_up_no"
perl -p -i -e "s/^check_server_name=.*/check_server_name=\"$check_server_name_flag\"/g" $NETINITRD_CFG
perl -p -i -e "s/^iretry_max=.*/iretry_max=\"$retry_max_no\"/g" $NETINITRD_CFG
perl -p -i -e "s/^sleep_time_after_NIC_up=.*/sleep_time_after_NIC_up=\"$sleep_time_after_NIC_up_no\"/g" $NETINITRD_CFG

if [ -n "$net_modules" ]; then
  echo "The extra network device module assigned: $net_modules"
  # Clean all in $NETDEV_MOD, make it as initial one.
  perl -p -i -e "s/^[[:space:]]*[^#]+.*//g" $NETDEV_MOD
  for imod in $net_modules; do
     echo $imod >> $NETDEV_MOD
  done
fi

if [ -n "$net_dev" ]; then
  echo "The priority of network card for client to request IP address is: $net_dev"
  perl -p -i -e "s/^[[:space:]]*netdevices=.*/netdevices=\"$net_dev\"/g" $NETDEV_CFG
fi

# If we do not specifie kernel, try to find in 
# (1) /lib/modules (by rpm ...) - old way
# (2) /tftpboot/lib/modules (by finding directory)
if [ -z "$selected_kernel" ]; then
   echo "Searching the latest installed kernel for DRBL client... This might take several minutes..."
   kernel_mod_list=$(find $drbl_common_root/lib/modules/ -maxdepth 1 -mindepth 1 -name "[0-9].[0-9]*" -type d -print | sort -g | tail -n 1)
   if [ -n "$kernel_mod_list" ]; then
      # the kernel in drbl_common_root is found, 
      # use method (2) (/tftpboot/lib/modules)
      echo "Trying to find the kernel in $drbl_common_root "
      drbl_kernel="$(basename $kernel_mod_list)"
   else
      # the kernel in drbl_common_root is NOT found, 
      # use method (1) (/lib/modules)
      latest_drbl_kernel="$(rpm -qa | grep -E "kernel${SMP_OPTION}[#-]2\.[0-9]+\.[0-9]+" | grep -v "test" | $DRBL_SCRIPT_PATH/bin/pkg-ver-latest)"
      [ -z "$latest_drbl_kernel" ] && echo "Unable to find kernel for client!!! Program terminated!!!" && exit 1

      rpm -q --qf '%{filenames}\n' $latest_drbl_kernel &> /dev/null && boot_kernel="$(rpm -q --qf '%{filenames}\n' $latest_drbl_kernel)"
      drbl_kernel="$(echo $boot_kernel |cut -d"-" -f2-)"
   fi
else
   # specify the kernel, so we have to check if the kernel exist or not.
   # By checking the kernel modules, we will know that!
   # (Here we assume the kernel modules will be created, not all buildin)
   # the kernel will exist in 
   # (1) /lib/modules - old way
   # (2) /tftpboot/lib/modules
   [ ! -d "$drbl_common_root/lib/modules/$selected_kernel" -a ! -d "/lib/modules/$selected_kernel" ] && echo "Can NOT find the kernel \"$selected_kernel\" you specified! Program terminated!" && exit 1
   drbl_kernel="$selected_kernel"
   # if we can find the modules in the common_root, use it first.
   # the drbl_kernel_mod_path is the leading path.
   if [ -d "$drbl_common_root/lib/modules/$selected_kernel" ]; then
      drbl_kernel_mod_path="$drbl_common_root/"
   else
      drbl_kernel_mod_path=""
   fi
   echo "Using the kernel modules from $drbl_kernel_mod_path/lib/modules..."

fi

# 
echo "The selected kernel for DRBL clients is: $drbl_kernel"

# prepare the directories for DRBL clients
[ ! -d /tftpboot ] && mkdir /tftpboot
[ ! -d /tftpboot/nbi_img ] && mkdir -p /tftpboot/nbi_img
[ ! -d /tftpboot/node_root ] && mkdir -p /tftpboot/node_root
[ ! -d /tftpboot/nodes ] && mkdir -p /tftpboot/nodes

# FORGET about this!!! check the latter one marked as "2004/05/11 Steven Shiau"
# If no /etc/dhcpd.conf.etherboot-pcimap.include, we still need to scan all NICs
# and create it, not matter user specifies -n option.
#if [ "$NIC" = "all" -o ! -f "/etc/dhcpd.conf.etherboot-pcimap.include" ]; then
#     [ ! -f "/etc/dhcpd.conf.etherboot-pcimap.include" ] && echo "No /etc/dhcpd.conf.etherboot-pcimap.include, we have to scan all the NICs and create it..."
#     mknbi-set -k /boot/vmlinuz-$drbl_kernel
#     echo "$drbl_kernel" > /tftpboot/nbi_img/kernel_version_in_initrd.txt
#else
#     echo "$drbl_kernel" > /tftpboot/nbi_img/kernel_version_in_initrd_$NIC.txt
#     mkinitrd-net --nolink --kernel $drbl_kernel $NIC
#     mknbi-linux /boot/vmlinuz-$drbl_kernel /tftpboot/nbi_img/initrd-$NIC.$drbl_kernel.img > /tftpboot/nbi_img/boot-$NIC.nbi
#fi

#
echo "$drbl_kernel" > $pxecfg_pd/kernel_version_in_initrd.txt
[ -n "$CLIENT_ARCH" ] && echo "$CLIENT_ARCH" > $pxecfg_pd/client_kernel_arch.txt

# 2005/4/4 Steven Shiau
# Since etherboot 5.4.0 is released, which can almost simulate etherboot as PXE except localboot. We drop this code... use PXE will be easier.

# 2004/05/11 Steven Shiau
# we do the same method like PXE initrd, i.e. let initrd to detect its own NIC
# forget the original method from Mandrake 9.1.
# so borrow the vmlinuz and initrd from that is created by mkpxeinitrd-net

# for ifile in vmlinuz-$drbl_kernel initrd-pxe.$drbl_kernel.img; do
#    [ ! -f /tftpboot/nbi_img/$ifile ] && echo "/tftpboot/nbi_img/$ifile NOT found!!! Program terminated!" && exit 1
# done
# 
# # clean the old file
# [ -f /tftpboot/nbi_img/boot_etherboot-$drbl_kernel.img ] && rm -f /tftpboot/nbi_img/boot_etherboot-$drbl_kernel.img
# 
# echo -n "Creating the etherboot image for clients..."
# mknbi-linux --param="ramdisk_size=$default_ramdisk_size" /tftpboot/nbi_img/vmlinuz-$drbl_kernel  /tftpboot/nbi_img/initrd-pxe.$drbl_kernel.img > /tftpboot/nbi_img/boot_etherboot_$drbl_kernel.img
# (cd /tftpboot/nbi_img/; ln -fs boot_etherboot_$drbl_kernel.img boot_etherboot.img)

# echo "done!"

if [ "$CP_MODULES" != "no" ]; then
   /opt/drbl/sbin/update_client_kernel_from_server.sh
fi

# create the initrd and kernel for clients.
KARCH_CLIENT="$(cat $pxecfg_pd/client_kernel_arch.txt 2>/dev/null)"
KARCH_EXISTING_KERNEL="$($DRBL_SCRIPT_PATH/bin/check_kernel_cpu_arch --drbl-client $drbl_kernel)"
if [ -z "$KARCH_EXISTING_KERNEL" ]; then 
  [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
  echo "The requested kernel \"$KARCH_CLIENT\" $drbl_kernel kernel files are NOT found in  $drbl_common_root/lib/modules/s and $drbl_common_root/boot in the server! The necessary modules in the network initrd can NOT be created!"
  echo "Client will NOT remote boot correctly!"
  echo "Program terminated!"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  exit 1
fi

if [ "$KARCH_CLIENT" = "best_optimization" -o "$KARCH_EXISTING_KERNEL" = "$KARCH_CLIENT" ]; then
  echo "Creating the network boot initrd for PXE clients..."
  mkpxeinitrd-net $usb_kb_opt $VERBOSE -k $drbl_kernel
else
  [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
  echo "The requested kernel \"$KARCH_CLIENT\" $drbl_kernel kernel files are NOT found in  $drbl_common_root/lib/modules/s and $drbl_common_root/boot in the server! The necessary modules in the network initrd can NOT be created!"
  echo "Client will NOT remote boot correctly!"
  echo "Program terminated!"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  exit 1
fi

echo "Finished!"
