feat(installer): Rework install.sh for non-interactive mode and improved UX

Performs a major refactoring of 'install.sh' to introduce non-interactive installation via command-line arguments (e.g., '--overwrite-config'). Enhances the interactive menu with detailed package information (display name, description, version, update status) and improves config file handling with diff previews. Updates 'packages.conf' format to support new package metadata and uses short, lowercase keys.
This commit is contained in:
2025-09-25 15:43:12 +02:00
parent d9760b9072
commit a16b8aa42b
2 changed files with 130 additions and 125 deletions

View File

@@ -2,38 +2,26 @@
# --- 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.
# This script presents a menu of software packages, or installs them
# non-interactively via command-line arguments. It downloads files from a
# remote configuration, shows a diff for config updates, and checks versions.
# --- 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.
# Compare two version strings. Returns 0 if v1 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
@@ -41,181 +29,199 @@ is_version_greater() {
fi
}
# New function to process a single selected package.
# 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}'"
local choice_key="$1"
local force_overwrite="$2" # Expects "true" or "false"
if [[ -z "${SCRIPT_PACKAGES[$choice_key]}" ]]; then
echo "[❌] Invalid package name provided: '${choice_key}'"
return
fi
echo
log "⬇️" "Processing package: '${choice}'..."
echo "[⬇️] Processing package: '${choice_key}'..."
# 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-)
# Parse the new config format
config_value="${SCRIPT_PACKAGES[$choice_key]}"
display_name=$(echo "${config_value}" | cut -d'|' -f1)
remote_version=$(echo "${config_value}" | cut -d'|' -f2)
description=$(echo "${config_value}" | cut -d'|' -f3)
urls_to_download=$(echo "${config_value}" | cut -d'|' -f4-)
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.
# Handle config file overwrites
if [[ "${filename}" == *.conf && -f "${filename}" ]]; then
log "->" "Found existing config file: '${filename}'."
tmp_file=$(mktemp)
if [[ "$force_overwrite" == "true" ]]; then
echo "[⚠️] Overwriting '${filename}' due to --overwrite-config flag."
if ! curl -fsSL -o "${filename}" "${url}"; then
echo "[❌] Error: Failed to download '${filename}'."
fi
continue
fi
echo "[->] Found existing config file: '${filename}'."
tmp_file=$(mktemp)
if curl -fsSL -o "${tmp_file}" "${url}"; then
log "🔎" "Comparing versions..."
echo "[🔎] 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}'."
echo "[✅] Updated '${filename}'."
else
rm "${tmp_file}"
log "🤷" "Kept existing version of '${filename}'."
echo "[🤷] Kept existing version of '${filename}'."
fi
else
log "❌" "Error: Failed to download new version of '${filename}' for comparison."
echo "[❌] Error downloading new version of '${filename}' for comparison."
rm -f "${tmp_file}"
fi
else
# Original download logic for all other files.
log "->" "Downloading '${filename}'..."
echo "[->] Downloading '${filename}'..."
if curl -fsSL -o "${filename}" "${url}"; then
log "✅" "Successfully downloaded '${filename}'."
echo "[✅] Successfully downloaded '${filename}'."
if [[ "${filename}" == *.sh || "${filename}" == *.bash ]]; then
chmod +x "${filename}"
log "🤖" "Made '${filename}' executable."
echo "[🤖] Made '${filename}' executable."
fi
else
log "❌" "Error: Failed to download '${filename}'."
echo "[❌] Error: Failed to download '${filename}'."
fi
fi
done
log "📦" "Package processing complete for '${choice}'."
echo "[📦] Package processing complete for '${choice_key}'."
}
# --- 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..."
echo "[🔄] 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."
echo "[❌] Error: Failed to download packages.conf. Exiting."
exit 1
fi
log "✅" "Configuration file downloaded successfully."
echo "[✅] Configuration file downloaded successfully."
# Source the configuration file to load the SCRIPT_PACKAGES associative array.
source "${conf_file}"
# --- Update Check & User Interface ---
# --- Argument Parsing for Non-Interactive Mode ---
if [ "$#" -gt 0 ]; then
declare -a packages_to_install
overwrite_configs=false
for arg in "$@"; do
case $arg in
--overwrite-config)
overwrite_configs=true
;;
-*)
echo "[❌] Unknown flag: $arg" >&2
exit 1
;;
*)
packages_to_install+=("$arg")
;;
esac
done
# 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}]"
if [ ${#packages_to_install[@]} -eq 0 ]; then
echo "[❌] Flag provided with no package names. Exiting."
exit 1
fi
echo "[🚀] Running in non-interactive mode."
for pkg_key in "${packages_to_install[@]}"; do
if [[ -n "${SCRIPT_PACKAGES[$pkg_key]}" ]]; then
process_package "$pkg_key" "$overwrite_configs"
else
echo "[⚠️] Unknown package: '$pkg_key'. Skipping."
fi
options+=("${key}${status}")
done
done
echo "[🏁] Non-interactive run complete."
exit 0
fi
options+=("Quit") # Add a Quit option to the menu.
# --- Interactive Mode ---
declare -a ordered_keys
package_keys_sorted=($(for k in "${!SCRIPT_PACKAGES[@]}"; do echo $k; done | sort))
ordered_keys=("${package_keys_sorted[@]}")
# --- User Interaction ---
# Manually display the options with numbers.
# --- Display Menu ---
echo
echo "-------------------------------------"
echo " Script Downloader "
echo "-------------------------------------"
for i in "${!options[@]}"; do
printf "%d) %s\n" "$((i+1))" "${options[$i]}"
done
echo "[🔎] Checking for updates..."
echo
# Prompt the user for one or more choices.
for i in "${!ordered_keys[@]}"; do
key="${ordered_keys[$i]}"
config_value="${SCRIPT_PACKAGES[$key]}"
display_name=$(echo "${config_value}" | cut -d'|' -f1)
remote_version=$(echo "${config_value}" | cut -d'|' -f2)
description=$(echo "${config_value}" | cut -d'|' -f3)
urls=$(echo "${config_value}" | cut -d'|' -f4-)
read -r -a url_array <<< "$urls"
main_script_filename=$(basename "${url_array[0]}")
local_version=$(get_local_version "${main_script_filename}")
# Print main package line
echo -e "\033[1m$((i+1))) $key - $display_name (v$remote_version)\033[0m"
# Print description
echo " $description"
# Print status
if [[ -f "${main_script_filename}" ]]; then
if is_version_greater "$remote_version" "$local_version"; then
echo -e " \033[33m[Update available: v${local_version} -> v${remote_version}]\033[0m"
else
echo -e " \033[32m[Installed: v${local_version}]\033[0m"
fi
fi
echo
done
quit_num=$((${#ordered_keys[@]} + 1))
echo -e "\033[1m${quit_num}) Quit\033[0m"
echo
# --- Handle User Input ---
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."
echo "[👋] 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."
echo "[⚠️] 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."
if [ "$choice_num" -eq "$quit_num" ]; then
echo "[👋] Quit selected. Exiting."
exit 0
fi
# Process the selected package.
process_package "${choice}"
index=$((choice_num - 1))
if [[ -z "${ordered_keys[$index]}" ]]; then
echo "[⚠️] Skipping invalid choice: '${choice_num}'. Out of range."
continue
fi
choice_key="${ordered_keys[$index]}"
process_package "$choice_key" "false" # Never force overwrite in interactive mode
done
echo
log "🏁" "All selected packages have been processed."
echo "[🏁] All selected packages have been processed."

View File

@@ -1,17 +1,16 @@
#!/bin/bash
#
# This file contains the configuration for the script downloader.
# The `SCRIPT_PACKAGES` associative array maps a package name to a
# pipe-separated string: "<version>|<space-separated list of URLs>".
# The `SCRIPT_PACKAGES` associative array maps a short package name
# to a pipe-separated string with the following format:
# "<Display Name>|<Version>|<Description>|<Space-separated list of URLs>"
declare -A SCRIPT_PACKAGES
# The version should match the "# Version: x.x.x" line in the main script file.
SCRIPT_PACKAGES["Aurora Suite"]="2.1.0|https://git.technopunk.space/tomi/Scripts/raw/branch/main/aurora/aurora.sh https://git.technopunk.space/tomi/Scripts/raw/branch/main/aurora/aurora.conf"
SCRIPT_PACKAGES["Backup Suite"]="1.0.5|https://git.technopunk.space/tomi/Scripts/raw/branch/main/backup/backup.sh https://git.technopunk.space/tomi/Scripts/raw/branch/main/backup/backup.conf"
SCRIPT_PACKAGES["Monitor Suite"]="1.0.5|https://git.technopunk.space/tomi/Scripts/raw/branch/main/monitor/monitor.sh https://git.technopunk.space/tomi/Scripts/raw/branch/main/monitor/monitor.conf"
SCRIPT_PACKAGES["Key Manager"]="1.2.1|https://git.technopunk.space/tomi/Scripts/raw/branch/main/hdb_keymanager.sh"
SCRIPT_PACKAGES["File Cleaner"]="1.1.0|https://git.technopunk.space/tomi/Scripts/raw/branch/main/clean.sh"
SCRIPT_PACKAGES["HANA Tool"]="1.5.0|https://git.technopunk.space/tomi/Scripts/raw/branch/main/hanatool.sh"
# Example: Add a new script with its version.
# SCRIPT_PACKAGES["My Other Script"]="1.0.0|https://path/to/my-other-script.sh"
# Format: short_name="Display Name|Version|Description|URL1 URL2..."
SCRIPT_PACKAGES["aurora"]="Aurora Suite|2.1.0|A collection of scripts for managing Aurora database instances.|https://git.technopunk.space/tomi/Scripts/raw/branch/main/aurora/aurora.sh https://git.technopunk.space/tomi/Scripts/raw/branch/main/aurora/aurora.conf"
SCRIPT_PACKAGES["backup"]="Backup Suite|1.0.5|A comprehensive script for backing up system files and databases.|https://git.technopunk.space/tomi/Scripts/raw/branch/main/backup/backup.sh https://git.technopunk.space/tomi/Scripts/raw/branch/main/backup/backup.conf"
SCRIPT_PACKAGES["monitor"]="Monitor Suite|1.0.5|Scripts for monitoring system health and performance metrics.|https://git.technopunk.space/tomi/Scripts/raw/branch/main/monitor/monitor.sh https://git.technopunk.space/tomi/Scripts/raw/branch/main/monitor/monitor.conf"
SCRIPT_PACKAGES["keymanager"]="Key Manager|1.2.1|A utility for managing HDB user keys for SAP HANA.|https://git.technopunk.space/tomi/Scripts/raw/branch/main/hdb_keymanager.sh"
SCRIPT_PACKAGES["cleaner"]="File Cleaner|1.1.0|A simple script to clean up temporary files and logs.|https://git.technopunk.space/tomi/Scripts/raw/branch/main/clean.sh"
SCRIPT_PACKAGES["hanatool"]="HANA Tool|1.5.0|A command-line tool for various SAP HANA administration tasks.|https://git.technopunk.space/tomi/Scripts/raw/branch/main/hanatool.sh"