old mode 100755
new mode 100644
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# This software is a part of Isar.
-# Copyright (C) Siemens AG, 2024
+# Copyright (C) Siemens AG, 2026
#
# SPDX-License-Identifier: MIT
@@ -9,293 +9,202 @@ installdata=${INSTALL_DATA:-/install}
SCRIPT_DIR=$( dirname -- "$( readlink -f -- "$0"; )"; )
. "${SCRIPT_DIR}/../lib/deploy-image-wic/handle-config.sh"
+. "${SCRIPT_DIR}/sys_api.sh"
+. "${SCRIPT_DIR}/installer_ui.sh"
-if [ "$installer_unattended" = true ] && [ "$installer_unattended_abort_enable" = true ]; then
- abort_file=/tmp/attended_mode_trigger
- for ((i=$installer_unattended_abort_timeout; i>0; i--)); do
- echo -ne "\rUnattended installation will start in $i seconds. Press any key to switch to attended mode..."
+#--------------------------------------------------------------------------
+# This module contains high-level installer flow logic
+# while keeping low-level backend APIs in sys_api.sh
+# and user-facing dialogs in installer_ui.sh.
+#--------------------------------------------------------------------------
- # Switch to attended mode if the abort file exists or any key pressed during countdown
- # Create abort file to notify all other console instances to abort
- if [ -f "$abort_file" ] || read -n 1 -t 1; then
- installer_unattended=false
- touch "$abort_file"
- break
- fi
- done
-fi
-
-if ! $installer_unattended; then
- installer_image_uri=$(find "$installdata" -type f -iname "*.wic*" -a -not -iname "*.wic.bmap" -exec basename {} \;)
- if [ -z "$installer_image_uri" ] || [ ! -f "$installdata/$installer_image_uri" ]; then
- pushd "$installdata"
- for f in $(find . -type f ! -iname "*.wic.bmap"); do
- array+=("$f" "$f")
- done
- popd
- if [ ${#array[@]} -gt 0 ]; then
- if ! installer_image_uri=$(dialog --no-tags \
- --menu "Select image to be installed" 10 60 3 \
- "${array[@]}" --output-fd 1); then
- exit 0
- fi
- fi
- fi
-
- if [ ! -f "$installdata/$installer_image_uri" ]; then
- dialog --msgbox "Could not find an image to install. Installation aborted." 6 60
- exit 1
- fi
-
- # inspired by poky/meta/recipes-core/initrdscripts/files/install-efi.sh
- target_device_list=""
- current_root_dev_type=$(findmnt / -o fstype -n)
- exclude_list=()
-
- if [ "$current_root_dev_type" = "nfs" ]; then
- current_root_dev="nfs"
- exclude_list+=("nfs")
- else
- # For normal or immutable systems, get the backing device of '/'
- root_source=$(findmnt / -o source -n)
- root_source_resolved=$(readlink -f "$root_source" 2>/dev/null || echo "$root_source")
- current_root_dev=${root_source_resolved#/dev/}
-
- # Always exclude the exact device mounted as /
- exclude_list+=("$current_root_dev")
-
- if [[ "$current_root_dev" =~ ^(mmcblk|nvme) ]]; then
- base_no_part="${current_root_dev%p[0-9]*}"
- else
- base_no_part="${current_root_dev%%[0-9]*}"
- fi
- if [ -n "$base_no_part" ]; then
- exclude_list+=("$base_no_part")
- fi
-
- # If root is coming through a dm-* device (e.g., dm-verity),
- # the actual physical devices appear under /sys/block/<dm>/slaves/.
- # We must exclude those slaves as well, otherwise the installer
- # might show the live USB stick as a valid target.
- if [ -d "/sys/block/$current_root_dev/slaves" ]; then
- for slave in /sys/block/"$current_root_dev"/slaves/*; do
- [ -e "$slave" ] || continue
- slave_dev=$(basename "$slave")
- exclude_list+=("$slave_dev")
- slave_base=${slave_dev%%[0-9]*}
- [ -n "$slave_base" ] && exclude_list+=("$slave_base")
- done
- fi
- fi
-
- echo "Searching for target device..."
-
- devices=$(find /sys/block/ -type b,c,f,l -not -iname "mmcblk*" -printf "%f\n") || true
- mmc_devices=$(find /sys/block/ -type b,c,f,l -iname "mmcblk[0-9]" -printf "%f\n") || true
- devices="$devices $mmc_devices"
-
- for device in $devices; do
- is_raid_member=0
-
- if [ -d "/sys/block/$device/holders" ] && [ ! -d "/sys/block/$device/md" ]; then
- for holder_path in /sys/block/$device/holders/*; do
- holder_name=$(basename "$holder_path")
- case "$holder_name" in
- md[0-9]*)
- is_raid_member=1
- break
- ;;
- esac
- done
- fi
-
- if [ "$is_raid_member" -eq 1 ]; then
- continue # Skip RAID member disks
- fi
-
- if [[ "$device" == md* ]]; then
- if [ -f "/sys/block/$device/md/array_state" ]; then
- state=$(cat /sys/block/$device/md/array_state)
- if [ "$state" != "active" ] && [ "$state" != "clean" ]; then
- continue
- fi
- else
- continue
- fi
- fi
-
- case $device in
- dm-*)
- # skip device-mapper device
- ;;
- loop*)
- # skip loop device
- ;;
- mtd*)
- ;;
- sr*)
- # skip CDROM device
- ;;
- ram*)
- # skip ram device
- ;;
- zram*)
- # skip zram device
- ;;
- *)
- #skip any excluded devices (root and its slaves)
- skip_device=0
- for ex in "${exclude_list[@]}"; do
- if [[ "$device" == "$ex"* ]]; then
- skip_device=1
- break
- fi
- done
-
- if [ "$skip_device" -eq 0 ]; then
- target_device_list="$target_device_list $device"
- fi
- ;;
- esac
- done
-
- if [ -z "${target_device_list}" ]; then
- dialog --msgbox "You need another device (besides the live device /dev/${current_root_dev}) to install the image. Installation aborted." 7 60
- exit 1
- fi
-
- if [ "$(echo "$target_device_list" | wc -w)" -gt 1 ]; then
- array=()
- for target in $(echo "$target_device_list" | xargs -n1 | sort); do
- target_size=$(lsblk --nodeps --noheadings -o SIZE /dev/"$target" | tr -d " ")
- if cmp /dev/zero /dev/"$target" -n 1M; then
- state="empty"
- else
- state="contains data"
- fi
- array+=("/dev/$target" "/dev/$target ($target_size, $state)")
- done
- if ! installer_target_dev=$(dialog --no-tags \
- --menu "Select device to install image to" 10 60 3 \
- "${array[@]}" --output-fd 1); then
- exit 0
- fi
- else
- installer_target_dev="/dev/$(echo "$target_device_list" | tr -d " ")"
- fi
- TARGET_DEVICE_SIZE=$(lsblk --nodeps --noheadings -o SIZE "$installer_target_dev" | tr -d " ")
- if ! dialog --yes-label Ok --no-label Cancel \
- --yesno "Start installing\n'$installer_image_uri'\nto $installer_target_dev (capacity: $TARGET_DEVICE_SIZE)" 7 60; then
- exit 0
- fi
-
- # set absolute paths to be compatible with unattended mode
- installer_image_uri="$installdata/$installer_image_uri"
-fi
-
-if ! cmp /dev/zero "$installer_target_dev" -n 1M; then
- if ! $installer_unattended; then
- if ! dialog --defaultno \
- --yesno "WARNING: Target device is not empty! Continue anyway?" 5 60; then
- exit 0
- fi
- else
- if [ "$installer_target_overwrite" != "OVERWRITE" ]; then
- echo "Target device is not empty! -> Abort"
- echo "If you want to override existing data set \"installer.target.overwrite=OVERWRITE\" on your kernel cmdline or edit your \"auto.install\" file accordingly."
-
- exit 1
- fi
- fi
-fi
-
-bmap_options=""
-
-# bmap file is expected to be next to the installer image
-DISK_BMAP="${installer_image_uri%.wic*}.wic.bmap"
-
-if [ ! -f "$DISK_BMAP" ]; then
- bmap_options="--nobmap"
-fi
-
-if ! $installer_unattended; then
- clear
-fi
-
-# Function to compare version numbers
-version_ge() {
- if [ "$(printf '%s\n' "$1"X "$2" | sort -V | head -n 1)" != "$1"X ]; then
- return 0
- else
- return 1
- fi
+#--------------------------------------------------------------------------
+# flow_run_attended
+#
+# Handles all attended-mode interactions and assigns:
+# installer_image_uri
+# installer_target_dev
+#
+# Returns:
+# 0 on success
+# 1 on hard error
+# 2 on user cancel
+#--------------------------------------------------------------------------
+flow_run_attended() {
+ local default_image
+ local selected_image
+ local selected_target
+ local target_device_size
+
+ default_image=$(sys_first_default_image_name "$installdata")
+ if [ -n "$default_image" ] && [ -f "$installdata/$default_image" ]; then
+ installer_image_uri="$default_image"
+ fi
+
+ if [ -z "$installer_image_uri" ] || [ ! -f "$installdata/$installer_image_uri" ]; then
+ selected_image=$(ui_select_image_menu "$installdata")
+ case $? in
+ 0)
+ installer_image_uri="$selected_image"
+ ;;
+ 1)
+ ui_show_error "Could not find an image to install. Installation aborted."
+ return 1
+ ;;
+ 2)
+ return 2
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ fi
+
+ if [ ! -f "$installdata/$installer_image_uri" ]; then
+ ui_show_error "Could not find an image to install. Installation aborted."
+ return 1
+ fi
+
+ echo "Searching for target device..."
+ sys_discover_target_devices
+ if [ "${#SYS_TARGET_DEVICES[@]}" -eq 0 ]; then
+ ui_show_error "You need another device (besides the live device /dev/${SYS_CURRENT_ROOT_DEV}) to install the image. Installation aborted."
+ return 1
+ fi
+
+ if [ "${#SYS_TARGET_DEVICES[@]}" -gt 1 ]; then
+ selected_target=$(ui_select_target_device_menu "${SYS_TARGET_DEVICES[@]}")
+ case $? in
+ 0)
+ installer_target_dev="$selected_target"
+ ;;
+ 1)
+ ui_show_error "No installable target devices available. Installation aborted."
+ return 1
+ ;;
+ 2)
+ return 2
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ else
+ installer_target_dev="${SYS_TARGET_DEVICES[0]}"
+ fi
+
+ target_device_size=$(sys_device_size "$installer_target_dev")
+ if ! ui_confirm_install "$installer_image_uri" "$installer_target_dev" "$target_device_size"; then
+ return 2
+ fi
+
+ installer_image_uri="$installdata/$installer_image_uri"
+ return 0
}
-lockfile="/run/installer.lock"
-progress_pipe="/run/installer.fifo"
-
-exec 9>"$lockfile"
-if flock -n 9; then
- # Get bmap-tools version
- bmap_version=$(bmaptool --version | awk '{ print $NF }')
-
- if version_ge "$bmap_version" "3.6"; then
- if ! mkfifo "$progress_pipe"; then
- echo "Error: Failed to create named pipe $progress_pipe"
- exit 1
- fi
-
- # Add psplash pipe to bmap_options
- bmap_options="$bmap_options --psplash-pipe=$progress_pipe"
- quiet_flag="-q"
-
- # Initialize the dialog gauge and update it dynamically
- (
- while true; do
- if read -r line < "$progress_pipe"; then
- percentage=$(echo "$line" | awk '{ print $2 }')
- echo "$percentage"
- fi
- done
- ) | dialog --gauge "Flashing image, please wait..." 10 70 0 &
-
- gauge_pid=$!
- fi
-
- if ! bmaptool $quiet_flag copy $bmap_options "$installer_image_uri" "$installer_target_dev"; then
- kill "$gauge_pid"
- exit 1
- fi
-
- # Attempt to terminate the gauge process if still running.
- # Errors are ignored since the process may already have exited.
- kill "$gauge_pid" 2>/dev/null
-else
- echo "Installation already running in another console."
+#--------------------------------------------------------------------------
+# flow_validate_target_overwrite_policy
+#
+# Enforces overwrite policy for non-empty targets in both attended and
+# unattended modes.
+#
+# Returns:
+# 0 when policy permits installation
+# 1 when policy rejects installation
+# 2 when user cancels in attended mode
+#--------------------------------------------------------------------------
+flow_validate_target_overwrite_policy() {
+ if sys_device_is_empty "$installer_target_dev"; then
+ return 0
+ fi
+
+ if ! $installer_unattended; then
+ if ! ui_confirm_overwrite; then
+ return 2
+ fi
+ else
+ if [ "$installer_target_overwrite" != "OVERWRITE" ]; then
+ echo "Target device is not empty! -> Abort"
+ echo "If you want to override existing data set \"installer.target.overwrite=OVERWRITE\" on your kernel cmdline or edit your \"auto.install\" file accordingly."
+ return 1
+ fi
+ fi
+
+ return 0
+}
- # Wait for running console to create the progress pipe
- sleep 5
+#--------------------------------------------------------------------------
+# flow_maybe_switch_to_attended
+#
+# If unattended abort-by-key is enabled, this function offers a timeout
+# window to switch to attended mode.
+#--------------------------------------------------------------------------
+flow_maybe_switch_to_attended() {
+ local abort_file
+
+ if [ "$installer_unattended" = true ] && [ "$installer_unattended_abort_enable" = true ]; then
+ abort_file=/tmp/attended_mode_trigger
+ if ui_countdown_allow_attended_switch "$installer_unattended_abort_timeout" "$abort_file"; then
+ installer_unattended=false
+ fi
+ fi
+}
- # Check if progress pipe exists and has content
- if [ -e "$progress_pipe" ]; then
- echo "Installation progress..."
- tail -f "$progress_pipe" | while read line; do
- printf "\r%s%%" "$line"
- done
- else
- # Periodically check if bmaptool is still running every 5 seconds
- echo "Waiting for installation to finish..."
- while pgrep -x "bmaptool" > /dev/null; do
- sleep 5
- done
- fi
-fi
+#--------------------------------------------------------------------------
+# flow_run
+#
+# Main installer flow combining attended/unattended execution paths.
+#--------------------------------------------------------------------------
+flow_run() {
+ flow_maybe_switch_to_attended
+
+ if ! $installer_unattended; then
+ flow_run_attended
+ case $? in
+ 0)
+ ;;
+ 2)
+ return 0
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ fi
+
+ flow_validate_target_overwrite_policy
+ case $? in
+ 0)
+ ;;
+ 2)
+ return 0
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+
+ if ! $installer_unattended; then
+ clear
+ fi
+
+ if ! sys_install_image_with_lock "$installer_image_uri" "$installer_target_dev"; then
+ if ! $installer_unattended; then
+ ui_show_error "Installation failed."
+ fi
+ return 1
+ fi
+
+ if ! $installer_unattended; then
+ ui_show_info "Installation was successful. System will be rebooted. Please remove the USB stick."
+ else
+ echo "Installation was successful."
+ fi
+
+ return 0
+}
-if ! $installer_unattended; then
- dialog --title "Reboot" \
- --msgbox "Installation was successful. System will be rebooted. Please remove the USB stick." 6 60
-else
- echo "Installation was successful."
-fi
+# Entrypoint: run the installer flow and propagate status.
+flow_run
+exit $?
-exit 0