#!/usr/bin/env bash
# Support-only launcher for legacy RapidIdentity appliance migrations.

set -Eeuo pipefail
IFS=$'\n\t'

readonly LAUNCHER_VERSION="2026.08.04.2"
readonly DEFAULT_ARTIFACT_BASE_URL="https://dudkgm4ae3xpz.cloudfront.net"
readonly BUNDLE_ARCHIVE="migrate-appliance.tar.gz"
readonly BUNDLE_SHA256="80fb91aed8bb871a80ce3a5b15191d83bf0c02b6ebd4bc459106eb448b5fee44"
readonly PG11_PREFLIGHT="ri-2019-pg11-preflight.sh"
readonly PG11_PREFLIGHT_SHA256="31de37dd0e5ca6ce9dd61d48e51d00e2e03a38a761c6bc73550b6d5a8a0273be"
readonly LEGACY_SSH_KEY_PASSPHRASE="XfJpYw4tKq!!75dSGrXbCzrR"

ARTIFACT_BASE_URL="${MIGRATION_ARTIFACT_BASE_URL:-$DEFAULT_ARTIFACT_BASE_URL}"
WORKDIR="${MIGRATION_WORKDIR:-/var/tmp/ri-legacy-migration-$(date +%Y%m%d-%H%M%S)}"
SECRET_FILE=""
ASKPASS_FILE=""

info() {
  printf '[INFO] %s\n' "$*"
}

warn() {
  printf '[WARN] %s\n' "$*" >&2
}

die() {
  printf '[ERROR] %s\n' "$*" >&2
  exit 1
}

cleanup_sensitive() {
  if [[ -n "$SECRET_FILE" ]]; then
    rm -f -- "$SECRET_FILE"
  fi
  if [[ -n "$ASKPASS_FILE" ]]; then
    rm -f -- "$ASKPASS_FILE"
  fi
  unset MIGRATION_ASKPASS_SECRET_FILE || true
}
trap cleanup_sensitive EXIT

require_root() {
  [[ "${EUID}" -eq 0 ]] || die "Run this launcher as root from technician mode."
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "Required command is unavailable: $1"
}

confirm() {
  local prompt="$1"
  local answer

  while true; do
    read -r -p "$prompt [yes/no]: " answer
    case "${answer,,}" in
      yes|y) return 0 ;;
      no|n) return 1 ;;
      *) printf 'Please answer yes or no.\n' ;;
    esac
  done
}

prompt_host() {
  local prompt="$1"
  local value

  while true; do
    read -r -p "$prompt: " value
    if [[ "$value" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
      REPLY="$value"
      return 0
    fi
    printf 'Enter an IPv4 address or a simple hostname.\n'
  done
}

valid_ipv4() {
  local ip="$1"
  local octet
  local -a octets

  [[ "$ip" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || return 1
  IFS='.' read -r -a octets <<< "$ip"
  for octet in "${octets[@]}"; do
    (( 10#$octet <= 255 )) || return 1
  done
}

prompt_ipv4() {
  local prompt="$1"
  local value

  while true; do
    read -r -p "$prompt: " value
    if valid_ipv4 "$value"; then
      REPLY="$value"
      return 0
    fi
    printf 'Enter a valid IPv4 address.\n'
  done
}

download() {
  local object_name="$1"
  local destination="$2"
  local url="${ARTIFACT_BASE_URL%/}/$object_name"

  info "Downloading $object_name"
  curl --fail --location --proto '=https' --tlsv1.2 --retry 3 --show-error \
    --output "$destination" "$url"
}

verify_sha256() {
  local file="$1"
  local expected="$2"
  local actual

  actual="$(sha256sum "$file" | awk '{print $1}')"
  [[ "${actual,,}" == "${expected,,}" ]] || die "Checksum verification failed for $(basename "$file")."
  info "Checksum verified for $(basename "$file")"
}

idautodb_addresses() {
  awk '
    $0 !~ /^[[:space:]]*#/ {
      for (i = 2; i <= NF; i++) {
        if ($i ~ /^#/) {
          break
        }
        if ($i == "idautodb") {
          print $1
          break
        }
      }
    }
  ' /etc/hosts
}

restore_hosts_and_die() {
  local backup_file="$1"
  local failure_message="$2"

  if cat "$backup_file" > /etc/hosts; then
    die "$failure_message Restored the original file from $backup_file."
  fi
  die "$failure_message Automatic restoration also failed; the original backup remains at $backup_file."
}

set_idautodb_alias() {
  local db_ip="$1"
  local backup_file="$WORKDIR/hosts.before-idautodb"
  local candidate_file="$WORKDIR/hosts.with-idautodb"
  local resulting_aliases

  cp -p /etc/hosts "$backup_file"
  awk '
    /^[[:space:]]*#/ {
      print
      next
    }
    {
      hash_position = index($0, "#")
      if (hash_position > 0) {
        data = substr($0, 1, hash_position - 1)
        comment = substr($0, hash_position)
      } else {
        data = $0
        comment = ""
      }

      field_count = split(data, fields, /[[:space:]]+/)
      found_alias = 0
      kept_fields = 0
      output = ""
      for (i = 1; i <= field_count; i++) {
        if (fields[i] == "") {
          continue
        }
        if (fields[i] == "idautodb") {
          found_alias = 1
          continue
        }
        kept_fields++
        output = output (output == "" ? "" : " ") fields[i]
      }

      if (!found_alias) {
        print
      } else if (kept_fields >= 2) {
        print output (comment == "" ? "" : " " comment)
      } else if (comment != "") {
        print comment
      }
    }
  ' /etc/hosts > "$candidate_file"
  printf '\n%s idautodb\n' "$db_ip" >> "$candidate_file"

  if ! cat "$candidate_file" > /etc/hosts; then
    restore_hosts_and_die "$backup_file" "Could not update /etc/hosts."
  fi

  resulting_aliases="$(idautodb_addresses || true)"
  if [[ "$resulting_aliases" != "$db_ip" ]] ||
     ! getent ahostsv4 idautodb 2>/dev/null | awk -v expected="$db_ip" '
         $1 == expected { found = 1 }
         END { exit(found ? 0 : 1) }
       '; then
    restore_hosts_and_die "$backup_file" "idautodb verification failed after the hosts-file update."
  fi

  info "Set idautodb -> $db_ip in /etc/hosts. Backup: $backup_file"
}

prepare_askpass() {
  local passphrase="$1"

  SECRET_FILE="$WORKDIR/.legacy-ssh-key-passphrase"
  ASKPASS_FILE="$WORKDIR/.ssh-askpass"
  umask 077
  printf '%s\n' "$passphrase" > "$SECRET_FILE"
  cat > "$ASKPASS_FILE" <<'EOF'
#!/bin/sh
set -eu
cat "${MIGRATION_ASKPASS_SECRET_FILE:?}"
EOF
  chmod 700 "$ASKPASS_FILE"
  export MIGRATION_ASKPASS_SECRET_FILE="$SECRET_FILE"
}

stage_artifacts() {
  mkdir -p "$WORKDIR"
  chmod 700 "$WORKDIR"

  download "$BUNDLE_ARCHIVE" "$WORKDIR/$BUNDLE_ARCHIVE"
  verify_sha256 "$WORKDIR/$BUNDLE_ARCHIVE" "$BUNDLE_SHA256"
  tar -xzf "$WORKDIR/$BUNDLE_ARCHIVE" -C "$WORKDIR"
  [[ -f "$WORKDIR/migrate-appliance.sh" ]] || die "The migration bundle did not contain migrate-appliance.sh."
  chmod 700 "$WORKDIR/migrate-appliance.sh"

  download "$PG11_PREFLIGHT" "$WORKDIR/$PG11_PREFLIGHT"
  verify_sha256 "$WORKDIR/$PG11_PREFLIGHT" "$PG11_PREFLIGHT_SHA256"
  chmod 700 "$WORKDIR/$PG11_PREFLIGHT"

  cat > "$WORKDIR/launcher-state.txt" <<EOF
Launcher version: $LAUNCHER_VERSION
Artifact base URL: $ARTIFACT_BASE_URL
Bundle checksum: $BUNDLE_SHA256
PG11 preflight checksum: $PG11_PREFLIGHT_SHA256
EOF
}

configure_idautodb_alias() {
  local current_aliases

  prompt_ipv4 "Enter the new DB appliance temporary IP"
  DB_IP="$REPLY"
  current_aliases="$(idautodb_addresses || true)"

  if [[ "$current_aliases" == "$DB_IP" ]]; then
    info "idautodb already points only to $DB_IP; no hosts-file change is needed."
  elif [[ -z "$current_aliases" ]]; then
    info "idautodb is absent; adding the new DB appliance mapping automatically."
    set_idautodb_alias "$DB_IP"
  else
    warn "Replacing stale or duplicate idautodb mapping(s): ${current_aliases//$'\n'/, }"
    set_idautodb_alias "$DB_IP"
  fi

  TARGET_DB_HOST="idautodb"
}

run_preflight() {
  info "Running the PG11 target preflight before the DB migration."
  (
    cd "$WORKDIR"
    bash "./$PG11_PREFLIGHT" --prepare-new-target-db
  )
}

run_migration() {
  local legacy_key_passphrase="$LEGACY_SSH_KEY_PASSPHRASE"
  local -a command
  local status

  prepare_askpass "$legacy_key_passphrase"
  unset legacy_key_passphrase

  command=(bash "$WORKDIR/migrate-appliance.sh" "--src-host=$SOURCE_HOST" "--target-db-host=$TARGET_DB_HOST")
  if [[ "$IS_DB" == "yes" ]]; then
    command+=(--migrate-db)
  fi

  info "Starting the original migration bundle. The passphrase is supplied through a root-only temporary SSH_ASKPASS file and is removed when this launcher exits."
  set +e
  if [[ "$IS_DB" == "yes" ]]; then
    PATH="/usr/pgsql-11/bin:$PATH" \
      SSH_ASKPASS="$ASKPASS_FILE" \
      SSH_ASKPASS_REQUIRE=force \
      DISPLAY="${DISPLAY:-:0}" \
      MIGRATION_ASKPASS_SECRET_FILE="$SECRET_FILE" \
      "${command[@]}" </dev/null
  else
    SSH_ASKPASS="$ASKPASS_FILE" \
      SSH_ASKPASS_REQUIRE=force \
      DISPLAY="${DISPLAY:-:0}" \
      MIGRATION_ASKPASS_SECRET_FILE="$SECRET_FILE" \
      "${command[@]}" </dev/null
  fi
  status=$?
  set -e

  cleanup_sensitive
  if (( status != 0 )); then
    die "The original migration bundle exited with status $status. Preserve $WORKDIR and investigate before retrying."
  fi
}

main() {
  require_root
  require_command awk
  require_command bash
  require_command curl
  require_command getent
  require_command sha256sum
  require_command tar

  printf '\nRapidIdentity legacy migration launcher %s\n\n' "$LAUNCHER_VERSION"
  info "Artifacts will be staged in $WORKDIR"
  stage_artifacts

  if confirm "Is this the new DB appliance?"; then
    IS_DB="yes"
    TARGET_DB_HOST="localhost"
  else
    IS_DB="no"
    configure_idautodb_alias
  fi

  prompt_host "Enter the legacy source appliance IP or hostname"
  SOURCE_HOST="$REPLY"

  if [[ "$IS_DB" == "yes" ]]; then
    printf '\nThis is the DB path. The launcher will run the PG11 preflight and call the original bundle with --migrate-db and --target-db-host=localhost.\n'
    if confirm "Run the PG11 target preflight now?"; then
      run_preflight
    else
      die "The DB migration cannot continue without the documented PG11 target preflight."
    fi
  else
    printf '\nThis is a non-DB path. It will use --target-db-host=%s.\n' "$TARGET_DB_HOST"
    if ! getent hosts "$TARGET_DB_HOST" >/dev/null 2>&1; then
      warn "The selected DB target does not currently resolve through getent: $TARGET_DB_HOST"
    fi
    if ! confirm "Has the new DB appliance migration completed and is the target DB reachable?"; then
      die "Migrate the DB appliance first, then rerun this launcher on the non-DB appliance."
    fi
  fi

  cat >> "$WORKDIR/launcher-state.txt" <<EOF
Role: $IS_DB
Legacy source: $SOURCE_HOST
Target DB: $TARGET_DB_HOST
EOF

  printf '\nStaging is complete. Stop RapidIdentity on the legacy source appliance before continuing.\n'
  printf 'Source appliance: %s\nTarget DB host: %s\nWork directory: %s\n\n' "$SOURCE_HOST" "$TARGET_DB_HOST" "$WORKDIR"
  if ! confirm "Are you ready to start the original migration now?"; then
    info "No migration was started. Staged files remain in $WORKDIR."
    exit 0
  fi

  run_migration
  printf '\nMigration bundle completed successfully. Keep %s until post-migration validation is accepted.\n' "$WORKDIR"
  printf 'After validation, remove launcher artifacts with: rm -rf %q\n' "$WORKDIR"
}

main "$@"
