#!/bin/bash

# $Id$
#*********************************************************************
#
# fai-cd -- make a fai CD, a bootable CD that performs the FAI
#
# This script is part of FAI (Fully Automatic Installation)
# (c) 2004-2011 by Thomas Lange, lange@informatik.uni-koeln.de
# Universitaet zu Koeln
# Support for grub2 added by Sebastian Hetze, s.hetze@linux-ag.de
# with major support from Michael Prokop, prokop@grml-solutions.com
#
# based on a script called make-fai-bootcd by Niall Young <niall@holbytla.org>
#
#*********************************************************************
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# A copy of the GNU General Public License is available as
# `/usr/share/common-licences/GPL' in the Debian GNU/Linux distribution
# or on the World Wide Web at http://www.gnu.org/copyleft/gpl.html.  You
# can also obtain it by writing to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#*********************************************************************

set -e 

# last die exit code 22

grub_version=2
boot_image="boot/grub/eltorito.img"
forceremoval=0
burn=0
bootonly=0
keep=0
makeiso=1
makeusb=0
hidedirs="/usr/share/locale /usr/share/doc /var/lib/apt /var/cache/apt /usr/share/man /var/lib/dpkg/info /media/mirror/aptcache /media/mirror/.apt-move /boot"

# we need FAI_CONFIGDIR, NFSROOT

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
usage() {

    cat <<-EOF
	fai-cd Copyright (C) 2004-2011 Thomas Lange

	Usage: fai-cd [OPTIONS] -m MIRRORDIR ISONAME
	Usage: fai-cd [OPTIONS] -B ISONAME
	Create a FAI CD, a bootable CD that performs the FAI.
	Read the man pages pages fai-cd(8).
EOF
exit 0
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
die() {

    local e=$1   # first parameter is the exit code
    shift

    echo "ERROR: $@"    # print error message
    exit $e
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
create_grub1_image() {

    mkdir -p $tmp/boot/grub $nfsrootdir/live/filesystem.dir
    if [ -f $NFSROOT/usr/lib/grub/*-pc/stage2_eltorito ]; then
	echo "preparing grub1 eltorito boot loader using NFSROOT"
	[ -d $NFSROOT/usr/lib/grub ] && cp -p $NFSROOT/usr/lib/grub/*-pc/stage2_eltorito $tmp/boot/grub/
	[ -d $NFSROOT/usr/lib/grub ] && cp -p $NFSROOT/usr/lib/grub/*-pc/stage{1,2} $tmp/boot/grub/
    else
	# if grub1 is not available in NFSROOT, we download the package based on the current
	# sources.list, unpack and use these files for further processing
	# this does not work if file: URI's are used in sources.lst
	apt-get -d install --reinstall grub >/dev/null
	grubPackage=`ls -rt /var/cache/apt/archives/grub_*|tail -1`
	if [ -z "$grubPackage" ]; then
	    die 21 "Downloading grub failed."
	fi

	GRUBDIR=$tmp/grubPackage
	echo "no grub1 installation found in NFSROOT, using downloaded $grubPackage"
	mkdir -p $GRUBDIR
	dpkg -x $grubPackage $GRUBDIR
	cp -p $GRUBDIR/usr/lib/grub/*-pc/stage2_eltorito $tmp/boot/grub/
	cp -p $GRUBDIR/usr/lib/grub/*-pc/stage{1,2} $tmp/boot/grub/
	rm -rf $GRUBDIR
    fi
    cp $grub_config $tmp/boot/grub/menu.lst
    # insert date into grub menu
    if [ $bootonly -eq 1 ]; then
	echo "Using kernel version $kernelversion"
	perl -pi -e "s/_VERSIONSTRING_/Kernel : $kernelversion/" $tmp/boot/grub/menu.lst
	perl -pi -e "s#_NFSROOT_#nfsroot=$servernfsroot#"        $tmp/boot/grub/menu.lst
    else
	perl -pi -e "s/_VERSIONSTRING_/   $isoversion     /" $tmp/boot/grub/menu.lst
    fi
    cp -p $NFSROOT/boot/vmlinuz-$kernelversion $tmp/boot/vmlinuz
    cp -p $NFSROOT/boot/initrd.img-$kernelversion $tmp/boot/initrd.img
    cp -p $NFSROOT/boot/config-$kernelversion $tmp/boot/
    boot_image="boot/grub/stage2_eltorito"
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
create_grub2_image() {

    mkdir -p $tmp/boot/grub $nfsrootdir/live/filesystem.dir $NFSROOT/boot/grub
    if [ -f $NFSROOT/boot/grub/core.img ]; then
	echo "preparing grub2 eltorito.img using existing core.img from NFSROOT"
	cp $NFSROOT/boot/grub/core.img $tmp/boot/grub
	MODULEBASE=$NFSROOT/usr/lib/grub/
    elif [ -f $NFSROOT/usr/lib/grub/i386-pc/cdboot.img ]; then
	echo "preparing grub2 eltorito.img creating core.img in NFSROOT"
        if chroot $NFSROOT /usr/bin/grub-mkimage --help | grep -q -- --format ; then
	    chroot $NFSROOT /usr/bin/grub-mkimage -d /usr/lib/grub/i386-pc -o /boot/grub/core.img biosdisk iso9660 --format=i386-pc
        else
	    chroot $NFSROOT /usr/bin/grub-mkimage -d /usr/lib/grub/i386-pc -o /boot/grub/core.img biosdisk iso9660
        fi
	cp $NFSROOT/boot/grub/core.img $tmp/boot/grub
	MODULEBASE=$NFSROOT/usr/lib/grub/
    else
	echo "no grub2 installation found in NFSROOT, using downloaded $grubPackage"
	apt-get -y -d install --reinstall grub-pc >/dev/null
	apt-get -y -d install --reinstall grub-common >/dev/null
	grubPackage=`ls -rt /var/cache/apt/archives/grub-pc*|tail -1`
	grubCommonPackage=`ls -rt /var/cache/apt/archives/grub-common*|tail -1`
	if [ -z "$grubPackage" ]; then
	    die 22 "Downloading grub2 failed."
	fi
	if [ -z "$grubCommonPackage" ]; then
            die 22 "Downloading grub-common failed."
        fi 

	GRUBDIR=$tmp/grubPackage
	mkdir -p $GRUBDIR
	dpkg -x $grubPackage $GRUBDIR
	cd $GRUBDIR
	if ./usr/bin/grub-mkimage --help | grep -q -- --format ; then
	    ./usr/bin/grub-mkimage -d usr/lib/grub/*-pc -o $tmp/boot/grub/core.img biosdisk iso9660 --format=i386-pc
	else
	    ./usr/bin/grub-mkimage -d usr/lib/grub/*-pc -o $tmp/boot/grub/core.img biosdisk iso9660
	fi
	MODULEBASE=$GRUBDIR/usr/lib/grub/
    fi
    for a in $MODULEBASE/*-pc/{*.mod,efiemu??.o,cdboot.img,command.lst,moddep.lst,fs.lst,handler.lst,parttool.lst}; do
        [[ -e $a ]] && cp $a $tmp/boot/grub
    done
    rm -rf $GRUBDIR
    cp $grub_config $tmp/boot/grub/grub.cfg
    # insert date into grub menu
    perl -pi -e "s/_VERSIONSTRING_/   $isoversion     /" $tmp/boot/grub/grub.cfg
    cp -p $NFSROOT/boot/vmlinuz-$kernelversion $tmp/boot/vmlinuz
    cp -p $NFSROOT/boot/initrd.img-$kernelversion $tmp/boot/initrd.img
    cp -p $NFSROOT/boot/config-$kernelversion $tmp/boot/
    cat $tmp/boot/grub/cdboot.img $tmp/boot/grub/core.img > $tmp/boot/grub/eltorito.img
    boot_image="boot/grub/eltorito.img"
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
provide_memtest_boot_option() {

    if [ -f $NFSROOT/boot/memtest86+.bin ]; then
	cp $NFSROOT/boot/memtest86+.bin $tmp/boot
    elif [ -f /boot/memtest86+.bin ]; then
	cp /boot/memtest86+.bin $tmp/boot
    else
	echo "no memtest86+.bin found, omit memtest boot option"
	return
    fi
    
    if [ "$grub_version" -eq 1 ]; then
        cat >> $tmp/boot/grub/menu.lst <<EOF

title           memtest86+
root		(cd)
kernel          /boot/memtest86+.bin
quiet
EOF
    elif [ "$grub_version" -eq 2 ]; then
        cat >> $tmp/boot/grub/grub.cfg <<EOF

menuentry "Memory test (memtest86+)" {
    linux16 /boot/memtest86+.bin
}
EOF
    fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
customize_nfsroot() {

    # hide some dirs to save space and make the CD image smaller
    local d

    mkdir $tmp/empty
    for d in $hidedirs; do
	if [ -d $nfsrootdir/$d ]; then
	    [ "$debug" ] && echo "hiding $d"
	    mount --bind $tmp/empty $nfsrootdir/$d
	fi
    done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
unhide_dirs() {

    set +e
    for d in $hidedirs; do
	if [ -d $nfsrootdir/$d ]; then
	    [ "$debug" ] && echo "disclosing $d"
	    umount $nfsrootdir/$d 2>/dev/null
	fi
    done
    set -e
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
umount_dirs() {

    set +e
    local d
    local dirs="boot $FAI media/mirror etc/apt/sources.list"
    for d in $dirs; do
	umount $nfsrootdir/$d 2>/dev/null
    done
    rm -f $nfsrootdir/etc/RUNNING_FROM_FAICD
    [ -d $nfsrootdir ] && umount $nfsrootdir
    set -e
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
sleep_now() {

	sleep 6666 || true
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
mkiso() {

    [ $makeiso -eq 0 ] && return

    echo "Writing FAI CD-ROM image to $isoname. This may need some time."
    genisoimage --iso-level 4 -V "$vname" -A "$aname" -log-file /dev/null -quiet -l -R -J -b "$boot_image" -no-emul-boot -boot-load-size 4 -boot-info-table -o $isoname $tmp || die 12 "genisoimage failed." 
    echo -n "ISO image size and filename: "; du -h $isoname
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
mkusb(){

    local device

    [ $makeusb -eq 0 ] && return

    # TODO: If usbdir is a device (matches /dev/) mount it
    [ -d $usbdir ] || die 17 "$usbdir does not exist."
    if [ $rsync -eq 1 ]; then
	echo "Syncing FAI data to USB device $usbdir"
	rsync --delete -avW $tmp/boot $usbdir || true
	[ -d $tmp/live ] && rsync --delete -avW $tmp/live $usbdir || true
    else
	echo "Writing FAI data to USB device $usbdir"
	cp -a $tmp/boot $usbdir 2>/dev/null || true
	[ -d $tmp/live ] && cp -a $tmp/live $usbdir 2>/dev/null || true
    fi
    echo $isoversion > $usbdir/.FAI-CD-VERSION


    if [ "$grub_version" -eq 1 ]; then

    # now make the USB device bootable (grub1)
    rootpartition=$(find_fai_data)
    usbdev=$(echo $rootpartition | sed -e 's/,[[:digit:]]//')
    echo "Root partition is $rootpartition, device is: $usbdev"
    if [ -n "$rootpartition" -a -n "$usbdev" ]; then
	echo "Installing grub."
	echo -e "root $rootpartition\n setup $usbdev" | grub --batch >/dev/null
    else
	echo "Device could not be detemined. Installing grub will be skipped."
    fi

    elif [ "$grub_version" -eq 2 ]; then
    # detect device of mounted usb stick, grub2
    device=$(grub-probe -tdrive $usbdir | perl -ane 'm#(/dev/\w+),# && print "$1\n"')
    echo "Installing grub2 to $device."
    # this call seems to remove file and copies them again onto the stick
    grub-install --no-floppy --root-directory=$usbdir $device
    fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
create_iso() {

    # Create the El Torito bootable iso9660 cdrom image
    # or copy all FAI data to directory (for USB)

    echo "Bind mounting all required parts"

    mount --bind $NFSROOT $nfsrootdir && echo "NFSROOT $NFSROOT mounted"
    mkdir -p $nfsrootdir/media/mirror || true

    > $nfsrootdir/etc/RUNNING_FROM_FAICD
    mount --bind $FAI_CONFIGDIR $nfsrootdir/$FAI && echo "Config space $FAI_CONFIGDIR mounted"
    mount --bind $mirrordir $nfsrootdir/media/mirror && echo "Mirror $mirrordir mounted"
# TODO: customize /etc/apt, copy apt preferences etc.

    # this will be the sources.list for the CD
    tmp1=$(mktemp) || exit 12
    cat > $tmp1 <<EOF
# mirror location for fai CD, file generated by fai-cd
EOF

    dists=$(find $mirrordir -name "Packages*" | grep binary | sed 's/binary-.*//' | \
         sed "s#$mirrordir/*dists/##" | xargs -r -n 1 dirname | sort | uniq )
    [ -z "$dists" ] && die 19 "No suitable Packages file found in mirror."

    for i in $dists ; do
	comp=$(find $mirrordir/dists/$i -maxdepth 2 -type d -name "binary-*" | \
        sed -e "s#$mirrordir/*dists/$i/##" -e 's#/binary-.*##' | tr '\n' " ")
	echo "deb file:/media/mirror $i $comp" >> $tmp1
    done

    mount --bind $tmp1 $nfsrootdir/etc/apt/sources.list
    customize_nfsroot

    if [ $keep -eq 1 ]; then
	echo "fai-cd script stopped for 6666 seconds. The filesystem is now available in $tmp."
	echo "To continue the script and call the cleanup call: pkill -f 'sleep 6666'"
	sleep_now 2>/dev/null || true
	echo "Continue cleanup."
    fi

    mkiso
    mkusb
    rm $tmp1
    unhide_dirs
    umount_dirs
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
find_fai_data() {

    # you must run this as root (otherwise you may get wrong device names
    echo "find /.FAI-CD-VERSION" | grub --batch 2>/dev/null | grep '(hd'
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
burniso() {

    wodim -v -eject $isoname
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
# main program

rsync=0
if [ -x "$(which rsync)" ] ; then rsync=1 ; fi

# Parse commandline options
while getopts "nkfhHg:bBG:m:C:u:" opt ; do
    case "$opt" in
        C)  cdir=$OPTARG ;;
	f)  forceremoval=1 ;;
	h)  usage ;;
	H)  hidedirs="" ;;
	g)  grub_config="$OPTARG" ;;
	k)  keep=1 ;;
	m)  mirrordir="$OPTARG" ;;
	n)  makeiso=0 ;;
	b)  burn=1 ;;
	B)  bootonly=1 ;;
	G)  grub_version="$OPTARG" ;;
	u)  usbdir="$OPTARG"
	    makeusb=1
	    makeiso=0
	    [ $rsync -eq 0 -a -e $usbdir/live ] && die 11 "Please remove $usbdir/live. Aborting."
	    ;;
	?)  usage ;;
    esac
done
shift $(($OPTIND - 1))
isoname=$1

[ $makeiso -eq 1 -a "$#" -eq 0 ]        && die 2 "Please specify the output file for the ISO image."
if [ $bootonly -eq 0 ]; then
    [ -z "$mirrordir" ]   && die 4 "Please specify the directory of your mirror using -m"
    [ -d "$mirrordir" ]   || die 5 "$mirrordir is not a directory"
    [ -z "$(ls $mirrordir)" ] && die 18 "No mirror found in $mirrordir. Empty directory."
fi
[ -f "$isoname" ]     && [ $forceremoval -eq 1 ] && rm $isoname
[ -f "$isoname" ]     && die 3 "Outputfile $isoname already exists. Please remove it or use -f."

if [ $bootonly -eq 0 -o $makeusb -eq 1 ]; then
    [ $(id -u) != "0" ]   && die 9 "Run this program as root."
fi
[ "$grub_version" -eq 1 ] || [ "$grub_version" -eq 2 ] || die 20 "grub version -G must be either 1 or 2."

if [ $makeiso -eq 1 ]; then
    [ -x "$(which genisoimage)" ] || die 8 "genisoimage not found. Please install package."
fi

# use FAI_ETC_DIR from environment variable
if [ -n "$FAI_ETC_DIR" -a -z "$cdir" ]; then
    echo "Using environment variable \$FAI_ETC_DIR."
    cfdir=$FAI_ETC_DIR
fi
[ -n "$cdir" ] && cfdir=$cdir
: ${cfdir:=/etc/fai}
cfdir=$(readlink -f $cfdir) # canonicalize path
if [ ! -d "$cfdir" ]; then
    echo "$cfdir is not a directory"
    exit 6
fi
[ "$verbose" ] && echo "Using configuration files from $cfdir"
. $cfdir/fai.conf
. $cfdir/make-fai-nfsroot.conf
servernfsroot=$NFSROOT
NFSROOT="$NFSROOT/live/filesystem.dir"

[ -d "$NFSROOT/etc/fai" ] || die 10 "Please create NFSROOT by calling make-fai-nfsroot or fai-setup."

# if -g is a file name, add prefix

if [ -z "$grub_config" ]; then
    if [ $bootonly -eq 1 ]; then
	grub_config="/usr/share/fai/menu.lst" # default for boot-only CD
    else
	grub_config="$cfdir/menu.lst" # set default if undefined
    fi

    if [ "$grub_version" -eq 2 ]; then
	grub_config="$cfdir/grub.cfg" # set default if undefined
    fi
fi

# if grub_config contains / do not change it, else add prefix $cfdir
echo $grub_config | grep -q '/' || grub_config="$cfdir/$grub_config"
[ -f "$grub_config" ] || die 13 "Grub menu file $grub_config not found."

if [ $bootonly -eq 0 ]; then
    [ -z "$FAI_CONFIGDIR" ]  && die 14 "Variable \$FAI_CONFIG not set."
    [ -d $FAI_CONFIGDIR ] || die 15 "Can't find config space $FAI_CONFIGDIR."
    [ -d $FAI_CONFIGDIR/class ] || die 16 "Config space $FAI_CONFIGDIR seems to be empty."
fi

tmp=$(mktemp -t -d fai-cd.XXXXXX) || exit 13
nfsrootdir=$tmp/live/filesystem.dir
kernelversion=$(ls -tr $NFSROOT/boot/vmlinu?-* | tail -1 | sed -e 's#.*/vmlinuz-##')

faiversion=$(dpkg --root=$NFSROOT -l fai-client|awk '/fai-client/ {print $3}')
isoversion="FAI $faiversion -- build $(date '+%c')"
vname="Fully Automatic Installation CD"
aname="Fully Automatic Installation by Thomas Lange, $isoversion"

if [ "$grub_version" -eq 1 ]; then
    create_grub1_image
elif [ "$grub_version" -eq 2 ]; then
    create_grub2_image
fi

provide_memtest_boot_option

if [ $bootonly -eq 1 ]; then
    rm -rf $tmp/live
    mkiso
    mkusb
else
    trap "unhide_dirs;umount_dirs" EXIT ERR
    create_iso
fi

rm -rf $tmp
[ "$burn" -eq 1 ] && burniso
exit 0
