Skip to content

Commit 19e0713

Browse files
authored
feat(install): add Sentry error telemetry to install script (#334)
## Summary Add fire-and-forget error reporting to the install script via Sentry's envelope API. Uses the CLI's existing public write-only DSN — no new secrets or projects needed. This would have caught the macOS digest extraction bug (#331) automatically. ## Changes - `report_error()` — builds a minimal Sentry envelope and sends it via background `curl` (2s timeout, never blocks installation) - `die()` — replaces all 11 `echo + exit 1` patterns with error reporting + exit - `ERR` trap — catches unexpected `set -e` / `pipefail` exits like the gunzip pipeline failure that triggered #331 - UUID generation with fallback chain: `/proc` → `uuidgen` → `awk` - Opt-out via `SENTRY_CLI_NO_TELEMETRY=1` (same as the CLI binary) Each error event is tagged with `os`, `arch`, `channel`, `step`, `install.version`, and bash version for quick triage.
1 parent f7933f0 commit 19e0713

File tree

1 file changed

+79
-18
lines changed

1 file changed

+79
-18
lines changed

install

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,75 @@ RED='\033[0;31m'
55
MUTED='\033[0;2m'
66
NC='\033[0m'
77

8+
# Sentry error telemetry — fire-and-forget error reporting via envelope API.
9+
# Uses the CLI's public write-only DSN. No PII collected.
10+
# Opt-out: SENTRY_CLI_NO_TELEMETRY=1
11+
SENTRY_DSN_KEY="1188a86f3f8168f089450587b00bca66"
12+
SENTRY_INGEST="https://o1.ingest.us.sentry.io"
13+
SENTRY_PROJECT_ID="4510776311808000"
14+
15+
# Generate a UUID for the event. Tries /proc, uuidgen, then awk fallback.
16+
gen_uuid() {
17+
if [[ -r /proc/sys/kernel/random/uuid ]]; then
18+
cat /proc/sys/kernel/random/uuid
19+
elif command -v uuidgen >/dev/null 2>&1; then
20+
uuidgen | tr '[:upper:]' '[:lower:]'
21+
else
22+
awk 'BEGIN{srand();for(i=1;i<=32;i++)printf "%c",substr("0123456789abcdef",int(rand()*16)+1,1);print ""}'
23+
fi
24+
}
25+
26+
# Send an error event to Sentry. Runs in a subshell in the background so it
27+
# never blocks installation or propagates failures.
28+
# Usage: report_error "message" "step-name"
29+
report_error() {
30+
# Respect the same opt-out as the CLI binary
31+
[[ "${SENTRY_CLI_NO_TELEMETRY:-}" == "1" ]] && return 0
32+
33+
(
34+
set +e # Telemetry must never fail the script
35+
local msg="${1:-unknown error}"
36+
local step="${2:-unknown}"
37+
local event_id
38+
event_id=$(gen_uuid | tr -d '-')
39+
local timestamp
40+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
41+
42+
# Escape a value for safe JSON string interpolation
43+
_esc() { printf '%s' "$1" | sed 's/\\/\\\\/g;s/"/\\"/g' | tr '\n' ' '; }
44+
45+
local json_msg; json_msg=$(_esc "$msg")
46+
local json_step; json_step=$(_esc "$step")
47+
local json_channel; json_channel=$(_esc "${requested_version:-stable}")
48+
local json_version; json_version=$(_esc "${version:-unknown}")
49+
50+
local envelope
51+
envelope=$(printf '%s\n%s\n%s' \
52+
"{\"event_id\":\"${event_id}\",\"dsn\":\"https://${SENTRY_DSN_KEY}@o1.ingest.us.sentry.io/${SENTRY_PROJECT_ID}\"}" \
53+
'{"type":"event"}' \
54+
"{\"event_id\":\"${event_id}\",\"timestamp\":\"${timestamp}\",\"platform\":\"other\",\"level\":\"error\",\"logger\":\"install\",\"server_name\":\"install-script\",\"message\":{\"formatted\":\"${json_msg}\"},\"tags\":{\"os\":\"${os:-unknown}\",\"arch\":\"${arch:-unknown}\",\"channel\":\"${json_channel}\",\"step\":\"${json_step}\",\"install.version\":\"${json_version}\"},\"contexts\":{\"runtime\":{\"name\":\"bash\",\"version\":\"${BASH_VERSION:-unknown}\"}}}")
55+
56+
curl -sf --max-time 2 \
57+
-H "Content-Type: application/x-sentry-envelope" \
58+
-H "X-Sentry-Auth: Sentry sentry_key=${SENTRY_DSN_KEY},sentry_version=7" \
59+
-d "$envelope" \
60+
"${SENTRY_INGEST}/api/${SENTRY_PROJECT_ID}/envelope/" \
61+
>/dev/null 2>&1
62+
) &
63+
}
64+
65+
# Print error message, report to Sentry, and exit.
66+
# Usage: die "message" "step-name"
67+
die() {
68+
echo -e "${RED}$1${NC}" >&2
69+
report_error "$1" "${2:-unknown}"
70+
wait 2>/dev/null || true # Let the background curl finish; ignore its exit status
71+
exit 1
72+
}
73+
74+
# Catch unexpected failures from set -e / pipefail (e.g., gunzip failing)
75+
trap 'die "Unexpected failure at line $LINENO" "trap"' ERR
76+
877
usage() {
978
cat <<EOF
1079
Sentry CLI Installer
@@ -39,8 +108,7 @@ while [[ $# -gt 0 ]]; do
39108
requested_version="$2"
40109
shift 2
41110
else
42-
echo -e "${RED}Error: --version requires a version argument${NC}"
43-
exit 1
111+
die "Error: --version requires a version argument" "args"
44112
fi
45113
;;
46114
--no-modify-path)
@@ -60,24 +128,23 @@ case "$(uname -s)" in
60128
Darwin*) os="darwin" ;;
61129
Linux*) os="linux" ;;
62130
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
63-
*) echo -e "${RED}Unsupported OS: $(uname -s)${NC}"; exit 1 ;;
131+
*) die "Unsupported OS: $(uname -s)" "detect-os" ;;
64132
esac
65133

66134
# Detect architecture
67135
arch=$(uname -m)
68136
case "$arch" in
69137
x86_64) arch="x64" ;;
70138
aarch64|arm64) arch="arm64" ;;
71-
*) echo -e "${RED}Unsupported architecture: $arch${NC}"; exit 1 ;;
139+
*) die "Unsupported architecture: $arch" "detect-arch" ;;
72140
esac
73141

74142
# Validate supported combinations
75143
suffix=""
76144
if [[ "$os" == "windows" ]]; then
77145
suffix=".exe"
78146
if [[ "$arch" != "x64" ]]; then
79-
echo -e "${RED}Unsupported: windows-$arch (only windows-x64 is supported)${NC}"
80-
exit 1
147+
die "Unsupported: windows-$arch (only windows-x64 is supported)" "detect-arch"
81148
fi
82149
fi
83150

@@ -103,8 +170,7 @@ if [[ "$requested_version" == "nightly" ]]; then
103170
"https://ghcr.io/token?scope=repository:getsentry/cli:pull" \
104171
| awk -F'"' '{for(i=1;i<=NF;i++) if($i=="token"){print $(i+2);exit}}')
105172
if [[ -z "$GHCR_TOKEN" ]]; then
106-
echo -e "${RED}Failed to get GHCR token${NC}"
107-
exit 1
173+
die "Failed to get GHCR token" "ghcr-token"
108174
fi
109175

110176
# Step 2: Fetch the OCI manifest for the :nightly tag
@@ -113,16 +179,14 @@ if [[ "$requested_version" == "nightly" ]]; then
113179
-H "Accept: application/vnd.oci.image.manifest.v1+json" \
114180
"https://ghcr.io/v2/getsentry/cli/manifests/nightly")
115181
if [[ -z "$MANIFEST" ]]; then
116-
echo -e "${RED}Failed to fetch nightly manifest from GHCR${NC}"
117-
exit 1
182+
die "Failed to fetch nightly manifest from GHCR" "ghcr-manifest"
118183
fi
119184

120185
# Step 3: Extract version from manifest annotation
121186
version=$(echo "$MANIFEST" \
122187
| awk -F'"' '{for(i=1;i<=NF;i++) if($i=="version"){print $(i+2);exit}}')
123188
if [[ -z "$version" ]]; then
124-
echo -e "${RED}Failed to extract version from nightly manifest${NC}"
125-
exit 1
189+
die "Failed to extract version from nightly manifest" "ghcr-version"
126190
fi
127191

128192
echo -e "${MUTED}Installing nightly sentry ${version}...${NC}"
@@ -140,8 +204,7 @@ if [[ "$requested_version" == "nightly" ]]; then
140204
}
141205
}')
142206
if [[ -z "$digest" ]]; then
143-
echo -e "${RED}No nightly build found for ${gz_filename}${NC}"
144-
exit 1
207+
die "No nightly build found for ${gz_filename}" "ghcr-digest"
145208
fi
146209

147210
# Step 5: Get the redirect URL from the blob endpoint (don't use -L: auth
@@ -150,8 +213,7 @@ if [[ "$requested_version" == "nightly" ]]; then
150213
-H "Authorization: Bearer $GHCR_TOKEN" \
151214
"https://ghcr.io/v2/getsentry/cli/blobs/${digest}" | tail -1)
152215
if [[ -z "$redir_url" ]]; then
153-
echo -e "${RED}Failed to get blob redirect URL from GHCR${NC}"
154-
exit 1
216+
die "Failed to get blob redirect URL from GHCR" "ghcr-redirect"
155217
fi
156218

157219
# Step 6: Download the .gz blob and decompress (without auth header)
@@ -164,8 +226,7 @@ else
164226
version=$(curl -fsSL https://api.github.com/repos/getsentry/cli/releases/latest \
165227
| sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
166228
if [[ -z "$version" ]]; then
167-
echo -e "${RED}Failed to fetch latest version${NC}"
168-
exit 1
229+
die "Failed to fetch latest version" "gh-version"
169230
fi
170231
else
171232
version="$requested_version"

0 commit comments

Comments
 (0)