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
13 changes: 13 additions & 0 deletions hard-stop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"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"
]
}
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
206 changes: 206 additions & 0 deletions install/check-hard-stop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# 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"}
Comment thread
cursor[bot] marked this conversation as resolved.
# This should be a bash array string, and should be equivalent with the list
# on https://develop.sentry.dev/self-hosted/releases/#hard-stops
mapfile -t hard_stops < <(cat hard-stop.json | $jq -r '.hard_stops[]')

_write_latest_version() {
echo "$1" >"$latest_version_file"
Comment thread
cursor[bot] marked this conversation as resolved.
}

# 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
Comment thread
sentry[bot] marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The compare_calver function's result is captured from stdout, but it returns a value via exit code. This causes compare_result to be empty, leading to an upgrade script failure.
Severity: CRITICAL

Suggested Fix

Modify the compare_calver function to echo its result to standard output instead of using return. Alternatively, change the calling code to capture the exit code using $? immediately after the function call: compare_calver "..." "..."; compare_result=$?. If capturing the exit code, be aware that return -1 becomes exit code 255 in bash, so the function's return values and the caller's checks must be adjusted to use valid exit codes (0-255).

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: install/check-hard-stop.sh#L74

Potential issue: The `compare_calver` function is designed to communicate its result via
an exit code (`return`), but the calling code captures its standard output into the
`compare_result` variable. Since the function does not write its result to stdout,
`compare_result` is always an empty string. This causes the subsequent conditional
checks to fail, leading the script to always execute the `else` block and exit with an
error. This bug triggers during the common upgrade scenario for existing installations,
causing the install script to fail.

Also affects:

  • install/check-hard-stop.sh:80~80
  • install/check-hard-stop.sh:86~86
  • install/check-hard-stop.sh:160~161

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
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compare result never reaches caller

High Severity

compare_calver reports its result via return, but the caller stores stdout in compare_result. The function never prints that value, so compare_result is always empty and every upgrade aborts on the unexpected-value path. return -1 is also not a valid bash status.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2472e2c. Configure here.


# 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 "--------------------------------------------------------------------------------"
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 "--------------------------------------------------------------------------------"
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.
Comment thread
cursor[bot] marked this conversation as resolved.

# 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
Comment thread
sentry[bot] marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Top-level local crashes upgrade installs

High Severity

local _wrote_version=0 runs at script scope in a sourced file, which bash rejects. Combined with set -e in install.sh, any upgrade that already has a tracking file aborts immediately, so the hard-stop check never runs and the install cleanup trap can stop a live stack.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2b942d2. Configure here.

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 "--------------------------------------------------------------------------------"
echo
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
Comment thread
sentry[bot] marked this conversation as resolved.
# 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"
Comment thread
sentry[bot] marked this conversation as resolved.
fi
fi

echo "${_endgroup}"
Loading