feat: Offer to send an anonymous failure report in the install script (#37016)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Charlie Kolb
2026-08-27 14:49:03 +00:00
committed by GitHub
parent c2f636bc2d
commit 269de76c36
2 changed files with 107 additions and 3 deletions
+55 -3
View File
@@ -12,7 +12,7 @@
# Source: https://github.com/n8n-io/n8n/blob/master/docker/get-n8n.sh
set -eu
SCRIPT_VERSION="1.2.0"
SCRIPT_VERSION="1.3.0"
# The version to install is derived from the latest stable GitHub release in
# resolve_n8n_version(); this fallback only applies when that lookup fails.
FALLBACK_N8N_VERSION="2.32.0"
@@ -31,6 +31,19 @@ UPGRADE=0
NO_START=0
REQUESTED_VERSION=""
# Anonymous failure report (RudderStack): offered only when an install or
# upgrade fails, and sent only after the user answers yes. The payload is the
# script version, OS name, install|upgrade mode and the failed step — nothing
# else. The write key is a public client-side key. DO_NOT_TRACK=1 disables
# the offer entirely.
TELEMETRY_URL="https://nnrry.dataplane.rudderstack.com/v1/track"
TELEMETRY_WRITE_KEY="3IDNZwfcE0lfjVDb0Y1wp8ZVGlg"
# Set before steps whose failure could be on n8n's side (stack definition
# download, image pulls, container start, health check). Failures of the
# user's environment (no docker, port taken, ...) leave it empty and no
# report is offered.
STEP=""
# Apply ANSI styling only when stdout is a terminal that supports it and the
# user hasn't opted out (https://no-color.org).
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-dumb}" != "dumb" ]; then
@@ -53,8 +66,36 @@ fi
say() { printf '%s\n' "$*"; }
ok() { printf '%s\342\234\224%s %s\n' "$GREEN" "$RESET" "$*"; }
# Never sends without an explicit yes. The prompt uses /dev/tty because
# 'curl | sh' owns stdin; without a terminal there is no prompt and no report.
offer_failure_report() {
[ -n "$STEP" ] || return 0
[ -z "${DO_NOT_TRACK:-}" ] || return 0
printf '\nSend an anonymous failure report to n8n, so we can fix this?\n(script version, OS name, failed step — nothing else) [y/N] ' >/dev/tty 2>/dev/null || return 0
read -r answer </dev/tty 2>/dev/null || return 0
case "$answer" in
[Yy]*) ;;
*) return 0 ;;
esac
mode="install"
[ "$UPGRADE" -eq 1 ] && mode="upgrade"
body="{\"anonymousId\":\"$(gen_secret)\",\"event\":\"install_failed\",\"properties\":{\"scriptVersion\":\"${SCRIPT_VERSION}\",\"os\":\"$(uname -s)\",\"mode\":\"${mode}\",\"step\":\"${STEP}\"}}"
if command -v curl >/dev/null 2>&1; then
curl -s -o /dev/null --max-time 5 -u "${TELEMETRY_WRITE_KEY}:" \
-H 'Content-Type: application/json' -d "$body" "$TELEMETRY_URL" 2>/dev/null || true
elif command -v wget >/dev/null 2>&1; then
wget -q -O /dev/null -T 5 \
--header="Authorization: Basic $(printf '%s:' "$TELEMETRY_WRITE_KEY" | base64)" \
--header='Content-Type: application/json' \
--post-data="$body" "$TELEMETRY_URL" 2>/dev/null || true
fi
printf 'Thank you!\n' >/dev/tty 2>/dev/null || true
}
fail() {
printf '%sError:%s %s\n' "$RED" "$RED_RESET" "$*" >&2
offer_failure_report
exit 1
}
@@ -77,6 +118,7 @@ Options:
Environment:
N8N_DIR Install directory (default: ./n8n)
DO_NOT_TRACK=1 Never offer to send an anonymous failure report.
For production-grade setups (TLS, Postgres, queue mode) see:
${DOCS_HOSTING_URL}
@@ -312,6 +354,7 @@ compose() {
check_rate_limit() {
grep -qiE 'toomanyrequests|rate ?limit' "$1" || return 0
rm -f "$1"
STEP="docker-hub-rate-limit"
fail "Docker Hub pull rate limit reached — your configuration in ${N8N_DIR} is
unaffected. Wait about an hour, then start n8n with:
docker compose -f ${N8N_DIR}/compose.yml up -d
@@ -374,7 +417,9 @@ do_upgrade() {
fi
say "Pulling images..."
STEP="image-pull"
compose_pull
STEP="container-start"
compose_checked up -d --quiet-pull
ok "Restarted with n8n ${target}"
}
@@ -425,6 +470,7 @@ main() {
if [ -f "${N8N_DIR}/compose.yml" ] || [ -f "${N8N_DIR}/.env" ]; then
if [ "$UPGRADE" -eq 1 ]; then
do_upgrade
STEP="n8n-readiness-timeout"
wait_for_n8n || fail "n8n did not become ready — check 'docker compose -f ${N8N_DIR}/compose.yml logs n8n'."
print_summary
exit 0
@@ -434,10 +480,11 @@ main() {
if http_get "http://127.0.0.1:${N8N_PORT}/healthz"; then
say "n8n is already running at: http://localhost:${N8N_PORT}"
else
say "To start it: docker compose -f ${N8N_DIR}/compose.yml up -d"
say "To start it: docker compose -f ${N8N_DIR}/compose.yml up -d"
say "Once started, n8n runs at: http://localhost:${N8N_PORT}"
fi
say "To upgrade: curl -fsSL https://get.n8n.io | sh -s -- --upgrade"
say "To upgrade: curl -fsSL https://get.n8n.io | sh -s -- --upgrade"
say "To uninstall: docker compose -f ${N8N_DIR}/compose.yml down -v && rm -rf ${N8N_DIR} # DELETES all n8n data"
check_compose_freshness
exit 0
fi
@@ -454,7 +501,9 @@ main() {
INSTALL_VERSION="${REQUESTED_VERSION:-$(resolve_n8n_version)}"
mkdir -p "${N8N_DIR}"
STEP="stack-definition-download"
write_compose
STEP=""
ok "Created ${N8N_DIR}/compose.yml"
write_searxng_settings
ok "Created ${N8N_DIR}/searxng-settings.yml"
@@ -469,9 +518,12 @@ main() {
fi
say "Pulling images (this can take a few minutes on first run)..."
STEP="image-pull"
compose_pull
STEP="container-start"
compose_checked up -d --quiet-pull
ok "Started n8n ${INSTALL_VERSION} and sandbox services"
STEP="n8n-readiness-timeout"
wait_for_n8n || fail "n8n did not become ready — check 'docker compose -f ${N8N_DIR}/compose.yml logs n8n'."
print_summary
}
+52
View File
@@ -17,6 +17,8 @@ SCRIPT="$(cd "$(dirname "$0")" && pwd)/get-n8n.sh"
# on master — the harness must test this branch's compose file.
COMPOSE_SRC="$(cd "$(dirname "$0")" && pwd)/get-n8n-compose.yml"
export N8N_COMPOSE_URL="$COMPOSE_SRC"
# Failure-path tests must not prompt for (or send) a failure report.
export DO_NOT_TRACK=1
E2E=0
[ "${1:-}" = "--e2e" ] && E2E=1
@@ -61,6 +63,8 @@ sh "$SCRIPT" --version | grep -q '^get-n8n.sh v' && pass "--version prints scrip
fail "--version prints script version"
sh "$SCRIPT" --help | grep -q 'Usage:' && pass "--help prints usage" ||
fail "--help prints usage"
sh "$SCRIPT" --help | grep -q 'DO_NOT_TRACK' && pass "--help documents DO_NOT_TRACK" ||
fail "--help documents DO_NOT_TRACK"
check_not "unknown flag fails" sh "$SCRIPT" --bogus
# fresh --no-start install
@@ -103,6 +107,8 @@ rerun_out="$(env N8N_DIR="$WORK/a" sh "$SCRIPT" 2>&1)" && pass "re-run on existi
fail "re-run leaves files untouched"
echo "$rerun_out" | grep -q 'http://localhost:5678' && pass "re-run tells the user where n8n runs" ||
fail "re-run tells the user where n8n runs"
echo "$rerun_out" | grep -q 'To uninstall: docker compose' && pass "re-run shows the uninstall command" ||
fail "re-run shows the uninstall command"
# stack definition versioning
[ -n "$(sed -n 's/^# compose-version: *//p' "$WORK/a/compose.yml")" ] &&
@@ -171,6 +177,52 @@ ratelimit_out="$(env PATH="$WORK/shim:$PATH" N8N_DIR="$WORK/ratelimit" sh "$SCRI
echo "$ratelimit_out" | grep -q 'pull rate limit reached' && pass "rate-limit failure prints recovery advice" ||
fail "rate-limit failure prints recovery advice"
# failure-report consent: a reportable failure on a terminal must prompt, send
# the failed step only on an explicit yes, and send nothing on the default No.
# Needs a pty, so run the script under script(1); its syntax differs between
# util-linux and BSD/macOS, and Git Bash has neither. The curl shim records
# telemetry calls and fails everything else the way an offline curl would.
if [ "$WINDOWS" -eq 0 ] && command -v script >/dev/null 2>&1; then
cat >"$WORK/shim/curl" <<EOF
#!/bin/sh
case "\$*" in
*rudderstack*) echo "\$*" >>"$WORK/consent.log" ;;
esac
exit 6
EOF
chmod +x "$WORK/shim/curl"
run_with_tty() { # run_with_tty <answer> <cmd...>
answer="$1"
shift
if script --version 2>/dev/null | grep -q util-linux; then
{
sleep 1
printf '%s\n' "$answer"
sleep 4
} | script -qec "$*" /dev/null
else
{
sleep 1
printf '%s\n' "$answer"
sleep 4
} | script -q /dev/null "$@"
fi
}
rm -f "$WORK/consent.log"
run_with_tty y env "PATH=$WORK/shim:$PATH" DO_NOT_TRACK= N8N_DIR="$WORK/ratelimit" \
sh "$SCRIPT" --upgrade --version 2.32.0 >/dev/null 2>&1
grep -q '"event":"install_failed".*"step":"docker-hub-rate-limit"' "$WORK/consent.log" 2>/dev/null &&
pass "consented failure report carries the failed step" ||
fail "consented failure report carries the failed step"
rm -f "$WORK/consent.log"
run_with_tty '' env "PATH=$WORK/shim:$PATH" DO_NOT_TRACK= N8N_DIR="$WORK/ratelimit" \
sh "$SCRIPT" --upgrade --version 2.32.0 >/dev/null 2>&1
[ ! -s "$WORK/consent.log" ] && pass "declined failure report sends nothing" ||
fail "declined failure report sends nothing"
else
skip "failure-report consent tests (need script(1) for a pty)"
fi
# truncated download must execute nothing
mkdir -p "$WORK/trunc" && cd "$WORK/trunc" || exit 1
head -c 1000 "$SCRIPT" | sh >/dev/null 2>&1