build-host.sh 4.58 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
function init() {
  readonly ORIG_PWD="${PWD}"
  readonly OUTPUT="${PWD}/output"
12
13
14
  local tmpdir
  tmpdir="$(mktemp --dry-run --directory --tmpdir="${PWD}/tmp")"
  readonly TMPDIR="${tmpdir}"
15
16
17
18
  mkdir -p "${OUTPUT}" "${TMPDIR}"

  cd "${TMPDIR}"
}
19

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

Kristian Klausen's avatar
Kristian Klausen committed
27
# Use local Arch iso or download the latest iso and extract the relevant files
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
44
45
  # 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.
46
47
48
49
50
  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
51
  # Used to communicate with qemu
52
53
54
55
56
57
  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
58
    -smp 4 \
59
    -m 2048 \
60
61
62
    -net nic \
    -net user \
    -kernel vmlinuz-linux \
Kristian Klausen's avatar
Kristian Klausen committed
63
    -initrd initramfs-linux.img \
64
    -append "archisobasedir=arch archisolabel=${ISO_VOLUME_ID} cow_spacesize=2G ip=dhcp net.ifnames=0 console=ttyS0 mirror=${MIRROR}" \
65
66
67
68
69
70
71
    -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 "${$}"; } &

72
73
  # We want to send the output to both stdout (fd1) and a new file descriptor (used by the expect function)
  exec 3>&1 {fd}< <(tee /dev/fd/3 <guest.out)
74
75
}

Kristian Klausen's avatar
Kristian Klausen committed
76
# Wait for a specific string from qemu
77
function expect() {
78
79
  local length="${#1}"
  local i=0
80
  local timeout="${2:-30}"
Kristian Klausen's avatar
Kristian Klausen committed
81
  # 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
82
83
84
  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
85
    IFS= read -r -u ${fd} -n 1 -t "${timeout}" c
86
87
88
89
90
91
92
93
94
95
96
    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
97
# Send string to qemu
98
99
100
101
function send() {
  echo -en "${1}" >guest.in
}

102
103
104
105
106
107
function main() {
  init
  prepare_boot
  start_qemu

  # Login
108
  expect "archiso login:"
109
110
111
112
113
114
115
116
117
118
119
120
121
122
  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 "# "
123
  send "cp -a /mnt/arch-boxes/{box.ovf,build-inside-vm.sh,images} .\n"
124
125
126
127
128
129
130
131
  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
132
  # Explicitly lookup mirror address as we'd get random failures otherwise during pacman
133
  send "curl -sSo /dev/null ${MIRROR}\n"
Sven-Hendrik Haase's avatar
Sven-Hendrik Haase committed
134
135
  expect "# "

136
  # Install required packages
137
138
  send "pacman -Syu --ignore linux --noconfirm qemu-headless jq\n"
  expect "# " 120 # (10/14) Updating module dependencies...
139
140

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

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