#!/bin/bash
# ---------------------------------------------------------------------------#
# script to make a bootable disk/usb stick from a compiled SMITUX
#   or to install directly on a machine...
#
# LICENSE  : LGPL
# AUTHOR   : Michael H.G. Schmidt
# EMAIL    : michael@schmidt2.de
# DATE     : 20211101
# ---------------------------------------------------------------------------#

#################### FUNCTIONS #####################
#
#

yesno () {

  unset YESNO
  while [ "@$YESNO" != "@y" -a "@$YESNO" != "@Y" -a "@$YESNO" != "@n" -a "@$YESNO" != "@N" ] 
  do
    echo -e "$1 (Yy/Nn) \c"
    read YESNO
  done
  [ "$YESNO" = "y" -o "$YESNO" = "Y" ] && return 0
  [ "$YESNO" = "n" -o "$YESNO" = "N" ] && return 1

}

ask () {
  unset ASK
  answer_not_ok=1

  [ $# -lt 1 ] && {
    echo -e "ERROR in function \"ask() \"- need at least 1 Argument !"
    # $1 = Questionstring
    # $2 = Defaultvalue
    exit 1
  }

  while [ $answer_not_ok -eq 1 ]
  do
    echo -e "$1 [$2] ? \c"
    read ASK
    # Case 1: we have an answer
    [ "@$ASK" != "@" ] && answer_not_ok=0
    # Case 2: we have no answer but we have a default ...
    [ "@$ASK" = "@" -a $# -ge 2 ] && {
      answer_not_ok=0
      ASK=$2
    }
  done

}

################ init ##################

# messages in english please
export LC_MESSAGES=C

# user must be "root" ...
if [ "@`id -un 2>/dev/null`" != "@root" ] ; then
  echo "ERROR: this script must be run as root (use sudo -E) !"
  exit 1
fi

# check commandline ...
if [ $# -lt 2 -o $# -gt 3 ] ; then
  cat << EOF

usage: `basename $0` <image> <drive1> [drive2]

installs a compiled STX system to a disk and makes it bootable

   image: path to SMITUX image (must be of type tar.gz or tgz)
  drive1: physical path to boot disk 1
  drive2: physical path to boot disk 2 (optional mirror)

  WARNING: This script DELETES all content on the target disk !

EOF
  exit 1
fi

# name for imagefile ...
STX_IMAGE=$1

# mirroring off (default) ...
MIRROR=0

# get arguments (1 drive) ...
DRIVE1=$2

# get arguments (2 drives) ...
[ $# -eq 3 ] && {
  MIRROR=1
  DRIVE2=$3
}

# network interface name ...
STX_BRIDGE=br0

# interface config file ...
IFCONFIG="etc/sysconfig/ifconfig.${STX_BRIDGE}"

# set partition type to empty ...
PTYPE=""

# temporary mountpoint ...
MP=/tmp/ROOTFS.$$

# grub config ...
GRUBCFG=/boot/grub/grub.cfg

# fstab ...
FSTAB=/etc/fstab

# mdstat ...
MDSTAT=/proc/mdstat

# mirror names ...
SWAPMD=/dev/md0
ROOTMD=/dev/md1

# check for needed binaries ...
BINS='mount swapon dd parted bc fdisk partprobe mkswap mkfs tar chroot chpasswd mdadm'
for b in $BINS
do
  which $b 1>/dev/null 2>&1 || {
    echo "ERROR: binary [ $b ] NOT found !"
    exit 1
  }
done

# check for imagefile presence ...
[ ! -f $STX_IMAGE ] && {
  echo "ERROR: [ $STX_IMAGE ] NOT found !"
  exit 1
}

# no logging ...

############################################
################## MAIN ####################
############################################

for D in $DRIVE1 $DRIVE2 ; do

  # check for mounted filesystems on target drive ...
  mount -l | grep -i $D 1>/dev/null 2>&1 && {
    echo "ERROR: mounted filesystems found on [ $D ] !"
    exit 1
  }

  # check for running swap on target drive ...
  swapon -s | grep -i $D 1>/dev/null 2>&1 && {
    echo "ERROR: running swap found on [ $D ] !"
    exit 1
  }

  # is it a real device and does it exist ?
  [ ! -b $D ] && {
    echo "ERROR: DRIVE [ $D ] ist not a block device or does not exist !"
    exit 1
  }

done

# ask some questions (ip, hostname and so on ...)
ask "Hostname" "stx" ; MYHOSTNAME="$ASK"
ask "Domainname" "localdomain" ; DOMAINNAME="$ASK"
ask "Swapspace in GB" "1" ; SWAPSPACE="$ASK"
ask "ROOT filesystem in GB" "8" ; ROOTSIZE="$ASK"
ask "IP address" "192.168.89.40" ; IP="$ASK"
ask "Netmask" "24" ; NETMASK="$ASK"
ask "Gateway" "192.168.89.10" ; GATEWAY="$ASK"
ask "DNS" "8.8.8.8" ; DNS="$ASK"
cat <<-HERE
---------------------------------------------
HINT:for old machines choose "dos" partition!
HERE
while [ "@$PTYPE" != "@msdos" -a "@$PTYPE" != "@gpt" ] 
do
  ask "Partition TYPE (msdos OR gpt)" "msdos" ; PTYPE="$ASK"
done

cat <<-HERE
---------------------------------------------
Hostname            = $MYHOSTNAME
Domainname          = $DOMAINNAME
SWAP in GB          = $SWAPSPACE
ROOT filesystem[GB] = $ROOTSIZE
IP address          = $IP
Netmask             = $NETMASK
Gateway             = $GATEWAY
DNS                 = $DNS
Partition TYPE      = $PTYPE
---------------------------------------------
HERE

yesno "READY" || exit 1

echo "=============================="
echo "#### STARTING disk install ###"
echo "------------------------------"
echo

echo "INFO: stopping RAID devices ..."
mdadm --stop --scan || exit 1

for D in $DRIVE1 $DRIVE2 ; do
  # first: write zeroes to the first 10mb of the disk ...
  echo "INFO: writing zeroes to first 10MB on [ $D ] ..."
  dd if=/dev/zero of=$D bs=512 count=20480 || exit 1
done

if [ "$PTYPE" = "gpt" ] ; then
  # calculate begin of root filesystem and convert all values to MB ...
  BIOSPARTSIZE=64
  BIOSPARTSIZE=`echo "($BIOSPARTSIZE * 1.1) / 1" | bc 2>/dev/null`
  SWAPSPACE=`echo "(($SWAPSPACE * 1024) + $BIOSPARTSIZE) * 1.1 / 1" | bc 2>/dev/null`
  ROOTBEGIN=$SWAPSPACE
  ROOTSIZE=`echo "(($ROOTSIZE * 1024) * 1.1 / 1) + $ROOTBEGIN" | bc 2>/dev/null`

  # second: write a new label(GPT) and 3 partitions (BIOS,root,swap) ...
  for D in $DRIVE1 $DRIVE2 ; do
    echo -e "\nINFO: writing new label and partitions on drive [ $D ] ..."
    parted $D << EOF
unit compact
mklabel $PTYPE
mkpart primary 0% ${BIOSPARTSIZE}M
set 1 bios_grub on
mkpart primary ${BIOSPARTSIZE}M ${SWAPSPACE}M
mkpart primary ${ROOTBEGIN}M ${ROOTSIZE}M
mkpart primary ${ROOTSIZE}M 100%
set 3 boot on 
EOF

    if [ $MIRROR -eq 1 ] ; then
      # set type for partition 2 to dec 19 (GPT Linux RAID) ...
      # set type for partition 3 to dec 20 (GPT Linux RAID) ...
      # set type for partition 4 to dec 31 (GPT Linux LVM) ...
      fdisk $D << EOF
t
2
29
t
3
29
t
4
31
w
EOF
    else
      # set type for partition 2 to dec 19 (GPT Linux swap) ...
      # set type for partition 3 to dec 20 (GPT Linux filesystem) ...
      # set type for partition 4 to dec 31 (GPT Linux LVM) ...
      fdisk $D << EOF
t
2
19
t
3
20
t
4
31
w
EOF
    fi

  done

  # set devicepath for root and swap ...
  DRIVE1_SWAP=${DRIVE1}2 ; DRIVE1_ROOT=${DRIVE1}3
  # check for nvme disks (they have a different device naming) ...
  echo $DRIVE1 | grep -i "dev/nvme" 1>/dev/null 2>&1 && {
    DRIVE1_SWAP=${DRIVE1}p2; DRIVE1_ROOT=${DRIVE1}p3
  }

  # second drive ...
  if [ "@$DRIVE2" != "@" ] ; then
    DRIVE2_SWAP=${DRIVE2}2 ; DRIVE2_ROOT=${DRIVE2}3
    echo $DRIVE2 | grep -i "dev/nvme" 1>/dev/null 2>&1 && {
      DRIVE2_SWAP=${DRIVE2}p2; DRIVE2_ROOT=${DRIVE2}p3
    }
  fi

else

  # calculate begin of root filesystem and convert all values to MB...
  SWAPSPACE=`echo "($SWAPSPACE * 1024) * 1.1 / 1" | bc 2>/dev/null`
  ROOTBEGIN=$SWAPSPACE
  ROOTSIZE=`echo "(($ROOTSIZE * 1024) * 1.1 / 1) + $ROOTBEGIN" | bc 2>/dev/null`

  # second: write a new label(DOS) and 3 partitions (swap,root,lvm)...
  for D in $DRIVE1 $DRIVE2 ; do
    echo -e "\nINFO: writing new label and partitions on drive [ $D ]..."
    parted $D << EOF
unit compact
mklabel $PTYPE
mkpart primary 0% ${SWAPSPACE}M
mkpart primary ${ROOTBEGIN}M ${ROOTSIZE}M
mkpart primary ${ROOTSIZE}M 100%
set 2 boot on
EOF

    if [ $MIRROR -eq 1 ] ; then
      # set type for partition 1 to hex fd (Linux RAID autodetect) ...
      # set type for partition 2 to hex fd (Linux RAID autodetect) ...
      # set type for partition 3 to hex 8e (Linux LVM) ...
      fdisk $D << EOF
t
1
fd
t
2
fd
t
3
8e
w
EOF
    else
      # set type for partition 1 to hex 82 (Linux swap) ...
      # set type for partition 2 to hex 83 (Linux filesystem) ...
      # set type for partition 3 to hex 8e (Linux LVM) ...
      fdisk $D << EOF
t
1
82
t
2
83
t
3
8e
w
EOF
    fi

  done

  # set devicepath for root and swap ...
  DRIVE1_SWAP=${DRIVE1}1 ; DRIVE1_ROOT=${DRIVE1}2
  # check for nvme disks (they have a different device naming) ...
  echo $DRIVE1 | grep -i "dev/nvme" 1>/dev/null 2>&1 && {
    DRIVE1_SWAP=${DRIVE1}p1; DRIVE1_ROOT=${DRIVE1}p2
  }

  # second drive ...
  if [ "@$DRIVE2" != "@" ] ; then
    DRIVE2_SWAP=${DRIVE2}1 ; DRIVE2_ROOT=${DRIVE2}2
    echo $DRIVE2 | grep -i "dev/nvme" 1>/dev/null 2>&1 && {
      DRIVE2_SWAP=${DRIVE2}p1; DRIVE2_ROOT=${DRIVE2}p2
    }
  fi

fi

# cleanup MD devices (volume manager devices must be cleaned by the user!)
echo "INFO: stopping MD devices ..."
mdadm --stop /dev/md127 2>/dev/null
mdadm --stop $SWAPMD 2>/dev/null
mdadm --stop $ROOTMD 2>/dev/null

echo "INFO: drive cleanup ..."
for D in $DRIVE1 $DRIVE2 ; do
  for P in 1 2 3 4
  do
    echo $D | grep -i "dev/nvme" 1>/dev/null 2>&1 && P=p${P}

    echo "cleaning: partition [$P] on drive [ $D ] ..."
    mdadm --zero-superblock $D$P 2>/dev/null
    wipefs -af $D$P 2>/dev/null
  done
done

# wait a few seconds for the disks to settle down ...
echo -e "\nINFO: probing disks"
for D in $DRIVE1 $DRIVE2 ; do
  echo "[ $D ] ..."
  partprobe $D
  sleep 3
done

if [ $MIRROR -eq 1 ] ; then

  # use MD devices (mirroring) ... 
  echo -e "\nINFO: creating SWAP md device ..."
  yes | mdadm --create $SWAPMD -qf --metadata=0.90 --level=1 --raid-devices=1 $DRIVE1_SWAP || exit 1

  echo "INFO: creating ROOT md device ..."
  yes | mdadm --create $ROOTMD -qf --metadata=0.90 --level=1 --raid-devices=1 $DRIVE1_ROOT || exit 1

  echo -e "\nINFO: attaching SWAP mirror ..."
  mdadm --grow --raid-devices=2 $SWAPMD --add $DRIVE2_SWAP

  echo "INFO: attaching ROOT mirror ..."
  mdadm --grow --raid-devices=2 $ROOTMD --add $DRIVE2_ROOT

  # show mdstat...
  echo -e "\n==== BEGIN $MDSTAT ===="
  sleep 3
  cat $MDSTAT
  echo "==== END   $MDSTAT ===="
  echo

  # define root and swap device
  ROOTDEV=$ROOTMD
  SWAPDEV=$SWAPMD

else

  # use RAW devices (NO mirroring) ... 
  ROOTDEV=$DRIVE1_ROOT
  SWAPDEV=$DRIVE1_SWAP

fi

# next: create swap ...
echo -e "\nINFO: creating SWAP space on [ $SWAPDEV ] ..."
mkswap $SWAPDEV || exit 1

# next: create root filesystem...
echo -e "\nINFO: creating ROOT filesystem on [ $ROOTDEV ] ..."
mkfs -t ext4 -F -E nodiscard $ROOTDEV || exit 1

# next: create mountpoint for root and copy the OS files ...
echo "INFO: mounting $ROOTDEV to [ $MP ] ..."
mkdir $MP || exit 1
mount $ROOTDEV $MP

# unpack imagefile ...
echo "INFO: unpack prepared OS in file [ $STX_IMAGE ] to [ $MP ] ..."
tar -C $MP -xzf $STX_IMAGE || {
  echo "ERROR: while unpacking archive!"
  umount -lf $MP
  exit 1
}

####
#

# write configfiles...

echo "INFO: create [ $MP/etc/hosts ] ..."
cat <<-EOF >$MP/etc/hosts
127.0.0.1 localhost
$IP ${MYHOSTNAME}.${DOMAINNAME} $MYHOSTNAME
EOF

echo "INFO: create [ $MP/etc/resolv.conf ] ..."
cat <<-EOF >$MP/etc/resolv.conf
domain $DOMAINNAME
search $DOMAINNAME
nameserver $DNS
EOF

echo "INFO: write hostname file ..."
echo "$MYHOSTNAME" >$MP/etc/hostname

echo "INFO: delete \"BROADCAST\" from ifconfig (normally it is not needed...)"
sed -i '/BROADCAST/d' $MP/$IFCONFIG

echo "INFO: edit [ $MP/$IFCONFIG ] ..."
sed -i "s/.*IP=.*/IP=$IP/" $MP/$IFCONFIG
sed -i "s/.*GATEWAY=.*/GATEWAY=$GATEWAY/" $MP/$IFCONFIG
sed -i "s/.*PREFIX=.*/PREFIX=$NETMASK/" $MP/$IFCONFIG

echo "INFO: edit root profile..."
sed -i "s|root@stx:|root@\\\\h:|" $MP/root/.profile
sed -i "s|^PATH=.*|PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin|" $MP/root/.profile

#
####

# extract kernels revision from image name ...
KERNELNAME=$(echo `basename $STX_IMAGE` | sed 's|^stx-image-||;s|\.tar\.gz$||;s|\.tgz||')

# set symbolic link to kernel ...
(cd $MP/boot ; ln -sFv stx*kernel vmlinuz)

# create grub.cfg ...
echo -e "\nINFO: preparing to create ${MP}$GRUBCFG ..."

if [ $MIRROR -eq 1 ] ; then

  # get UUID (disk identifier) ...
  echo -e "\nINFO: getting UUID for root partition [ $ROOTDEV ] ..."
  root_UUID=`blkid -s UUID -o value $ROOTDEV`
  echo -e ">>>>>> UUID root = $root_UUID <<<<<<\n"

  cat <<-EOF >${MP}$GRUBCFG
set default=0
set timeout=5

search --no-floppy --set=root --hint='mduuid/$root_UUID' $ROOTDEV
insmod ext4

menuentry "SMITUX $KERNELNAME" {
  linux /boot/vmlinuz root=$ROOTDEV net.ifnames=0 ipv6.disable=1 ro rootwait rootfstype=ext4
}
EOF

else

  # get PARTUUID (disk identifier)...
  echo "INFO: getting PARTUUID's for drive [ $DRIVE1 ] ..."

  swap_PARTUUID=`blkid -s PARTUUID -o value $SWAPDEV`
  echo -e ">>>>>> PARTUUID swap = $swap_PARTUUID <<<<<<"
  root_PARTUUID=`blkid -s PARTUUID -o value $ROOTDEV`
  echo -e ">>>>>> PARTUUID root = $root_PARTUUID <<<<<<\n"

  cat <<-EOF >${MP}$GRUBCFG
set default=0
set timeout=5

search --no-floppy --part-uuid --set=root $root_PARTUUID
insmod ext4

menuentry "SMITUX $KERNELNAME" {
  linux /boot/vmlinuz root=PARTUUID=$root_PARTUUID net.ifnames=0 ipv6.disable=1 ro rootwait rootfstype=ext4
}
EOF

  # redefine root and swap device for fstab
  ROOTDEV="PARTUUID=$root_PARTUUID"
  SWAPDEV="PARTUUID=$swap_PARTUUID"

fi

# show grub config...
echo "==== BEGIN $GRUBCFG ===="
cat ${MP}$GRUBCFG
echo "==== END   $GRUBCFG ===="
echo

# write grub to disk(s) ...
echo -e "\nINFO: installing grub to [ $DRIVE1 ]."
grub-install --boot-directory=$MP/boot $DRIVE1 || exit 1

if [ "@$DRIVE2" != "@" ] ; then
  echo -e "\nINFO: installing grub to [ $DRIVE2 ]."
  grub-install --boot-directory=$MP/boot $DRIVE2 || exit 1
fi

# create fstab ...
echo -e "\nINFO: creating ${MP}$FSTAB ..."
cat <<-EOF >$MP/etc/fstab
# filesystem mountpoint type options dump fsck-order
$ROOTDEV / ext4 noatime,nodiratime,errors=remount-ro 0 1
$SWAPDEV swap swap pri=1 0 0
proc                 /proc      proc     nosuid,noexec,nodev 0 0
sysfs                /sys       sysfs    nosuid,noexec,nodev 0 0
devpts               /dev/pts   devpts   gid=5,mode=620      0 0
tmpfs                /run       tmpfs    defaults            0 0
devtmpfs             /dev       devtmpfs mode=0755,nosuid    0 0
EOF

# show fstab ...
echo "==== BEGIN $FSTAB ===="
cat ${MP}$FSTAB
echo "==== END   $FSTAB ===="

# generate ssh keys...
echo "INFO: generating SSH keys ..."
ssh-keygen -t rsa -N '' -f $MP/root/.ssh/id_rsa -C "root@$MYHOSTNAME"
ssh-keygen -e -f $MP/root/.ssh/id_rsa.pub | grep -v "Comment:" > /tmp/id_rsa_rfc.pub
mv -v /tmp/id_rsa_rfc.pub ${MP}/root/.ssh

# generate a password for the root account ...
which uuidgen 1>/dev/null 2>&1
if [ $? -eq 0 ] ; then
  ROOTPW=`uuidgen | tr -d '-' | cut -c1-8`
else
  ROOTPW=`date +%s%N | sha256sum 2>/dev/null | base64 2>/dev/null | head -c 8 | tr '[:upper:]' '[:lower:]'`
fi

[ "@$ROOTPW" = "@" ] && {
  echo "WARNING: cannot set a RANDOM root password !"
  echo "WARNING: setting a trivial password..."
  ROOTPW="stx"
}
  
echo "INFO: setting root password to ---> $ROOTPW <---"
echo "root:$ROOTPW" | chroot $MP /usr/sbin/chpasswd

echo "FINALLY: copying imagefile to target ..."
cp -v $STX_IMAGE $MP/$(basename $STX_IMAGE)

# cleanup & exit...
echo "INFO: trying to unmount [ $MP ] ..."
while [ 1 ]
do
  umount $MP && {
    echo "INFO: cleaning up ..."
    rmdir $MP || exit 1
    echo "READY."
    exit 0
  }
  sleep 10
  echo "ERROR. retrying to unmount [ $MP ] ..."
done

