#!/bin/sh
#
# This script creates a new disk image file (as a sparse file),
# partitions it with a single hurd type ext2fs file system, and
# primes it with legacy bios bootloader.
#
# Needs:
# 1) loop module with max_parts > 0
# 2) sh built-in: echo eval exec exit trap
# 3) packages:
#      crosshurd: crosshurd
#      mount: setup mount umount
#      e2fsprogs: e2fs
#      sed: sed
#      fdisk: sfdisk
#      sudo: sudo (for non-root)
#      fusefile: fusefile
#+++ add dependencies into debian/control
#
######################################################################

#### Setup defaults
# $DISK holds the name for the image file (base.img)
DISK=base.img

# $SIZE is the disk image file size in gigabytes
SIZE=40

# $NAME is the chosen hostname to stamp into the built filesystem
NAME=hurd-test

# $IP is the ipv4 address to stamp into the built filsystem
IP=192.168.240.2/24

# $GW is the ipv4 address used by the host tap. See $NET below
GW=192.168.240.1

# $BOOTLOADER is either grub or extlinux
BOOTLOADER=grub

# $CPU is the crosshurd cpu type, either i386 or x86_64
CPU=x86_64

# $MB is the multiboot definition (in grub.conf format)
MB=/usr/share/crosshurd-image/servers.boot

usage() {
    	    cat <<EOF >&2
Usage: $0 [ option ]*
  --disk=filename   - the name for the image file ($DISK)
  --size=NN         - the disk image file size in gigabytes (${SIZE})
  --name=VM-name    - the VM hostname ($NAME)
  --ip=address/bits - the VM ipv4 address and network bits ($IP)
  --gw=address      - the VM ipv4 gateway address ($GW)
  --cpu=cputype	    - the CPU type to install for ($CPU) 
  --extlinux        - use extlinux boot equipment ($BOOTLOADER)
  --mb=pathname     - use multiboot conf ($MB)
EOF
}

# Override defaults from the command line
while [ "$1" ] ; do
    case "$1" in
	--disk=*) DISK="${1#--disk=}" ;;
	--size=*) SIZE="${1#--size=}" ;;
	--name=*) NAME="${1#--name=}" ;;
	--ip=*) IP="${1#--ip=}" ;;
	--gw=*) GW="${1#--gw=}" ;;
	--cpu=*) CPU="${1#--cpu=}" ;;
	--extlinux) BOOTLOADER=extlinux ;;
	--mb=*) MB="${1#--mb=}" ;;
	--help) usage; exit 0 ;;
	*) echo "Unrecognized: $1" ; usage ; exit 1 ;;
    esac
    shift
done

## Utility function to print to stderr and exit 1
die() { echo "$*" >&2 ; exit 1 ; }

## Setup for undoing mounts etc on exit, with a utility function to
## extends the undoing/restore actions.
dotrap() { eval "$TRAP" ; }
trap "dotrap" 0 2

## Utility function to append commands to the trap restore action
restore() {
    TRAP="$*
$TRAP"
}

## Utility function to write heredoc into a target file after ensuring
## that the targeted directory exists
writefile() { mkdir -p "${1%/*}" ; cat > "$1" ; }

## Utility function top copy files into a target directory after
## ensuring that the directory exists
cpfiles() { mkdir -p "$1" ; cp -t "$@" ; }

## Hereafter the script should exit on any error, and print its
## commands on stderr
set -e
set -x

## Refuse to change an existing disk image file
[ -e $DISK ] && die "** ERROR: pathname $DISK exists"

## Create the disk image file; a sparse file of $SIZE GiB
runuser -u ${SUDO_USER:-root} -- \
	dd if=/dev/zero of=$DISK bs=1G count=0 seek=$SIZE
restore rm $DISK

## Create the partition tabls with a single, bootable, Linux
## partition, starting 1 MiB into the image.
sfdisk -f $DISK <<EOF
label: dos

2048 - L *
EOF

## Setup a loop device for the disk and its partition. Note that this
## requires the loop module's "max_part" parameter be non-zero.
LOOP="$(losetup -f --show $DISK)"
PART="${LOOP}p1"
restore losetup -d $LOOP

## Create a root filesystem; an "ext2 -o hurd" filesystem
mke2fs -o hurd -L GNUHurd $PART

## Create a temporary mount point in the working directory
MNT="$(mktemp -d hurdXXXXXX)"
restore rmdir $MNT

## Mount the root filesystem on the temporary mount point
mount $PART $MNT
restore umount $MNT

## Prime the root filesystem using crosshurd. For initial content, see
## /etc/crosshurd/packages/gnu and /etc/crosshurd/packages/common
env http_proxy=$http_proxy crosshurd -t $MNT -s gnu -c $CPU -d unstable

## Utility function to determine drive type, which is hd0 for CPU=i386 and
## wd0 for CPU=x86_64 ($MB has wd0)
case "$CPU" in
    i386) drivefix() { sed '1s|:wd0|:hd0|' ; } ;;
    *)    drivefix() { sed '' ; } ;;
esac

case "$BOOTLOADER" in
    grub)
	## Utility function to process the "$MB" file into
	## the module argument line for grub legacy multiboot
	mbootargs() {
	    sed '/^\s*#/d;/\S/s/$/\\/'
	}
	
	#=============================================================
	writefile $MNT/boot/grub/grub.cfg <<EOF
# Manual grub configuration for GNU Hurd
set timeout_style=hidden
set timeout=0
serial --unit=0 --speed=115200
terminal_input serial; terminal_output serial

insmod part_msdos
insmod ext2
insmod multiboot

menuentry 'Debian GNU/Linux GNU/Hurd amd64' --class debian --class gnu --class os 'gnuhurd-simple-8e02c53f-47c0-4479-a23f-c446d7fabce8' {

    echo multiboot Loading GNU Hurd amd64 ...

    multiboot $(drivefix < "$MB" | mbootargs)

    echo HERE WE GO
}
EOF
	#==============================================================

	grub-install -s \
		     --target i386-pc \
		     --boot-directory=$MNT/boot \
		     --disk-module=native \
		     --force-file-id \
		     --recheck \
		     $LOOP
	;;

    extlinux)
	## Utility function to process the "$MB" file into
	## the module argument line for syslimux' mboot.
	mbootargs() {
	    sed '/#/d;/^\s*$/d;s/module/ ---/' | \
		tr -d "'\012" | sed 's/\s\+/ /g'
	}

	## Preparing the boot equipment
	### a. install all syslinux modules into target /boot/syslinux/
	cpfiles $MNT/boot/syslinux /usr/lib/syslinux/modules/bios/*

	#==============================================================
	### b. prepare extlinux configuration file, /boot/syslinux/syslinux.cfg
	writefile $MNT/boot/syslinux/syslinux.cfg <<EOF
DEFAULT hurd
LABEL hurd
  KERNEL mboot.c32
  APPEND $(drivefix < "$MB" | mbootargs)
EOF
	#==============================================================

	### c. install extlinux bootloader as sibling to the configuration file
	extlinux --install $MNT/boot/syslinux

	### d. stamp the disk image file with extlinux' logacy bios MBR patch
	dd if=/usr/lib/EXTLINUX/mbr.bin of=$DISK conv=notrunc

	### e. already done: mark the partition of /boot/syslinux as bootable
	;;
    *)
	die "*** Unknown BOOTLOADER $BOOTLOADER"
	;;
esac

## Setup hostname from $NAME, or ask for it if $NAME is empty
sed "1s|.*|$NAME|" -i $MNT/etc/hostname
sed "1s| [^\\s]*\$| $NAME|" -i $MNT/etc/hosts

## Prepare the ifupdown configuration for /dev/eth0
writefile $MNT/etc/network/interfaces.d/dev-eth0 <<EOF
auto /dev/eth0
iface /dev/eth0 inet static
    address $IP
    gateway $GW
EOF


set +x

echo "ALL DONE ($DISK)"

# Disable trap; run all but last undoings
trap "" 0
eval "$(echo -n "$TRAP" | sed '$d')"
