build-inside-vm.sh 5.62 KB
Newer Older
1
#!/bin/bash
2
# build-inside-vm.sh builds the images (cloud image, vagrant boxes)
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
shopt -s extglob
8
readonly DEFAULT_DISK_SIZE="2G"
9
readonly IMAGE="image.img"
10
# shellcheck disable=SC2016
11
readonly MIRROR='https://mirror.pkgbuild.com/$repo/os/$arch'
12

13
14
15
function init() {
  readonly ORIG_PWD="${PWD}"
  readonly OUTPUT="${PWD}/output"
16
17
18
  local tmpdir
  tmpdir="$(mktemp --dry-run --directory --tmpdir="${PWD}/tmp")"
  readonly TMPDIR="${tmpdir}"
19
  mkdir -p "${OUTPUT}" "${TMPDIR}"
20
  if [ -n "${SUDO_UID:-}" ] && [[ -n "${SUDO_GID:-}" ]]; then
21
22
23
    chown "${SUDO_UID}:${SUDO_GID}" "${OUTPUT}" "${TMPDIR}"
  fi
  cd "${TMPDIR}"
24

25
26
27
  readonly MOUNT="${PWD}/mount"
  mkdir "${MOUNT}"
}
28

Kristian Klausen's avatar
Kristian Klausen committed
29
# Do some cleanup when the script exits
30
function cleanup() {
Kristian Klausen's avatar
Kristian Klausen committed
31
  # We want all the commands to run, even if one of them fails.
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  set +o errexit
  if [ -n "${LOOPDEV:-}" ]; then
    losetup -d "${LOOPDEV}"
  fi
  if [ -n "${MOUNT:-}" ] && mountpoint -q "${MOUNT}"; then
    # We do not want risking deleting ex: the package cache
    umount --recursive "${MOUNT}" || exit 1
  fi
  if [ -n "${TMPDIR:-}" ]; then
    rm -rf "${TMPDIR}"
  fi
}
trap cleanup EXIT

Kristian Klausen's avatar
Kristian Klausen committed
46
# Create the disk, partitions it, format the partition and mount the filesystem
47
function setup_disk() {
48
  truncate -s "${DEFAULT_DISK_SIZE}" "${IMAGE}"
49
50
51
52
53
54
  sgdisk --clear \
    --new 1::+1M --typecode=1:ef02 \
    --new 2::-0 --typecode=2:8300 \
    "${IMAGE}"

  LOOPDEV=$(losetup --find --partscan --show "${IMAGE}")
55
  # Partscan is racy
56
  wait_until_settled "${LOOPDEV}"
57
58
59
60
  mkfs.btrfs "${LOOPDEV}p2"
  mount -o compress-force=zstd "${LOOPDEV}p2" "${MOUNT}"
}

Kristian Klausen's avatar
Kristian Klausen committed
61
# Install Arch Linux to the filesystem (bootstrap)
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
function bootstrap() {
  cat <<EOF >pacman.conf
[options]
Architecture = auto

[core]
Include = mirrorlist

[extra]
Include = mirrorlist

[community]
Include = mirrorlist
EOF
  echo "Server = ${MIRROR}" >mirrorlist

  # We use the hosts package cache
Kristian Klausen's avatar
Kristian Klausen committed
79
  pacstrap -c -C pacman.conf -M "${MOUNT}" base linux grub openssh sudo btrfs-progs reflector
80
81
82
  cp mirrorlist "${MOUNT}/etc/pacman.d/"
}

Kristian Klausen's avatar
Kristian Klausen committed
83
# Cleanup the image and trim it
84
85
86
87
function image_cleanup() {
  # Remove pacman key ring for re-initialization
  rm -rf "${MOUNT}/etc/pacman.d/gnupg/"

88
89
90
91
92
93
94
95
96
  # The mkinitcpio autodetect hook removes modules not needed by the
  # running system from the initramfs. This make the image non-bootable
  # on some systems as initramfs lacks the relevant kernel modules.
  # Ex: Some systems need the virtio-scsi kernel module and not the
  # "autodetected" virtio-blk kernel module for disk access.
  #
  # So for the initial install we use the fallback initramfs, and
  # "autodetect" should add the relevant modules to the initramfs when
  # the user updates the kernel.
97
  cp --reflink=always -a "${MOUNT}/boot/"{initramfs-linux-fallback.img,initramfs-linux.img}
98

99
100
101
102
  sync -f "${MOUNT}/etc/os-release"
  fstrim --verbose "${MOUNT}"
}

103
104
105
106
# Helper function: wait until a given loop device has settled
# ${1} - loop device
function wait_until_settled() {
  udevadm settle
Kristian Klausen's avatar
Kristian Klausen committed
107
  blockdev --flushbufs --rereadpt "${1}"
108
  until test -e "${1}p2"; do
Kristian Klausen's avatar
Kristian Klausen committed
109
110
    echo "${1}p2 doesn't exist yet..."
    sleep 1
111
112
113
  done
}

Kristian Klausen's avatar
Kristian Klausen committed
114
# Mount image helper (loop device + mount)
115
116
function mount_image() {
  LOOPDEV=$(losetup --find --partscan --show "${1:-${IMAGE}}")
117
  # Partscan is racy
Kristian Klausen's avatar
Kristian Klausen committed
118
  wait_until_settled "${LOOPDEV}"
119
120
121
122
123
  mount -o compress-force=zstd "${LOOPDEV}p2" "${MOUNT}"
  # Setup bind mount to package cache
  mount --bind "/var/cache/pacman/pkg" "${MOUNT}/var/cache/pacman/pkg"
}

Kristian Klausen's avatar
Kristian Klausen committed
124
# Unmount image helper (umount + detach loop device)
125
126
127
128
129
130
function unmount_image() {
  umount --recursive "${MOUNT}"
  losetup -d "${LOOPDEV}"
  LOOPDEV=""
}

Kristian Klausen's avatar
Kristian Klausen committed
131
# Compute SHA256, adjust owner to $SUDO_UID:$SUDO_UID and move to output/
132
133
134
135
136
137
138
139
function mv_to_output() {
  sha256sum "${1}" >"${1}.SHA256"
  if [ -n "${SUDO_UID:-}" ]; then
    chown "${SUDO_UID}:${SUDO_GID}" "${1}"{,.SHA256}
  fi
  mv "${1}"{,.SHA256} "${OUTPUT}/"
}

Kristian Klausen's avatar
Kristian Klausen committed
140
# Helper function: create a new image from the "base" image
Kristian Klausen's avatar
Kristian Klausen committed
141
142
143
# ${1} - final file
# ${2} - pre
# ${3} - post
144
function create_image() {
Kristian Klausen's avatar
Kristian Klausen committed
145
146
  local tmp_image
  tmp_image="$(basename "$(mktemp -u)")"
147
148
149
150
151
152
153
154
155
156
157
158
159
  cp -a "${IMAGE}" "${tmp_image}"
  if [ -n "${DISK_SIZE}" ]; then
    truncate -s "${DISK_SIZE}" "${tmp_image}"
    sgdisk --delete 2 "${tmp_image}"
    sgdisk --move-second-header \
      --new 2::-0 --typecode=2:8300 \
      "${tmp_image}"
  fi
  mount_image "${tmp_image}"
  if [ -n "${DISK_SIZE}" ]; then
    btrfs filesystem resize max "${MOUNT}"
  fi

160
161
162
163
164
165
  if [ 0 -lt "${#PACKAGES[@]}" ]; then
    arch-chroot "${MOUNT}" /usr/bin/pacman -S --noconfirm "${PACKAGES[@]}"
  fi
  if [ 0 -lt "${#SERVICES[@]}" ]; then
    arch-chroot "${MOUNT}" /usr/bin/systemctl enable "${SERVICES[@]}"
  fi
Kristian Klausen's avatar
Kristian Klausen committed
166
  "${2}"
167
168
  image_cleanup
  unmount_image
Kristian Klausen's avatar
Kristian Klausen committed
169
170
  "${3}" "${tmp_image}" "${1}"
  mv_to_output "${1}"
171
172
}

173
# ${1} - Optional build version. If not set, will generate a default based on date.
174
175
176
177
178
function main() {
  if [ "$(id -u)" -ne 0 ]; then
    echo "root is required"
    exit 1
  fi
Kristian Klausen's avatar
Kristian Klausen committed
179
  init
180

181
182
  setup_disk
  bootstrap
183
  # shellcheck source=images/base.sh
184
  source "${ORIG_PWD}/images/base.sh"
185
  pre
186
187
  unmount_image

188
189
  local build_version
  if [ -z "${1:-}" ]; then
190
    build_version="$(date +%Y%m%d).0"
191
192
193
194
    echo "WARNING: BUILD_VERSION wasn't set!"
    echo "Falling back to $build_version"
  else
    build_version="${1}"
195
  fi
196
197

  # shellcheck source=images/common.sh
198
  source "${ORIG_PWD}/images/common.sh"
199
200
  for image in "${ORIG_PWD}/images/"!(base|common).sh; do
    # shellcheck source=/dev/null
201
    source "${image}"
202
203
    create_image "${IMAGE_NAME}" pre post
  done
204
}
205
main "$@"