#!/bin/bash

#*********************************************************************
#
# fai-cd -- make a fai CD, a bootable CD that performs the FAI
#
# This script is part of FAI (Fully Automatic Installation)
# (c) 2004-2016 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
# dracut/squashfs support by Kerim Gueney, gueney@informatik.uni-koeln.de
# 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

boot_image="boot/grub/eltorito.img"
count=0
config_space=""
configset=0
forceremoval=0
burn=0
bootonly=0
squash_only=0
nomirror=0
hidevartmp=0
rel=0
target=0
autodiscover=0
# arguments to be passed on to internal fai-cd call
arguments="-MB"

liveos="squashfs-root/LiveOS/"
hidedirs="/usr/share/locale /usr/share/doc /var/lib/apt /var/lib/aptitude /var/cache/debconf /var/cache/apt /var/cache/man /usr/share/man /var/lib/dpkg/info /media/mirror/aptcache "

# we need FAI_CONFIGDIR, NFSROOT

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

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

	Usage: fai-cd [OPTIONS] -m MIRRORDIR ISONAME
	Usage: fai-cd [OPTIONS] -B ISONAME
	Usage: fai-cd [OPTIONS] -S IMAGEFILE
	Create a FAI CD, a bootable CD that performs the FAI or a squashfs boot image.
	Read the man pages pages fai-cd(8) for more information.
EOF
exit 0
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
die() {

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

    echo "ERROR: $@" >&2   # print error message
    exit $e
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
create_ext3fs_image() {

    # create the folder that will be used for the squashfs.img later
    # along with it, a temporary folder is created to mount the ext3fs.img
    mkdir -p $tmp/$liveos/mounted-ext3fs

    calculate_required_size

    # creates an empty file
    dd if=/dev/zero of=$tmp/$liveos/ext3fs.img bs=1MB count=$count 2>/dev/null ||
        die 9 "dd failed. Maybe no enough free space in $tmp"

    mkfs.ext4 -q -O^has_journal -F -L FAI-NFSROOT $tmp/$liveos/ext3fs.img

    # the resulting ext3fs.img is mounted
    mount -o loop,noatime $tmp/$liveos/ext3fs.img $tmp/$liveos/mounted-ext3fs

    # hide some directories before copying the nfsroot
    customize_nfsroot
    echo "Copying the nfsroot to CD image"
    # copy the contents of the nfsroot into it
    cp -Ppr $ONFSROOT/. $tmp/$liveos/mounted-ext3fs/ || die $? "Not enough space left on device for the nfsroot"
    rm -f $tmp/$liveos/mounted-ext3fs/etc/resolv.conf

    # find $tmp/$liveos/mounted-ext3fs # for debugging
    # copy config space into nfsroot-copy unless -d is given
    if [ $configset -eq 0 ]; then
        echo "Copying the config space to CD image"
        cp -Ppr $FAI_CONFIGDIR/* $tmp/$liveos/mounted-ext3fs/var/lib/fai/config/ || die $? "Not enough space left on device for the config directory"
    fi
    # add the partial mirror to the CD
    addmirror

    unhide_dirs
    umount $tmp/$liveos/mounted-ext3fs
    rmdir $tmp/$liveos/mounted-ext3fs
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
create_autodiscover_iso() {

    # boot-only CD does not need those dracut modules
    cat <<EOF > $ONFSROOT/etc/dracut.conf.d/02-omit.conf
omit_dracutmodules+=" fs-lib livenet rootfs-block "
EOF

    if [ -n "$sqopt" ]; then
	compress="--xz"
    else
	compress=--compress='gzip -1'
    fi
    mv $ONFSROOT/boot/initrd.img* $ONFSROOT/tmp
    hide_dirs
    mountpoint -q $ONFSROOT/proc || mount --bind /proc $ONFSROOT/proc
    chroot $ONFSROOT dracut --add 'fai-autodiscover' "$compress" /boot/initrd.img-$rel $rel 2>/dev/null
    umount $ONFSROOT/proc
    unhide_dirs
    rm -f $ONFSROOT/etc/dracut.conf.d/02-omit.conf

    fai-cd $arguments -g $grub_config $isoname
    mv $ONFSROOT/tmp/initrd.img* $ONFSROOT/boot
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
create_squashfs_image() {

    # this is where the squashfs.img needs to be placed
    mkdir -p $tmp/LiveOS

    # create the squashfs.img
    output_path=$tmp/LiveOS/squashfs.img

    if [ $squash_only -eq 1 ]; then
        output_path=$isoname
    fi

    mksquashfs $tmp/squashfs-root $output_path $sqopt

    rm $tmp/$liveos/ext3fs.img
    rmdir $tmp/$liveos $tmp/squashfs-root
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
create_grub2_image() {

    mkdir -p $tmp/boot/grub $NFSROOT/boot/grub
    if [ -f $NFSROOT/boot/grub/core.img ]; then
        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
        chroot $NFSROOT /usr/bin/grub-mkimage -p /boot/grub -d /usr/lib/grub/i386-pc -o /boot/grub/core.img biosdisk iso9660 --format=i386-pc
        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
        ./usr/bin/grub-mkimage -p /boot/grub -d usr/lib/grub/*-pc -o $tmp/boot/grub/core.img biosdisk iso9660 --format=i386-pc
        MODULEBASE=$GRUBDIR/usr/lib/grub
    fi

    # check for jessie path
    if strings $NFSROOT/usr/lib/grub/i386-pc/kernel.img | grep -q i386-pc/%s.mod; then
	grubtarget=$tmp/boot/grub/i386-pc
    else
	# wheezy
	grubtarget=$tmp/boot/grub
    fi

    mkdir -p $grubtarget
    for a in $MODULEBASE/*-pc/{*.mod,efiemu??.o,cdboot.img,boot.img,command.lst,moddep.lst,fs.lst,handler.lst,parttool.lst}; do
        [[ -e $a ]] && cp $a $grubtarget
    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
    if [ -n "$config_space" ] || [ $configset -eq 1 ]; then
	perl -pi -e "s|FAI_CONFIG_SRC=(.*?)\S+|FAI_CONFIG_SRC=$config_space|" $tmp/boot/grub/grub.cfg
    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/
    cat $grubtarget/cdboot.img $tmp/boot/grub/core.img > $tmp/boot/grub/eltorito.img
    boot_image="boot/grub/eltorito.img"

    embed_image="$tmp/boot/grub/embed.img"
    cat $grubtarget/boot.img $tmp/boot/grub/core.img > $embed_image
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
calculate_required_size() {
    # calculates the amount of space needed for the ext3fsimage

    [ $configset -eq 1 ] && unset FAI_CONFIGDIR
    if [ $nomirror -eq 0 ]; then
	size=`du -sc $ONFSROOT $FAI_CONFIGDIR $mirrordir/{dists,pool} | tail -n1 | awk '{print $1}'`
    else
	size=`du -sc $ONFSROOT $FAI_CONFIGDIR $mdir | tail -n1 | awk '{print $1}'`
    fi

    # not dividing by 1024 to get the exact, this allows us to get some additional space
    count=$(($size/900))
}

provide_memtest_boot_option() {

    if [ $bootonly -eq 1 ]; then
	return
    fi

    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

    cat >> $tmp/boot/grub/grub.cfg <<EOF

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

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

    mkdir -p $tmp/empty
    for d in $hidedirs; do
        if [ -d $target/$d ]; then
            [ "$debug" ] && echo "hiding $d"
            mount --bind $tmp/empty $target/$d
        fi
    done
}

customize_nfsroot() {

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

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

    set +e
    for d in $hidedirs; do
	if [ $autodiscover -eq 1 ]; then
	    if [ -d $target/$d ]; then
		[ "$debug" ] && echo "disclosing $d"
		umount  $target/$d 2>/dev/null
            fi
	else
            if [ -d $ONFSROOT/$d ]; then
		[ "$debug" ] && echo "disclosing $d"
		umount $ONFSROOT/$d 2>/dev/null
            fi
	fi
    done
    set -e
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
cleanup_liveos_mounts() {

    mountpoint -q $tmp/$liveos/mounted-ext3fs && umount $tmp/$liveos/mounted-ext3fs
    unhide_dirs
    rm -rf $tmp
    if [ $autodiscover -eq 1 ]; then
	mv $ONFSROOT/tmp/initrd.img* $ONFSROOT/boot 2>/dev/null
    fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
mkiso() {

    echo "Writing FAI CD-ROM image to $isoname. This may need some time."
    xorriso -report_about HINT -as mkisofs -graft-points -b $boot_image \
        -V "$vname" -A "$aname" \
        -no-emul-boot -boot-info-table --embedded-boot ${embed_image} -partition_offset 16 -no-pad -o $isoname \
        -R $tmp --sort-weight 0 / --sort-weight 1 /boot || die 12 "xorriso failed."
    echo -n "ISO image size and filename: "; du -h $isoname

    unhide_dirs
}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
addmirror() {

    [ $nomirror -eq 1 ] && return

    mkdir $tmp/$liveos/mounted-ext3fs/media/mirror

    echo "Copying mirror to CD image"
    cp -Pr $mirrordir/{dists,pool} $tmp/$liveos/mounted-ext3fs/media/mirror 2>/dev/null || die $? "Not enough space left on device for the mirror"

    # create the sources.list for the CD
    cat > $tmp/$liveos/mounted-ext3fs/etc/apt/sources.list <<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-.*##' | uniq | tr '\n' " ")
        echo "deb file:/media/mirror $i $comp" >> $tmp/squashfs-root/LiveOS/mounted-ext3fs/etc/apt/sources.list
    done

}
# - - - - - - - - - - - - - - - - - - - - - - - - - -
burniso() {

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

# Parse commandline options
while getopts "AenkfhHg:JbSBMm:C:d:" opt ; do
    case "$opt" in
	A)  autodiscover=1 ;;
        C)  cdir="$OPTARG" ; arguments+=" -C $cdir";;
        e)  hidevartmp=1 ;;
        f)  forceremoval=1 ;;
        h)  usage ;;
        H)  hidedirs="" ;;
        g)  grub_config="$OPTARG" ;;
        J)  sqopt="-comp xz" ; arguments+=" -J";;
        M)  nomirror=1 ;;
        m)  mirrordir="$OPTARG" ;;
        b)  burn=1 ; arguments+=" -b";;
        S)  squash_only=1 ;;
        B)  bootonly=1 ;;
	d)  config_space="$OPTARG"; configset=1 ; arguments+=" -d $config_space";;
        ?)  usage ;;
    esac
done
shift $(($OPTIND - 1))
isoname=$1


if [ $autodiscover -eq 1 ]; then
    nomirror=1
    bootonly=1
    hidedirs="
    net/ceph
    net/wireless
    sound
    drivers/block/drbd
    drivers/staging/lustre
    drivers/scsi
    drivers/ata
    drivers/target
    drivers/pcmcia
    drivers/mmc
    drivers/media
    drivers/infiniband
    drivers/isdn
    drivers/video
    drivers/mtd
    drivers/md
    "
fi

[ $nomirror -eq 1 -a -n "$mirrordir" ]  && die 6 "Do not use -M and -m at the same time."
[ "$#" -eq 0 ]        && die 2 "Please specify the output file for the ISO image."
if [ $bootonly -eq 0 -a $nomirror -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 ]; then
    [ $(id -u) != "0" ]   && die 9 "Run this program as root."
fi

[ $squash_only -eq 1 -o -x "$(which xorriso)" ] || die 8 "xorriso not found. Please install package."
[ -x "$(which mksquashfs)" ] || die 8 "mksquashfs not found. Please install the package squashfs-tools."
[ -x "$(which strings)" ] || die 8 "command strings not found. Please install package binutils."

# 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" >&2
    exit 6
fi
[ X$verbose = X1 ] && echo "Using configuration files from $cfdir"
. $cfdir/fai.conf
. $cfdir/nfsroot.conf
: ${FAI:=/var/lib/fai/config} # default value
ONFSROOT=$NFSROOT # save original nfsroot

if [ $autodiscover -eq 1 ]; then
    rel=$(ls $ONFSROOT/lib/modules/|head -1)
    target=$ONFSROOT/lib/modules/$rel/kernel
fi

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

[ $hidevartmp -eq 1 ] && hidedirs+="/var/tmp"

# if -g is a file name, add prefix

if [ -z "$grub_config" ]; then

    grub_config="$cfdir/grub.cfg" # set default if undefined
    [ $autodiscover -eq 1 ] && grub_config="$cfdir/grub.cfg.autodiscover" # set default if undefined
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 -o $configset -eq 1 ]; 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

trap "cleanup_liveos_mounts" EXIT ERR

if [ $bootonly -eq 0 ]; then
create_ext3fs_image
create_squashfs_image
fi

if [ $autodiscover -eq 1 ]; then
    create_autodiscover_iso
    exit 0
fi

[ $squash_only -eq 1 ] && exit 0


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=$(printf "FAI %-6s build $(date '+%Y %b %d - %R')" "$faiversion")
vname="FAI_CD"
aname="Fully Automatic Installation by Thomas Lange, $isoversion"

create_grub2_image

provide_memtest_boot_option

mkiso

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