#!/bin/bash # # Copyright (C) 2022 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # set -e set -u SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) usage() { echo "usage: $0 [-h] -i input.raw -o output.iso" exit 1 } input= output= while getopts ":hi:o:" opt; do case "${opt}" in h) usage ;; i) input="${OPTARG}" ;; o) output="${OPTARG}" ;; \?) echo "Invalid option: ${OPTARG}" >&2 usage ;; :) echo "Invalid option: ${OPTARG} requires an argument" >&2 usage ;; esac done if [[ -z "${input}" ]]; then echo "Must specify input file!" usage fi if [[ -z "${output}" ]]; then echo "Must specify output file!" usage fi grub_cmdline="ro net.ifnames=0 console=ttyAMA0 loglevel=4" grub_rootfs="LABEL=install" # Validate format of the input disk /sbin/sgdisk -p "${input}" | grep -q "Disk identifier (GUID)" || \ ( echo "${input} is not a GUID partitioned disk!" && exit 2 ) partitions="$(/sbin/sgdisk -p "${input}" | \ grep -m1 -A2 "Number Start (sector)" | tail -n2)" ( IFS=$'\n' for line in $partitions; do IFS=' ' read -r -a partition <<< "$line" if [[ "${partition[0]}" = "1" && "${partition[5]}" != "EF00" ]]; then echo "${input} partition 1 is not an ESP!" && exit 3 fi if [[ "${partition[0]}" = "2" && "${partition[6]}" != "rootfs" ]]; then echo "${input} partition 2 is not rootfs!" && exit 4 fi done ) failure() { echo "ISO generation process failed." >&2 rm -f "${output}" } trap failure ERR mount=$(mktemp -d) mount_remove() { rmdir "${mount}" } trap mount_remove EXIT workdir=$(mktemp -d) workdir_remove() { rm -rf "${workdir}" mount_remove } trap workdir_remove EXIT # Build a grub.cfg for CD booting cat >"${workdir}"/grub.cfg <"${workdir}"/install.sh << EOF #!/bin/sh set -e set -u SCRIPT_DIR=\$(CDPATH= cd -- "\$(dirname -- "\${0}")" && pwd -P) if [ "\${1#*nvme}" != "\${1}" ]; then partition=p else partition= fi sgdisk --load-backup="\${SCRIPT_DIR}"/gpt.img \${1} sgdisk --delete=2 \${1} sgdisk --new=2:129M:0 --typecode=2:8305 --change-name=2:rootfs --attributes=2:set:2 \${1} partx -v --update \${1} dd if="\${SCRIPT_DIR}"/esp.img of=\${1}\${partition}1 bs=16M mkfs.ext4 -L ROOT -U \$(cat \${SCRIPT_DIR}/rootfs_uuid) \${1}\${partition}2 mount \${1}\${partition}2 /media tar -C /media -Spxf \${SCRIPT_DIR}/rootfs.tar.xz umount /media EOF chmod a+x "${workdir}"/install.sh cat >"${workdir}"/override-getty.conf <"${workdir}"/override-serial-getty.conf </dev/null efi_partition_num=1 efi_partition_start=$(partx -g -o START -s -n ${efi_partition_num} "${input}" | xargs) efi_partition_end=$(partx -g -o END -s -n ${efi_partition_num} "${input}" | xargs) efi_partition_offset=$((${efi_partition_start} * 512)) efi_partition_num_sectors=$((${efi_partition_end} - ${efi_partition_start} + 1)) # Back up the ESP so we can restore it when installing touch "${workdir}"/esp.img dd if="${input}" of="${workdir}"/esp.img bs=512 skip=${efi_partition_start} count=${efi_partition_num_sectors} status=none >/dev/null # Determine the architecture of the disk from the portable GRUB image path sudo mount -o loop,offset=${efi_partition_offset} "${input}" "${mount}" unmount() { sudo umount "${mount}" } trap unmount EXIT grub_blob=$(cd "${mount}" && echo EFI/Boot/*) case "${grub_blob}" in EFI/Boot/BOOTAA64.EFI) grub_arch=arm64-efi grub_cd=gcdaa64.efi ;; EFI/Boot/BOOTIA64.EFI) grub_arch=x86_64-efi grub_cd=gcdx64.efi ;; *) echo "Unknown GRUB architecture for ${grub_blob}!" exit 5 ;; esac sudo umount "${mount}" root_partition_num=2 root_partition_start=$(partx -g -o START -s -n ${root_partition_num} "${input}" | xargs) root_partition_end=$(partx -g -o END -s -n ${root_partition_num} "${input}" | xargs) root_partition_offset=$((${root_partition_start} * 512)) # Mount original rootfs and remove previous patching, then tar rootfs_uuid=$(/sbin/blkid --probe -s UUID -o value -O ${root_partition_offset} "${input}") sudo mount -o loop,offset=${root_partition_offset} "${input}" "${mount}" trap unmount EXIT sudo rm -f "${mount}"/root/esp.img "${mount}"/root/gpt.img sudo rm -f "${mount}"/root/rootfs.tar.xz sudo rm -f "${mount}"/root/rootfs_uuid sudo rm -f "${mount}"/boot/grub/eltorito.img sudo rm -f "${mount}"/boot/grub/${grub_arch}/grub.cfg sudo rm -rf "${mount}"/tmp/* sudo rm -rf "${mount}"/var/tmp/* ( cd "${mount}" && sudo tar -SJcpf "${workdir}"/rootfs.tar.xz * ) # Prepare a new ESP for the ISO's El Torito image mkdir -p "${workdir}/EFI/Boot" cp "${mount}/usr/lib/grub/${grub_arch}/monolithic/${grub_cd}" \ "${workdir}/${grub_blob}" truncate -s 4M "${workdir}"/eltorito.img /sbin/mkfs.msdos -n SYSTEM -F 12 -M 0xf8 -h 0 -s 4 -g 64/32 -S 512 \ "${workdir}"/eltorito.img >/dev/null mmd -i "${workdir}"/eltorito.img EFI EFI/Boot mcopy -o -i "${workdir}"/eltorito.img -s "${workdir}/EFI" :: # Build ISO from rootfs sudo cp "${workdir}"/esp.img "${workdir}"/gpt.img "${mount}"/root sudo cp "${workdir}"/rootfs.tar.xz "${workdir}"/install.sh "${mount}"/root echo -n "${rootfs_uuid}" | sudo tee "${mount}"/root/rootfs_uuid >/dev/null sudo cp "${workdir}"/eltorito.img "${mount}"/boot/grub sudo cp "${workdir}"/grub.cfg "${mount}"/boot/grub/${grub_arch}/grub.cfg sudo mkdir -p "${mount}"/etc/systemd/system/getty@tty1.service.d sudo cp "${workdir}"/override-getty.conf "${mount}"/etc/systemd/system/getty@tty1.service.d/override.conf sudo mkdir -p "${mount}"/etc/systemd/system/serial-getty@ttyAMA0.service.d sudo cp "${workdir}"/override-serial-getty.conf "${mount}"/etc/systemd/system/serial-getty@ttyAMA0.service.d/override.conf sudo chown root:root \ "${mount}"/root/esp.img "${mount}"/root/gpt.img \ "${mount}"/boot/grub/eltorito.img \ "${mount}"/boot/grub/${grub_arch}/grub.cfg sudo mv "${mount}"/usr "${mount}"/usr_o sudo mkzftree "${mount}"/usr_o "${mount}"/usr sudo rm -rf "${mount}"/usr_o sudo mv "${mount}"/root "${mount}"/root_o sudo mkzftree "${mount}"/root_o "${mount}"/root sudo rm -rf "${mount}"/root_o sudo mv "${mount}"/var "${mount}"/var_o sudo mkzftree "${mount}"/var_o "${mount}"/var sudo rm -rf "${mount}"/var_o rm -f "${output}" touch "${output}" sudo xorriso \ -as mkisofs -r -checksum_algorithm_iso sha256,sha512 -V install "${mount}" \ -o "${output}" -e boot/grub/eltorito.img -no-emul-boot \ -append_partition 2 0xef "${workdir}"/eltorito.img \ -z \ -partition_cyl_align all echo "Output ISO generated at '${output}'."