#!/bin/bash
set -euo pipefail

CONN_CHECK_URL="http://conncheck.opensuse.org"

NAUTILUS_BASE=/etc/nautilus
NAUTILUS_CERT=${NAUTILUS_BASE}/certs
KUBEEDGE_BASE=/etc/kubeedge
OPENVPN_BASE=/etc/openvpn
CNI_NET_BASE=/etc/cni/net.d
SSH_BASE=/etc/ssh
TMP_DIR=$(mktemp --directory)
CONN_STATE="unknown"
FIRST_BOOT_SHUTDOWN="unknown"

##############################################################################

# Check and assign the onboarding URL from file
[[ -s ${NAUTILUS_BASE}/edgizer-onboarding.host ]] && \
  ONBOARDING_HOST=$(<${NAUTILUS_BASE}/edgizer-onboarding.host) || \
  { echo "[edgizer-agent] error: onboarding host not set"; exit 1; }

# check for device certs
[[ ! -s ${NAUTILUS_CERT}/device.key.pem || \
   ! -s ${NAUTILUS_CERT}/device.crt.pem ]] && \
  { echo "[edgizer-agent] error: device certificate missing!"; exit 1; }

# checks if device is for production usw, which will result in early shutdown
[[ $(</proc/cmdline) = *"first_boot_shutdown"* ]] && poweroff || true

##############################################################################

# download a file from the onboarding service to a specified location
# onboarding_api "resource_name" "output_file"
onboarding_api() {
  [[ -z "${3:-}" ]] && OPT_PARAM=" " || OPT_PARAM="$3"
  curl --key ${NAUTILUS_CERT}/device.key.pem \
       --cert ${NAUTILUS_CERT}/device.crt.pem \
       "https://${ONBOARDING_HOST}/edgizer/v1/onboarding/$1" \
       --fail ${OPT_PARAM} --output "$2"
}

# setup 1nce mobile LTE connection
setup_mobile_connection() {
  # TODO: allow to setup normal mobile operation (customer SIM)
  [[ $(nmcli --g connection.autoconnect con show lte-1nce 2>&1) = "yes" ]] && return

  # NOTE: "{ grep XYZ || true; }" will prevent grep from pipefail
  MODEM_ID=$(mmcli --list-modems | { grep QUALCOMM || true; } | { egrep -o "Modem/[0-9]+" || true; } | sed -e 's/Modem\///')
  [[ -z "${MODEM_ID:-}" ]] && return
  SIM_ID=$(mmcli --modem=${MODEM_ID} | { egrep -o '/org/freedesktop/ModemManager1/SIM/[0-9]+' || true; } | sed -e 's/\/org\/freedesktop\/ModemManager1\/SIM\///')
  [[ -z "${SIM_ID:-}" ]] && return
  IIN=$(mmcli --sim=${SIM_ID} | { grep "iccid" || true; } | { egrep -o "8988280|8988330|8988228" || true; })
  [[ -z "${IIN:-}" ]] && return

  echo "[edgizer-agent] setup mobile maintanance connection"
  # route table 200 = mobile-setup
  # supplied mtu (1460) is wrong, SSH connection can not
  # be established, we need to manually override it here
  nmcli con add type gsm ifname cdc-wdm0 con-name lte-1nce \
    connection.autoconnect yes \
    ipv4.route-table 200 \
    gsm.apn iot.1nce.net \
    gsm.mtu 1374
    # gsm.pin 123456
}

# function returns prefix for given netmask in arg1
netmask_to_cidr() {
  bits=0
  for octet in $(echo $1 | sed 's/\./ /g'); do
    binbits=$(echo "obase=2; ibase=10; ${octet}" | bc | sed 's/0//g') 
    let bits+=${#binbits}
  done
  echo "${bits}"
}

# checks if the device is connected / check connectivity
check_conn() {
  echo "[edgizer-agent] checking connectivity using ${CONN_CHECK_URL}"
  curl --fail --silent --head --connect-timeout 10 ${CONN_CHECK_URL} | \
    grep --no-messages --silent "x-networkmanager-status: online" && \
    CONN_STATE="online" || CONN_STATE="offline"
}

# checks if the device is used in production or testing
check_op_mode() {
  echo "[edgizer-agent] checking operation mode of this device"
  [[ $(</proc/cmdline) == *"first_boot_shutdown"* ]] && FIRST_BOOT_SHUTDOWN="true" || \
    FIRST_BOOT_SHUTDOWN="false"
}

# setup eth0 if fixed ip settings are present
setup_system_uplink() {
  [[ ! -f ${NAUTILUS_BASE}/network-setup.json ]] && return
  echo "[edgizer-agent] setup uplink ip configuration"
  LINK_ID=$(nmcli -g "GENERAL.CONNECTION" device show eth0)
  LINK_ADDRESS=$(jq --raw-output .ip_config.ip_address ${NAUTILUS_BASE}/network-setup.json)
  LINK_GATEWAY=$(jq --raw-output .ip_config.gateway ${NAUTILUS_BASE}/network-setup.json)
  LINK_DNSSRV1=$(jq --raw-output .ip_config.primary_dns ${NAUTILUS_BASE}/network-setup.json)
  LINK_DNSSRV2=$(jq --raw-output .ip_config.secondary_dns ${NAUTILUS_BASE}/network-setup.json)
  LINK_SUBNET=$(jq --raw-output .ip_config.subnet_mask ${NAUTILUS_BASE}/network-setup.json)
  if [[ -n "${LINK_ID}" && \
       "${LINK_ADDRESS}" != "null" && -n "${LINK_ADDRESS}" && \
       "${LINK_GATEWAY}" != "null" && -n "${LINK_GATEWAY}" && \
       "${LINK_DNSSRV1}" != "null" && -n "${LINK_DNSSRV1}" && \
       "${LINK_DNSSRV2}" != "null" && -n "${LINK_DNSSRV2}" && \
       "${LINK_SUBNET}"  != "null" && -n "${LINK_SUBNET}" ]]; then
    LINK_CIDR=$(netmask_to_cidr "${LINK_SUBNET}")
    nmcli con modify "${LINK_ID}" \
      ipv4.method manual \
      ipv4.addresses ${LINK_ADDRESS}/${LINK_CIDR} \
      ipv4.gateway ${LINK_GATEWAY} \
      ipv4.dns ${LINK_DNSSRV1},${LINK_DNSSRV1} \
      ipv4.dns-priority 75
   else
    nmcli con modify "${LINK_ID}" \
      ipv4.method auto \
      ipv4.addresses "" \
      ipv4.gateway "" \
      ipv4.dns "" \
      ipv4.dns-priority 0
  fi
  nmcli con up "${LINK_ID}"
}

# setup to use a proxy server for the system
setup_system_proxy() {
  [[ ! -f ${NAUTILUS_BASE}/network-setup.json ]] && return
  echo "[edgizer-agent] setup web proxy configuration"
  PROXY_USER=$(jq --raw-output .http_proxy.user_name ${NAUTILUS_BASE}/network-setup.json)
  PROXY_PASS=$(jq --raw-output .http_proxy.password ${NAUTILUS_BASE}/network-setup.json)
  PROXY_SERVER=$(jq --raw-output .http_proxy.host ${NAUTILUS_BASE}/network-setup.json)
  PROXY_PORT=$(jq --raw-output .http_proxy.port ${NAUTILUS_BASE}/network-setup.json)
  if [[ "${PROXY_SERVER}" != "null" && -n "${PROXY_SERVER}" && \
        "${PROXY_PORT}"   != "null" && -n "${PROXY_PORT}" ]]; then
    [[ "${PROXY_USER}" = "null" ]] && PROXY_USER=""
    [[ "${PROXY_PASS}" = "null" ]] && PROXY_PASS=""
    [[ -z "${PROXY_USER}" || -z "${PROXY_PASS}" ]] && PROXY_CRED="" || \
      PROXY_CRED="${PROXY_USER}:${PROXY_PASS}@"
    PROXY_URL="${PROXY_CRED}${PROXY_SERVER}:${PROXY_PORT}"
    sed -i "s/^PROXY_ENABLED.*/PROXY_ENABLED=\"yes\"/" /etc/sysconfig/proxy
    sed -i "s/^HTTP_PROXY.*/HTTP_PROXY=\"http:\/\/${PROXY_URL}\"/" /etc/sysconfig/proxy
    sed -i "s/^HTTPS_PROXY.*/HTTPS_PROXY=\"http:\/\/${PROXY_URL}\"/" /etc/sysconfig/proxy
    sed -i "s/^FTP_PROXY.*/FTP_PROXY=\"http:\/\/${PROXY_URL}\"/" /etc/sysconfig/proxy
   else
    sed -i "s/^PROXY_ENABLED.*/PROXY_ENABLED=\"no\"/" /etc/sysconfig/proxy
    sed -i "s/^HTTP_PROXY.*/HTTP_PROXY=\"\"/" /etc/sysconfig/proxy
    sed -i "s/^HTTPS_PROXY.*/HTTPS_PROXY=\"\"/" /etc/sysconfig/proxy
    sed -i "s/^FTP_PROXY.*/FTP_PROXY=\"\"/" /etc/sysconfig/proxy
  fi
  set +eu; source /etc/profile.d/profile.sh; set -eu
}

# fetch proxy and or fixed ip settings using dedicated mobile conn and setup uplink
get_pre_network_config() {
  [[ ! -c /dev/cdc-wdm0 ]] && return || true
  echo "[edgizer-agent] try to setup pre network configuration"
  WWAN_WAIT_CNT=30
  WWAN_STATE=$(nmcli -g GENERAL.STATE con show lte-1nce)
  while [ "${WWAN_STATE}" != "activated" ]; do
    [[ ${WWAN_WAIT_CNT} -eq 0 ]] && return
    echo "[edgizer-agent] waiting for wwan interface to be connected..."
    sleep 1
    WWAN_WAIT_CNT=$((${WWAN_WAIT_CNT} - 1))
    WWAN_STATE=$(nmcli -g GENERAL.STATE con show lte-1nce)
  done
  WWAN_INTF=$(nmcli --get-values GENERAL.IP-IFACE con show lte-1nce)
  [[ -z "${WWAN_INTF}" ]] && return 1
  # we need another ip rule here, because curl was not compiled with c-ares
  # thats why 'curl --dns-interface' is not working for us here...
  ip rule add to 8.8.8.8 table mobile-setup
  onboarding_api "wan/config" "${TMP_DIR}/network-setup.json" "--interface ${WWAN_INTF} --noproxy ${ONBOARDING_HOST}"
  # remove the rule again, we do not want traffic to go here during normal operation
  ip rule del to 8.8.8.8 table mobile-setup
  mv ${TMP_DIR}/network-setup.json ${NAUTILUS_BASE}/network-setup.json
  setup_system_uplink
  setup_system_proxy
  check_conn
  [[ "${CONN_STATE}" != "online" ]] && \
    echo "[edgizer-agent] tried to setup ip and/or proxy, but there is still no wan connection!" || true
}

# prepare kubeedge directories and download edgecore.yaml
get_kubeedge_config() {
  echo "[edgizer-agent] fetching edgecore config"
  [[ ! -d ${KUBEEDGE_BASE}/ca ]] && mkdir -p ${KUBEEDGE_BASE}/ca
  [[ ! -d ${KUBEEDGE_BASE}/certs ]] && mkdir -p ${KUBEEDGE_BASE}/certs
  [[ ! -d ${KUBEEDGE_BASE}/config ]] && mkdir -p ${KUBEEDGE_BASE}/config
  onboarding_api "kubeedge/config" "${TMP_DIR}/edgecore.yaml"
  mv ${TMP_DIR}/edgecore.yaml ${KUBEEDGE_BASE}/config/edgecore.yaml
  chmod 0400 ${KUBEEDGE_BASE}/config/edgecore.yaml
}

# download kubeedge repository, remove old one and install new one into system
get_kubeedge_repo() {
  echo "[edgizer-agent] setup edgizer package repository"
  onboarding_api "kubeedge/repo" "${TMP_DIR}/kubeedge.repo"
  mv ${TMP_DIR}/kubeedge.repo ${NAUTILUS_BASE}/kubeedge.repo
  # currently 'zypper removerepo' returns also '0' if we wont find any old 'kubeedge' repo
  # if this behaviour changes, we could go with xargs --no-run-if-empty
  #zypper repos | awk '$3 == "kubeedge" { print $1 }' | xargs --no-run-if-empty --max-args=1 zypper removerepo
  zypper --non-interactive --quiet removerepo kubeedge
  zypper --non-interactive --quiet addrepo ${NAUTILUS_BASE}/kubeedge.repo
}

# download openvpn config
get_openvpn_config() {
  [[ ! -d ${OPENVPN_BASE} ]] && exit 1
  echo "[edgizer-agent] fetching openvpn config"
  onboarding_api "vpn/config" ${TMP_DIR}/cluster.conf
  mv ${TMP_DIR}/cluster.conf ${OPENVPN_BASE}/cluster.conf
  chmod 0400 ${OPENVPN_BASE}/cluster.conf
}

# download cni bridge config
get_cni_config() {
  [[ ! -d ${CNI_NET_BASE} ]] && exit 1
  echo "[edgizer-agent] fetching cni config"
  onboarding_api "cni/config" ${TMP_DIR}/10-crio-bridge.conf
  mv ${TMP_DIR}/10-crio-bridge.conf ${CNI_NET_BASE}/10-crio-bridge.conf
  chmod 0644 ${CNI_NET_BASE}/10-crio-bridge.conf
}

# bind openvpn client ip to hostname
get_openvpn_node_ip() {
  echo "[edgizer-agent] setup vpn node ip"
  onboarding_api "vpn/ip" "${TMP_DIR}/vpn-node-ip.txt"
  mv ${TMP_DIR}/vpn-node-ip.txt ${NAUTILUS_BASE}/vpn-node-ip.txt
  VPN_IP=$(cut -d "/" -f 1 ${NAUTILUS_BASE}/vpn-node-ip.txt)
  NODE_NAME=$(hostname)
  HOSTS_LINE="${VPN_IP} ${NODE_NAME} ${NODE_NAME}.local"
  grep ${NODE_NAME} /etc/hosts && \
    sed -i "s/.*${NODE_NAME}.*/$HOSTS_LINE/" /etc/hosts || \
    echo $HOSTS_LINE >> /etc/hosts
}

# install trusted user ca and new host key into system
get_ssh_certs() {
  echo "[edgizer-agent] setup ssh trusted user CA key"
  onboarding_api "ssh/certs" "${TMP_DIR}/ssh-certs.json"
  jq --raw-output .key_signing_ca_certificate ${TMP_DIR}/ssh-certs.json > \
    ${TMP_DIR}/trusted-user-ca-keys.pem && mv ${TMP_DIR}/trusted-user-ca-keys.pem ${SSH_BASE}/trusted-user-ca-keys.pem
  jq --raw-output .host_certificate ${TMP_DIR}/ssh-certs.json > \
    ${TMP_DIR}/ssh_host_rsa_key.pub && mv ${TMP_DIR}/ssh_host_rsa_key.pub ${SSH_BASE}/ssh_host_rsa_key.pub
  jq --raw-output .host_key ${TMP_DIR}/ssh-certs.json > \
    ${TMP_DIR}/ssh_host_rsa_key && mv ${TMP_DIR}/ssh_host_rsa_key ${SSH_BASE}/ssh_host_rsa_key
  chmod 0644 ${SSH_BASE}/trusted-user-ca-keys.pem
  chmod 0644 ${SSH_BASE}/ssh_host_rsa_key.pub
  chmod 0600 ${SSH_BASE}/ssh_host_rsa_key
  [[ -n "$(grep TrustedUserCAKeys ${SSH_BASE}/sshd_config)" ]] || \
    echo -e "TrustedUserCAKeys ${SSH_BASE}/trusted-user-ca-keys.pem" >> ${SSH_BASE}/sshd_config
  rm ${TMP_DIR}/ssh-certs.json
}

# factory reset our device, back to the start
factory_reset() {
  REPLY="NO"
  echo "!!! Factory Reset"
  echo "!!! This will permanently delete all onboarding settings, configs"
  echo "!!! containers and resets the device to the factoy state."
  echo "!!! The device will reboot afterwards and try to onboard itself."
  read -t 10 -p "!!! FACTORY RESET YOUR DEVICE NOW? (type YES): "
  [[ "${REPLY}" != "YES" ]] && return || true
  echo "[Factory Reset] Stopping System Services"
  systemctl stop edgecore
  systemctl stop openvpn@cluster
  systemctl stop crio
  pkill -f conmon || true
  echo "[Factory Reset] Delete KubeEdge Config and Certs and Data"
  rm -rf ${KUBEEDGE_BASE}/ca/*
  rm -rf ${KUBEEDGE_BASE}/certs/*
  rm -rf ${KUBEEDGE_BASE}/config/*
  mount | { grep lib/edged || true; } | awk '{print "umount " $3}' | sort -r | sh
  rm -rf /var/lib/edged
  rm -rf /var/lib/kubeedge
  echo "[Factory Reset] Unmount Overlays and delete Containers and Logs"
  mount | { grep lib/container || true; } | awk '{print "umount " $3}' | sort -r | sh
  rm -rf /var/lib/cni/*
  rm -rf /var/lib/containers/*
  rm -rf /var/lib/edged/*
  rm -rf /var/lib/kubeedge/*
  rm -rf /var/log/crio/*
  rm -rf /var/log/containers/*
  rm -rf /var/log/pods/*
  echo "[Factory Reset] Delete Cluster VPN Config"
  systemctl disable openvpn@cluster
  rm -f ${OPENVPN_BASE}/cluster.conf
  echo "[Factory Reset] Delete CNI Config"
  rm -f ${CNI_NET_BASE}/10-crio-bridge.conf
  echo "[Factory Reset] Delete VPN IP System Settings"
  rm -f ${NAUTILUS_BASE}/vpn-node-ip.txt
  sed -i "s/.*$(hostname).*//" /etc/hosts
  echo "[Factory Reset] Delete Trusted SSH CA"
  rm -f ${SSH_BASE}/trusted-user-ca-keys.pem
  sed -i "s/.*TrustedUserCAKeys.*//" ${SSH_BASE}/sshd_config
  echo "[Factory Reset] Uninstall KubeEdge Edgecore"
  transactional-update --non-interactive pkg remove kubeedge-edgecore
  echo "[Factory Reset] Remove KubeEdge Repository"
  zypper --non-interactive --quiet removerepo kubeedge
  rm -f ${NAUTILUS_BASE}/kubeedge.repo
  if [[ -f ${NAUTILUS_BASE}/network-setup.json ]]; then
    echo "[Factory Reset] Remove PreNetworkConfig"
    echo "{}" > ${NAUTILUS_BASE}/network-setup.json
    setup_system_uplink
    setup_system_proxy
    rm -f ${NAUTILUS_BASE}/network-setup.json
  fi
  echo "[Factory Reset] Done. Rebooting now..."
  reboot
}

# clean up the dance floor
cleanup() {
  rmdir ${TMP_DIR}
  exit $1
}

usage() {
  echo "$0 [OPTION]"
  echo "  without options, all components will be checked, default onboarding"
  echo
  echo "  -a, --agent-update       check for new edgizer agent version"
  echo "  -k, --kubeedge-config    check for new edgecore config"
  echo "  -r, --kubeedge-repo      check for new kubeedge repo"
  echo "  -e, --edgecore-update    check for new edgecore version"
  echo "  -n, --network-config     check for new network configuration"
  echo "  -F, --factory-reset      factory reset device, removes all onboarding settings"
  echo "  -h, --help               display this help"
  echo
}

##############################################################################

# check for user parameter
while [[ "$#" -gt 0 ]]; do
  case $1 in
    -a|--agent-update)
      transactional-update --non-interactive reboot pkg install edgizer-agent
      cleanup 0
      ;;
    -k|--kubeedge-config)
      get_kubeedge_config
      cleanup 0
      ;;
    -r|--kubeedge-repo)
      get_kubeedge_repo
      cleanup 0
      ;;
    -e|--edgecore-update)
      transactional-update --non-interactive reboot pkg install kubeedge-edgecore
      cleanup 0
      ;;
    -n|--network-config)
      get_pre_network_config
      cleanup 0
      ;;
    -F|--factory-reset)
      factory_reset
      cleanup 0
      ;;
    -h|--help)
      usage
      cleanup 0
      ;;
    0|1|2)
      # ignore incoming RPM post parameter
      cleanup 0
      ;;
    *)
      echo "[edgizer-agent] Unknown parameter passed: $1"
      usage
      cleanup 1
      ;;
  esac
  shift
done

# default onboarding sequence, won't overwrite files here
# to update files, the script needs to be called with parameter

# make sure, we respect the current proxy settings in here
set +eu; source /etc/profile.d/profile.sh; set -eu
check_op_mode
[[ "$FIRST_BOOT_SHUTDOWN" == "true" ]] && poweroff
check_conn
[[ -c /dev/cdc-wdm0 ]] && setup_mobile_connection
[[ "${CONN_STATE}" != "online" ]] && get_pre_network_config
[[ ! -f ${KUBEEDGE_BASE}/config/edgecore.yaml ]] && get_kubeedge_config
[[ ! -f ${OPENVPN_BASE}/cluster.conf ]] && get_openvpn_config
[[ ! -f ${CNI_NET_BASE}/10-crio-bridge.conf ]] && get_cni_config
[[ -z "$(grep $(cat ${NAUTILUS_BASE}/edgizer.uuid) /etc/hosts)" ]] && get_openvpn_node_ip
[[ ! -f ${NAUTILUS_BASE}/kubeedge.repo ]] && get_kubeedge_repo
[[ ! -f ${SSH_BASE}/trusted-user-ca-keys.pem ]] && get_ssh_certs
[[ "$(systemctl is-enabled openvpn@cluster)" = "disabled" ]] && systemctl --now enable openvpn@cluster
echo "[edgizer-agent] setup edgecore package"
transactional-update --non-interactive reboot pkg install --download in-advance kubeedge-edgecore
# do not wait for kured, reboot now if needed, because the system was just started
[[ -f /var/run/reboot-required ]] && reboot || exit 0
