#!/bin/bash -xe
PS4='+ ($LINENO) ' # To ease debugging
VERSION="1.2-emac-rc3"

# Config #
##########
			# Failures happens if $WORKDIR has "nodev" mount option
WORKDIR=./work		# sudo rm -r this folder if you want to re-run everything
DLDIR=./downloads
OUTDIR=./out
#OUTUSB=/dev/sdb	# If enabled, it could wreck everything there !
LEGACY=n		# make USB bootable key compatible with non UEFI-BIOS
DEVEL_MODE=n		# Adds debugging tools in the generated image
INCLUDE_TCPDUMP=y	# tcpdump costs few Mb with libcrypto (but lspci depends on it also)
INCLUDE_PERF=n		# perf costs few Mb with libpython2.7 and so
ROOTCMD=sudo
WGET="wget"		# "wget --no-check-certificate" could help but is a security concern

# Should be customized with your NFS server IP address or options
NFS_MOUNT_CMDLINE="mount -v -t nfs -o nolock eficast-nfs.mines-albi.fr:/masters /mnt/nfs"

# You probably need to tweak version numbers in following URLs if you have an HTTP 404 - Not found error
KERNEL_TARBALL_URL=https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.7.7.tar.xz
#KERNEL_TARBALL_URL=https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.2.9.tar.xz	# ok in qemu, seems sane on real hardware
#KERNEL_TARBALL_URL=https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.20.17.tar.xz	# ok in qemu, seems sane on real hardware
#KERNEL_TARBALL_URL=https://cdn.kernel.org/pub/linux/kernel/v3.x/linux-3.18.140.tar.xz	# ok in qemu
#KERNEL_TARBALL_URL=https://cdn.kernel.org/pub/linux/kernel/v3.x/linux-3.12.74.tar.xz	# reset loop in qemu
# old kernels 2.6, 3.x before year 2017 don't cope with recent distro/gcc defaults options (-fPIE)
# https://unix.stackexchange.com/questions/436071/compile-old-kernel-with-new-gcc
# https://lore.kernel.org/patchwork/patch/731680/
#KERNEL_TARBALL_URL=https://cdn.kernel.org/pub/linux/kernel/v3.x/linux-3.11.9.tar.xz	# Kconfiglib v12.13.0 can't cope with it, and gcc probably too recent
#KERNEL_TARBALL_URL=https://cdn.kernel.org/pub/linux/kernel/v2.6/linux-2.6.39.4.tar.xz


# KConfigLib allows to create kernel .config in a programmatic manner, in a somewhat portable way
# Main project URL is https://github.com/ulfalizer/Kconfiglib
KCONFIGLIB_MAIN_URL=https://raw.githubusercontent.com/ulfalizer/Kconfiglib/v14.1.0/kconfiglib.py
KCONFIGLIB_PATCH_URL=https://raw.githubusercontent.com/ulfalizer/Kconfiglib/master/makefile.patch
NIC_FIRMWARE_URL=http://fr.archive.ubuntu.com/ubuntu/pool/main/l/linux-firmware/nic-firmware_1.188_all.udeb
BUSYBOX_BIN_URL=https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
PCI_IDS_URL=https://pci-ids.ucw.cz/v2.2/pci.ids
USB_IDS_URL=https://usb-ids.gowdy.us/usb.ids

# Free space checking to not let everything go wrong
ROOT_MIN_AVAIL_MIO=256
WORKDIR_MIN_AVAIL_MIO=2560
DLDIR_MIN_AVAIL_MIO=512
OUTDIR_MIN_AVAIL_MIO=64

# Utilities #
#############
function check_free_space
{
	limit_mio=$1
	path="$2"
	LANG=C df "$path" | tail -1 | (
		set +x
		read dev total used avail percent mp
		avail=${avail:--1024}
		avail_mio=$(($avail/1024))
		if [ "$avail_mio" -lt "$limit_mio" ]
		then	echo "$mp ($dev) should have more free space $avail_mio Mio < $limit_mio Mio)"
			exit 2
		fi
	)
}

function add_initrd_script
{
	[ "$WORKDIR/initrd/$1" -nt "$0" ] || ( echo "#!/bin/busybox sh" ; cat ) > "$WORKDIR/initrd/$1" && chmod +x "$WORKDIR/initrd/$1"
}

# From https://landley.net/writing/rootfs-programming.html
# Its first argument is the new directory, and the rest of its arguments are executables to copy.
function mkchroot
{
  [ $# -lt 2 ] && return 0
  dest=$1
  shift
  for i in "$@"
  do
    # Get an absolute path for the file
    p=$i
    [ "${p:0:1}" == "/" ] || p=$(which $i) || true
    if [ ! -e "$p" ]
    then echo "mkchoot not found: $i"
         return 1
    fi
    # Skip files that already exist at target.
    [ -s "$dest/$p" ] && continue
    # Create destination path
    d=$(echo "$p" | grep -o '.*/') &&
    mkdir -p "$dest/$d" &&
    # Copy file
    echo + cp --dereference --preserve=mode "$p" "$dest/$p" &&
    cp --dereference --preserve=mode "$p" "$dest/$p" &&
    # Recursively copy shared libraries' shared libraries.
    mkchroot "$dest" $(ldd "$p" | egrep -o '/.* ') || return $?
  done
}

# Environement and dependencies #
#################################
which lsb_release || $ROOTCMD apt install lsb-release
codename=$(lsb_release -sc || true)
if [ "x$codename" != "xbuster" ]
then	cat >&2 <<EOT
This script is tested only on GNU/Linux Debian 10 amd64 (aka Buster).
The fastest way to have the right environment is :
 * download Debian 10 amd64 live
   * here : http://cdimage.debian.org/debian-cd/current-live/amd64/iso-hybrid/
   * or here : http://cdimage.debian.org/mirror/cdimage/archive/
 * burn it or copy it on a USB stick (as raw, with "sudo cp XX.iso /dev/sdX")
   * alternatively launch a VM with it
 * run this script from there
EOT
	exit 1
fi

umask 0022 # Needed for embeding initrd without "sudo make" in kernel dir

[ -d "$WORKDIR" ] || ( mkdir -p "$WORKDIR" && check_free_space $WORKDIR_MIN_AVAIL_MIO "$WORKDIR" && touch "$WORKDIR/NOBACKUP.TAG" )
[ -d "$DLDIR"   ] || ( mkdir -p "$DLDIR"   && check_free_space $DLDIR_MIN_AVAIL_MIO   "$DLDIR"   )
[ -d "$OUTDIR"  ] || ( mkdir -p "$OUTDIR"  && check_free_space $OUTDIR_MIN_AVAIL_MIO  "$OUTDIR"  )


# When using sudo with password auth, ask and cache pass first
$ROOTCMD true

if [ ! -e "$DLDIR/apt-update-done" ]
then	$ROOTCMD apt-get update
	> "$DLDIR/apt-update-done"
fi

if [ ! -e "$WORKDIR/apt-install-done" ]
then	check_free_space $ROOT_MIN_AVAIL_MIO /

	# Dependencies of this script (assuming default debian install or live)
	$ROOTCMD apt-get install wget libncurses5-dev coreutils
	[ "x$LEGACY" == "xy" ] && $ROOTCMD apt-get install mbr syslinux
	# Dependencies for kernel building
	$ROOTCMD apt-get install build-essential flex bison libelf-dev

	# Dependencies for kernel tools
	[ "x$DEVEL_MODE" == "xy" ] && $ROOTCMD apt-get install libunwind-dev \
		libdw-dev libaudit-dev libssl-dev libslang2-dev libnuma-dev \
		systemtap-sdt-dev python-dev binutils-dev libiberty-dev libbabeltrace-ctf-dev
	# Optionnally qemu to run the result for santity checking
	[ "x$DEVEL_MODE" = "xy" ] && $ROOTCMD apt-get install qemu-system-x86
	# Dependencies to put into the initrd
	# util-linux : blkdiscard, fdisk, sfdisk, lsblk, setterm
	# net-tools : mii-tool
	# ncurses-bin : tput
	$ROOTCMD apt-get install dmidecode pciutils usbutils lshw sysstat iftop strace rsync \
		ntfs-3g e2fsprogs dosfstools exfat-utils partclone util-linux gdisk efibootmgr \
		pv bc figlet toilet-fonts ncurses-bin beep net-tools ethtool pigz tmux iperf
	# Optionnal dependencies to put into the initrd
	[ "x$INCLUDE_TCPDUMP" == "xy" ] && $ROOTCMD apt-get install tcpdump
	> "$WORKDIR/apt-install-done"
fi

# Kernel build setup #
######################
kernel_tarball=$DLDIR/$(basename $KERNEL_TARBALL_URL)
[ -s "$kernel_tarball" ] || $WGET -O "$kernel_tarball" "$KERNEL_TARBALL_URL"
if [ ! -s "$WORKDIR/kernel/Makefile" ]
then	mkdir -p "$WORKDIR/kernel"
	tar xf "$kernel_tarball" --strip-components=1 -C "$WORKDIR/kernel"
fi

if [ ! -s "$WORKDIR/kernel/scripts/Kconfiglib/kconfiglib.py" ]
then
	[ -s "$DLDIR/kconfiglib.py" ] || $WGET -O "$DLDIR/kconfiglib.py" "$KCONFIGLIB_MAIN_URL"
	[ -s "$DLDIR/makefile.patch" ] || $WGET -O "$DLDIR/makefile.patch" "$KCONFIGLIB_PATCH_URL"
	mkdir -p "$WORKDIR/kernel/scripts/Kconfiglib"
	patch -t -p1 -d "$WORKDIR/kernel" < "$DLDIR/makefile.patch"
	cp "$DLDIR/kconfiglib.py" "$WORKDIR/kernel/scripts/Kconfiglib/kconfiglib.py"
fi

cat >"$WORKDIR/kernel/scripts/Kconfiglib/customize.py" <<"EOT"
#!/usr/bin/env python
import sys
from kconfiglib import Kconfig, standard_config_filename, TRI_TO_STR, TRISTATE

def sset(sym, value=None):
    # Default value
    if value == None:
        if sym.assignable:
            # find highest possible assignable value (last item of modifiable sorted tuple)
            value = sym.assignable[-1]
        else:
            print('%s is not modifiable at all for now'%sym.name)
            return True
    # Sanity check
    if isinstance(value, (int, long)) and value not in sym.assignable:
        print('%s can\'t be set to %s for now'%(sym.name,TRI_TO_STR[value]))
        return True
    # Idempotency check
    if isinstance(value, (int, long)):
        old_value = sym.tri_value
    else:
        old_value = sym.str_value
    if old_value == value:
        # No more_work
        return False
    # Set value
    if isinstance(value, (int, long)):
        print('%s=%s [was: %s]'%(sym.name,TRI_TO_STR[value],TRI_TO_STR[old_value]))
    else:
        print('%s=%s [was: %s]'%(sym.name,value,old_value))
    sym.set_value(value)
    # plausible more_work to do
    return True

kconf = Kconfig(sys.argv[1])
kconf.load_config(standard_config_filename())
debug = '--debug' in sys.argv;
passes = 5

support_xz = 'HAVE_KERNEL_XZ' in kconf.syms
print('support_xz == %r'%support_xz)

i = 0
more_work = True
while more_work and i < passes:
    more_work = False
    i += 1
    print('Kconfiglib/customize.py pass %i'%i)

    for sym in kconf.defined_syms:
        # Default hostname is (none) and could make FreeBSD's dhcpd complain because unallowed '()'
        if sym.name == 'DEFAULT_HOSTNAME':
            more_work = sset(sym, 'eficast') or more_work

        # Embed initrd in the EFI bootable kernel
        if sym.name == 'INITRAMFS_SOURCE':
            more_work = sset(sym, '../initrd/') or more_work

        # Support dumb hotplug via /sbin/hotplug script (disabled by default on 5.x kernels, may be some 4.x also)
        # We don't ship mdev/udev/systemd or any netlink listener to load appropriate modules for now
        if sym.name == 'UEVENT_HELPER':
            more_work = sset(sym) or more_work
        if sym.name == 'UEVENT_HELPER_PATH':
            more_work = sset(sym, '/sbin/hotplug') or more_work

        # Make kernel directly loadable by EFI, add USB3, Dell flash
        if sym.name in ['EFI_STUB', 'EARLY_PRINTK_EFI']:
            more_work = sset(sym) or more_work
        if sym.name in ['EFI_VARS', 'DELL_RBU', 'USB_XHCI_HCD']:
            more_work = sset(sym, 1) or more_work

        # Support FUSE, NVMe, soft RAID (linux) and hard RAID (some cards)
        if sym.name in ['FUSE_FS', 'DM_RAID', 'MEGARAID_SAS']:
            more_work = sset(sym, 1) or more_work
        if sym.name in ['BLK_DEV_NVME', 'SCSI_LOWLEVEL', 'MEGARAID_NEWGEN']:
            more_work = sset(sym) or more_work

        # If --debug passed as arg, make kernel aware of virtual drivers (used for testing eficast on qemu/kvm)
        if debug and sym.name in ['VIRTIO_PCI', 'VIRTIO_MMIO', 'VIRTIO_NET', 'VIRTIO_BLK', 'SCSI_LOWLEVEL', 'SCSI_VIRTIO']:
            more_work = sset(sym) or more_work

        # Disable thing that are unneeded or annoying for the purpose of disk cloning
        if sym.name in [ 'HAMRADIO', 'HIBERNATION', 'LOGO', 'NETFILTER', 'PCCARD', 'RFKILL', 'SECURITY', 'SOUND', 'SUSPEND', 'VIRTUALIZATION', 'WIRELESS', 'WLAN']:
            more_work = sset(sym, 0) or more_work

        # Following generic actions should done only on visible TRISTATE symbols
        if sym.type == TRISTATE and sym.visibility > 0:

            # Build all available net/ethernet drivers
            if True in [ ('drivers/net/ethernet' in node.filename) for node in sym.nodes ]:
                if sym.assignable and 1 in sym.assignable:
                    more_work = sset(sym, 1) or more_work

        # Following tunings are not mandatory
        # Try at each pass but don't create an extra pass if nothing else as asked for
        # To "do" this, don't set more_work var

        # If --debug passed as arg, make kernel aware of virtual drivers (used for testing eficast on qemu/kvm)
        if debug and sym.name in ['VIRTIO_PCI', 'VIRTIO_MMIO', 'VIRTIO_NET', 'VIRTIO_BLK', 'SCSI_VIRTIO']:
            sset(sym, 1)
            #sset(sym)

        # Compress everything with XZ if available (slower, smaller)
        if support_xz:
            if sym.name in ['KERNEL_XZ', 'RD_XZ', 'INITRAMFS_COMPRESSION_XZ']:
                sset(sym)
            if sym.name in ['RD_GZIP', 'RD_BZIP2', 'RD_LZMA', 'RD_LZO', 'RD_LZ4']:
                sset(sym, 0)
            if sym.name == 'INITRAMFS_COMPRESSION':
                sset(sym, '.xz')

	# Enable beeps (pcspkr) and .config to be readable from /proc/config.gz
        if sym.name in ['IKCONFIG','IKCONFIG_PROC', 'INPUT_PCSPKR']:
            sset(sym)

        # Try to get rid of some useless features in EFIcast context
        if sym.name in ['IP_ADVANCED_ROUTER']:
            sset(sym, 0)

# Write .config even if some symbols are unset
msg = kconf.write_config(standard_config_filename())
print(msg)

res = 0
if not isinstance(msg, str) or not msg.startswith('Configuration saved'):
    res = 2

if i == passes:
    print('ERROR : can\'t set some of kernel config symbols after %i passes'%passes)
    res = 1
sys.exit(res)

EOT
chmod +x "$WORKDIR/kernel/scripts/Kconfiglib/customize.py"

# Kernel prepare + make tools #
###############################
if [ ! -s "$WORKDIR/kernel/.config" ]
then	pushd "$WORKDIR/kernel"
	make defconfig
	if [ "x$DEVEL_MODE" == "xy" ]
	then extra="SCRIPT_ARG=--debug"
	else extra=""
	fi
	make scriptconfig SCRIPT=scripts/Kconfiglib/customize.py $extra || ( mv .config .config-broken; exit 3 )
	popd
fi

if [ "x$INCLUDE_PERF" == "xy" -a ! -s "$WORKDIR/kernel/tools/perf/perf" ]
then	pushd "$WORKDIR/kernel"
	# Workaround : linux-3.16.57 (and others?) have make tools/perf break in some cases, ignore it
	make tools/perf || true
	popd
fi

# Initial Ram Disk building (embed in kernel) #
###############################################
if [ ! -s "$WORKDIR/initrd/etc/group" ]
then	mkdir -p "$WORKDIR/initrd/"{bin,dev,etc/rc.d,mnt/nfs,root,proc,root,sbin,sys,run/lock,run,tmp,usr/share/udhcpc,var/log}
	$ROOTCMD cp -a /dev/{null,console,tty1} "$WORKDIR/initrd/dev/"
	$ROOTCMD chmod 1777 "$WORKDIR/initrd/run/lock"
	touch "$WORKDIR/initrd/etc/fstab"
	ln -s "/proc/mounts" "$WORKDIR/initrd/etc/mtab"
	ln -s "../run" "$WORKDIR/initrd/var/run"
	ln -s "../run/lock" "$WORKDIR/initrd/var/lock"
	echo 'root::0:0:root:/root:/bin/sh' > "$WORKDIR/initrd/etc/passwd"
	echo 'root:x:0:' > "$WORKDIR/initrd/etc/group"
fi

# XXX workaround, kernel makefile's cpio preseves everything. Consider using fakeroot
$ROOTCMD chown -R $USER: "$WORKDIR/initrd"

[ -s "$DLDIR/busybox" ] || $WGET -O "$DLDIR/busybox" "$BUSYBOX_BIN_URL"
cp "$DLDIR/busybox" "$WORKDIR/initrd/bin/busybox"
chmod 0755 "$WORKDIR/initrd/bin/busybox"
ln -sf /bin/busybox "$WORKDIR/initrd/init"

cp -a /etc/localtime "$WORKDIR/initrd/etc/"

if [ ! -s "$WORKDIR/initrd/etc/keys.bmap" ]
then
	$ROOTCMD dumpkeys | $ROOTCMD loadkeys -ub > "$WORKDIR/initrd/etc/keys.bmap"
fi


if [ ! -s "$WORKDIR/initrd/usr/bin/rsync" ]
then	(
		set +x
		PATH="$WORKDIR/kernel/tools/perf:/usr/sbin:/usr/bin:/sbin:/bin"
		# Diagnostic tools
		mkchroot "$WORKDIR/initrd" dmidecode iftop iostat lshw lspci lsblk lsusb mpstat iperf
		# Console tools and manpages display
		mkchroot "$WORKDIR/initrd" tput setterm strace groff nroff troff grotty gtbl tmux bc pv figlet beep
		# Filesystem tools
		mkchroot "$WORKDIR/initrd" mkfs mke2fs /sbin/mkfs.ext* mkntfs mkfs.ntfs mkfs.fat mkexfatfs mkfs.exfat mkfs
		mkchroot "$WORKDIR/initrd" ntfs-3g mount.ntfs mount.fuse mount.exfat-fuse mount.exfat
		mkchroot "$WORKDIR/initrd" /sbin/ntfs* /bin/ntfs*
		# Network tools
		mkchroot "$WORKDIR/initrd" mii-tool ethtool
		# Disk tools
		mkchroot "$WORKDIR/initrd" blkdiscard fdisk gdisk sfdisk sgdisk
		# Cloning tools
		mkchroot "$WORKDIR/initrd" /usr/sbin/partclone* efibootmgr pigz scp rsync
		# Some dyn-loaded libraries (ldd will not display them)
		mkchroot "$WORKDIR/initrd" /lib/x86_64-linux-gnu/libusb-1.0.so.0 /lib/x86_64-linux-gnu/libusb-1.0.so.0.1.0

		if [ "x$INCLUDE_TCPDUMP" == "xy" ]
		then	# tcpdump costs few Mb with libcrypto
			mkchroot "$WORKDIR/initrd" tcpdump
		fi
	)
fi

# Some needed data files
if [ ! -s "$WORKDIR/initrd/usr/share/figlet/standard.flf" ]
then
	cp -ra /lib/terminfo "$WORKDIR"/initrd/lib/
	mkdir -p "$WORKDIR"/initrd/usr/lib/locale "$WORKDIR"/initrd/usr/share/figlet
	cp -ra /usr/lib/locale/C.UTF-8 "$WORKDIR"/initrd/usr/lib/locale/
	cp -a /usr/share/figlet/{standard,mono12}* "$WORKDIR"/initrd/usr/share/figlet/
fi

# Perf tool : skip copy and deps if compilation has failed or was skipped
p="$WORKDIR/kernel/tools/perf/perf"
if [ -s "$p" ]
then	(
		cp -a "$p" "$WORKDIR/initrd/sbin/"
		set +x
		mkchroot "$WORKDIR/initrd" $(ldd "$p" | egrep -o '/.* ')
	)
fi

if [ ! -f "$WORKDIR/initrd/etc/groff/man.local" ]
then	mkdir -p "$WORKDIR"/initrd/usr/man/man{1,6,8} "$WORKDIR"/initrd/usr/share/groff/current/font
	mkdir -p "$WORKDIR"/initrd/etc/groff/

	cp -a /usr/share/man/man1/{beep,iostat,lshw,mpstat,setterm,bc,pv,strace,tmux,pigz}* "$WORKDIR/initrd/usr/man/man1/"
	cp -a /usr/share/man/man6/figlet* "$WORKDIR/initrd/usr/man/man6/"
	cp -a /usr/share/man/man8/{dmidecode,iftop,lspci,lsblk,lsusb,partclone,efibootmgr,mkfs}* "$WORKDIR/initrd/usr/man/man8/"
	cp -a /usr/share/man/man8/{ntfs,mkntfs,mkexfatfs,mount.ntfs,mount.fuse,mount.exfat-fuse}* "$WORKDIR/initrd/usr/man/man8/"
	cp -a /usr/share/man/man8/{mount.exfat,fdisk,gdisk,sfdisk,sgdisk,tcpdump,mii-tool,ethtool}* "$WORKDIR/initrd/usr/man/man8/"

	cp -ra /usr/share/groff/current/font/devascii "$WORKDIR/initrd/usr/share/groff/current/font/"
	cp -ra /usr/share/groff/current/tmac "$WORKDIR/initrd/usr/share/groff/current/"
	cp -a /etc/groff/man.local "$WORKDIR/initrd/etc/groff/"
fi

if [ ! -s "$WORKDIR/initrd/usr/share/misc/pci.ids" ]
then	[ -s "$DLDIR/pci.ids" ] || $WGET -O "$DLDIR/pci.ids" "$PCI_IDS_URL"
	[ -s "$DLDIR/usb.ids" ] || $WGET -O "$DLDIR/usb.ids" "$USB_IDS_URL"
	mkdir -p "$WORKDIR/initrd/var/lib/usbutils" "$WORKDIR/initrd/usr/share/misc"
	cp "$DLDIR/usb.ids" "$WORKDIR/initrd/var/lib/usbutils/"
	cp "$DLDIR/pci.ids" "$WORKDIR/initrd/usr/share/misc/"
fi

if [ ! -d "$WORKDIR/firmware" ]
then	[ -s "$DLDIR/nic-firmware.deb" ] || $WGET -O "$DLDIR/nic-firmware.deb" "$NIC_FIRMWARE_URL"
	mkdir -p "$WORKDIR/firmware"
	dpkg -x "$DLDIR/nic-firmware.deb" "$WORKDIR/firmware/"
fi
if [ ! -d "$WORKDIR/initrd/lib/firmware/rtl_nic" ]
then	mkdir -p "$WORKDIR/initrd/lib/firmware"
	cp -r "$WORKDIR/firmware/lib/firmware/rtl_nic" "$WORKDIR/initrd/lib/firmware/"
fi

echo $VERSION > "$WORKDIR/initrd/etc/eficast_version"

cat > "$WORKDIR/initrd/etc/rc.d/funcs" <<"EOF"
# echo_color <foreground_color> <background_color> [prefix_string] <message>
echo_color() {
	setterm --foreground "$1" --background "$2" --bold on
	echo -n "$3"
	setterm --foreground white --background black --bold off
	[ "x$4" == "xversion" ] && echo " (eficast v"$(cat /etc/eficast_version)")" || echo
}

# no args, print colored message, wait 10 sec and reboot
eficast_end() {
	echo -e "\e]2;eficast_end\007"	# Term title (tmux), intentionnal carriage return
	( sfx_success ; touch /run/nosound ) &
	message success
	[ -r /run/eficast_end ] && action=$(cat /run/eficast_end)
	case $action in
		poweroff) 	read -t10 -p 'Ctrl+C to have a shell, Enter to skip wait time for poweroff...'
				wait # if beep command is killed from sfx_success, internal speaker can beep for a long anoying time
				poweroff
				;;
		reboot)		read -t10 -p 'Ctrl+C to have a shell, Enter to skip wait time for reboot...'
				wait # if beep command is killed from sfx_success, internal speaker can beep for a long anoying time
				reboot
				;;
		*)		echo "Dropping a shell (consider putting poweroff or reboot in /run/eficast_end)"
				PS1='\h:\w# ' HOME='/root/' /bin/busybox sh
				;;
	esac
}

# note : rcS rescue_shell is slighly different because most of the env is not ready in rcS
rescue_shell() {
	echo -e "\e]2;rescue_shell\007"	# Term title (tmux), intentionnal carriage return
	message rescue
	echo_color white red "Something went wrong. Dropping to a shell." version
	( sfx_failure ; touch /run/nosound ) &
	PS1='\h:\w# ' HOME='/root/' busybox sh
}

machine_info() {
	setterm -bold on
	grep -F MemTotal: /proc/meminfo
	for k in system-manufacturer system-product-name \
		baseboard-manufacturer baseboard-product-name \
		bios-version bios-release-date
	do
		echo $k: $(dmidecode -s $k)
	done
	lspci -nn | cut -d' ' -f2- | sed -ne 's/^Ethernet[^:]*/network-card/p'
	ip -o l | sed -ne 's/[0-9]*: \([^:]*\):[^\\]*\\\s*link\/ether\s/network-mac-\1: /p'
	lsblk -dnl --output TRAN,PATH,SIZE,RO,TYPE,MODEL,REV | sort | sed 's/^/disk: /'
	#lsusb 2>/dev/null | grep -vE hub$ | cut -d: -f2- | sed 's/^ ID/usb-device:/'
	setterm -bold off
}

network_up() {
	echo ip link set dev lo up
	ip link set dev lo up

	ip -oneline link | grep DOWN | cut -d: -f2 | grep -v sit | grep -v lo | while read iface
	do
		# BugFix Intel 217LM + WakeOnLan + dual-boot Windows 10
		# After Windows hibernation or fast boot powerdown, link goes up but no network packets go out of the chip
		d=/sys/class/net/$iface/device
		if [ 0x153a = $(cat $d/device) ]
		then	echo 1 \> $d/reset
			echo 1 > $d/reset
		fi
		# BugFix Realtek 8168 + dual-boot Windows 10
		# After reboot to linux, linux kernel don't load firmware, link never came up
		if [ 0x8168 = $(cat $d/device) ]
		then	echo 1 \> $d/reset
			echo 1 > $d/reset
		fi

		echo ip link set dev $iface up
		ip link set dev $iface up
	done
	while ! ip -oneline link | grep LOWER_UP | cut -d: -f2 | grep -v sit | grep -v lo
	do
		sleep 1
	done
}

network_conf() {
	pidof udhcpc >/dev/null && killall udhcpc # Reap background udhcpc in case of rc2 retry
	ip -oneline link | grep LOWER_UP | cut -d: -f2 | grep -v sit | grep -v lo | while read iface
	do
		udhcpc -b $iface -t 15
	done
}

network_show() {
	setterm -bold on
	ip -o addr show | sed -ne 's/[0-9]*:\s*\(\S*\)\s*inet6*\s\(\S*\)\s.*$/\1: \2/p'
	setterm -bold off
}

notes() {
  echo 'print "n=CCDEEFFGAABB\na=nsnbnnsnbnbn\n(\n"; for (d=21;d<109;d++) {
      scale=20; f=440*e((d-69)/12*l(2)); fr=f+0.5; scale=0; fr=fr/1; o=d/12-1; t=d%12;
      print "echo ${n:",t,":1}${a:",t,":1}",o,"=",fr,"\n"
    }; print ") | tr -d n"' | bc -l | busybox sh
}

sound() {
	[ -f /run/nosound ] && return 0
	mode=$1; dur=$2; shift 2; args="-l0"
	while [ -n "$1" ]; do
		case $mode in
			1) args="$args -n -f$1 -l$dur";;
			2) args="$args -n -f$1 -l$(($2*dur))";;
			3) args="$args -n -f$1 -l$(($2*dur)) -D$(($3*dur))";;
		esac
		shift $mode
	done
	beep $args
}

sfx_question() { : ; }
sfx_success() { sound 2 50 $E7 2 $C7 3; }
sfx_failure() { sound 1 600 $G4 ; }
sfx_starting() { : ; }

eval $(notes)
EOF

# All shell spawned here must be login-shell, will load /etc/profile
cat > "$WORKDIR/initrd/etc/inittab" <<"EOF"
# Custom init scripts
::sysinit:/etc/rc.d/rcS
# Standard things follow
::ctrlaltdel:/sbin/reboot -f
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
tty1::respawn:/bin/sh -lc /etc/rc.d/rc2-or-rescue
tty2::askfirst:/bin/sh -l
tty3::askfirst:/bin/sh -l
EOF
cat > "$WORKDIR/initrd/etc/profile" <<"EOF"
export LC_ALL=C.UTF-8
export TERM=linux # busybox on qemu set vt102, then tmux segfaults
# https://git.busybox.net/busybox/tree/init/init.c, search for VT_OPENQRY
# happens if qemu pass "-append console=ttyS0" or similar to the kernel
EOF

add_initrd_script etc/rc.d/rcS <<"EOF"
echo -e '\e[32m/etc/rc.d/rcS script will run on /dev/console now\e[0m'
echo -e '\e[37;43m\e[1m'----- rcS script started -----'\e[0m'	# Hint for user about boot steps if its hangs

# Declare some funcs to have a tidy output with trace mode
# note : rc.d/funcs has a rescue_shell() that is slighly different (the environment is not fully intialized here)
rescue_shell() {
	/bin/busybox echo -e '\e[37;41m\e[1m'Something went wrong. Dropping to a shell.'\e[0m'
	/bin/busybox beep
	PS1='\h:\w# ' busybox setsid busybox cttyhack busybox sh
	/bin/busybox sync; busybox umount /dev/pts /dev /sys /proc
	/bin/busybox reboot -f
}

mount_pseudofilesystems() {
	# Mount pseudo-filesystems
	mount -t proc none /proc || return $?
	mount -t sysfs none /sys || return $?
	mount -t devtmpfs -o size=1m none /dev || return $?
	mkdir /dev/pts || return $?
	mount -t devpts none /dev/pts || return $?
	ln -s /proc/self/fd/2 /dev/stderr
	return 0
}

# TODO Kernel 5.x disable UEVENT_HELPER by default, it may need some clean alternative
coldplugging() {
	echo 4 > /proc/sys/kernel/printk
	for d in /sys/bus/*/devices/*
	do
		[ -d "$d" ] && cd "$d" && [ -r driver ] || echo add > uevent
	done
	sleep 3
	cut -f4 /proc/sys/kernel/printk > /proc/sys/kernel/printk
}

# Trace execution
set -v
/bin/busybox --install -s||rescue_shell	# Setup busybox symlinks for all applets
mount_pseudofilesystems || rescue_shell	# Setup /dev, /proc, /sys and so
klogd; syslogd				# Start logging in /var/log/messages
mount -o remount -o size=80% /		# Allow using most of RAM for rootfs
coldplugging				# Load modules for cold-plugged peripherials
loadkmap < /etc/keys.bmap		# Load keyboard layout
set +v
# Hint users about boot steps to help them if it hangs
echo -e '\e[32m/etc/rc.d/rc2-or-rescue script will run on /dev/tty1 now\e[0m'
echo -e '\e[37;43m\e[1m----- rcS script ended -----\e[0m'
EOF
add_initrd_script etc/rc.d/rc2-or-rescue <<"EOF"
. /etc/rc.d/funcs		# Load helper functions
if [ -f /run/rescue ]
then	sleep 1 # reboot command in eficast_end kill all processes and could trigger rescue mode
	rm /run/rescue
	echo_color red black "/etc/rc.d/rc2 dead. Spawning rescue_shell on /dev/tty1 now"
	rescue_shell
else	touch /run/rescue
	echo_color green black "/etc/rc.d/rc2 script will run on /dev/tty1 now"
	/etc/rc.d/rc2
fi
EOF
add_initrd_script etc/rc.d/rc2 <<"EOF"
. /etc/rc.d/funcs		# Load helper functions
echo_color white yellow '----- rc2 script started -----'
message boot-ok
setterm -blank 60		# screen sleep mode after 60 minutes
set -v				# Trace execution
network_up
network_conf
network_show
machine_info
set -e # Go in rescue shell if any further step goes wrong
/etc/rc.d/initrd-autorun
eficast_end
EOF
add_initrd_script etc/rc.d/initrd-autorun <<"EOF"
echo -ne "\e]2;$0\007"		# Term title (tmux)
. /etc/rc.d/funcs		# Load helper functions
echo_color white yellow '----- initrd-autorun script started -----'
echo -e '\e[32m/etc/rc.d/nfs-mount script will run now\e[0m'
busybox sh -v /etc/rc.d/nfs-mount
if [ $? -ne 0 ]
then	echo_color white red "----- NFS server : not mounted ------"
	echo_color green black "Check for failure above. Exiting rescue shell will retry everything"
	exit 1
fi
if ! [ -x /mnt/nfs/nfs-autorun.sh ]
then	echo_color white red "----- NFS server : missing script ------"
	echo_color green black "/mnt/nfs/nfs-autorun.sh : should be present, readable and executable"
	echo ls -l /mnt/nfs
	ls -l /mnt/nfs
	exit 2
fi
echo_color white green "----- NFS server ready ------"
read -t10 -p 'Ctrl+C to have a shell, Enter to skip wait time...'
message nfs-ok
echo -ne "\e]2;nfs-autorun.sh\007" # Term title (tmux)
cd /mnt/nfs
# don't use source here, exit in inner script will skip cleanup routine (and rescue_shell)
./nfs-autorun.sh
EOF
add_initrd_script etc/rc.d/nfs-mount <<EOF
mount | grep -q /mnt/nfs && umount /mnt/nfs
$NFS_MOUNT_CMDLINE
EOF
add_initrd_script sbin/hotplug <<"EOF"
# Be verbose for PCI cards, be silent for the rest (many many things), log everything
# No support for change or remove events
# Could run very early, before busybox links installation
if [ "x$ACTION" = "xadd" ]
then	if [ -n "$PCI_ID" ]
	then	if [ -n "$MODALIAS" ]
		then	/bin/busybox modprobe -v $MODALIAS 2>&1 | /bin/busybox awk -vT="$0: DEVPATH=$DEVPATH PCI_ID==$PCI_ID " '{ print T $0 }' | /bin/busybox tee -a /var/log/hotplug-pci.log
		else	echo -e '\e[37;43m\e[1m'$0: PCI_ID==$PCI_ID, no MODALIAS found'\e[0m'
			echo $0: PCI_ID==$PCI_ID, no MODALIAS found >> /var/log/hotplug-pci.log
		fi

	else	if [ -n "$MODALIAS" ]
		then	/bin/busybox modprobe -v $MODALIAS 2>&1 | /bin/busybox awk -vT="$0: DEVPATH=$DEVPATH MODALIAS==$MODALIAS " '{ print T $0 }' >> /var/log/hotplug-non-pci.log
		else	echo "$0: DEVPATH=$DEVPATH no MODALIAS found" >> /var/log/hotplug-non-pci.log
		fi
	fi
else	echo "$0: DEVPATH=$DEVPATH : no support for '$ACTION'" >> /var/log/hotplug-unsupported.log
fi > /dev/console 2>&1
EOF
add_initrd_script bin/iftop-watch <<"EOF"
echo -ne "\e]2;$*\007" # Term title (tmux)
iftop -nNl
EOF
add_initrd_script bin/iostat-watch <<"EOF"
# Emulates watch command with iostat (filtered info, minimal height)
echo -ne "\e]2;$*\007" # Term title (tmux)
iostat -cdmz 1 | awk '$1=="avg-cpu:"{system("clear")} length($0)>0{print}'
EOF
add_initrd_script bin/mpstat-watch <<"EOF"
# Emulates watch command with mpstat
echo -ne "\e]2;$*\007" # Term title (tmux)
while true; do
	mpstat -I SCPU | grep -vE '^(Linux.*|)$'
	sleep 1
done
EOF
add_initrd_script bin/pigz-watch <<"EOF"
# Emulate watch command using pipeview (pv) for a running pigz
echo -ne "\e]2;$*\007" # Term title (tmux)
while true; do
	pid=$(pidof -s pigz)
	[ -n "$pid" ] && pv -F "%N %b %T %t %r %a %p %I" -d $pid
	sleep 1
done
EOF
add_initrd_script bin/pstree-watch <<"EOF"
echo -ne "\e]2;$*\007" # Term title (tmux)
while true; do
	watch -t "pstree"
	sleep 1
done
EOF
add_initrd_script bin/lsblk-watch <<"EOF"
echo -ne "\e]2;$*\007" # Term title (tmux)
watch -t lsblk -o NAME,SIZE,MODEL,SERIAL,FSTYPE,LABEL,MOUNTPOINT
EOF
add_initrd_script usr/share/udhcpc/default.script <<"EOF"
#!/bin/sh
case $1 in
	bound)
		# Configure interface and default gateway
		busybox ifconfig $interface ${mtu:+mtu $mtu} $ip netmask $subnet ${broadcast:+broadcast $broadcast}
		busybox ip -4 route add default via $router dev $interface

		# Update resolver configuration file
		[ -n "$domain" ] && R="domain $domain" || R=""
		for i in $dns; do
			R="$R\nnameserver $i"
		done
		echo -e "$R" > /etc/resolv.conf

		# Update in-kernel-memory hostname
		[ -n "$hostname" ] && hostname $hostname
	;;
	renew | deconf) echo "no action taken: $1: $interface" >&2 ;;
	leasefail | nak) echo "configuration failed: $1: $message" >&2 ;;
esac
EOF
add_initrd_script bin/message <<"EOF"
# Output some center ASCII-art text, one line per argument
printf '\e]2;'"$*"'\e\\' # Term title (tmux)
CMDFIG="figlet -t -f mono12"
while [ -n "$1" ]; do
	termwidth=$(tput cols)
	textlen=$(echo $1 | $CMDFIG | head -n1 | wc -c)
	offset=$(( (termwidth-textlen)/2 ))
	echo $1 | $CMDFIG | awk "{printf \"%${offset}s\"; print}"
	shift
done
EOF

# Kernel build (with embed initramfs) #
#######################################
pushd "$WORKDIR/kernel"
nproc=$(nproc --all)
nproc=${nproc:-4}
# This make will produce a kernel with embed initrd without modules
make -j $((nproc+1))
# This will complete the inird tree with modules
[ -d ../initrd/lib/modules ] && rm -r ../initrd/lib/modules
INSTALL_MOD_PATH=../initrd/ make modules_install
# XXX workaround, kernel makefile cpio preseves everything
$ROOTCMD chown -R root: ../initrd
# XXX Workaround : some kernel releases forget to update embed initramfs in certain cases
[ -f usr/initramfs_data.cpio.gz ] && rm usr/initramfs_data.cpio.gz
$ROOTCMD chmod -R go+rX ../initrd/lib/modules
# This produce the final image
make
popd

# Copy / run result EFI file #
##############################
# Workaround : direct kernel boot with libvirt/virt-manager do a chown on BOOTX64.EFI and cp won't overwrite
[ -f "$OUTDIR/BOOTX64.EFI" ] && rm -f "$OUTDIR/BOOTX64.EFI"
cp "$WORKDIR/kernel/arch/x86/boot/bzImage" "$OUTDIR/BOOTX64.EFI"

# Prepare an USB bootable disk if $OUTUSB is set and is has at least 1 primary partition
if [ -n "$OUTUSB" -a -b "${OUTUSB}1" ]
then	[ -d "$WORKDIR/mountpoint" ] || mkdir "$WORKDIR/mountpoint"
	mount | grep -E "^${OUTUSB}1" -q && $ROOTCMD umount "${OUTUSB}1"
	if [ "x$LEGACY" == "xy" ]
	then	$ROOTCMD install-mbr -f "${OUTUSB}"
		$ROOTCMD sfdisk --activate "$OUTUSB" 1
		sleep 1 # XXX do a proper udev wait
		$ROOTCMD mount "${OUTUSB}1" "$WORKDIR/mountpoint"
		$ROOTCMD tee "$WORKDIR/mountpoint/syslinux.cfg" > /dev/null <<"EOT"
default eficast
label eficast
 kernel /EFI/BOOT/BOOTX64.EFI
 append from=syslinux
label debug
 kernel /EFI/BOOT/BOOTX64.EFI
 append debug ignore_loglevel bootmem_debug apic=debug show_lapic=all acpi.debug_layer=0xffffffff acpi.debug_level=0xffffffff debugpat initcall_debug pnp.debug=1 sched_debug from=syslinux
label noacpi
 kernel /EFI/BOOT/BOOTX64.EFI
 append noacpi from=syslinux
timeout 10
prompt 1
EOT
	else	$ROOTCMD mount "${OUTUSB}1" "$WORKDIR/mountpoint"
	fi
	$ROOTCMD mkdir -p "$WORKDIR/mountpoint/EFI/BOOT"
	$ROOTCMD cp "$OUTDIR/BOOTX64.EFI" "$WORKDIR/mountpoint/EFI/BOOT"
	$ROOTCMD umount "$WORKDIR/mountpoint"
fi

if [ "x$DEVEL_MODE" == "xy" ]
then	qemu-system-x86_64 -M q35 -m 256 -kernel "$OUTDIR/BOOTX64.EFI" -enable-kvm
	# -serial stdio -append "console=ttyAMA0 console=ttyS0" hangs boot on bad serial driver
fi

echo "$0 successful end of execution"