#!/usr/bin/env bash

LAYEROPS_ROOT_DIR=/data/layerops
LAYEROPS_CONFIG_DIR=${LAYEROPS_ROOT_DIR}/config
LAYEROPS_IMAGES_DIR=${LAYEROPS_ROOT_DIR}/images
LAYEROPS_STORAGE_DIR=${LAYEROPS_ROOT_DIR}/storage
LAYEROPS_STORAGE_NAME=layerops
DOCKER_DATA_PATH=/data/docker
LAYEROPS_DATA_PATH=/data/layerops
WIREGUARD_UDP_PORT=51820
SSH_EXPOSED_PORT=2222
LXC_BRIDGE_DEVICE=lxdbr0
LXC_CONTAINER_NIC=eth0

function usage {
  cat << EOF
Usage: $(basename "${BASH_SOURCE[0]}") [start|stop|clean]
EOF
  exit 0
}

function check_envvars() {
  MISSING=""
  for envvar in $@
  do
    [ -z "${!envvar}" ] && MISSING="$envvar $MISSING"
  done

  if [ ! -z "$MISSING" ]
  then
    echo "Missing following environment variables:"
    for var in $MISSING
    do
      echo "  $var"
    done
    exit 1
  fi
}

function lxc_init() {
  if [[ "$(echo $OSTYPE)" == "darwin"* ]];
  then
    msg "${RED}***Please check REMOTE LXC is configured.***"
    exit 1
  fi

  if ! [ -x "$(command -v lxd)" ]; then
    msg "${RED}***Please install lxd (see: https://linuxcontainers.org/lxd/getting-started-cli/ )"
    exit 1
  fi

  # Install required packages
  apt-get update && apt-get install -y jq ipcalc-ng


  # Init LXD
  lxd init --minimal

  # Create storages
  mkdir -p $LAYEROPS_STORAGE_DIR
  lxc storage create $LAYEROPS_STORAGE_NAME dir source=$LAYEROPS_STORAGE_DIR
}

function check_local_image() {
  LAYEROPS_IMAGE_NAME=layerops-orchestrator-lxc-${LXC_IMAGE_VERSION}
  LAYEROPS_IMAGE_FILE=${LAYEROPS_IMAGES_DIR}/${LAYEROPS_IMAGE_NAME}.tar.gz
  LAYEROPS_IMAGE_CHECKSUM_FILE=${LAYEROPS_IMAGE_FILE}.sha256
  LXC_IMAGE_CHECKSUM_URL=${LXC_IMAGE_URL}.sha256
  IMAGE_FOUND=false
  for IMAGE in $(lxc image ls -f compact | grep "$LAYEROPS_IMAGE_NAME" | awk '{print $1}')
  do
    if [ "$IMAGE" == "$LAYEROPS_IMAGE_NAME" ] ; then
      IMAGE_FOUND=true
      msg "${GREEN}${LAYEROPS_IMAGE_NAME} Image found"
      break
    fi
  done
  if [ "$IMAGE_FOUND" == "false" ]; then
    msg "${YELLOW}No ${LAYEROPS_IMAGE_NAME} image found"
    rm -f ${LAYEROPS_IMAGE_FILE} ${LAYEROPS_IMAGE_FILE}.sha256
    msg "${YELLOW}Download ${LAYEROPS_IMAGE_NAME} image"
    mkdir -p $LAYEROPS_IMAGES_DIR
    wget -O ${LAYEROPS_IMAGE_FILE} ${LXC_IMAGE_URL}
    wget -O ${LAYEROPS_IMAGE_CHECKSUM_FILE} ${LXC_IMAGE_CHECKSUM_URL}

    msg "${YELLOW}Verify ${LAYEROPS_IMAGE_FILE} checksum"
    if [ ! -f "${LAYEROPS_IMAGE_FILE}" ]; then
      msg "${RED}ERROR: Missing image file ${LAYEROPS_IMAGE_FILE}"
      exit 1
    fi
    EXPECTED_CHECKSUM=$(cat ${LAYEROPS_IMAGE_CHECKSUM_FILE} | awk '{print $1}')
    ACTUAL_CHECKSUM=$(sha256sum ${LAYEROPS_IMAGE_FILE} | awk '{print $1}')
    if [ "$EXPECTED_CHECKSUM" == "$ACTUAL_CHECKSUM" ];then
      msg "${GREEN}Checksum OK."
    else
      msg "${RED}ERROR: Bad checksum for image file ${LAYEROPS_IMAGE_FILE}:"
      msg "${RED}    Expected: ${EXPECTED_CHECKSUM}"
      msg "${RED}    Found: ${ACTUAL_CHECKSUM}"
      exit 1
    fi

    msg "${YELLOW}Import ${LAYEROPS_IMAGE_NAME} image"
    lxc image import ${LAYEROPS_IMAGE_FILE} --alias $LAYEROPS_IMAGE_NAME

    rm -f ${LAYEROPS_IMAGE_FILE} ${LAYEROPS_IMAGE_FILE}.sha256
  fi
}

function lxc_clean() {
  msg "delete"

  LAYEROPS_IMAGES=$(lxc image ls -f compact|grep layerops-orchestrator|awk '{print $1}')
  if [ "$LAYEROPS_IMAGES" == "" ]
  then
    msg "${GREEN}No 'layerops-orchestrator' Image found: nothing do delete"
  else
    for LAYEROPS_IMAGE_NAME in $LAYEROPS_IMAGES
    do
      msg "${YELLOW} Delete image ${LAYEROPS_IMAGE_NAME}"
      lxc image rm ${LAYEROPS_IMAGE_NAME}
    done
  fi

  if [[ "$(lxc storage ls -f json |jq -r ".[] | select(.name == \"$LAYEROPS_STORAGE_NAME\") | .name")" == "$LAYEROPS_STORAGE_NAME" ]]
  then
    msg "${YELLOW}${LAYEROPS_STORAGE_NAME} storage pool found: delete it"
    lxc storage rm $LAYEROPS_STORAGE_NAME
  else
    msg "${GREEN}No ${LAYEROPS_STORAGE_NAME} storage pool found: nothing do delete"
  fi

  rm -fR $LAYEROPS_CONFIG_DIR $LAYEROPS_IMAGES_DIR $LAYEROPS_STORAGE_DIR
}

function lxc_run () {

  # Setup user-data
  mkdir -p ${LAYEROPS_CONFIG_DIR}/${1}
  test -f ${LAYEROPS_CONFIG_DIR}/${1}/user-data || \
    cat > ${LAYEROPS_CONFIG_DIR}/${1}/user-data <<EOF
#cloud-config
write_files:
- path: /etc/profile.d/layerops.sh
  content: |
    export INSTANCE_API_URL=${INSTANCE_API_URL}
    export INSTANCE_UUID=${INSTANCE_UUID}
    export ORCHESTRATOR_UUID=${ORCHESTRATOR_UUID}
    export INSTANCE_TOKEN=${INSTANCE_TOKEN}
    export SSH_PUBKEY="${SSH_PUBKEY}"
    export LOCAL_TIMEZONE=${LOCAL_TIMEZONE}
  append: false
- path: /usr/local/bin/startup.sh
  permissions: 0755
  content: |
    #!/bin/bash
    . /etc/profile.d/layerops.sh
    envsubst < /etc/layerops/api-access.json.tpl > /etc/layerops/api-access.json
    chmod 600 /etc/layerops/api-access.json
    /etc/layerops/scripts/launch.sh
- path: /home/layerops/.ssh/authorized_keys
  owner: layerops:layerops
  permissions: 0644
  content: |
    ${SSH_PUBKEY}
runcmd:
  - bash /usr/local/bin/startup.sh
EOF
  chmod 600 ${LAYEROPS_CONFIG_DIR}/${1}/user-data

  # Create Container
  LAYEROPS_IMAGE_NAME=layerops-orchestrator-lxc-${LXC_IMAGE_VERSION}
  lxc init $LAYEROPS_IMAGE_NAME ${1} -s $LAYEROPS_STORAGE_NAME
  lxc config set ${1} security.privileged=true security.nesting=true security.syscalls.intercept.mknod=true security.syscalls.intercept.setxattr=true
  lxc config set ${1} user.user-data - < ${LAYEROPS_CONFIG_DIR}/${1}/user-data

  # Create storage volumes
  lxc storage volume create ${LAYEROPS_STORAGE_NAME} "${1}-docker"
  lxc config device add ${1} docker disk pool=${LAYEROPS_STORAGE_NAME} source="${1}-docker" path=${DOCKER_DATA_PATH}

  lxc storage volume create ${LAYEROPS_STORAGE_NAME} "${1}-data"
  lxc config device add ${1} layerops disk pool=${LAYEROPS_STORAGE_NAME} source="${1}-data" path=${LAYEROPS_DATA_PATH}

  # Configure Network interface
  LXC_BRIDGE_CIDR=$(lxc network list -f json | jq -r ".[] | select (.name == \"$LXC_BRIDGE_DEVICE\").config.\"ipv4.address\"")
  LXC_CONTAINER_IP="$(ipcalc-ng -n $LXC_BRIDGE_CIDR | cut -d '=' -f 2 | cut -d '.' -f 1,2,3).100"
  lxc config device add ${1} $LXC_CONTAINER_NIC nic nictype=bridged parent=$LXC_BRIDGE_DEVICE ipv4.address=$LXC_CONTAINER_IP

  # Configure NAT proxy for incoming traffic
  HOST_NET_INTERFACE=$(ip route list |grep default  | awk -F 'dev' '{print $2}' | awk '{print $1}')
  HOST_IP=$(ip -brief address show $HOST_NET_INTERFACE | awk '{print $3}' | cut -d '/' -f 1)
  lxc config device add ${1} wireguard proxy listen=udp:${HOST_IP}:${WIREGUARD_UDP_PORT} connect=udp:${LXC_CONTAINER_IP}:${WIREGUARD_UDP_PORT} nat=true
  lxc config device add ${1} tcp_ssh proxy listen=tcp:${HOST_IP}:${SSH_EXPOSED_PORT} connect=tcp:${LXC_CONTAINER_IP}:22 nat=true
  for tcp_port in 80 443 4646 8200 8501 9888 9107
  do
    lxc config device add ${1} tcp_${tcp_port} proxy listen=tcp:${HOST_IP}:${tcp_port} connect=tcp:${LXC_CONTAINER_IP}:${tcp_port} nat=true
  done

  # Start Container
  lxc start ${1}
}

function lxc_remove () {
  lxc stop ${1}
  lxc rm ${1}
  lxc storage volume rm ${LAYEROPS_STORAGE_NAME} "${1}-docker"
  lxc storage volume rm ${LAYEROPS_STORAGE_NAME} "${1}-data"
  rm -fR ${LAYEROPS_CONFIG_DIR}/${1}
}

function node_create() {
    msg "${PURPLE}LayerOps:"
    msg "- ${BLUE}Setup and Deploy node $1"
    check_local_image
    lxc_run $1
}

function node_delete() {
    msg "${PURPLE}LayerOps:"
    msg "- ${RED}Stop and Delete node $1"
    lxc_remove $1
}

if [[ "$#" -eq 0 ]]; then
  usage
fi

setup_colors() {
  if [[ -t 2 ]] && [[ -z "${NO_COLOR-}" ]] && [[ "${TERM-}" != "dumb" ]]; then
    NOFORMAT='\033[0m' RED='\033[0;31m' GREEN='\033[0;32m' ORANGE='\033[0;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' YELLOW='\033[1;33m'
  else
    NOFORMAT='' RED='' GREEN='' ORANGE='' BLUE='' PURPLE='' CYAN='' YELLOW=''
  fi
}

msg() {
  echo >&2 -e "${1-}${NOFORMAT}"
}

setup_colors

case "$1" in
  start)
    check_envvars \
      INSTANCE_UUID \
      ORCHESTRATOR_UUID \
      INSTANCE_API_URL \
      LXC_IMAGE_URL \
      LXC_IMAGE_VERSION \
      INSTANCE_TOKEN \
      SSH_PUBKEY
    lxc_init
    check_local_image
    node_create layerops-orchestrator-${INSTANCE_UUID}
    ;;
  stop)
    check_envvars INSTANCE_UUID
    node_delete layerops-orchestrator-${INSTANCE_UUID}
    ;;
  clean)
    lxc_clean;;
  *)
    usage;;
esac
