diff --git a/builder/build-iso.sh b/builder/build-iso.sh index db8daa2c..41e9e567 100755 --- a/builder/build-iso.sh +++ b/builder/build-iso.sh @@ -5,6 +5,21 @@ set -e OMARCHY_ISO_REF="${OMARCHY_ISO_REF:-quattro}" OMARCHY_MIRROR="${OMARCHY_MIRROR:-stable}" +# Architecture-dependent selections. The build targets the host architecture; +# everything below derives from these rather than hardcoding one of them. +case "$(uname -m)" in + aarch64) + ISO_ARCH=aarch64 + ISO_NODE_ARCH=arm64 + ISO_KERNEL=linux-aarch64 + ;; + *) + ISO_ARCH=x86_64 + ISO_NODE_ARCH=x64 + ISO_KERNEL=linux-t2 + ;; +esac + # Edge, dev, and local-source ISOs install the dev packages explicitly. Those # package recipes track the quattro branch. This avoids relying on pacman's # provides=omarchy resolution and shows the real package names being tested in @@ -30,7 +45,46 @@ pacman --noconfirm -Sy archlinux-keyring # so this container can be months behind the mirror it installs from. A plain # -Sy install is then a partial upgrade — new packages linked against a glibc # the container doesn't have yet. -pacman --noconfirm -Syu archiso git sudo base-devel jq grub imagemagick neovim nodejs npm tree-sitter-cli +pacman --noconfirm -Syu git sudo base-devel jq grub imagemagick neovim nodejs npm tree-sitter-cli + +# Prefer the archiso package. Not every repository carries it -- Arch Linux ARM +# has no archiso -- so fall back to the vendored submodule, which is the same +# source the package is built from. +if ! pacman --noconfirm -S --needed archiso; then + echo "archiso package unavailable; installing from the vendored submodule" + # The package would have pulled these in; installing from source does not: + # mksquashfs, mkfs.fat, mmd/mcopy, xorriso, mkfs.erofs, pacstrap. + pacman --noconfirm -S --needed \ + squashfs-tools dosfstools mtools libisoburn erofs-utils arch-install-scripts e2fsprogs + # /archiso is mounted read-only, so build out of a copy. scripts+profiles only: + # the default install target runs rst2man (python-docutils) for man pages, + # which this build has no use for. + cp -r /archiso /tmp/archiso-src + + # archiso hardcodes a GRUB module list taken from an x86 bug report, and + # at_keyboard (PS/2), keylayouts, usb and the usbserial_* drivers are not + # built for arm64-efi, so grub-mkstandalone aborts on the first one it cannot + # find. Filter the list to what the target platform actually provides. + # + # This belongs upstream in archiso rather than here; carried against the + # vendored copy until it lands there, and applied with --forward so it becomes + # a no-op once the submodule is bumped past it. + patch -d /tmp/archiso-src -p1 --forward --batch \ + &2 + echo "built for arm64-efi (at_keyboard, keylayouts, usb, usbserial_*), so" >&2 + echo "grub-mkstandalone would abort. Apply builder/patches/archiso-grubmodules.patch" >&2 + echo "or use an archiso that already filters the list." >&2 + exit 1 +fi # Pre-import the omarchy signing key (so pacman trusts our [omarchy] repo # during the build without keyserver lookups). @@ -38,18 +92,81 @@ pacman-key --add /builder/omarchy.gpg pacman-key --lsign-key 40DFB630FF42BCFFB047046CF0134EE680CAC571 # omarchy-keyring is needed inside the offline mirror too. -pacman --config /configs/pacman-online-${OMARCHY_MIRROR}.conf --noconfirm -Sy omarchy-keyring +# The checked-in pacman configs describe an x86_64 build. Rather than edit them, +# stage a filtered copy for aarch64 and use that; x86_64 keeps reading the +# originals untouched. +# +# [multilib] 32-bit x86 libraries. No ARM mirror carries it. +# [arch-mact2] an Apple T2 repository, x86 by definition. +# core/extra the explicit Server line points at Omarchy's mirror of Arch, +# which is x86_64-only. Both sections already Include the +# mirrorlist, so dropping the line is enough. Substituting +# another URL would not work: Arch Linux ARM lays its tree out +# as $arch/$repo rather than Arch's $repo/os/$arch. +PACMAN_ONLINE_CONF="/configs/pacman-online-${OMARCHY_MIRROR}.conf" +if [[ $ISO_ARCH == aarch64 ]]; then + PACMAN_ONLINE_CONF="/tmp/pacman-online-${OMARCHY_MIRROR}.conf" + awk ' + /^\[multilib\]$/ { skip = 1; next } + /^\[arch-mact2\]$/ { skip = 1; next } + /^\[/ { skip = 0; section = $0 } + skip { next } + (section == "[core]" || section == "[extra]") && /^Server[[:space:]]*=/ { next } + { print } + ' "/configs/pacman-online-${OMARCHY_MIRROR}.conf" > "$PACMAN_ONLINE_CONF" + echo "aarch64: staged $PACMAN_ONLINE_CONF without [multilib]/[arch-mact2]" +fi + +pacman --config $PACMAN_ONLINE_CONF --noconfirm -Sy omarchy-keyring pacman-key --populate omarchy # Append the [omarchy] repo to the container's /etc/pacman.conf so subsequent # tools (notably makepkg in build-omarchy-packages.sh) can resolve omarchy- # only build deps like limine-snapper-sync and limine-mkinitcpio-hook. if ! grep -q '^\[omarchy\]' /etc/pacman.conf; then - awk '/^\[omarchy\]/,/^$/' /configs/pacman-online-${OMARCHY_MIRROR}.conf >> /etc/pacman.conf + awk '/^\[omarchy\]/,/^$/' $PACMAN_ONLINE_CONF >> /etc/pacman.conf fi # Build locations build_cache_dir=/var/cache + +# ---------------------------------------------------------------- aarch64 +# Packages in the lists above that do not exist for aarch64. pacman -Syw aborts +# the entire transaction on the first "target not found", so these must be +# removed rather than tolerated. +# +# Grouped by *why*, because "not on ARM" covers three very different cases and +# only the third is a gap worth closing. +OMARCHY_ARCH_DROP=( + # 1. x86/x86_64 platform hardware. Meaningless on ARM, not a gap. + amd-ucode intel-ucode + apple-bcm-firmware apple-t2-audio-config t2fanrd linux-t2 linux-t2-headers + macbook12-spi-driver-dkms macbook8-spi-pxa2xx-nodma-dkms + asusctl supergfxctl rog-control-center + dell-xps-touchpad-haptics tuxedo-drivers-nocompatcheck-dkms + intel-ipu7-camera intel-lpmd intel-media-driver libva-intel-driver + thermald linux-ptl linux-ptl-headers vpl-gpu-rt libvpl + vulkan-intel vulkan-radeon + nvidia-dkms nvidia-open-dkms nvidia-utils nvidia-580xx-dkms nvidia-580xx-utils + libva-nvidia-driver lib32-nvidia-utils lib32-nvidia-580xx-utils + broadcom-wl yt6801-dkms qmk-hid xpadneo-dkms + edk2-shell memtest86+ memtest86+-efi syslinux refind + + # 2. x86-only virtualisation guest tooling. + hyperv open-vm-tools virtualbox-guest-utils-nox qemu-user-static-binfmt + + # 3. Software with no aarch64 build. Unlike the categories above, these are + # real gaps rather than things that are meaningless on ARM. + obs-studio obsidian pinta dotnet-runtime asdcontrol + + # 4. Build artefacts / not carried by ALARM. + yay-debug reflector +) + +# quickshell-git is a git snapshot of 0.3.0; ALARM ships a tagged quickshell +# 0.3.1, which is newer and satisfies the same dependency. +OMARCHY_ARCH_SUBST_FROM=(quickshell-git mise) +OMARCHY_ARCH_SUBST_TO=(quickshell mise-bin) offline_mirror_dir="$build_cache_dir/airootfs/var/cache/omarchy/mirror/offline" mkdir -p "$build_cache_dir" "$offline_mirror_dir" @@ -64,6 +181,40 @@ rm -rf "$build_cache_dir/airootfs/etc/xdg/reflector" # Bring in our archiso profile additions. cp -r /configs/* "$build_cache_dir/" + +# The GRUB configs name the kernel and initramfs they boot. That name follows the +# live kernel package, so substitute it here rather than keeping a second copy of +# each file. Both grub.cfg and loopback.cfg carry those names -- loopback.cfg is +# what boots the ISO from a file on disk, and missing it leaves that path +# requesting a kernel the ISO does not contain. On x86_64 ISO_KERNEL is linux-t2 +# and this is a no-op. +for _grub_cfg in "$build_cache_dir"/grub/*.cfg; do + [[ -e $_grub_cfg ]] || continue + sed -i \ + -e "s|vmlinuz-linux-t2|vmlinuz-${ISO_KERNEL}|g" \ + -e "s|initramfs-linux-t2\\.img|initramfs-${ISO_KERNEL}.img|g" \ + "$_grub_cfg" +done + +# Live-ISO mkinitcpio overrides that only make sense on aarch64. They are kept +# out of configs/airootfs/ so an x86_64 build never ships them, and staged into +# the airootfs here instead. They must exist before pacstrap runs mkinitcpio's +# pacman hook, which is why customize_airootfs.sh cannot handle those two. +if [[ $ISO_ARCH == aarch64 ]]; then + install -Dm644 /configs/aarch64/zz-aarch64-live.conf \ + "$build_cache_dir/airootfs/etc/mkinitcpio.conf.d/zz-aarch64-live.conf" + install -Dm644 /configs/aarch64/linux.preset \ + "$build_cache_dir/airootfs/etc/mkinitcpio.d/linux.preset" + install -Dm755 /configs/aarch64/customize_airootfs.sh \ + "$build_cache_dir/airootfs/root/customize_airootfs.sh" + # The T2 preset has no kernel image on this architecture, and pacstrap's + # mkinitcpio hook would fail on it before customize_airootfs.sh can run: + # ERROR: Invalid option -k -- '/boot/vmlinuz-linux-t2' must be readable + rm -f "$build_cache_dir/airootfs/etc/mkinitcpio.d/linux-t2.preset" + echo "aarch64: staged live-ISO mkinitcpio overrides" +fi +# configs/aarch64/ is a staging directory, not part of the airootfs. +rm -rf "$build_cache_dir/aarch64" mkdir -p "$build_cache_dir/airootfs/usr/share/omarchy-iso" echo "$OMARCHY_MIRROR" > "$build_cache_dir/airootfs/root/omarchy_mirror" echo "$OMARCHY_ISO_REF" > "$build_cache_dir/airootfs/root/omarchy_iso_ref" @@ -107,8 +258,8 @@ fi # Node.js binary for offline mise install. NODE_DIST_URL="https://nodejs.org/dist/latest" NODE_SHASUMS=$(curl -fsSL "$NODE_DIST_URL/SHASUMS256.txt") -NODE_FILENAME=$(echo "$NODE_SHASUMS" | grep "linux-x64.tar.gz" | awk '{print $2}') -NODE_SHA=$(echo "$NODE_SHASUMS" | grep "linux-x64.tar.gz" | awk '{print $1}') +NODE_FILENAME=$(echo "$NODE_SHASUMS" | grep "linux-${ISO_NODE_ARCH}.tar.gz" | awk '{print $2}') +NODE_SHA=$(echo "$NODE_SHASUMS" | grep "linux-${ISO_NODE_ARCH}.tar.gz" | awk '{print $1}') curl -fsSL "$NODE_DIST_URL/$NODE_FILENAME" -o "/tmp/$NODE_FILENAME" echo "$NODE_SHA /tmp/$NODE_FILENAME" | sha256sum -c - mkdir -p "$build_cache_dir/airootfs/opt/packages/" @@ -118,8 +269,8 @@ cp "/tmp/$NODE_FILENAME" "$build_cache_dir/airootfs/opt/packages/" # The selected omarchy-settings package is needed here so its post_install hook # drops Omarchy's plymouthd.conf into /etc/plymouth before mkarchiso builds the # live initramfs. -arch_packages=(linux-t2 git gum jq openssl plymouth ttfx tzupdate omarchy-keyring "$OMARCHY_SETTINGS_PACKAGE" lvm2 cryptsetup parted) -printf '%s\n' "${arch_packages[@]}" >> "$build_cache_dir/packages.x86_64" +arch_packages=("$ISO_KERNEL" git gum jq openssl plymouth ttfx tzupdate omarchy-keyring "$OMARCHY_SETTINGS_PACKAGE" lvm2 cryptsetup parted) +printf '%s\n' "${arch_packages[@]}" >> "$build_cache_dir/packages.$ISO_ARCH" # The live ISO boots linux-t2 (see airootfs/etc/mkinitcpio.d/linux-t2.preset), so # stock linux is a second kernel nobody boots: ~147MB of ISO, plus its own archiso @@ -132,7 +283,10 @@ printf '%s\n' "${arch_packages[@]}" >> "$build_cache_dir/packages.x86_64" # install is entirely offline and the live environment needs no Wi-Fi driver. # # Anchored so linux-t2 and linux-firmware are untouched. -sed -i -E '/^(linux|broadcom-wl)$/d' "$build_cache_dir/packages.x86_64" +_drop_re='^(linux|broadcom-wl)$' +# ALARM's releng list also carries linux-firmware-marvell, which stock Arch does not. +[[ $ISO_ARCH == aarch64 ]] && _drop_re='^(linux|linux-firmware-marvell|broadcom-wl)$' +sed -i -E "/$_drop_re/d" "$build_cache_dir/packages.$ISO_ARCH" # Build the offline mirror: everything pacstrap might want during the target # install. With --local-source, the omarchy* packages we just built are @@ -147,8 +301,8 @@ else bootstrap_cache_dir=/tmp/omarchy-pkg-bootstrap rm -rf "$bootstrap_cache_dir" /tmp/offlinedb-bootstrap /tmp/omarchy-pkglists mkdir -p "$bootstrap_cache_dir" /tmp/offlinedb-bootstrap - pacman --config /configs/pacman-online-${OMARCHY_MIRROR}.conf --noconfirm -Syw "$OMARCHY_RUNTIME_PACKAGE" --cachedir "$bootstrap_cache_dir" --dbpath /tmp/offlinedb-bootstrap >/dev/null - omarchy_pkg=$(find "$bootstrap_cache_dir" -maxdepth 1 -type f -name "$OMARCHY_RUNTIME_PACKAGE-*.pkg.tar.zst" | sort | head -1) + pacman --config $PACMAN_ONLINE_CONF --noconfirm -Syw "$OMARCHY_RUNTIME_PACKAGE" --cachedir "$bootstrap_cache_dir" --dbpath /tmp/offlinedb-bootstrap >/dev/null + omarchy_pkg=$(find "$bootstrap_cache_dir" -maxdepth 1 -type f \( -name "$OMARCHY_RUNTIME_PACKAGE-*.pkg.tar.zst" -o -name "$OMARCHY_RUNTIME_PACKAGE-*.pkg.tar.xz" \) | sort | head -1) if [[ -z $omarchy_pkg ]]; then echo "ERROR: downloaded package for $OMARCHY_RUNTIME_PACKAGE not found in $bootstrap_cache_dir" >&2 exit 1 @@ -187,19 +341,82 @@ if [[ ! -f $setup_form ]]; then fi cp "$setup_form" "$build_cache_dir/airootfs/usr/share/omarchy-iso/setup-form.sh" +# ---------------------------------------------------------------- aarch64 +# Apply the same architecture filter to the lists this ISO ships. These are read +# both by resolve_expected_packages() below and by the installer at runtime, so +# leaving them unfiltered yields an ISO that builds cleanly and then fails +# partway through a real install. +filter_shipped_package_list() { + local file="$1" tmp + [[ -f $file ]] || return 0 + tmp="$(mktemp)" + local line kept=0 dropped=0 + while IFS= read -r line || [[ -n $line ]]; do + if [[ -z $line || $line == \#* ]]; then printf '%s\n' "$line" >>"$tmp"; continue; fi + local skip="" + for d in "${OMARCHY_ARCH_DROP[@]}"; do [[ $line == "$d" ]] && { skip=1; break; }; done + if [[ -n $skip ]]; then dropped=$((dropped+1)); continue; fi + for i in "${!OMARCHY_ARCH_SUBST_FROM[@]}"; do + [[ $line == "${OMARCHY_ARCH_SUBST_FROM[$i]}" ]] && line="${OMARCHY_ARCH_SUBST_TO[$i]}" + done + printf '%s\n' "$line" >>"$tmp"; kept=$((kept+1)) + done <"$file" + mv "$tmp" "$file" + echo "aarch64: $(basename "$file"): kept $kept, dropped $dropped" +} + +if [[ $(uname -m) == aarch64 ]]; then + ARCHINSTALL_PACKAGES="$build_cache_dir/builder/archinstall.packages" + # The live environment's own list, which mkarchiso pacstraps directly. Missing + # this one fails the build late, at "Failed to install packages to new root". + filter_shipped_package_list "$build_cache_dir/packages.$ISO_ARCH" + filter_shipped_package_list "$build_cache_dir/airootfs/usr/share/omarchy-iso/omarchy-base.packages" + filter_shipped_package_list "$build_cache_dir/airootfs/usr/share/omarchy-iso/omarchy-other.packages" + # archinstall.packages is read from /builder (read-only mount); stage a copy + # so the filtered version is what gets resolved. + mkdir -p "$build_cache_dir/builder" + cp /builder/archinstall.packages "$build_cache_dir/builder/archinstall.packages" + filter_shipped_package_list "$build_cache_dir/builder/archinstall.packages" +fi + # Collect every package we want available in the offline mirror. declare -a all_packages mapfile -t all_packages < <( { - cat "$build_cache_dir/packages.x86_64" + # mkarchiso allows comments and blank lines in packages.$arch, so strip them + # the same way the lists below are read. releng's own list carries none, + # which is why a bare cat worked until this profile shipped a commented one. + grep -hv '^#\|^$' "$build_cache_dir/packages.$ISO_ARCH" grep -hv '^#\|^$' "${base_pkg_lists[@]}" - grep -hv '^#\|^$' /builder/archinstall.packages + grep -hv '^#\|^$' "${ARCHINSTALL_PACKAGES:-/builder/archinstall.packages}" # Always include the selected Omarchy packages so the target install can # find the runtime and companion packages in the offline mirror. printf '%s\n' "$OMARCHY_RUNTIME_PACKAGE" "$OMARCHY_SETTINGS_PACKAGE" "$OMARCHY_NVIM_PACKAGE" } | sort -u ) +if [[ $(uname -m) == aarch64 ]]; then + declare -a _kept=() _dropped=() + for _p in "${all_packages[@]}"; do + _skip="" + for _d in "${OMARCHY_ARCH_DROP[@]}"; do + [[ $_p == "$_d" ]] && { _skip=1; break; } + done + if [[ -n $_skip ]]; then _dropped+=("$_p"); continue; fi + for _i in "${!OMARCHY_ARCH_SUBST_FROM[@]}"; do + if [[ $_p == "${OMARCHY_ARCH_SUBST_FROM[$_i]}" ]]; then + _p="${OMARCHY_ARCH_SUBST_TO[$_i]}" + echo "aarch64: substituting ${OMARCHY_ARCH_SUBST_FROM[$_i]} -> $_p" + fi + done + _kept+=("$_p") + done + mapfile -t all_packages < <(printf '%s\n' "${_kept[@]}" | sort -u) + echo "aarch64: dropped ${#_dropped[@]} package(s) with no aarch64 build:" + printf ' %s\n' "${_dropped[@]}" + echo "aarch64: ${#all_packages[@]} package(s) remain for the offline mirror" +fi + # With --local-source we already built these omarchy* packages directly into # the mirror; strip them from the pacman -Syw list so it doesn't try to fetch # the published versions on top. @@ -215,7 +432,7 @@ fi mkdir -p /tmp/offlinedb download_offline_packages() { - pacman --config /configs/pacman-online-${OMARCHY_MIRROR}.conf --noconfirm -Syw \ + pacman --config $PACMAN_ONLINE_CONF --noconfirm -Syw \ "${all_packages[@]}" --cachedir "$offline_mirror_dir/" --dbpath /tmp/offlinedb --needed } @@ -233,7 +450,7 @@ fi # newest version of every cached package name) removes packages that have left # the lists or dependency closure, such as an old Electron major version. if ! resolved_package_files="$( - pacman --config "/configs/pacman-online-${OMARCHY_MIRROR}.conf" --noconfirm \ + pacman --config "$PACMAN_ONLINE_CONF" --noconfirm \ --dbpath /tmp/offlinedb -S --print --print-format '%f' "${all_packages[@]}" )"; then echo "ERROR: could not resolve the package files required by the offline mirror" >&2 @@ -272,7 +489,19 @@ printf '%s\n' "${required_package_files[@]}" | # Rebuild the offline repo db from scratch so size/checksum/depends entries # always reflect only the package files selected for this build. rm -f "$offline_mirror_dir"/offline.db* "$offline_mirror_dir"/offline.files* -repo-add "$offline_mirror_dir/offline.db.tar.gz" "$offline_mirror_dir/"*.pkg.tar.zst +# Enumerate rather than glob inline: a mirror holding only one of the two +# formats leaves the other pattern unmatched, and bash would hand it to +# repo-add literally, which fails the build under set -e. +offline_repo_packages=() +for package_file in "$offline_mirror_dir/"*.pkg.tar.zst "$offline_mirror_dir/"*.pkg.tar.xz; do + [[ -e $package_file ]] || continue + offline_repo_packages+=("$package_file") +done +if (( ${#offline_repo_packages[@]} == 0 )); then + echo "ERROR: no package files found in $offline_mirror_dir" >&2 + exit 1 +fi +repo-add "$offline_mirror_dir/offline.db.tar.gz" "${offline_repo_packages[@]}" # mkarchiso expects the mirror at /var/cache/omarchy/mirror/offline inside the # container (the airootfs path); symlink rather than duplicate. @@ -299,7 +528,7 @@ resolve_expected_packages() { mapfile -t targets < <( { - grep -hv '^#\|^$' /builder/archinstall.packages + grep -hv '^#\|^$' "${ARCHINSTALL_PACKAGES:-/builder/archinstall.packages}" # Read the shipped copy, which is what _runtime_package_list reads at # install time, not the build-time source it came from. grep -hv '^#\|^$' \ diff --git a/builder/build-omarchy-packages.sh b/builder/build-omarchy-packages.sh index 5f7173fb..2f878a82 100755 --- a/builder/build-omarchy-packages.sh +++ b/builder/build-omarchy-packages.sh @@ -70,7 +70,8 @@ for pkg in "${packages[@]}"; do done mkdir -p "$offline_mirror_dir" -for package_file in "$work_dir"/*.pkg.tar.zst; do +for package_file in "$work_dir"/*.pkg.tar.zst "$work_dir"/*.pkg.tar.xz; do + [[ -e $package_file ]] || continue destination="$offline_mirror_dir/$(basename "$package_file")" # A cached signature belongs to the previously downloaded or locally built @@ -82,4 +83,4 @@ done echo echo "Built Omarchy packages, placed in $offline_mirror_dir:" -ls "$offline_mirror_dir"/omarchy*.pkg.tar.zst | sed 's|^| |' +ls "$offline_mirror_dir"/omarchy*.pkg.tar.{zst,xz} 2>/dev/null | sed 's|^| |' diff --git a/builder/patches/archiso-grubmodules.patch b/builder/patches/archiso-grubmodules.patch new file mode 100644 index 00000000..2c838cb7 --- /dev/null +++ b/builder/patches/archiso-grubmodules.patch @@ -0,0 +1,49 @@ +# Filter archiso's hardcoded GRUB module list to what the target platform +# provides. at_keyboard (PS/2), keylayouts, usb and the usbserial_* drivers +# are not built for arm64-efi, so grub-mkstandalone aborts without this. +# +# Generated against the vendored archiso (v87). Belongs upstream in archiso; +# carried here until it lands there. build-iso.sh applies it with --forward, +# so it becomes a no-op once the submodule is bumped past the fix. +--- a/archiso/mkarchiso ++++ b/archiso/mkarchiso +@@ -669,6 +669,31 @@ + fi + } + ++# Filter a GRUB module list down to what a platform actually provides. ++# Not every module archiso asks for is built everywhere: at_keyboard is PS/2 and ++# the usbserial_* drivers are x86-oriented, so none of them exist for arm64-efi ++# and grub-mkstandalone aborts on the first one it cannot find. ++# $1: GRUB target (e.g. arm64-efi) ++# $@: module names ++# Prints the available modules, one per line. ++_filter_grubmodules() { ++ local _target="${1}" ++ shift ++ local _module _available=() _missing=() ++ for _module in "$@"; do ++ if [[ -e "/usr/lib/grub/${_target}/${_module}.mod" ]]; then ++ _available+=("${_module}") ++ else ++ _missing+=("${_module}") ++ fi ++ done ++ if (( ${#_missing[@]} )); then ++ # stdout carries the module list, so this has to go to stderr. ++ _msg_info "Skipping GRUB module(s) not built for ${_target}: ${_missing[*]}" >&2 ++ fi ++ printf '%s\n' "${_available[@]}" ++} ++ + # Prepare GRUB + _make_bootmode_uefi.grub() { + local grub_target grubmodules=() files_to_copy=() +@@ -691,6 +716,7 @@ + minicmd normal ntfs ntfscomp part_apple part_gpt part_msdos png read reboot regexp search \ + search_fs_file search_fs_uuid search_label serial sleep tpm udf usb usbserial_common usbserial_ftdi \ + usbserial_pl2303 usbserial_usbdebug video xfs zstd) ++ mapfile -t grubmodules < <(_filter_grubmodules "$grub_target" "${grubmodules[@]}") + grub-mkstandalone -O "$grub_target" \ + --modules="${grubmodules[*]}" \ + --locales="en@quot" \ diff --git a/configs/aarch64/customize_airootfs.sh b/configs/aarch64/customize_airootfs.sh new file mode 100644 index 00000000..ff0765de --- /dev/null +++ b/configs/aarch64/customize_airootfs.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# Runs inside the live-ISO chroot after packages are installed. +# +# WHAT THIS IS FOR +# ---------------- +# Arch Linux ARM's kernel packages name things differently from Arch's, and +# archiso and GRUB both look for the Arch names. This reconciles the two. +# +# It is deliberately *not* responsible for generating the initramfs. On Arch +# that happens by itself: the kernel package installs +# usr/lib/modules//{vmlinuz,pkgbase}, 90-mkinitcpio-install.hook triggers +# on the former, and /usr/share/libalpm/scripts/mkinitcpio reads the latter to +# learn the kernel's name. ALARM installs neither file, so the hook never fires +# -- proposed upstream as archlinuxarm/PKGBUILDs#2215. With that applied the +# hook does run, but two naming gaps remain and are what this script closes: +# +# * ALARM's preset is PRESETS=('default') writing /boot/initramfs-linux.img, +# while the GRUB entries load initramfs-linux-aarch64.img. +# * Nothing creates /boot/vmlinuz-*, and mkarchiso copies the kernel with +# `install -- "${pacstrap_dir}/boot/vmlinuz-"*`, which would match nothing. +# +# It also sets archiso_config in the preset, which is how x86's linux-t2.preset +# keeps an installed system's HOOKS out of the live initramfs. +# +# NOTE: mkarchiso prints a deprecation warning for customize_airootfs.sh. There +# is currently no replacement hook that runs inside the chroot after packages +# install, which is required here because the kernel image only exists then. +# Shipping the preset in airootfs/ does not work either: the overlay is copied +# before pacstrap, so linux-aarch64's own preset overwrites it. +set -uo pipefail + +# Everything here addresses differences in Arch Linux ARM's kernel packaging. +# On any other architecture this script is a no-op. +if [[ $(uname -m) != aarch64 ]]; then + exit 0 +fi + +# Omarchy forces the thunderbolt module; ALARM's aarch64 kernel does not build +# it, and mkinitcpio treats an unresolvable MODULES entry as an error. +if [[ -f /etc/mkinitcpio.conf.d/thunderbolt_module.conf ]]; then + sed -i 's/^MODULES+=(thunderbolt)/#MODULES+=(thunderbolt) # not built for aarch64/' \ + /etc/mkinitcpio.conf.d/thunderbolt_module.conf +fi + +# ALARM installs the kernel as /boot/Image. mkarchiso copies /boot/vmlinuz-* +# into the ISO, so without this name the kernel never reaches the image. +if [[ ! -e /boot/vmlinuz-linux-aarch64 ]]; then + if [[ -e /boot/Image ]]; then + cp -a /boot/Image /boot/vmlinuz-linux-aarch64 + else + echo "customize_airootfs: no kernel image at /boot/Image or /boot/vmlinuz-linux-aarch64" >&2 + exit 1 + fi +fi + +# Drop presets whose kernel image is absent: `mkinitcpio -P` aborts on them. +for preset in /etc/mkinitcpio.d/*.preset; do + [[ -e $preset ]] || continue + kname="$(basename "$preset" .preset)" + if [[ $kname != linux-aarch64 && ! -e /boot/vmlinuz-$kname ]]; then + echo "customize_airootfs: dropping $kname.preset (no kernel image)" + rm -f "$preset" + fi +done + +# Replace ALARM's preset, which writes /boot/initramfs-linux.img, with one that +# writes the name the GRUB entries load. archiso_config additionally makes +# mkinitcpio read archiso.conf as its configuration, bypassing +# /etc/mkinitcpio.conf.d entirely -- the same trick x86's linux-t2.preset uses +# to keep omarchy_hooks.conf, which describes an *installed* system, out of the +# *live* initramfs. +cat > /etc/mkinitcpio.d/linux-aarch64.preset <<'PRESET' +# Live-ISO preset for Arch Linux ARM's linux-aarch64. +PRESETS=('archiso') + +ALL_kver='/boot/vmlinuz-linux-aarch64' +archiso_config='/etc/mkinitcpio.conf.d/archiso.conf' + +archiso_image="/boot/initramfs-linux-aarch64.img" +PRESET + +echo "customize_airootfs: building the live initramfs from archiso.conf" +rm -f /boot/initramfs-linux*.img + +# mkinitcpio exits non-zero on this platform even when it produces a complete +# image: archiso's memdisk hook wants the phram module and the memdiskfind +# binary, and neither exists for aarch64 (memdiskfind ships in syslinux, which +# is x86-only). Judge the result on the artefact rather than the exit status. +mkinitcpio -p linux-aarch64 || \ + echo "customize_airootfs: mkinitcpio reported errors; checking for the image" + +# The GRUB entries boot this file by name. If it is missing the ISO builds +# cleanly and then fails to boot, so fail the build here instead. +if [[ ! -s /boot/initramfs-linux-aarch64.img ]]; then + echo "customize_airootfs: no live initramfs was produced; the ISO would not boot" >&2 + exit 1 +fi +echo "customize_airootfs: initramfs present ($(stat -c %s /boot/initramfs-linux-aarch64.img) bytes)" + +exit 0 diff --git a/configs/aarch64/linux.preset b/configs/aarch64/linux.preset new file mode 100644 index 00000000..188d28ec --- /dev/null +++ b/configs/aarch64/linux.preset @@ -0,0 +1,5 @@ +# The live ISO boots linux-aarch64 (/boot/Image). A stock linux.preset points at +# /boot/vmlinuz-linux, which does not exist here, and `mkinitcpio -P` fails with +# ERROR: Invalid option -k -- '/boot/vmlinuz-linux' must be readable +# An empty PRESETS array makes this preset a no-op. +PRESETS=() diff --git a/configs/aarch64/zz-aarch64-live.conf b/configs/aarch64/zz-aarch64-live.conf new file mode 100644 index 00000000..1933f073 --- /dev/null +++ b/configs/aarch64/zz-aarch64-live.conf @@ -0,0 +1,32 @@ +# Live-ISO overrides. Sorts last in /etc/mkinitcpio.conf.d, so it has the final +# word, and is unowned by any package so pacman cannot clobber it. +# +# WHY THIS EXISTS +# --------------- +# archiso.conf sets the HOOKS needed to boot a live ISO: +# base udev microcode modconf kms memdisk archiso archiso_loop_mnt +# archiso_pxe_* block filesystems keyboard +# omarchy-settings then ships omarchy_hooks.conf, which sets the HOOKS for an +# *installed* system (autodetect, encrypt, fsck, btrfs-overlayfs...). +# +# "omarchy_hooks.conf" sorts after "archiso.conf", so it wins -- and the live +# initramfs ends up with no `archiso` hook. The ISO then boots the kernel and +# panics in the initramfs: +# ERROR: Failed to mount '' on real root +# Restore the live hooks, keeping Omarchy's plymouth (the ISO installs +# omarchy-settings specifically so its plymouthd.conf lands before mkinitcpio). +# +# `microcode` is omitted: on aarch64 that hook only emits +# WARNING: architecture 'aarch64' not supported, skipping hook +HOOKS=(base udev plymouth modconf kms memdisk archiso archiso_loop_mnt + archiso_pxe_common archiso_pxe_nbd archiso_pxe_http archiso_pxe_nfs + block filesystems keyboard) + +# thunderbolt is x86-oriented hardware; ALARM's aarch64 kernel does not build +# the module, and a missing MODULES entry is fatal to mkinitcpio. +_keep=() +for _m in "${MODULES[@]:-}"; do + [[ $_m == "thunderbolt" || -z $_m ]] || _keep+=("$_m") +done +MODULES=("${_keep[@]}") +unset _keep _m diff --git a/configs/airootfs/root/.automated_script.sh b/configs/airootfs/root/.automated_script.sh index 16904f87..b8823b80 100644 --- a/configs/airootfs/root/.automated_script.sh +++ b/configs/airootfs/root/.automated_script.sh @@ -83,7 +83,7 @@ warm_offline_mirror() { ((spent_kb + size_kb > budget_kb)) && continue cat -- "$path" >/dev/null 2>&1 || true spent_kb=$((spent_kb + size_kb)) - done < <(du -k "$mirror"/*.pkg.tar.zst 2>/dev/null | sort -rn) + done < <(du -k "$mirror"/*.pkg.tar.zst "$mirror"/*.pkg.tar.xz 2>/dev/null | sort -rn) } warm_offline_mirror & diff --git a/configs/airootfs/root/configurator b/configs/airootfs/root/configurator index 4cd3db3d..3bb2d202 100644 --- a/configs/airootfs/root/configurator +++ b/configs/airootfs/root/configurator @@ -9,6 +9,32 @@ OMARCHY_SETTINGS_PACKAGE="${OMARCHY_SETTINGS_PACKAGE:-omarchy-settings}" LOGO_PATH="$OMARCHY_PATH/logo.txt" +# Limine installs BOOT.EFI as limine_.efi. The Arch package ships +# every architecture's binary, so the x64 name resolves on ARM too and the +# install silently registers an unbootable x86-64 loader. Derive it instead. +case "$(uname -m)" in + x86_64) limine_efi_binary="limine_x64.efi" ;; + i?86) limine_efi_binary="limine_ia32.efi" ;; + aarch64) limine_efi_binary="limine_aa64.efi" ;; + riscv64) limine_efi_binary="limine_riscv64.efi" ;; + loongarch64) limine_efi_binary="limine_loongarch64.efi" ;; + *) limine_efi_binary="limine_x64.efi" ;; +esac + +# archinstall prepends these to the target's mirrorlist. They are Arch x86_64 +# mirrors laid out as $repo/os/$arch; Arch Linux ARM uses $arch/$repo on +# different hosts, so on aarch64 every one of them 404s ahead of the mirror the +# distribution's own pacman-mirrorlist package installed. Leave that list alone +# there. +case "$(uname -m)" in + aarch64) mirror_custom_servers='[]' ;; + *) mirror_custom_servers='[ + {"url": "https://mirror.omarchy.org/$repo/os/$arch"}, + {"url": "https://mirror.rackspace.com/archlinux/$repo/os/$arch"}, + {"url": "https://geo.mirror.pkgbuild.com/$repo/os/$arch"} + ]' ;; +esac + # The setup form — keyboard, account, hostname, and timezone questions, plus the # rules their answers are checked against — shared verbatim with the first-boot # owner setup that finishes a deferred install. build-iso.sh vendors it out of @@ -412,6 +438,12 @@ to_gb() { # T2 Macs need their own kernel for keyboard/wifi drivers. detect_kernel() { + # Arch Linux ARM ships no bare `linux` package -- its kernel is + # `linux-aarch64`, and that is also the pkgbase every bootloader hook and + # validate_boot() looks for. T2 detection is x86-only by construction. + case "$(uname -m)" in + aarch64) echo "linux-aarch64"; return ;; + esac if lspci -nn 2>/dev/null | grep -q "106b:180[12]"; then echo "linux-t2" else @@ -805,7 +837,7 @@ run_partition_execute() { "boot": { "esp_mount": "$esp_mount_in_target", "esp_path": "/EFI/limine", - "efi_binary": "limine_x64.efi", + "efi_binary": "$limine_efi_binary", "enable_fallback": false }, "storage": { @@ -836,11 +868,7 @@ run_partition_execute() { }, "mirror_config": { "custom_repositories": [], - "custom_servers": [ - {"url": "https://mirror.omarchy.org/\$repo/os/\$arch"}, - {"url": "https://mirror.rackspace.com/archlinux/\$repo/os/\$arch"}, - {"url": "https://geo.mirror.pkgbuild.com/\$repo/os/\$arch"} - ], + "custom_servers": $mirror_custom_servers, "mirror_regions": {}, "optional_repositories": [] }, @@ -1144,7 +1172,7 @@ cat <<-_EOF_ >user_configuration.json "boot": { "esp_mount": "/boot", "esp_path": "/EFI/limine", - "efi_binary": "limine_x64.efi", + "efi_binary": "$limine_efi_binary", "enable_fallback": true }, "storage": { @@ -1225,11 +1253,7 @@ cat <<-_EOF_ >user_configuration.json }, "mirror_config": { "custom_repositories": [], - "custom_servers": [ - {"url": "https://mirror.omarchy.org/\$repo/os/\$arch"}, - {"url": "https://mirror.rackspace.com/archlinux/\$repo/os/\$arch"}, - {"url": "https://geo.mirror.pkgbuild.com/\$repo/os/\$arch"} - ], + "custom_servers": $mirror_custom_servers, "mirror_regions": {}, "optional_repositories": [] }, diff --git a/configs/airootfs/usr/share/omarchy-iso/orchestrator/context.py b/configs/airootfs/usr/share/omarchy-iso/orchestrator/context.py index 9b5c50ff..2e0b7ff7 100644 --- a/configs/airootfs/usr/share/omarchy-iso/orchestrator/context.py +++ b/configs/airootfs/usr/share/omarchy-iso/orchestrator/context.py @@ -6,11 +6,26 @@ import json import os +import platform import secrets from dataclasses import dataclass, field from pathlib import Path from typing import Any +# Limine ships BOOT{X64,IA32,AA64,RISCV64,LOONGARCH64}.EFI and the Arch package +# installs *all* of them, so hardcoding the x64 name does not fail on ARM -- it +# silently installs an x86-64 binary the firmware cannot execute. Derive the +# name instead, matching limine-common-functions' limine_efi_arch(). +_LIMINE_EFI_ARCH = { + "x86_64": "X64", + "i686": "IA32", + "aarch64": "AA64", + "riscv64": "RISCV64", + "loongarch64": "LOONGARCH64", +}.get(platform.machine(), "X64") +_LIMINE_SOURCE_EFI = f"BOOT{_LIMINE_EFI_ARCH}.EFI" +_LIMINE_EFI_BINARY = f"limine_{_LIMINE_EFI_ARCH.lower()}.efi" + @dataclass class InstallContext: @@ -184,7 +199,7 @@ def _default_omarchy_install(user_configuration: dict) -> dict[str, Any]: "boot": { "esp_mount": "/boot", "esp_path": "/EFI/limine", - "efi_binary": "limine_x64.efi", + "efi_binary": _LIMINE_EFI_BINARY, "enable_fallback": mode == "full_disk", }, "storage": {}, diff --git a/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py b/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py index 6d486245..16c7a06f 100644 --- a/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py +++ b/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py @@ -24,6 +24,7 @@ import hashlib import os +import platform import re import shutil import subprocess @@ -38,6 +39,20 @@ from .keyboard import configure_keyboard from .ui import error, info +# Limine ships BOOT{X64,IA32,AA64,RISCV64,LOONGARCH64}.EFI and the Arch package +# installs *all* of them, so hardcoding the x64 name does not fail on ARM -- it +# silently installs an x86-64 binary the firmware cannot execute. Derive the +# name instead, matching limine-common-functions' limine_efi_arch(). +_LIMINE_EFI_ARCH = { + "x86_64": "X64", + "i686": "IA32", + "aarch64": "AA64", + "riscv64": "RISCV64", + "loongarch64": "LOONGARCH64", +}.get(platform.machine(), "X64") +_LIMINE_SOURCE_EFI = f"BOOT{_LIMINE_EFI_ARCH}.EFI" +_LIMINE_EFI_BINARY = f"limine_{_LIMINE_EFI_ARCH.lower()}.efi" + # Package targets are written by builder/build-iso.sh. Stable ISOs use the # stable package names, while dev/local-source ISOs install the dev package @@ -387,7 +402,7 @@ def _install_pre_mounted_limine(ctx: InstallContext) -> None: disk=Path(disk), part=part, esp_path=boot.get("esp_path", "/EFI/limine"), - efi_binary=boot.get("efi_binary", "limine_x64.efi"), + efi_binary=boot.get("efi_binary", _LIMINE_EFI_BINARY), pre_state=pre_state, ) @@ -405,15 +420,15 @@ def _install_limine_efi( part: int, removable: bool = False, esp_path: str = "/EFI/limine", - efi_binary: str = "limine_x64.efi", + efi_binary: str = _LIMINE_EFI_BINARY, pre_state: dict | None = None, ) -> None: if removable: esp_path = "/EFI/BOOT" - efi_binary = "BOOTX64.EFI" + efi_binary = _LIMINE_SOURCE_EFI limine_path = ctx.target / "usr" / "share" / "limine" - source_name = "BOOTX64.EFI" + source_name = _LIMINE_SOURCE_EFI target_dir = Path(esp_mount) / esp_path.lstrip("/") target_path = target_dir / efi_binary _copy_required(limine_path / source_name, ctx.target / target_path.relative_to("/")) @@ -755,7 +770,7 @@ def _boot_intent(ctx: InstallContext) -> dict: boot = dict(ctx.omarchy_install.get("boot") or {}) boot.setdefault("esp_mount", "/boot") boot.setdefault("esp_path", "/EFI/limine") - boot.setdefault("efi_binary", "limine_x64.efi") + boot.setdefault("efi_binary", _LIMINE_EFI_BINARY) boot.setdefault("enable_fallback", not ctx.is_protected) return boot @@ -1160,6 +1175,10 @@ def run_system_finalizer(ctx: InstallContext) -> None: PROVISION_STATE_DIR = "var/lib/omarchy/provisioning" PROVISION_KEYFILE = "etc/omarchy/provisioning.key" NODE_PACKAGES_DIR = Path("/opt/packages") +# Node ships x64/arm64 builds under different filenames; the ISO bundles the +# one matching the target architecture. +_NODE_ARCH = {"x86_64": "x64", "aarch64": "arm64"}.get(platform.machine(), "x64") +NODE_TARBALL_GLOB = f"node-v*-linux-{_NODE_ARCH}.tar.gz" def stage_provisioning_state(ctx: InstallContext) -> None: @@ -1200,7 +1219,7 @@ def stage_provisioning_state(ctx: InstallContext) -> None: def _stage_node_tarball(ctx: InstallContext, provisioning_dir) -> None: - tarballs = sorted(NODE_PACKAGES_DIR.glob("node-v*-linux-x64.tar.gz")) + tarballs = sorted(NODE_PACKAGES_DIR.glob(NODE_TARBALL_GLOB)) if not tarballs: # Hard error on every install, not just deferred-provisioning installs: the stash is what lets a # later factory reset finalize the next owner offline, and an ISO @@ -1669,7 +1688,7 @@ def validate_boot(ctx: InstallContext) -> None: kernel = storage.get("kernel") or (ctx.user_configuration.get("kernels") or ["linux"])[0] if arch.has_uefi(): - limine_binary = esp_mount / boot.get("esp_path", "/EFI/limine").lstrip("/") / boot.get("efi_binary", "limine_x64.efi") + limine_binary = esp_mount / boot.get("esp_path", "/EFI/limine").lstrip("/") / boot.get("efi_binary", _LIMINE_EFI_BINARY) if not limine_binary.exists() or limine_binary.stat().st_size == 0: raise RuntimeError(f"{limine_binary} missing or empty") diff --git a/configs/packages.aarch64 b/configs/packages.aarch64 new file mode 100644 index 00000000..68f401ac --- /dev/null +++ b/configs/packages.aarch64 @@ -0,0 +1,128 @@ +# Package list for the aarch64 live environment. +# +# Derived from archiso's releng packages.x86_64, with entries that do not exist +# for aarch64 removed: x86 microcode and boot tooling (intel-ucode, syslinux, +# refind, memtest86+, edk2-shell), x86-only guest tooling (hyperv, open-vm-tools, +# virtualbox-guest-utils-nox), broadcom-wl (a prebuilt module for the stock +# kernel), and packages Arch Linux ARM does not carry (reflector, +# ghostty-terminfo). The kernel itself comes from ISO_KERNEL in build-iso.sh. +# +# mkarchiso reads packages.$arch from the profile directory, so this overrides +# the releng copy seeded earlier in the build. +alsa-utils +amd-ucode +arch-install-scripts +archinstall +b43-fwcutter +base +bcachefs-tools +bind +bolt +brltty +btrfs-progs +clonezilla +cloud-init +cryptsetup +darkhttpd +ddrescue +dhcpcd +diffutils +dmidecode +dmraid +dnsmasq +dosfstools +e2fsprogs +efibootmgr +espeakup +ethtool +exfatprogs +f2fs-tools +fatresize +foot-terminfo +fsarchiver +gpart +gpm +gptfdisk +grml-zsh-config +grub +hdparm +irssi +iw +iwd +jfsutils +kitty-terminfo +kmscon-terminfo +ldns +less +lftp +libfido2 +libusb-compat +linux-atm +linux-firmware +linux-firmware-marvell +livecd-sounds +lsscsi +lvm2 +lynx +man-db +man-pages +mc +mdadm +mkinitcpio +mkinitcpio-archiso +mkinitcpio-nfs-utils +mmc-utils +modemmanager +mtools +nano +nbd +ndisc6 +nfs-utils +nilfs-utils +nmap +ntfsprogs +nvme-cli +open-iscsi +openconnect +openpgp-card-tools +openssh +openvpn +partclone +parted +partimage +pcsclite +ppp +pptpclient +pv +qemu-guest-agent +rsync +rxvt-unicode-terminfo +screen +sdparm +sequoia-sq +sg3_utils +smartmontools +sof-firmware +squashfs-tools +sudo +systemd-resolvconf +tcpdump +terminus-font +testdisk +tmux +tpm2-tools +tpm2-tss +udftools +usb_modeswitch +usbmuxd +usbutils +vim +vpnc +wireless-regdb +wireless_tools +wpa_supplicant +wvdial +xdg-utils +xfsprogs +xl2tpd +zsh diff --git a/configs/profiledef.sh b/configs/profiledef.sh index d7d483cf..ae24da16 100644 --- a/configs/profiledef.sh +++ b/configs/profiledef.sh @@ -8,8 +8,17 @@ iso_application="Omarchy Installer" iso_version="$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)" install_dir="arch" buildmodes=('iso') -bootmodes=('bios.syslinux' 'uefi.grub') -arch="x86_64" +case "$(uname -m)" in + aarch64) + arch="aarch64" + # ARM has no legacy BIOS; syslinux is x86-only. + bootmodes=('uefi.grub') + ;; + *) + arch="x86_64" + bootmodes=('bios.syslinux' 'uefi.grub') + ;; +esac pacman_conf="pacman-offline.conf" airootfs_image_type="squashfs" # Package archives in the offline mirror are already zstd-compressed. Storing @@ -22,9 +31,20 @@ airootfs_image_type="squashfs" # cold on every boot: kernel, plymouth, systemd, python, archinstall, gum. The # whole ISO grows well under a percent for it, and dropping the x86 BCJ filter # also removes one of the blockers listed in plans/aarch64-support.md. +# Arch Linux ARM builds its kernel without CONFIG_SQUASHFS_ZSTD (ZLIB, LZ4 and +# XZ only), so a zstd airootfs builds correctly and then cannot be mounted by +# the very kernel on the ISO: +# mount: /run/archiso/airootfs: fsconfig() failed: +# Filesystem uses "zstd" compression. This is not supported. +# Arch's kernel does enable it, so zstd remains the default there and the +# measured rationale above is unaffected. +if [[ $arch == aarch64 ]]; then + _airootfs_comp=('-comp' 'xz' '-Xbcj' 'arm') +else + _airootfs_comp=('-comp' 'zstd' '-Xcompression-level' '19') +fi airootfs_image_tool_options=( - '-comp' 'zstd' - '-Xcompression-level' '19' + "${_airootfs_comp[@]}" '-b' '1M' '-action' 'uncompressed@subpathname(var/cache/omarchy/mirror/offline)' ) @@ -44,3 +64,10 @@ file_permissions=( ["/usr/local/bin/omarchy-upload-log"]="0:0:755" ["/var/cache/omarchy/mirror/offline/"]="0:0:775" ) + +# Staged into the airootfs by build-iso.sh on aarch64 only. +if [[ $arch == aarch64 ]]; then + file_permissions["/etc/mkinitcpio.conf.d/zz-aarch64-live.conf"]="0:0:644" + file_permissions["/etc/mkinitcpio.d/linux.preset"]="0:0:644" + file_permissions["/root/customize_airootfs.sh"]="0:0:755" +fi