#!/usr/bin/env bash
#/ Usage: ghe-host-check [-h] [--version] [<host>]
#/
#/ Verify connectivity with the GitHub Enterprise Server host.
#/
#/ OPTIONS:
#/   -h | --help       Show this message.
#/        --version    Display version information.
#/   <host>            The GitHub Enterprise Server host to check. When no
#/                     <host> is provided, the $GHE_HOSTNAME configured in
#/                     backup.config is assumed.
#/

set -e

while true; do
  case "$1" in
  -h | --help)
    export GHE_SHOW_HELP=true
    shift
    ;;
  --version)
    export GHE_SHOW_VERSION=true
    shift
    ;;
  -*)
    echo "Error: invalid argument: '$1'" 1>&2
    exit 1
    ;;
  *)
    break
    ;;
  esac
done

# Bring in the backup configuration
# shellcheck source=share/github-backup-utils/ghe-backup-config
. "$(dirname "${BASH_SOURCE[0]}")/../share/github-backup-utils/ghe-backup-config"

# Use the host provided on the command line if provided, or fallback on the
# $GHE_HOSTNAME configured in backup.config when not present.
host="${1:-$GHE_HOSTNAME}"

# Options to pass to SSH during connection check
options="
  -o PasswordAuthentication=no
  -o ConnectTimeout=5
  -o ConnectionAttempts=1
"

# Split host:port into parts
port=$(ssh_port_part "$host")
hostname=$(ssh_host_part "$host")

set +e
# ghe-negotiate-version verifies if the target is a GitHub Enterprise Server instance
output=$(echo "ghe-negotiate-version backup-utils $BACKUP_UTILS_VERSION" | ghe-ssh -o BatchMode=no $options $host -- /bin/sh 2>&1)
rc=$?
set -e

if [ $rc -ne 0 ]; then
  case $rc in
  255)
    if echo "$output" | grep -i "port 22: Network is unreachable\|port 22: connection refused\|port 22: no route to host\|ssh_exchange_identification: Connection closed by remote host\|Connection timed out during banner exchange\|port 22: Connection timed out" >/dev/null; then
      exec "$(basename $0)" "$hostname:122"
    fi

    echo "$output" 1>&2
    echo "Error: ssh connection with '$host' failed" 1>&2
    echo "Note that your SSH key needs to be setup on $host as described in:" 1>&2
    echo "* https://docs.github.com/enterprise-server/admin/configuration/configuring-your-enterprise/accessing-the-administrative-shell-ssh" 1>&2
    ;;
  101)
    echo "Error: couldn't read GitHub Enterprise Server fingerprint on '$host' or this isn't a GitHub appliance." 1>&2
    ;;
  1)
    if [ "${port:-22}" -eq 22 ] && echo "$output" | grep "use port 122" >/dev/null; then
      exec "$(basename $0)" "$hostname:122"
    else
      echo "$output" 1>&2
    fi
    ;;

  esac
  exit $rc
fi

if ghe-ssh "$host" -- \
  "[ -f '$GHE_REMOTE_ROOT_DIR/etc/github/cluster' ]"; then
  CLUSTER=true
fi

set +e
# ensure all nodes in the cluster are online/reachable and running the same version
if [ "$CLUSTER" ]; then
  ghe-ssh "$host" ghe-cluster-host-check > /dev/null || online_status=$?
  if [[ "$online_status" -eq 2 ]]; then
    log_error "Error: Not all nodes are online! Please ensure cluster is in a healthy state before using backup-utils." 1>&2
    exit 1
  fi

  node_version_list=$(ghe-ssh "$host" ghe-cluster-each -- ghe-version)
  distinct_versions=$(echo "$node_version_list" | awk '{split($0, a, ":"); print a[2]}' | awk '{print $4}' | uniq | wc -l)
  if [ "$distinct_versions" -ne 1 ]; then
    echo "Version mismatch: $node_version_list" 1>&2
    log_error "Error: Not all nodes are running the same version! Please ensure all nodes are running the same version before using backup-utils." 1>&2
    exit 1
  fi
fi
set -e

version=$(echo "$output" | grep "GitHub Enterprise" | awk '{print $NF}')

if [ -z "$version" ]; then
  echo "Error: failed to parse version on '$host' or this isn't a GitHub appliance." 1>&2
  exit 2
fi

NON_WRITABLE=""
# ensure all nodes are writable
if [ "$CLUSTER" ]; then
  if [ -z "$GHE_FILE_SYSTEM_WRITE_CHECK" ]; then
    if [ -d "/data/user/tmp" ]; then
      WRITE_CHECK_FILE="/data/user/tmp/test-ro-file.txt"
    else
      WRITE_CHECK_FILE="/tmp/test-ro-file.txt"
    fi
  else
    WRITE_CHECK_FILE="$GHE_FILE_SYSTEM_CHECK/test-ro-file.txt"
  fi

  # Iterate through each node in the cluster
  nodes=$(ghe-ssh "$host" ghe-cluster-nodes)
  for node in $nodes; do
    if ! echo "set -o pipefail; ssh $node -- 'touch $WRITE_CHECK_FILE && rm $WRITE_CHECK_FILE'" | ghe-ssh "$host" /bin/bash; then
      echo "File system is not writeable or no permission on $node" 1>&2
      NON_WRITABLE+="$node "
    fi || true
  done
  # Display the comma-separated list of non-writable nodes
  if [ -n "$NON_WRITABLE" ]; then
    NON_WRITABLE=$(echo "$NON_WRITABLE" | sed 's/ /, /g; s/, $//')
    log_error "Error: Following nodes are non-writable - $NON_WRITABLE. Please make sure the filesystem for all GHES nodes are writable." 1>&2
    exit 1
  else
    log_info "All nodes are writable."
  fi
fi

# Block restoring snapshots to older releases of GitHub Enterprise Server
if [ -n "$GHE_RESTORE_SNAPSHOT_PATH" ]; then
  snapshot_version=$(cat $GHE_RESTORE_SNAPSHOT_PATH/version)
  # shellcheck disable=SC2046 # Word splitting is required to populate the variables
  read -r snapshot_version_major snapshot_version_minor _ <<<$(ghe_parse_version $snapshot_version)
  if [ "$(version $GHE_REMOTE_VERSION)" -lt "$(version $snapshot_version_major.$snapshot_version_minor.0)" ]; then
    echo "Error: Snapshot can not be restored to an older release of GitHub Enterprise Server." >&2
    exit 1
  fi
fi

if [ -z "$GHE_ALLOW_REPLICA_BACKUP" ]; then
  if [ "$(ghe-ssh $host -- cat $GHE_REMOTE_ROOT_DIR/etc/github/repl-state 2>/dev/null || true)" = "replica" ]; then
    echo "Error: high availability replica detected." 1>&2
    echo "Backup Utilities should be used to backup from the primary node in" 1>&2
    echo "high availability environments to ensure consistent and reliable backups." 1>&2
    exit 1
  fi
fi

# backup-utils 2.13 onwards limits support to the current and previous two releases
# of GitHub Enterprise Server.
supported_minimum_version="3.10.0"

if [ "$(version $version)" -ge "$(version $supported_minimum_version)" ]; then
  supported=1
fi

if [ -z "$supported" ]; then
  echo "Error: unsupported release of GitHub Enterprise Server detected." 1>&2
  echo "Backup Utilities v$BACKUP_UTILS_VERSION requires GitHub Enterprise Server v$supported_minimum_version or newer." 1>&2
  echo "Please update your GitHub Enterprise Server appliance or use an older version of Backup Utilities." 1>&2
  exit 1
fi

if [[ "$CALLING_SCRIPT" == "ghe-backup" && "$GHE_SKIP_CHECKS" != "true" ]]; then
  cat << SKIP_MSG
**You can disable the following storage & version checks by running ghe-backup with option "--skip-checks"
OR updating GHE_SKIP_CHECKS to 'true' in your backup.config file.

SKIP_MSG

  # Bring in the requirements file
  min_rsync=""
  min_openssh=""
  min_jq=""
  # shellcheck source=share/github-backup-utils/requirements.txt
  . "$(dirname "${BASH_SOURCE[0]}")/../share/github-backup-utils/requirements.txt"

  #source disk size file
  # shellcheck source=share/github-backup-utils/ghe-rsync-size
  . "$(dirname "${BASH_SOURCE[0]}")/../share/github-backup-utils/ghe-rsync-size"

  #Check if GHE_DATA_DIR is NFS mounted
  fs_info=$(stat -f -c "%T" "$GHE_DATA_DIR") || true
  if [ "$fs_info" == "nfs" ]; then
    echo "Warning: NFS (Network File System) detected for $GHE_DATA_DIR" 1>&2
    echo "Please review https://gh.io/backup-utils-storage-requirements for details." 1>&2
  fi

  #Display dir requirements for repositories and mysql
  echo -e "\nChecking host for sufficient space for a backup..."
  available_space=$(df -B 1k $GHE_DATA_DIR | awk 'END{printf "%.0f", $4 * 1024}')
  echo "  We recommend allocating at least 5x the amount of storage allocated to the primary GitHub appliance for historical snapshots and growth over time."

  repos_disk_size=$(transfer_size repositories /tmp)
  pages_disk_size=$(transfer_size pages /tmp)
  es_disk_size=$(transfer_size elasticsearch /tmp)
  stor_disk_size=$(transfer_size storage /tmp)
  minio_disk_size=$(transfer_size minio /tmp)
  mysql_disk_size=$(transfer_size mysql /tmp)
  actions_disk_size=$(transfer_size actions /tmp)
  mssql_disk_size=$(transfer_size mssql /tmp)

  min_disk_req=$((repos_disk_size + pages_disk_size + es_disk_size + stor_disk_size + minio_disk_size + mysql_disk_size + actions_disk_size + mssql_disk_size))
  recommended_disk_req=$((min_disk_req * 5))
  echo "   - Available space: $((available_space / (1024 ** 2))) MB"
  echo "   - Min Disk required for this backup is at least $min_disk_req MB"
  echo -e "   - Recommended Disk requirement is $recommended_disk_req MB\n"

  printf '### Estimated Data Transfer Sizes

 - repositories: %d MB
 - pages: %d MB
 - elasticsearch: %d MB
 - storage: %d MB
 - minio: %d MB
 - mysql: %d MB
 - actions: %d MB
 - mssql: %d MB
\n' \
  "$repos_disk_size" "$pages_disk_size" "$es_disk_size" "$stor_disk_size" "$minio_disk_size" "$mysql_disk_size" "$actions_disk_size" "$mssql_disk_size"

  if [[ $((available_space / (1024 * 1024))) -lt $min_disk_req ]]; then
    echo "There is not enough disk space for the backup. Please allocate more space and continue." 1>&2
    exit 1
  fi

  #Check rsync, openssh & jq versions
  commands=("jq" "rsync" "ssh")
  missing_command=""
  for cmd in "${commands[@]}"; do
    if ! command -v "$cmd" > /dev/null 2>&1; then
      missing_command+="$cmd "
    fi
  done

  # Check if any command is missing
  if [[ -n "$missing_command" ]]; then
    echo "One or more required tools not found: $missing_command" 1>&2
    echo "Please make sure the following utils are installed and available in your PATH: $missing_command" 1>&2
    exit 1
  fi

  echo "### Software versions"
  rsync_version=$(rsync --version | grep 'version' | awk '{print $3}' | tr -cd '[:digit:].\n')
  if awk "BEGIN {exit !($rsync_version < $min_rsync)}" &> /dev/null; then
    echo "rsync version $rsync_version in backup-host does not meet minimum requirements." 1>&2
    echo "Please make sure you have the minimum required version of rsync: $min_rsync installed" 1>&2
    exit 1
  elif [[ $rsync_version < 3.2.5 ]] && [[ $RSYNC_WARNING != "no" ]]; then
    printf "\n  **WARNING:** rsync version %s on backup host is less than 3.2.5, which could result in performance degradation.
  For more details, please read documentation at https://gh.io/april-2023-update-of-rsync-requirements
  Please note, if your operating system uses a non-standard versioning scheme for packages, then this version check may return incorrect results.
  You can disable this warning by changing RSYNC_WARNING to 'no' in your backup.config file.\n\n" \
    "$rsync_version"
  fi
  echo " - rsync ${rsync_version} >= required ($min_rsync)"

  ssh_version=$(ssh -V 2>&1 | awk '{print $1}' | grep -Eo '[0-9]+\.[0-9]+' | head -1)
  if awk "BEGIN {exit !($ssh_version < $min_openssh)}" &> /dev/null; then
    echo "openSSH version $ssh_version in backup-host does not meet minimum requirements." 1>&2
    echo "Please make sure the minimum required version of openSSH: $min_openssh is installed" 1>&2
    exit 1
  else
    echo " - openSSH ${ssh_version} >= required ($min_openssh)"
  fi

  jq_version=$(jq --version |awk -F\- '{print $2}' | tr -cd '[:digit:].\n')
  if awk "BEGIN {exit !($jq_version < $min_jq)}" &> /dev/null; then
    echo "jq version $jq_version in backup-host does not meet minimum requirements." 1>&2
    echo "Please make sure you have the minimum required version of jq: $min_jq installed" 1>&2
    exit 1
  else
    echo " - jq ${jq_version} >= required ($min_jq)"
  fi
fi

echo -e "\nConnect $hostname:$port OK (v$version)"
