#!/bin/bash # --- Main Script --- # This script presents a menu of software packages defined in a remote # configuration file. The user can select one or more packages, and the # script will download the corresponding files. It includes a feature to show # a diff and ask for confirmation before overwriting existing config files. # It can also check for updates to already-installed scripts. # --- Functions --- # A simple function to log messages with a consistent format. log() { echo "[$1] $2" } # Get the version from a local script file. # It reads the first 5 lines and extracts the version number. get_local_version() { local file_path="$1" if [[ -f "${file_path}" ]]; then # Grep for the version line, then use awk to get the last field. head -n 5 "${file_path}" | grep -m 1 "^# Version:" | awk '{print $NF}' else echo "0.0.0" # Return a base version if file doesn't exist. fi } # Compare two version strings (e.g., "1.2.0" vs "1.10.0"). # Returns 0 if v1 is newer, 1 if they are the same or v2 is newer. is_version_greater() { local v1=$1 local v2=$2 # Use sort's version sorting capability to find the "highest" version. # If the highest version is v1, then v1 > v2. if [[ "$(printf '%s\n' "$v1" "$v2" | sort -V | head -n 1)" != "$v1" ]]; then return 0 # v1 is greater else return 1 # v1 is not greater (equal or less) fi } # New function to process a single selected package. process_package() { local choice="$1" # Check if the choice is a valid package name. if [[ -z "${SCRIPT_PACKAGES[$choice]}" ]]; then log "❌" "Invalid package name provided: '${choice}'" return fi echo log "⬇️" "Processing package: '${choice}'..." # Get the config value and split it into version and URLs config_value="${SCRIPT_PACKAGES[$choice]}" remote_version=$(echo "${config_value}" | cut -d'|' -f1) urls_to_download=$(echo "${config_value}" | cut -d'|' -f2-) read -r -a urls_to_download_array <<< "$urls_to_download" for url in "${urls_to_download_array[@]}"; do filename=$(basename "${url}") # If it's a .conf file AND it already exists, ask to overwrite. if [[ "${filename}" == *.conf && -f "${filename}" ]]; then log "->" "Found existing config file: '${filename}'." tmp_file=$(mktemp) if curl -fsSL -o "${tmp_file}" "${url}"; then log "🔎" "Comparing versions..." echo "-------------------- DIFF START --------------------" if command -v colordiff &> /dev/null; then colordiff -u "${filename}" "${tmp_file}" else # Attempt to use diff's color option, which is common. diff --color=always -u "${filename}" "${tmp_file}" 2>/dev/null || diff -u "${filename}" "${tmp_file}" fi echo "--------------------- DIFF END ---------------------" read -p "Do you want to overwrite '${filename}'? (y/N) " -n 1 -r REPLY echo if [[ $REPLY =~ ^[Yy]$ ]]; then mv "${tmp_file}" "${filename}" log "✅" "Updated '${filename}'." else rm "${tmp_file}" log "🤷" "Kept existing version of '${filename}'." fi else log "❌" "Error: Failed to download new version of '${filename}' for comparison." rm -f "${tmp_file}" fi else # Original download logic for all other files. log "->" "Downloading '${filename}'..." if curl -fsSL -o "${filename}" "${url}"; then log "✅" "Successfully downloaded '${filename}'." if [[ "${filename}" == *.sh || "${filename}" == *.bash ]]; then chmod +x "${filename}" log "🤖" "Made '${filename}' executable." fi else log "❌" "Error: Failed to download '${filename}'." fi fi done log "📦" "Package processing complete for '${choice}'." } # --- Main Logic --- # Generate a unique temporary filename with a timestamp. conf_file="packages.conf.$(date +%Y%m%d%H%M%S)" # Set up a trap to delete the temporary file on exit. trap 'rm -f "${conf_file}"' EXIT # Download the configuration file. log "🔄" "Downloading configuration file..." if ! curl -fsSL -o "${conf_file}" "https://git.technopunk.space/tomi/Scripts/raw/branch/main/packages.conf"; then log "❌" "Error: Failed to download packages.conf. Exiting." exit 1 fi log "✅" "Configuration file downloaded successfully." # Source the configuration file to load the SCRIPT_PACKAGES associative array. source "${conf_file}" # --- Update Check & User Interface --- # Create an array of options from the package names. # We will modify this array to show installation and update status. declare -a options package_keys=("${!SCRIPT_PACKAGES[@]}") log "🔎" "Checking for updates..." for key in "${package_keys[@]}"; do # The config format is now "VERSION|URL1 URL2..." config_value="${SCRIPT_PACKAGES[$key]}" remote_version=$(echo "${config_value}" | cut -d'|' -f1) # Get just the URLs and assume the first URL is the main script to check. urls=$(echo "${config_value}" | cut -d'|' -f2-) read -r -a url_array <<< "$urls" main_script_filename=$(basename "${url_array[0]}") # Get the local version of the main script file. local_version=$(get_local_version "${main_script_filename}") status="" if [[ -f "${main_script_filename}" ]]; then status=" (Installed: v${local_version})" # Compare versions if is_version_greater "$remote_version" "$local_version"; then status+=" [Update available: v${remote_version}]" fi fi options+=("${key}${status}") done options+=("Quit") # Add a Quit option to the menu. # --- User Interaction --- # Manually display the options with numbers. echo echo "-------------------------------------" echo " Script Downloader " echo "-------------------------------------" for i in "${!options[@]}"; do printf "%d) %s\n" "$((i+1))" "${options[$i]}" done echo # Prompt the user for one or more choices. read -p "Please enter your choice(s) (e.g., 1 3 4), or press Enter to quit: " -r -a user_choices # If no choices are made, exit gracefully. if [ ${#user_choices[@]} -eq 0 ]; then log "👋" "No selection made. Exiting." exit 0 fi # Loop through the user's selections and process each one. for choice_num in "${user_choices[@]}"; do # Validate that the input is a number. if ! [[ "$choice_num" =~ ^[0-9]+$ ]]; then log "⚠️" "Skipping invalid input: '${choice_num}'. Not a number." continue fi # Convert selection number to array index (0-based). index=$((choice_num - 1)) # Validate that the index is within the bounds of the options array. if [[ -z "${options[$index]}" ]]; then log "⚠️" "Skipping invalid choice: '${choice_num}'. Out of range." continue fi # Get the choice text from the array. choice_with_status="${options[$index]}" # Strip the status message to get the package key. choice=$(echo "${choice_with_status}" | sed 's/ (.*//') # Handle the "Quit" option. if [[ "${choice}" == "Quit" ]]; then log "👋" "Quit selected. Exiting now." exit 0 fi # Process the selected package. process_package "${choice}" done echo log "🏁" "All selected packages have been processed."