build-host.sh 4.43 KB
Newer Older
1
#!/bin/bash
2
# build-host.sh runs build-inside-vm.sh in a qemu VM running the latest Arch installer iso
Kristian Klausen's avatar
Kristian Klausen committed
3
4
5
#
# 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
set -o nounset -o errexit
7
readonly MIRROR="https://mirror.pkgbuild.com"
8

9
10
11
12
13
14
15
16
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}"
}
17

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

Kristian Klausen's avatar
Kristian Klausen committed
25
# Use local Arch iso or download the latest iso and extract the relevant files
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
42
43
  # 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.
44
45
46
47
48
  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
49
  # Used to communicate with qemu
50
51
52
53
54
55
  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 \
Sven-Hendrik Haase's avatar
Sven-Hendrik Haase committed
56
    -smp 4 \
57
58
59
60
    -m 768 \
    -net nic \
    -net user \
    -kernel vmlinuz-linux \
Kristian Klausen's avatar
Kristian Klausen committed
61
    -initrd initramfs-linux.img \
Kristian Klausen's avatar
Kristian Klausen committed
62
    -append "archisobasedir=arch archisolabel=${ISO_VOLUME_ID} ip=dhcp net.ifnames=0 console=ttyS0 mirror=${MIRROR}" \
63
64
65
66
67
68
69
    -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
70
  # We want to send the output to both stdout (fd1) and fd10 (used by the expect function)
71
72
73
  exec 3>&1 10< <(tee /dev/fd/3 <guest.out)
}

Kristian Klausen's avatar
Kristian Klausen committed
74
# Wait for a specific string from qemu
75
function expect() {
76
77
  local length="${#1}"
  local i=0
78
  local timeout="${2:-30}"
Kristian Klausen's avatar
Kristian Klausen committed
79
  # We can't use ex: grep as we could end blocking forever, if the string isn't followed by a newline
Kristian Klausen's avatar
Kristian Klausen committed
80
81
82
  while true; do
    # read should never exit with a non-zero exit code,
    # but it can happen if the fd is EOF or it times out
83
    IFS= read -r -u 10 -n 1 -t "${timeout}" c
84
85
86
87
88
89
90
91
92
93
94
    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
95
# Send string to qemu
96
97
98
99
function send() {
  echo -en "${1}" >guest.in
}

100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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/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 "# "
121
  send "cp -a /mnt/arch-boxes/{box.ovf,build-inside-vm.sh,http} .\n"
122
123
124
125
126
127
128
129
  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 "# "

Sven-Hendrik Haase's avatar
Sven-Hendrik Haase committed
130
131
132
133
  # Explicitly lookup mirror address as we'd get random failures otherwise during pacman
  send "host ${MIRROR}\n"
  expect "# "

134
135
136
137
138
  # Install required packages
  send "pacman -Sy --noconfirm qemu-headless jq\n"
  expect "# "

  ## Start build and copy output to local disk
139
  send "bash -x ./build-inside-vm.sh ${BUILD_VERSION:-}\n"
140
  expect "# " 240 # qemu-img convert can take a long time
141
  send "cp -r --preserve=mode,timestamps output /mnt/arch-boxes/tmp/$(basename "${TMPDIR}")/\n"
142
  expect "# " 60
143
144
145
146
147
148
149
  mv output/* "${OUTPUT}/"

  # Shutdown the VM
  send "shutdown now\n"
  wait
}
main