diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 2606751e..5588144b 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -58,6 +58,18 @@ jobs: - ubuntu-latest - ubuntu-24.04-arm + target: + - VM + - RPI + + exclude: + # don't try to do raspi tests on amd64 + - runner: ubuntu-latest + target: RPI + # don't do raspi tests on <= bookworm hosts, needs at least QEMU 9.x + - host_release: bookworm + target: RPI + # We want a working shell, qemu, python and docker. Specific version should not matter (much). runs-on: ${{ matrix.runner }} @@ -73,22 +85,24 @@ jobs: name: "Setup test environment" - run: ./tests/build-vm-and-test.sh run - name: "Build VM image using grml-debootstrap on host ${{matrix.host_release}} for ${{matrix.release}}" + name: "Build ${{matrix.target}} image using grml-debootstrap on host ${{matrix.host_release}} for ${{matrix.release}}" env: HOST_RELEASE: ${{matrix.host_release}} RELEASE: ${{matrix.release}} + TARGET: ${{matrix.target}} - run: ./tests/build-vm-and-test.sh test id: build_vm_and_test_test - name: "Test built VM image for ${{matrix.release}}" + name: "Test built ${{matrix.target}} image for ${{matrix.release}}" env: RELEASE: ${{matrix.release}} + TARGET: ${{matrix.target}} - name: Archive VM image on failure uses: actions/upload-artifact@v6 if: always() && (steps.build_vm_and_test_test.outcome == 'failure') with: - name: vm-image-${{matrix.host_release}}-${{matrix.release}}-${{matrix.runner}} + name: vm-image-${{matrix.host_release}}-${{matrix.release}}-${{matrix.runner}}-${{matrix.target}} if-no-files-found: error path: qemu.img retention-days: 5 @@ -97,6 +111,6 @@ jobs: uses: actions/upload-artifact@v6 if: always() with: - name: vm-results-${{matrix.host_release}}-${{matrix.release}}-${{matrix.runner}} + name: vm-results-${{matrix.host_release}}-${{matrix.release}}-${{matrix.runner}}-${{matrix.target}} if-no-files-found: error path: tests/results/ diff --git a/chroot-script b/chroot-script index 9e3cc3d9..5c7475d2 100755 --- a/chroot-script +++ b/chroot-script @@ -36,6 +36,11 @@ bash -n /etc/debootstrap/variables [ -r /proc/1 ] || mount -t proc none /proc [ -r /sys/kernel ] || mount -t sysfs none /sys +if [ -n "$RPIFIRM" ]; then + mkdir --parents /boot/firmware + mount "$RPIFIRM" /boot/firmware +fi + # variable checks {{{ # use aptitude only if it's available @@ -368,6 +373,9 @@ kernel() { if expr "$COMPONENTS" : '.*non-free' >/dev/null ; then KERNELPACKAGES="$KERNELPACKAGES firmware-linux" fi + if [ -n "$RPI" ] ; then + KERNELPACKAGES="$KERNELPACKAGES raspi-firmware" + fi # shellcheck disable=SC2086 DEBIAN_FRONTEND=$DEBIAN_FRONTEND $APTINSTALL $KERNELPACKAGES else @@ -376,6 +384,19 @@ kernel() { } # }}} +# fix kernel command line for Raspberry Pi {{{ +rpi_cmdline() { + if [ -z "$RPI" ]; then + return 0 + fi + + local cmdline + cmdline="$(cat /boot/firmware/cmdline.txt)" + cmdline="$(sed "s/root=[^ ]*/root=UUID=${TARGET_UUID}/" <<< "$cmdline")" + echo "$cmdline" > /boot/firmware/cmdline.txt +} +# }}} + # reconfigure packages {{{ # shellcheck disable=SC2329 reconfigure() { @@ -522,6 +543,11 @@ if [ -n "$EFI" ] ; then echo "UUID=$UUID_EFI /boot/efi vfat umask=0077 0 1" >> /etc/fstab fi +if [ -n "$RPIFIRM" ] ; then + UUID_RPIFIRM="$(blkid -o value -s UUID "$RPIFIRM")" + echo "UUID=$UUID_RPIFIRM /boot/firmware vfat umask=0077 0 1" >> /etc/fstab +fi + cat >> /etc/fstab << EOF proc /proc proc defaults 0 0 /dev/cdrom /mnt/cdrom0 iso9660 ro,user,noauto 0 0 @@ -874,6 +900,8 @@ finalize() { umount /sys >/dev/null 2>/dev/null || true umount /proc >/dev/null 2>/dev/null || true + + umount /boot/firmware >/dev/null 2>/dev/null || true } # }}} @@ -899,7 +927,7 @@ trap signal_handler HUP INT QUIT TERM kernel packages extrapackages reconfigure hosts \ default_locales timezone fstab install_fs_tools hostname \ initrd grub_install passwords \ - custom_scripts upgrade_system remove_apt_cache services \ + custom_scripts upgrade_system rpi_cmdline remove_apt_cache services \ remove_chrootmirror; do if stage "$i" ; then "$i" diff --git a/grml-debootstrap b/grml-debootstrap index fd2f2c2a..a5f6c8c7 100755 --- a/grml-debootstrap +++ b/grml-debootstrap @@ -75,7 +75,6 @@ MNTPOINT="/mnt/debootstrap.$$" [ -n "$TIMEZONE" ] || TIMEZONE='Europe/Vienna' [ -n "$TUNE2FS" ] || TUNE2FS='tune2fs -c0 -i0' [ -n "$UPGRADE_SYSTEM" ] || UPGRADE_SYSTEM='yes' -[ -n "$VMSIZE" ] || VMSIZE="2G" [ -n "$GRUB_INSTALL" ] || GRUB_INSTALL='yes' # make sure interactive mode is only executed when @@ -102,11 +101,20 @@ Bootstrap options: --nodebootstrap Skip debootstrap, only do configuration to the target. --grub Target for grub installation. Usage example: /dev/sda --efi Target for EFI installation. Usage example: /dev/sda1 + --rpifirm Target for Raspberry Pi firmware installation. Usage + example: /dev/mmcblk0p1 --arch Set target architecture. --filesystem Filesystem that should be used when target is a partition or Virtual Machine (see --vmfile). --force Do not prompt for user acknowledgement. +Options for image file deployments: + + --imagesize Use specified size for size of image file (default: + 2G for BIOS and UEFI systems, 3G for Raspberry Pi + images). Syntax as supported by qemu-img, like: + --imagesize 3G + Options for Virtual Machine deployment: --vm Set up a Virtual Machine on an existing block device @@ -116,10 +124,18 @@ Options for Virtual Machine deployment: --vmfile Like --vm, but install into a regular file (created by 'qemu-img create -f raw ...') instead. Example: --vmfile --target /mnt/sda1/qemu.img - --vmsize Use specified size for size of VM file (default: 2G). - Syntax as supported by qemu-img, like: --vmsize 3G + --vmsize Deprecated alias for --imagesize. --vmefi Create an EFI boot partition for the VM. +Options for Raspberry Pi deployment: + + --rpi Set up a Raspberry Pi image on an existing block device + instead of plainly installing to a partition or directory. + Needs to be combined with --target. + Example: --rpi --target /dev/mapper/your-rpi-disk + --rpifile Like --rpi, but install into a regular file (created by + 'qemu-img create -f raw ...') instead. + Configuration options: -c, --config Use specified configuration file, defaults to @@ -369,7 +385,7 @@ fi # }}} # cmdline handling {{{ -CMDLINE_OPTS=mirror:,iso:,release:,target:,mntpoint:,debopt:,defaultinterfaces,interactive,nodebootstrap,nointerfaces,nokernel,nopackages,filesystem:,config:,confdir:,packages:,chroot-scripts:,scripts:,post-scripts:,pre-scripts:,debconf:,vm,vmfile,vmsize:,vmefi,keep_src_list,hostname:,password:,nopassword,grmlrepos,backportrepos,bootappend:,grub:,efi:,arch:,insecure,verbose,help,version,force,debug,contrib,non-free,remove-configs,sshcopyid,sshcopyauth +CMDLINE_OPTS=mirror:,iso:,release:,target:,mntpoint:,debopt:,defaultinterfaces,interactive,nodebootstrap,nointerfaces,nokernel,nopackages,filesystem:,config:,confdir:,packages:,chroot-scripts:,scripts:,post-scripts:,pre-scripts:,debconf:,vm,vmfile,vmsize:,imagesize:,vmefi,rpi,rpifile,keep_src_list,hostname:,password:,nopassword,grmlrepos,backportrepos,bootappend:,grub:,efi:,rpifirm:,arch:,insecure,verbose,help,version,force,debug,contrib,non-free,remove-configs,sshcopyid,sshcopyauth if ! _opt_temp=$(getopt --name grml-debootstrap -o +m:i:r:t:p:c:d:vhV --long \ $CMDLINE_OPTS -- "$@"); then @@ -391,6 +407,9 @@ while :; do --target|-t) # Target partition (/dev/...) or directory shift; _opt_target="$1" ;; + --imagesize) # size of image deployment file + shift; _opt_imagesize="$1" + ;; --vm) # Virtual machine image (no file) _opt_vm="T" ;; @@ -398,11 +417,17 @@ while :; do _opt_vmfile="T" ;; --vmsize) # size of Virtual machine file - shift; _opt_vmsize="$1" + shift; _opt_imagesize="$1" ;; --vmefi) # Create an EFI boot partition for the VM _opt_vmefi="T" ;; + --rpi) # RPi image (no file) + _opt_rpi="T" + ;; + --rpifile) # RPi image file + _opt_rpifile="T" + ;; --mntpoint|-p) # Mountpoint used for mounting the target system shift; _opt_mntpoint="$1" ;; @@ -503,6 +528,9 @@ while :; do --efi) # Target for EFI boot installation shift; _opt_efi="$1" ;; + --rpifirm) # Target for RPi firmware installation + shift; _opt_rpifirm="$1" + ;; --contrib) # Add 'contrib' to list of components _opt_contrib=T ;; @@ -565,9 +593,11 @@ done [ "$_opt_release" ] && RELEASE=$_opt_release [ "$_opt_target" ] && TARGET=$_opt_target [ "$_opt_vm" ] && VIRTUAL=1 -[ "$_opt_vmfile" ] && VMFILE=1 && VIRTUAL=1 -[ "$_opt_vmsize" ] && VMSIZE=$_opt_vmsize +[ "$_opt_vmfile" ] && IMAGEFILE=1 && VIRTUAL=1 +[ "$_opt_imagesize" ] && IMAGESIZE=$_opt_imagesize [ "$_opt_vmefi" ] && VMEFI=1 +[ "$_opt_rpi" ] && RPI=1 +[ "$_opt_rpifile" ] && IMAGEFILE=1 && RPI=1 [ "$_opt_mntpoint" ] && MNTPOINT=$_opt_mntpoint [ "$_opt_debopt" ] && DEBOOTSTRAP_OPT=$_opt_debopt [ "$_opt_interactive" ] && INTERACTIVE=1 @@ -593,6 +623,7 @@ done [ "$_opt_bootappend" ] && BOOT_APPEND=$_opt_bootappend [ "$_opt_grub" ] && GRUB=$_opt_grub [ "$_opt_efi" ] && EFI=$_opt_efi +[ "$_opt_rpifirm" ] && RPIFIRM=$_opt_rpifirm && RPI=1 [ "$_opt_arch" ] && ARCH=$_opt_arch [ "$_opt_insecure" ] && echo "Warning: --insecure is deprecated, continuing anyway." [ "$_opt_force" ] && FORCE=$_opt_force @@ -605,6 +636,35 @@ done [ "$_opt_contrib" ] && COMPONENTS="$COMPONENTS contrib" [ "$_opt_non_free" ] && COMPONENTS="$COMPONENTS non-free-firmware non-free" +# If $VMSIZE is set and $IMAGESIZE is unset, put $VMSIZE's value in $IMAGESIZE. +# +# If $VMSIZE is unset and $IMAGESIZE is set, keep $IMAGESIZE's value. +# +# If $VMSIZE is unset and $IMAGESIZE is unset, $IMAGESIZE defaults to 2G for VM images and 3G for +# RPi images (which need a bit more space). +if [ -n "$VMSIZE" ] && [ -z "$IMAGESIZE" ]; then + IMAGESIZE="$VMSIZE" +elif [ -z "$IMAGESIZE" ]; then + if [ "$RPI" ]; then + IMAGESIZE="3G" + else + IMAGESIZE="2G" + fi +fi + +if [ "$RPI" ] && [ -z "$ARCH" ]; then + # Ensure the architecture for the build is set to arm64 even if building on + # a non-arm64 machine, this is necessary for cross-builds to work + ARCH='arm64' +fi + +# RPi images *require* non-free-firmware as the raspi-firmware package resides +# there, and Raspberry Pis are not bootable without it. +if [ -z "$_opt_non_free" ] && [ "$RPI" ]; then + eerror "Raspberry Pi images require non-free firmware, please add --non-free to your command line." + bailout 1 +fi + # command line option checks if [ "$_opt_scripts_set" ] ; then ewarn "Deprecation NOTE: --scripts option is deprecated, please switch to --post-scripts instead." @@ -633,6 +693,27 @@ if [ "$_opt_efi" ] && [ "$_opt_vmefi" ] ; then bailout 1 fi +if [ "$RPI" ] && [ "$EFI" ] ; then + eerror "Raspberry Pi UEFI support has not yet been implemented, please drop EFI-related options from your command line." + bailout 1 +fi + +if [ "$RPI" ] && [ "$VIRTUAL" ] ; then + eerror "Raspberry Pi and virtual machine image options are mutually exclusive, please drop one of them from your command line." + eerror "If you want to build a virtual machine image for use on a Raspberry Pi, please build a VM image for the ARM64 architecture." + bailout 1 +fi + +if [ "$RPI" ] && [ "$GRUB" ] ; then + eerror "Raspberry Pi images do not use GRUB, please drop GRUB-related options from your command line." + bailout 1 +fi + +if [ "$RPI" ] && [ -n "$ARCH" ] && [ "$ARCH" != 'arm64' ] ; then + eerror "Raspberry Pi images support only the arm64 architecture. Please drop the --arch option from your command line or change it to specify arm64." + bailout 1 +fi + if [ "${_opt_sshcopyid}" ] && [ "${_opt_sshcopyauth}" ] ; then eerror "The --sshcopyid option is incompatible with --sshcopyauth, please drop either of them from your command line." bailout 1 @@ -941,26 +1022,29 @@ fi } # }}} -# format efi partition {{{ -format_efi_partition() { - if [ -z "$EFI" ] ; then - return 0 - fi - - if ! [ -b "$EFI" ] ; then - eerror "Specified efi argument [$EFI] not a valid block device." - bailout 1 - fi - - if fsck.vfat -bn "$EFI" >/dev/null; then - einfo "EFI partition $EFI seems to have a FAT filesystem, not modifying." - else - einfo "EFI partition $EFI doesn't seem to be formatted, creating filesystem." - if ! mkfs.fat -F32 -n "EFI" "$EFI" ; then - eerror "Error while creating filesystem on ${EFI}." +# format firmware boot partitions (ESP, Raspberry Pi firmware) {{{ +format_fwboot_partitions() { + for fwboot in "EFI $EFI" "RPIFIRM $RPIFIRM"; do + fwboot_type="${fwboot% *}" + fwboot_part="${fwboot#* }" + if [ -z "$fwboot_part" ]; then + continue + fi + if ! [ -b "$fwboot_part" ]; then + eerror "$fwboot_type partition $fwboot_part is not a valid block device." bailout 1 fi - fi + + if fsck.vfat -bn "$fwboot_part" >/dev/null; then + einfo "$fwboot_type partition $fwboot_part seems to have a FAT filesystem, not modifying." + else + einfo "$fwboot_type partition $fwboot_part doesn't seem to be formatted, creating filesystem." + if ! mkfs.fat -F32 -n "$fwboot_type" "$fwboot_part" ; then + eerror "Error while creating filesystem on ${fwboot_part}." + bailout 1 + fi + fi + done } # }}} @@ -1044,8 +1128,8 @@ else # if not running automatic installation display configuration and prompt fo [ -n "$CONFFILES" ] && echo " Config files: $CONFFILES" if [ -n "$VIRTUAL" ] ; then echo " Deploying as Virtual Machine." - if [ -n "$VMSIZE" ] && [ -n "$VMFILE" ]; then - echo " Using Virtual Disk file with size of ${VMSIZE}." + if [ -n "$IMAGESIZE" ] && [ -n "$IMAGEFILE" ]; then + echo " Using Virtual Disk file with size of ${IMAGESIZE}." fi fi @@ -1184,7 +1268,7 @@ set_target_directory(){ TARGET="$(readlink -f "$TARGET")" } -if [ -b "$TARGET" ] || [ -n "$VIRTUAL" ] ; then +if [ -b "$TARGET" ] || [ -n "$VIRTUAL" ] || [ -n "$RPI" ]; then PARTITION=1 else # $TARGET was not detected as block device, but we do not want to create target directory in /dev/ @@ -1240,10 +1324,17 @@ mkfs() { mkfs.fat -F32 -n "EFI" "$EFI_TARGET" fi + if [ -n "$RPI" ] && [ -n "$RPIFIRM_TARGET" ]; then + einfo "Creating FAT filesystem on $RPIFIRM_TARGET" + mkfs.fat -F32 -n "RPIFIRM" "$RPIFIRM_TARGET" + fi + # make sure /dev/disk/by-uuid/... is up2date, otherwise grub # will fail to detect the uuid in the chroot if [ -n "$VIRTUAL" ] ; then einfo "Virtual environment doesn't require blockdev --rereadpt, skipping therefore" + elif [ -n "$RPI" ] ; then + einfo "Raspberry Pi image build doesn't require blockdev --rereadpt, skipping therefore" elif echo "$TARGET" | grep -q "/dev/md" ; then blockdev --rereadpt "${TARGET}" else @@ -1356,17 +1447,17 @@ mount_target() { # }}} # prepare VM image for usage with debootstrap {{{ -prepare_vm() { - if [ -z "$VIRTUAL" ] ; then +prepare_image() { + if [ -z "$VIRTUAL" ] && [ -z "$RPI" ]; then return 0 # be quiet by intention fi - if [ -b "$TARGET" ] && [ -n "$VMFILE" ] ; then - eerror "Error: specified virtual disk target ($TARGET) is an existing block device." + if [ -b "$TARGET" ] && [ -n "$IMAGEFILE" ] ; then + eerror "Error: specified disk image target ($TARGET) is an existing block device." bailout 1 fi - if [ ! -b "$TARGET" ] && [ -z "$VMFILE" ] ; then - eerror "Error: specified virtual disk target ($TARGET) does not exist yet." + if [ ! -b "$TARGET" ] && [ -z "$IMAGEFILE" ] ; then + eerror "Error: specified disk image target ($TARGET) does not exist yet." bailout 1 fi @@ -1389,7 +1480,7 @@ prepare_vm() { case "$ARCH" in amd64) true;; - arm64) VMEFI=1;; + arm64) [ -z "$RPI" ] && VMEFI=1;; esac parted_type_command_supported='no' @@ -1397,8 +1488,8 @@ prepare_vm() { parted_type_command_supported='yes' fi - if [ -n "$VMFILE" ]; then - qemu-img create -f raw "${TARGET}" "${VMSIZE}" + if [ -n "$IMAGEFILE" ]; then + qemu-img create -f raw "${TARGET}" "${IMAGESIZE}" fi if [ -n "$VMEFI" ]; then parted -s "${TARGET}" 'mklabel gpt' @@ -1423,6 +1514,10 @@ prepare_vm() { parted -s "${TARGET}" 'type 3 4f68bce3-e8cd-4db1-96e7-fbcaf984b709' fi fi + elif [ -n "$RPI" ]; then + parted -s "${TARGET}" 'mklabel msdos' + parted -s "${TARGET}" 'mkpart primary fat32 4MiB 508MiB' + parted -s "${TARGET}" 'mkpart primary ext4 508MiB 100%' else parted -s "${TARGET}" 'mklabel msdos' if [ "$FIXED_DISK_IDENTIFIERS" = "yes" ] ; then @@ -1444,16 +1539,29 @@ prepare_vm() { # hopefully this always works as expected LOOP_PART="${DEVINFO##add map }" # 'loop0p1 (254:5): 0 20477 linear 7:0 3' + + case "${LOOP_PART}" in + sd*|vd*|xvd*) part_prefix="" ;; + *) part_prefix="p" ;; + esac + LOOP_PART="${LOOP_PART// */}" # 'loop0p1' if [ -n "$VMEFI" ]; then - export EFI_TARGET="/dev/mapper/$LOOP_PART" # '/dev/mapper/loop0p1' + EFI_TARGET="/dev/mapper/$LOOP_PART" # '/dev/mapper/loop0p1' if [ "$ARCH" = 'arm64' ]; then - LOOP_PART="${LOOP_PART%p1}p2" + LOOP_PART="${LOOP_PART%"${part_prefix}"1}${part_prefix}2" else - LOOP_PART="${LOOP_PART%p1}p3" + LOOP_PART="${LOOP_PART%"${part_prefix}"1}${part_prefix}3" fi + elif [ -n "$RPI" ]; then + RPIFIRM_TARGET="/dev/mapper/$LOOP_PART" # '/dev/mapper/loop0p1' + LOOP_PART="${LOOP_PART%"${part_prefix}"1}${part_prefix}2" + fi + if [ -n "${part_prefix}" ]; then + LOOP_DISK="${LOOP_PART%"${part_prefix}"*}" # 'loop0' + else + LOOP_DISK="${LOOP_PART%%[0-9]*}" # 'sda' fi - LOOP_DISK="${LOOP_PART%p*}" # 'loop0' export TARGET="/dev/mapper/$LOOP_PART" # '/dev/mapper/loop0p1' if [ -z "$TARGET" ] ; then @@ -1462,10 +1570,12 @@ prepare_vm() { fi if [ -z "$GRUB" ]; then - GRUB="/dev/$LOOP_DISK" # '/dev/mapper/loop0' + GRUB="/dev/$LOOP_DISK" # '/dev/loop0' or '/dev/sda' fi if [ -z "$EFI" ] && [ "$VMEFI" = '1' ]; then EFI="${EFI_TARGET}" + elif [ -z "$RPIFIRM" ] && [ "$RPI" = '1' ]; then + RPIFIRM="${RPIFIRM_TARGET}" fi } # }}} @@ -1578,6 +1688,7 @@ preparechroot() { [ -n "$GRMLREPOS" ] && echo "GRMLREPOS='${GRMLREPOS//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$GRUB" ] && echo "GRUB='${GRUB//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$HOSTNAME" ] && echo "HOSTNAME='${HOSTNAME//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" + [ -n "$IMAGESIZE" ] && echo "IMAGESIZE='${IMAGESIZE//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$INITRD" ] && echo "INITRD='${INITRD//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$INITRD_GENERATOR" ] && echo "INITRD_GENERATOR='${INITRD_GENERATOR//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$INITRD_GENERATOR_OPTS" ] && echo "INITRD_GENERATOR_OPTS='${INITRD_GENERATOR_OPTS//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" @@ -1593,6 +1704,8 @@ preparechroot() { [ -n "$PRE_SCRIPTS" ] && echo "PRE_SCRIPTS='${PRE_SCRIPTS//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$RECONFIGURE" ] && echo "RECONFIGURE='${RECONFIGURE//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$RELEASE" ] && echo "RELEASE='${RELEASE//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" + [ -n "$RPI" ] && echo "RPI='${RPI}'" >> "$CHROOT_VARIABLES" + [ -n "$RPIFIRM" ] && echo "RPIFIRM='${RPIFIRM//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$RM_APTCACHE" ] && echo "RM_APTCACHE='${RM_APTCACHE//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$ROOTPASSWORD" ] && echo "ROOTPASSWORD='${ROOTPASSWORD//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$SCRIPTS" ] && echo "SCRIPTS='${SCRIPTS//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" @@ -1603,7 +1716,6 @@ preparechroot() { [ -n "$TARGET_UUID" ] && echo "TARGET_UUID='${TARGET_UUID//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$TIMEZONE" ] && echo "TIMEZONE='${TIMEZONE//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" [ -n "$TUNE2FS" ] && echo "TUNE2FS='${TUNE2FS//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" - [ -n "$VMSIZE" ] && echo "VMSIZE='${VMSIZE//\'/\'\\\'\'}'" >> "$CHROOT_VARIABLES" cp ${VERBOSE:+-v} "${CONFFILES}"/chroot-script "${MNTPOINT}"/bin/chroot-script chmod 755 "${MNTPOINT}"/bin/chroot-script @@ -1717,6 +1829,10 @@ iface ${interface} inet dhcp einfo "Setting up Virtual Machine, installing default /etc/network/interfaces" mkdir -p "${MNTPOINT}/etc/network" echo "$DEFAULT_INTERFACES" > "${MNTPOINT}/etc/network/interfaces" + elif [ -n "$RPI" ] ; then + einfo "Setting up Raspberry Pi installation, installing default /etc/network/interfaces" + mkdir -p "${MNTPOINT}/etc/network" + echo "$DEFAULT_INTERFACES" > "${MNTPOINT}/etc/network/interfaces" elif [ -r /etc/network/interfaces ] ; then einfo "Copying /etc/network/interfaces from host to target system" mkdir -p "${MNTPOINT}/etc/network" @@ -1910,7 +2026,7 @@ remove_configs() { # }}} # now execute all the functions {{{ -for i in format_efi_partition prepare_vm mkfs tunefs \ +for i in format_fwboot_partitions prepare_image mkfs tunefs \ mount_target mountpoint_to_blockdevice debootstrap_system \ preparechroot execute_pre_scripts chrootscript execute_post_scripts \ remove_configs umount_chroot umount_target fscktool ; do diff --git a/grml-debootstrap.8.adoc b/grml-debootstrap.8.adoc index 9f2636b0..d9d0d9c9 100644 --- a/grml-debootstrap.8.adoc +++ b/grml-debootstrap.8.adoc @@ -133,6 +133,14 @@ Options and environment variables Use specified hostname instead of the default (being $HOSTNAME or if unset 'grml'). +*--imagesize* _size_:: + + Use specified size for size of Virtual Machine or Raspberry Pi disk file. + If not specified it defaults to 2G (being 2GiB) for virtual machines, and 3G + (being 3GiB) for Raspberry Pi images. Syntax as supported by qemu-img (see + manpage qemu-img(1) for details. + Usage example: --imagesize 3G + *--keep_src_list*:: Do not override user provided /etc/apt/sources.list.d/debian.sources or @@ -219,6 +227,32 @@ Options and environment variables Delete grml-debootstrap configuration files (/etc/debootstrap/*) from installed system. Useful for reproducible builds or if you don't want to leak information. +*--rpi*:: + + Set up a Raspberry Pi installation on an existing block device, which will + be partitioned. This allows deployment of a physical Raspberry Pi system. + The option needs to be combined with the --target option. + This option automatically enables the --defaultinterfaces option. + Usage example: --rpi --target /dev/mmcblk0 + +*--rpifile*:: + + Set up a Raspberry Pi installation using a regular file instead of + installing to a partition/block device. This allows creation of a + Raspberry Pi image. The option needs to be combined with the --target + option ('qemu-img create -f raw ...' is executed on the specified target). + This option automatically enables the --defaultinterfaces option. + Usage example: --rpifile --target /mnt/sda1/rpi.img + +*--rpifirm* _device_:: + + Partition where Raspberry Pi firmware should be installed to. If the + specified device doesn't use a FAT filesystem yet grml-debootstrap + automatically creates the filesystem. If a FAT filesystem is already + present the filesystem creation is skipped. Please ensure that it's a + valid FAT32 filesystem. This option is NOT supported for Raspberry Pi + image file deployments. + *--sshcopyauth*:: Use locally available _$HOME/.ssh/authorized_keys_ to authorise root login on the target system. @@ -262,10 +296,7 @@ Options and environment variables *--vmsize* _size_:: - Use specified size for size of Virtual Machine disk file. If not specified it - defaults to 2G (being 2GB). Syntax as supported by qemu-img (see manpage - qemu-img(1) for details. - Usage example: --vmsize 3G + Deprecated alias for --imagesize. *--vmefi*:: @@ -327,7 +358,7 @@ Install default Debian release (trixie) on /dev/sda3 and install bootmanager GRU Use specified mirror instead of the default (http://deb.debian.org/debian) one. mount /dev/sda1 /mnt/sda1 - grml-debootstrap --vmfile --vmsize 3G --target /mnt/sda1/qemu.img + grml-debootstrap --vmfile --imagesize 3G --target /mnt/sda1/qemu.img Install default debian release (trixie) in a Virtual Machine file with 3GB disk size (including GRUB as bootmanager in MBR of the virtual disk file). diff --git a/tests/build-vm-and-test.sh b/tests/build-vm-and-test.sh index b7d7b569..9b5c40a4 100755 --- a/tests/build-vm-and-test.sh +++ b/tests/build-vm-and-test.sh @@ -50,7 +50,9 @@ fi # Debian version to install using grml-debootstrap RELEASE="${RELEASE:-trixie}" -TARGET="${TARGET:-qemu.img}" +TARGET="${TARGET:-no}" + +QEMU_IMG="${QEMU_IMG:-qemu.img}" if [ "$1" == "run" ]; then # Debian version on which grml-debootstrap will *run* @@ -68,11 +70,11 @@ if [ "$1" == "run" ]; then -e TERM="$TERM" \ -w /code \ debian:"$HOST_RELEASE" \ - bash -c './tests/docker-install-deb.sh '"$DEB_NAME"' && ./tests/docker-build-vm.sh '"$(id -u)"' '"/code/$TARGET"' '"$RELEASE" + bash -c './tests/docker-install-deb.sh '"$DEB_NAME"' && ./tests/docker-build-vm.sh '"$(id -u)"' '"/code/$QEMU_IMG"' '"$RELEASE"' '"$TARGET" elif [ "$1" == "test" ]; then # run tests from inside Debian system - exec ./tests/test-vm.sh "$PWD/$TARGET" "$RELEASE" + exec ./tests/test-vm.sh "$PWD/$QEMU_IMG" "$RELEASE" "$TARGET" else echo "$0: unknown parameters, see --help" >&2 diff --git a/tests/docker-build-vm.sh b/tests/docker-build-vm.sh index 0512f468..781975b3 100755 --- a/tests/docker-build-vm.sh +++ b/tests/docker-build-vm.sh @@ -6,14 +6,15 @@ set -eu -o pipefail -if [ "$#" -ne 3 ]; then +if [ "$#" -ne 4 ]; then echo "$0: Invalid arguments" >&2 - echo "Expect: $0 HOST_UID TARGET RELEASE" >&2 + echo "Expect: $0 HOST_UID TARGET_FILE RELEASE TARGET" >&2 exit 1 fi HOST_UID="$1" -TARGET="$2" +TARGET_FILE="$2" RELEASE="$3" +TARGET="$4" set -x @@ -23,16 +24,22 @@ MIRROR='https://deb.debian.org/debian' echo " ****************************************************************** " echo " * Running grml-debootstrap" +if [ "$TARGET" = 'RPI' ]; then + extra_buildopts=(--rpifile --non-free) +else + extra_buildopts=(--vmfile) +fi + grml-debootstrap \ --debug \ --force \ - --vmfile \ - --vmsize 3G \ - --target "$TARGET" \ + "${extra_buildopts[@]}" \ + --imagesize 3G \ + --target "$TARGET_FILE" \ --bootappend "console=ttyS0,115200 console=tty0 vga=791" \ --password grml \ --release "$RELEASE" \ --hostname "$RELEASE" \ --mirror "$MIRROR" -chown "$HOST_UID" "$TARGET" +chown "$HOST_UID" "$TARGET_FILE" diff --git a/tests/extract-rpi-bootdata.sh b/tests/extract-rpi-bootdata.sh new file mode 100755 index 00000000..2a321748 --- /dev/null +++ b/tests/extract-rpi-bootdata.sh @@ -0,0 +1,43 @@ +#!/bin/bash +VM_IMAGE="$1" +WORK_DIR="${WORK_DIR:-./rpi-work}" +KERN_DIR="${KERN_DIR:-./rpi-kernel}" + +bail_noremove() { + echo "$1" >&2 + exit 1 +} + +cleanup() { + if mountpoint "$WORK_DIR" >/dev/null 2>&1; then + umount "$WORK_DIR" >/dev/null 2>&1 + fi + rmdir "$WORK_DIR" >/dev/null 2>&1 + kpartx -d -s -v "$VM_IMAGE" >/dev/null 2>&1 +} + +bail() { + cleanup + bail_noremove "$1" +} + +mkdir --parents "$WORK_DIR" || bail_noremove "Could not create work dir!" +mkdir --parents "$KERN_DIR" || bail_noremove "Could not create kernel dir!" +[ "$(cd "$WORK_DIR"; find '.')" = '.' ] || bail_noremove "Work dir is not empty!" +[ "$(cd "$KERN_DIR"; find '.')" = '.' ] || bail_noremove "Kernel dir is not empty!" + +kpartx_info="$(kpartx -a -s -v "$VM_IMAGE")" +readarray -t kpartx_part_list < <(awk '{ print $3 }' <<< "$kpartx_info") +[ "${#kpartx_part_list[@]}" = '2' ] || bail "VM image has wrong number of partitions!" +mount /dev/mapper/"${kpartx_part_list[0]}" "$WORK_DIR" +config_txt_cont="$(cat "${WORK_DIR}/config.txt")" +kernel_name="$(grep '^kernel=' <<< "$config_txt_cont" | cut -d'=' -f2 | head -n1)" +[ -z "$kernel_name" ] && bail 'Could not detect kernel name!' +initramfs_name="$(grep '^initramfs ' <<< "$config_txt_cont" | cut -d' ' -f2 | head -n1)" +[ -z "$initramfs_name" ] && bail 'Could not detect initramfs name!' +cp "${WORK_DIR}/${kernel_name}" "$KERN_DIR"/ +cp "${WORK_DIR}/${initramfs_name}" "$KERN_DIR"/ +umount "$WORK_DIR" +root_part_uuid="$(grep "${kpartx_part_list[1]}" < <(lsblk -f) | awk '{ print $4 }')" +cleanup +echo "${KERN_DIR}/${kernel_name}|${KERN_DIR}/${initramfs_name}|root=UUID=${root_part_uuid} rw" diff --git a/tests/test-vm.sh b/tests/test-vm.sh index 3b7c44bb..e17d3bb5 100755 --- a/tests/test-vm.sh +++ b/tests/test-vm.sh @@ -6,15 +6,16 @@ set -eu -o pipefail -if [ "$#" -ne 2 ]; then +if [ "$#" -ne 3 ]; then echo "$0: Invalid arguments" >&2 - echo "Expect: $0 VM_IMAGE VM_HOSTNAME" >&2 + echo "Expect: $0 VM_IMAGE VM_HOSTNAME TARGET" >&2 exit 1 fi set -x VM_IMAGE="$1" VM_HOSTNAME="$2" +TARGET="$3" TEST_PWD="$PWD" TEST_TMPDIR=$(mktemp -d) @@ -28,7 +29,7 @@ bailout() { ps --pid="${QEMU_PID}" -o pid= | grep -q '.' && kill "${QEMU_PID:-}" fi - rm -rf "${TEST_TMPDIR}" + sudo rm -rf "${TEST_TMPDIR}" [ -n "${1:-}" ] && EXIT_CODE="$1" || EXIT_CODE=1 exit "$EXIT_CODE" @@ -72,17 +73,30 @@ declare -a qemu_command DPKG_ARCHITECTURE=$(dpkg --print-architecture) if [ "${DPKG_ARCHITECTURE}" = "amd64" ]; then - qemu_command=( qemu-system-x86_64 ) - qemu_command+=( -machine q35 ) + qemu_command=( qemu-system-x86_64 ) + qemu_command+=( -machine q35 ) elif [ "${DPKG_ARCHITECTURE}" = "arm64" ]; then + if [ "$TARGET" = 'RPI' ]; then + if ! rpi_bootdata="$(sudo "$TEST_PWD"/tests/extract-rpi-bootdata.sh "$VM_IMAGE")"; then + echo "E: could not extract RPi boot data" >&2 + exit 1 + fi + IFS='|' read rpi_kern rpi_initrd rpi_kerncmd <<< "$rpi_bootdata" + qemu_command=( qemu-system-aarch64 ) + qemu_command+=( -machine "type=virt,gic-version=max,accel=kvm:tcg,highmem=off" ) + qemu_command+=( -kernel "$rpi_kern" ) + qemu_command+=( -initrd "$rpi_initrd" ) + qemu_command+=( -append "$rpi_kerncmd" ) + else cp /usr/share/AAVMF/AAVMF_VARS.fd efi_vars.fd qemu_command=( qemu-system-aarch64 ) qemu_command+=( -machine "type=virt,gic-version=max,accel=kvm:tcg" ) qemu_command+=( -drive "if=pflash,format=raw,unit=0,file.filename=/usr/share/AAVMF/AAVMF_CODE.no-secboot.fd,file.locking=off,readonly=on" ) qemu_command+=( -drive "if=pflash,format=raw,unit=1,file=efi_vars.fd" ) + fi else - echo "E: unsupported ${DPKG_ARCHITECTURE}" - exit 1 + echo "E: unsupported ${DPKG_ARCHITECTURE}" >&2 + exit 1 fi qemu_command+=( -cpu max ) qemu_command+=( -smp 2 )