build-host.sh 4.28 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
56
57
58
59
60
  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 \
Kristian Klausen's avatar
Kristian Klausen committed
61
    -append "archisobasedir=arch archisolabel=${ISO_VOLUME_ID} ip=dhcp net.ifnames=0 console=ttyS0 mirror=${MIRROR}" \
62
63
64
65
66
67
68
    -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
69
  # We want to send the output to both stdout (fd1) and fd10 (used by the expect function)
70
71
72
  exec 3>&1 10< <(tee /dev/fd/3 <guest.out)
}

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

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

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

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

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