#!/usr/bin/env bash # Ignore SC2317 for the entire file # shellcheck disable=SC2317 # Copyright (c) 2024 Expanso Inc. # SPDX-License-Identifier: MIT # # This script is MIT licensed. The Expanso software (expanso-edge, expanso-cli) # installed by this script is proprietary software subject to the Expanso Terms # of Service at https://expanso.io/terms #============================================================================= # CONFIGURATION VARIABLES #============================================================================= # EXPANSO binary installation location : "${EXPANSO_INSTALL_DIR:="/usr/local/bin"}" # EXPANSO version to install (empty for latest) : "${EXPANSO_VERSION:=""}" # Module type - will be set by the install script server EXPANSO_MODULE_TYPE="edge" # sudo is required to copy binary to EXPANSO_INSTALL_DIR for linux : "${USE_SUDO:="false"}" # Binary naming will be determined from the manifest response # Initial values will be updated after fetching artifact info EXPANSO_BINARY_NAME="" EXPANSO_BINARY_PATH="" # Analytics and verification EXPANSO_INSTALLATION_ID="" EXPANSO_INSTALL_SCRIPT_HASH="91760dc25149a94c686f518607cbe075bb4b9b04" EXPANSO_HTTP_REQUEST_CLI="curl" # Default http request CLI # Node bootstrap parameters (filled by install script server) EXPANSO_BOOTSTRAP_TOKEN="" EXPANSO_BOOTSTRAP_URL="" # PostHog Configuration POSTHOG_ENDPOINT="https://installs.t.expanso.io/capture/" POSTHOG_EVENT_NAME="expanso.install_v1" # Disable telemetry if needed (default: enabled) : "${EXPANSO_DISABLEANALYTICS:="false"}" # Current time in seconds for tracking duration START_TIME=$(date +%s) # Expanso public key for verification EXPANSO_PUBLIC_KEY=$(cat <<-END -----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAygfObSuJJmuOeRaWh3e2 Xx0v7TunQjlN0HrmXA+SMALx2H+ayYQlQUVdIWYDn+IUnQ72INOMBLRN9UkgbVxw 2QulcNrc51tEchtJdneXBOYr10VMWcdDDZesoF8WDDq/EiWsPCkKmut9h0AkdZU7 wf3wuK0GsiKc1XyX8H3eE+UIE4/G73e9tC7gJ+OHkyO+LLBVzZMDDQN+XC2WrUAj eclHmxoQV2UXPaHSPhpGWmwEWJCCpR+yHlHa8czpeLTqree+li2gwlY4WL1VXYLj btC7cnW8NWQL53q8ntxjO5uJvIiWszU7irIvWfR8WK+cXdMX6OZN1jilEHMFBvG5 p0F94AGYG3iNklYJlcbVoeJGtVjb+vW4agUG0DtLNVQiggZx3uYcV/tZZhCBR1i2 oHCrZxyjL3Hckq3SEqNHIakMHZusKRUDsvBWiZtcY1HmxY94/L1+WRo/1gD0+eyI Wn4c2n1FP23H9tToVSOU7GMAXdqmD5xyCdjdmhjDTwCUWycVj5R2IBe1uJfJx/NF nhfudFkIgIWGHU/uyajwCGTbhP4/j6C+Lt4AVwEApgf4sigHkolET+ZluifNQUXs 16kJN0YXtC0YXD+42cx4H167gDtFVqdqPhXZ3vVd0SgBsMkN7dF706JpqO80HMzj 2DV/WzixxHzFL3CcaDw2IqMCAwEAAQ== -----END PUBLIC KEY----- END ) #============================================================================= # HELPER FUNCTIONS #============================================================================= # Display usage information usage() { echo "Usage: $0 [VERSION] [options]" echo "or: $0 [options]" echo "" echo "Options:" echo " -v, --version VERSION Specify Expanso version to install (with or without 'v' prefix)" echo " -d, --dir DIRECTORY Specify installation directory" echo " -h, --help Show this help message" echo "" echo "If VERSION is provided as the first argument, it will be used as the version to install." } # Check if a command exists command_exists() { command -v "$1" > /dev/null 2>&1 } # Run a command with sudo if needed runAsRoot() { local cmd=( "$@" ) if [ "$EUID" -ne 0 ] && [ "$USE_SUDO" = "true" ]; then cmd=( sudo "${cmd[@]}" ) fi "${cmd[@]}" } # Setup temporary directory and cleanup handler setup_tmp() { EXPANSO_TMP_ROOT=$(mktemp -d 2>/dev/null || mktemp -d -t 'expanso-install.XXXXXXXXXX') cleanup_tmp() { code=$? set +e trap - EXIT rm -rf "${EXPANSO_TMP_ROOT}" exit $code } trap cleanup_tmp INT EXIT } # Cleanup temporary files cleanup() { if [[ -d "${EXPANSO_TMP_ROOT:-}" ]]; then rm -rf "$EXPANSO_TMP_ROOT" fi } # Handle failure trap fail_trap() { result=$? if [ "$result" != "0" ]; then echo "" >&2 echo "Installation failed (exit code: $result)" >&2 echo "" >&2 # Add standardized failure info to telemetry add_telemetry_property "status" "failed" "string" # Don't override a more specific failure reason if already set if ! [[ "$TELEMETRY_PROPERTIES" =~ \"failure_reason\" ]]; then add_telemetry_property "failure_reason" "unknown_error" "string" fi # Send the single telemetry event before exiting send_telemetry fi cleanup exit "$result" } #============================================================================= # SYSTEM DETECTION FUNCTIONS #============================================================================= # Get system architecture and OS information getSystemInfo() { # Detect architecture ARCH=$(uname -m) case $ARCH in armv7*) ARCH="arm7" ;; armv6*) ARCH="arm6" ;; aarch64) ARCH="arm64" ;; x86_64) ARCH="amd64" ;; esac # Detect OS OS=$(eval "echo $(uname)|tr '[:upper:]' '[:lower:]'") # Set sudo requirements based on OS and install location if { [ "$OS" == "linux" ] || [ "$OS" == "darwin" ]; } && [ "$EXPANSO_INSTALL_DIR" == "/usr/local/bin" ]; then USE_SUDO="true" fi # Detect Linux distribution if possible if command_exists lsb_release; then DISTRO=$(lsb_release -si) else DISTRO="NOLSB" fi # Add system info to telemetry add_telemetry_property "os_type" "$OS" "string" add_telemetry_property "os_arch" "$ARCH" "string" add_telemetry_property "platform" "$DISTRO" "string" add_telemetry_property "use_sudo" "$USE_SUDO" "string" add_telemetry_property "module_type" "$EXPANSO_MODULE_TYPE" "string" # Store system info for reference SYSTEM_INFO="operating_system=$OS,architecture=$ARCH,platform=$DISTRO" } # Verify that the system is supported verifySupported() { local supported=(linux-amd64 linux-arm64 linux-arm7 linux-arm6 darwin-amd64 darwin-arm64) local current_osarch="${OS}-${ARCH}" for osarch in "${supported[@]}"; do if [ "$osarch" == "$current_osarch" ]; then echo "Your system is ${OS}_${ARCH}. Your platform is $DISTRO." return fi done echo "No prebuilt binary for ${current_osarch} and platform ${DISTRO}." add_telemetry_property "failure_reason" "unsupported_system" "string" exit 1 } #============================================================================= # HTTP AND DOWNLOAD FUNCTIONS #============================================================================= # Check for HTTP request CLI availability checkHttpRequestCLI() { if command_exists "curl"; then EXPANSO_HTTP_REQUEST_CLI=curl elif command_exists "wget"; then EXPANSO_HTTP_REQUEST_CLI=wget else echo "Either curl or wget is required" exit 1 fi } # Make HTTP requests with retry logic httpRequest() { local url=$1 local max_retries=${2:-3} local retry_delay=${3:-1} local retry_timeout_seconds=${4:-60} local response local http_code if [ "$EXPANSO_HTTP_REQUEST_CLI" == "curl" ]; then # For curl, use a single temp file for the response body local tmp_body tmp_body=$(mktemp -p "$EXPANSO_TMP_ROOT" body.XXXXXXXXXX) # Make the request with curl # Write status code to variable and capture response to file http_code=$(curl \ --retry "$max_retries" \ --retry-delay "$retry_delay" \ --retry-max-time "$retry_timeout_seconds" \ --silent \ --write-out "%{http_code}" \ --output "$tmp_body" \ "$url") response=$(cat "$tmp_body") else # For wget, use separate files for headers and body local tmp_headers local tmp_body tmp_headers=$(mktemp -p "$EXPANSO_TMP_ROOT" headers.XXXXXXXXXX) tmp_body=$(mktemp -p "$EXPANSO_TMP_ROOT" body.XXXXXXXXXX) # Make the request with wget wget \ --tries="$max_retries" \ --wait="$retry_delay" \ --retry-connrefused \ --timeout="$retry_timeout_seconds" \ --server-response \ --quiet \ --output-document="$tmp_body" \ "$url" 2>"$tmp_headers" # Extract HTTP status code from headers http_code=$(awk '/^ HTTP/{print $2}' "$tmp_headers" | tail -n 1) # If no HTTP code was found, set a default failure code if [ -z "$http_code" ]; then http_code=500 fi # Get response content response=$(cat "$tmp_body") fi # Output HTTP code first, then response (to be captured by caller) echo "$http_code" echo "$response" } # Download files with retry logic httpDownload() { local url=$1 local output=$2 local max_retries=${3:-3} local retry_delay=${4:-1} local retry_timeout_seconds=${5:-60} local exit_code=0 if [ "$EXPANSO_HTTP_REQUEST_CLI" == "curl" ]; then curl \ --retry "$max_retries" \ --retry-delay "$retry_delay" \ --retry-max-time "$retry_timeout_seconds" \ --show-error \ --silent \ --location \ --no-buffer \ --output "$output" \ "$url" exit_code=$? else wget \ --tries="$max_retries" \ --wait="$retry_delay" \ --retry-connrefused \ --timeout="$retry_timeout_seconds" \ --quiet \ --output-document="$output" \ "$url" 2>/dev/null # Redirecting stderr to avoid server response output exit_code=$? fi return $exit_code } #============================================================================= # POSTHOG TELEMETRY FUNCTIONS #============================================================================= # Initialize empty telemetry properties TELEMETRY_PROPERTIES="" # Add property to the telemetry collection # This function collects all properties during the installation # but does not send any events until the end add_telemetry_property() { local property_name=$1 local property_value=$2 local property_type=$3 # "string" or "boolean" or "number" # Format property based on type local formatted_property case "$property_type" in "string") formatted_property="\"$property_name\":\"$property_value\"" ;; "boolean"|"number") formatted_property="\"$property_name\":$property_value" ;; *) formatted_property="\"$property_name\":\"$property_value\"" ;; esac # Add to global properties if [ -z "$TELEMETRY_PROPERTIES" ]; then TELEMETRY_PROPERTIES="$formatted_property" else TELEMETRY_PROPERTIES="$TELEMETRY_PROPERTIES,$formatted_property" fi } # Get a distinct ID for telemetry with multiple fallback mechanisms # Returns: ID and source as "id:source" # Get a distinct ID for telemetry with multiple fallback mechanisms # Returns: ID and source as "id:source" get_distinct_id() { local distinct_id="" local id_source="" # Helper function to return result consistently return_result() { local id=$1 local source=$2 echo "${id}:${source}" return 0 } # 1. Try passed installation ID from environment if [ -n "$EXPANSO_INSTALLATION_ID" ] && [[ "$EXPANSO_INSTALLATION_ID" != *REPLACE_ME* ]]; then return_result "$EXPANSO_INSTALLATION_ID" "installation_id_env" return 0 fi # 2. Try to read from installation ID file local config_dir if [[ "$OS" == "mingw"* || "$OS" == "msys"* || "$OS" == "cygwin"* ]]; then config_dir="$APPDATA/expanso/$EXPANSO_MODULE_TYPE" else config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/expanso/$EXPANSO_MODULE_TYPE" fi local id_file="$config_dir/installation_id" if [ -f "$id_file" ] && [ -r "$id_file" ]; then distinct_id=$(cat "$id_file" 2>/dev/null) if [ -n "$distinct_id" ]; then return_result "$distinct_id" "installation_id_file" return 0 fi fi # 3. Try to read from system metadata local expanso_metadata="$HOME/.expanso/system_metadata.yaml" if [ -f "$expanso_metadata" ] && [ -r "$expanso_metadata" ]; then # Try InstanceID first distinct_id=$(grep -E "^InstanceID:" "$expanso_metadata" 2>/dev/null | awk '{print $2}' | tr -d '[:space:]') if [ -n "$distinct_id" ]; then return_result "$distinct_id" "instance_id" return 0 fi fi # 4. Try to generate hash from machine-specific components local mac_addr="" hostname="" machine_id="" # Get MAC address if command_exists "ifconfig"; then mac_addr=$(ifconfig 2>/dev/null | grep -E "ether|HWaddr" | head -n 1 | awk '{print $2}' | tr -d ':.-') elif command_exists "ip"; then mac_addr=$(ip link 2>/dev/null | grep -E "link/ether" | head -n 1 | awk '{print $2}' | tr -d ':.-') elif [ -d "/sys/class/net" ]; then local primary_if=$(ls -1 /sys/class/net/ | grep -v "lo" | head -n 1) [ -n "$primary_if" ] && [ -f "/sys/class/net/$primary_if/address" ] && \ mac_addr=$(cat "/sys/class/net/$primary_if/address" | tr -d ':.-') fi # Get hostname hostname=$(hostname 2>/dev/null || echo "unknown") # Get machine-id if [ -f "/etc/machine-id" ] && [ -r "/etc/machine-id" ]; then machine_id=$(cat "/etc/machine-id" 2>/dev/null | tr -d '[:space:]') elif [ -f "/var/lib/dbus/machine-id" ] && [ -r "/var/lib/dbus/machine-id" ]; then machine_id=$(cat "/var/lib/dbus/machine-id" 2>/dev/null | tr -d '[:space:]') fi # Generate hash if we have any components if [ -n "$mac_addr" ] || [ -n "$machine_id" ] || [ -n "$hostname" ]; then local hash_base="${machine_id}${mac_addr}${hostname}" # Try to generate hash with available tools if command_exists "openssl"; then distinct_id=$(echo -n "$hash_base" | openssl sha256 2>/dev/null | awk '{print $2}') if [ -n "$distinct_id" ]; then return_result "$distinct_id" "machine_identifiers" return 0 fi elif command_exists "shasum"; then distinct_id=$(echo -n "$hash_base" | shasum -a 256 2>/dev/null | awk '{print $1}') if [ -n "$distinct_id" ]; then return_result "$distinct_id" "machine_identifiers" return 0 fi elif command_exists "sha256sum"; then distinct_id=$(echo -n "$hash_base" | sha256sum 2>/dev/null | awk '{print $1}') if [ -n "$distinct_id" ]; then return_result "$distinct_id" "machine_identifiers" return 0 fi fi fi # Ultimate fallback return_result "unknown" "unknown" } # Send a single telemetry event at the end send_telemetry() { # Skip if telemetry is disabled if [ "$EXPANSO_DISABLEANALYTICS" = "true" ]; then return 0 fi # Calculate elapsed time local elapsed_time_in_seconds=$(($(date +%s) - START_TIME)) # Get a distinct ID for telemetry and parse the result local result result=$(get_distinct_id) # Split the result into ID and source # The format is "id:source" local telemetry_id local id_source # Extract ID (everything before the colon) telemetry_id=$(echo "$result" | cut -d':' -f1) # Extract source (everything after the colon) id_source=$(echo "$result" | cut -d':' -f2) # Add standard properties add_telemetry_property "distinct_id" "$telemetry_id" "string" add_telemetry_property "id_source" "$id_source" "string" add_telemetry_property "script_git_hash" "$EXPANSO_INSTALL_SCRIPT_HASH" "string" add_telemetry_property "duration_seconds" "$elapsed_time_in_seconds" "number" # Create payload with all collected properties local payload="{\"event\":\"$POSTHOG_EVENT_NAME\",\"properties\":{$TELEMETRY_PROPERTIES}}" # Send telemetry asynchronously (don't block installation) if [ "$EXPANSO_HTTP_REQUEST_CLI" == "curl" ]; then curl -s -X POST "$POSTHOG_ENDPOINT" \ -H "Content-Type: application/json" \ -d "$payload" \ --max-time 5 \ >/dev/null 2>&1 & elif [ "$EXPANSO_HTTP_REQUEST_CLI" == "wget" ]; then # Create a temporary file for the payload local tmp_payload tmp_payload=$(mktemp -p "$EXPANSO_TMP_ROOT" payload.XXXXXXXXXX) echo "$payload" > "$tmp_payload" wget --quiet --post-file="$tmp_payload" \ --header="Content-Type: application/json" \ --timeout=5 \ "$POSTHOG_ENDPOINT" \ -O /dev/null 2>&1 & fi } # Store installation ID on user's system addInstallationID() { # Return early if EXPANSO_INSTALLATION_ID is empty or still has the placeholder value if [ -z "$EXPANSO_INSTALLATION_ID" ] || [[ "$EXPANSO_INSTALLATION_ID" == *REPLACE_ME* ]]; then return 0 # Best effort: if the id is invalid, return early fi # Determine the appropriate config directory based on the OS local id_file if [[ "$OS" == "mingw"* || "$OS" == "msys"* || "$OS" == "cygwin"* ]]; then # Use APPDATA on Windows id_file="$APPDATA/expanso/$EXPANSO_MODULE_TYPE/installation_id" else # Use XDG standard or default to ~/.config for Linux and Darwin local config_dir="${XDG_CONFIG_HOME:-$HOME/.config}" id_file="$config_dir/expanso/$EXPANSO_MODULE_TYPE/installation_id" fi # Create the directory if it doesn't exist local id_dir id_dir=$(dirname "$id_file") if ! mkdir -p "$id_dir"; then add_telemetry_property "installation_id_failure" "failed_to_create_dir" "string" return 0 # Best effort: if mkdir fails, return early fi # Write the installation ID to the file if ! echo "$EXPANSO_INSTALLATION_ID" > "$id_file"; then add_telemetry_property "installation_id_failure" "failed_to_write_file" "string" return 0 # Best effort: if write fails, return early fi # Set appropriate permissions (readable and writable by user only) if ! chmod 600 "$id_file" 2>/dev/null; then add_telemetry_property "installation_id_failure" "failed_to_set_permissions" "string" return 0 # Best effort: if chmod fails, return early fi return 0 } #============================================================================= # DOWNLOAD AND VERIFICATION FUNCTIONS #============================================================================= # Parse artifact information from API response parseArtifactInfo() { local artifactInfo=$1 local downloadUrl local signatureUrl local version local binaryName # Extract URLs and version from the response downloadUrl=$(echo "$artifactInfo" | grep -o '"downloadUrl":"[^"]*"' | cut -d'"' -f4) signatureUrl=$(echo "$artifactInfo" | grep -o '"signatureUrl":"[^"]*"' | cut -d'"' -f4) version=$(echo "$artifactInfo" | grep -o '"version":"[^"]*"' | cut -d'"' -f4) binaryName=$(echo "$artifactInfo" | grep -o '"binaryName":"[^"]*"' | cut -d'"' -f4) # Check all required fields if [ -z "$downloadUrl" ] || [ -z "$signatureUrl" ] || [ -z "$version" ] || [ -z "$binaryName" ]; then echo "Error: Missing required fields in API response" >&2 echo " downloadUrl: ${downloadUrl:-}" >&2 echo " signatureUrl: ${signatureUrl:-}" >&2 echo " version: ${version:-}" >&2 echo " binaryName: ${binaryName:-}" >&2 add_telemetry_property "failure_reason" "artifact_parse_failed" "string" return 1 fi add_telemetry_property "binary_name" "$binaryName" "string" # Return values as a space-separated string echo "$downloadUrl $signatureUrl $version $binaryName" } # Get artifact information from Expanso API getArtifactInfo() { add_telemetry_property "version_requested" "$EXPANSO_VERSION" "string" # Normalize version string if [ -n "$EXPANSO_VERSION" ]; then case "$EXPANSO_VERSION" in latest) EXPANSO_VERSION="stable" ;; pre-release | pre | pre_release) EXPANSO_VERSION="pre" ;; esac else EXPANSO_VERSION="stable" fi # Get the artifact information from the resolve endpoint local resolveUrl="https://get.expanso.io/api/${EXPANSO_MODULE_TYPE}/artifacts/resolve?os=${OS}&arch=${ARCH}&release=${EXPANSO_VERSION}" local response_output local http_code local response response_output=$(httpRequest "$resolveUrl") http_code=$(echo "$response_output" | head -n 1) response=$(echo "$response_output" | tail -n +2) if [ "$http_code" != "200" ]; then case "$http_code" in "404") echo "Error: Requested version $EXPANSO_VERSION does not exist. HTTP status code: $http_code" >&2 add_telemetry_property "failure_reason" "artifact_not_found" "string" ;; "400") echo "Error: Invalid version $EXPANSO_VERSION. HTTP status code: $http_code" >&2 add_telemetry_property "failure_reason" "artifact_invalid_version" "string" ;; *) echo "Error: Failed to fetch version information. HTTP status code: $http_code" >&2 add_telemetry_property "failure_reason" "artifact_fetch_failed" "string" ;; esac exit 1 fi if [ -z "$response" ]; then echo "Error: Received empty response from resolve endpoint" >&2 add_telemetry_property "failure_reason" "artifact_empty_response" "string" exit 1 fi ret_val=$response } # Download binary and signature files downloadFile() { local version=$1 local downloadUrl=$2 local signatureUrl=$3 BINARY_TMP_FILE="$EXPANSO_TMP_ROOT/expanso.tar.gz" SIGNATURE_TMP_FILE="$EXPANSO_TMP_ROOT/expanso.tar.gz.sig" echo "Downloading $downloadUrl ..." add_telemetry_property "version_download" "$version" "string" add_telemetry_property "download_url" "$downloadUrl" "string" httpDownload "$downloadUrl" "$BINARY_TMP_FILE" if [ ! -f "$BINARY_TMP_FILE" ]; then echo "Failed to download $downloadUrl ..." add_telemetry_property "failure_reason" "binary_download_failed" "string" exit 1 fi echo "Downloading sig file $signatureUrl ..." add_telemetry_property "signature_url" "$signatureUrl" "string" httpDownload "$signatureUrl" "$SIGNATURE_TMP_FILE" if [ ! -f "$SIGNATURE_TMP_FILE" ]; then echo "Failed to download $signatureUrl ..." add_telemetry_property "failure_reason" "signature_download_failed" "string" exit 1 fi } # Verify tarball signature verifyTarBall() { if ! command_exists openssl; then echo "WARNING: openssl could not be found. We are NOT verifying this tarball is correct!" add_telemetry_property "verification_status" "skipped" "string" return fi echo "$EXPANSO_PUBLIC_KEY" > "$EXPANSO_TMP_ROOT/EXPANSO_public_file.pem" openssl base64 -d -in "$SIGNATURE_TMP_FILE" -out "$SIGNATURE_TMP_FILE".decoded if openssl dgst -sha256 -verify "$EXPANSO_TMP_ROOT/EXPANSO_public_file.pem" -signature "$SIGNATURE_TMP_FILE".decoded "$BINARY_TMP_FILE"; then add_telemetry_property "verification_status" "success" "string" return else echo "Failed to verify signature of tarball." add_telemetry_property "failure_reason" "verification_failed" "string" add_telemetry_property "verification_status" "failed" "string" exit 1 fi } # Extract the tarball expandTarBall() { echo "Extracting tarball ..." tar xzf "$BINARY_TMP_FILE" -C "$EXPANSO_TMP_ROOT" } #============================================================================= # NODE BOOTSTRAP FUNCTIONS #============================================================================= # Bootstrap the node with Expanso Edge (only for edge installations) bootstrapNode() { # Only bootstrap for edge installations if [ "$EXPANSO_MODULE_TYPE" != "edge" ]; then return 0 fi # Check if bootstrap token is provided if [ -z "$EXPANSO_BOOTSTRAP_TOKEN" ] || [[ "$EXPANSO_BOOTSTRAP_TOKEN" == *REPLACE_ME* ]]; then add_telemetry_property "bootstrap_attempted" "false" "boolean" add_telemetry_property "bootstrap_skip_reason" "no_token" "string" return 0 fi echo "Bootstrapping node with Expanso Cloud..." add_telemetry_property "bootstrap_attempted" "true" "boolean" # Prepare bootstrap command local reg_cmd=("$EXPANSO_BINARY_PATH" "bootstrap" "--token" "$EXPANSO_BOOTSTRAP_TOKEN") # Add bootstrap URL if provided if [ -n "$EXPANSO_BOOTSTRAP_URL" ] && [[ "$EXPANSO_BOOTSTRAP_URL" != *REPLACE_ME* ]]; then reg_cmd+=("--url" "$EXPANSO_BOOTSTRAP_URL") fi # Execute bootstrap command if "${reg_cmd[@]}"; then add_telemetry_property "bootstrap_status" "success" "string" else local exit_code=$? echo "Node bootstrap failed with exit code: $exit_code" >&2 echo "You can manually bootstrap later using:" echo " ${reg_cmd[*]}" add_telemetry_property "bootstrap_status" "failed" "string" add_telemetry_property "bootstrap_exit_code" "$exit_code" "number" # Don't fail the entire installation if bootstrap fails fi } #============================================================================= # INSTALLATION FUNCTIONS #============================================================================= # Create installation directory if it doesn't exist createInstallDir() { if [ "$USE_SUDO" != "true" ] && [ ! -d "$EXPANSO_INSTALL_DIR" ]; then echo "Custom installation directory $EXPANSO_INSTALL_DIR does not exist. Creating it now..." if mkdir -p "$EXPANSO_INSTALL_DIR"; then echo "Successfully created directory $EXPANSO_INSTALL_DIR" else echo "Failed to create directory $EXPANSO_INSTALL_DIR" add_telemetry_property "failure_reason" "create_dir_failed" "string" exit 1 fi fi } # Check for existing Expanso installation checkExistingExpanso() { if command_exists "$EXPANSO_BINARY_NAME"; then current_version=$($EXPANSO_BINARY_NAME version) echo -e "${EXPANSO_BINARY_NAME} is detected: $current_version" echo -e "Reinstalling ${EXPANSO_BINARY_PATH}..." add_telemetry_property "existing_installation" "true" "boolean" add_telemetry_property "version_existing" "$current_version" "string" else echo -e "No ${EXPANSO_BINARY_NAME} detected. Fresh installation..." add_telemetry_property "existing_installation" "false" "boolean" fi } # Install the Expanso binary installFile() { local tmp_root_expanso_cli="$EXPANSO_TMP_ROOT/$EXPANSO_BINARY_NAME" if [ ! -f "$tmp_root_expanso_cli" ]; then echo "Failed to unpack ${EXPANSO_BINARY_NAME} executable." add_telemetry_property "failure_reason" "unpacking_failed" "string" exit 1 fi # Set permissions to allow execution by owner, group, and others chmod 755 "$tmp_root_expanso_cli" if [ -f "$EXPANSO_BINARY_PATH" ]; then runAsRoot rm -f "$EXPANSO_BINARY_PATH" fi if [ ! -d "$EXPANSO_INSTALL_DIR" ]; then runAsRoot mkdir -p "$EXPANSO_INSTALL_DIR" fi runAsRoot cp "$tmp_root_expanso_cli" "$EXPANSO_BINARY_PATH" # Verify installation and check PATH if [ -f "$EXPANSO_BINARY_PATH" ]; then echo "$EXPANSO_BINARY_NAME installed into $EXPANSO_INSTALL_DIR successfully." $EXPANSO_BINARY_PATH version add_telemetry_property "install_dir" "$EXPANSO_INSTALL_DIR" "string" else echo "Failed to install $EXPANSO_BINARY_NAME" add_telemetry_property "failure_reason" "final_copy_failed" "string" exit 1 fi installed_path=$(which "$EXPANSO_BINARY_NAME" 2>/dev/null) if [ -z "$installed_path" ]; then echo "WARNING: $EXPANSO_BINARY_PATH is not on your PATH: $PATH" 1>&2 add_telemetry_property "path_status" "not_on_path" "string" elif [ "$installed_path" != "$EXPANSO_BINARY_PATH" ]; then echo "WARNING: The ${EXPANSO_BINARY_NAME} found on your PATH ($installed_path) is different from the one we just installed ($EXPANSO_BINARY_PATH)." 1>&2 echo "You may want to update your PATH or remove old installations." 1>&2 add_telemetry_property "path_status" "path_conflict" "string" else add_telemetry_property "path_status" "correct" "string" fi } #============================================================================= # MAIN SCRIPT #============================================================================= # Parse command line arguments # Check if the first argument is a version (doesn't start with a dash) if [[ $1 && $1 != -* ]]; then EXPANSO_VERSION="$1" shift fi # Parse command line arguments while [[ $# -gt 0 ]]; do key="$1" case $key in -v|--version) EXPANSO_VERSION="$2" shift 2 ;; -d|--dir) EXPANSO_INSTALL_DIR="$2" shift 2 ;; -h|--help) usage exit 0 ;; *) echo "Unknown option: $1" usage exit 1 ;; esac done # Setup error trap trap "fail_trap" EXIT # Display welcome banner cat << 'EOF' _____ __ __ ____ _ _ _ ____ ___ _____ ____ ____ _____ | ____|\ \/ /| _ \ / \ | \ | |/ ___| / _ \ | ____|| _ \ / ___|| ____| | _| \ / | |_) |/ _ \ | \| |\___ \ | | | | | _| | | | || | _ | _| | |___ / \ | __// ___ \ | |\ | ___) || |_| | | |___ | |_| || |_| || |___ |_____|/_/\_\|_| /_/ \_\|_| \_||____/ \___/ |_____||____/ \____||_____| Distributed Edge Data Processing =============================================================================== EOF # Execute installation steps getSystemInfo verifySupported checkHttpRequestCLI createInstallDir getArtifactInfo if [ -z "$ret_val" ]; then echo 1>&2 "Error getting artifact information... Please file a bug here: https://expanso.io" exit 1 fi # Parse artifact information and extract values if ! parsed_output=$(parseArtifactInfo "$ret_val"); then exit 1 fi read -r downloadUrl signatureUrl version binaryName <<< "$parsed_output" echo "Installing $binaryName $version in $EXPANSO_INSTALL_DIR..." # Update the binary filename and file path EXPANSO_BINARY_NAME="$binaryName" EXPANSO_BINARY_PATH="${EXPANSO_INSTALL_DIR}/${EXPANSO_BINARY_NAME}" # Download and install checkExistingExpanso setup_tmp addInstallationID downloadFile "$version" "$downloadUrl" "$signatureUrl" verifyTarBall expandTarBall installFile # Bootstrap node if bootstrap parameters are provided bootstrapNode # Send the final telemetry event add_telemetry_property "status" "success" "string" send_telemetry # Display thank you message cat << EOF Thanks for installing ${EXPANSO_BINARY_NAME}! We're building the future of edge data processing. You have just installed: EOF # Add conditional message based on what was installed if [ "$EXPANSO_MODULE_TYPE" == "edge" ]; then echo "Expanso Edge - The edge agent for distributed data processing" echo "Run 'expanso-edge run' to start processing data at the edge" elif [ "$EXPANSO_MODULE_TYPE" == "cli" ]; then echo "Expanso CLI - Command-line interface for your control plane" echo "Run 'expanso-cli' to manage your edge infrastructure" fi cat << 'EOF' Next Steps: 1. Create your control plane at https://cloud.expanso.io 2. Connect your edge nodes to start processing data 3. Monitor and manage through the web UI or CLI Resources: - 🌐 Website: https://expanso.io - ☁️ Control Plane: https://cloud.expanso.io - 📧 Contact Us: https://expanso.io/contact ~ Team Expanso EOF cleanup