diff --git a/README.md b/README.md index 7ed242e..4afe050 100644 --- a/README.md +++ b/README.md @@ -1,127 +1,43 @@ # Manjaro ARM Installer -Scripts for installing Manjaro ARM directly to SD/eMMC cards or USB sticks without the need for images. +Fork of [upstream](https://gitlab.manjaro.org/manjaro-arm/applications/manjaro-arm-installer) with `--output-image` mode added — builds to a sparse `.img` file via loop device instead of writing to a real block device. With no flags, behaves identically to upstream. -This script is "interactive". Meaning that it asks you questions when run to customize your install. Like username, password etc. +## Usage +```bash +# build to an image file (12 GB) +sudo ./manjaro-arm-installer --output-image ./manjaro.img --image-size 12G -## Dependencies (Arch package names): -* bash -* wget -* git -* systemd -* dialog -* parted -* libarchive -* qemu-user-static-binfmt (only needed when script is run from other architectures than `aarch64`) -* openssl -* gawk -* dosfstools -* polkit -* btrfs-progs (for btrfs filesystem support) -* f2fs-tools (for f2fs filesystem support) -* cryptsetup (for encryption support) -* grub-efi-arm64 (only needed when script is run from other arcitectures than `aarch64`) (from AUR) (for `generic-efi` support) +# env-var form (drop-in for scripts) +sudo IMG_OUTPUT=manjaro.img IMG_SIZE=12G ./manjaro-arm-installer -## Installing and using from Manjaro (x64 and ARM) repositories: -To use this script, please make sure that the following is correct: - -* An SD/eMMC card with at least 8 GB storage is plugged in, but not mounted. This Script **will** remove everything on it. -* That your user account has `sudo` rights. - -Then install the `manjaro-arm-installer` package with: -``` -sudo pacman -Syu manjaro-arm-installer -``` -Then reboot or run `sudo systemctl restart systemd-binfmt`. You can now launch the installer with: -``` -sudo bash manjaro-arm-installer +# compress after build (xz preferred, zstd fallback) +sudo ./manjaro-arm-installer --output-image ./manjaro.img --compress ``` +Default image size is `8G` if `--image-size` isn't passed. -## Installing and using from gitlab: -To use this script, please make sure that the following is correct: - -* An SD/eMMC card or USB stick with at least 8 GB storage is plugged in, but not mounted. This Script **will** remove everything on it. -* That your user account has `sudo` rights. -* That you have rebooted or restarted the binfmt service with `sudo systemctl restart systemd-binfmt` if you are running the script from any other architecture than `aarch64`. - -Then use this to get it: -``` -git clone https://gitlab.manjaro.org/manjaro-arm/applications/manjaro-arm-installer -cd manjaro-arm-installer -chmod +x manjaro-arm-installer -sudo bash ./manjaro-arm-installer (Use Default stable branch) -sudo bash ./manjaro-arm-installer arm-testing (Use testing branch) -sudo bash ./manjaro-arm-installer arm-unstable (Use unstable branch) -``` - -## Known Issues: -* Because `dialog` is weird, the script needs to be run in `bash`. - -## Supported Devices: -* ClockworkPi DevTerm -* Generic -* Generic EFI -* Beelink GT1 Ultimate -* Odroid C4 -* Odroid C2 -* Odroid HC4 -* Odroid M1 (new) -* Odroid N2 -* Odroid N2 -* Orange Pi 4 LTS (new) -* Pine64-LTS / Sopine -* Pine64+ -* Pinebook -* Pine H64 -* PinePhone -* PinePhone Pro (Experimental) -* Pinebook Pro -* PineTab -* Radxa Zero -* Raspberry Pi 5's/4's/3's/zero2w -* Rock 3A (new) -* Rock64 -* Rock Pi 4B -* Rock Pi 4C -* RockPro64 -* LibreComputer Renegade -* NanoPC T4 -* Quartz64 Model A -* Quartz64 Model B (new) -* Khadas Vim 1 -* Khadas Vim 2 -* Khadas Vim 3 - -## Supported Editions / Desktops: -* Minimal (no xorg, no apps) -* KDE/Plasma (full plasma desktop with apps) -* XFCE (full XFCE desktop with apps) -* i3 (tiling window manager with gtk apps) -* Sway (tiling wayland window manager with gtk apps) -* LXQT (full LXQT desktop with some qt apps) -* Mate (full mate desktop with apps) -* Server (minimal install with LAMP and Docker) -* Gnome (full Gnome desktop with apps) -* Budgie (full Budge desktop with apps) (EXPERIMENTAL) -* Phosh (A mobile interface for phones based on GTK/Gnome) -* Plasma Mobile (A mobile interface for phones based on QT/Plasma) - -## Other notes: -This script is available in the **Manjaro** repository and can be installed with `sudo pacman -S manjaro-arm-installer`. - -This script **should** be distro-agnostic, which means you can run the install script to install *Manjaro ARM* from **any** distro, as long as the dependencies are met. - -### Quirks Ubuntu -Ubuntu does not list keymaps the same way as Arch based systems. The script will not fail - but it will skip the console keymap selection thus defaulting to us console keymap. - -See https://www.claudiokuenzler.com/blog/1257/how-to-fix-missing-keymaps-debian-ubuntu-localectl-failed-read-list - -Ubuntu stores the qemu bin formats in different locations and this will make the script fail. As the reference is only a validation - it is possible to work around it by creating a symlink - - sudo ln -s /usr/lib/binfmt.d/qemu-aarch.conf /usr/lib/binfmt.d/qemu-aarch64-static.conf - +The interactive dialogs (edition, user, password, locale, etc.) run as normal. +## Dependencies +Same as upstream: `bash wget git systemd dialog parted libarchive openssl gawk dosfstools polkit`. Plus `binfmt-qemu-static` if running on x86_64 (reboot or `sudo systemctl restart systemd-binfmt` after install). `xz` or `zstd` only if using `--compress`. + diff --git a/manjaro-arm-installer b/manjaro-arm-installer old mode 100755 new mode 100644 index e07d45f..0f9ceec --- a/manjaro-arm-installer +++ b/manjaro-arm-installer @@ -48,6 +48,43 @@ LOCALE="" 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 @@ if [ ! -z "$CONFIRMROOTPASSWORD" ] 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 @@ WARNING! This WILL destroy the data on it!" 20 50 10 \ DEV_NAME=$SDCARD SDCARD=/dev/$SDCARD SDTYP=${SDCARD:5:2} + fi else clear exit 1 @@ -973,7 +1030,8 @@ fi 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 @@ create_install 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 ──────────────────────────────────────────── diff --git a/manjaro-arm-installer.patch b/manjaro-arm-installer.patch new file mode 100644 index 0000000..d4ef70b --- /dev/null +++ b/manjaro-arm-installer.patch @@ -0,0 +1,117 @@ +--- /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 ────────────────────────────────────────────