refactor: subdivide injection functions

This commit is contained in:
Julian Lobbes
2022-11-27 13:49:32 +01:00
parent 76eb606c97
commit 21f68af0e7
5 changed files with 77 additions and 71 deletions

View File

@@ -1,8 +1,9 @@
# UDIB # UDIB
UDIB is the Unattended Debian Installation Builder. UDIB is the Unattended Debian Installation Builder.
It provides a handy commandline utility for creating preseeded Debian installation ISOs. It provides a handy commandline utility for injecting files into Debian installation ISOs.
Preseeded ISOs allow partially or fully automated Debian installations on bare metal (or anywhere else). Using UDIB, you can preseed an ISO by injecting a preseed file.
Preseeded ISOs allow fully automated Debian installations on bare metal (or anywhere else).
## Quick Start Guide ## Quick Start Guide
@@ -12,9 +13,9 @@ This short guide explains how to build a Debian ISO with a customized and automa
2. clone this repo and `cd` into your local copy 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` 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` 4. install the required python packages: `pip install --user -r requirements.txt`
5. get an example preseed file: `./udib.py -o my-preseed.cfg get preseed-file-basic` 5. download an example preseed file: `./udib.py -o preseed.cfg get preseed-file-basic`
6. customize your installation by editing `my-preseed.cfg` (the comments are pretty self-explanatory) 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 my-preseed.cfg` 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) 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* 9. in the Debian installer menu, navigate to *Advanced options > Automated install*
10. drink some coffee 10. drink some coffee
@@ -35,23 +36,12 @@ If you want to know more, you can have a look at Debian's [official guide](https
## How does UDIB work? ## How does UDIB work?
UDIB's main purpose is the injection of preseed files into existing Debian installation ISOs. UDIB's main purpose is the injection files into existing Debian installation ISOs.
In a nutshell, it does this by extracting the ISO, adding the preseed file to it, and repacking the ISO again. In a nutshell, it does this by extracting the ISO, adding the files to the ISO's initrd, and repacking the ISO again.
You could do all of this manually of course by following the [basic](https://wiki.debian.org/DebianInstaller/Preseed/EditIso#Adding_a_Preseed_File_to_the_Initrd) and [advanced](https://wiki.debian.org/RepackBootableISO) guides for ISO repacking on the Debian wiki, but UDIB does all of this for you. You could do all of this manually of course by following the [basic](https://wiki.debian.org/DebianInstaller/Preseed/EditIso#Adding_a_Preseed_File_to_the_Initrd) and [advanced](https://wiki.debian.org/RepackBootableISO) guides for ISO repacking on the Debian wiki, but UDIB does all of this for you.
# Dependencies # Dependencies
UDIB has some hard- and software requirements, both regarding the [target machine](#target-machine) for which it can build ISOs, as well as on the [build machine](#build-machine) on which UDIB is run.
## Target Machine
Images built using UDIB can be used to install Debian on systems satisfying these requirements:
- **CPU Architecture:** x86 (64-Bit)
- **Network:** an ethernet connection with internet access
## Build Machine
Using UDIB to create ISOs requires the following software: Using UDIB to create ISOs requires the following software:
- GNU/Linux - GNU/Linux
@@ -90,13 +80,15 @@ udib.py [--output-file FILE | --output-dir DIR] get WHAT
- `preseed-file-full` to download Debian's full example preseed file (has a LOT of customization options) - `preseed-file-full` to download Debian's full example preseed file (has a LOT of customization options)
- `iso` to download the latest, unmodified Debian stable x86-64 netinst ISO - `iso` to download the latest, unmodified Debian stable x86-64 netinst ISO
## Creating a preseeded ISO ## Injecting files into an ISO
To inject an existing preseed file into an ISO, you can run the following command: To inject existing files into an ISO, you can run the following command:
``` ```
udib.py [--output-file FILE | --output-dir DIR] inject PRESEEDFILE [--image-file IMAGEFILE] udib.py [--output-file FILE | --output-dir DIR] inject [--image-file IMAGEFILE] FILE [FILE ...]
``` ```
where `PRESEEDFILE` is the path to your preseed file. where `FILE` is the path to the file you want to inject.
If you don't specify an `--image-file`, UDIB will download the latest Debian x86-64 netinst ISO and inject your `PRESEEDFILE` into it. Injected files are added at the root of the installer's filesystem and can be accessed there during the installation.
**NOTE:** the installer will not recognize a preseed file unless it's filename is `preseed.cfg` exactly.
If you don't specify an `--image-file`, UDIB will download the latest Debian x86-64 netinst ISO and inject your `FILE`s into it.

View File

@@ -62,11 +62,12 @@ def get_argument_parser():
# register arguments for the 'inject' subcommand # register arguments for the 'inject' subcommand
subparser_inject.add_argument( subparser_inject.add_argument(
"PRESEEDFILE", "FILES",
action='store', action='store',
type=str, type=str,
metavar='PRESEEDFILE', nargs='+',
help="Path to the preseed file you want to inject", metavar='FILES',
help="Paths to all input files you want to inject",
) )
subparser_inject.add_argument( subparser_inject.add_argument(
"-i", "-i",

View File

@@ -37,7 +37,7 @@ def hash_user_password(printer=None):
return return
p.info("Password hash:") p.info("Password hash:")
p.info(crypt(password, crypt.METHOD_SHA512)) p.info(crypt(password, METHOD_SHA512))
def assert_system_dependencies_installed(): def assert_system_dependencies_installed():
"""Checks whether all system dependencies required by udib are installed. """Checks whether all system dependencies required by udib are installed.

View File

@@ -149,19 +149,24 @@ def append_file_contents_to_initrd_archive(
# NOTE cpio must be called from within the input file's parent # NOTE cpio must be called from within the input file's parent
# directory, and the input file's name is piped into it # directory, and the input file's name is piped into it
completed_process = subprocess.Popen( completed_process = subprocess.Popen(
["cpio", "-H", "newc", "-o", "-A", [
"-F", str(path_to_initrd_extracted.resolve())], "cpio", "-H", "newc", "-o", "-A",
"-F", str(path_to_initrd_extracted.resolve())
],
cwd=path_to_input_file.resolve().parent, cwd=path_to_input_file.resolve().parent,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE
)
completed_process.communicate( completed_process.communicate(
input=str(path_to_input_file.name).encode()) input=str(path_to_input_file.name).encode()
)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
raise RuntimeError(f"Failed while appending contents of " raise RuntimeError(
f"'{path_to_input_file}' to " f"Failed while appending contents of '{path_to_input_file}' to "
f"'{path_to_initrd_archive}'.") f"'{path_to_initrd_archive}'."
)
# repack archive # repack archive
with gzip.open(path_to_initrd_archive, "wb") as file_gz: with gzip.open(path_to_initrd_archive, "wb") as file_gz:
@@ -412,17 +417,17 @@ def repack_iso(path_to_output_iso,
f"'{path_to_input_files_root_dir}'.") f"'{path_to_input_files_root_dir}'.")
def inject_preseed_file_into_iso( def inject_files_into_iso(
path_to_output_iso_file, path_to_output_iso_file,
path_to_input_iso_file, path_to_input_iso_file,
path_to_input_preseed_file, input_file_paths,
iso_filesystem_name="Debian", iso_filesystem_name="Debian",
printer=None, printer=None,
): ):
"""Injects the specified preseed file into the specified ISO file. """Injects the specified input files into the specified ISO file.
Extracts the input ISO into a temporary directory, then extracts the input Extracts the input ISO into a temporary directory, then extracts the input
ISO's MBR into a temporary file, then appends the preseed file to the ISO's MBR into a temporary file, then appends the input files to the
extracted ISO's initrd, then regenerates the extracted ISO's internal MD5 extracted ISO's initrd, then regenerates the extracted ISO's internal MD5
hash list and finally repacks the extracted ISO into the output ISO. hash list and finally repacks the extracted ISO into the output ISO.
@@ -435,8 +440,8 @@ def inject_preseed_file_into_iso(
Path to which the resulting ISO file will be saved. Path to which the resulting ISO file will be saved.
path_to_input_iso_file : str or pathlike object path_to_input_iso_file : str or pathlike object
Path to the origin ISO file. Path to the origin ISO file.
path_to_input_preseed_file : str or pathlike object input_file_paths : list containing str or pathlike objects
Path to the input preseed file. A list of paths to the input files.
printer : clibella.Printer printer : clibella.Printer
A printer for CLI output. A printer for CLI output.
""" """
@@ -456,11 +461,16 @@ def inject_preseed_file_into_iso(
if not path_to_output_iso_file.parent.is_dir(): if not path_to_output_iso_file.parent.is_dir():
raise NotADirectoryError(f"No such directory: '{path_to_output_iso_file.parent}'.") raise NotADirectoryError(f"No such directory: '{path_to_output_iso_file.parent}'.")
if "~" in str(path_to_input_preseed_file): input_file_paths_old = input_file_paths.copy()
path_to_input_preseed_file = Path(path_to_input_preseed_file).expanduser() input_file_paths = []
path_to_input_preseed_file = Path(path_to_input_preseed_file).resolve() for path in input_file_paths_old:
if not path_to_input_preseed_file.is_file(): if "~" in str(path):
raise FileNotFoundError(f"No such file: '{path_to_input_preseed_file}'.") 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: if printer is None:
p = Printer() p = Printer()
@@ -490,18 +500,18 @@ def inject_preseed_file_into_iso(
) )
p.ok("MBR extraction complete.") p.ok("MBR extraction complete.")
# create a correctly named copy of the input preseed file and append it # append all input files to the extracted ISO's initrd
# to the extracted ISO's initrd temp_file_dir = TemporaryDirectory()
p.info(f"Appending {path_to_input_preseed_file.name} to initrd...") path_to_file_dir = Path(temp_file_dir.name)
temp_preseed_dir = TemporaryDirectory() for path_to_file in input_file_paths:
path_to_preseed_dir = Path(temp_preseed_dir.name) p.info(f"Appending {path_to_file.name} to initrd...")
path_to_preseed_file = path_to_preseed_dir/"preseed.cfg" path_to_file_copy = path_to_file_dir/path_to_file.name
shutil.copy(path_to_input_preseed_file, path_to_preseed_file) shutil.copy(path_to_file, path_to_file_copy)
append_file_contents_to_initrd_archive( append_file_contents_to_initrd_archive(
path_to_extracted_iso_dir/"install.amd"/"initrd.gz", path_to_extracted_iso_dir/"install.amd"/"initrd.gz",
path_to_preseed_file path_to_file_copy
) )
p.ok("Preseed file appended successfully.") p.ok(f"{path_to_file.name} appended successfully.")
# regenerate extracted ISO's md5sum.txt file # regenerate extracted ISO's md5sum.txt file
p.info("Regenerating MD5 checksums...") p.info("Regenerating MD5 checksums...")
@@ -519,6 +529,6 @@ def inject_preseed_file_into_iso(
p.success(f"ISO file was created successfully at '{path_to_output_iso_file}'.") p.success(f"ISO file was created successfully at '{path_to_output_iso_file}'.")
# clear out temporary directories # clear out temporary directories
temp_preseed_dir.cleanup() temp_file_dir.cleanup()
temp_mbr_dir.cleanup() temp_mbr_dir.cleanup()
temp_extracted_iso_dir.cleanup() temp_extracted_iso_dir.cleanup()

29
udib.py
View File

@@ -8,7 +8,7 @@ from tempfile import TemporaryDirectory
from cli.clibella import Printer from cli.clibella import Printer
from cli.parser import get_argument_parser from cli.parser import get_argument_parser
from core.utils import assert_system_dependencies_installed, download_and_verify_debian_iso from core.utils import assert_system_dependencies_installed, download_and_verify_debian_iso
from iso.injection import inject_preseed_file_into_iso from iso.injection import inject_files_into_iso
from net.download import download_file from net.download import download_file
from net.scrape import get_debian_preseed_file_urls, get_debian_iso_urls from net.scrape import get_debian_preseed_file_urls, get_debian_iso_urls
@@ -112,20 +112,23 @@ def main():
elif args.subparser_name == "inject": elif args.subparser_name == "inject":
image_file_name = Path(get_debian_iso_urls()["image_file"]["name"]) image_file_name = Path(get_debian_iso_urls()["image_file"]["name"])
if not path_to_output_file: if not path_to_output_file:
output_file_name = image_file_name.stem + "-preseeded" + image_file_name.suffix output_file_name = image_file_name.stem + "-modified" + image_file_name.suffix
if path_to_output_dir: if path_to_output_dir:
path_to_output_file = path_to_output_dir / output_file_name path_to_output_file = path_to_output_dir / output_file_name
else: else:
path_to_output_file = Path.cwd() / output_file_name path_to_output_file = Path.cwd() / output_file_name
# verify preseed file path # verify input file paths
path_to_preseed_file = Path(args.PRESEEDFILE) input_file_paths = []
if "~" in str(path_to_preseed_file): for path in args.FILES:
path_to_preseed_file = Path(path_to_preseed_file).expanduser() path = Path(path)
path_to_preseed_file = Path(path_to_preseed_file).resolve() if "~" in str(path):
if not path_to_preseed_file.is_file(): path = Path(path).expanduser()
p.error(f"No such file: '{path_to_preseed_file}'.") path = Path(path).resolve()
exit(1) 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 # verify image file path if set by user or download fresh iso if unset
temp_iso_dir = None temp_iso_dir = None
@@ -145,11 +148,11 @@ def main():
path_to_image_file = path_to_iso_dir/image_file_name path_to_image_file = path_to_iso_dir/image_file_name
download_and_verify_debian_iso(path_to_image_file, printer=p) download_and_verify_debian_iso(path_to_image_file, printer=p)
# inject the preseed file # inject the input files
inject_preseed_file_into_iso( inject_files_into_iso(
path_to_output_file, path_to_output_file,
path_to_image_file, path_to_image_file,
path_to_preseed_file, input_file_paths,
printer=p, printer=p,
) )