1363 lines
49 KiB
Bash
Executable File
1363 lines
49 KiB
Bash
Executable File
#!/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 dnf or yum command line utility. It returns $TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET
|
|
# if neither dnf nor yum is available.
|
|
install_yum() {
|
|
if command_exists "dnf"; then
|
|
YUM_COMMAND="dnf"
|
|
elif command_exists "yum"; then
|
|
YUM_COMMAND="yum"
|
|
else
|
|
return "${TOFU_INSTALL_EXIT_CODE_INSTALL_REQUIREMENTS_NOT_MET}"
|
|
fi
|
|
log_info "Installing OpenTofu using ${YUM_COMMAND}..."
|
|
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_COMMAND}" install -y tofu; then
|
|
log_error "Failed to install tofu via ${YUM_COMMAND}."
|
|
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 $?
|