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 is the Unattended Debian Installation Builder.
It provides a handy commandline utility for creating preseeded Debian installation ISOs.
Preseeded ISOs allow partially or fully automated Debian installations on bare metal (or anywhere else).
It provides a handy commandline utility for injecting files into Debian installation ISOs.
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
@@ -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
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. get an example preseed file: `./udib.py -o my-preseed.cfg get preseed-file-basic`
6. customize your installation by editing `my-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`
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
@@ -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?
UDIB's main purpose is the injection of preseed 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.
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 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.
# 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:
- 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)
- `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.
If you don't specify an `--image-file`, UDIB will download the latest Debian x86-64 netinst ISO and inject your `PRESEEDFILE` into it.
where `FILE` is the path to the file you want to inject.
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
subparser_inject.add_argument(
"PRESEEDFILE",
"FILES",
action='store',
type=str,
metavar='PRESEEDFILE',
help="Path to the preseed file you want to inject",
nargs='+',
metavar='FILES',
help="Paths to all input files you want to inject",
)
subparser_inject.add_argument(
"-i",

View File

@@ -37,7 +37,7 @@ def hash_user_password(printer=None):
return
p.info("Password hash:")
p.info(crypt(password, crypt.METHOD_SHA512))
p.info(crypt(password, METHOD_SHA512))
def assert_system_dependencies_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
# directory, and the input file's name is piped into it
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,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stderr=subprocess.PIPE
)
completed_process.communicate(
input=str(path_to_input_file.name).encode())
input=str(path_to_input_file.name).encode()
)
except subprocess.CalledProcessError:
raise RuntimeError(f"Failed while appending contents of "
f"'{path_to_input_file}' to "
f"'{path_to_initrd_archive}'.")
raise RuntimeError(
f"Failed while appending contents of '{path_to_input_file}' to "
f"'{path_to_initrd_archive}'."
)
# repack archive
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}'.")
def inject_preseed_file_into_iso(
def inject_files_into_iso(
path_to_output_iso_file,
path_to_input_iso_file,
path_to_input_preseed_file,
input_file_paths,
iso_filesystem_name="Debian",
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
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
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_input_iso_file : str or pathlike object
Path to the origin ISO file.
path_to_input_preseed_file : str or pathlike object
Path to the input preseed file.
input_file_paths : list containing str or pathlike objects
A list of paths to the input files.
printer : clibella.Printer
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():
raise NotADirectoryError(f"No such directory: '{path_to_output_iso_file.parent}'.")
if "~" in str(path_to_input_preseed_file):
path_to_input_preseed_file = Path(path_to_input_preseed_file).expanduser()
path_to_input_preseed_file = Path(path_to_input_preseed_file).resolve()
if not path_to_input_preseed_file.is_file():
raise FileNotFoundError(f"No such file: '{path_to_input_preseed_file}'.")
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()
@@ -490,18 +500,18 @@ def inject_preseed_file_into_iso(
)
p.ok("MBR extraction complete.")
# create a correctly named copy of the input preseed file and append it
# to the extracted ISO's initrd
p.info(f"Appending {path_to_input_preseed_file.name} to initrd...")
temp_preseed_dir = TemporaryDirectory()
path_to_preseed_dir = Path(temp_preseed_dir.name)
path_to_preseed_file = path_to_preseed_dir/"preseed.cfg"
shutil.copy(path_to_input_preseed_file, path_to_preseed_file)
append_file_contents_to_initrd_archive(
path_to_extracted_iso_dir/"install.amd"/"initrd.gz",
path_to_preseed_file
)
p.ok("Preseed file appended successfully.")
# append all input files to the extracted ISO's initrd
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.")
# regenerate extracted ISO's md5sum.txt file
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}'.")
# clear out temporary directories
temp_preseed_dir.cleanup()
temp_file_dir.cleanup()
temp_mbr_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.parser import get_argument_parser
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.scrape import get_debian_preseed_file_urls, get_debian_iso_urls
@@ -112,20 +112,23 @@ def main():
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 + "-preseeded" + image_file_name.suffix
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 preseed file path
path_to_preseed_file = Path(args.PRESEEDFILE)
if "~" in str(path_to_preseed_file):
path_to_preseed_file = Path(path_to_preseed_file).expanduser()
path_to_preseed_file = Path(path_to_preseed_file).resolve()
if not path_to_preseed_file.is_file():
p.error(f"No such file: '{path_to_preseed_file}'.")
exit(1)
# 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
@@ -145,11 +148,11 @@ def main():
path_to_image_file = path_to_iso_dir/image_file_name
download_and_verify_debian_iso(path_to_image_file, printer=p)
# inject the preseed file
inject_preseed_file_into_iso(
# inject the input files
inject_files_into_iso(
path_to_output_file,
path_to_image_file,
path_to_preseed_file,
input_file_paths,
printer=p,
)