Skip to content
Open
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 @@
{
Comment thread
BYK marked this conversation as resolved.
"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
129 changes: 129 additions & 0 deletions install/check-hard-stop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# 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,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure if I like the "hard stop file" approach. Why can we not infer the previous version from the sentry image/instance itself? This is additional state we need to keep track of now.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

How? We don't have sentry --version' kind of thing. Also this is being run after the user changed their .env` values.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have these labels baked into the image which we can extend to versions or use to derive versions: https://github.com/getsentry/sentry/blob/c4bca2967197bc758abb1bc141c42f75902814d7/self-hosted/Dockerfile#L143-L145

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't think we can fetch that. This works: docker inspect --format='{{index .Config.Labels "org.opencontainers.image.version"}}' ghcr.io/getsentry/sentry:26.9.0, but it outputs 91e940e6fe4df840c91d865b9d29df9a1b044faf rather than 26.9.0.

Also, we will never know the actual image being used, we need to use docker compose for that.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Actuallyyy, we can do this:

$ docker compose images --format json | jq 'first(.[] | select(.Repository == "ghcr.io/getsentry/snuba")) | .Tag'
"26.9.0"

Can't do that with getsentry/sentry because of this:

  {
    "ID": "sha256:3e697ae50aec8548451fa0c4e75e906c69ce8fe3caf94c675bf924b3b00bb4db",
    "ContainerName": "sentry-self-hosted-taskscheduler-1",
    "Repository": "sentry-self-hosted-local",
    "Tag": "latest",
    "Platform": "linux/amd64",
    "Size": 1406387641,
    "Created": "2026-09-16T21:19:42.544492665+07:00",
    "LastTagTime": "2026-09-17T07:57:24.173640312+07:00"
  }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

# 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 ... "

# 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[]')

# 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Image tag parsed from wrong field

High Severity

cut -d: -f2 splits on the colon in ghcr.io, so new_version becomes the image path rather than the tag. That value then fails the CalVer length check, and the hard-stop logic never runs for the default SENTRY_IMAGE.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0a605e1. Configure here.

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)
Comment thread
sentry[bot] marked this conversation as resolved.
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
echo "WARNING: Could not determine the new version of the self-hosted Sentry"
echo "to perform a hard stop check. Assuming you know what you're doing. Good luck."
echo
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 new version."
elif [[ "$new_version" == "nightly" ]]; then
# If the `new_version` is nightly, we emit a different warning.
# This is for fun.
echo "--------------------------------------------------------------------------------"
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
echo "--------------------------------------------------------------------------------"
elif [[ "${#new_version}" -gt 7 ]]; then

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 CalVer length check (${#new_version} -gt 7) is too strict, causing valid versions to be flagged as invalid and silently skipping the hard-stop check.
Severity: HIGH

Suggested Fix

Relax the version string length check at line 70 to accommodate valid CalVer formats that can be longer than 7 characters, such as those with two-digit months or patch numbers (e.g., YY.MM.DD).

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#L70

Potential issue: A length check at line 70 (`${#new_version} -gt 7`) intended to
validate CalVer versions is too restrictive. Valid Sentry CalVer strings can exceed 7
characters (e.g., `24.12.10` is 8 characters). When a user tries to upgrade to a valid
version with a string longer than 7 characters, the script incorrectly identifies it as
an invalid format. This causes the script to silently skip the entire hard-stop
validation logic, potentially allowing a user to upgrade past a required hard-stop
without any warning.

# Invalid CalVer format. This might be an e2e test, or a user-provided version.
echo "--------------------------------------------------------------------------------"
echo
echo "WARNING: Could not determine the new version of the self-hosted Sentry"
echo "to perform a hard stop check due to invalid CalVer format."
echo "Assuming you know what you're doing. Good luck."
echo
echo "--------------------------------------------------------------------------------"
else
# We use snuba image because we don't rebuild the image, and it preserves
current_version=$($dc images --format json | $jq -r 'first(.[] | select(.Repository == "ghcr.io/getsentry/snuba")) | .Tag')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing snuba image aborts install

High Severity

$dc images lists only created containers, and jq first exits non-zero when no ghcr.io/getsentry/snuba image is present. Under set -e that aborts first-time installs and upgrades after containers were removed.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0a605e1. Configure here.


for hard_stop in "${hard_stops[@]}"; do
Comment thread
aldy505 marked this conversation as resolved.
# Let's say we have 3 variables in play here:
# current_version: 24.8.0
# new_version: 26.8.0
# hard_stop: 25.5.1
# We don't want to allow the installation to continue if the hard stop is
# greater than the new version. So the comparison logic here is:
#
# if hard_stop > current_version:
# Check if new_version > hard_stop
# if new_version > hard_stop:
# User is NOT on the right track, they're skipping a hard stop.
# We should give them the warning prompt.
# else:
# User is on the right track.
# continue

# the version in the image tag.
# We also need to handle if those 3 variables are the same, because we
# don't want to skip the hard stop if the new version is the same as the
# hard stop.
if [[ "$current_version" == "$new_version" ]]; then
break
elif vergte "$hard_stop" "$current_version"; then
if vergte "$new_version" "$hard_stop"; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Inclusive comparison flags valid upgrades

High Severity

vergte treats equality as a skipped hard stop, so upgrading to or away from a hard-stop version still triggers the warning. The prompt defaults to no and therefore cancels a valid upgrade.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0a605e1. Configure here.

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 script incorrectly warns users they are skipping a hard-stop version when they are upgrading exactly to it, due to a >= check instead of >.
Severity: MEDIUM

Suggested Fix

Change the version comparison at line 107 from vergte (greater than or equal to) to a strict greater-than check. This will ensure the warning only triggers when the new_version is actually newer than the hard_stop version, not equal to it.

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#L107

Potential issue: The script uses `vergte` (version greater than or equal to) at line 107
to check if the `new_version` skips a `hard_stop` version. This comparison incorrectly
returns true when the `new_version` is exactly equal to the `hard_stop` version. As a
result, a user correctly upgrading *to* a required hard-stop version will receive a
false and confusing warning message stating they are skipping the version they are
trying to install. This will happen for every valid upgrade to any of the listed
hard-stop versions.

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
echo "Canceled. 😅"
exit 1
fi
fi

# NO BREAK HERE — keep looping to check remaining hard stops
done
Comment thread
aldy505 marked this conversation as resolved.
fi

echo "${_endgroup}"
1 change: 1 addition & 0 deletions install/error-handling.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ fi
$dbuild -t sentry-self-hosted-jq-local --platform="$DOCKER_PLATFORM" jq

jq="$CONTAINER_ENGINE run --rm -i sentry-self-hosted-jq-local"
gh="$CONTAINER_ENGINE run --rm -i docker.io/serversideup/github-cli:alpine"
sentry_cli="$CONTAINER_ENGINE run --rm -v /tmp:/work -e SENTRY_DSN=$SENTRY_DSN getsentry/sentry-cli"
DEFAULT_BREADCRUMB_LINE_LIMIT=200

Expand Down
Loading