refactor: subdivide injection functions
This commit is contained in:
38
README.md
38
README.md
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
29
udib.py
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user