Dirty hacks to be able to inject 'raw' files to customize the install menu and branding

This commit is contained in:
Alexandre Aubin
2024-07-31 16:55:46 +02:00
parent 21f68af0e7
commit 2c50ccee25
10 changed files with 340 additions and 78 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
tags
**/__pycache__
**/.venv
*.iso

View File

@@ -12,14 +12,10 @@ This short guide explains how to build a Debian ISO with a customized and automa
1. make sure you have all the [required software](#build-machine) installed
2. clone this repo and `cd` into your local copy
3. (optional) create and activate a virtual environment: `python3 -m venv .venv && . .venv/bin/activate`
4. install the required python packages: `pip install --user -r requirements.txt`
5. download an example preseed file: `./udib.py -o preseed.cfg get preseed-file-basic`
6. customize your installation by editing `preseed.cfg` (the comments are pretty self-explanatory)
7. create a Debian ISO with your preseed file: `./udib.py -o my-image.iso inject preseed.cfg`
8. boot from your newly created ISO `my-image.iso` on your target machine (or in a VM)
9. in the Debian installer menu, navigate to *Advanced options > Automated install*
10. drink some coffee
11. return to your new Debian system
4. install the required python packages: `pip install -r requirements.txt`
5. all the Debian ISOs are available in <https://cdimage.debian.org/mirror/cdimage/archive/>
5. create the custom ISOs: `./udib.py inject -i debian-11.10.0-amd64-netinst.iso`
6. boot from the `-modified.iso` on your target machine (or in a VM)
Depending on how many answers you provided in the preseed file, the installation may require some manual interaction.
Preseed files are very powerful, and if you need more customization you can have a deeper look into [how they work](#whats-preseeding).

View File

@@ -61,14 +61,6 @@ def get_argument_parser():
)
# register arguments for the 'inject' subcommand
subparser_inject.add_argument(
"FILES",
action='store',
type=str,
nargs='+',
metavar='FILES',
help="Paths to all input files you want to inject",
)
subparser_inject.add_argument(
"-i",
"--image-file",

View File

@@ -0,0 +1,37 @@
menu hshift 11
menu width 57
menu title YunoHost installation
menu background splash.png
menu color title * #FFFFFFFF *
menu color border * #00000000 #00000000 none
menu color sel * #ffffffff #76a1d0ff *
menu color hotsel 1;7;37;40 #ffffffff #76a1d0ff *
menu color tabmsg * #ffffffff #00000000 *
menu color help 37;40 #ffdddd00 #00000000 none
menu vshift 16
menu rows 7
menu helpmsgrow 12
menu cmdlinerow 12
menu tabmsgrow 13
menu tabmsg
default installgui
label install
menu label ^Text install
kernel /install.amd/vmlinuz
append preseed/file=/cdrom/preseeds/default.preseed vga=788 initrd=/install.amd/initrd.gz -- quiet
label installgui
menu label ^Graphical install (recommended)
menu default
kernel /install.amd/vmlinuz
append preseed/file=/cdrom/preseeds/default.preseed video=vesa:ywrap,mtrr vga=788 initrd=/install.amd/gtk/initrd.gz -- quiet
label expert
menu label Expert text install
kernel /install.amd/vmlinuz
append preseed/file=/cdrom/preseeds/expert.preseed vga=788 initrd=/install.amd/initrd.gz -- quiet
label expertgui
menu label Expert graphical install
kernel /install.amd/vmlinuz
append preseed/file=/cdrom/preseeds/expert.preseed video=vesa:ywrap,mtrr vga=788 initrd=/install.amd/gtk/initrd.gz -- quiet

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
files_to_inject/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,132 @@
### Network configuration
d-i netcfg/choose_interface select auto
d-i netcfg/dhcp_failed note
d-i netcfg/dhcp_options select Configure network manually
d-i netcfg/disable_dhcp boolean false
d-i netcfg/get_hostname string yunohost
d-i netcfg/get_domain string yunohost.org
### Mirror settings
d-i mirror/country string manual
d-i mirror/http/hostname string ftp.debian.org
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string
### Localization
d-i tzdata/Areas select Europe
d-i localechooser/continentlist select Europe
d-i time/zone string Europe/Paris
d-i time/zone seen false
d-i clock-setup/utc boolean true
d-i clock-setup/ntp boolean true
d-i clock-setup/ntp-server string 0.fr.pool.ntp.org
### Partitioning
d-i partman-auto/method string lvm
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-auto/choose_recipe select atomic
d-i partman-lvm/device_remove_lvm_span boolean true
d-i partman-auto/purge_lvm_from_device boolean true
d-i partman-auto-lvm/new_vg_name string system
d-i partman-lvm/confirm boolean true
d-i partman-auto-lvm/guided_size string max
d-i partman-auto/expert_recipe string \
boot-root :: \
128 256 256 ext4 \
$primary{ } \
$bootable{ } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ /boot } \
. \
512 1024 1024 linux-swap \
$lvmok{ } \
lv_name{ swap } \
method{ swap } format{ } \
. \
4096 4096 1000000000 ext4 \
$lvmok{ } \
lv_name{ root } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ / } \
.
d-i partman/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
### Account setup
d-i passwd/root-password-crypted password $1$6xBdkGvE$8nLCNRxwABespdFJniEiX0
d-i passwd/make-user boolean false
### Base system installation
d-i apt-setup/non-free boolean false
d-i apt-setup/contrib boolean true
d-i apt-setup/services-select multiselect security, updates
d-i apt-setup/security_host string security.debian.org
d-i apt-setup/local0/repository string \
http://forge.yunohost.org/debian/ bullseye stable
d-i apt-setup/local0/key string http://forge.yunohost.org/yunohost_bullseye.asc
d-i apt-setup/local0/comment string YunoHost repository
#d-i debian-installer/allow_unauthenticated string true
# Skip "scan another CD/DVD"
# https://unix.stackexchange.com/a/409237
apt-cdrom-setup apt-setup/cdrom/set-next boolean false
d-i apt-setup/cdrom/set-first boolean false
d-i apt-setup/cdrom/set-next boolean false
d-i apt-setup/cdrom/set-failed boolean false
### Package selection
tasksel tasksel/first multiselect standard, ssh-server
# Keep postfix in here, otherwise exim4 might get installed and create a weird
# conflict...
d-i pkgsel/include string postfix yunohost yunohost-admin
d-i pkgsel/upgrade select none
### Deactivate poll on popular packages
popularity-contest popularity-contest/participate boolean false
debconf debconf/frontend select Noninteractive
### Boot loader installation
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
d-i grub-installer/bootdev string default
### Finishing up the installation
d-i finish-install/reboot_in_progress note
d-i cdrom-detect/eject boolean true
d-i debian-installer/exit/reboot boolean true
### YunoHost Debconf (same than in yunohost/install_script)
slapd slapd/password1 password yunohost
slapd slapd/password2 password yunohost
slapd slapd/domain string yunohost.org
slapd shared/organization string yunohost.org
slapd slapd/allow_ldap_v2 boolean false
slapd slapd/invalid_config boolean true
slapd slapd/backend select MDB
postfix postfix/main_mailer_type select Internet Site
postfix postfix/mailname string /etc/mailname
mysql-server-5.5 mysql-server/root_password password yunohost
mysql-server-5.5 mysql-server/root_password_again password yunohost
mariadb-server-10.0 mysql-server/root_password password yunohost
mariadb-server-10.0 mysql-server/root_password_again password yunohost
nslcd nslcd/ldap-bindpw password
nslc nslcd/ldap-starttls boolean false
nslcd nslcd/ldap-reqcert select
nslcd nslcd/ldap-uris string ldap://localhost/
nslcd nslcd/ldap-binddn string
nslcd nslcd/ldap-base string dc=yunohost,dc=org
libnss-ldapd libnss-ldapd/nsswitch multiselect group, passwd, shadow
postsrsd postsrsd/domain string yunohost.org
### Avoid circular dependencies error
d-i preseed/early_command string apt-install debhelper || true

View File

@@ -0,0 +1,133 @@
### Network configuration
d-i netcfg/choose_interface select auto
d-i netcfg/dhcp_failed note
d-i netcfg/dhcp_options select Configure network manually
d-i netcfg/disable_dhcp boolean false
d-i netcfg/get_hostname string yunohost
d-i netcfg/get_domain string yunohost.org
### Mirror settings
d-i mirror/country string manual
d-i mirror/http/hostname string ftp.debian.org
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string
d-i mirror/http/proxy seen false
### Localization
d-i tzdata/Areas select Europe
d-i localechooser/continentlist select Europe
d-i time/zone string Europe/Paris
d-i time/zone seen false
d-i clock-setup/utc boolean true
d-i clock-setup/ntp boolean true
d-i clock-setup/ntp-server string 0.fr.pool.ntp.org
### Partitioning
d-i partman-auto/method string lvm
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-auto/choose_recipe select atomic
d-i partman-lvm/device_remove_lvm_span boolean true
d-i partman-auto/purge_lvm_from_device boolean true
d-i partman-auto-lvm/new_vg_name string system
d-i partman-lvm/confirm boolean true
d-i partman-auto-lvm/guided_size string max
d-i partman-auto/expert_recipe string \
boot-root :: \
128 256 256 ext4 \
$primary{ } \
$bootable{ } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ /boot } \
. \
512 1024 1024 linux-swap \
$lvmok{ } \
lv_name{ swap } \
method{ swap } format{ } \
. \
4096 4096 1000000000 ext4 \
$lvmok{ } \
lv_name{ root } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ / } \
.
d-i partman/confirm_write_new_label boolean true
#d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
### Account setup
d-i passwd/root-password-crypted password $1$6xBdkGvE$8nLCNRxwABespdFJniEiX0
d-i passwd/make-user boolean false
### Base system installation
d-i apt-setup/non-free boolean false
d-i apt-setup/contrib boolean true
d-i apt-setup/services-select multiselect security, updates
d-i apt-setup/security_host string security.debian.org
d-i apt-setup/local0/repository string \
http://forge.yunohost.org/debian/ bullseye stable
d-i apt-setup/local0/key string http://forge.yunohost.org/yunohost_bullseye.asc
d-i apt-setup/local0/comment string YunoHost repository
#d-i debian-installer/allow_unauthenticated string true
# Skip "scan another CD/DVD"
# https://unix.stackexchange.com/a/409237
apt-cdrom-setup apt-setup/cdrom/set-next boolean false
d-i apt-setup/cdrom/set-first boolean false
d-i apt-setup/cdrom/set-next boolean false
d-i apt-setup/cdrom/set-failed boolean false
### Package selection
tasksel tasksel/first multiselect standard, ssh-server
# Keep postfix in here, otherwise exim4 might get installed and create a weird
# conflict...
d-i pkgsel/include string postfix yunohost yunohost-admin
d-i pkgsel/upgrade select none
### Deactivate poll on popular packages
popularity-contest popularity-contest/participate boolean false
debconf debconf/frontend select Noninteractive
### Boot loader installation
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
#d-i grub-installer/bootdev string default
### Finishing up the installation
d-i finish-install/reboot_in_progress note
d-i cdrom-detect/eject boolean true
d-i debian-installer/exit/reboot boolean true
### YunoHost Debconf (same than in yunohost/install_script)
slapd slapd/password1 password yunohost
slapd slapd/password2 password yunohost
slapd slapd/domain string yunohost.org
slapd shared/organization string yunohost.org
slapd slapd/allow_ldap_v2 boolean false
slapd slapd/invalid_config boolean true
slapd slapd/backend select MDB
postfix postfix/main_mailer_type select Internet Site
postfix postfix/mailname string /etc/mailname
mysql-server-5.5 mysql-server/root_password password yunohost
mysql-server-5.5 mysql-server/root_password_again password yunohost
mariadb-server-10.0 mysql-server/root_password password yunohost
mariadb-server-10.0 mysql-server/root_password_again password yunohost
nslcd nslcd/ldap-bindpw password
nslc nslcd/ldap-starttls boolean false
nslcd nslcd/ldap-reqcert select
nslcd nslcd/ldap-uris string ldap://localhost/
nslcd nslcd/ldap-binddn string
nslcd nslcd/ldap-base string dc=yunohost,dc=org
libnss-ldapd libnss-ldapd/nsswitch multiselect group, passwd, shadow
postsrsd postsrsd/domain string yunohost.org
### Avoid circular dependencies error
d-i preseed/early_command string apt-install debhelper || true

View File

@@ -6,7 +6,7 @@ inside the ISO and rebuilding bootable ISOs from directories on the
local filesystem.
"""
import os
from pathlib import Path
from tempfile import TemporaryDirectory
import gzip
@@ -80,7 +80,8 @@ def extract_iso(path_to_output_dir, path_to_input_file):
def append_file_contents_to_initrd_archive(
path_to_initrd_archive,
path_to_input_file
base_dir,
relative_path_to_input_file
):
"""Appends the input file to the specified initrd archive.
@@ -117,10 +118,6 @@ def append_file_contents_to_initrd_archive(
path_to_initrd_archive = Path(path_to_initrd_archive).expanduser()
path_to_initrd_archive = Path(path_to_initrd_archive).resolve()
if "~" in str(path_to_input_file):
path_to_input_file = Path(path_to_input_file).expanduser()
path_to_input_file = Path(path_to_input_file).resolve()
# check if initrd file exists and has the correct name
if not path_to_initrd_archive.is_file():
raise FileNotFoundError(f"No such file: '{path_to_initrd_archive}'.")
@@ -128,10 +125,6 @@ def append_file_contents_to_initrd_archive(
raise AssertionError(f"Does not seem to be an initrd.gz archive: "
f"'{path_to_initrd_archive.name}'.")
# check if input file exists
if not path_to_input_file.is_file():
raise FileNotFoundError(f"No such file: '{path_to_input_file}'.")
# make archive and its parent directory temporarily writable
path_to_initrd_archive.chmod(0o644)
path_to_initrd_archive.parent.chmod(0o755)
@@ -153,18 +146,18 @@ def append_file_contents_to_initrd_archive(
"cpio", "-H", "newc", "-o", "-A",
"-F", str(path_to_initrd_extracted.resolve())
],
cwd=path_to_input_file.resolve().parent,
cwd=base_dir,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
completed_process.communicate(
input=str(path_to_input_file.name).encode()
input=str(relative_path_to_input_file).encode()
)
except subprocess.CalledProcessError:
raise RuntimeError(
f"Failed while appending contents of '{path_to_input_file}' to "
f"Failed while appending contents of '{relative_path_to_input_file}' to "
f"'{path_to_initrd_archive}'."
)
@@ -420,7 +413,6 @@ def repack_iso(path_to_output_iso,
def inject_files_into_iso(
path_to_output_iso_file,
path_to_input_iso_file,
input_file_paths,
iso_filesystem_name="Debian",
printer=None,
):
@@ -461,17 +453,6 @@ def inject_files_into_iso(
if not path_to_output_iso_file.parent.is_dir():
raise NotADirectoryError(f"No such directory: '{path_to_output_iso_file.parent}'.")
input_file_paths_old = input_file_paths.copy()
input_file_paths = []
for path in input_file_paths_old:
if "~" in str(path):
path = Path(path).expanduser()
path = Path(path).resolve()
if not path.is_file():
raise FileNotFoundError(f"No such file: '{path}'.")
input_file_paths.append(path)
del input_file_paths_old
if printer is None:
p = Printer()
else:
@@ -500,18 +481,28 @@ def inject_files_into_iso(
)
p.ok("MBR extraction complete.")
# append all input files to the extracted ISO's initrd
# For some reason this 'xen' thing takes up an extra 50-70ish MB compared
# to the original iso ... not sure to understand ... but doesn't seem to be
# actually used anywhere so let's get rid of it to save space ...
os.system(f"chmod +w '{path_to_extracted_iso_dir}/install.amd'")
os.system(f"chmod -R +w '{path_to_extracted_iso_dir}/install.amd/xen'")
os.system(f"rm -rf '{path_to_extracted_iso_dir}/install.amd/xen'")
os.system(f"chmod -w '{path_to_extracted_iso_dir}/install.amd'")
# ADd the input files to the extracted ISO
os.system(f"chmod +w {path_to_extracted_iso_dir}/isolinux/*")
os.system(f"cp -r ./files_to_inject/* {path_to_extracted_iso_dir}/")
os.system(f"chmod -w {path_to_extracted_iso_dir}/isolinux/*")
# This stuff gotta go into the initrd with cpio trick etc
temp_file_dir = TemporaryDirectory()
path_to_file_dir = Path(temp_file_dir.name)
for path_to_file in input_file_paths:
p.info(f"Appending {path_to_file.name} to initrd...")
path_to_file_copy = path_to_file_dir/path_to_file.name
shutil.copy(path_to_file, path_to_file_copy)
os.system(f"mkdir -p {temp_file_dir.name}/usr/share/graphics/")
os.system(f"cp ./files_to_inject/logo.png {temp_file_dir.name}/usr/share/graphics/logo_debian.png")
append_file_contents_to_initrd_archive(
path_to_extracted_iso_dir/"install.amd"/"initrd.gz",
path_to_file_copy
path_to_extracted_iso_dir/"install.amd"/"gtk"/"initrd.gz",
temp_file_dir.name,
"usr/share/graphics/logo_debian.png"
)
p.ok(f"{path_to_file.name} appended successfully.")
# regenerate extracted ISO's md5sum.txt file
p.info("Regenerating MD5 checksums...")

32
udib.py
View File

@@ -110,25 +110,6 @@ def main():
exit(0)
elif args.subparser_name == "inject":
image_file_name = Path(get_debian_iso_urls()["image_file"]["name"])
if not path_to_output_file:
output_file_name = image_file_name.stem + "-modified" + image_file_name.suffix
if path_to_output_dir:
path_to_output_file = path_to_output_dir / output_file_name
else:
path_to_output_file = Path.cwd() / output_file_name
# verify input file paths
input_file_paths = []
for path in args.FILES:
path = Path(path)
if "~" in str(path):
path = Path(path).expanduser()
path = Path(path).resolve()
if not path.is_file():
p.error(f"No such file: '{path}'.")
exit(1)
input_file_paths.append(path)
# verify image file path if set by user or download fresh iso if unset
temp_iso_dir = None
@@ -141,18 +122,17 @@ def main():
p.error(f"No such file: '{path_to_image_file}'.")
exit(1)
else:
# download a Debian ISO to a temporary directory
p.info("Downloading the latest Debian x86-64 netinst image...")
temp_iso_dir = TemporaryDirectory()
path_to_iso_dir = Path(temp_iso_dir.name)
path_to_image_file = path_to_iso_dir/image_file_name
download_and_verify_debian_iso(path_to_image_file, printer=p)
p.error("Must provide the input ISO file")
exit(1)
if not path_to_output_file:
path_to_output_file = str(path_to_image_file).replace(".iso", "-modified.iso")
# inject the input files
inject_files_into_iso(
path_to_output_file,
path_to_image_file,
input_file_paths,
iso_filesystem_name="YunoHost install image",
printer=p,
)