#!/bin/bash
set -e -o pipefail

# Copyright (C) 2019-2020 Gunter Miegel coinboot.io
#
# This file is part of Coinboot.
#
# Coinboot 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
# (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.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

MACHINE_IP='192.168.1.10'
COINBOOT_SERVER_IP=192.168.1.2/24
MACHINE_MAC_ADDRESS_BIOS='52:54:04:b9:ab:45'
MACHINE_MAC_ADDRESS_UEFI='52:54:04:b9:ab:46'
DEFAULT_ROUTE=$(ip route | grep default | grep -v br0)
DEFAULT_GW=$(ip route | grep default | grep -v br0 | cut -d ' ' -f 3)
EXT_IF=$(ip route show default | grep -oP '(?<=dev )\w+')
TIMEOUT=300
VCPUS=$(nproc)

if [ -d "/vagrant" ]; then
  if ip a | grep -q eth1; then
    COINBOOT_SERVER_IF='eth1'
  else
    COINBOOT_SERVER_IF='enp0s8'
  fi
else
  COINBOOT_SERVER_IF=$EXT_IF
fi

echo "Debug environment at run_coinboot"
env

get_ip_address_associated() {
  local MACHINE_MAC_ADDRESS=$1

  while ! ip neighbour show dev br0 nud reachable | grep $MACHINE_MAC_ADDRESS >&2 ; do
    echo "Waiting until MAC address $MACHINE_MAC_ADDRESS of VM shows up" >&2
    sleep 10
  done


  ip neighbour show dev br0 nud reachable | grep $MACHINE_MAC_ADDRESS -m 1| cut -d ' ' -f 1
}

change_to_build_dir() {
  if [ ! -d "/vagrant" ] && [[ $(basename $PWD) != server ]]; then
    cd server
  else
    cd /vagrant/server
  fi
}

get_bridge_interface() {
  ip a | grep '192.168.1.2/24' | grep -oP 'br-.*'
}

export_interface_for_bridge() {
  if ip route | grep 192.168.1.0/24; then
    export COINBOOT_SERVER_IF=$(ip route |grep 192.168.1.0/24 | grep -oP '(?<=dev )\w+')
  else
   export COINBOOT_SERVER_IF=enp0s8
  fi
}

get_default_route() {
  ip route | grep default | grep -v br0
}

get_default_gateway() {
  ip route | grep default | grep -v br0 | cut -d ' ' -3
}

update_default_route() {
  ip route
  sudo dhclient -v br0
  ip route
}

add_address_to_br0() {
  sudo ip addr add 192.168.1.2/24 dev br0
}

move_ext_interface_to_bridge() {
  brctl show
  ip a
  if [[ -z  $(get_bridge_interface) ]] && ! ip a | grep -oP '\sbr0'; then
    sudo brctl addbr br0
    sudo brctl addif br0 $COINBOOT_SERVER_IF
    sudo ip link set br0 up
    sudo ip link set $COINBOOT_SERVER_IF up
  fi
  brctl show
}

migrate_ips_to_bridge_interface() {
  ip a
  while ip -4 addr show dev $COINBOOT_SERVER_IF | grep inet; do
   #COINBOOT_SERVER_IP=$(ip -4 a show dev $COINBOOT_SERVER_IF | grep -m 1 inet | cut -d ' ' -f6)
   IP=$(ip -4 a show dev $EXT_IF | grep -m 1 inet | cut -d ' ' -f6)
   echo "Migrating $IP to br0 now"
   sudo ip addr add $IP dev br0
   sudo ip addr del $IP dev $COINBOOT_SERVER_IF
  done
}

configure_proxy_dhcp() {
  sed -i "s/dhcp-range=192.168.1.10,192.168.1.100,6h/dhcp-range=192.168.1.0,proxy,255.255.255.0/" conf/dnsmasq/coinboot.conf
}

check_dnsmasq_config() {
  dnsmasq --test --conf-file=./docker/dnsmasq/dnsmasq.conf
}

build_docker_image() {
  docker build -t coinboot/coinboot:latest ./docker/
}

create_test_plugin() {
  echo 'RELEASE is set to :' $RELEASE
  pushd .

  cd ../plugins/

  cat << EOF > src/test-plugin.yaml
---
plugin: Test plugin
archive_name: test-plugin
version: v0.0.1
description: Test plugin for verification
maintainer: Gunter Miegel <gunter.miegel@coinboot.io>
source: https://github.com/frzb/coinboot
run: |
    sudo mkdir /home/ubuntu/test_dir
    echo 'This is a test' | sudo tee -a /home/ubuntu/test_dir/file1
    sudo chown -R ubuntu:ubuntu /home/ubuntu/test_dir
EOF

  ./coinbootmaker -p test-plugin.yaml

  pwd
  ls -la

  cp -v build/coinboot_test-plugin* enabled/

  popd
}

up_docker_compose() {
  docker-compose up -d
  sleep 30
  docker-compose logs
}

up_docker_compose_separate_dhcp_server() {
  docker-compose -f docker-compose.yml -f docker-compose_external_dhcp_server.yml up -d
  sleep 30
  docker-compose logs
}

down_docker_compose() {
  docker-compose down
}

# TODO: Refactor this - should be possible with one curl call
wait_for_server_to_be_ready() {
  while ! [ $(curl --silent http://192.168.1.2 | jq length) -gt 4 ]; do
    curl --silent http://192.168.1.2 | jq '.[].name'
    echo 'Waiting for Coinboot server to provide the necessary files'
    echo '------------'
    sleep 10
  done
}

cleanup_virsh_domains() {
  # Domains with UEFI need to be deleted with the additional parameter --nvram
  for DOMAIN in $(sudo virsh list --all --name); do
    if [[ $DOMAIN == *"uefi"* ]]; then
      sudo virsh undefine --nvram $DOMAIN
    else
      sudo virsh undefine $DOMAIN
    fi
  done
}

run_with_libvirt_bios() {
  sudo virt-install --pxe --name coinboot-bios --memory 1024 --vcpus=$VCPUS --disk none --network bridge=br0,mac=$MACHINE_MAC_ADDRESS_BIOS --boot menu=on,useserial=on --noautoconsole --graphics vnc,listen=0.0.0.0
}

run_with_libvirt_uefi() {
  sudo virt-install --pxe --name coinboot-uefi --memory 1024 --vcpus=$VCPUS --disk none --network bridge=br0,mac=$MACHINE_MAC_ADDRESS_UEFI --boot uefi,menu=on,useserial=on --noautoconsole --graphics vnc,listen=0.0.0.0
}

verify_and_shutdown_over_ssh() {
  local MACHINE_MAC_ADDRESS=$1
  local MACHINE_IP=$(get_ip_address_associated $MACHINE_MAC_ADDRESS)

  local COUNTER=0
  while ! ping -c1 $MACHINE_IP ; do
    sleep 1
    COUNTER=$((COUNTER+1))
    if [ "$COUNTER" -ge "$TIMEOUT" ]; then
      echo "Timeout reached"
      exit 1
    fi
    echo "Waiting $COUNTER second(s) Coinboot machine to respond to our ICMP echo requests ..."
  done

  # Pure nc -z -w with timeout fails for ssh all the time, so again with the while loop.
  local COUNTER=0
  while ! nc -v -z $MACHINE_IP 22; do
    sleep 1
    COUNTER=$((COUNTER+1))
    if [ "$COUNTER" -ge "$TIMEOUT" ]; then
      echo "Timeout reached"
      exit 1
    fi
    echo "Waiting $COUNTER second(s) for Coinboot machine to listen on port 22/SSH ..."
  done

  #while ! sshpass -p ubuntu ssh -o StrictHostKeyChecking=no -l ubuntu -p 22 $MACHINE_IP; do
  #   echo 'Waiting for SSH login to succeed...'
  #  sleep 10
  #done

  #  FIXME: RELEASE does not expanse to date string when 'latest' is specified
  #sshpass -p ubuntu ssh -o StrictHostKeyChecking=no -l ubuntu -p 22 $MACHINE_IP
  pushd .
  cd ./test
  bundle exec cinc-auditor exec coinboot_node_spec.rb --user ubuntu --password ubuntu -t ssh://"${MACHINE_IP}"
  popd 

  DOMAIN=$(sudo virsh list --name)
  sudo virsh destroy $DOMAIN
  sudo virsh list --all
 }

# ------------- main -------------

sudo apt-get update

sudo apt-get  install --yes --no-install-recommends bridge-utils dnsmasq jq sshpass libvirt-daemon-system: virtinst qemu-system-x86 ipxe-qemu ovmf zstd

# Enable the execution of virsh without root access
# mostly used for local debugging and just for Vagrant.
if [ -d "/vagrant" ]; then
  if id | grep -v libvirt; then
    sudo usermod -a -G libvirt vagrant
  fi
fi

# Enable the execution of virsh without root access
# mostly used for local debugging and just for Vagrant.
if [ -d "/vagrant" ]; then
  if id | grep -v libvirt; then
    sudo usermod -a -G libvirt vagrant
  fi
fi

# Enable the execution of virsh without root access
# mostly used for local debugging and just for Vagrant.
if [ -d "/vagrant" ]; then
  if id | grep -v libvirt; then
    sudo usermod -a -G libvirt vagrant
  fi
fi

change_to_build_dir

move_ext_interface_to_bridge

migrate_ips_to_bridge_interface

add_address_to_br0

#update_default_route

echo 'nameserver 8.8.8.8' | sudo tee /etc/resolv.conf

dig registry-1.docker.io || true

# The Vagrant VM has two network interfaces:
# A default one for internet access and a second "private network" which we include in
# as network bridge for the Coinboot Server Container and the Coinboot Qemu worker VMs.
# Because of this we don't have to refresh the default route after moving IPs between interfaces.
if ! [ -d "/vagrant" ]; then
 update_default_route
fi

# We also need to load the environment before calling docker-compose:
# https://github.com/docker/compose/issues/3435
#export $(grep -v '^#' ./conf/environment/* | xargs)

# Config syntax check fails because the build environment on Ubuntu focal is
# running dnsmasq 2.80 which is lacking "dhcp-ignore-clid", this feature was introduced with 2.81
# check_dnsmasq_config

create_test_plugin

build_docker_image

up_docker_compose

wait_for_server_to_be_ready

cleanup_virsh_domains

run_with_libvirt_bios

verify_and_shutdown_over_ssh $MACHINE_MAC_ADDRESS_BIOS

run_with_libvirt_uefi

verify_and_shutdown_over_ssh $MACHINE_MAC_ADDRESS_UEFI

cleanup_virsh_domains
