build-in-qemu.sh 3.67 KB
Newer Older
1
#!/bin/bash
Kristian Klausen's avatar
Kristian Klausen committed
2
3
4
5
# build-in qemu.sh runs build.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."
6
7
8
9
10
11
12
13
14
set -o nounset -o errexit
MIRROR="https://mirror.pkgbuild.com"

ORIG_PWD="${PWD}"
OUTPUT="${PWD}/output"
mkdir -p "tmp" "${OUTPUT}"
TMPDIR="$(mktemp --directory --tmpdir="${PWD}/tmp")"
cd "${TMPDIR}"

Kristian Klausen's avatar
Kristian Klausen committed
15
# Do some cleanup when the script exits
16
17
18
19
20
21
function cleanup() {
  rm -rf "${TMPDIR}"
  jobs -p | xargs --no-run-if-empty kill
}
trap cleanup EXIT

Kristian Klausen's avatar
Kristian Klausen committed
22
# Use local Arch iso or download the latest iso and extract the relevant files
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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

Kristian Klausen's avatar
Kristian Klausen committed
39
40
  # 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.
41
42
43
44
45
  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() {
Kristian Klausen's avatar
Kristian Klausen committed
46
  # Used to communicate with qemu
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
  mkfifo guest.out guest.in
  # We could use a sparse file but we want to fail early
  fallocate -l 4G scratch-disk.img

  { qemu-system-x86_64 \
    -machine accel=kvm:tcg \
    -m 768 \
    -net nic \
    -net user \
    -kernel vmlinuz-linux \
    -initrd archiso.img \
    -append "archisobasedir=arch archisolabel=${ISO_VOLUME_ID} ip=dhcp net.ifnames=0 console=ttyS0" \
    -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 "${$}"; } &

Kristian Klausen's avatar
Kristian Klausen committed
66
  # We want to send the output to both stdout (fd1) and fd10 (used by the expect function)
67
68
69
  exec 3>&1 10< <(tee /dev/fd/3 <guest.out)
}

Kristian Klausen's avatar
Kristian Klausen committed
70
# Wait for a specific string from qemu
71
72
73
function expect() {
  length="${#1}"
  i=0
Kristian Klausen's avatar
Kristian Klausen committed
74
  # We can't use ex: grep as we could end blocking forever, if the string isn't followed by a newline
75
76
77
78
79
80
81
82
83
84
85
86
  while IFS= read -r -u 10 -n 1 c; do
    if [ "${1:${i}:1}" = "${c}" ]; then
      i="$((i + 1))"
      if [ "${length}" -eq "${i}" ]; then
        break
      fi
    else
      i=0
    fi
  done
}

Kristian Klausen's avatar
Kristian Klausen committed
87
# Send string to qemu
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
function send() {
  echo -en "${1}" >guest.in
}

prepare_boot
start_qemu

expect "archiso login:"
send "root\n"
expect "# "

send "bash\n"
expect "# "
send "trap \"shutdown now\" ERR\n"
expect "# "

send "mkdir /mnt/arch-boxes && mount -t 9p -o trans=virtio host /mnt/arch-boxes -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/arch-boxes/{box.ovf,build.sh,http} .\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 "# "

send "pacman -Sy --noconfirm qemu-headless jq\n"
expect "# "

send "bash -x ./build.sh\n"
expect "# "
send "cp -r --preserve=mode,timestamps output /mnt/arch-boxes/tmp/$(basename "${TMPDIR}")/\n"
expect "# "

mv output/* "${OUTPUT}/"

send "shutdown now\n"

wait