--- /home/claude/work/arm-installer/manjaro-arm/manjaro-arm-installer 2026-04-25 00:04:54.000000000 +0000 +++ /home/claude/work/manjaro-arm-installer-patched 2026-04-25 00:08:21.126014412 +0000 @@ -48,6 +48,43 @@ HOSTNAME="" CRYPT="" +# ─── image-output mode (added) ──────────────────────────────────────────────── +# When IMG_OUTPUT is set, the installer writes to a sparse image file via +# losetup -fP instead of asking the user to pick a block device. +# +# Set via environment OR via --output-image / -o flag, e.g.: +# sudo IMG_OUTPUT=manjaro.img IMG_SIZE=8G ./manjaro-arm-installer +# sudo ./manjaro-arm-installer --output-image manjaro.img --image-size 8G +# +# Defaults: IMG_SIZE=8G, IMG_COMPRESS=0 +IMG_OUTPUT="${IMG_OUTPUT:-}" +IMG_SIZE="${IMG_SIZE:-8G}" +IMG_COMPRESS="${IMG_COMPRESS:-0}" +IMG_LOOPDEV="" + +# parse our own flags out of $@ before the script's branch-detection logic +# (which only inspects $1) sees them +_filtered_args=() +while [[ $# -gt 0 ]]; do + case "$1" in + -o|--output-image) IMG_OUTPUT="$2"; shift 2 ;; + --image-size) IMG_SIZE="$2"; shift 2 ;; + --compress) IMG_COMPRESS=1; shift ;; + *) _filtered_args+=("$1"); shift ;; + esac +done +set -- "${_filtered_args[@]}" + +# cleanup hook — detaches the loop device on exit/interrupt so we don't leak +_img_cleanup() { + if [[ -n "$IMG_LOOPDEV" ]] && losetup "$IMG_LOOPDEV" &>/dev/null; then + sync + losetup -d "$IMG_LOOPDEV" 2>/dev/null || true + fi +} +trap _img_cleanup EXIT INT TERM +# ─── end image-output mode ──────────────────────────────────────────────────── + # check if root if [ "$EUID" -ne 0 ]; then echo "*******************************************************************************************" @@ -951,6 +988,25 @@ then # simple command to put the results of lsblk (just the names of the devices) into an array and make that array populate the options + if [[ -n "$IMG_OUTPUT" ]]; then + # ─── image-output branch (added) ────────────────────────────────────── + msg "Image-output mode: writing to $IMG_OUTPUT (size $IMG_SIZE)" + if [[ -e "$IMG_OUTPUT" ]]; then + info "removing existing $IMG_OUTPUT" + rm -f "$IMG_OUTPUT" || { msg "could not remove existing image"; exit 1; } + fi + # create sparse file + truncate -s "$IMG_SIZE" "$IMG_OUTPUT" \ + || { msg "truncate failed"; exit 1; } + # losetup with -P enables partition device nodes (loopNp1, loopNp2) + IMG_LOOPDEV=$(losetup -fP --show "$IMG_OUTPUT") \ + || { msg "losetup failed"; exit 1; } + info "attached as $IMG_LOOPDEV" + SDCARD="$IMG_LOOPDEV" + DEV_NAME="${IMG_LOOPDEV#/dev/}" + SDTYP="${SDCARD:5:2}" # will be "lo" — handled below + # ─── end image-output branch ────────────────────────────────────────── + else let i=0 W=() while read -r line; do @@ -966,6 +1022,7 @@ DEV_NAME=$SDCARD SDCARD=/dev/$SDCARD SDTYP=${SDCARD:5:2} + fi else clear exit 1 @@ -973,7 +1030,8 @@ if [[ "$SDTYP" = "sd" ]]; then SDDEV="" -elif [[ "$SDTYP" = "mm" || "$SDTYP" = "nv" ]]; then +elif [[ "$SDTYP" = "mm" || "$SDTYP" = "nv" || "$SDTYP" = "lo" ]]; then + # loop devices use /dev/loopNp1 partition naming, same as mmcblk/nvme SDDEV="p" else clear @@ -1148,3 +1206,26 @@ cleanup show_elapsed_time "${FUNCNAME}" "${timer_start}" sync + +# ─── image-output finalization (added) ──────────────────────────────────────── +if [[ -n "$IMG_OUTPUT" ]]; then + msg "Detaching loop device $IMG_LOOPDEV" + sync + losetup -d "$IMG_LOOPDEV" 2>/dev/null || true + IMG_LOOPDEV="" + + if [[ "$IMG_COMPRESS" = "1" ]]; then + msg "Compressing $IMG_OUTPUT (this may take a while)" + if command -v xz &>/dev/null; then + xz -T0 -v "$IMG_OUTPUT" + msg "Compressed: ${IMG_OUTPUT}.xz" + elif command -v zstd &>/dev/null; then + zstd -T0 -19 --rm "$IMG_OUTPUT" -o "${IMG_OUTPUT}.zst" + msg "Compressed: ${IMG_OUTPUT}.zst" + else + info "Neither xz nor zstd found — leaving image uncompressed" + fi + fi + msg "Image build complete: ${IMG_OUTPUT}$([[ "$IMG_COMPRESS" = "1" ]] && echo ".xz")" +fi +# ─── end image-output finalization ────────────────────────────────────────────