install.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bash
# Opengrep installation script
set -euo pipefail
if [[ "$0" == "bash" || "$0" == "-bash" ]]; then
SCRIPT_NAME="install.sh (via stdin)"
else
SCRIPT_NAME="$0"
fi
print_usage() {
echo "Usage:"
echo " $SCRIPT_NAME [-v <version>] [--verify-signatures]"
echo " Install the latest or specified version (default: latest)"
echo
echo " $SCRIPT_NAME -l"
echo " List the latest 3 available versions"
echo
echo " $SCRIPT_NAME -h"
echo " Show this help message"
echo
echo "Options:"
printf " %-22s %s\n" "-v <version>" "Specify version to install (optional)"
printf " %-22s %s\n" "--verify-signatures" "Require Cosign verification of signature"
printf " %-22s %s\n" "-l" "List latest 3 versions (no install)"
printf " %-22s %s\n" "-h" "Display help (no install)"
echo
echo "Notes:"
echo " - '--verify-signatures' can be used with or without '-v'."
echo " - '-l' and '-h' cannot be combined with other options."
}
check_has_curl() {
command -v curl > /dev/null 2>&1 || {
echo >&2 "Required tool curl could not be found. Aborting."
exit 1
}
}
fetch_failed() {
echo "Error: Failed to fetch available versions from GitHub${1:+: $1}." 1>&2
exit 1
}
# Lists the COUNT most recent releases, newest first, marking pre-releases.
list_versions() {
local COUNT="$1"
check_has_curl
local RESPONSE HTTP_STATUS BODY REASON VERSIONS
RESPONSE=$(curl -sS -w '\n%{http_code}' \
"https://api.github.com/repos/opengrep/opengrep/releases?per_page=${COUNT}") || {
# curl -sS has already printed its error on stderr.
fetch_failed
}
HTTP_STATUS="${RESPONSE##*$'\n'}"
BODY="${RESPONSE%$'\n'*}"
if [ "$HTTP_STATUS" != "200" ]; then
# GitHub API error bodies explain the failure in a "message" field.
REASON=$(echo "$BODY" | awk -F'"' '$2 == "message" { print $4; exit }')
fetch_failed "HTTP status ${HTTP_STATUS}${REASON:+ (${REASON})}"
fi
# Split on double quotes, so $2 is the JSON key and $4 its string value.
# Tags that are not release tags are not listed.
VERSIONS=$(echo "$BODY" | awk -F'"' -v TAG_RE="$RELEASE_TAG_REGEX" '
$2 == "tag_name" && $4 ~ TAG_RE { TAG = $4 }
$2 == "prerelease" && TAG != "" {
if ($3 ~ /true/) { print TAG " (pre-release)" } else { print TAG }
TAG = ""
}')
if [ -z "$VERSIONS" ]; then
fetch_failed
fi
echo "$VERSIONS"
}
print_available_versions() {
local COUNT="$1"
echo "Available versions (latest ${COUNT}):"
list_versions "$COUNT"
}
# Function to get the latest version. The /releases/latest web redirect never
# points to a pre-release or draft, and does not use the rate-limited API.
# curl's exit status is ignored: even if the connection dies after the
# redirect was followed, url_effective already holds the tag URL.
get_latest_version() {
check_has_curl
local FINAL_URL
FINAL_URL=$(curl -ILsS -o /dev/null -w '%{url_effective}' \
https://github.com/opengrep/opengrep/releases/latest) || true
if [[ "$FINAL_URL" =~ /releases/tag/([^/]+)$ ]]; then
echo "${BASH_REMATCH[1]}"
else
echo "Error: Failed to determine the latest version from GitHub." 1>&2
exit 1
fi
}
# The version is interpolated into the install path and download URL, so accept
# only a release tag. Same semver shape as validate-inputs in rolling-release,
# with the suffix restricted to tag characters so no path separator gets through.
# [.] rather than \. so the same regex works in bash =~ and as an awk -v value.
readonly RELEASE_TAG_REGEX='^v[0-9]+[.][0-9]+[.][0-9]+(-[A-Za-z0-9._-]+)?$'
validate_version_format() {
local VERSION="$1"
if ! [[ "$VERSION" =~ $RELEASE_TAG_REGEX ]]; then
echo "Error: Invalid version '${VERSION}'. Expected a release tag such as v1.27.1." 1>&2
exit 1
fi
}
# Releases before 2.0.0 ship one self-extracting executable per platform,
# with separate glibc and musl builds. From 2.0.0 the asset is the static
# binary itself, one per architecture. The tag has already been checked
# against RELEASE_TAG_REGEX, so the major component is an integer.
is_2_or_later() {
local V="${1#v}"
[ "${V%%.*}" -ge 2 ]
}
# pre: $SIG_EXISTS == "true"
validate_signature() {
local P="$1"
if $HAS_COSIGN; then
echo "Verifying signatures for ${P}/opengrep.cert"
if cosign verify-blob \
--cert "$P/opengrep.cert" \
--signature "${P}/opengrep.sig" \
--certificate-identity-regexp "https://github.com/opengrep/opengrep.+" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"${P}/opengrep"; then
echo "Signature valid."
else
# if we have signatures and we also have cosign installed, then always
# verify, even without --verify-signatures.
echo "Error: Signature validation error."
exit 1
fi
else
echo "Warning: cosign needed for signature validation; the package will still be installed."
echo "If this was not intended, delete and rerun with --verify-signatures or install cosign."
fi
}
# carefully cleanup the expected files; we don't want a programming error to run
# rm -rf on any directory that is not intended...
cleanup_on_failure() {
local P="$1"
echo "An error occurred during the installation. Cleaning up ${P}..."
rm -f "${P}/opengrep" || true
rm -f "${P}/opengrep.download" || true
rm -f "${P}/opengrep.sig" || true
rm -f "${P}/opengrep.cert" || true
rmdir "${P}" || true
exit 1
}
main() {
local VERSION="$1"
local VERIFY_SIGNATURES="$2"
PREFIX="${HOME}/.opengrep/cli"
INST="${PREFIX}/${VERSION}"
LATEST="${PREFIX}/latest"
OS="${OS:-$(uname -s)}"
ARCH="${ARCH:-$(uname -m)}"
DIST=""
check_has_curl
# check and set "os_arch"
if [ "$OS" = "Linux" ]; then
if is_2_or_later "$VERSION"; then
if [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ]; then
DIST="opengrep_linux_x86"
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
DIST="opengrep_linux_aarch64"
fi
elif ldd /bin/sh 2>&1 | grep -qi musl; then
if [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ]; then
DIST="opengrep_musllinux_x86"
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
DIST="opengrep_musllinux_aarch64"
fi
else
if [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ]; then
DIST="opengrep_manylinux_x86"
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
DIST="opengrep_manylinux_aarch64"
fi
fi
elif [ "$OS" = "Darwin" ]; then
if [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ]; then
DIST="opengrep_osx_x86"
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
DIST="opengrep_osx_arm64"
fi
fi
if [ -z "${DIST}" ]; then
echo "Operating system '${OS}' / architecture '${ARCH}' is unsupported." 1>&2
exit 1
fi
URL="https://github.com/opengrep/opengrep/releases/download/${VERSION}/${DIST}"
# check if binary already exists
if [ -f "${INST}/opengrep" ]; then
echo "Destination binary ${INST}/opengrep already exists."
rm -f "${LATEST}" || exit 1
ln -s "${INST}" "${LATEST}" || exit 1
echo "Updated symlink from ${LATEST}/opengrep to point to ${INST}/opengrep."
if $VERIFY_SIGNATURES; then
echo "Signature verification skipped for existing installation."
fi
else
echo
echo "*** Installing Opengrep ${VERSION} for ${OS} (${ARCH}) ***"
# cleanup on error
trap '[ "$?" -eq 0 ] || cleanup_on_failure "$INST"' EXIT
mkdir -p "${INST}"
if [ ! -d "${INST}" ]; then
echo "Failed to create install directory ${INST}." 1>&2
exit 1
fi
# The download also validates the version, avoiding the rate-limited
# GitHub API: a 404 means the tag does not exist or has no asset for
# this platform. A temporary name, renamed only on success, so an
# interrupted download is never mistaken for an installed binary.
HTTP_STATUS=$(curl --show-error --location --progress-bar \
--write-out '%{http_code}' --output "${INST}/opengrep.download" "${URL}") || HTTP_STATUS=""
if [[ -z "$HTTP_STATUS" ]]; then
# curl has already printed its error on stderr.
echo "Error: Failed to download ${URL}." 1>&2
exit 1
elif [[ "$HTTP_STATUS" == "404" ]]; then
echo "Error: Version ${VERSION} not found, or it has no ${DIST} asset." 1>&2
# Subshell, so a listing failure cannot mask the diagnosis above.
(print_available_versions 3) 1>&2 || true
exit 1
elif [[ "$HTTP_STATUS" != 200 ]]; then
echo "Error: Failed to download ${URL}: HTTP status ${HTTP_STATUS}." 1>&2
exit 1
fi
mv "${INST}/opengrep.download" "${INST}/opengrep"
local SIG_EXISTS=true
# Try downloading .cert
CERT_STATUS=$(curl --location --silent --show-error --write-out "%{http_code}" \
--output "${INST}/opengrep.cert" "${URL}.cert")
if [[ "$CERT_STATUS" == "404" ]]; then
SIG_EXISTS=false
rm -f "${INST}/opengrep.cert" # It's there but contains "Not found".
echo "Warning: Certificate file not found at ${URL}.cert"
elif [[ "$CERT_STATUS" != 200 ]]; then
echo "Error: Failed to download ${URL}.cert: HTTP status $CERT_STATUS."
exit 1
else
# Only attempt .sig if .cert was found
SIG_STATUS=$(curl --location --silent --show-error --write-out "%{http_code}" \
--output "${INST}/opengrep.sig" "${URL}.sig")
if [[ "$SIG_STATUS" == "404" ]]; then
SIG_EXISTS=false
echo "Error: Signature file not found at ${URL}.sig, but ${URL}.cert was found."
exit 1 # we downloaded .cert, so exit with error
elif [[ "$SIG_STATUS" != 200 ]]; then
echo "Error: Failed to download ${URL}.sig: HTTP status $SIG_STATUS."
exit 1
fi
fi
# check signature if SIG_EXIST
if [[ "$SIG_EXISTS" == true ]]; then
validate_signature "${INST}"
else
if [[ "$VERIFY_SIGNATURES" == true ]]; then
echo "Error: No signature / certificate found for ${VERSION} but --verify-signatures was requested."
echo "Error: It is likely that signature verification was added after this version."
exit 1
else
echo "Warning: No signature / certificate found for ${VERSION}. Skipping signature verification."
echo "Warning: The package will still be installed. It is likely that signature verification was added after this version."
fi
fi
# make executable by all users
chmod a+x "${INST}/opengrep" || exit 1
if [ ! -f "${INST}/opengrep" ]; then
echo "Failed to download binary at ${INST}/opengrep" 1>&2
exit 1
fi
echo "Testing binary..."
# Test by calling --version on the downloaded binary.
# Set OPENGREP_SKIP_BINARY_TEST=1 to bypass this check in environments where
# binary execution is unreliable (e.g. docker buildx multi-platform builds).
if [[ "${OPENGREP_SKIP_BINARY_TEST:-}" == "1" ]]; then
echo "Skipping binary test (OPENGREP_SKIP_BINARY_TEST=1)."
else
STDERR_LOG=$(mktemp)
TEST=$("${INST}/opengrep" --version 2>"$STDERR_LOG" || true)
if [ -z "$TEST" ]; then
echo "Failed to execute installed binary: ${INST}/opengrep." 1>&2
if [ -s "$STDERR_LOG" ]; then
echo "Error output:" 1>&2
cat "$STDERR_LOG" 1>&2
echo 1>&2
echo "Full error log saved to: $STDERR_LOG" 1>&2
fi
echo "If reporting this issue, please include the above output." 1>&2
exit 1
fi
rm -f "$STDERR_LOG"
fi
echo
echo "Successfully installed Opengrep binary at ${INST}/opengrep"
rm -f "${LATEST}" || exit 1
ln -s "${INST}" "${LATEST}" || exit 1
echo "with a symlink from ${LATEST}/opengrep"
fi
LOCALBIN="${HOME}/.local/bin"
# Only need to create the symlink from .local/bin once if not created before.
# For all subsequent installations, the ./local/bin symlink will still point
# to the updated symlink (created above).
if [ -d "${LOCALBIN}" ] && [ -w "${LOCALBIN}" ]; then
# Only create the symlink if it doesn't already exist
if [ ! -f "${LOCALBIN}/opengrep" ]; then
ln -s "${LATEST}/opengrep" "${LOCALBIN}/opengrep"
echo "Created symlink from ${LOCALBIN}/opengrep to ${LATEST}/opengrep"
fi
echo
echo "To launch Opengrep now, type:"
# Do not assume that ~/.local/bin is in the PATH even if it exists,
# as it is not always the case.
if echo "$PATH" | tr ':' '\n' | grep -Fxq "$HOME/.local/bin"; then
echo "opengrep"
else
echo "${LOCALBIN}/opengrep"
fi
echo
else
echo
echo "To launch Opengrep now, type:"
echo "${LATEST}/opengrep"
echo
echo "Hint: Append the following line to your shell profile:"
echo "export PATH='${LATEST}':\$PATH"
echo
fi
}
# Argument parsing
if command -v cosign &> /dev/null; then
HAS_COSIGN=true
COSIGN_MAJOR_VERSION=$(cosign version | grep GitVersion | sed 's/GitVersion:[[:space:]]*v\{0,1\}//' | grep -oE '^[0-9]+' || true)
else
HAS_COSIGN=false
fi
HELP=false
LIST=false
VERIFY_SIGNATURES=false
VERSION=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h)
HELP=true
shift
;;
-l)
LIST=true
shift
;;
--verify-signatures)
VERIFY_SIGNATURES=true
shift
;;
-v)
if [[ -z "${2-}" ]] || ! [[ -n "$2" && "$2" != -* ]]; then
echo "Error: -v requires a version argument."
exit 1
else
VERSION="$2"
shift 2
fi
;;
*)
echo "Error: Unknown option: $1."
print_usage
exit 1
;;
esac
done
if { { $VERIFY_SIGNATURES || [[ -n "$VERSION" ]] || $LIST; } && $HELP; } || { { $VERIFY_SIGNATURES || [[ -n "$VERSION" ]] || $HELP; } && $LIST; }; then
echo "Error: incorrect arguments:"
print_usage
exit 1
fi
if $VERIFY_SIGNATURES && ! $HAS_COSIGN; then
echo "Error: cosign is required for --verify-signatures but is not installed."
echo "Go to https://github.com/sigstore/cosign to install it or run without the --verify-signatures flag to install without signature verification."
exit 1
elif ! $HAS_COSIGN; then
echo "Warning: cosign is required for --verify-signatures but is not installed. Skipping signature validation."
echo "Go to https://github.com/sigstore/cosign to install it."
elif [[ -z "$COSIGN_MAJOR_VERSION" ]]; then
if $VERIFY_SIGNATURES; then
echo "Error: could not determine cosign version and --verify-signatures was requested."
echo "Your cosign binary may have been built without version metadata (e.g. distro packages)."
echo "Install cosign from https://github.com/sigstore/cosign or run without --verify-signatures."
exit 1
else
echo "Warning: could not determine cosign version. Signature validation may not work correctly."
fi
elif [[ "$COSIGN_MAJOR_VERSION" -lt 2 ]]; then
echo "Warning: cosign version is less than 2.0.0, signature validation may fail."
fi
if "$HELP"; then
print_usage
exit 0
fi
if $LIST; then
print_available_versions 3
exit 0
fi
if [ -z "$VERSION" ]; then
VERSION=$(get_latest_version)
fi
validate_version_format "$VERSION"
main "$VERSION" "$VERIFY_SIGNATURES"