Files
n8n-install/scripts/telemetry.sh

232 lines
8.7 KiB
Bash

#!/bin/bash
# =============================================================================
# telemetry.sh - Anonymous telemetry for n8n-install
# =============================================================================
# Sends anonymous usage statistics to help improve the project.
# No personal data is collected. Users can opt-out by setting:
# SCARF_ANALYTICS=false or DO_NOT_TRACK=1
#
# Depends on: utils.sh (must be sourced first for ENV_FILE, PROJECT_ROOT)
#
# Functions:
# get_installation_id() - Get or generate persistent installation ID
# get_os_type() - Detect OS type
# send_telemetry() - Send anonymous telemetry event
# set_telemetry_stage() - Set current stage for error tracking
# send_error_telemetry() - Send error telemetry event
# setup_error_telemetry_trap() - Setup global ERR trap
# save_installation_id() - Save installation ID to .env
#
# Usage: source "$SCRIPT_DIR/telemetry.sh"
# =============================================================================
# Scarf endpoint for anonymous telemetry
SCARF_ENDPOINT="https://kossakovsky.gateway.scarf.sh/n8n-install"
#=============================================================================
# INSTALLATION ID
#=============================================================================
# Get or generate installation ID (persistent across sessions)
# Usage: id=$(get_installation_id)
# Priority: 1) exported env var, 2) .env file, 3) generate new
get_installation_id() {
# First check if ID is already in environment (exported from parent process)
# This ensures consistency when called multiple times before .env exists
if [[ -n "${INSTALLATION_ID:-}" ]]; then
echo "$INSTALLATION_ID"
return 0
fi
# Then check .env file for existing ID
if [[ -f "$ENV_FILE" ]]; then
local existing_id
existing_id=$(grep "^INSTALLATION_ID=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2- | tr -d '"' | tr -d "'")
if [[ -n "$existing_id" ]]; then
echo "$existing_id"
return 0
fi
fi
# Generate new ID (12 char hex = 6 bytes)
local new_id
new_id=$(od -An -tx1 -N6 /dev/urandom | tr -d ' \n')
# Save to .env if file exists
if [[ -f "$ENV_FILE" ]]; then
if grep -q "^INSTALLATION_ID=" "$ENV_FILE" 2>/dev/null; then
sed -i.bak "s/^INSTALLATION_ID=.*/INSTALLATION_ID=\"${new_id}\"/" "$ENV_FILE"
rm -f "${ENV_FILE}.bak"
else
echo "INSTALLATION_ID=\"${new_id}\"" >> "$ENV_FILE"
fi
fi
echo "$new_id"
}
# Save installation ID to .env file
# Usage: save_installation_id
# Priority: 1) exported from parent, 2) existing .env, 3) generate new
# Note: Requires write_env_var from utils.sh
save_installation_id() {
local output_file="${1:-$ENV_FILE}"
# Priority: exported var > existing .env value > generate new
local install_id="${INSTALLATION_ID:-}"
if [[ -z "$install_id" && -f "$output_file" ]]; then
install_id=$(grep "^INSTALLATION_ID=" "$output_file" 2>/dev/null | cut -d'=' -f2- | tr -d '"' | tr -d "'")
fi
if [[ -z "$install_id" ]]; then
# Generate new ID for existing installations upgrading from pre-telemetry version
install_id=$(od -An -tx1 -N6 /dev/urandom | tr -d ' \n')
fi
write_env_var "INSTALLATION_ID" "$install_id" "$output_file"
}
#=============================================================================
# OS DETECTION
#=============================================================================
# Detect OS type
# Usage: os=$(get_os_type)
get_os_type() {
if [[ -f /etc/os-release ]]; then
. /etc/os-release
echo "${ID:-linux}-${VERSION_ID:-unknown}"
elif [[ "$(uname)" == "Darwin" ]]; then
echo "macos-$(sw_vers -productVersion 2>/dev/null || echo 'unknown')"
else
echo "$(uname -s | tr '[:upper:]' '[:lower:]')-unknown"
fi
}
#=============================================================================
# TELEMETRY EVENTS
#=============================================================================
# Send anonymous telemetry event
# Usage: send_telemetry "install_start"
# send_telemetry "install_complete" "$COMPOSE_PROFILES"
# send_telemetry "update_start"
# send_telemetry "update_complete" "$COMPOSE_PROFILES"
# Arguments:
# $1 - event_type: "install_start", "install_complete", "update_start", "update_complete"
# $2 - services (optional): comma-separated list of selected profiles (for *_complete events)
# When provided, sends a separate request for each service to enable per-service analytics.
# Note: To get installation ID, call get_installation_id directly before this function
send_telemetry() {
local event_type="$1"
local services="${2:-}"
# Load environment (for SCARF_ANALYTICS check and INSTALLATION_ID)
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE" 2>/dev/null
# Get installation ID and OS
local install_id
install_id=$(get_installation_id)
local os_type
os_type=$(get_os_type)
# Opt-out check: respect SCARF_ANALYTICS and DO_NOT_TRACK
if [[ "${SCARF_ANALYTICS:-true}" == "false" ]] || [[ "${DO_NOT_TRACK:-0}" == "1" ]]; then
return 0
fi
# Get version from VERSION file
local version="unknown"
if [[ -f "$PROJECT_ROOT/VERSION" ]]; then
version=$(cat "$PROJECT_ROOT/VERSION" | tr -d '\n\r')
fi
# Build base URL with query parameters
local base_url="${SCARF_ENDPOINT}?event=${event_type}&version=${version}&id=${install_id}&os=${os_type}"
if [[ -n "$services" ]]; then
# Split services by comma and send individual request for each service
# This allows Scarf to track usage statistics per service
local service
IFS=',' read -ra service_array <<< "$services"
for service in "${service_array[@]}"; do
# Trim whitespace
service=$(echo "$service" | xargs)
[[ -z "$service" ]] && continue
# Send telemetry for this service in background
curl -sf --connect-timeout 2 --max-time 2 "${base_url}&services=${service}" >/dev/null 2>&1 &
done
else
# No services specified, send single event
curl -sf --connect-timeout 2 --max-time 2 "$base_url" >/dev/null 2>&1 &
fi
}
#=============================================================================
# ERROR TELEMETRY
#=============================================================================
# Tracks installation/update failures with stage and exit code.
# Uses same opt-out as regular telemetry (SCARF_ANALYTICS=false or DO_NOT_TRACK=1)
# Set current telemetry stage for error tracking
# Usage: set_telemetry_stage "system_prep"
set_telemetry_stage() {
export TELEMETRY_STAGE="$1"
}
# Send error telemetry event (called by trap handler)
# Usage: send_error_telemetry 1
# Arguments:
# $1 - exit_code: The exit code that triggered the error
send_error_telemetry() {
local exit_code="${1:-1}"
local stage="${TELEMETRY_STAGE:-unknown}"
# Prevent duplicate sends
[[ "${TELEMETRY_ERROR_SENT:-0}" == "1" ]] && return 0
export TELEMETRY_ERROR_SENT=1
# Load environment (for SCARF_ANALYTICS check)
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE" 2>/dev/null
# Opt-out check: respect SCARF_ANALYTICS and DO_NOT_TRACK
[[ "${SCARF_ANALYTICS:-true}" == "false" || "${DO_NOT_TRACK:-0}" == "1" ]] && return 0
# Determine event type based on stage prefix
local event_type="install_error"
[[ "$stage" == update_* || "$stage" == git_* ]] && event_type="update_error"
# Get installation ID and OS
local install_id
install_id=$(get_installation_id)
local os_type
os_type=$(get_os_type)
# Get version from VERSION file
local version="unknown"
[[ -f "$PROJECT_ROOT/VERSION" ]] && version=$(cat "$PROJECT_ROOT/VERSION" | tr -d '\n\r')
# Build URL with error-specific parameters
local url="${SCARF_ENDPOINT}?event=${event_type}&version=${version}&id=${install_id}&os=${os_type}&stage=${stage}&exit_code=${exit_code}"
# Send telemetry in background with short timeout (non-blocking, silent)
curl -sf --connect-timeout 2 --max-time 2 "$url" >/dev/null 2>&1 &
}
# Internal trap handler for ERR signal
_telemetry_error_handler() {
local exit_code=$?
# Only send if we actually have an error (exit_code > 0)
[[ $exit_code -gt 0 ]] && send_error_telemetry "$exit_code"
# Re-exit with the original code (important for set -e behavior)
exit $exit_code
}
# Setup global ERR trap for error telemetry
# Usage: setup_error_telemetry_trap
# Note: Only call this in top-level orchestrator scripts (install.sh, apply_update.sh, update.sh)
setup_error_telemetry_trap() {
trap '_telemetry_error_handler' ERR
}