#!/bin/sh
# Copyright (c) The OpenTofu Authors
# SPDX-License-Identifier: Apache-2.0

# OpenTofu Installer
#
# This script installs OpenTofu via any of the supported methods.
# License: https://github.com/opentofu/get.opentofu.org/blob/main/LICENSE

export TOFU_INSTALL_EXIT_CODE_OK=0
export TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET=1
export TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED=2
export TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT=3

export TOFU_INSTALL_RETURN_CODE_COMMAND_NOT_FOUND=11
export TOFU_INSTALL_RETURN_CODE_DOWNLOAD_FAILED=13

bold=""
normal=""
red=""
green=""
yellow=""
blue=""
magenta=""
cyan=""
gray=""
if [ -t 1 ]; then
    if command -v "tput" >/dev/null 2>&1; then
      colors=$(tput colors)
    else
      colors=2
    fi

    if [ "${colors}" -ge 8 ]; then
        bold="$(tput bold)"
        normal="$(tput sgr0)"
        red="$(tput setaf 1)"
        green="$(tput setaf 2)"
        yellow="$(tput setaf 3)"
        blue="$(tput setaf 4)"
        magenta="$(tput setaf 5)"
        cyan="$(tput setaf 6)"
        gray="$(tput setaf 245)"
    fi
fi

ROOT_METHOD="auto"
INSTALL_METHOD=""
DEFAULT_INSTALL_PATH="/opt/opentofu"
INSTALL_PATH="${DEFAULT_INSTALL_PATH}"
DEFAULT_SYMLINK_PATH="/usr/local/bin"
SYMLINK_PATH="${DEFAULT_SYMLINK_PATH}"
DEFAULT_OPENTOFU_VERSION="latest"
OPENTOFU_VERSION="${DEFAULT_OPENTOFU_VERSION}"
DEFAULT_DEB_GPG_URL="https://get.opentofu.org/opentofu.gpg"
DEB_GPG_URL="${DEFAULT_DEB_GPG_URL}"
DEFAULT_DEB_REPO_GPG_URL="https://packages.opentofu.org/opentofu/tofu/gpgkey"
DEB_REPO_GPG_URL="${DEFAULT_DEB_REPO_GPG_URL}"
DEFAULT_DEB_REPO_URL="https://packages.opentofu.org/opentofu/tofu/any/"
DEB_REPO_URL="${DEFAULT_DEB_REPO_URL}"
DEFAULT_DEB_REPO_SUITE="any"
DEB_REPO_SUITE="${DEFAULT_DEB_REPO_SUITE}"
DEFAULT_DEB_REPO_COMPONENTS="main"
DEB_REPO_COMPONENTS="${DEFAULT_DEB_REPO_COMPONENTS}"
DEFAULT_RPM_REPO_URL="https://packages.opentofu.org/opentofu/tofu/rpm_any/rpm_any/"

RPM_REPO_URL="${DEFAULT_RPM_REPO_URL}"
DEFAULT_RPM_REPO_GPG_URL="https://packages.opentofu.org/opentofu/tofu/gpgkey"
DEFAULT_RPM_GPG_URL="https://get.opentofu.org/opentofu.asc"
RPM_GPG_URL="${DEFAULT_RPM_GPG_URL}"
RPM_REPO_GPG_URL="${DEFAULT_RPM_REPO_GPG_URL}"
DEFAULT_APK_REPO_URL="@community https://dl-cdn.alpinelinux.org/alpine/edge/community"
APK_REPO_URL="${DEFAULT_APK_REPO_URL}"
DEFAULT_APK_PACKAGE="opentofu@community"
APK_PACKAGE="${DEFAULT_APK_PACKAGE}"
DEFAULT_GPG_PATH="gpg"
GPG_PATH="${DEFAULT_GPG_PATH}"
DEFAULT_GPG_URL="https://get.opentofu.org/opentofu.asc"
GPG_URL="https://get.opentofu.org/opentofu.asc"
DEFAULT_GPG_KEY_ID="E3E6E43D84CB852EADB0051D0C0AF313E5FD9F80"
GPG_KEY_ID="${DEFAULT_GPG_KEY_ID}"
DEFAULT_COSIGN_PATH="cosign"
COSIGN_PATH="${DEFAULT_COSIGN_PATH}"
DEFAULT_COSIGN_IDENTITY="autodetect"
COSIGN_IDENTITY="${DEFAULT_COSIGN_IDENTITY}"
DEFAULT_COSIGN_OIDC_ISSUER="https://token.actions.githubusercontent.com"
COSIGN_OIDC_ISSUER="${DEFAULT_COSIGN_OIDC_ISSUER}"
SKIP_VERIFY=0

# region ZSH
if [ -n "${ZSH_VERSION}" ]; then
  ## Enable POSIX-style word splitting:
  setopt SH_WORD_SPLIT >/dev/null 2>&1
fi
# endregion

log_success() {
  if [ -z "$1" ]; then
    return
  fi
  echo "${green}$1${normal}" 1>&2
}

log_warning() {
  if [ -z "$1" ]; then
    return
  fi
  echo "${yellow}$1${normal}" 1>&2
}

log_info() {
  if [ -z "$1" ]; then
    return
  fi
  echo "${cyan}$1${normal}" 1>&2
}

log_debug() {
  if [ -z "$1" ]; then
    return
  fi
  if [ -z "${LOG_DEBUG}" ]; then
    return
  fi
  echo "${gray}$1${normal}" 1>&2
}

log_error() {
  if [ -z "$1" ]; then
    return
  fi
  echo "${red}$1${normal}" 1>&2
}

# This function checks if the command specified in $1 exists.
command_exists() {
  log_debug "Determining if the ${1} command is available..."
  if [ -z "$1" ]; then
    log_error "Bug: no command supplied to command_exists()"
    return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
  fi
  if ! command -v "$1" >/dev/null 2>&1; then
    log_debug "The ${1} command is not available."
    return "${TOFU_INSTALL_RETURN_CODE_COMMAND_NOT_FOUND}"
  fi
  log_debug "The ${1} command is available."
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

is_root() {
  if [ "$(id -u || true)" -eq 0 ]; then
    return 0
  fi
  return 1
}

# This function runs the specified command as root.
as_root() {
  # shellcheck disable=SC2145
  log_debug "Running command as root: $*"
  case "${ROOT_METHOD}" in
    auto)
      log_debug "Automatically determining root method..."
      if is_root; then
        log_debug "We are already root, no user change needed."
        "$@"
      elif command_exists "sudo"; then
        log_debug "Running command using sudo."
        sudo "$@"
      elif command_exists "su"; then
        log_debug "Running command using su."
        su root "$@"
      else
        log_error "Neither su nor sudo is installed, cannot obtain root privileges."
        return "${TOFU_INSTALL_RETURN_CODE_COMMAND_NOT_FOUND}"
      fi
      return $?
      ;;
    none)
      log_debug "Using manual root method 'none'."
      "$@"
      return $?
      ;;
    sudo)
      log_debug "Using manual root method 'sudo'."
      sudo "$@"
      return $?
      ;;
    su)
      log_debug "Using manual root method 'su'."
      su root "$@"
      return $?
      ;;
    *)
      log_error "Bug: invalid root method value: $1"
      return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
  esac
}

# This function attempts to execute a function as the current user and switches to root if it fails.
maybe_root() {
  if ! "$@" >/dev/null 2>&1; then
    if ! as_root "$@"; then
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
  fi
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

# This function verifies if one of the supported download tools is installed and returns with
# $TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET if that is not th ecase.
download_tool_exists() {
    log_debug "Determining if a supported download tool is installed..."
    if command_exists "wget"; then
      log_debug "wget is installed."
      return "${TOFU_INSTALL_EXIT_CODE_OK}"
    elif command_exists "curl"; then
      log_debug "curl is installed."
      return "${TOFU_INSTALL_EXIT_CODE_OK}"
    else
      log_debug "No supported download tool is installed."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
    fi
}

# This function downloads the URL specified in $1 into the file specified in $2.
# It returns $TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET if no supported download tool is installed, or $TOFU_INSTALL_RETURN_CODE_DOWNLOAD_FAILED
# if the download failed.
download_file() {
  if [ -z "$1" ]; then
    log_error "Bug: no URL supplied to download_file()"
    return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
  fi
  if [ -z "$2" ]; then
    log_error "Bug: no destination file supplied to download_file()"
    return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
  fi
  log_debug "Downloading URL ${1} to ${2}..."
  IS_GITHUB=0
  if [ -n "${GITHUB_TOKEN}" ]; then
    if [ "$(echo "$1" | grep -c "api.github.com" || true)" -ne 0 ]; then
      IS_GITHUB=1
    fi
  fi
  if command_exists "wget"; then
    if [ "${IS_GITHUB}" -eq 1 ]; then
      log_debug "Downloading using wget with GITHUB_TOKEN..."
      if ! wget -q --header="Authorization: token ${GITHUB_TOKEN}" -O "$2" "$1"; then
        log_debug "Download failed."
        return "${TOFU_INSTALL_RETURN_CODE_DOWNLOAD_FAILED}"
      fi
    else
      log_debug "Downloading using wget without GITHUB_TOKEN, this may lead to rate limit issues..."
      if ! wget -q -O "$2" "$1"; then
        log_debug "Download failed, please try specifying the GITHUB_TOKEN environment variable."
        return "${TOFU_INSTALL_RETURN_CODE_DOWNLOAD_FAILED}"
      fi
    fi
  elif command_exists "curl"; then
    if [ "${IS_GITHUB}" -eq 1 ]; then
      log_debug "Downloading using curl with GITHUB_TOKEN..."
      if ! curl --proto '=https' --tlsv1.2 -fsSL -H "Authorization: token ${GITHUB_TOKEN}" -o "$2" "$1"; then
        log_debug "Download failed."
        return "${TOFU_INSTALL_RETURN_CODE_DOWNLOAD_FAILED}"
      fi
    else
      log_debug "Downloading using curl without GITHUB_TOKEN, this may lead to rate limit issues..."
      if ! curl --proto '=https' --tlsv1.2 -fsSL -o "$2" "$1"; then
        log_debug "Download failed, please try specifying the GITHUB_TOKEN environment variable."
        return "${TOFU_INSTALL_RETURN_CODE_DOWNLOAD_FAILED}"
      fi
    fi
  else
    log_error "Neither wget nor curl are available on your system. Please install one of them to proceed."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
  fi
  log_debug "Download successful."
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

# This function downloads the OpenTofu GPG key from the specified URL to the specified location. Setting the third
# parameter to 1 causes the file to be moved as root. It returns $TOFU_INSTALL_RETURN_CODE_DOWNLOAD_FAILED if the
# download fails, or $TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET if no download tool is available.
download_gpg() {
  if [ -z "$1" ]; then
    log_error "Bug: no URL passed to download_gpg."
    return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
  fi
  if [ -z "$2" ]; then
    log_error "Bug: no destination passed to download_gpg."
    return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
  fi
  if ! command_exists "gpg"; then
    log_error "Missing gpg binary."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
  fi
  log_debug "Downloading GPG key from ${1} to ${2}..."
  if ! download_tool_exists; then
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
  fi
  log_debug "Creating temporary directory..."
  TEMPDIR=$(mktemp -d)
  if [ -z "${TEMPDIR}" ]; then
    log_error "Failed to create temporary directory for GPG download."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  TEMPFILE="${TEMPDIR}/opentofu.gpg"

  if ! download_file "${1}" "${TEMPFILE}"; then
    log_debug "Removing temporary directory..."
    rm -rf "${TEMPFILE}"
    return "${TOFU_INSTALL_RETURN_CODE_DOWNLOAD_FAILED}"
  fi
  if [ "$(grep 'BEGIN PGP PUBLIC KEY BLOCK' -c "${TEMPFILE}" || true)" -ne 0 ]; then
    log_debug "Performing GPG dearmor on ${TEMPFILE}"
    if ! gpg --no-tty --batch --dearmor -o "${TEMPFILE}.tmp" <"${TEMPFILE}"; then
      log_error "Failed to GPG dearmor ${TEMPFILE}."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
    if ! mv "${TEMPFILE}.tmp" "${TEMPFILE}"; then
      log_error "Failed to move ${TEMPFILE}.tmp to ${TEMPFILE}."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
  fi
  if [ "$3" = "1" ]; then
    log_debug "Moving GPG file as root..."
    if ! as_root mv "${TEMPFILE}" "${2}"; then
      log_error "Failed to move ${TEMPFILE} to ${2}."
      rm -rf "${TEMPFILE}"
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
  else
    log_debug "Moving GPG file as the current user..."
    if ! mv "${TEMPFILE}" "${2}"; then
      log_error "Failed to move ${TEMPFILE} to ${2}."
      rm -rf "${TEMPFILE}"
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
  fi

  log_debug "Removing temporary directory..."
  rm -rf "${TEMPFILE}"
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

# This is a helper function that downloads a GPG URL to the specified file.
deb_download_gpg() {
  DEB_GPG_URL="${1}"
  GPG_FILE="${2}"
  if [ -z "${DEB_GPG_URL}" ]; then
    log_error "Bug: no GPG URL specified for deb_download_gpg."
    return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
  fi
  if [ -z "${GPG_FILE}" ]; then
    log_error "Bug: no destination path specified for deb_download_gpg."
    return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
  fi
  if ! download_gpg "${DEB_GPG_URL}" "${GPG_FILE}" 1; then
    log_error "Failed to download GPG key from ${DEB_GPG_URL}."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  log_debug "Changing ownership and permissions of ${GPG_FILE}..."
  if ! as_root chown root:root "${GPG_FILE}"; then
    log_error "Failed to chown ${GPG_FILE}."
    rm -rf "${GPG_FILE}"
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  if ! as_root chmod a+r "${GPG_FILE}"; then
    log_error "Failed to chmod ${GPG_FILE}."
    rm -rf "${GPG_FILE}"
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

# This function installs OpenTofu via a Debian repository. It returns
# $TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET if this is not a Debian system.
install_deb() {
  log_info "Attempting installation via Debian repository..."
  if ! command_exists apt-get; then
    log_info "The apt-get command is not available, skipping Debian repository installation."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
  fi

  if ! is_root; then
    log_info "Root privileges are required to install OpenTofu as a Debian package."
    log_info "The installer will now verify if it can correctly assume root privileges."
    log_info "${bold}You may be asked to enter your password.${normal}"
    if ! as_root echo -n ""; then
      log_error "Cannot assume root privileges."
      log_info "Please set up either '${bold}su${normal}' or '${bold}sudo${normal}'."
      log_info "Alternatively, run this script with ${bold}-h${normal} for other installation methods."
    fi
  fi

  log_info "Updating package list..."
  if ! as_root apt-get update; then
    log_error "Failed to update apt package list."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi

  log_debug "Determining packages to install..."
  PACKAGE_LIST="apt-transport-https ca-certificates"
  if [ "${SKIP_VERIFY}" -ne "1" ]; then
    PACKAGE_LIST="${PACKAGE_LIST} gnupg"
  fi
  if ! download_tool_exists; then
    log_debug "No download tool present, adding curl to the package list..."
    PACKAGE_LIST="${PACKAGE_LIST} curl"
  fi

  log_info "Installing necessary packages for installation..."
  log_debug "Installing ${PACKAGE_LIST}..."
  # shellcheck disable=SC2086
  if ! as_root apt-get install -y ${PACKAGE_LIST}; then
    log_error "Failed to install requisite packages for Debian repository installation."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  log_debug "Necessary packages installed."

  if [ "${SKIP_VERIFY}" -ne "1" ]; then
    log_info "Installing the OpenTofu GPG keys..."
    log_debug "Creating /etc/apt/keyrings..."
    if ! as_root install -m 0755 -d /etc/apt/keyrings; then
      log_error "Failed to create /etc/apt/keyrings."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
    log_debug "Created /etc/apt/keyrings."

    PACKAGE_GPG_FILE=/etc/apt/keyrings/opentofu.gpg
    log_debug "Downloading the GPG key from ${DEB_GPG_URL}.."
    if ! deb_download_gpg "${DEB_GPG_URL}" "${PACKAGE_GPG_FILE}"; then
      log_error "Failed to download GPG key from ${DEB_GPG_URL}."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
    if [ -n "${DEB_REPO_GPG_URL}" ] && [ "${DEB_REPO_GPG_URL}" != "-" ]; then
      log_debug "Downloading the repo GPG key from ${DEB_REPO_GPG_URL}.."
      REPO_GPG_FILE=/etc/apt/keyrings/opentofu-repo.gpg
      if ! deb_download_gpg "${DEB_REPO_GPG_URL}" "${REPO_GPG_FILE}" 1; then
        log_error "Failed to download GPG key from ${DEB_REPO_GPG_URL}."
        return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
      fi
    fi
  fi

  log_info "Creating OpenTofu sources list..."
  if [ "${SKIP_VERIFY}" -ne "1" ]; then
    if [ -n "${REPO_GPG_FILE}" ]; then
      if ! as_root tee /etc/apt/sources.list.d/opentofu.list; then
        log_error "Failed to create /etc/apt/sources.list.d/opentofu.list."
        return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
      fi <<EOF
deb [signed-by=${PACKAGE_GPG_FILE},${REPO_GPG_FILE}] ${DEB_REPO_URL} ${DEB_REPO_SUITE} ${DEB_REPO_COMPONENTS}
deb-src [signed-by=${PACKAGE_GPG_FILE},${REPO_GPG_FILE}] ${DEB_REPO_URL} ${DEB_REPO_SUITE} ${DEB_REPO_COMPONENTS}
EOF
    else
      if ! as_root tee /etc/apt/sources.list.d/opentofu.list; then
        log_error "Failed to create /etc/apt/sources.list.d/opentofu.list."
        return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
      fi <<EOF
deb [signed-by=${PACKAGE_GPG_FILE}] ${DEB_REPO_URL} ${DEB_REPO_SUITE} ${DEB_REPO_COMPONENTS}
deb-src [signed-by=${PACKAGE_GPG_FILE}] ${DEB_REPO_URL} ${DEB_REPO_SUITE} ${DEB_REPO_COMPONENTS}
EOF
    fi
  else
    if ! as_root tee /etc/apt/sources.list.d/opentofu.list; then
      log_error "Failed to create /etc/apt/sources.list.d/opentofu.list."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi <<EOF
deb [trusted] ${DEB_REPO_URL} ${DEB_REPO_SUITE} ${DEB_REPO_COMPONENTS}
deb-src [trusted] ${DEB_REPO_URL} ${DEB_REPO_SUITE} ${DEB_REPO_COMPONENTS}
EOF
  fi

  log_info "Updating package list..."
  if ! as_root apt-get update; then
    log_error "Failed to update apt package list."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  log_info "Installing OpenTofu..."
  if ! as_root apt-get install -y tofu; then
    log_error "Failed to install opentofu."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi

  log_info "Checking if OpenTofu is installed correctly..."
  if ! tofu --version > /dev/null; then
    log_error "Failed to run tofu after installation."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

# This function installs OpenTofu via the zypper command line utility. It returns
# $TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET if zypper is not available.
install_zypper() {
  if ! command_exists "zypper"; then
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
  fi
  log_info "Installing OpenTofu using zypper..."
  if [ "${SKIP_VERIFY}" -ne "1" ]; then
    GPGCHECK=1
    FINAL_GPG_URL="${RPM_GPG_URL}"
    if [ "${RPM_REPO_GPG_URL}" != "-" ]; then
      FINAL_GPG_URL=$(cat <<EOF
${FINAL_GPG_URL}
       ${RPM_REPO_GPG_URL}
EOF
)
    fi
  else
    GPGCHECK=0
    FINAL_GPG_URL=""
  fi
  if ! as_root tee /etc/zypp/repos.d/opentofu.repo; then
    log_error "Failed to write /etc/zypp/repos.d/opentofu.repo"
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi <<EOF
[opentofu]
name=opentofu
baseurl=${RPM_REPO_URL}\$basearch
repo_gpgcheck=${GPGCHECK}
gpgcheck=${GPGCHECK}
enabled=1
gpgkey=${FINAL_GPG_URL}
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300

[opentofu-source]
name=opentofu-source
baseurl=${RPM_REPO_URL}SRPMS
repo_gpgcheck=${GPGCHECK}
gpgcheck=${GPGCHECK}
enabled=1
gpgkey=${FINAL_GPG_URL}
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300
EOF
  for GPG_SRC in ${FINAL_GPG_URL}; do
    log_debug "Importing GPG key from ${GPG_SRC}..."
    if ! as_root rpm --import "${GPG_SRC}"; then
      log_error "Failed to import GPG key from ${GPG_SRC}."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
  done
  for REPO in opentofu opentofu-source; do
    log_debug "Importing GPG key for repo ${REPO} into zypper..."
    if ! as_root zypper --gpg-auto-import-keys refresh opentofu; then
      log_error "Failed to auto-import GPG key for repo ${REPO} into zypper."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
  done
  if ! as_root zypper install -y tofu; then
    log_error "Failed to install tofu via zypper."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  if ! tofu --version; then
    log_error "Failed to run tofu after installation."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

# This function installs OpenTofu via the yum command line utility. It returns $TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET
# if yum is not available.
install_yum() {
  if ! command_exists "yum"; then
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
  fi
  log_info "Installing OpenTofu using yum..."
  if [ "${SKIP_VERIFY}" -ne "1" ]; then
    GPGCHECK=1
    FINAL_GPG_URL="${RPM_GPG_URL}"
    if [ "${RPM_REPO_GPG_URL}" != "-" ]; then
      FINAL_GPG_URL=$(cat <<EOF
${FINAL_GPG_URL}
       ${RPM_REPO_GPG_URL}
EOF
)
    fi
  else
    GPGCHECK=0
    GPG_URL=""
  fi
  if ! as_root tee /etc/yum.repos.d/opentofu.repo; then
    log_error "Failed to write /etc/yum.repos.d/opentofu.repo"
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi <<EOF
[opentofu]
name=opentofu
baseurl=${RPM_REPO_URL}\$basearch
repo_gpgcheck=${GPGCHECK}
gpgcheck=${GPGCHECK}
enabled=1
gpgkey=${FINAL_GPG_URL}
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300

[opentofu-source]
name=opentofu-source
baseurl=${RPM_REPO_URL}SRPMS
repo_gpgcheck=${GPGCHECK}
gpgcheck=${GPGCHECK}
enabled=1
gpgkey=${FINAL_GPG_URL}
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300
EOF
  for GPG_SRC in ${FINAL_GPG_URL}; do
    log_debug "Importing GPG key from ${GPG_SRC}..."
    if ! as_root rpm --import "${GPG_SRC}"; then
      log_error "Failed to import GPG key from ${GPG_SRC}."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
  done
  if ! as_root yum install -y tofu; then
    log_error "Failed to install tofu via yum."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  if ! tofu --version; then
    log_error "Failed to run tofu after installation."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

# This function installs OpenTofu via an RPM repository. It returns $TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET
# if this is not an RPM-based system.
install_rpm() {
  if command_exists "zypper"; then
    install_zypper
    return $?
  else
    install_yum
    return $?
  fi
}

# This function installs OpenTofu via an APK (Alpine Linux) package. It returns
# $TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET if this is not an Alpine Linux system.
install_apk() {
  if ! command_exists "apk"; then
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
  fi
  log_info "Installing OpenTofu using APK..."
  if [ "${APK_REPO_URL}" != "-" ]; then
    APK_REPO_PARAM="--repository=${APK_REPO_URL}"
  else
    APK_REPO_PARAM=""
  fi
  if ! apk add "${APK_PACKAGE}" "${APK_REPO_PARAM}"; then
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  if ! tofu --version; then
    log_error "Failed to run tofu after installation."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

# This function installs OpenTofu via Snapcraft. It returns $TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET if
# Snap is not available.
install_snap() {
  if ! command_exists "snap"; then
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
  fi
  log_info "Installing OpenTofu using Snap..."
  if ! as_root snap install --classic opentofu; then
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  if ! tofu --version; then
    log_error "Failed to run tofu after installation."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

# This function installs OpenTofu via Homebrew. It returns $TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET if
# Homebrew is not available.
install_brew() {
  if ! command_exists "brew"; then
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
  fi
  log_info "Installing OpenTofu using Homebrew..."
  log_info "Updating brew..."
  if ! brew update; then
    log_info "brew update failed."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  if ! brew install opentofu; then
    log_info "brew install opentofu failed."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  if ! tofu --version; then
    log_error "Failed to run tofu after installation."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

# This function installs OpenTofu as a standalone installation. It returns $TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED if the installation
# was unsuccessful.
install_standalone() {
  if ! download_tool_exists; then
    log_error "Neither wget nor curl are available on your system. Please install at least one of them to proceed."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
  fi
  if ! command_exists "unzip"; then
    log_warning "Unzip is missing, please install it to use the standalone installation method."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
  fi
  if ! command_exists "shasum"; then
    if ! command_exists "sha256sum"; then
      log_warning "shasum is missing, please install it to use the standalone installation method."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
    fi
  fi

  VERIFY_METHOD=-
  if [ "${SKIP_VERIFY}" -ne "1" ]; then
    if command_exists "${COSIGN_PATH}"; then
      VERIFY_METHOD="cosign"
    elif command_exists "${GPG_PATH}"; then
      VERIFY_METHOD="gpg"
    else
      log_error "Additional tools are needed for the installation. Please read the following text carefully!"
      log_error "${normal}This installer tries to verify that the OpenTofu version downloaded has been ${bold}signed by OpenTofu and has not been tampered with${normal}. This is only possible if either cosign or GPG is installed on the system, but neither was found. You have the following options:"
      log_error "${normal}1. ${bold}Install cosign${normal} and add it to your ${magenta}PATH${normal} or provide the ${magenta}--cosign-path${normal} parameter to your cosign installation."
      log_error "${normal}2. ${bold}Install GPG${normal} and add it to your ${magenta}PATH${normal} or provide the ${magenta}--gpg-path${normal} parameter to your GPG installation."
      log_error "${normal}3. ${bold}Disabling integrity verification${normal} with ${magenta}-skip-verify${normal} (${red}not recommended${normal})."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
    fi
  fi

  log_info "Installing OpenTofu using the standalone installation method..."

  if [ "${OPENTOFU_VERSION}" = "latest" ]; then
    log_info "Determining latest OpenTofu version..."
    OPENTOFU_VERSION=$(download_file "https://api.github.com/repos/opentofu/opentofu/releases/latest" - | grep "tag_name" | sed -e 's/.*tag_name":"v//' -e 's/.*tag_name": "v//' -e 's/".*//')
    if [ -z "${OPENTOFU_VERSION}" ]; then
      log_error "Failed to obtain the latest release from the GitHub API. Try passing --opentofu-version to specify a version."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
  fi

  OS="$(uname | tr '[:upper:]' '[:lower:]')"
  ARCH="$(uname -m | sed -e 's/aarch64/arm64/' -e 's/x86_64/amd64/')"
  if [ -z "${OS}" ]; then
    log_error "Failed to determine OS version."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  if [ -z "${ARCH}" ]; then
    log_error "Failed to determine CPU architecture."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi

  log_info "Downloading OpenTofu version ${OPENTOFU_VERSION}..."
  TEMPDIR="$(mktemp -d)"
  if [ -z "${TEMPDIR}" ]; then
    log_error "Failed to create temporary directory"
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  # shellcheck disable=SC2064
  trap "rm -rf '${TEMPDIR}' || true" EXIT

  ZIPDIR="$(mktemp -d)"
  if [ -z "${ZIPDIR}" ]; then
    log_error "Failed to create temporary directory"
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  # shellcheck disable=SC2064
  trap "rm -rf '${ZIPDIR}' || true" EXIT

  ZIPFILE="tofu_${OPENTOFU_VERSION}_${OS}_${ARCH}.zip"
  if ! download_file "https://github.com/opentofu/opentofu/releases/download/v${OPENTOFU_VERSION}/${ZIPFILE}" "${TEMPDIR}/${ZIPFILE}"; then
    log_error "Failed to download the OpenTofu release ${OPENTOFU_VERSION}."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi

  log_info "Performing checksum verification..."
  SUMSFILE="tofu_${OPENTOFU_VERSION}_SHA256SUMS"
  if ! download_file "https://github.com/opentofu/opentofu/releases/download/v${OPENTOFU_VERSION}/${SUMSFILE}" "${TEMPDIR}/${SUMSFILE}"; then
    log_error "Failed to download ${SUMSFILE} for OpenTofu version ${OPENTOFU_VERSION}."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  if command_exists "shasum"; then
    REALSUM=$(shasum -a 256 "${TEMPDIR}/${ZIPFILE}" | cut -f 1 -d ' ')
  else
    REALSUM=$(sha256sum "${TEMPDIR}/${ZIPFILE}" | cut -f 1 -d ' ')
  fi
  if [ -z "${REALSUM}" ]; then
    log_error "Checksum could not be calculated for ${TEMPDIR}/${ZIPFILE}."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  EXPECTEDSUM=$(grep "${ZIPFILE}" "${TEMPDIR}/${SUMSFILE}" | cut -f 1 -d ' ')
  if [ -z "${EXPECTEDSUM}" ]; then
    log_error "No checksum found for ${ZIPFILE}."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  if [ "${REALSUM}" != "${EXPECTEDSUM}" ]; then
    log_error "Checksum mismatch for ${ZIPFILE}, expected: ${EXPECTEDSUM}, got: ${REALSUM}."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi
  log_info "Checksum for ${ZIPFILE} is ${REALSUM}, as expected."

  if [ "${VERIFY_METHOD}" = "cosign" ]; then
    log_info "Performing signature verification..."
    SIGFILE="tofu_${OPENTOFU_VERSION}_SHA256SUMS.sig"
    CERTFILE="tofu_${OPENTOFU_VERSION}_SHA256SUMS.pem"
    for FILE in "${SIGFILE}" "${CERTFILE}"; do
      if ! download_file "https://github.com/opentofu/opentofu/releases/download/v${OPENTOFU_VERSION}/${FILE}" "${TEMPDIR}/${FILE}"; then
        log_error "Failed to download ${FILE} for OpenTofu version ${OPENTOFU_VERSION}."
        return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
      fi
    done

    IDENTITY="${COSIGN_IDENTITY}"
    if [ "${IDENTITY}" = "autodetect" ]; then
      if [ "$(echo "${OPENTOFU_VERSION}" | grep -c "alpha" || true)" -ne "0" ] || [ "$(echo "${OPENTOFU_VERSION}" | grep -c "beta" || true)" -ne "0" ]; then
        IDENTITY="https://github.com/opentofu/opentofu/.github/workflows/release.yml@refs/heads/main"
      else
        IDENTITY="https://github.com/opentofu/opentofu/.github/workflows/release.yml@refs/heads/v$(echo "${OPENTOFU_VERSION}" | sed -e "s/\([0-9]*\)\.\([0-9]*\)\..*/\1.\2/")"
      fi
    fi

    if ! "${COSIGN_PATH}" verify-blob \
      --certificate-identity "${IDENTITY}" \
      --signature "${TEMPDIR}/${SIGFILE}" \
      --certificate "${TEMPDIR}/${CERTFILE}" \
      --certificate-oidc-issuer "${COSIGN_OIDC_ISSUER}" \
      "${TEMPDIR}/${SUMSFILE}"; then
        log_error "Signature verification failed, the downloaded files may be corrupted."
        return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
    log_info "Verification successful."
  elif [ "${VERIFY_METHOD}" = "gpg" ]; then
    log_info "Performing signature verification..."

    log_info "Downloading signature file..."
    SIGFILE="tofu_${OPENTOFU_VERSION}_SHA256SUMS.gpgsig"
    if ! download_file "https://github.com/opentofu/opentofu/releases/download/v${OPENTOFU_VERSION}/${SIGFILE}" "${TEMPDIR}/${SIGFILE}"; then
      log_error "Failed to download ${SIGFILE} for OpenTofu version ${OPENTOFU_VERSION}."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi

    log_info "Downloading GPG key..."
    GPG_FILE="opentofu.asc"
    GPG_FILE_PATH="${TEMPDIR}/${GPG_FILE}"
    if ! download_file "${GPG_URL}" "${TEMPDIR}/${GPG_FILE}"; then
      log_error "Failed to download the GPG key from ${GPG_URL}."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi

    log_info "Dearmoring GPG key..."
    if ! "${GPG_PATH}" --dearmor "${GPG_FILE_PATH}"; then
      log_error "Failed to dearmor the GPG key from ${GPG_URL}."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
    GPG_FILE_PATH="${GPG_FILE_PATH}.gpg"
    export GPGHOME="${TEMPDIR}"

    log_info "Verifying GPG key fingerprint..."
    if [ "$("${GPG_PATH}" "--trustdb-name" "${TEMPDIR}/trustdb" "--with-colons" "--show-keys" "--fingerprint" "${GPG_FILE_PATH}" | grep -c "fpr:::::::::${GPG_KEY_ID}:" || true)" -eq 0 ]; then
      log_error "Key with fingerprint ${GPG_KEY_ID} not found in ${GPG_URL}."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi

    if ! "${GPG_PATH}" "--no-use-agent" "--no-tty" "--trustdb-name" "${TEMPDIR}/trustdb" "--no-default-keyring" "--primary-keyring" "${GPG_FILE_PATH}" --import "${GPG_FILE_PATH}"; then
      log_error "Failed to import key"
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi

    if ! echo "${GPG_KEY_ID}:6:" | "${GPG_PATH}" "--no-tty" "--trustdb-name" "${TEMPDIR}/trustdb" "--no-default-keyring" "--primary-keyring" "${GPG_FILE_PATH}" --import-ownertrust ; then
      log_error "Failed to trust GPG key"
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi

    if ! "${GPG_PATH}" "--no-tty" "--trustdb-name" "${TEMPDIR}/trustdb" "--no-default-keyring" "--primary-keyring" "${GPG_FILE_PATH}" "--with-fingerprint" "--verify" "${TEMPDIR}/${SIGFILE}" "${TEMPDIR}/${SUMSFILE}"; then
      log_error "Signature verification failed."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
    FINGERPRINT=$("${GPG_PATH}" "--no-tty" "--trustdb-name" "${TEMPDIR}/trustdb" --no-default-keyring --primary-keyring "${GPG_FILE_PATH}" --with-fingerprint --verify "${TEMPDIR}/${SIGFILE}" "${TEMPDIR}/${SUMSFILE}" 2>&1 | grep "Primary key fingerprint: " | sed -e 's/^Primary key fingerprint: //' -e 's/ //g')
    if [ "${FINGERPRINT}" != "${GPG_KEY_ID}" ]; then
      log_error "The release is signed with the incorrect key: ${FINGERPRINT}."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
    log_info "Signature verified."
  elif [ "${VERIFY_METHOD}" != "-" ]; then
    log_error "Bug: unsupported verification method: ${VERIFY_METHOD}"
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi

  log_info "Unpacking OpenTofu..."
  if ! unzip -d "${ZIPDIR}" "${TEMPDIR}/${ZIPFILE}"; then
    log_error "Failed to unzip ${TEMPDIR}/${ZIPFILE} to /"
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi

  log_info "Moving OpenTofu installation to ${INSTALL_PATH}..."
  if ! maybe_root mkdir -p "${INSTALL_PATH}"; then
    log_error "Cannot create installation path at ${INSTALL_PATH}."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi

  if ! maybe_root mv "${ZIPDIR}"/* "${INSTALL_PATH}" >/dev/null 2>&1; then
    log_error "Cannot move ${ZIPDIR} contents to ${INSTALL_PATH}. Please check the permissions on the target directory."
    return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
  fi

  if [ "${SYMLINK_PATH}" != "-" ]; then
    log_info "Creating tofu symlink at ${SYMLINK_PATH}/tofu..."
    if ! maybe_root ln -sf "${INSTALL_PATH}/tofu" "${SYMLINK_PATH}/tofu"; then
      log_error "Failed to create symlink at ${INSTALL_PATH}/tofu."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
  fi
  log_info "Checking if OpenTofu is installed correctly..."
  if [ "${SYMLINK_PATH}" != "-" ]; then
    if ! "${SYMLINK_PATH}/tofu" --version; then
      log_error "Failed to run ${SYMLINK_PATH}/tofu after installation."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
  else
    if ! "${INSTALL_PATH}/tofu" --version; then
      log_error "Failed to run ${INSTALL_PATH}/tofu after installation."
      return "${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}"
    fi
  fi
  log_success "Installation complete."
  return "${TOFU_INSTALL_EXIT_CODE_OK}"
}

usage() {
  if [ -n "$1" ]; then
    log_error "Error: $1"
  fi
  cat <<EOF
${bold}${blue}Usage:${normal} $(basename "$0") ${magenta}[OPTIONS]${normal}

${bold}${blue}OPTIONS for all installation methods:${normal}

  ${bold}-h|--help${normal}                     Print this help.
  ${bold}--root-method ${magenta}METHOD${normal}          The method to use to obtain root credentials.
                                (${bold}One of:${normal} ${magenta}none${normal}, ${magenta}su${normal}, ${magenta}sudo${normal}, ${magenta}auto${normal}; ${bold}default:${normal} ${magenta}auto${normal})
  ${bold}--install-method ${magenta}METHOD${normal}       The installation method to use. (${red}required${normal})
                                Must be one of:
                                    ${magenta}deb${normal}         Debian repository installation
                                    ${magenta}rpm${normal}         RPM repository installation
                                    ${magenta}apk${normal}         APK (Alpine) repository installation
                                    ${magenta}snap${normal}        Snapcraft installation
                                    ${magenta}brew${normal}        Homebrew installation
                                    ${magenta}standalone${normal}  Standalone installation
  ${bold}--skip-verify${normal}                 Skip GPG or cosign integrity verification.
                                (${bold}${red}not recommended${normal}).
  ${bold}--debug${normal}                       Enable debug logging.

${bold}${blue}OPTIONS for the Debian repository installation:${normal}

  ${bold}--deb-url ${magenta}URL${normal}                 Debian repository URL.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_DEB_REPO_URL}${normal})
  ${bold}--deb-suite ${magenta}SUITE${normal}             Debian repository suite.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_DEB_REPO_SUITE}${normal})
  ${bold}--deb-components ${magenta}COMPONENTS${normal}   Debian repository components.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_DEB_REPO_COMPONENTS}${normal})
  ${bold}--gpg-url ${magenta}URL${normal}                 The URL where the GPG signing key is located.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_DEB_GPG_URL}${normal})
  ${bold}--deb-repo-gpg-url ${magenta}URL${normal}        Sets the GPG key for the Debian repository.
                                This is a workaround and may be removed in the future.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_DEB_REPO_GPG_URL}${normal}}

${bold}${blue}OPTIONS for the RPM repository installation:${normal}

  ${bold}--rpm-url ${magenta}URL${normal}                 RPM repository URL.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_RPM_REPO_URL}${normal})
  ${bold}--gpg-url ${magenta}URL${normal}                 The URL where the GPG signing key is located.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_RPM_GPG_URL}${normal})
  ${bold}--rpm-repo-gpg-url ${magenta}URL${normal}        Sets the GPG key for the RPM repository.
                                This is a workaround and may be removed in the future.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_RPM_REPO_GPG_URL}${normal}}

${bold}${blue}OPTIONS for the Alpine repository installation:${normal}

  ${bold}--apk-repo ${magenta}URL${normal}                APK repository URL. Pass ${bold}-${normal} to install from
                                the included packages.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_APK_REPO_URL}${normal})
  ${bold}--apk-package ${magenta}PACKAGE${normal}         APK package to install.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_APK_PACKAGE}${normal})

${bold}${blue}OPTIONS for the standalone installation:${normal}

  ${bold}--opentofu-version ${magenta}VERSION${normal}    Installs the specified OpenTofu version.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_OPENTOFU_VERSION}${normal})
  ${bold}--install-path ${magenta}PATH${normal}           Installs OpenTofu to the specified path.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_INSTALL_PATH}${normal})
  ${bold}--symlink-path ${magenta}PATH${normal}           Symlink the OpenTofu binary to this directory.
                                Pass "${bold}-${normal}" to skip creating a symlink.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_SYMLINK_PATH}${normal})
  ${bold}--cosign-path ${magenta}PATH${normal}            Path to cosign. (${bold}Default:${normal} ${magenta}${DEFAULT_COSIGN_PATH}${normal})
  ${bold}--cosign-oidc-issuer ${magenta}ISSUER${normal}   OIDC issuer for cosign verification.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_COSIGN_OIDC_ISSUER}${normal})
  ${bold}--cosign-identity ${magenta}IDENTITY${normal}    Cosign certificate identity.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_COSIGN_IDENTITY}${normal})
  ${bold}--gpg-path ${magenta}PATH${normal}               Path to GPG.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_GPG_PATH}${normal})
  ${bold}--gpg-url ${magenta}URL${normal}                 The URL where the GPG signing key is located.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_GPG_URL}${normal})
  ${bold}--gpg-key-id ${magenta}ID${normal}               Key ID (fingerprint) to expect at the GPG URL.
                                (${bold}Default:${normal} ${magenta}${DEFAULT_GPG_KEY_ID}${normal})

  ${bold}Note:${normal} If you do not specify the OpenTofu version, the script calls the GitHub API.
  This API is rate-limited. If you encounter problems, please create a GitHub token at
  https://github.com/settings/tokens without any permissions and set the ${bold}GITHUB_TOKEN${normal}
  environment variable. This will increase the rate limit.

${bold}${blue}Exit codes:${normal}

  ${bold}${TOFU_INSTALL_EXIT_CODE_OK}${normal}                             Installation successful.
  ${bold}${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}${normal}                             Your system is missing one or more requirements
                                for these selected installation method. Please
                                install the indicated tools to continue.
                                (e.g. Homebrew for the ${bold}brew${normal} installation method.)
  ${bold}${TOFU_INSTALL_EXIT_CODE_INSTALL_FAILED}${normal}                             The installation failed.
  ${bold}${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}${normal}                             Invalid configuration options.

EOF
}

main() {
  echo "${blue}${bold}OpenTofu Installer${normal}"
  echo ""

  while [ -n "$1" ]; do
    case $1 in
      -h)
        usage
        return "${TOFU_INSTALL_EXIT_CODE_OK}"
        ;;
      --help)
        usage
        return "${TOFU_INSTALL_EXIT_CODE_OK}"
        ;;
      --root-method)
        shift
        case $1 in
          auto|sudo|su|none)
            ROOT_METHOD=$1
            ;;
          "")
            usage "--root-method requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
          *)
            if [ -z "$1" ]; then
              usage "Invalid value for --root-method: $1."
              return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            fi
        esac
        ;;
      --install-method)
        shift
        case $1 in
          auto|deb|rpm|apk|snap|brew|standalone)
            INSTALL_METHOD=$1
            ;;
          "")
            usage "--install-method requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
          *)
            usage "Invalid value for --install-method: $1."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
        esac
        ;;
      --install-path)
        shift
        case $1 in
          "")
            usage "--install-path requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
          *)
            INSTALL_PATH=$1
            ;;
        esac
        ;;
      --symlink-path)
        shift
        case $1 in
          "")
            usage "--symlink-path requires an argument (pass - to skip creating a symlink)."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
          *)
            SYMLINK_PATH=$1
            ;;
        esac
        ;;
      --gpg-path)
        shift
        case $1 in
          "")
            usage "--gpg-path requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            GPG_PATH="${1}"
            ;;
        esac
        ;;
      --gpg-url)
        shift
        case $1 in
          "")
            usage "--gpg-url requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            GPG_URL="${1}"
            DEB_GPG_URL="${1}"
            RPM_GPG_URL="${1}"
            ;;
        esac
        ;;
      --gpg-key-id)
        shift
        case $1 in
          "")
            usage "--gpg-key-id requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            GPG_KEY_ID="${1}"
            ;;
        esac
        ;;
      --deb-gpg-url)
        shift
        case $1 in
          "")
            usage "--deb-gpg-url requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            DEB_GPG_URL="${1}"
            ;;
        esac
        ;;
      --deb-repo-gpg-url)
        shift
        case $1 in
          "")
            usage "--deb-repo-gpg-url requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            DEB_REPO_GPG_URL="${1}"
            ;;
        esac
        ;;
      --deb-url)
        shift
        case $1 in
          "")
            usage "--deb-url requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            DEB_REPO_URL="${1}"
            ;;
        esac
        ;;
      --deb-suite)
        shift
        case $1 in
          "")
            usage "--deb-suite requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            DEB_REPO_SUITE="${1}"
            ;;
        esac
        ;;
      --deb-components)
        shift
        case $1 in
          "")
            usage "--deb-components requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            DEB_REPO_COMPONENTS="${1}"
            ;;
        esac
        ;;
      --rpm-url)
        shift
        case $1 in
          "")
            usage "--rpm-url requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            RPM_REPO_URL="${1}"
            ;;
        esac
        ;;
      --rpm-gpg-url)
        shift
        case $1 in
          "")
            usage "--rpm-gpg-url requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            RPM_GPG_URL="${1}"
            ;;
        esac
        ;;
      --rpm-repo-gpg-url)
        shift
        case $1 in
          "")
            usage "--rpm-repo-gpg-url requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            RPM_REPO_GPG_URL="${1}"
            ;;
        esac
        ;;
      --apk-repo)
        shift
        case $1 in
          "")
            usage "--apk-repo requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            APK_REPO_URL="${1}"
            ;;
        esac
        ;;
      --apk-package)
        shift
        case $1 in
          "")
            usage "--apk-package requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            APK_PACKAGE="${1}"
            ;;
        esac
        ;;
      --cosign-path)
        shift
        case $1 in
          "")
            usage "--cosign-path requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            COSIGN_PATH="${1}"
            ;;
        esac
        ;;
      --cosign-oidc-issuer)
        shift
        case $1 in
          "")
            usage "--cosign-oidc-issuer requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            COSIGN_OIDC_ISSUER="${1}"
            ;;
        esac
        ;;
      --cosign-identity)
        shift
        case $1 in
          "")
            usage "--cosign-identity requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            COSIGN_IDENTITY="${1}"
            ;;
        esac
        ;;
      --opentofu-version)
        shift
        case $1 in
          "")
            usage "--opentofu-version requires an argument."
            return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
            ;;
        *)
            OPENTOFU_VERSION="${1}"
            ;;
        esac
        ;;
      --skip-verify)
        SKIP_VERIFY=1
        log_warning "Skipping integrity verification. This is not recommended."
        ;;
      --debug)
        LOG_DEBUG=1
        ;;
      *)
        usage "Unknown option: $1."
        return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
        ;;
    esac
    shift
  done
  case "${INSTALL_METHOD}" in
  deb)
    install_deb
    return $?
    ;;
  rpm)
    install_rpm
    return $?
    ;;
  apk)
    install_apk
    return $?
    ;;
  snap)
    install_snap
    return $?
    ;;
  brew)
    install_brew
    return $?
    ;;
  standalone)
    install_standalone
    return $?
    ;;
  "")
    usage "Please select an installation method with --install-method."
    return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
    ;;
  *)
    usage "Unsupported installation method: ${INSTALL_METHOD}."
    return "${TOFU_INSTALL_EXIT_CODE_INVALID_ARGUMENT}"
  esac
}

main "$@"
exit $?
