This repository has been archived by the owner on Jan 25, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbuild_image.py
296 lines (233 loc) · 12.6 KB
/
build_image.py
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#!/usr/bin/env python3
# This script is cloud oriented, therefore it is not very user-friendly.
import argparse
from functions import *
# parse arguments from the cli. Only for testing/advanced use. All other parameters are handled by cli_input.py
def process_args():
parser = argparse.ArgumentParser()
parser.add_argument("--dev", dest="dev_build", default=False, help="Use latest dev build. May be unstable.")
parser.add_argument("--chromeos", dest="stable", default=False, help="Use chromeos stable kernel.")
return parser.parse_args()
# Create, mount, partition the img and flash the mainline eupnea kernel
def prepare_image() -> str:
print_status("Preparing image")
try:
bash("fallocate -l 10G eupneaos.bin")
except subprocess.CalledProcessError: # try fallocate, if it fails use dd
bash(f"dd if=/dev/zero of=eupneaos.bin status=progress bs=1024 count={10 * 1000000}")
print_status("Mounting empty image")
img_mnt = bash("losetup -f --show eupneaos.bin")
if img_mnt == "":
print_error("Failed to mount image")
exit(1)
# partition image
print_status("Preparing device/image partition")
# format as per depthcharge requirements, but with a boot partition for uefi
# READ: https://wiki.gentoo.org/wiki/Creating_bootable_media_for_depthcharge_based_devices
bash(f"parted -s {img_mnt} mklabel gpt")
bash(f"parted -s -a optimal {img_mnt} unit mib mkpart Kernel 1 65") # kernel partition
bash(f"parted -s -a optimal {img_mnt} unit mib mkpart Kernel 65 129") # reserve kernel partition
bash(f"parted -s -a optimal {img_mnt} unit mib mkpart ESP 129 629") # EFI System Partition
bash(f"parted -s -a optimal {img_mnt} unit mib mkpart Root 629 100%") # rootfs partition
bash(f"cgpt add -i 1 -t kernel -S 1 -T 5 -P 15 {img_mnt}") # set kernel flags
bash(f"cgpt add -i 2 -t kernel -S 1 -T 5 -P 1 {img_mnt}") # set backup kernel flags
bash(f"cgpt add -i 3 -t efi {img_mnt}") # Set ESP type to efi
print_status("Formatting rootfs part")
# Format boot
bash(f"yes 2>/dev/null | mkfs.vfat -F32 {img_mnt}p3") # 2>/dev/null is to supress yes broken pipe warning
# Create rootfs ext4 partition
bash(f"yes 2>/dev/null | mkfs.ext4 {img_mnt}p4") # 2>/dev/null is to supress yes broken pipe warning
# Mount rootfs partition
bash(f"mount {img_mnt}p4 /mnt/eupneaos")
# Mount esp
bash("mkdir -p /mnt/eupneaos/boot")
bash(f"mount {img_mnt}p3 /mnt/eupneaos/boot")
# get uuid of rootfs partition
rootfs_partuuid = bash(f"blkid -o value -s PARTUUID {img_mnt}p4")
# write PARTUUID to kernel flags and save it as a file
with open("configs/kernel.flags", "r") as flags:
temp_cmdline = flags.read().replace("insert_partuuid", rootfs_partuuid).strip()
with open("kernel.flags", "w") as config:
config.write(temp_cmdline)
print_status("Partitioning complete")
flash_kernel(f"{img_mnt}p1")
flash_kernel(f"{img_mnt}p2") # flash reserve kernel
return img_mnt
def flash_kernel(kernel_part: str) -> None:
print_status("Flashing kernel to image")
# Sign kernel
bash("futility vbutil_kernel --arch x86_64 --version 1 --keyblock /usr/share/vboot/devkeys/kernel.keyblock"
+ " --signprivate /usr/share/vboot/devkeys/kernel_data_key.vbprivk --bootloader kernel.flags" +
" --config kernel.flags --vmlinuz /tmp/eupneaos-build/bzImage --pack /tmp/eupneaos-build/bzImage.signed")
bash(f"dd if=/tmp/eupneaos-build/bzImage.signed of={kernel_part}") # part 1 is the kernel partition
cpfile("/tmp/eupneaos-build/bzImage", "/mnt/eupneaos/boot/vmlinuz-eupnea") # Copy kernel to /boot for uefi
print_status("Kernel flashed successfully")
def get_uuids(img_mnt: str) -> list:
bootuuid = bash(f"blkid -o value -s PARTUUID {img_mnt}p3")
rootuuid = bash(f"blkid -o value -s PARTUUID {img_mnt}p4")
return [bootuuid, rootuuid]
# Make a bootable rootfs
def bootstrap_rootfs() -> None:
bash("tar xfp /tmp/eupneaos-build/rootfs.tar.xz -C /mnt/eupneaos --checkpoint=.10000")
# Create a temporary resolv.conf for internet inside the chroot
mkdir("/mnt/eupneaos/run/systemd/resolve", create_parents=True) # dir doesnt exist coz systemd didn't run
cpfile("/etc/resolv.conf",
"/mnt/eupneaos/run/systemd/resolve/stub-resolv.conf") # copy hosts resolv.conf to chroot
# TODO: Replace generic repos with own EupneaOS repos
chroot("dnf install --releasever=37 --allowerasing -y generic-logos generic-release generic-release-common")
# Add eupnea repo
chroot("dnf config-manager --add-repo https://eupnea-linux.github.io/rpm-repo/eupnea.repo")
# Add RPMFusion repos
chroot("dnf install -y https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-37.noarch.rpm")
chroot("dnf install -y https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-37.noarch.rpm")
chroot("dnf update --refresh -y") # update repos
chroot("dnf upgrade -y") # upgrade the whole system
# Install hardware support packages
chroot("dnf group install -y 'Hardware Support'")
chroot("dnf group install -y 'Common NetworkManager Submodules'")
chroot("dnf install -y linux-firmware")
chroot("dnf install -y eupnea-utils eupnea-system") # install eupnea packages
def configure_rootfs() -> None:
# copy previously downloaded firmware
print_status("Copying google firmware")
cpdir("linux-firmware", "/mnt/eupneaos/lib/firmware")
print_status("Configuring liveuser")
chroot("useradd --create-home --shell /bin/bash liveuser") # add user
chroot("usermod -aG wheel liveuser") # add user to wheel
chroot('echo "liveuser:eupneaos" | chpasswd') # set password to eupneaos
# set up automatic login on boot for temp-user
with open("/mnt/eupneaos/etc/sddm.conf", "a") as sddm_conf:
sddm_conf.write("\n[Autologin]\nUser=liveuser\nSession=plasma.desktop\n")
# copy preset eupnea settings file for postinstall scripts
cpfile("configs/eupnea.json", "/mnt/eupneaos/etc/eupnea.json")
print_status("Fixing sleep")
# disable hibernation aka S4 sleep, READ: https://eupnea-linux.github.io/docs/chromebook/bootlock
# TODO: Fix S4 sleep
mkdir("/mnt/eupneaos/etc/systemd/") # just in case systemd path doesn't exist
with open("/mnt/eupneaos/etc/systemd/sleep.conf", "a") as conf:
conf.write("SuspendState=freeze\nHibernateState=freeze\n")
# Remove stock fedora kernel
chroot("dnf remove kernel -y")
# Install eupnea kernel, modules and headers are installed automatically as dependencies
chroot("dnf install -y eupnea-mainline-kernel")
# systemd-resolved.service needed to create /etc/resolv.conf link. Not enabled by default for some reason
chroot("systemctl enable systemd-resolved")
# Append lines to fstab
with open("/mnt/eupneaos/etc/fstab", "a") as fstab:
fstab.write(f"PARTUUID={uuids[1]} / ext4 rw,relatime 0 1")
# Install systemd-bootd
# bootctl needs some paths mounted, arch-chroot does that automatically
bash('arch-chroot /mnt/eupneaos bash -c "bootctl install --esp-path=/boot"')
# Configure loader
with open("configs/sysdboot-eupnea.conf", "r") as conf:
temp_conf = conf.read().replace("insert_partuuid", uuids[1])
with open("configs/sysdboot-eupnea.conf", "w") as conf:
conf.write(temp_conf)
cpfile("configs/sysdboot-eupnea.conf", "/mnt/eupneaos/boot/loader/entries/eupnea.conf")
with open("/mnt/eupneaos/boot/loader/loader.conf", "w") as conf:
conf.write("default eupnea")
def customize_kde() -> None:
# Install KDE
chroot("dnf group install -y 'KDE Plasma Workspaces'")
# Set system to boot to gui
chroot("systemctl set-default graphical.target")
# Set kde ui settings
print_status("Setting General UI settings")
mkdir("/mnt/eupneaos/home/liveuser/.config")
cpfile("configs/kde-configs/kwinrc", "/mnt/eupneaos/home/liveuser/.config/kwinrc") # set general kwin settings
cpfile("configs/kde-configs/kcminputrc", "/mnt/eupneaos/home/liveuser/.config/kcminputrc") # set touchpad settings
chroot("chown -R liveuser:liveuser /home/liveuser/.config") # set permissions
print_status("Installing global kde theme")
# Installer needs to be run from within chroot
cpdir("eupneaos-theme", "/mnt/eupneaos/tmp/eupneaos-theme")
# run installer script for global kde theme from chroot
chroot("cd /tmp/eupneaos-theme && bash /tmp/eupneaos-theme/install.sh")
# apply global dark theme
def relabel_files() -> None:
# Fedora requires all files to be relabeled for SELinux to work
# If this is not done, SELinux will prevent users from logging in
print_status("Relabeling files for SELinux")
# copy /proc files needed for fixfiles
mkdir("/mnt/eupneaos/proc/self")
cpfile("configs/selinux/mounts", "/mnt/eupneaos/proc/self/mounts")
open("/mnt/eupneaos/proc/self/mountinfo", "w").close() # create empty /proc/self/mountinfo
# copy /sys files needed for fixfiles
# mkdir("/mnt/eupneaos/sys/fs/selinux/initial_contexts/", create_parents=True)
# cpfile("configs/selinux/unlabeled", "/mnt/eupneaos/sys/fs/selinux/initial_contexts/unlabeled")
# Backup original selinux
cpfile("/mnt/eupneaos/usr/sbin/fixfiles", "/mnt/eupneaos/usr/sbin/fixfiles.bak")
# Copy patched fixfiles script
cpfile("configs/selinux/fixfiles", "/mnt/eupneaos/usr/sbin/fixfiles")
chroot("/sbin/fixfiles restore")
# Restore original fixfiles script
cpfile("/mnt/eupneaos/usr/sbin/fixfiles.bak", "/mnt/eupneaos/usr/sbin/fixfiles")
rmfile("/mnt/eupneaos/usr/sbin/fixfiles.bak")
# Shrink image to actual size
def compress_image(img_mnt: str) -> None:
print_status("Shrinking image")
bash(f"e2fsck -fpv {img_mnt}p4") # Force check filesystem for errors
bash(f"resize2fs -f -M {img_mnt}p4")
block_count = int(bash(f"dumpe2fs -h {img_mnt}p4 | grep 'Block count:'")[12:].split()[0])
actual_fs_in_bytes = block_count * 4096
# the kernel part is always the same size -> sector amount: 131072 * 512 => 67108864 bytes
# There are 2 kernel partitions -> 67108864 bytes * 2 = 134217728 bytes
actual_fs_in_bytes += 134217728
# EFI partition is always the same size -> sector amount: 1024000 * 512 => 524288000 bytes
actual_fs_in_bytes += 524288000
actual_fs_in_bytes += 20971520 # add 20mb for linux to be able to boot properly
bash(f"truncate --size={actual_fs_in_bytes} ./eupneaos.bin")
# compress image to tar. Tars are smaller but the native file manager on chromeos cant uncompress them
# These are stored as backups in the GitHub releases
bash("tar -cv -I 'xz -9 -T0' -f ./eupneaos.bin.tar.xz ./eupneaos.bin")
# Rar archives are bigger, but natively supported by the ChromeOS file manager
# These are uploaded as artifacts and then manually uploaded to a cloud storage
bash("rar a eupneaos.bin.rar -m5 eupneaos.bin")
print_status("Calculating sha256sums")
# Calculate sha256sum sums
with open("eupneaos.sha256", "w") as file:
file.write(bash("sha256sum eupneaos.bin eupneaos.bin.tar.xz "
"eupneaos.bin.rar"))
def chroot(command: str) -> None:
bash(f'chroot /mnt/eupneaos /bin/bash -c "{command}"') # always print output
if __name__ == "__main__":
args = process_args() # process args
set_verbose(True) # increase verbosity
# parse arguments
kernel_type = "mainline"
if args.dev_build:
print_warning("Using dev release")
if args.stable:
print_warning("Using stable chromes kernel")
kernel_type = "stable"
# prepare mount
mkdir("/mnt/eupneaos", create_parents=True)
image_props = prepare_image()
uuids = get_uuids(image_props)
# # Bind mount directories
# print_status("Bind-mounting directories")
# mkdir("/mnt/eupneaos/dev")
# bash("mount --rbind /dev /mnt/eupneaos/dev")
# bash("mount --make-rslave /mnt/eupneaos/dev")
bootstrap_rootfs()
configure_rootfs()
customize_kde()
# unmount boot before relabeling
bash("umount -f /mnt/eupneaos/boot")
relabel_files()
# Clean image of temporary files
rmdir("/mnt/eupneaos/tmp")
rmdir("/mnt/eupneaos/var/tmp")
rmdir("/mnt/eupneaos/var/cache")
rmdir("/mnt/eupneaos/proc")
rmdir("/mnt/eupneaos/run")
rmdir("/mnt/eupneaos/sys")
rmdir("/mnt/eupneaos/lost+found")
rmdir("/mnt/eupneaos/dev")
rmfile("/mnt/eupneaos/.stop_progress")
bash("sync") # write all pending changes to image
# Force unmount image
bash("umount -f /mnt/eupneaos")
sleep(5) # wait for umount to finish
compress_image(image_props)
bash(f"losetup -d {image_props}") # unmount image
print_header("Image creation completed successfully!")