diff --git a/.gitignore b/.gitignore index b399c10..4b1f4c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ tags **/__pycache__ **/.venv +*.iso diff --git a/README.md b/README.md index 9bd7b27..0c41a9a 100644 --- a/README.md +++ b/README.md @@ -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 +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). diff --git a/cli/parser.py b/cli/parser.py index 0ca953e..f95aca7 100644 --- a/cli/parser.py +++ b/cli/parser.py @@ -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", diff --git a/files_to_inject/isolinux/menu.cfg b/files_to_inject/isolinux/menu.cfg new file mode 100644 index 0000000..996ec49 --- /dev/null +++ b/files_to_inject/isolinux/menu.cfg @@ -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 diff --git a/files_to_inject/isolinux/splash.png b/files_to_inject/isolinux/splash.png new file mode 100644 index 0000000..4b93b6a Binary files /dev/null and b/files_to_inject/isolinux/splash.png differ diff --git a/files_to_inject/logo.png b/files_to_inject/logo.png new file mode 100644 index 0000000..c9cab92 Binary files /dev/null and b/files_to_inject/logo.png differ diff --git a/files_to_inject/preseeds/default.preseed b/files_to_inject/preseeds/default.preseed new file mode 100644 index 0000000..fd80cf0 --- /dev/null +++ b/files_to_inject/preseeds/default.preseed @@ -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 diff --git a/files_to_inject/preseeds/expert.preseed b/files_to_inject/preseeds/expert.preseed new file mode 100644 index 0000000..967dcd9 --- /dev/null +++ b/files_to_inject/preseeds/expert.preseed @@ -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 diff --git a/iso/injection.py b/iso/injection.py index 9d6adfe..71c7448 100644 --- a/iso/injection.py +++ b/iso/injection.py @@ -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) - append_file_contents_to_initrd_archive( - path_to_extracted_iso_dir/"install.amd"/"initrd.gz", - path_to_file_copy - ) - p.ok(f"{path_to_file.name} appended successfully.") + 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"/"gtk"/"initrd.gz", + temp_file_dir.name, + "usr/share/graphics/logo_debian.png" + ) # regenerate extracted ISO's md5sum.txt file p.info("Regenerating MD5 checksums...") diff --git a/udib.py b/udib.py index adce9bf..7c55f17 100755 --- a/udib.py +++ b/udib.py @@ -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, )