#!/bin/bash # LeetSolv Installation Script # This script installs LeetSolv CLI application on Linux and macOS set -e # Use set -u to exit if an unset variable is used. set -u # Use set -o pipefail to exit if a command in a pipe fails. set -o pipefail # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration REPO_OWNER="eannchen" REPO_NAME="leetsolv" BINARY_NAME="leetsolv" INSTALL_DIR="/usr/local/bin" CONFIG_DIR="$HOME/.leetsolv" BACKUP_DIR="$CONFIG_DIR/backup" # Temp directory variable that will be cleaned up on exit. TEMP_DIR="" # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" # Exit with a non-zero status code on error. exit 1 } # Cleanup function to be called on script exit. cleanup() { if [ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ]; then rm -rf "$TEMP_DIR" fi } # Trap the EXIT signal to run the cleanup function. trap cleanup EXIT # Function to detect OS and architecture detect_platform() { OS="" ARCH="" case "$(uname -s)" in Linux*) OS="linux";; Darwin*) OS="darwin";; *) print_error "Unsupported operating system. This script only supports Linux and macOS.";; esac case "$(uname -m)" in x86_64) ARCH="amd64";; aarch64) ARCH="arm64";; arm64) ARCH="arm64";; *) print_error "Unsupported architecture: $(uname -m). This script only supports x86_64 (amd64) and aarch64/arm64.";; esac print_status "Detected platform: $OS-$ARCH" } # Function to check if command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Flag to track if this is an upgrade IS_UPGRADE="false" # Function to backup existing installation backup_existing() { if command_exists "$BINARY_NAME"; then IS_UPGRADE="true" print_status "Backing up existing installation..." mkdir -p "$BACKUP_DIR" local backup_path="$BACKUP_DIR/$(date +%Y%m%d_%H%M%S)_$BINARY_NAME" # Use command -v instead of which for better portability. cp "$(command -v "$BINARY_NAME")" "$backup_path" print_success "Backup created at: $backup_path" fi } # Function to download latest release download_release() { print_status "Checking for latest release..." local api_url="https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/latest" local latest_release="" # Prioritize jq for robust JSON parsing. if command_exists "jq"; then if command_exists "curl"; then latest_release=$(curl -s "$api_url" | jq -r .tag_name) elif command_exists "wget"; then latest_release=$(wget -qO- "$api_url" | jq -r .tag_name) fi else print_warning "jq not found. Falling back to grep/sed for version detection." # Fallback to grep/sed if jq is not available if command_exists "curl"; then latest_release=$(curl -s "$api_url" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') elif command_exists "wget"; then latest_release=$(wget -qO- "$api_url" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') else print_error "Neither curl nor wget found. Please install one of them." fi fi if [ -z "$latest_release" ] || [ "$latest_release" == "null" ]; then print_warning "Could not determine latest release from GitHub API. Using 'latest' tag." latest_release="latest" fi print_status "Latest release: $latest_release" local download_url="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/$latest_release/${BINARY_NAME}-${OS}-${ARCH}" print_status "Downloading from: $download_url" # Create temporary directory TEMP_DIR=$(mktemp -d) cd "$TEMP_DIR" # Download binary # Added explicit failure checks for downloads. if command_exists "curl"; then if ! curl -f -L -o "$BINARY_NAME" "$download_url"; then print_error "Download failed using curl. Please check the URL and your connection." fi else if ! wget -O "$BINARY_NAME" "$download_url"; then print_error "Download failed using wget. Please check the URL and your connection." fi fi chmod +x "$BINARY_NAME" print_success "Download completed" } # Function to install binary install_binary() { print_status "Installing binary to $INSTALL_DIR..." if [ -w "$INSTALL_DIR" ]; then mv "$BINARY_NAME" "$INSTALL_DIR/" else print_warning "No write permission to $INSTALL_DIR. Trying with sudo..." if command_exists "sudo"; then sudo mv "$BINARY_NAME" "$INSTALL_DIR/" else print_error "No sudo available and no write permission to $INSTALL_DIR. Please move the binary manually from '$TEMP_DIR/$BINARY_NAME' to a directory in your PATH." fi fi print_success "Binary installed to $INSTALL_DIR" } # Function to verify installation verify_installation() { print_status "Verifying installation..." if command_exists "$BINARY_NAME"; then local install_path # Use command -v instead of which for better portability. install_path=$(command -v "$BINARY_NAME") local version version=$("$BINARY_NAME" version 2>/dev/null || echo "unknown") print_success "LeetSolv installed successfully!" print_status "Version: $version" print_status "Location: $install_path" else print_error "Installation verification failed. '$BINARY_NAME' not found in your PATH." fi } # Function to create configuration directory setup_config() { print_status "Setting up configuration directory..." mkdir -p "$CONFIG_DIR" print_success "Configuration directory ready at $CONFIG_DIR" } # Function to show post-install instructions show_post_install() { local is_upgrade="$1" echo print_success "Installation completed successfully!" echo echo "Next steps:" echo "1. Run '$BINARY_NAME' to start the application." echo "2. Run '$BINARY_NAME help' to see available commands." echo "3. Configuration files are in '$CONFIG_DIR'." echo # Show migration notice for upgrades if [ "$is_upgrade" = "true" ]; then echo -e "${YELLOW}╭───────────────────────────────────────────────────╮${NC}" echo -e "${YELLOW}│ UPGRADE NOTICE: Timezone Migration │${NC}" echo -e "${YELLOW}├───────────────────────────────────────────────────┤${NC}" echo -e "${YELLOW}│ This version stores timestamps in UTC format. │${NC}" echo -e "${YELLOW}│ To migrate your existing data, run: │${NC}" echo -e "${YELLOW}│ │${NC}" echo -e "${YELLOW}│ leetsolv migrate │${NC}" echo -e "${YELLOW}│ │${NC}" echo -e "${YELLOW}│ This is optional but recommended if you travel │${NC}" echo -e "${YELLOW}│ across timezones. │${NC}" echo -e "${YELLOW}╰───────────────────────────────────────────────────╯${NC}" echo fi echo "To uninstall, run: 'sudo rm \"$INSTALL_DIR/$BINARY_NAME\"' or use the --uninstall flag with this script." echo } # Main installation function main() { # Check if already installed via Homebrew if command_exists "brew" && brew list leetsolv &>/dev/null; then print_warning "LeetSolv is already installed via Homebrew." print_warning "To upgrade, use: brew upgrade leetsolv" print_warning "To switch to script installation, first run: brew uninstall leetsolv" exit 1 fi echo -e "${BLUE}╭───────────────────────────────────────────────────╮${NC}" echo -e "${BLUE}│ │${NC}" echo -e "${BLUE}│ ░▒▓ LeetSolv — CLI SRS for DSA ▓▒░ │${NC}" echo -e "${BLUE}│ │${NC}" echo -e "${BLUE}│ Installation Script │${NC}" echo -e "${BLUE}│ │${NC}" echo -e "${BLUE}╰───────────────────────────────────────────────────╯${NC}" echo detect_platform backup_existing download_release install_binary setup_config verify_installation show_post_install "$IS_UPGRADE" } # Uninstall function with interactive cleanup. uninstall() { print_status "Uninstalling LeetSolv..." local install_path # Use command -v instead of which for better portability. install_path=$(command -v "$BINARY_NAME" || echo "") if [ -n "$install_path" ]; then print_status "Removing binary from $install_path" if [ -w "$(dirname "$install_path")" ]; then rm -f "$install_path" else sudo rm -f "$install_path" fi print_success "LeetSolv binary uninstalled." else print_warning "LeetSolv binary not found in PATH." fi if [ -d "$CONFIG_DIR" ]; then echo print_warning "Configuration directory found at '$CONFIG_DIR'." read -p "Do you want to remove all configurations and backups? [y/N] " -n 1 -r < /dev/tty echo if [[ $REPLY =~ ^[Yy]$ ]]; then print_status "Removing configuration directory..." rm -rf "$CONFIG_DIR" print_success "Configuration directory removed." else print_status "Skipping removal of configuration directory." fi fi echo print_success "Uninstallation process finished." } # Handle command line arguments case "${1:-}" in --help|-h) echo "Usage: $0 [COMMAND]" echo echo "Commands:" echo " (no command) Install or update LeetSolv" echo " --uninstall Uninstall LeetSolv" echo " --help, -h Show this help message" echo " --version, -v Show installer version" exit 0 ;; --version|-v) echo "LeetSolv Installer v1.1.0" exit 0 ;; --uninstall) uninstall exit 0 ;; "") # No arguments, proceed with installation main ;; *) print_error "Unknown option: $1" ;; esac