Browse Source

refactor(installer): simplify package manager system with auto-detection

Redesign the package manager architecture to use dynamic auto-detection
instead of hardcoded dispatchers. Each PM implementation now follows a
consistent naming convention (pm_<name>_detect, pm_<name>_get_missing_deps,
pm_<name>_install_missing_deps) and is automatically discovered by the
compile script.

- Remove complex dispatcher pattern in favor of PM_IMPLEMENTATIONS array
- Simplify pm_apt.sh and pm_dnf.sh with direct dependency lists
- Fix sudo re-execution to preserve original command line arguments
- Update build progress output with step count format
- Rename "Universal Software Manager" to "Universal Source Manifest
clanker 5 days ago
parent
commit
7277b4f47d
8 changed files with 203 additions and 309 deletions
  1. 2 8
      installer/build_config.sh
  2. 42 90
      installer/compile.sh
  3. 9 28
      installer/main.sh
  4. 2 1
      installer/meson.build
  5. 40 57
      installer/pm_apt.sh
  6. 44 59
      installer/pm_base.sh
  7. 40 57
      installer/pm_dnf.sh
  8. 24 9
      installer/ui.sh

+ 2 - 8
installer/build_config.sh

@@ -42,8 +42,6 @@ build_invercargill() {
     local prefix="$2"
     local output_redirect=$(get_output_redirect)
     
-    log_step "Building Invercargill..."
-    
     pushd "$src_dir" >/dev/null || return 1
     
     # Configure with meson
@@ -81,8 +79,6 @@ build_invercargill_json() {
     local prefix="$2"
     local output_redirect=$(get_output_redirect)
     
-    log_step "Building Invercargill-Json..."
-    
     pushd "$src_dir" >/dev/null || return 1
     
     # Configure with meson
@@ -120,11 +116,9 @@ build_usm() {
     local prefix="$2"
     local output_redirect=$(get_output_redirect)
     
-    log_step "Building USM..."
-    
     pushd "$src_dir" >/dev/null || return 1
     
-    # Configure with meson
+    # Configure with meson (USM's meson.build is at root, not in src/)
     if ! meson setup builddir --prefix="$prefix" >"$output_redirect" 2>&1; then
         log_error "Failed to configure USM"
         [[ "$VERBOSE" != "true" ]] && log_info "Run with --verbose for details"
@@ -212,7 +206,7 @@ build_all() {
     
     for component in "${BUILD_ORDER[@]}"; do
         ((current++))
-        print_progress "$current" "$total" "Building $(get_component_name "$component")..."
+        log_step "[$current/$total] Building $(get_component_name "$component")..."
         
         local src_dir
         

+ 42 - 90
installer/compile.sh

@@ -47,10 +47,13 @@ if [[ ! -d "$SOURCES_DIR/Invercargill-Json" ]]; then
     }
 fi
 
-# Copy USM source
+# Copy USM source (USM's meson.build is in src/, not project root)
 echo "Copying USM source..."
 mkdir -p "$SOURCES_DIR/usm"
-cp -r "$USM_SOURCE_DIR" "$SOURCES_DIR/usm/src"
+# USM_SOURCE_DIR points to the src/ directory which contains meson.build
+# Copy the entire src directory contents to usm/
+cp -r "$USM_SOURCE_DIR"/* "$SOURCES_DIR/usm/" 2>/dev/null || true
+# Copy additional root level files for reference
 cp "$USM_SOURCE_DIR/../MANIFEST.usm" "$SOURCES_DIR/usm/" 2>/dev/null || true
 cp "$USM_SOURCE_DIR/../usm.config" "$SOURCES_DIR/usm/" 2>/dev/null || true
 
@@ -69,107 +72,56 @@ set -e
 
 SCRIPT_HEADER
 
-# Step 3: Combine all shell scripts
-# We need to rename the PM functions to avoid conflicts
+# Step 3: Auto-detect package manager implementations
+PM_IMPLEMENTATIONS=()
+for pm_file in "$SCRIPT_DIR"/pm_*.sh; do
+    if [[ -f "$pm_file" ]]; then
+        # Extract PM name from filename (pm_apt.sh -> apt)
+        pm_name=$(basename "$pm_file" .sh | sed 's/^pm_//')
+        # Skip pm_base.sh - it's the interface, not an implementation
+        if [[ "$pm_name" != "base" ]]; then
+            PM_IMPLEMENTATIONS+=("$pm_name")
+        fi
+    fi
+done
+
+echo "  Detected package managers: ${PM_IMPLEMENTATIONS[*]}"
+
+# Step 4: Combine all shell scripts
 COMBINED_FILE="$WORK_DIR/combined.sh"
 
-# Function to process a bash script (minify and adapt)
+# Build the PM implementations array initialization
+PM_ARRAY_FILE="$WORK_DIR/pm_array.sh"
+cat > "$PM_ARRAY_FILE" << PM_ARRAY
+# Auto-detected package manager implementations
+PM_IMPLEMENTATIONS=(${PM_IMPLEMENTATIONS[*]})
+
+PM_ARRAY
+
+# Function to process a script file
 process_script() {
     local file="$1"
     local section="$2"
     
     echo "# === $section ==="
-    
-    # For PM files, we need to prefix the functions
-    case "$section" in
-        "Package Manager APT")
-            # Rename functions with apt_ prefix
-            sed -e 's/^pm_is_installed()/pm_apt_is_installed()/' \
-                -e 's/^pm_get_package_name()/pm_apt_get_package_name()/' \
-                -e 's/^pm_update()/pm_apt_update()/' \
-                -e 's/^pm_install()/pm_apt_install()/' \
-                -e 's/^get_missing_deps()/get_missing_deps_apt()/' \
-                "$file"
-            ;;
-        "Package Manager DNF")
-            # Rename functions with dnf_ prefix
-            sed -e 's/^pm_is_installed()/pm_dnf_is_installed()/' \
-                -e 's/^pm_get_package_name()/pm_dnf_get_package_name()/' \
-                -e 's/^pm_update()/pm_dnf_update()/' \
-                -e 's/^pm_install()/pm_dnf_install()/' \
-                -e 's/^get_missing_deps()/get_missing_deps_dnf()/' \
-                "$file"
-            ;;
-        *)
-            cat "$file"
-            ;;
-    esac
-}
-
-# Create the dispatcher functions
-DISPATCHER_FILE="$WORK_DIR/dispatcher.sh"
-
-cat > "$DISPATCHER_FILE" << 'DISPATCHER'
-
-# === Package Manager Dispatcher ===
-# These functions dispatch to the appropriate PM implementation
-
-pm_is_installed() {
-    case "$PM_TYPE" in
-        apt) pm_apt_is_installed "$@" ;;
-        dnf) pm_dnf_is_installed "$@" ;;
-    esac
-}
-
-pm_get_package_name() {
-    case "$PM_TYPE" in
-        apt) pm_apt_get_package_name "$@" ;;
-        dnf) pm_dnf_get_package_name "$@" ;;
-    esac
+    cat "$file"
 }
 
-pm_update() {
-    case "$PM_TYPE" in
-        apt) pm_apt_update "$@" ;;
-        dnf) pm_dnf_update "$@" ;;
-    esac
-}
-
-pm_install() {
-    case "$PM_TYPE" in
-        apt) pm_apt_install "$@" ;;
-        dnf) pm_dnf_install "$@" ;;
-    esac
-}
-
-get_missing_deps() {
-    case "$PM_TYPE" in
-        apt) get_missing_deps_apt "$@" ;;
-        dnf) get_missing_deps_dnf "$@" ;;
-    esac
-}
-
-DISPATCHER
-
-# Combine scripts in order - IMPORTANT: 
-# 1. PM implementations (apt, dnf) come first with prefixed names
-# 2. Dispatcher comes next to provide the generic interface
-# 3. pm_base.sh uses the generic interface (get_missing_deps, etc.)
-# 4. main.sh and others come last
+# Combine scripts in order
 {
     process_script "$SCRIPT_DIR/utils.sh" "Utils"
     echo ""
     process_script "$SCRIPT_DIR/ui.sh" "UI"
     echo ""
-    # PM implementations first (with prefixed function names)
-    process_script "$SCRIPT_DIR/pm_apt.sh" "Package Manager APT"
-    echo ""
-    process_script "$SCRIPT_DIR/pm_dnf.sh" "Package Manager DNF"
-    echo ""
-    # Dispatcher provides the generic interface using PM_TYPE
-    cat "$DISPATCHER_FILE"
+    # Include PM implementations first
+    for pm_name in "${PM_IMPLEMENTATIONS[@]}"; do
+        process_script "$SCRIPT_DIR/pm_${pm_name}.sh" "Package Manager ${pm_name}"
+        echo ""
+    done
+    # Include PM array initialization
+    cat "$PM_ARRAY_FILE"
     echo ""
-    # Now pm_base.sh can use the generic interface
+    # Include pm_base which uses the array
     process_script "$SCRIPT_DIR/pm_base.sh" "Package Manager Base"
     echo ""
     process_script "$SCRIPT_DIR/build_config.sh" "Build Config"
@@ -184,14 +136,14 @@ DISPATCHER
 # Replace version placeholder in combined file
 sed -i "s/@VERSION@/$VERSION/g" "$COMBINED_FILE"
 
-# Step 4: Create the payload
+# Step 5: Create the payload
 echo "Creating compressed payload..."
 PAYLOAD_FILE="$WORK_DIR/payload.tar.xz"
 
 # Use highest compression for smaller file size
 tar -C "$SOURCES_DIR" -cf - . | xz -9 -T0 > "$PAYLOAD_FILE"
 
-# Step 5: Assemble final installer
+# Step 6: Assemble final installer
 echo "Assembling final installer..."
 {
     cat "$HEADER_FILE"

+ 9 - 28
installer/main.sh

@@ -8,6 +8,7 @@ ASSUME_YES="false"
 VERBOSE="false"
 SCRIPT_VERSION="@VERSION@"
 PM_TYPE=""
+ORIGINAL_ARGS=("$@")
 
 # Parse command line arguments
 parse_args() {
@@ -69,25 +70,6 @@ Examples:
 EOF
 }
 
-# Initialize package manager functions based on detected type
-init_package_manager() {
-    local pm="$1"
-    
-    # Set the PM type for reference
-    PM_TYPE="$pm"
-    
-    # The PM functions are already defined from the concatenated scripts
-    # We just need to verify the PM type is supported
-    case "$pm" in
-        apt|dnf)
-            # PM functions are available
-            ;;
-        *)
-            show_error_and_exit "Unsupported package manager: $pm"
-            ;;
-    esac
-}
-
 # Check for root privileges and offer to re-run with sudo
 check_root() {
     if is_root; then
@@ -100,11 +82,11 @@ check_root() {
         show_error_and_exit "This script requires root privileges. Please run with sudo or as root."
     fi
     
-    log_warn "This script requires root privileges."
+    log_error "This script requires root privileges."
     
     if confirm "Would you like to re-run the script with $sudo_cmd?" "y"; then
         log_info "Re-running with $sudo_cmd..."
-        exec $sudo_cmd "$0" "$@"
+        exec $sudo_cmd "$0" "${ORIGINAL_ARGS[@]}"
     else
         show_error_and_exit "Root privileges are required. Installation cancelled."
     fi
@@ -125,7 +107,7 @@ run_installation() {
     
     # Install system dependencies
     log_step "Checking system dependencies..."
-    if ! install_missing_deps; then
+    if ! pm_install_missing_deps; then
         show_error_and_exit "Failed to install system dependencies. Installation halted."
     fi
     
@@ -153,19 +135,18 @@ main() {
     check_root
     
     # Detect package manager
-    local pm=$(detect_package_manager)
-    if [[ "$pm" == "unknown" ]]; then
+    if ! detect_package_manager; then
         show_error_and_exit "Could not detect a supported package manager (apt or dnf)"
     fi
     
-    # Initialize the appropriate package manager
-    init_package_manager "$pm"
+    log_info "Detected package manager: ${PM_TYPE}"
     
-    # Count missing dependencies
+    # Get missing dependencies for display
+    local missing_deps=$(pm_get_missing_deps)
     local deps_count=$(count_missing_deps)
     
     # Show installation summary and get confirmation
-    if ! show_installation_summary "$TARGET_DIR" "$pm" "$deps_count"; then
+    if ! show_installation_summary "$TARGET_DIR" "$PM_TYPE" "$deps_count" "$missing_deps"; then
         echo "Installation cancelled."
         exit 0
     fi

+ 2 - 1
installer/meson.build

@@ -8,6 +8,7 @@ project('usm-installer', 'c',
 
 installer_dir = meson.current_source_dir()
 project_root = installer_dir / '..'
+usm_source_dir = project_root / 'src'
 
 # Version from project
 version = meson.project_version()
@@ -48,7 +49,7 @@ build_installer = custom_target(
         bash, compile_script,
         '@OUTPUT@',
         version,
-        project_root
+        usm_source_dir
     ],
     build_by_default: true,
     install: false,

+ 40 - 57
installer/pm_apt.sh

@@ -1,75 +1,58 @@
 #!/bin/bash
 # pm_apt.sh - APT package manager implementation for Debian/Ubuntu systems
+# 
+# To add a new package manager, create a file named pm_<name>.sh with:
+#   - pm_<name>_detect: Returns 0 if this PM is available, 1 otherwise
+#   - pm_<name>_get_missing_deps: Prints space-separated list of missing packages
+#   - pm_<name>_install_missing_deps: Installs the missing packages
 
-# Package name mappings for apt
-declare -A APT_PACKAGE_NAMES=(
-    ["vala"]="valac"
-    ["meson"]="meson"
-    ["ninja"]="ninja-build"
-    ["git"]="git"
-    ["pkgconfig"]="pkg-config"
-    ["gcc"]="gcc"
-    ["glib"]="libglib2.0-dev"
-    ["libsodium"]="libsodium-dev"
-    ["gtk"]="libgtk-3-dev"
-)
+# Package names required by USM
+APT_DEPS="valac meson ninja-build git pkg-config gcc libglib2.0-dev libsodium-dev"
 
-# Check if a package is installed via apt/dpkg
-pm_is_installed() {
-    local pkg="$1"
-    dpkg -l "$pkg" 2>/dev/null | grep -q "^ii"
+# Check if this package manager is available
+pm_apt_detect() {
+    command -v apt-get &>/dev/null
 }
 
-# Get the apt package name for a generic dependency
-pm_get_package_name() {
-    local dep="$1"
-    if [[ -n "${APT_PACKAGE_NAMES[$dep]}" ]]; then
-        echo "${APT_PACKAGE_NAMES[$dep]}"
-    else
-        echo "$dep"
-    fi
+# Get list of missing dependencies
+pm_apt_get_missing_deps() {
+    local -a missing=()
+    
+    for pkg in $APT_DEPS; do
+        if ! dpkg -l "$pkg" 2>/dev/null | grep -q "^ii"; then
+            missing+=("$pkg")
+        fi
+    done
+    
+    echo "${missing[*]}"
 }
 
-# Update apt cache
-pm_update() {
-    local sudo=$(get_sudo)
-    if [[ -n "$sudo" ]]; then
-        $sudo apt-get update -qq
-    else
-        apt-get update -qq
+# Install missing dependencies
+pm_apt_install_missing_deps() {
+    local missing=$(pm_apt_get_missing_deps)
+    
+    if [[ -z "$missing" ]]; then
+        log_info "All dependencies are already installed"
+        return 0
     fi
-}
-
-# Install packages via apt
-pm_install() {
-    local -a pkgs=("$@")
-    local sudo=$(get_sudo)
-    local apt_opts="-y -qq"
     
+    log_step "Installing packages via apt: ${missing}"
+    
+    local sudo=""
+    if ! is_root; then
+        sudo=$(get_sudo)
+    fi
+    
+    local apt_opts="-y -qq"
     if [[ "$ASSUME_YES" == "true" ]]; then
         apt_opts="-y -qq --allow-downgrades --allow-remove-essential --allow-change-held-packages"
     fi
     
-    log_step "Installing packages via apt: ${pkgs[*]}"
-    
     if [[ -n "$sudo" ]]; then
-        DEBIAN_FRONTEND=noninteractive $sudo apt-get install $apt_opts "${pkgs[@]}"
+        DEBIAN_FRONTEND=noninteractive $sudo apt-get update -qq
+        DEBIAN_FRONTEND=noninteractive $sudo apt-get install $apt_opts $missing
     else
-        DEBIAN_FRONTEND=noninteractive apt-get install $apt_opts "${pkgs[@]}"
+        DEBIAN_FRONTEND=noninteractive apt-get update -qq
+        DEBIAN_FRONTEND=noninteractive apt-get install $apt_opts $missing
     fi
 }
-
-# Get list of missing dependencies (apt-specific override)
-get_missing_deps() {
-    local -a missing=()
-    local deps=("vala" "meson" "ninja" "git" "pkgconfig" "gcc" "glib" "libsodium")
-    
-    for dep in "${deps[@]}"; do
-        local pkg_name=$(pm_get_package_name "$dep")
-        if ! pm_is_installed "$pkg_name"; then
-            missing+=("$pkg_name")
-        fi
-    done
-    
-    echo "${missing[@]}"
-}

+ 44 - 59
installer/pm_base.sh

@@ -1,76 +1,61 @@
 #!/bin/bash
 # pm_base.sh - Base package manager interface for USM installer
+# 
+# This file provides the generic interface that dispatches to the
+# appropriate package manager implementation.
+#
+# To add a new package manager:
+#   1. Create pm_<name>.sh with:
+#      - pm_<name>_detect: Returns 0 if available, 1 otherwise
+#      - pm_<name>_get_missing_deps: Prints missing package names
+#      - pm_<name>_install_missing_deps: Installs missing packages
+#   2. The compiler will automatically pick it up
 
-# Common dependencies required by USM and its components
-# These are package names that should be common across package managers
-declare -A COMMON_DEPS=(
-    ["vala"]="vala"
-    ["meson"]="meson"
-    ["ninja"]="ninja-build"
-    ["git"]="git"
-    ["pkgconfig"]="pkg-config"
-    ["gcc"]="gcc"
-    ["glib"]="glib2"
-)
-
-# Base function to check if a package is installed
-# Must be overridden by package manager implementation
-pm_is_installed() {
-    log_error "pm_is_installed not implemented"
-    return 1
-}
-
-# Base function to install packages
-# Must be overridden by package manager implementation
-pm_install() {
-    log_error "pm_install not implemented"
-    return 1
-}
-
-# Base function to update package cache
-# Must be overridden by package manager implementation
-pm_update() {
-    log_error "pm_update not implemented"
+# Detect the available package manager
+# Sets PM_TYPE and returns 0 on success, 1 if no supported PM found
+detect_package_manager() {
+    # Try each PM implementation
+    for pm_impl in "${PM_IMPLEMENTATIONS[@]}"; do
+        if "pm_${pm_impl}_detect" 2>/dev/null; then
+            PM_TYPE="$pm_impl"
+            return 0
+        fi
+    done
+    
+    PM_TYPE="unknown"
     return 1
 }
 
-# Base function to get package names for dependencies
-# Must be overridden by package manager implementation
-pm_get_package_name() {
-    log_error "pm_get_package_name not implemented"
-    echo ""
+# Get missing dependencies using the detected package manager
+pm_get_missing_deps() {
+    case "$PM_TYPE" in
+        apt) pm_apt_get_missing_deps "$@" ;;
+        dnf) pm_dnf_get_missing_deps "$@" ;;
+        *)
+            log_error "No package manager detected"
+            echo ""
+            ;;
+    esac
 }
 
-# Install all missing dependencies
-install_missing_deps() {
-    local missing=$(get_missing_deps)
-    
-    if [[ -z "$missing" ]]; then
-        log_info "All dependencies are already installed"
-        return 0
-    fi
-    
-    log_step "Installing missing dependencies: ${missing}"
-    
-    if ! pm_update; then
-        log_warn "Failed to update package cache, continuing anyway..."
-    fi
-    
-    if ! pm_install $missing; then
-        log_error "Failed to install dependencies"
-        return 1
-    fi
-    
-    log_info "Dependencies installed successfully"
-    return 0
+# Install missing dependencies using the detected package manager
+pm_install_missing_deps() {
+    case "$PM_TYPE" in
+        apt) pm_apt_install_missing_deps "$@" ;;
+        dnf) pm_dnf_install_missing_deps "$@" ;;
+        *)
+            log_error "No package manager detected"
+            return 1
+            ;;
+    esac
 }
 
 # Count missing dependencies
 count_missing_deps() {
-    local missing=$(get_missing_deps)
+    local missing=$(pm_get_missing_deps)
     if [[ -z "$missing" ]]; then
         echo 0
     else
-        echo $(echo "$missing" | wc -w)
+        echo "$missing" | wc -w
     fi
 }

+ 40 - 57
installer/pm_dnf.sh

@@ -1,75 +1,58 @@
 #!/bin/bash
 # pm_dnf.sh - DNF package manager implementation for Fedora/RHEL systems
+# 
+# To add a new package manager, create a file named pm_<name>.sh with:
+#   - pm_<name>_detect: Returns 0 if this PM is available, 1 otherwise
+#   - pm_<name>_get_missing_deps: Prints space-separated list of missing packages
+#   - pm_<name>_install_missing_deps: Installs the missing packages
 
-# Package name mappings for dnf
-declare -A DNF_PACKAGE_NAMES=(
-    ["vala"]="vala"
-    ["meson"]="meson"
-    ["ninja"]="ninja-build"
-    ["git"]="git"
-    ["pkgconfig"]="pkgconf-pkg-config"
-    ["gcc"]="gcc"
-    ["glib"]="glib2-devel"
-    ["libsodium"]="libsodium-devel"
-    ["gtk"]="gtk3-devel"
-)
+# Package names required by USM
+DNF_DEPS="vala meson ninja-build git pkgconf-pkg-config gcc glib2-devel libsodium-devel"
 
-# Check if a package is installed via dnf/rpm
-pm_is_installed() {
-    local pkg="$1"
-    rpm -q "$pkg" &>/dev/null
+# Check if this package manager is available
+pm_dnf_detect() {
+    command -v dnf &>/dev/null
 }
 
-# Get the dnf package name for a generic dependency
-pm_get_package_name() {
-    local dep="$1"
-    if [[ -n "${DNF_PACKAGE_NAMES[$dep]}" ]]; then
-        echo "${DNF_PACKAGE_NAMES[$dep]}"
-    else
-        echo "$dep"
-    fi
+# Get list of missing dependencies
+pm_dnf_get_missing_deps() {
+    local -a missing=()
+    
+    for pkg in $DNF_DEPS; do
+        if ! rpm -q "$pkg" &>/dev/null; then
+            missing+=("$pkg")
+        fi
+    done
+    
+    echo "${missing[*]}"
 }
 
-# Update dnf cache
-pm_update() {
-    local sudo=$(get_sudo)
-    if [[ -n "$sudo" ]]; then
-        $sudo dnf makecache -q 2>/dev/null || $sudo dnf clean metadata
-    else
-        dnf makecache -q 2>/dev/null || dnf clean metadata
+# Install missing dependencies
+pm_dnf_install_missing_deps() {
+    local missing=$(pm_dnf_get_missing_deps)
+    
+    if [[ -z "$missing" ]]; then
+        log_info "All dependencies are already installed"
+        return 0
     fi
-}
-
-# Install packages via dnf
-pm_install() {
-    local -a pkgs=("$@")
-    local sudo=$(get_sudo)
-    local dnf_opts="-y --quiet"
     
+    log_step "Installing packages via dnf: ${missing}"
+    
+    local sudo=""
+    if ! is_root; then
+        sudo=$(get_sudo)
+    fi
+    
+    local dnf_opts="-y --quiet"
     if [[ "$ASSUME_YES" == "true" ]]; then
         dnf_opts="-y --quiet --skip-broken"
     fi
     
-    log_step "Installing packages via dnf: ${pkgs[*]}"
-    
     if [[ -n "$sudo" ]]; then
-        $sudo dnf install $dnf_opts "${pkgs[@]}"
+        $sudo dnf makecache -q 2>/dev/null || $sudo dnf clean metadata
+        $sudo dnf install $dnf_opts $missing
     else
-        dnf install $dnf_opts "${pkgs[@]}"
+        dnf makecache -q 2>/dev/null || dnf clean metadata
+        dnf install $dnf_opts $missing
     fi
 }
-
-# Get list of missing dependencies (dnf-specific override)
-get_missing_deps() {
-    local -a missing=()
-    local deps=("vala" "meson" "ninja" "git" "pkgconfig" "gcc" "glib" "libsodium")
-    
-    for dep in "${deps[@]}"; do
-        local pkg_name=$(pm_get_package_name "$dep")
-        if ! pm_is_installed "$pkg_name"; then
-            missing+=("$pkg_name")
-        fi
-    done
-    
-    echo "${missing[@]}"
-}

+ 24 - 9
installer/ui.sh

@@ -4,7 +4,7 @@
 # Print a banner/header
 print_banner() {
     echo -e "${CYAN}${BOLD}"
-    echo "USM Installer - Universal Software Manager"
+    echo "USM Installer - Universal Source Manifest"
     echo -e "Version: ${SCRIPT_VERSION}${NC}"
     echo ""
 }
@@ -89,29 +89,44 @@ show_installation_summary() {
     local target_dir="$1"
     local pm="$2"
     local deps_count="$3"
+    local missing_deps="$4"
     
     print_section "Installation Summary"
     
     local -a summary_items=(
         "Target Directory: ${target_dir}"
         "Package Manager: ${pm}"
-        "Dependencies to install: ${deps_count}"
-        "Components:"
     )
     
+    if (( deps_count > 0 )); then
+        summary_items+=("Dependencies to install: ${deps_count}")
+    else
+        summary_items+=("Dependencies: All installed")
+    fi
+    
     print_summary "${summary_items[@]}"
     
     echo ""
+    
+    # Show package installation step if there are deps to install
+    local step_num=1
+    if (( deps_count > 0 )); then
+        echo -e "${YELLOW}Step $step_num:${NC} The following packages will be installed using ${pm}:"
+        for pkg in $missing_deps; do
+            print_list_item "•" "$pkg"
+        done
+        echo ""
+        ((step_num++))
+    fi
+    
+    echo -e "${YELLOW}Step $step_num:${NC} Build and install components:"
+    ((step_num++))
     print_list_item "1" "Invercargill (invercargill-1) - Core library"
     print_list_item "2" "Invercargill-Json - JSON serialization"
-    print_list_item "3" "USM - Universal Software Manager"
+    print_list_item "3" "USM - Universal Source Manifest"
     
     echo ""
-    echo -e "${YELLOW}This script will:${NC}"
-    print_list_item "•" "Install required system packages via ${pm}"
-    print_list_item "•" "Build and install Invercargill libraries"
-    print_list_item "•" "Build and install USM to ${target_dir}"
-    print_list_item "•" "Create a shim at /usr/bin/usm"
+    echo -e "${YELLOW}Step $step_num:${NC} Create shim at /usr/bin/usm"
     
     echo ""
     if ! confirm "Do you want to continue with the installation?" "n"; then