#!/usr/bin/env bash # Flow Pro Installation Script # Downloads and installs Flow Pro CLI and Desktop application # Requires valid Polar.sh license key # # Usage: # curl -fsSL https://install.flowexec.io | bash -s -- YOUR_LICENSE_KEY [VERSION] # FLOW_PRO_LICENSE=YOUR_LICENSE_KEY curl -fsSL https://install.flowexec.io | bash # # Environment Variables: # FLOW_PRO_LICENSE - License key (alternative to passing as argument) # FLOW_PRO_VERSION - Version to install (defaults to 'latest') # FLOW_PRO_DEBUG - Set to '1' for verbose output # FLOW_PRO_INSTALL_DIR - Installation directory (defaults to ~/.flow-pro) # FLOW_PRO_SKIP_DESKTOP - Set to '1' to skip desktop app installation set -euo pipefail # ============================================================================ # Configuration # ============================================================================ INSTALL_DIR="${FLOW_PRO_INSTALL_DIR:-$HOME/.flow-pro}" BIN_DIR="${INSTALL_DIR}/bin" DOWNLOAD_DIR="${INSTALL_DIR}/downloads" BASE_URL="${FLOW_PRO_BASE_URL:-https://releases.flowexec.io}" # Parse command-line arguments LICENSE_KEY="${1:-${FLOW_PRO_LICENSE:-}}" VERSION="${2:-${FLOW_PRO_VERSION:-latest}}" DEBUG="${FLOW_PRO_DEBUG:-0}" SKIP_DESKTOP="${FLOW_PRO_SKIP_DESKTOP:-0}" # ============================================================================ # Colors and Logging # ============================================================================ # Color codes (disabled if not a TTY or NO_COLOR is set) if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' else RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN='' BOLD='' NC='' fi log_info() { echo -e "${BLUE}ℹ${NC} $1" } log_success() { echo -e "${GREEN}✓${NC} $1" } log_error() { echo -e "${RED}✗${NC} $1" >&2 } log_warn() { echo -e "${YELLOW}⚠${NC} $1" } log_debug() { if [ "$DEBUG" = "1" ]; then echo -e "${CYAN}[DEBUG]${NC} $1" fi } log_step() { echo -e "\n${BOLD}${MAGENTA}▶${NC} ${BOLD}$1${NC}" } banner() { cat << "EOF" ,d8888b d8b 88P' 88P d888888P d88 ?88' 888 d8888b ?88 d8P d8P 88P ?88 d8P' ?88 d88 d8P' d8P' d88 88b 88b d88 ?8b ,88b ,88' d88' 88b`?8888P' `?888P'888P' EOF } # ============================================================================ # Utility Functions # ============================================================================ # Cleanup on error or exit cleanup() { local exit_code=$? if [ $exit_code -ne 0 ]; then log_error "Installation failed!" if [ "$DEBUG" = "0" ]; then log_info "Run with FLOW_PRO_DEBUG=1 for more details" fi fi # Clean up temporary downloads on error if [ -d "$DOWNLOAD_DIR" ] && [ $exit_code -ne 0 ]; then log_debug "Cleaning up download directory: $DOWNLOAD_DIR" rm -rf "$DOWNLOAD_DIR" fi } trap cleanup EXIT # Check if command exists command_exists() { command -v "$1" &> /dev/null } # Download file with progress download_file() { local url="$1" local output="$2" log_debug "Downloading: $url -> $output" if command_exists curl; then curl -fsSL --retry 3 --retry-delay 2 "$url" -o "$output" elif command_exists wget; then wget -q --retry-connrefused --waitretry=2 --tries=3 "$url" -O "$output" else log_error "Neither curl nor wget found. Please install curl or wget." exit 1 fi } # Verify SHA256 checksum verify_checksum() { local file="$1" local expected="$2" log_debug "Verifying checksum for: $file" log_debug "Expected: $expected" local actual="" if command_exists sha256sum; then actual=$(sha256sum "$file" | awk '{print $1}') elif command_exists shasum; then actual=$(shasum -a 256 "$file" | awk '{print $1}') else log_error "Neither sha256sum nor shasum found" exit 1 fi log_debug "Actual: $actual" if [ "$actual" != "$expected" ]; then log_error "Checksum verification failed for $file" log_error "Expected: $expected" log_error "Got: $actual" return 1 fi log_success "Checksum verified for $(basename "$file")" return 0 } # ============================================================================ # Platform Detection # ============================================================================ detect_platform() { local os="" local arch="" # Detect OS case "$(uname -s | tr '[:upper:]' '[:lower:]')" in linux*) os="linux" ;; darwin*) os="darwin" ;; *) log_error "Unsupported operating system: $(uname -s)" log_error "Flow Pro supports: Linux, macOS" exit 1 ;; esac # Detect architecture case "$(uname -m)" in x86_64|amd64) arch="amd64" ;; arm64|aarch64) arch="arm64" ;; *) log_error "Unsupported architecture: $(uname -m)" log_error "Flow Pro supports: x86_64 (amd64), arm64" exit 1 ;; esac echo "${os}-${arch}" } # ============================================================================ # License Validation # ============================================================================ validate_license() { local license="$1" log_step "Validating license with Polar.sh..." if [ -z "$license" ]; then log_error "No license key provided!" echo "" echo "Usage:" echo " curl -fsSL https://install.flowexec.io | bash -s -- YOUR_LICENSE_KEY" echo "" echo "Or set environment variable:" echo " export FLOW_PRO_LICENSE=YOUR_LICENSE_KEY" echo " curl -fsSL https://install.flowexec.io | bash" echo "" echo "Get your license at: ${BOLD}https://flowexec.io/pro${NC}" exit 1 fi log_debug "License key: ${license:0:10}..." flow license set "$license" } # ============================================================================ # Version Resolution # ============================================================================ resolve_version() { local version="$1" if [ "$version" = "latest" ]; then log_step "Resolving latest version..." # Download latest version manifest local version_json="${DOWNLOAD_DIR}/version.json" download_file "${BASE_URL}/latest/version.json" "$version_json" if command_exists jq; then version=$(jq -r '.version' "$version_json") log_success "Latest version: $version" else log_error "jq is required to resolve latest version" log_info "Please install jq or specify a version explicitly" exit 1 fi fi echo "$version" } # ============================================================================ # Installation # ============================================================================ install_cli() { local version="$1" local platform="$2" log_step "Installing Flow Pro CLI..." local version_json="${DOWNLOAD_DIR}/version.json" local version_url="${BASE_URL}/${version}/version.json" log_info "Downloading version manifest..." download_file "$version_url" "$version_json" if ! command_exists jq; then log_error "jq is required for installation" log_info "Install jq: https://stedolan.github.io/jq/download/" exit 1 fi local cli_info=$(jq -r ".cli[\"$platform\"]" "$version_json") if [ "$cli_info" = "null" ] || [ -z "$cli_info" ]; then log_error "No CLI binary found for platform: $platform" log_info "Available platforms:" jq -r '.cli | keys[]' "$version_json" exit 1 fi local filename=$(echo "$cli_info" | jq -r '.filename') local checksum=$(echo "$cli_info" | jq -r '.checksum') log_info "Downloading CLI: $filename" local archive_path="${DOWNLOAD_DIR}/${filename}" download_file "${BASE_URL}/${version}/${filename}" "$archive_path" verify_checksum "$archive_path" "$checksum" log_info "Extracting CLI binary..." mkdir -p "$BIN_DIR" tar -xzf "$archive_path" -C "$DOWNLOAD_DIR" local binary=$(find "$DOWNLOAD_DIR" -name "flow" -type f | head -1) if [ -z "$binary" ]; then log_error "Binary 'flow' not found in archive" exit 1 fi mv "$binary" "${BIN_DIR}/flow" chmod +x "${BIN_DIR}/flow" if "${BIN_DIR}/flow" --version &> /dev/null; then local installed_version=$("${BIN_DIR}/flow" --version | head -1) log_success "CLI installed: $installed_version" else log_error "CLI installation verification failed" exit 1 fi } install_desktop_macos() { local version="$1" local version_json="${DOWNLOAD_DIR}/version.json" log_step "Installing Flow Pro Desktop (macOS)..." local desktop_info=$(jq -r '.desktop.darwin' "$version_json") if [ "$desktop_info" = "null" ] || [ -z "$desktop_info" ]; then log_warn "No macOS desktop app found for this version" return 0 fi local filename=$(echo "$desktop_info" | jq -r '.filename') local checksum=$(echo "$desktop_info" | jq -r '.checksum') log_info "Downloading Desktop: $filename" local dmg_path="${DOWNLOAD_DIR}/${filename}" download_file "${BASE_URL}/${version}/${filename}" "$dmg_path" verify_checksum "$dmg_path" "$checksum" log_info "Mounting DMG..." local mount_point=$(hdiutil attach "$dmg_path" | grep Volumes | awk '{print $3}') if [ -z "$mount_point" ]; then log_error "Failed to mount DMG" return 1 fi log_debug "Mounted at: $mount_point" local app_bundle=$(find "$mount_point" -name "*.app" -maxdepth 1 | head -1) if [ -z "$app_bundle" ]; then hdiutil detach "$mount_point" -quiet log_error "No .app bundle found in DMG" return 1 fi log_info "Installing to /Applications..." sudo cp -R "$app_bundle" /Applications/ hdiutil detach "$mount_point" -quiet log_success "Desktop app installed to /Applications" } install_desktop_linux() { local version="$1" local version_json="${DOWNLOAD_DIR}/version.json" log_step "Installing Flow Pro Desktop (Linux)..." local desktop_info=$(jq -r '.desktop.linux' "$version_json") if [ "$desktop_info" = "null" ] || [ -z "$desktop_info" ]; then log_warn "No Linux desktop app found for this version" return 0 fi local filename=$(echo "$desktop_info" | jq -r '.filename') local checksum=$(echo "$desktop_info" | jq -r '.checksum') log_info "Downloading Desktop: $filename" local appimage_path="${DOWNLOAD_DIR}/${filename}" download_file "${BASE_URL}/${version}/${filename}" "$appimage_path" verify_checksum "$appimage_path" "$checksum" mv "$appimage_path" "${BIN_DIR}/flow-desktop" chmod +x "${BIN_DIR}/flow-desktop" log_info "Creating desktop entry..." local desktop_file="${HOME}/.local/share/applications/flow-pro.desktop" mkdir -p "$(dirname "$desktop_file")" cat > "$desktop_file" << EOF [Desktop Entry] Type=Application Name=Flow Desktop Comment=Flow Automation Platform Exec=${BIN_DIR}/flow-desktop Icon=flow-pro Terminal=false Categories=Development;Utility; EOF log_success "Desktop app installed" log_info "Run with: flow-desktop" } # ============================================================================ # PATH Configuration # ============================================================================ configure_path() { log_step "Configuring PATH..." if echo "$PATH" | grep -q "${BIN_DIR}"; then log_success "Already in PATH" return 0 fi local shell_configs=( "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile" ) local path_line="export PATH=\"${BIN_DIR}:\$PATH\"" local added=false for config in "${shell_configs[@]}"; do if [ -f "$config" ]; then if ! grep -q "flow-pro/bin" "$config"; then echo "" >> "$config" echo "# Flow Pro" >> "$config" echo "$path_line" >> "$config" log_success "Added to $(basename "$config")" added=true fi fi done if [ "$added" = true ]; then log_warn "Restart your shell or run: source ~/.bashrc (or ~/.zshrc)" else log_info "Add to PATH manually: $path_line" fi } # ============================================================================ # Version Tracking # ============================================================================ save_installation_info() { local version="$1" local platform="$2" log_step "Saving installation info..." local info_file="${INSTALL_DIR}/version.json" local installed=$(date -u +"%Y-%m-%dT%H:%M:%SZ") cat > "$info_file" << EOF { "version": "$version", "platform": "$platform", "installed": "$installed", "install_dir": "$INSTALL_DIR", "bin_dir": "$BIN_DIR" } EOF log_debug "Installation info saved to $info_file" } # ============================================================================ # Main Installation Flow # ============================================================================ main() { clear banner echo "" log_info "Flow Pro Installer" log_info "Installation directory: $INSTALL_DIR" echo "" PLATFORM=$(detect_platform) log_info "Platform: $PLATFORM" OS=$(echo "$PLATFORM" | cut -d'-' -f1) VERSION=$(resolve_version "$VERSION") log_info "Installing version: $VERSION" mkdir -p "$INSTALL_DIR" "$BIN_DIR" "$DOWNLOAD_DIR" install_cli "$VERSION" "$PLATFORM" validate_license "$LICENSE_KEY" if [ "$SKIP_DESKTOP" = "0" ]; then case "$OS" in darwin) install_desktop_macos "$VERSION" || log_warn "Desktop installation failed (non-fatal)" ;; linux) install_desktop_linux "$VERSION" || log_warn "Desktop installation failed (non-fatal)" ;; esac else log_info "Skipping desktop app installation (FLOW_PRO_SKIP_DESKTOP=1)" fi configure_path save_installation_info "$VERSION" "$PLATFORM" log_debug "Cleaning up downloads..." rm -rf "$DOWNLOAD_DIR" echo "" echo -e "${GREEN}${BOLD}" echo "╔════════════════════════════════════════════════════════╗" echo "║ ║" echo "║ Flow Pro installed successfully! ║" echo "║ ║" echo "╚════════════════════════════════════════════════════════╝" echo -e "${NC}" echo "" log_info "${BOLD}Version:${NC} $VERSION" log_info "${BOLD}Location:${NC} $BIN_DIR/flow" echo "" log_info "${BOLD}Next steps:${NC}" echo " 1. Restart your shell or run: ${BOLD}source ~/.bashrc${NC}" echo " 2. Verify installation: ${BOLD}flow --version${NC}" echo " 3. Get started: ${BOLD}flow --help${NC}" echo "" log_info "Documentation: ${BOLD}https://flowexec.io/${NC}" echo "" } # Run main installation main