Backport mkksiso to RHEL 8.5.0
This allows you to modify the boot.iso to run a kickstart and add additional files to the iso. Resolves: rhbz#1955355
This commit is contained in:
parent
a64b6e9677
commit
6c07a08be0
@ -249,6 +249,7 @@ latex_documents = [
|
||||
man_pages = [
|
||||
('lorax', 'lorax', u'Lorax Documentation', [u'Weldr Team'], 1),
|
||||
('livemedia-creator', 'livemedia-creator', u'Live Media Creator Documentation', [u'Weldr Team'], 1),
|
||||
('mkksiso', 'mkksiso', u'Make Kickstart ISO Utility Documentation', [u'Weldr Team'], 1),
|
||||
('lorax-composer', 'lorax-composer', u'Lorax Composer Documentation', [u'Weldr Team'], 1),
|
||||
('composer-cli', 'composer-cli', u'Composer Cmdline Utility Documentation', [u'Weldr Team'], 1),
|
||||
]
|
||||
|
129
docs/mkksiso.rst
Normal file
129
docs/mkksiso.rst
Normal file
@ -0,0 +1,129 @@
|
||||
mkksiso
|
||||
=======
|
||||
|
||||
:Authors:
|
||||
Brian C. Lane <bcl@redhat.com>
|
||||
|
||||
``mkksiso`` is a tool for creating kickstart boot isos. In it's simplest form
|
||||
you can add a kickstart to a boot.iso and the kickstart will be executed when
|
||||
the iso is booted. If the original iso was created with EFI and Mac support the
|
||||
kickstart boot.iso will include this support as well.
|
||||
|
||||
``mkksiso`` needs to be run as root, it depends on mounting the original iso
|
||||
and you need to be root to be able to do that.
|
||||
|
||||
|
||||
mkksiso cmdline arguments
|
||||
-------------------------
|
||||
|
||||
Add a kickstart and files to an iso
|
||||
|
||||
``usage: mkksiso [-h] [-a ADD_PATHS] [-c CMDLINE] [--debug] ks input_iso output_iso``
|
||||
|
||||
Optional arguments
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
-h, --help show this help message and exit
|
||||
-a ADD_PATHS, --add ADD_PATHS
|
||||
File or directory to add to ISO (may be used multiple
|
||||
times)
|
||||
-c CMDLINE, --cmdline CMDLINE
|
||||
Arguments to add to kernel cmdline
|
||||
--debug print debugging info
|
||||
-V VOLID, --volid VOLID
|
||||
Set the ISO volume id, defaults to input's
|
||||
|
||||
Positional arguments
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:ks: Kickstart to add to the ISO
|
||||
|
||||
:input_iso: ISO to modify
|
||||
|
||||
:output_iso: Full pathname of iso to be created
|
||||
|
||||
|
||||
Create a kickstart boot.iso or DVD
|
||||
----------------------------------
|
||||
|
||||
Create a kickstart like you normally would, kickstart documentation can be
|
||||
`found here <https://pykickstart.readthedocs.io/en/latest/>`_, including the
|
||||
``url`` and ``repo`` commands. If you are creating a DVD and only need the
|
||||
content on the DVD you can use the ``cdrom`` command to install without a
|
||||
network connection. Then run ``mkksiso`` like this::
|
||||
|
||||
mkksiso /PATH/TO/KICKSTART /PATH/TO/ISO /PATH/TO/NEW-ISO
|
||||
|
||||
This will create a new iso with the kickstart in the root directory, and the
|
||||
kernel cmdline will have ``inst.ks=...`` added to it so that it will be
|
||||
executed when the iso is booted (be careful not to boot on a system you don't
|
||||
want to wipe out! There will be no prompting).
|
||||
|
||||
By default the volume id of the iso is preserved. You can set a custom volid
|
||||
by passing ``-V`` and the string to set. The kernel cmdline will be changes, and the iso will have th custom volume id.
|
||||
eg.::
|
||||
|
||||
mkksiso -V "Test Only" /PATH/TO/KICKSTART /PATH/TO/ISO /PATH/TO/NEW-ISO
|
||||
|
||||
|
||||
Adding package repos to a boot.iso
|
||||
----------------------------------
|
||||
|
||||
You can add repo directories to the iso using ``--add /PATH/TO/REPO/``, make
|
||||
sure it contains the ``repodata`` directory by running ``createrepo_c`` on it
|
||||
first. In the kickstart you can refer to the directories (and files) on the iso
|
||||
using ``file:///run/install/repo/DIRECTORY/``. You can then use these repos in
|
||||
the kickstart like this::
|
||||
|
||||
repo --name=extra-repo --baseurl=file:///run/install/repo/extra-repo/
|
||||
|
||||
Run ``mkksiso`` like so::
|
||||
|
||||
mkksiso --add /PATH/TO/REPO/ /PATH/TO/KICKSTART /PATH/TO/ISO /PATH/TO/NEW-ISO
|
||||
|
||||
|
||||
Create a liveimg boot.iso
|
||||
-------------------------
|
||||
|
||||
You can use the kickstart `liveimg command
|
||||
<https://pykickstart.readthedocs.io/en/latest/kickstart-docs.html#liveimg>`_,
|
||||
to install a pre-generated disk image or tar to the system the iso is booting
|
||||
on.
|
||||
|
||||
Create a disk image or tar with ``osbuild-composer`` or ``livemedia-creator``,
|
||||
make sure the image includes tools expected by ``anaconda``, as well as the
|
||||
kernel and bootloader support. In ``osbuild-composer`` use the ``tar`` image
|
||||
type and make sure to include the ``kernel``, ``grub2``, and ``grub2-tools``
|
||||
packages. If you plan to install it to a UEFI machine make sure to include
|
||||
``grub2-efi`` and ``efibootmgr`` in the blueprint.
|
||||
|
||||
Add the ``root.tar.xz`` file to the iso using ``--add /PATH/TO/ROOT.TAR.XZ``,
|
||||
and in the kickstart reference it with the ``liveimg`` command like this::
|
||||
|
||||
liveimg --url=file:///run/install/repo/root.tar.xz
|
||||
|
||||
It is also a good idea to use the ``--checksum`` argument to ``liveimg`` to be
|
||||
sure the file hasn't been corrupted::
|
||||
|
||||
mkksiso --add /PATH/TO/root.tar.xz /PATH/TO/KICKSTART /PATH/TO/ISO /PATH/TO/NEW-ISO
|
||||
|
||||
When this iso is booted it will execute the kickstart and install the liveimg
|
||||
contents to the system without any prompting.
|
||||
|
||||
|
||||
How it works
|
||||
------------
|
||||
|
||||
``mkksiso`` first examines the system to make sure the tools it needs are installed,
|
||||
it will work with ``xorrisofs`` or ``mkisofs`` installed. It mounts the source iso,
|
||||
and copies the directories that need to be modified to a temporary directory.
|
||||
|
||||
It then modifies the boot configuration files to include the ``inst.ks`` command,
|
||||
and checks to see if the original iso supports EFI. If it does it regenerates the
|
||||
EFI boot images with the new configuration, and then runs the available iso creation
|
||||
tool to add the new files and directories to the new iso. If the architecture is
|
||||
``x86_64`` it will also make sure the iso can be booted as an iso or from a USB
|
||||
stick (hybridiso).
|
||||
|
||||
The last step is to update the iso checksums so that booting with test enabled
|
||||
will pass.
|
@ -216,12 +216,14 @@ getent passwd weldr >/dev/null 2>&1 || useradd -r -g weldr -d / -s /sbin/nologin
|
||||
%{_sbindir}/lorax
|
||||
%{_sbindir}/mkefiboot
|
||||
%{_sbindir}/livemedia-creator
|
||||
%{_sbindir}/mkksiso
|
||||
%{_bindir}/image-minimizer
|
||||
%dir %{_sysconfdir}/lorax
|
||||
%config(noreplace) %{_sysconfdir}/lorax/lorax.conf
|
||||
%dir %{_datadir}/lorax
|
||||
%{_mandir}/man1/lorax.1*
|
||||
%{_mandir}/man1/livemedia-creator.1*
|
||||
%{_mandir}/man1/mkksiso.1*
|
||||
%{_tmpfilesdir}/lorax.conf
|
||||
|
||||
%files docs
|
||||
|
3
setup.py
3
setup.py
@ -21,7 +21,8 @@ for root, dnames, fnames in os.walk("share"):
|
||||
|
||||
# executable
|
||||
data_files.append(("/usr/sbin", ["src/sbin/lorax", "src/sbin/mkefiboot",
|
||||
"src/sbin/livemedia-creator", "src/sbin/lorax-composer"]))
|
||||
"src/sbin/livemedia-creator", "src/sbin/lorax-composer",
|
||||
"src/sbin/mkksiso"]))
|
||||
data_files.append(("/usr/bin", ["src/bin/image-minimizer",
|
||||
"src/bin/composer-cli"]))
|
||||
|
||||
|
618
src/sbin/mkksiso
Executable file
618
src/sbin/mkksiso
Executable file
@ -0,0 +1,618 @@
|
||||
#!/usr/bin/python3
|
||||
#
|
||||
# Copyright (C) 2019 Red Hat, Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
import logging as log
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import sys
|
||||
|
||||
from pylorax.mount import IsoMountpoint
|
||||
from pylorax.treebuilder import udev_escape
|
||||
|
||||
# Constants for efimode (MACBOOT implies EFIBOOT)
|
||||
NO_EFI = 0
|
||||
EFIBOOT = 1
|
||||
MACBOOT = 2
|
||||
|
||||
class Tool():
|
||||
"""A class to check for executables and required files"""
|
||||
tools = []
|
||||
paths = {}
|
||||
requirements = []
|
||||
arches = []
|
||||
|
||||
def __init__(self):
|
||||
# If there are arches listed it must be running on one of them
|
||||
if self.arches and os.uname().machine not in self.arches:
|
||||
log.debug("%s is not supported by this tool", os.uname().machine)
|
||||
raise RuntimeError("%s not supported" % os.uname().machine)
|
||||
|
||||
# Check the system to see if the tools are available and record their paths
|
||||
for e in self.tools:
|
||||
self.paths[e] = shutil.which(e)
|
||||
if not self.paths[e]:
|
||||
log.debug("No executable %s found in PATH", e)
|
||||
raise RuntimeError("Missing executable")
|
||||
|
||||
# Check to make sure all the required files are available
|
||||
for f in self.requirements:
|
||||
if not os.path.exists(f):
|
||||
log.debug("Missing file %s", f)
|
||||
raise RuntimeError("Missing requirement %s" % f)
|
||||
|
||||
|
||||
class MkefibootTool(Tool):
|
||||
"""Create the efiboot.img needed for EFI booting"""
|
||||
tools = ["mkefiboot"]
|
||||
|
||||
def run(self, isodir, tmpdir, product="Fedora"):
|
||||
cmd = ["mkefiboot", "--label=ANACONDA"]
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--debug")
|
||||
cmd.extend([os.path.join(tmpdir, "EFI/BOOT"), os.path.join(tmpdir, "images/efiboot.img")])
|
||||
log.debug(" ".join(cmd))
|
||||
try:
|
||||
subprocess.check_output(cmd)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log.error(str(e))
|
||||
raise RuntimeError("Running mkefiboot")
|
||||
|
||||
|
||||
class MkmacbootTool(MkefibootTool):
|
||||
"""Create the macboot.img needed to EFI boot on Mac hardware"""
|
||||
tools = ["mkefiboot"]
|
||||
# NOTE: Order is important
|
||||
requirements = ["/usr/share/pixmaps/bootloader/fedora.icns",
|
||||
"/usr/share/pixmaps/bootloader/fedora-media.vol"]
|
||||
|
||||
def run(self, isodir, tmpdir, product="Fedora"):
|
||||
cmd = ["mkefiboot", "--label", "ANACONDA",
|
||||
"--apple", "--icon", self.requirements[0],
|
||||
"--diskname", self.requirements[1],
|
||||
"--product", product]
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--debug")
|
||||
cmd.extend([os.path.join(tmpdir, "EFI/BOOT"), os.path.join(tmpdir, "images/macboot.img")])
|
||||
log.debug(" ".join(cmd))
|
||||
try:
|
||||
subprocess.check_output(cmd)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log.error(str(e))
|
||||
raise RuntimeError("Running mkefiboot --apple")
|
||||
|
||||
# images/macboot.img always exists with images/efiboot.img, already in grafts list
|
||||
return []
|
||||
|
||||
|
||||
class MakeISOTool(Tool):
|
||||
"""Class to hold details for specific iso creation tools"""
|
||||
def check_file_sizes(self, grafts):
|
||||
"""Return True any file exceeds 4GiB"""
|
||||
for src, _ in grafts:
|
||||
if os.path.isdir(src):
|
||||
for top, dirs, files in os.walk(src):
|
||||
for f in files + dirs:
|
||||
if os.stat(os.path.join(top,f)).st_size >= 4*1024**3:
|
||||
return True
|
||||
else:
|
||||
if os.stat(src).st_size >= 4*1024**3:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _exec(self, cmd, grafts, output_iso, efimode=NO_EFI, implantmd5=True):
|
||||
"""Add the grafts and run the command and then implant the md5 checksums"""
|
||||
cmd.append("-graft-points")
|
||||
|
||||
for src, dest in grafts:
|
||||
cmd.append("%s=%s" % (dest, src))
|
||||
|
||||
log.debug(" ".join(cmd))
|
||||
try:
|
||||
subprocess.check_output(cmd)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log.error(str(e))
|
||||
raise RuntimeError("New ISO creation failed")
|
||||
|
||||
if efimode > NO_EFI:
|
||||
cmd = ["isohybrid", "--uefi"]
|
||||
if efimode == MACBOOT:
|
||||
cmd.append("--mac")
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--verbose")
|
||||
cmd.append(output_iso)
|
||||
|
||||
log.debug(" ".join(cmd))
|
||||
try:
|
||||
subprocess.check_output(cmd)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log.error(str(e))
|
||||
raise RuntimeError("Hybrid ISO creation failed")
|
||||
|
||||
if not implantmd5:
|
||||
return
|
||||
|
||||
cmd = ["implantisomd5", output_iso]
|
||||
log.debug(" ".join(cmd))
|
||||
try:
|
||||
subprocess.check_output(cmd)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log.error(str(e))
|
||||
raise RuntimeError("implantisomd5 failed")
|
||||
|
||||
|
||||
class Mkisofs_aarch64(MakeISOTool):
|
||||
"""Use the mkisofs tool to create the final iso (aarch64)"""
|
||||
tools = ["mkisofs", "implantisomd5"]
|
||||
arches = ["aarch64", "arm"]
|
||||
|
||||
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
||||
cmd = ["mkisofs", "-o", output_iso, "-R", "-J", "-V", volume_name,
|
||||
"-T"]
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--verbose")
|
||||
if efimode > NO_EFI:
|
||||
cmd.extend(["-eltorito-alt-boot", "-e", "images/efiboot.img", "-no-emul-boot"])
|
||||
if efimode > EFIBOOT:
|
||||
cmd.extend(["-eltorito-alt-boot", "-e", "images/macboot.img", "-no-emul-boot"])
|
||||
|
||||
if self.check_file_sizes(grafts):
|
||||
cmd.append("-allow-limited-size")
|
||||
|
||||
# Create the iso and implant the md5 checksums
|
||||
self._exec(cmd, grafts, output_iso, implantmd5=True)
|
||||
|
||||
|
||||
class Mkisofs_ppc(MakeISOTool):
|
||||
"""Use the mkisofs tool to create the final iso (ppc)"""
|
||||
tools = ["mkisofs"]
|
||||
requirements = ["/usr/share/lorax/templates.d/99-generic/config_files/ppc/mapping"]
|
||||
arches = ["ppc"]
|
||||
|
||||
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
||||
cmd = ["mkisofs", "-o", output_iso, "-R", "-J", "-V", volume_name,
|
||||
"-U", "-T", "-part", "-hfs", "-r", "-l", "-sysid", "PPC",
|
||||
"-chrp-boot", "-no-desktop", "-allow-multidot",
|
||||
"-map", self.requirements[0], "-hfs-bless", "boot/grub/powerpc-ieee1275"]
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--verbose")
|
||||
|
||||
if self.check_file_sizes(grafts):
|
||||
cmd.append("-allow-limited-size")
|
||||
|
||||
# Create the iso and implant the md5 checksums
|
||||
self._exec(cmd, grafts, output_iso, efimode, implantmd5=True)
|
||||
|
||||
|
||||
class Mkisofs_ppc64le(MakeISOTool):
|
||||
"""Use the mkisofs tool to create the final iso (ppc64le)"""
|
||||
tools = ["mkisofs"]
|
||||
requirements = ["/usr/share/lorax/templates.d/99-generic/config_files/ppc/mapping"]
|
||||
arches = ["ppc64le"]
|
||||
|
||||
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
||||
cmd = ["mkisofs", "-o", output_iso, "-R", "-J", "-V", volume_name,
|
||||
"-T", "-part", "-hfs", "-r", "-l", "-sysid", "PPC",
|
||||
"-chrp-boot", "-no-desktop", "-allow-multidot",
|
||||
"-map", self.requirements[0]]
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--verbose")
|
||||
|
||||
if self.check_file_sizes(grafts):
|
||||
cmd.append("-allow-limited-size")
|
||||
|
||||
# Create the iso and implant the md5 checksums
|
||||
self._exec(cmd, grafts, output_iso, efimode, implantmd5=True)
|
||||
|
||||
|
||||
class Mkisofs_s390(MakeISOTool):
|
||||
"""Use the mkisofs tool to create the final iso (s390)"""
|
||||
tools = ["mkisofs"]
|
||||
requirements = []
|
||||
arches = ["s390", "s390x"]
|
||||
|
||||
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
||||
cmd = ["mkisofs", "-o", output_iso, "-R", "-J", "-V", volume_name,
|
||||
"-b", "images/cdboot.img",
|
||||
"-c", "images/boot.cat",
|
||||
"-boot-load-size", "4", "-no-emul-boot"]
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--verbose")
|
||||
|
||||
if self.check_file_sizes(grafts):
|
||||
cmd.append("-allow-limited-size")
|
||||
|
||||
# Create the iso and implant the md5 checksums
|
||||
self._exec(cmd, grafts, output_iso, efimode, implantmd5=True)
|
||||
|
||||
|
||||
class Mkisofs_x86_64(MakeISOTool):
|
||||
"""Use the mkisofs tool to create the final iso (x86_64)"""
|
||||
tools = ["mkisofs", "isohybrid"]
|
||||
requirements = []
|
||||
arches = ["x86_64", "i386"]
|
||||
|
||||
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
||||
cmd = ["mkisofs", "-o", output_iso, "-R", "-J", "-V", volume_name,
|
||||
"-b", "isolinux/isolinux.bin",
|
||||
"-c", "isolinux/boot.cat",
|
||||
"-boot-load-size", "4", "-boot-info-table", "-no-emul-boot"]
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--verbose")
|
||||
if efimode > NO_EFI:
|
||||
cmd.extend(["-eltorito-alt-boot", "-e", "images/efiboot.img", "-no-emul-boot"])
|
||||
if efimode > EFIBOOT:
|
||||
cmd.extend(["-eltorito-alt-boot", "-e", "images/macboot.img", "-no-emul-boot"])
|
||||
|
||||
if self.check_file_sizes(grafts):
|
||||
cmd.append("-allow-limited-size")
|
||||
|
||||
# Create the iso, implant the md5 checksums, and create hybrid iso
|
||||
self._exec(cmd, grafts, output_iso, efimode, implantmd5=True)
|
||||
|
||||
|
||||
class Xorrisofs_aarch64(MakeISOTool):
|
||||
"""Use the xorrisofs tool to create the final iso (aarch64)"""
|
||||
tools = ["xorrisofs", "implantisomd5"]
|
||||
requirements = []
|
||||
arches = ["aarch64", "arm"]
|
||||
|
||||
def run(self, tmpdir, grafts, volume_name, output_iso, efimode):
|
||||
cmd = ["xorrisofs", "-o", output_iso,
|
||||
"-R", "-J", "-V", volume_name]
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--verbose")
|
||||
if efimode >= EFIBOOT:
|
||||
cmd.extend(["-eltorito-alt-boot", "-e", "images/efiboot.img", "-no-emul-boot"])
|
||||
if efimode == MACBOOT:
|
||||
cmd.extend(["-eltorito-alt-boot", "-e", "images/macboot.img", "-no-emul-boot"])
|
||||
|
||||
if self.check_file_sizes(grafts):
|
||||
cmd.extend(["-iso-level", "3"])
|
||||
|
||||
# Create the iso and implant the md5 checksums
|
||||
self._exec(cmd, grafts, output_iso, implantmd5=True)
|
||||
|
||||
|
||||
class Xorrisofs_ppc64le(MakeISOTool):
|
||||
"""Use the xorrisofs tool to create the final iso (ppc64)"""
|
||||
tools = ["xorrisofs", "implantisomd5"]
|
||||
requirements = []
|
||||
arches = ["ppc64le"]
|
||||
|
||||
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
||||
cmd = ["xorrisofs", "-o", output_iso,
|
||||
"-R", "-J", "-V", volume_name,
|
||||
"-U", "-r", "-l", "-sysid", "PPC",
|
||||
"-A", volume_name, "-chrp-boot"]
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--verbose")
|
||||
|
||||
if self.check_file_sizes(grafts):
|
||||
cmd.extend(["-iso-level", "3"])
|
||||
|
||||
# Create the iso and implant the md5 checksums
|
||||
self._exec(cmd, grafts, output_iso, efimode=NO_EFI, implantmd5=True)
|
||||
|
||||
|
||||
class Xorrisofs_s390(MakeISOTool):
|
||||
"""Use the xorrisofs tool to create the final iso (s390)"""
|
||||
tools = ["xorrisofs", "implantisomd5"]
|
||||
requirements = []
|
||||
arches = ["s390", "s390x"]
|
||||
|
||||
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
||||
cmd = ["xorrisofs", "-o", output_iso,
|
||||
"-R", "-J", "-V", volume_name,
|
||||
"-b", "images/cdboot.img", "-c", "images/boot.cat",
|
||||
"-boot-load-size", "4", "-no-emul-boot"]
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--verbose")
|
||||
|
||||
if self.check_file_sizes(grafts):
|
||||
cmd.extend(["-iso-level", "3"])
|
||||
|
||||
# Create the iso and implant the md5 checksums
|
||||
self._exec(cmd, grafts, output_iso, efimode=NO_EFI, implantmd5=True)
|
||||
|
||||
|
||||
class Xorrisofs_x86_64(MakeISOTool):
|
||||
"""Use the xorrisofs tool to create the final iso"""
|
||||
tools = ["xorrisofs", "implantisomd5"]
|
||||
requirements = ["/usr/share/syslinux/isohdpfx.bin"]
|
||||
arches = ["x86_64", "i386"]
|
||||
|
||||
def run(self, tmpdir, grafts, volume_name, output_iso, efimode=NO_EFI):
|
||||
cmd = ["xorrisofs", "-o", output_iso,
|
||||
"-R", "-J", "-V", volume_name,
|
||||
"-isohybrid-mbr", self.requirements[0],
|
||||
"-b", "isolinux/isolinux.bin",
|
||||
"-c", "isolinux/boot.cat",
|
||||
"-boot-load-size", "4", "-boot-info-table", "-no-emul-boot"]
|
||||
if log.root.level < log.INFO:
|
||||
cmd.append("--verbose")
|
||||
if efimode >= EFIBOOT:
|
||||
cmd.extend(["-eltorito-alt-boot", "-e", "images/efiboot.img", "-no-emul-boot", "-isohybrid-gpt-basdat"])
|
||||
if efimode == MACBOOT:
|
||||
cmd.extend(["-eltorito-alt-boot", "-e", "images/macboot.img", "-no-emul-boot", "-isohybrid-gpt-hfsplus"])
|
||||
|
||||
if self.check_file_sizes(grafts):
|
||||
cmd.extend(["-iso-level", "3"])
|
||||
|
||||
# Create the iso and implant the md5 checksums
|
||||
self._exec(cmd, grafts, output_iso, efimode=NO_EFI, implantmd5=True)
|
||||
|
||||
|
||||
# xorrisofs based classes
|
||||
XorrisofsTools = [Xorrisofs_aarch64, Xorrisofs_ppc64le, Xorrisofs_s390, Xorrisofs_x86_64]
|
||||
|
||||
# mkisofs based classes
|
||||
MkisofsTools = [Mkisofs_aarch64, Mkisofs_ppc, Mkisofs_ppc64le, Mkisofs_s390, Mkisofs_x86_64]
|
||||
|
||||
|
||||
class MakeKickstartISO():
|
||||
def __init__(self, ks, input_iso, output_iso, add_paths, cmdline="", volid=None):
|
||||
self.ks = ks
|
||||
self.input_iso = input_iso
|
||||
self.output_iso = output_iso
|
||||
self.add_paths = add_paths
|
||||
self.isotool = None
|
||||
self._cmdline = cmdline
|
||||
self.label = ""
|
||||
self.iso = None
|
||||
self.mkmacboot = None
|
||||
self.efimode = NO_EFI
|
||||
self.grafts = []
|
||||
|
||||
errors = False
|
||||
for f in [ks, input_iso] + add_paths:
|
||||
if not os.path.exists(f):
|
||||
log.error("%s is missing", f)
|
||||
errors = True
|
||||
|
||||
if errors:
|
||||
raise RuntimeError("Missing Files")
|
||||
|
||||
# Mount the iso so we can gather information from it
|
||||
try:
|
||||
self.iso = IsoMountpoint(self.input_iso)
|
||||
self.label = self.iso.label if volid is None else volid
|
||||
log.info("Volume Id = %s", self.label)
|
||||
|
||||
if os.path.exists(os.path.join(self.iso.mount_dir, "images/efiboot.img")):
|
||||
self.efimode = EFIBOOT
|
||||
self.mkefiboot = MkefibootTool()
|
||||
if os.path.exists(os.path.join(self.iso.mount_dir, "images/macboot.img")):
|
||||
self.efimode = MACBOOT
|
||||
self.mkmacboot = MkmacbootTool()
|
||||
|
||||
for t in XorrisofsTools + MkisofsTools:
|
||||
try:
|
||||
self.isotool = t()
|
||||
break
|
||||
except RuntimeError:
|
||||
continue
|
||||
if not self.isotool:
|
||||
raise RuntimeError("No suitable iso creation tool found.")
|
||||
|
||||
log.info("Using %s to create the new iso", self.isotool.tools[0])
|
||||
except:
|
||||
if self.iso:
|
||||
self.iso.umount()
|
||||
raise
|
||||
|
||||
@property
|
||||
def escaped_label(self):
|
||||
"""Return the escaped iso volume label"""
|
||||
return udev_escape(self.label)
|
||||
|
||||
def close(self):
|
||||
if self.iso:
|
||||
self.iso.umount()
|
||||
|
||||
@property
|
||||
def add_args(self,):
|
||||
"""Return the arguments to add to the config file kernel cmdline"""
|
||||
return "inst.ks=hd:LABEL=%s:/%s %s" % (self.escaped_label, os.path.basename(self.ks), self._cmdline)
|
||||
|
||||
def remove_generated_files(self, grafts):
|
||||
"""Remove the files generated by the iso creation tools"""
|
||||
catalog_files = ["boot.cat", "boot.catalog", "TRANS.TBL"]
|
||||
for src, _ in grafts:
|
||||
if not os.path.isdir(src):
|
||||
continue
|
||||
for f in catalog_files:
|
||||
if os.path.exists(os.path.join(src, f)):
|
||||
os.unlink(os.path.join(src, f))
|
||||
|
||||
def run_mkefiboot(self, isodir, tmpdir):
|
||||
self.mkefiboot.run(isodir, tmpdir)
|
||||
if self.efimode == MACBOOT:
|
||||
self.mkmacboot.run(isodir, tmpdir)
|
||||
|
||||
def edit_configs(self, isodir, tmpdir):
|
||||
"""Find and edit any configuration files
|
||||
|
||||
Add the inst.ks= argument plus extra cmdline arguments
|
||||
"""
|
||||
# Note that some of these may not exist, depending on the arch being used
|
||||
self._edit_isolinux(isodir, tmpdir)
|
||||
self._edit_efi(isodir, tmpdir)
|
||||
self._edit_ppc(isodir, tmpdir)
|
||||
self._edit_s390(isodir, tmpdir)
|
||||
|
||||
def _edit_isolinux(self, isodir, tmpdir):
|
||||
"""Copy the isolinux.cfg file and add the cmdline args"""
|
||||
|
||||
orig_cfg = os.path.join(isodir, "isolinux/isolinux.cfg")
|
||||
if not os.path.exists(orig_cfg):
|
||||
log.warning("No isolinux/isolinux.cfg file found")
|
||||
return []
|
||||
|
||||
# Edit the config file
|
||||
with open(orig_cfg, "r") as in_fp:
|
||||
with open(os.path.join(tmpdir, "isolinux/isolinux.cfg"), "w") as out_fp:
|
||||
escaped_iso_label = udev_escape(self.iso.label)
|
||||
for line in in_fp:
|
||||
if escaped_iso_label in line:
|
||||
line = line.replace(escaped_iso_label, self.escaped_label)
|
||||
out_fp.write(line.rstrip("\n"))
|
||||
if "append" in line:
|
||||
out_fp.write(" "+self.add_args)
|
||||
out_fp.write("\n")
|
||||
|
||||
def _edit_efi(self, isodir, tmpdir):
|
||||
"""Copy the efi config files and add the cmdline args"""
|
||||
# At least one of these must be present
|
||||
efi_cfgs = ["EFI/BOOT/grub.cfg", "EFI/BOOT/BOOT.conf"]
|
||||
|
||||
if not os.path.exists(os.path.join(isodir, "EFI")):
|
||||
log.warning("No EFI directory file found")
|
||||
return []
|
||||
|
||||
found_cfg = False
|
||||
for cfg in efi_cfgs:
|
||||
orig_cfg = os.path.join(isodir, cfg)
|
||||
if not os.path.exists(orig_cfg):
|
||||
continue
|
||||
|
||||
dest_cfg = os.path.join(tmpdir, cfg)
|
||||
with open(orig_cfg, "r") as in_fp:
|
||||
with open(dest_cfg, "w") as out_fp:
|
||||
escaped_iso_label = udev_escape(self.iso.label)
|
||||
for line in in_fp:
|
||||
if escaped_iso_label in line:
|
||||
line = line.replace(escaped_iso_label, self.escaped_label)
|
||||
if line.strip().startswith("search"):
|
||||
line = line.replace(self.iso.label, self.label)
|
||||
out_fp.write(line.rstrip("\n"))
|
||||
# Some start with linux (aarch64), others with linuxefi (x86_64)
|
||||
if line.strip().startswith("linux"):
|
||||
out_fp.write(" "+self.add_args)
|
||||
out_fp.write("\n")
|
||||
found_cfg = True
|
||||
|
||||
if not found_cfg:
|
||||
raise RuntimeError("ISO is missing the EFI config files")
|
||||
|
||||
def _edit_ppc(self, isodir, tmpdir):
|
||||
"""Edit the boot/grub/grub.cfg file, adding the kickstart and extra arguments"""
|
||||
orig_cfg = os.path.join(isodir, "boot/grub/grub.cfg")
|
||||
if not os.path.exists(orig_cfg):
|
||||
log.warning("No boot/grub/grub.cfg file found")
|
||||
return []
|
||||
|
||||
# Edit the config file
|
||||
with open(orig_cfg, "r") as in_fp:
|
||||
with open(os.path.join(tmpdir, "boot/grub/grub.cfg"), "w") as out_fp:
|
||||
escaped_iso_label = udev_escape(self.iso.label)
|
||||
for line in in_fp:
|
||||
if escaped_iso_label in line:
|
||||
line = line.replace(escaped_iso_label, self.escaped_label)
|
||||
out_fp.write(line.rstrip("\n"))
|
||||
if line.strip().startswith("linux "):
|
||||
out_fp.write(" "+self.add_args)
|
||||
out_fp.write("\n")
|
||||
|
||||
def _edit_s390(self, isodir, tmpdir):
|
||||
"""Edit the images/generic.prm file, adding the kickstart and extra arguments"""
|
||||
orig_cfg = os.path.join(isodir, "images/generic.prm")
|
||||
if not os.path.exists(orig_cfg):
|
||||
log.warning("No images/generic.prm file found")
|
||||
return []
|
||||
|
||||
# Append to the config file
|
||||
with open(os.path.join(tmpdir, "images/generic.prm"), "a") as out_fp:
|
||||
out_fp.write(self.add_args+"\n")
|
||||
|
||||
def run(self):
|
||||
"""Modify the ISO"""
|
||||
try:
|
||||
# Make a temporary directory to hold modified files
|
||||
with tempfile.TemporaryDirectory(prefix="mkksiso-") as tmpdir:
|
||||
# Copy over the top level directories and populate grafts
|
||||
skip_iso = ["boot.cat", "boot.catalog", "TRANS.TBL"]
|
||||
for f in [f for f in os.listdir(self.iso.mount_dir) if f not in skip_iso]:
|
||||
if os.path.isdir(os.path.join(self.iso.mount_dir, f)):
|
||||
shutil.copytree(os.path.join(self.iso.mount_dir, f), os.path.join(tmpdir, f))
|
||||
else:
|
||||
shutil.copy2(os.path.join(self.iso.mount_dir, f), os.path.join(tmpdir, f))
|
||||
self.grafts.append((os.path.join(tmpdir, f), f))
|
||||
|
||||
# Copy and edit the configuration files
|
||||
self.edit_configs(self.iso.mount_dir, tmpdir)
|
||||
|
||||
# Run the mkefiboot tool on the edited EFI directory, add the new files to the grafts
|
||||
self.run_mkefiboot(self.iso.mount_dir, tmpdir)
|
||||
|
||||
# Add the kickstart to grafts
|
||||
self.grafts.extend([(self.ks, os.path.basename(self.ks))])
|
||||
|
||||
# Add the extra files to grafts
|
||||
self.grafts.extend([(f, os.path.basename(f)) for f in self.add_paths])
|
||||
|
||||
# Remove files that will be regenerated by the iso creation process
|
||||
self.remove_generated_files(self.grafts)
|
||||
|
||||
log.info("grafts = %s", self.grafts)
|
||||
self.isotool.run(tmpdir, self.grafts, self.label, self.output_iso, self.efimode)
|
||||
finally:
|
||||
self.close()
|
||||
|
||||
|
||||
def setup_args():
|
||||
""" Return argparse.Parser object of cmdline."""
|
||||
parser = argparse.ArgumentParser(description="Add a kickstart and files to an iso")
|
||||
|
||||
parser.add_argument("-a", "--add", action="append", dest="add_paths", default=[],
|
||||
type=os.path.abspath,
|
||||
help="File or directory to add to ISO (may be used multiple times)")
|
||||
parser.add_argument("-c", "--cmdline", dest="cmdline", metavar="CMDLINE", default="",
|
||||
help="Arguments to add to kernel cmdline")
|
||||
parser.add_argument("--debug", action="store_const", const=log.DEBUG,
|
||||
dest="loglevel", default=log.INFO,
|
||||
help="print debugging info")
|
||||
|
||||
parser.add_argument("ks", type=os.path.abspath, help="Kickstart to add to the ISO")
|
||||
parser.add_argument("input_iso", type=os.path.abspath, help="ISO to modify")
|
||||
parser.add_argument("output_iso", type=os.path.abspath, help="Full pathname of iso to be created")
|
||||
parser.add_argument("-V", "--volid", dest="volid", help="Set the ISO volume id, defaults to input's", default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = setup_args()
|
||||
log.basicConfig(format='%(levelname)s:%(message)s', level=args.loglevel)
|
||||
|
||||
if os.getuid() != 0:
|
||||
log.error("You must run this as root, it needs to mount the iso and run mkefiboot")
|
||||
sys.exit(-1)
|
||||
|
||||
try:
|
||||
app = MakeKickstartISO(args.ks, args.input_iso, args.output_iso,
|
||||
args.add_paths, args.cmdline, args.volid)
|
||||
app.run()
|
||||
except RuntimeError as e:
|
||||
log.error(str(e))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue
Block a user