blob: 103bfa34a53783a001db3d2effe854d81ec4cbf8 [file] [log] [blame] [edit]
#!/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 <<EOF
set timeout=0
menuentry "Linux" {
linux /vmlinuz ${grub_cmdline} root=${grub_rootfs}
initrd /initrd.img
}
EOF
# Build harddisk install script
cat >"${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 <<EOF
[Service]
ExecStart=
ExecStart=-/sbin/agetty -a root --noclear tty1 \$TERM
EOF
cat >"${workdir}"/override-serial-getty.conf <<EOF
[Service]
ExecStart=
ExecStart=-/sbin/agetty -a root --keep-baud 115200,57600,38400,9600 ttyAMA0 \$TERM
EOF
# Back up the GPT so we can restore it when installing
/sbin/sgdisk --backup="${workdir}"/gpt.img "${input}" >/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}'."