#!/usr/bin/env bash
#
# kvmx-create virtual machine installer
#
# Copyright (C) 2017 Silvio Rhatto - rhatto at riseup.net
#
# 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 3 of the License,
# or 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.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

# Parameters
BASENAME="`basename $0`"
DIRNAME="`dirname $0`"
GLOBAL_USER_CONFIG_FILE="$HOME/.config/kvmxconfig"

# Load basic functions
export APP_BASE="`$DIRNAME/kvmx app_base`"
source $APP_BASE/lib/kvmx/functions || exit 1

# Load configuration
function kvmx_config_load {
  if [ ! -z "$1" ] && [ -e "$1" ]; then
    source $1
  fi
}

# Read a parameter from user
function kvmx_user_input {
  local input
  local param="$1"
  local default="$2"
  shift 2

  if echo $param | grep -q 'passwd'; then
    read -s -rep "$* (defaults to $default): " input
  else
    read -rep "$* (defaults to $default): " input
  fi

  if [ -z "$input" ]; then
    export $param="$default"
  else
    export $param="$input"
  fi
}

# Get a configuration parameter if not previously defined by a sourced file
function kvmx_user_config {
  local param="$1"
  local default="$2"
  shift 2

  if [ -z "`eval echo '$'$param`" ]; then
    kvmx_user_input $param $default $*
  fi
}

# Install a package
function kvmx_install_package {
  if [ -z "$1" ]; then
    return
  fi

  dpkg -s $1 &> /dev/null

  if [ "$?" == "1" ]; then
    echo "Installing package $1..."
    DEBIAN_FRONTEND=noninteractive $SUDO apt-get install $1 -y || exit 1
  fi
}

# Abort on error
function kvmx_exit_on_error {
  if [ "$?" != "0" ]; then
    echo "Error: $*"
    exit 1
  fi
}

# Run a command using sudo and abort on error
function kvmx_sudo_run {
  if [ "`whoami`" != 'root' ]; then
    SUDO="sudo"
  fi

  $SUDO $*
  kvmx_exit_on_error $*
}

# Make sure there is provision config.
function kvmx_config {
  if [ -e "$GLOBAL_USER_CONFIG_FILE" ]; then
    source $GLOBAL_USER_CONFIG_FILE
  fi

  local default_password="`head -c 20 /dev/urandom | base64`"

  kvmx_user_config   hostname          machine                          "Hostname"
  kvmx_user_config   domain            example.org                      "Domain"
  kvmx_user_config   arch              amd64                            "System arch"
  kvmx_user_config   version           buster                           "Distro version"
  kvmx_user_config   mirror            https://deb.debian.org/debian/   "Debian mirror"
  #kvmx_user_config  ssh_support       y                                "Administration using passwordless SSH (y/n)"
  kvmx_user_config   ssh_custom        y                                "Setup a custom SSH keypair (y/n)"
  kvmx_user_config   user              user                             "Initial user name"
  kvmx_user_config   password          $default_password                "Initial user password"
  kvmx_user_config   net               user                             "Networking config (user or tap)"

  if [ "$net" == "tap" ]; then
    kvmx_user_config net_ip            10.1.1.2                         "IP address"
    kvmx_user_config net_mask          255.255.0                        "Netmask"
    kvmx_user_config net_gateway       10.1.1.1                         "Gateway"
    kvmx_user_config net_dns           192.168.1.1                      "DNS"
  fi

  if [ ! -z "$image_base" ]; then
    image="$image_base/$hostname/box.img"
  else
    image_base="$HOME/.local/share/kvmx"
    kvmx_user_config image             $image_base/$hostname/box.img    "Destination image (ending in .img)"
  fi

  kvmx_user_config   size              3G                               "Image size"
  kvmx_user_config   format            qcow2                            "Image format: raw or qcow2"

  if [ "$format" == "qcow2" ]; then
    kvmx_user_config qcow2_compression y                                "Image compression (y/n)"
  fi

  kvmx_user_config   bootloader        grub                             "Bootloader: grub or extlinux"
}

#
# Custom version
#
function kvmx_create_custom {
  # Check dependencies
  DEPENDENCIES="sudo apt qemu-img sed awk tr head debootstrap chroot"
  __kvmx_check_dependencies

  # Check for package requirements
  #for req in debootstrap parted apt-transport-https; do
  for req in debootstrap parted; do
    kvmx_install_package $req
  done

  if [ -z "$TMP" ]; then
    TMP="/tmp"
  fi

  WORK="`mktemp -d $TMP/kvmx-create.XXXXXXXXXX`"

  # Determine kernel architecture
  if [ "$arch" == "i386" ]; then
    kernel_arch="686"
  else
    kernel_arch="$arch"
  fi

  # Check the host distro
  host_distro="`head -n 1 /etc/issue | cut -d ' ' -f 1 | tr '[:upper:]' '[:lower:]'`"

  # Determine distro and kernel package name
  if echo $mirror | grep 'ubuntu'; then
    distro="ubuntu"
    kernel_package="linux-image-generic"

    if [ "$host_distro" == "debian" ]; then
      kvmx_install_package ubuntu-archive-keyring
    fi
  else
  #elif echo $mirror | grep 'debian'; then
    distro="debian"
    kernel_package="linux-image-$kernel_arch"

    if [ "$host_distro" == "ubuntu" ]; then
      kvmx_install_package debian-archive-keyring
    fi
  fi

  if [ -z "$image_type" ] || [ "$image_type" == "file" ]; then
    echo "Creating image file..."
    #kvmx_sudo_run dd if=/dev/zero of=$image bs=$size count=1
    kvmx_sudo_run qemu-img create -f raw $image $size
    device="`sudo losetup --find --show $image`"
    partition_prefix="p"
  elif [ -e "$image" ]; then
    device="`readlink $image || echo $image`"
  else
    echo "$BASENAME: image device $image does not exist"
    exit 1
  fi

  echo "Partitioning image at $device..."
  kvmx_sudo_run parted -s -- $device mklabel gpt
  kvmx_sudo_run parted -s -- $device unit    MB mkpart    non-fs 2  3
  kvmx_sudo_run parted -s -- $device set     1  bios_grub on
  kvmx_sudo_run parted -s -- $device unit    MB mkpart    ext2   3 -1
  kvmx_sudo_run parted -s -- $device set     2  boot on
  kvmx_sudo_run mkfs.ext4 ${device}${partition_prefix}2
  kvmx_sudo_run mount ${device}${partition_prefix}2 $WORK/

  # Trap $WORK umount
  trap 'if [ -e "$WORK" ]; then umount $WORK/{proc,sys,run,dev/pts,dev} &> /dev/null; umount $WORK &> /dev/null; rmdir $WORK; fi' INT TERM EXIT

  # Non-interactive installation
  #APT_INSTALL="LC_ALL=C DEBIAN_FRONTEND=noninteractive kvmx_sudo_run chroot $WORK/ apt-get install -y"
  #APT_INSTALL="kvmx_sudo_run LC_ALL=C DEBIAN_FRONTEND=noninteractive chroot $WORK/ apt-get install -y"
  APT_INSTALL="kvmx_sudo_run chroot $WORK/ /usr/bin/env LC_ALL=C DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get install -y"

  # Initial system install.
  echo "Installing base system..."
  LC_ALL=C DEBIAN_FRONTEND=noninteractive kvmx_sudo_run debootstrap \
    --force-check-gpg --arch=$arch $version $WORK/ $mirror

  # Initial configuration.
  echo "Applying initial configuration..."

  # Hostname configuration.
  echo $hostname.$domain     | $SUDO tee    $WORK/etc/hostname > /dev/null
  echo "127.0.0.1 localhost" | $SUDO tee -a $WORK/etc/hosts    > /dev/null

  # This ordering is important for facter correctly guess the domain name
  echo "127.0.0.1 $hostname.$domain $hostname" | $SUDO tee -a $WORK/etc/hosts > /dev/null

  # Invert hostname contents to avoid http://projects.puppetlabs.com/issues/2533
  tac $WORK/etc/hosts | $SUDO tee $WORK/etc/hosts.new > /dev/null
  kvmx_sudo_run mv $WORK/etc/hosts.new $WORK/etc/hosts

  # Fstab
  echo "/dev/vda2 / ext4 errors=remount-ro 0 1" | $SUDO tee $WORK/etc/fstab > /dev/null

  # Apt
  if [ "$distro" == "debian" ]; then
    if [ "$version" != "sid" ]; then
      echo "deb http://security.debian.org/ $version/updates main contrib non-free"     | $SUDO tee -a $WORK/etc/apt/sources.list > /dev/null
      echo "deb-src http://security.debian.org/ $version/updates main contrib non-free" | $SUDO tee -a $WORK/etc/apt/sources.list > /dev/null
    fi
  elif [ "$distro" == "ubuntu" ]; then
    $SUDO sed -i -e 's/main/main restricted universe multiverse/' $WORK/etc/apt/sources.list

    echo "deb http://archive.ubuntu.com/ubuntu/ ${version}-updates main restricted universe multiverse"        | $SUDO tee -a $WORK/etc/apt/sources.list > /dev/null
    echo "deb-src http://archive.ubuntu.com/ubuntu/ ${version}-updates main restricted universe multiverse"    | $SUDO tee -a $WORK/etc/apt/sources.list > /dev/null
    echo "deb http://archive.ubuntu.com/ubuntu/ ${version}-backports main restricted universe multiverse"      | $SUDO tee -a $WORK/etc/apt/sources.list > /dev/null
    echo "#deb-src http://archive.ubuntu.com/ubuntu/ ${version}-backports main restricted universe multiverse" | $SUDO tee -a $WORK/etc/apt/sources.list > /dev/null
    echo "deb http://security.ubuntu.com/ubuntu ${version}-security main restricted universe multiverse"       | $SUDO tee -a $WORK/etc/apt/sources.list > /dev/null
    echo "#deb-src http://security.ubuntu.com/ubuntu ${version}-security main restricted universe multivers"   | $SUDO tee -a $WORK/etc/apt/sources.list > /dev/null
    echo "#deb http://archive.canonical.com/ubuntu ${version} partner"     | $SUDO tee -a $WORK/etc/apt/sources.list > /dev/null
    echo "#deb-src http://archive.canonical.com/ubuntu ${version} partner" | $SUDO tee -a $WORK/etc/apt/sources.list > /dev/null
    echo "#deb http://extras.ubuntu.com/ubuntu ${version} main"            | $SUDO tee -a $WORK/etc/apt/sources.list > /dev/null
  fi

  # Mount auxiliary filesystems needed by the bootloader
  kvmx_sudo_run mount none -t proc     $WORK/proc
  kvmx_sudo_run mount none -t sysfs    $WORK/sys
  kvmx_sudo_run mount -o bind /run/    $WORK/run
  kvmx_sudo_run mount -o bind /dev/    $WORK/dev
  kvmx_sudo_run mount -o bind /dev/pts $WORK/dev/pts

  # Initial upgrade
  echo "Updating list of packages..."
  kvmx_sudo_run chroot $WORK/ apt-get update
  kvmx_sudo_run chroot $WORK/ apt-get dist-upgrade -y

  # Install kernel after mounting /proc
  $APT_INSTALL $kernel_package

  if [ "$bootloader" == "grub" ]; then
    $APT_INSTALL grub-pc

    # Serial console support
    echo ''                                            | $SUDO tee -a $WORK/etc/default/grub > /dev/null
    echo '# Custom configuration'                      | $SUDO tee -a $WORK/etc/default/grub > /dev/null
    echo 'GRUB_TERMINAL=serial'                        | $SUDO tee -a $WORK/etc/default/grub > /dev/null
    echo 'GRUB_SERIAL_COMMAND="serial --speed=115200"' | $SUDO tee -a $WORK/etc/default/grub > /dev/null
    echo 'GRUB_CMDLINE_LINUX="console=ttyS0,115200n8"' | $SUDO tee -a $WORK/etc/default/grub > /dev/null

    kvmx_sudo_run chroot $WORK/ update-grub
    kvmx_sudo_run chroot $WORK/ grub-install $device
    # Possible alternatives:
    # https://packages.debian.org/jessie/grub-firmware-qemu
    # https://superuser.com/questions/130955/how-to-install-grub-into-an-img-file
    #kvmx_sudo_run grub-install --boot-directory=$WORK/boot $image
  elif [ "$bootloader" == "extlinux" ]; then
    # http://www.grulic.org.ar/~mdione/glob/posts/create-a-disk-image-with-a-booting-running-debian/
    # http://www.syslinux.org/wiki/index.php?title=EXTLINUX
    # http://www.syslinux.org/wiki/index.php?title=Mbr
    $APT_INSTALL extlinux
    kvmx_sudo_run chroot $WORK/ extlinux --install /boot
    kvmx_sudo_run dd bs=440 count=1 conv=notrunc if=$WORK/usr/lib/EXTLINUX/gptmbr.bin of=$device
    cat <<-EOF | $SUDO tee $WORK/boot/syslinux.cfg > /dev/null
default linux
timeout 1

label   linux
say     Booting linux...
linux   /vmlinuz
append  root=/dev/vda1 ro
initrd  /initrd.img
EOF
  fi

  # Umount auxiliary filesystems
  kvmx_sudo_run umount $WORK/proc
  kvmx_sudo_run umount $WORK/sys
  kvmx_sudo_run umount $WORK/run
  kvmx_sudo_run umount $WORK/dev/pts
  kvmx_sudo_run umount $WORK/dev

  # Run basic provision
  __kvmx_create_custom_second_stage

  # Umount image
  kvmx_sudo_run umount $WORK
  kvmx_sudo_run rmdir  $WORK

  # Pack guest image
  if [ -z "$image_type" ] || [ "$image_type" == "file" ]; then
    kvmx_sudo_run losetup -d $device

    # Image conversion
    if [ "$format" == "qcow2" ]; then
      echo "Converting raw image to qcow2..."
      kvmx_sudo_run mv $image $image.raw
      kvmx_sudo_run qemu-img convert -O qcow2 -p $compression ${image}.raw $image
      kvmx_sudo_run rm ${image}.raw
    fi
  fi

  # Fix permissions
  if [ "`whoami`" != "root" ]; then
    kvmx_sudo_run chown -R `whoami`. `dirname $image`
  fi
}

# Second stage procedure
function __kvmx_create_custom_second_stage {
  if [ ! -z "$net_ip" ] && [ ! -z "$net_mask" ] && [ ! -z "$net_gateway" ]; then
    # Networking
    # See #799253 - virtio ens3 network interface
    #     https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=799253
    for net_dev in eth0 ens3 ens4; do
      cat <<-EOF | $SUDO tee $WORK/etc/network/interfaces.d/$net_dev > /dev/null
auto $net_dev
iface $net_dev inet static
  address $net_ip
  netmask $net_mask
  gateway $net_gateway
EOF
    done
  else
    # Networking
    # See #799253 - virtio ens3 network interface
    #     https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=799253
    for net_dev in eth0 ens3 ens4; do
      cat <<-EOF | $SUDO tee $WORK/etc/network/interfaces.d/$net_dev > /dev/null
allow-hotplug $net_dev
iface $net_dev inet dhcp
EOF
    done
  fi

  # DNS config
  if [ ! -z "$net_dns" ]; then
    if [ "$net_dns" == "host" ]; then
      cp /etc/resolv.conf $WORK/etc/resolv.conf
    else
      echo "nameserver $net_dns" > $WORK/etc/resolv.conf
    fi
  fi

  # Locale
  $APT_INSTALL locales
  echo "LANG=$LANG"  | $SUDO tee    $WORK/etc/default/locale > /dev/null
  echo "$LANG UTF-8" | $SUDO tee -a $WORK/etc/locale.gen     > /dev/null
  kvmx_sudo_run chroot $WORK/ locale-gen

  # Basic packages
  $APT_INSTALL screen cron lsb-release openssl rsync
  $APT_INSTALL spice-vdagent qemu-guest-agent

  # OpenSSH
  $APT_INSTALL openssh-server -y
  kvmx_sudo_run chroot $WORK/ service ssh stop

  # Fix hostname in keys
  kvmx_sudo_run sed -i -e "s/root@.*$/root@$hostname.$domain/" $WORK/etc/ssh/*.pub

  # SSH dir
  sshdir="`dirname $image`/ssh/"
  mkdir -p $sshdir

  # Save host SSH key fingerprints
  for key in $WORK/etc/ssh/*pub; do
    ssh-keygen -l        -f $key > $sshdir/`basename $key`.sha256
    ssh-keygen -l -E md5 -f $key > $sshdir/`basename $key`.md5
  done

  # Sudo
  echo "Installing sudo..."
  $APT_INSTALL sudo -y
  echo "%sudo ALL=NOPASSWD: ALL" | $SUDO tee $WORK/etc/sudoers.d/local > /dev/null

  # Scrambled root password
  #echo 'root:root'                               | kvmx_sudo_run chroot $WORK/ chpasswd
  echo "root:$(head -c 40 /dev/urandom | base64)" | kvmx_sudo_run chroot $WORK/ chpasswd

  # Initial user
  if ! grep -q "^$user:" $WORK/etc/passwd; then
    kvmx_sudo_run chroot $WORK/ useradd $user -G sudo -s /bin/bash
  fi

  # Initial user homedir
  kvmx_sudo_run mkdir -p $WORK/home/$user

  # There might be trouble managing the guest SSH keys when project folder name is different from
  # hostname: while kvmx-create uses $hostname to guess the pubkey file name, kvmx uses $VM.
  # Here we hope that # the user is not messing that much ;)
  #if [ "$ssh_support" == "y" ]; then
  if [ "$ssh_custom" == "y" ]; then
    if [ ! -z "$ssh_custom_pubkey" ]; then
      pubkey="$sshdir/$hostname.key.pub"

      if [ -e "$ssh_custom_pubkey" ]; then
        cp $ssh_custom_pubkey $pubkey
      else
        echo $ssh_custom_pubkey > $pubkey
      fi
    else
      privkey="$sshdir/$hostname.key"
      pubkey="${privkey}.pub"
      __kvmx_ssh_keygen $privkey "$user@$hostname"
    fi
  else
    pubkey="$DIRNAME/share/ssh/insecure_private_key.pub"
  fi

  kvmx_sudo_run chroot $WORK/ mkdir -p /home/$user/.ssh
  kvmx_sudo_run chroot $WORK/ chmod 700 /home/$user/.ssh
  kvmx_sudo_run cp $pubkey $WORK/home/$user/.ssh/authorized_keys
  kvmx_sudo_run chroot $WORK/ chmod 600 /home/$user/.ssh/authorized_keys
  kvmx_sudo_run touch  $WORK/home/$user/.hushlogin

  # Cleanup temporary file if needed
  if [ ! -z "$ssh_custom_pubkey" ]; then
    rm $pubkey
  fi
  #fi

  kvmx_sudo_run chroot $WORK/ chown -R $user.$user /home/$user
  echo "$user:$password" | kvmx_sudo_run chroot $WORK/ chpasswd
}

# Load config file
kvmx_config_load $1

# Get config parameters
kvmx_config

# Check
if [ -e "$image" ]; then
  kvmx_user_config overwrite n "WARNING: $image already exists. Overwrite the installation? (y/n)"

  if [ "$overwrite" != "y" ]; then
    exit 1
  fi
fi

# Ensure base folder exists
kvmx_sudo_run mkdir -p `dirname $image`

# Dispatch
kvmx_create_custom