-
Notifications
You must be signed in to change notification settings - Fork 59
/
Copy pathbuild-arch-gce
executable file
·200 lines (174 loc) · 6.54 KB
/
build-arch-gce
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
#!/bin/bash
# Copyright 2018 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -eEuo pipefail
trap 'echo "Error: \`$BASH_COMMAND\` exited with status $?"' ERR
if (( EUID != 0 )); then
echo 'This script must be run with root privileges.'
exit 1
fi
# Setup cleanup trap to remove all temporary data.
cleanup() {
echo '- Cleaning up.'
[[ ${mount_dir:-} ]] && umount --recursive -- "$mount_dir"
[[ ${loop_dev:-} ]] && losetup --detach "$loop_dev"
[[ ${work_dir:-} ]] && rm -r -- "$work_dir"
return 0
}
trap cleanup EXIT
echo '- Creating an empty raw disk image.'
work_dir=$(mktemp --directory --tmpdir="$PWD" build-arch-gce.XXX)
disk_raw=$work_dir/disk.raw
truncate --size=10G -- "$disk_raw"
echo '- Setting up a loop device and partitioning the image.'
loop_dev=$(losetup --find --partscan --show -- "$disk_raw")
sfdisk --quiet -- "$loop_dev" <<-'EOF'
label:gpt
type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B,size=300MiB,name=boot
type=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709,name=root
EOF
echo '- Formatting the root partition.'
root_dev=${loop_dev}p2
mkfs.ext4 -q -L root -- "$root_dev"
tune2fs -c 0 -i 0 -- "$root_dev"
echo '- Formatting the boot partition.'
boot_dev=${loop_dev}p1
mkfs.vfat -F32 -n BOOT -- "$boot_dev"
echo '- Mounting the root partition.'
mount_dir=$work_dir/disk.mnt
mkdir -- "$mount_dir"
mount -- "$root_dev" "$mount_dir"
echo '- Mounting the boot partition.'
mkdir -- "$mount_dir/boot"
mount -- "$boot_dev" "$mount_dir/boot"
echo '- Installing Arch Linux.'
append_gce_repo() {
gawk -i inplace '
/^\[gce\]$/ { found = 1 } { print }
ENDFILE { if (!found) {
print ""
print "[gce]"
print "Server = https://storage.googleapis.com/arch-linux-gce/repo"
print "SigLevel = Optional TrustAll"
} }' "$1"
}
cp /etc/pacman.conf "$work_dir"
append_gce_repo "$work_dir/pacman.conf"
pacstrap -G -M -C "$work_dir/pacman.conf" -- "$mount_dir" \
base linux dosfstools e2fsprogs dhclient openssh sudo google-compute-engine
append_gce_repo "$mount_dir/etc/pacman.conf"
echo '- Configuring fstab.'
root_uuid=$(lsblk --noheadings --raw --output UUID -- "$root_dev")
boot_uuid=$(lsblk --noheadings --raw --output UUID -- "$boot_dev")
print_fstab() {
printf '# LABEL=%s\n' "$1"
printf 'UUID=%-20s' "$2"
printf '\t%-10s' "$3" "$4" "$5"
printf '\t%s %s' "$6" "$7"
printf '\n\n'
} >> "$mount_dir/etc/fstab"
{
print_fstab root "$root_uuid" / ext4 rw,discard,errors=remount-ro,x-systemd.growfs 0 1
print_fstab boot "$boot_uuid" /boot vfat uid=root,gid=root,umask=022,showexec 0 0
}
echo '- Running additional setup in chroot.'
arch-chroot -- "$mount_dir" /bin/bash -s <<-'EOS'
set -eEuo pipefail
trap 'echo "Error: \`$BASH_COMMAND\` exited with status $?"' ERR
echo '-- Configuring time.'
ln -sf /usr/share/zoneinfo/UTC /etc/localtime
gawk -i assert -i inplace '
/^#NTP=/ { $0 = "NTP=metadata.google.internal"; ++f }
{ print } END { assert(f == 1, "f == 1") }' /etc/systemd/timesyncd.conf
systemctl --quiet enable systemd-timesyncd.service
echo '-- Configuring locale.'
gawk -i assert -i inplace '
/^#en_US\.UTF-8 UTF-8\s*$/ { $0 = substr($0, 2); ++f }
{ print } END { assert(f == 1, "f == 1") }' /etc/locale.gen
locale-gen
echo 'LANG=en_US.UTF-8' > /etc/locale.conf
echo '-- Configuring journald.'
gawk -i assert -i inplace '
/^#ForwardToConsole=/ { $0 = "ForwardToConsole=yes"; ++f }
{ print } END { assert(f == 1, "f == 1") }' /etc/systemd/journald.conf
echo '-- Configuring ssh.'
gawk -i assert -i inplace '
/^#PasswordAuthentication / { $0 = "PasswordAuthentication no"; ++f1 }
/^#PermitRootLogin / { $0 = "PermitRootLogin no"; ++f2 }
{ print } END { assert(f1 * f2 == 1, "f == 1") }' /etc/ssh/sshd_config
systemctl --quiet enable sshd.service
echo '-- Configuring pacman.'
curl --silent --show-error -o /etc/pacman.d/mirrorlist \
'https://archlinux.org/mirrorlist/?country=all&ip_version=4&use_mirror_status=on'
gawk -i assert -i inplace '
/^#Server / { $0 = substr($0, 2); ++f }
{ print } END { assert(f > 0, "f > 0") }' /etc/pacman.d/mirrorlist
cat <<-'EOF' > /etc/systemd/system/pacman-init.service
[Unit]
Description=Pacman keyring initialization
ConditionDirectoryNotEmpty=!/etc/pacman.d/gnupg
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/pacman-key --init
ExecStart=/usr/bin/pacman-key --populate archlinux
[Install]
WantedBy=multi-user.target
EOF
systemctl --quiet enable pacman-init.service
echo '-- Configuring systemd-repart.'
mkdir /etc/repart.d
cat <<-'EOF' > /etc/repart.d/root.conf
[Partition]
Type=root
EOF
mkdir /etc/systemd/system/systemd-repart.service.d
cat <<-'EOF' > /etc/systemd/system/systemd-repart.service.d/override.conf
[Unit]
Before=systemd-growfs@-.service
EOF
echo '-- Enabling other services.'
systemctl --quiet enable dhclient@eth0.service
echo '-- Configuring initcpio.'
gawk -i assert -i inplace '
/^MODULES=/ { $0 = "MODULES=(virtio_pci virtio_scsi sd_mod ext4)"; ++f1 }
/^BINARIES=/ { $0 = "BINARIES=(fsck fsck.ext4)"; ++f2 }
/^HOOKS=/ { $0 = "HOOKS=(systemd modconf)"; ++f3 }
{ print } END { assert(f1 * f2 * f3 == 1, "f == 1") }' /etc/mkinitcpio.conf
gawk -i assert -i inplace '
/^PRESETS=/ { $0 = "PRESETS=(default)"; ++f }
/#?fallback_/ { next }
{ print } END { assert(f == 1, "f == 1") }' /etc/mkinitcpio.d/linux.preset
rm /boot/initramfs-linux-fallback.img
mkinitcpio --nocolor --preset linux
echo '-- Configuring systemd-boot.'
bootctl install --no-variables --esp-path=/boot
echo 'default arch.conf' > /boot/loader/loader.conf
cat <<-'EOF' > /boot/loader/entries/arch.conf
title Arch Linux
linux /vmlinuz-linux
initrd /initramfs-linux.img
options root="LABEL=root" rw console=ttyS0,38400n8 net.ifnames=0 scsi_mod.use_blk_mq=Y
EOF
EOS
echo '- Cleaning up and finalizing the image.'
> "$mount_dir/etc/machine-id"
rm -- "$mount_dir/var/log/pacman.log"
umount --recursive -- "$mount_dir"
unset mount_dir
echo '- Building the compressed image.'
disk_tar="arch-v$(date --utc +%Y%m%d).tar.gz"
tar --sparse -czf "$work_dir/$disk_tar" --directory="$work_dir" disk.raw
mv -- "$work_dir/$disk_tar" .
echo "Successfully built image \`$disk_tar\`."