216 lines
8.2 KiB
Python
Executable File
216 lines
8.2 KiB
Python
Executable File
#!/bin/env python3
|
|
|
|
# Build a live ISO.
|
|
# Run this from an empty working directory,
|
|
# or a directory where you have run this before to reuse caches.
|
|
|
|
import argparse
|
|
import subprocess
|
|
import pathlib
|
|
import tomllib
|
|
import hashlib
|
|
import datetime
|
|
import urllib.request
|
|
|
|
|
|
DISTRIBUTION = "bookworm"
|
|
|
|
VARIANT_LABEL = {
|
|
"contestant": "contest",
|
|
"training-live": "live",
|
|
"training-installer": "install",
|
|
}
|
|
|
|
VARIANT_BOOT_OPTIONS = {
|
|
"contestant": [
|
|
dict(label="SOI contest", cmdline="boot=live toram=filesystem.squashfs"),
|
|
dict(label="SOI contest, run from drive", cmdline="boot=live"),
|
|
dict(label="SOI contest, fail-safe mode", cmdline="@LB_BOOTAPPEND_LIVE_FAILSAFE@"),
|
|
],
|
|
"training-live": [
|
|
dict(label="SOI live system, run from RAM", cmdline="boot=live toram=filesystem.squashfs"),
|
|
dict(label="SOI live system, run from drive", cmdline="boot=live"),
|
|
dict(label="SOI live system, fail-safe mode", cmdline="@LB_BOOTAPPEND_LIVE_FAILSAFE@"),
|
|
],
|
|
"training-installer": [],
|
|
}
|
|
|
|
VARIANT_EXTRA_LB_CONFIG = {
|
|
"training-installer": [
|
|
"--debian-installer", "live",
|
|
# Linux headers are needed for VirtualBox DKMS.
|
|
"--linux-packages", "linux-image linux-headers",
|
|
],
|
|
}
|
|
|
|
VARIANT_EXTRA_BOOTSTRAP = {
|
|
# Fasttrack keyring is needed for VirtualBox.
|
|
"training-installer": ",fasttrack-archive-keyring",
|
|
}
|
|
|
|
DOWNLOADS = [
|
|
dict(
|
|
name="soi-header.tar.gz",
|
|
url="https://git.soi.ch/SOI/soi-header/archive/19ddcef24eb55bdb5ddb817c1d91bfa04c8cb8dd.tar.gz",
|
|
sha256="38042587982af4e9431aea461e5c345bde358bcc79f0a0eadcf5b3ed77aeb8ab",
|
|
),
|
|
# From https://soi.ch/wiki/soi-codeblocks/#install-the-soi-project-template
|
|
dict(
|
|
name="soi_template_codeblocks_ubuntu.zip",
|
|
url="https://soi.ch/media/files/soi_template_codeblocks_ubuntu_RzdvSho.zip",
|
|
sha256="3f4cae26bbb0cbdfd4cf9f94bfce14988e395f847948d9b349c12d0b980386e9",
|
|
),
|
|
]
|
|
|
|
def run(args, check=True, **kwargs):
|
|
print(f"> {' '.join(args)}")
|
|
subprocess.run(args, check=check, **kwargs)
|
|
|
|
def mkdir(dirname):
|
|
pathlib.Path(dirname).mkdir(parents=True, exist_ok=True)
|
|
|
|
def edit_file(filename, fn):
|
|
with open(filename) as f:
|
|
content = f.read()
|
|
content = fn(content)
|
|
with open(filename, "w") as f:
|
|
f.write(content)
|
|
|
|
def sha256sum(filename):
|
|
with open(filename, "rb", buffering=0) as f:
|
|
return hashlib.file_digest(f, "sha256").hexdigest()
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"variant",
|
|
choices=["contestant", "training-live", "training-installer"],
|
|
help="Variant to build.",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
script_dir = pathlib.Path(__file__).parent.resolve()
|
|
|
|
with open(script_dir / "config/config.toml", "rb") as f:
|
|
config = tomllib.load(f)
|
|
|
|
# Remove files generated by previous build, but keep cache.
|
|
run("lb clean".split())
|
|
run("rm -rf .build config udeb-build".split())
|
|
|
|
# Download files.
|
|
mkdir("downloads")
|
|
for download in DOWNLOADS:
|
|
filename = f'downloads/{download["name"]}'
|
|
try:
|
|
if sha256sum(filename) == download["sha256"]:
|
|
continue
|
|
except FileNotFoundError:
|
|
pass
|
|
print(f'> Downloading {download["url"]}')
|
|
urllib.request.urlretrieve(download["url"], filename)
|
|
if sha256sum(filename) != download["sha256"]:
|
|
raise Exception(f"Downloaded file {filename} has wrong hash.")
|
|
|
|
# Create base configuration directory.
|
|
run(
|
|
[
|
|
"lb", "config",
|
|
"--clean",
|
|
"--ignore-system-defaults",
|
|
"--mode", "debian",
|
|
"--distribution", DISTRIBUTION,
|
|
"--archive-areas", "main contrib non-free non-free-firmware",
|
|
"--firmware-chroot", "false",
|
|
"--firmware-binary", "false",
|
|
"--apt-recommends", "false",
|
|
# isc-dhcp-client and ifupdown are obsoleted by network-manager,
|
|
# but they still have Priority: important.
|
|
# We need ca-certificates for fetching https packages repos.
|
|
"--debootstrap-options", "--exclude=isc-dhcp-client,isc-dhcp-common,ifupdown --include=ca-certificates" +
|
|
VARIANT_EXTRA_BOOTSTRAP.get(args.variant, ""),
|
|
"--loadlin", "false",
|
|
"--iso-volume", f"SOI {VARIANT_LABEL[args.variant]} @ISOVOLUME_TS@",
|
|
"--bootappend-live", "boot=live toram=filesystem.squashfs",
|
|
]
|
|
+ VARIANT_EXTRA_LB_CONFIG.get(args.variant, [])
|
|
)
|
|
|
|
# Add our own configuration on top.
|
|
run(["cp", "-rT", f"{script_dir}/layers/participant", "config"])
|
|
run(["cp", "-rT", f"{script_dir}/layers/{args.variant}", "config"])
|
|
|
|
if args.variant == "training-installer":
|
|
# Insert admin password into preseed.
|
|
edit_file("config/includes.installer/preseed.cfg",
|
|
lambda s: s.replace("@install_admin_password@", config["install_admin_password"]))
|
|
|
|
# Copy inventory file.
|
|
mkdir("config/includes.binary/install")
|
|
run(["cp", f"{script_dir}/config/installer-inventory.txt", "config/includes.binary/install/inventory.txt"])
|
|
|
|
# Insert build date in login screen logo.
|
|
edit_file("config/includes.chroot/usr/local/share/images/login-screen-logo.svg",
|
|
lambda s: s.replace("@date@", datetime.date.today().isoformat()))
|
|
|
|
# Build and install custom udeb packages for installer.
|
|
run(["cp", "-rT", f"{script_dir}/installer-udeb", "udeb-build"])
|
|
run("dpkg-buildpackage --build=all".split(), cwd="udeb-build/inventory-hostname")
|
|
mkdir("config/packages.binary")
|
|
run("cp udeb-build/inventory-hostname_0_all.udeb config/packages.binary/".split())
|
|
|
|
# Copy the source lists. The installer deletes everything in sources.list.d,
|
|
# so we need to copy them somewhere else and restore them after the install.
|
|
for listpath in pathlib.Path('config/archives').glob('*.list.chroot'):
|
|
run(["cp", str(listpath), f"config/includes.chroot/usr/local/share/target-sources/{listpath.name.removesuffix('.chroot')}"])
|
|
elif args.variant == "contestant":
|
|
# Insert root password into hook script.
|
|
edit_file("config/hooks/live/2010-contestant.hook.chroot",
|
|
lambda s: s.replace("@contestant_root_password@", config["contestant_root_password"]))
|
|
|
|
# Copy authorized_keys.
|
|
mkdir("config/includes.chroot/root/.ssh")
|
|
run(["cp", f"{script_dir}/config/contestant_authorized_keys", "config/includes.chroot/root/.ssh/authorized_keys"])
|
|
|
|
# Configure boot options.
|
|
grub_boot_options = '\n'.join(
|
|
f'menuentry "{option["label"]}" {{\n'
|
|
f' linux @KERNEL_LIVE@ {option["cmdline"]}\n'
|
|
f' initrd @INITRD_LIVE@\n'
|
|
f'}}\n'
|
|
for option in VARIANT_BOOT_OPTIONS[args.variant]
|
|
)
|
|
with open("/usr/share/live/build/bootloaders/grub-pc/grub.cfg") as f:
|
|
grub_cfg = f.read()
|
|
grub_cfg = grub_cfg.replace("@LINUX_LIVE@", grub_boot_options)
|
|
with open("config/bootloaders/grub-pc/grub.cfg", "w") as f:
|
|
f.write(grub_cfg)
|
|
|
|
syslinux_boot_options = ''.join(
|
|
f"label live-{i}\n"
|
|
f"\tmenu label {option['label']}\n"
|
|
+ (f"\tmenu default\n" if i == 0 else "") +
|
|
f"\tlinux @LINUX@\n"
|
|
f"\tinitrd @INITRD@\n"
|
|
f"\tappend {option['cmdline'].replace('@LB_BOOTAPPEND_LIVE_FAILSAFE@', '@APPEND_LIVE_FAILSAFE@')}\n"
|
|
f"\n"
|
|
for i, option in enumerate(VARIANT_BOOT_OPTIONS[args.variant])
|
|
)
|
|
mkdir("config/bootloaders/syslinux_common")
|
|
with open("config/bootloaders/syslinux_common/live.cfg.in", "w") as f:
|
|
f.write(syslinux_boot_options)
|
|
|
|
# Install soi header.
|
|
mkdir("config/includes.chroot/usr/local/include")
|
|
run(["tar", "--overwrite", "-xf", "downloads/soi-header.tar.gz", "-C", "config/includes.chroot/usr/local/include", "--strip-components=2", "soi-header/include/"])
|
|
|
|
# Install codeblocks template.
|
|
mkdir("config/includes.chroot/usr/share/codeblocks/templates/wizard")
|
|
run(["unzip", "-o", "downloads/soi_template_codeblocks_ubuntu.zip", "-d", "config/includes.chroot/usr/share/codeblocks/templates/wizard/"])
|
|
|
|
# Start the build.
|
|
run("lb build".split())
|
|
|
|
if __name__ == "__main__":
|
|
main()
|