Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ geoip/GeoIP.conf
geoip/*.mmdb
geoip/.geoipupdate.lock

# hard stop version tracking
.sentry-hard-stop

# integration testing
_integration-test/custom-ca-roots/nginx/*
sentry/test-custom-ca-roots.py
Expand Down
1 change: 1 addition & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ source install/dc-detect-version.sh
source install/error-handling.sh
# We set the trap at the top level so that we get better tracebacks.
trap_with_arg cleanup ERR INT TERM EXIT
source install/check-hard-stop.sh
source install/check-latest-commit.sh
source install/check-minimum-requirements.sh

Expand Down
222 changes: 222 additions & 0 deletions install/check-hard-stop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# The idea of this file is to prevent users from skipping a hard stop.
# This is done by creating a file in /var/run/sentry-hard-stop (or anything set
# in HARD_STOP_FILE) and checking for its existence before. If the file exists,
# we assume (and trust) that it's the latest version of the self-hosted sentry
# version, and no further check (git tag, values on `.env` ) would be done.
# Otherwise, we assume either this is the first installation, or the first
# time after this file is being written, and write into that file (by reading
# either the Git tag or values on `.env` or `.env.custom`).
#
# If the "reading" part fails anyway, we would skip this process and continue
# with the installation.
#
# If the user skipped a hard stop, we would halt the installation (or maybe,
# cancel it altogether), and ask for confirmation.
#
# This bit is written by a human.

echo "${_group}Checking for hard stop ... "

latest_version_file=${HARD_STOP_FILE:-".sentry-hard-stop"}
# This should be a bash array string, and should be equivalent with the list
# on https://develop.sentry.dev/self-hosted/releases/#hard-stops
hard_stops=("9.1.2" "21.5.0" "21.6.3" "23.6.2" "23.11.0" "24.8.0" "25.5.1" "26.5.0" "26.7.0")

_write_latest_version() {
echo "$1" >"$latest_version_file"
}

_show_warning() {
# this is for fun, yet also to seriously warn people about something serious
# only show this if `ENABLE_WARNING_BIG_ENOUGH` is set to `true` or `1` or `yes`
if [[ "${ENABLE_WARNING_BIG_ENOUGH:-}" == "true" || "${ENABLE_WARNING_BIG_ENOUGH:-}" == "1" || "${ENABLE_WARNING_BIG_ENOUGH:-}" == "yes" ]]; then
echo "░██ ░██ ░███ ░█████████ ░███ ░██ ░██████░███ ░██ ░██████ "
echo "░██ ░██ ░██░██ ░██ ░██ ░████ ░██ ░██ ░████ ░██ ░██ ░██ "
echo "░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██░██ ░██ ░██ ░██░██ ░██ ░██ "
echo "░██ ░████ ░██ ░█████████ ░█████████ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ █████ "
echo "░██░██ ░██░██ ░██ ░██ ░██ ░██ ░██ ░██░██ ░██ ░██ ░██░██ ░██ ██ "
echo "░████ ░████ ░██ ░██ ░██ ░██ ░██ ░████ ░██ ░██ ░████ ░██ ░███ "
echo "░███ ░███ ░██ ░██ ░██ ░██ ░██ ░███ ░██████░██ ░███ ░█████░█ "
fi
}

# Helper function to parse version components
# BASH_REMATCH requires Bash 3.0+
_parse_version_components() {
local ver="$1"
if [[ $ver =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]*)?(\+[0-9A-Za-z.-]*)?$ ]]; then
echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]} ${BASH_REMATCH[4]#-} ${BASH_REMATCH[5]#+}"
else
echo ""
fi
}

# Compare two calver versions
# Usage: compare_calver "1.2.3" "1.2.4"
# Returns: -1 if first < second, 0 if equal, 1 if first > second
#
# This bit is written by Claude Haiku 4.5.
compare_calver() {
local v1="$1"
local v2="$2"

if [[ -z "$v1" ]] || [[ -z "$v2" ]]; then
echo -e "ERROR: Invalid CalVer format" >&2
return 2
fi

# Remove leading 'v' if present
v1="${v1#v}"
v2="${v2#v}"

# Extract components
local parsed1=$(_parse_version_components "$v1")
local parsed2=$(_parse_version_components "$v2")

if [[ -z "$parsed1" ]] || [[ -z "$parsed2" ]]; then
echo -e "ERROR: Invalid CalVer format" >&2
return 2
fi

# Compare major.minor.patch
local arr1=($parsed1)
local arr2=($parsed2)

if ((arr1[0] > arr2[0])); then
return 1
elif ((arr1[0] < arr2[0])); then
return -1
fi

if ((arr1[1] > arr2[1])); then
return 1
elif ((arr1[1] < arr2[1])); then
return -1
fi

if ((arr1[2] > arr2[2])); then
return 1
elif ((arr1[2] < arr2[2])); then
return -1
fi

return 0 # Equal
}

# Acquire the new version. This is done by reading `.env` / `.env.custom`
# for Docker image tags; or by reading the Git tag for the current commit
declare new_version=""
# if `.env.custom` exists, prioritize it over `.env`
if [[ -f ".env.custom" ]]; then
new_version=$(grep -E '^SENTRY_IMAGE=' .env.custom | sed 's/^.*=//' | cut -d: -f2 || true)
fi

if [[ -z "$new_version" ]]; then
new_version=$(grep -E '^SENTRY_IMAGE=' .env | sed 's/^.*=//' | cut -d: -f2 || true)
fi

if [[ -z "$new_version" ]]; then
# Check whether `git` exists as a command, and `.git` directory exists
if [[ -n "$(command -v git)" ]] && [[ -d "../.git" || -d "./.git" ]]; then
# Get the latest tag from the repository
new_version=$(git describe --tags --abbrev=0)
fi
fi

# If the `new_version` is still empty, we emit a warning that
# they're on their own
if [[ -z "$new_version" ]]; then
echo "--------------------------------------------------------------------------------"
_show_warning
echo "WARNING: Could not determine the current version of the self-hosted Sentry"
echo "to perform a hard stop check. Assuming you know what you're doing. Good luck."
echo "--------------------------------------------------------------------------------"
fi

# If the `new_version` is empty, we cannot perform any hard stop check.
# This means the version detection failed across all methods. We already
# warned the user above, so we skip the check and continue with the installation.
if [[ -z "$new_version" ]]; then
echo "Skipping hard stop check: unable to determine the current version."
elif [[ "$new_version" == "nightly" ]]; then
# If the `new_version` is nightly, we emit a different warning.
# This is for fun.
echo "--------------------------------------------------------------------------------"
_show_warning
echo "WARNING: Hello, dear brave traveler. You are installing the nightly version."
echo "The hard stop check is skipped for this version. We wish you a safe journey."
echo "Good luck."
echo "--------------------------------------------------------------------------------"
else
# Only perform the hard stop check when we have a parseable semver version.
# Skip for empty or non-semver versions (e.g. "nightly") — the warnings above
# already informed the user.

# Acquire the current version. Read the file.
declare current_version=""
if [[ -f "$latest_version_file" ]]; then
current_version=$(cat "$latest_version_file")
fi

# We perform some checks if the `current_version` is not empty.
if [[ -n "$current_version" ]]; then
# We iterate over the list of hard stops, and check whether the current
# version is below any of them.
local _wrote_version=0
for hard_stop in "${hard_stops[@]}"; do
compare_result=$(compare_calver "$current_version" "$hard_stop")
if [[ "$compare_result" == 0 ]]; then
# equal, this is correct, they're visiting a hard stop
_write_latest_version "$new_version"
_wrote_version=1
break
elif [[ "$compare_result" == 1 ]]; then
# the current version is greater than the current hard stop loop, we continue
continue
elif [[ "$compare_result" == -1 ]]; then
# the current version is less than the current hard stop loop
# we alert the user and provide a confirmation
echo "--------------------------------------------------------------------------------"
_show_warning
echo "WARNING: Your new version ($new_version) will skip a required hard stop of $hard_stop."
echo "It is recommended to stop the current installation, and go through the hard stop first."
echo "Otherwise, you may encounter unexpected behaviors, such as migration failures, or data loss."
echo
echo "For future reference, please visit https://develop.sentry.dev/self-hosted/releases/#hard-stops"
echo
echo "Do you wish to continue? [y/N]"
read -r confirmation

if [[ "$confirmation" == "y" ]]; then
_write_latest_version "$new_version"
_wrote_version=1
break
else
echo "Canceled. 😅"
exit 1
fi
elif [[ "$compare_result" == 2 ]]; then
# invalid version, we exit
echo "ERROR: Invalid version in $latest_version_file"
exit 1
else
# a bug on our end, the `compare_result` returns unexpected value
echo 'ERROR: Unexpected return value from `compare_calver` function. This is a bug on our end.'
echo "The 'compare_result' value is: $compare_result"
exit 2
fi
done
# If the loop completed without writing (current_version > all hard stops),
# update the tracking file so the version stays current.
if [[ "$_wrote_version" -eq 0 ]]; then
_write_latest_version "$new_version"
fi
else
# If the `current_version` is empty (or the file does not exists), we assume
# this is a new installation.
echo "Self-hosted Sentry version tracking file not found. No hard stop check is needed."
_write_latest_version "$new_version"
fi
fi

echo "${_endgroup}"
Loading