-
-
Notifications
You must be signed in to change notification settings - Fork 65
/
build.sh
executable file
·209 lines (186 loc) · 6.09 KB
/
build.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#!/bin/bash
# Build virtual machine images (cloud image, vagrant boxes)
# nounset: "Treat unset variables and parameters [...] as an error when performing parameter expansion."
# errexit: "Exit immediately if [...] command exits with a non-zero status."
set -o nounset -o errexit
shopt -s extglob
readonly DEFAULT_DISK_SIZE="2G"
readonly IMAGE="image.img"
# shellcheck disable=SC2016
readonly MIRROR='https://geo.mirror.pkgbuild.com/$repo/os/$arch'
function init() {
readonly ORIG_PWD="${PWD}"
readonly OUTPUT="${PWD}/output"
local tmpdir
tmpdir="$(mktemp --dry-run --directory --tmpdir="${PWD}/tmp")"
readonly TMPDIR="${tmpdir}"
mkdir -p "${OUTPUT}" "${TMPDIR}"
if [ -n "${SUDO_UID:-}" ] && [[ -n "${SUDO_GID:-}" ]]; then
chown "${SUDO_UID}:${SUDO_GID}" "${OUTPUT}" "${TMPDIR}"
fi
cd "${TMPDIR}"
readonly MOUNT="${PWD}/mount"
mkdir "${MOUNT}"
}
# Do some cleanup when the script exits
function cleanup() {
# We want all the commands to run, even if one of them fails.
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
# Create the disk, partitions it, format the partition and mount the filesystem
function setup_disk() {
truncate -s "${DEFAULT_DISK_SIZE}" "${IMAGE}"
sgdisk --align-end \
--clear \
--new 0:0:+1M --typecode=0:ef02 --change-name=0:'BIOS boot partition' \
--new 0:0:+300M --typecode=0:ef00 --change-name=0:'EFI system partition' \
--new 0:0:0 --typecode=0:8304 --change-name=0:'Arch Linux root' \
"${IMAGE}"
LOOPDEV=$(losetup --find --partscan --show "${IMAGE}")
# Partscan is racy
wait_until_settled "${LOOPDEV}"
mkfs.fat -F 32 -S 4096 "${LOOPDEV}p2"
mkfs.btrfs "${LOOPDEV}p3"
mount -o compress-force=zstd "${LOOPDEV}p3" "${MOUNT}"
mount --mkdir "${LOOPDEV}p2" "${MOUNT}/efi"
}
# Install Arch Linux to the filesystem (bootstrap)
function bootstrap() {
cat <<EOF >pacman.conf
[options]
Architecture = auto
[core]
Include = mirrorlist
[extra]
Include = mirrorlist
EOF
echo "Server = ${MIRROR}" >mirrorlist
# We use the hosts package cache
pacstrap -c -C pacman.conf -K -M "${MOUNT}" base linux grub openssh sudo btrfs-progs dosfstools efibootmgr
# Workaround for https://gitlab.archlinux.org/archlinux/arch-install-scripts/-/issues/56
gpgconf --homedir "${MOUNT}/etc/pacman.d/gnupg" --kill gpg-agent
cp mirrorlist "${MOUNT}/etc/pacman.d/"
}
# Cleanup the image and trim it
function image_cleanup() {
# Remove pacman key ring for re-initialization
rm -rf "${MOUNT}/etc/pacman.d/gnupg/"
# 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.
cp --reflink=always -a "${MOUNT}/boot/"{initramfs-linux-fallback.img,initramfs-linux.img}
sync -f "${MOUNT}/etc/os-release"
fstrim --verbose "${MOUNT}"
fstrim --verbose "${MOUNT}/efi"
}
# Helper function: wait until a given loop device has settled
# ${1} - loop device
function wait_until_settled() {
udevadm settle
blockdev --flushbufs --rereadpt "${1}"
until test -e "${1}p3"; do
echo "${1}p3 doesn't exist yet..."
sleep 1
done
}
# Mount image helper (loop device + mount)
function mount_image() {
LOOPDEV=$(losetup --find --partscan --show "${1:-${IMAGE}}")
# Partscan is racy
wait_until_settled "${LOOPDEV}"
mount -o compress-force=zstd "${LOOPDEV}p3" "${MOUNT}"
# Setup bind mount to package cache
mount --bind "/var/cache/pacman/pkg" "${MOUNT}/var/cache/pacman/pkg"
}
# Unmount image helper (umount + detach loop device)
function unmount_image() {
umount --recursive "${MOUNT}"
losetup -d "${LOOPDEV}"
LOOPDEV=""
}
# Compute SHA256, adjust owner to $SUDO_UID:$SUDO_UID and move to output/
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}/"
}
# Helper function: create a new image from the "base" image
# ${1} - final file
# ${2} - pre
# ${3} - post
function create_image() {
local tmp_image
tmp_image="$(basename "$(mktemp -u)")"
cp -a "${IMAGE}" "${tmp_image}"
if [ -n "${DISK_SIZE}" ]; then
truncate -s "${DISK_SIZE}" "${tmp_image}"
sgdisk --align-end --delete 3 "${tmp_image}"
sgdisk --align-end --move-second-header \
--new 0:0:0 --typecode=0:8304 --change-name=0:'Arch Linux root' \
"${tmp_image}"
fi
mount_image "${tmp_image}"
if [ -n "${DISK_SIZE}" ]; then
btrfs filesystem resize max "${MOUNT}"
fi
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
"${2}"
image_cleanup
unmount_image
"${3}" "${tmp_image}" "${1}"
mv_to_output "${1}"
}
# ${1} - Optional build version. If not set, will generate a default based on date.
function main() {
if [ "$(id -u)" -ne 0 ]; then
echo "root is required"
exit 1
fi
init
setup_disk
bootstrap
# shellcheck source=images/base.sh
source "${ORIG_PWD}/images/base.sh"
pre
unmount_image
local build_version
if [ -z "${1:-}" ]; then
build_version="$(date +%Y%m%d).0"
echo "WARNING: BUILD_VERSION wasn't set!"
echo "Falling back to $build_version"
else
build_version="${1}"
fi
# shellcheck source=images/common.sh
source "${ORIG_PWD}/images/common.sh"
for image in "${ORIG_PWD}/images/"!(base|common).sh; do
# shellcheck source=/dev/null
source "${image}"
create_image "${IMAGE_NAME}" pre post
done
}
main "$@"