|
1 | 1 | #!/usr/bin/env bash |
2 | 2 | set -euo pipefail |
3 | 3 |
|
4 | | -# ─── COLORS & LOG FUNCTIONS ────────────────────────────────────────────────── |
| 4 | +# COLORS & LOG FUNCTIONS |
5 | 5 | GREEN="\e[32m"; YELLOW="\e[33m"; RED="\e[31m"; RESET="\e[0m" |
6 | 6 | log() { echo -e "${GREEN}[+]${RESET} $1"; } |
7 | 7 | warn() { echo -e "${YELLOW}[!]${RESET} $1"; } |
8 | 8 | error() { echo -e "${RED}[-]${RESET} $1"; exit 1; } |
9 | 9 |
|
10 | | -# ─── ENSURE ROOT & ENABLE TAB COMPLETION ───────────────────────────────────── |
| 10 | +log "Live USB creator utility" |
| 11 | + |
| 12 | +# ENSURE ROOT & ENABLE TAB COMPLETION |
11 | 13 | (( EUID == 0 )) || error "Run as root: sudo $0" |
12 | | -bind 'TAB:complete' |
| 14 | +if [[ $- == *i* ]]; then |
| 15 | + bind 'TAB:complete' |
| 16 | +fi |
| 17 | + |
| 18 | +# DISPLAY BLOCKS |
| 19 | +# GLOBAL ASSOCIATIVE ARRAY to store device mappings (index -> device path) |
| 20 | +declare -A DEVICES_MAP |
| 21 | +# Array to hold raw lsblk output lines, used to avoid subshell issues with DEVICES_MAP |
| 22 | +DEVICES_INFO=() |
| 23 | + |
| 24 | +# FUNCTION TO LIST AVAILABLE BLOCK DEVICES WITH INDEXES |
| 25 | +list_devices() { |
| 26 | + log "Finding devices" |
| 27 | + printf "%3s %-8s %-6s %-12s %-8s %-7s %-10s %s\n" "Idx" "Name" "Size" "Model" "Vendor" "Type" "Mountpoint" "RO" |
| 28 | + printf "%3s %-8s %-6s %-12s %-8s %-7s %-10s %s\n" "---" "--------" "------" "------------" "--------" "-------" "----------" "--" |
| 29 | + |
| 30 | + local i=1 # Start index from 1 |
| 31 | + |
| 32 | + # Populate DEVICES_INFO with lines from lsblk directly into the parent shell. |
| 33 | + # We exclude common non-physical/ephemeral device types by their major device numbers. |
| 34 | + # -d lists devices only (not partitions). |
| 35 | + # -n suppresses headers. |
| 36 | + mapfile -t DEVICES_INFO < <(lsblk -o NAME,SIZE,MODEL,VENDOR,TRAN,TYPE,MOUNTPOINT,RO -d -n -e 7,11,9,2,259,1) |
| 37 | + |
| 38 | + # Iterate over the captured lines to print and store mappings |
| 39 | + for line in "${DEVICES_INFO[@]}"; do |
| 40 | + # Use 'read' with a 'here string' to parse each line |
| 41 | + read -r name size model vendor tran type mountpoint ro <<< "$line" |
| 42 | + |
| 43 | + # Remove '/dev/' prefix if present, though lsblk -o NAME usually gives just sda, sdb etc. |
| 44 | + local clean_name=$(echo "$name" | sed 's|^/dev/||') |
| 45 | + local full_path="/dev/$clean_name" |
| 46 | + |
| 47 | + # Store the mapping: index -> full device path |
| 48 | + DEVICES_MAP[$i]="$full_path" |
| 49 | + |
| 50 | + # Print the indexed device information in a formatted way |
| 51 | + printf "%3d %-8s %-6s %-12s %-8s %-7s %-10s %s\n" \ |
| 52 | + "$i" "$clean_name" "$size" "$model" "$vendor" "$type" "$mountpoint" "$ro" |
| 53 | + ((i++)) |
| 54 | + done |
| 55 | + echo "" # Add a newline for better readability |
| 56 | + echo "Enter 0 to quit." |
| 57 | + echo "" |
| 58 | +} |
| 59 | + |
| 60 | +list_devices |
| 61 | + |
| 62 | + |
| 63 | +# PROMPT FOR INPUTS |
| 64 | +SELECTED_INDEX="" |
| 65 | + |
| 66 | +while true; do |
| 67 | + read -p "Select USB device by Idx (e.g., 1): " SELECTED_INDEX |
| 68 | + # Check if input is a number |
| 69 | + if [[ "$SELECTED_INDEX" =~ ^[0-9]+$ ]]; then |
| 70 | + if (( SELECTED_INDEX == 0 )); then |
| 71 | + error "Aborted." |
| 72 | + exit 0 # Exit if user chooses to quit |
| 73 | + elif [[ -n "${DEVICES_MAP[$SELECTED_INDEX]}" ]]; then |
| 74 | + # If a valid index is provided, retrieve the full device path |
| 75 | + DEV="${DEVICES_MAP[$SELECTED_INDEX]}" |
| 76 | + break # Valid selection, exit the loop |
| 77 | + else |
| 78 | + warn "Invalid index. Please enter a number from the list or 0 to quit." |
| 79 | + fi |
| 80 | + else |
| 81 | + warn "Invalid input. Please enter a number." |
| 82 | + fi |
| 83 | +done |
13 | 84 |
|
14 | | -# ─── PROMPT FOR INPUTS ──────────────────────────────────────────────────────── |
15 | | -read -e -p "Path to Ubuntu ISO: " ISO_PATH |
16 | | -[[ -f "$ISO_PATH" ]] || error "ISO not found at '$ISO_PATH'" |
| 85 | +# Double-check if the selected device exists, though it should if selected from the list |
| 86 | +[[ -b "$DEV" ]] || error "Selected block device '$DEV' not found unexpectedly." |
| 87 | +USB_PART="${DEV}1" |
17 | 88 |
|
18 | | -read -e -p "Path to autoinstall YAML: " YAML_PATH |
19 | | -[[ -f "$YAML_PATH" ]] || error "YAML not found at '$YAML_PATH'" |
| 89 | +read -e -p "Path to ISO: " ISO_PATH |
| 90 | +[[ -f "$ISO_PATH" ]] || error "ISO not found at '$ISO_PATH'" |
20 | 91 |
|
21 | | -read -e -p "USB device (e.g. /dev/sda): " DEV |
22 | | -[[ -b "$DEV" ]] || error "Block device '$DEV' not found" |
23 | | -USB_PART="${DEV}1" |
| 92 | +read -e -p "Optional path to autoinstall YAML (press Enter to skip): " YAML_PATH |
| 93 | +if [[ -n "$YAML_PATH" && ! -f "$YAML_PATH" ]]; then |
| 94 | + error "YAML not found at '$YAML_PATH'" |
| 95 | +fi |
24 | 96 |
|
25 | 97 | cat <<EOF |
26 | 98 |
|
27 | 99 | You are about to: |
28 | 100 |
|
29 | | - • Wipe & reformat ➜ $DEV |
30 | | - • Copy ISO ➜ $ISO_PATH |
31 | | - • Copy autoinstall ➜ $YAML_PATH |
| 101 | +- Wipe & reformat ➜ $DEV |
| 102 | +- Copy ISO ➜ $ISO_PATH |
| 103 | +- Copy autoinstall ➜ ${YAML_PATH:-Skipped} |
32 | 104 |
|
33 | 105 | EOF |
34 | 106 |
|
35 | 107 | read -p "Proceed? (yes/[no]) " CONFIRM |
36 | 108 | [[ "$CONFIRM" == "yes" ]] || { log "Aborted."; exit 0; } |
37 | 109 |
|
38 | | -# ─── PREP: UNMOUNT ─────────────────────────────────────────────────────────── |
| 110 | +# PREP: UNMOUNT |
39 | 111 | log "Unmounting partitions on $DEV if mounted..." |
40 | 112 | for p in $(lsblk -lnpo NAME,MOUNTPOINT "${DEV}"* | awk '$2!=""{print $1}'); do |
41 | 113 | warn " umount $p" |
42 | 114 | umount "$p" || warn "Could not unmount $p, continuing" |
43 | 115 | done |
44 | 116 |
|
45 | | -# ─── PARTITION & FORMAT ────────────────────────────────────────────────────── |
| 117 | +# PARTITION & FORMAT |
46 | 118 | log "Creating single FAT32 partition on $DEV..." |
47 | 119 | parted --script "$DEV" \ |
48 | 120 | mklabel msdos \ |
49 | 121 | mkpart primary fat32 1MiB 100% \ |
50 | 122 | set 1 boot on |
51 | 123 |
|
52 | 124 | log "Formatting ${USB_PART} as FAT32..." |
53 | | -mkfs.vfat -F32 "$USB_PART" |
| 125 | +mkfs.vfat -F32 "$USB_PART" > /dev/null 2>&1 |
54 | 126 |
|
55 | | -# ─── MOUNTS ─────────────────────────────────────────────────────────────────── |
| 127 | +# MOUNTS |
56 | 128 | MNT_ISO=$(mktemp -d) |
57 | 129 | MNT_USB=$(mktemp -d) |
58 | 130 |
|
59 | 131 | log "Mounting ISO -> $MNT_ISO" |
60 | | -mount -o loop "$ISO_PATH" "$MNT_ISO" |
| 132 | +mount -o loop "$ISO_PATH" "$MNT_ISO" > /dev/null 2>&1 |
61 | 133 |
|
62 | 134 | log "Mounting USB -> $MNT_USB" |
63 | 135 | mount "$USB_PART" "$MNT_USB" |
64 | 136 |
|
65 | | -# ─── COPY FILES (follow symlinks) ──────────────────────────────────────────── |
| 137 | +# COPY FILES |
66 | 138 | log "Copying ISO contents to USB..." |
67 | | -warn "Symbolic link errors are okay" |
68 | 139 |
|
69 | 140 | set +e |
70 | | -cp -aT "${MNT_ISO}/." "${MNT_USB}/" |
| 141 | +cp -aT "${MNT_ISO}/." "${MNT_USB}/" > /dev/null 2>&1 |
71 | 142 | CP_STATUS=$? |
72 | 143 | set -e |
73 | 144 |
|
74 | | -log "Adding autoinstall.yaml to USB root..." |
75 | | -cp "$YAML_PATH" "${MNT_USB}/autoinstall.yaml" |
| 145 | +if [[ -n "$YAML_PATH" ]]; then |
| 146 | + log "Adding autoinstall.yaml to USB root..." |
| 147 | + cp "$YAML_PATH" "${MNT_USB}/autoinstall.yaml" |
| 148 | +else |
| 149 | + log "Skipping autoinstall.yaml copy." |
| 150 | +fi |
76 | 151 |
|
77 | | -# ─── CLEANUP ───────────────────────────────────────────────────────────────── |
78 | | -log "Syncing disks..." |
| 152 | +# CLEANUP |
| 153 | +log "Syncing and unmounting USB..." |
79 | 154 | sync "$MNT_USB" |
| 155 | +umount "$MNT_USB" |
| 156 | + |
| 157 | +# Force unmount ISO, as it's read-only and doesn't require flushing |
| 158 | +log "Unmounting ISO..." |
| 159 | +umount -f "$MNT_ISO" |
80 | 160 |
|
81 | | -log "Unmounting mounts..." |
82 | | -umount "$MNT_ISO" "$MNT_USB" |
| 161 | +log "Cleaning up mounts..." |
83 | 162 | rmdir "$MNT_ISO" "$MNT_USB" |
84 | 163 |
|
85 | 164 | log "USB is ready!" |
0 commit comments