diff --git a/.gitlab/ci/build-host.sh b/.gitlab/ci/build-host.sh new file mode 100755 index 0000000..7661c81 --- /dev/null +++ b/.gitlab/ci/build-host.sh @@ -0,0 +1,149 @@ +#!/bin/bash +# build-host.sh runs build-inside-vm.sh in a qemu VM running the latest Arch installer iso +# +# nounset: "Treat unset variables and parameters [...] as an error when performing parameter expansion." +# errexit: "Exit immediately if [...] command exits with a non-zero status." +set -o nounset -o errexit +readonly MIRROR="https://mirror.pkgbuild.com" + +function init() { + readonly ORIG_PWD="${PWD}" + readonly OUTPUT="${PWD}/output" + readonly TMPDIR="$(mktemp --dry-run --directory --tmpdir="${PWD}/tmp")" + mkdir -p "${OUTPUT}" "${TMPDIR}" + + cd "${TMPDIR}" +} + +# Do some cleanup when the script exits +function cleanup() { + rm -rf "${TMPDIR}" + jobs -p | xargs --no-run-if-empty kill +} +trap cleanup EXIT + +# Use local Arch iso or download the latest iso and extract the relevant files +function prepare_boot() { + if LOCAL_ISO="$(ls "${ORIG_PWD}/"archlinux-*-x86_64.iso 2>/dev/null)"; then + echo "Using local iso: ${LOCAL_ISO}" + ISO="${LOCAL_ISO}" + fi + + if [ -z "${LOCAL_ISO}" ]; then + LATEST_ISO="$(curl -fs "${MIRROR}/iso/latest/" | grep -Eo 'archlinux-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-x86_64.iso' | head -n 1)" + if [ -z "${LATEST_ISO}" ]; then + echo "Error: Couldn't find latest iso'" + exit 1 + fi + curl -fO "${MIRROR}/iso/latest/${LATEST_ISO}" + ISO="${PWD}/${LATEST_ISO}" + fi + + # We need to extract the kernel and initrd so we can set a custom cmdline: + # console=ttyS0, so the kernel and systemd sends output to the serial. + xorriso -osirrox on -indev "${ISO}" -extract arch/boot/x86_64 . + ISO_VOLUME_ID="$(xorriso -indev "${ISO}" |& awk -F : '$1 ~ "Volume id" {print $2}' | tr -d "' ")" +} + +function start_qemu() { + # Used to communicate with qemu + mkfifo guest.out guest.in + # We could use a sparse file but we want to fail early + fallocate -l 8G scratch-disk.img + + { qemu-system-x86_64 \ + -machine accel=kvm:tcg \ + -smp "$(nproc)" \ + -m 4096 \ + -net nic \ + -net user \ + -kernel vmlinuz-linux \ + -initrd initramfs-linux.img \ + -append "archisobasedir=arch archisolabel=${ISO_VOLUME_ID} ip=dhcp net.ifnames=0 console=ttyS0 mirror=${MIRROR}" \ + -drive file=scratch-disk.img,format=raw,if=virtio \ + -drive file="${ISO}",format=raw,if=virtio,media=cdrom,read-only \ + -virtfs "local,path=${ORIG_PWD},mount_tag=host,security_model=none" \ + -monitor none \ + -serial pipe:guest \ + -nographic || kill "${$}"; } & + + # We want to send the output to both stdout (fd1) and fd10 (used by the expect function) + exec 3>&1 10< <(tee /dev/fd/3 guest.in +} + +function main() { + init + prepare_boot + start_qemu + + # Login + expect "archiso login:" + send "root\n" + expect "# " + + # Switch to bash and shutdown on error + send "bash\n" + expect "# " + send "trap \"shutdown now\" ERR\n" + expect "# " + + # Prepare environment + send "mkdir /mnt/project && mount -t 9p -o trans=virtio host /mnt/project -oversion=9p2000.L\n" + expect "# " + send "mkfs.ext4 /dev/vda && mkdir /mnt/scratch-disk/ && mount /dev/vda /mnt/scratch-disk && cd /mnt/scratch-disk\n" + expect "# " + send "cp -a /mnt/project/{.gitlab,archiso,configs,scripts} .\n" + expect "# " + send "mkdir pkg && mount --bind pkg /var/cache/pacman/pkg\n" + expect "# " + + # Wait for pacman-init + send "until systemctl is-active pacman-init; do sleep 1; done\n" + expect "# " + + # Explicitly lookup mirror address as we'd get random failures otherwise during pacman + send "curl -sSo /dev/null ${MIRROR}\n" + expect "# " + + # Install required packages + send "pacman -Sy --noconfirm qemu-headless jq dosfstools e2fsprogs libisoburn mtools squashfs-tools\n" + expect "# " + + ## Start build and copy output to local disk + send "bash -x ./.gitlab/ci/build-inside-vm.sh ${PROFILE}\n " + expect "# " 1000 # mksquashfs can take a long time + send "cp -r --preserve=mode,timestamps output /mnt/project/tmp/$(basename "${TMPDIR}")/\n" + expect "# " 60 + mv output/* "${OUTPUT}/" + + # Shutdown the VM + send "shutdown now\n" + wait +} +main diff --git a/.gitlab/ci/build-inside-vm.sh b/.gitlab/ci/build-inside-vm.sh new file mode 100755 index 0000000..dbaadea --- /dev/null +++ b/.gitlab/ci/build-inside-vm.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# +# This script is run within a virtual environment to build the available archiso profiles and create checksum files for +# the resulting images. +# The script needs to be run as root and assumes $PWD to be the root of the repository. + +readonly orig_pwd="${PWD}" +readonly output="${orig_pwd}/output" +readonly tmpdir="$(mktemp --dry-run --directory --tmpdir="${orig_pwd}/tmp")" + +cleanup() { + # clean up temporary directories + if [ -n "${tmpdir:-}" ]; then + rm -rf "${tmpdir}" + fi +} + +create_checksums() { + # create checksums for a file + # $1: a file + sha256sum "${1}" >"${1}.sha256" + sha512sum "${1}" >"${1}.sha512" + b2sum "${1}" >"${1}.b2" + if [ -n "${SUDO_UID:-}" ]; then + chown "${SUDO_UID}:${SUDO_GID}" "${1}"{,.b2,.sha{256,512}} + fi +} + +run_mkarchiso() { + # run mkarchiso + # $1: template name + mkdir -p "${output}/${1}" "${tmpdir}/${1}" + ./archiso/mkarchiso -o "${output}/${1}" -w "${tmpdir}/${1}" -v "configs/${1}" + create_checksums "${output}/${1}/"*.iso +} + +trap cleanup EXIT + +run_mkarchiso "${1}"