How to create customized bootable ISO image in RHEL/CentOS 7

I have written separate articles with detailed steps to create a user with encrypted password using kickstart file, or to perform interactive installation using kickstart file and with the steps to save the %pre installation logs as unless you save them, the pre installation logs would be deleted once the installation is successful.
For the snippets and examples used of this article I will be using Red Hat 7.3 vanilla DVD content but the same steps can be used to create a customized CentOS DVD.

For the sake of this article I will assume that you have the vanilla DVD from CentOS or Red Hat to copy the needed contents
To summarise below list of steps must be performed

 

1. Prepare the build server

You must replicate the directory structure as present in the Vanilla ISO from the original Red Hat or CentOS DVD.
NOTE: It is not mandatory to follow the same directory structure as present in the DVD but it will make your life easier or at the end some extra config files must be modified to make sure the boot process looks for the booting files in the correct location.
I will use the same directory structure as present in the vanilla DVD
Below will be my parent directory where I am replicating the DVD directory structure for the customization

# mkdir /root/geniso

I have mounted the vanilla DVD on my node hence will mount the DVD to a temporary mount point

# mount /dev/sr0 /mnt

Next copy the entire content to my build server location and later we can remove or customize the needed content

# cp -rvf /mnt/* /root/geniso

Once everything is copied successfully lets move to the next step
 

2. Create a kickstart file

Creating a kicstart file from scratch on your own can be tricky so you have two options here.

  1. You use the anaconda.cfg file available in the root folder as a template and modify it as per your requirement
  2. Create your own custom kickstart file using the Red Hat Kickstart Generator using below link
    https://access.redhat.com/labs/kickstartconfig/

Here I will paste a sample kickstart file I use

# Kickstart configuration for RHEL7.3
#platform=x86, AMD64, or Intel EM64T
# System authorization information
auth  --enableshadow  --passalgo=sha512
# Clear the Master Boot Record
zerombr
# Partition clearing information
clearpart --drives=sda --all
# Use text mode install
#text
graphical
# Firewall configuration
firewall --disabled
# Run the Setup Agent on first boot
firstboot --reconfig --enable
# System keyboard
keyboard us
# System language
lang en_US.UTF-8
# Skipping input of key
#key --skip
# Installation logging level
logging --level=info
# Use NFS installation media
cdrom
# Network Information
network --bootproto=static --hostname=my-linux --device=eth0 --gateway=1.2.3.1 --ip=1.2.3.4 --netmask=255.255.255.0 --noipv6 --nodns --onboot=on --activate
# System bootloader configuration
bootloader --location=mbr --driveorder=sda
# The following is the partition information you requested
ignoredisk --only-use=sda
# Disk Partioning
clearpart --all --initlabel
autopart
#Root password
rootpw --iscrypted $6$uiq8l/7xEWsYXfc0$c9RrvkF/jRzk1JtvQNX2l4NZfkKyokorhv/gieuBMHhrvaEgan4N21yhLa8K.U7UA12Th3PD11GOXvEcI40gp1
# SELinux configuration
selinux --disabled
# Do not configure the X Window System
# Do not configure the X Window System
skipx
#Disabling kdump services, owing to few problems with current kexec package
services --disabled kdump
# System timezone
timezone --utc Europe/Berlin
# Install OS instead of upgrade
install
# Reboot after installation
reboot
# list of packages to be installed
%packages
@ Core
@ Base --nodefaults
# packages deleted according to OS minimization
%end

Modify the network, storage and root password related configuration based on your requirement.

To create an encrypted password

# python -c 'import crypt,getpass; print crypt.crypt(getpass.getpass())'
Password:
$6$7fJnhduoqOGzwUPr$2ZHvcXyj3ktC9qrCKjuQrToM9kPFTJuG7zB1GAmeFdls07WPl9k29fo0HCcw3EycpaBASpGyajt1xU4HELid00

Use this encrypted password in the kickstart

Place this kickstart file 'ks.cfg' in the parent directory of the build server which for us is '/root/geniso/'
 

3. Minimizing the Package list

NOTE: You can skip this step if you do not want to reduce the ISO content and you intention is only to automate the ISO installation
This is the most tricky part and effort taking as it is very hard to identify the dependency rpms one by one for any package you add in your list. To start with assuming you want to create a minimal setup as I have used in the above kickstart file.
Remove all the rpms from Packages directory and start the minimization

# pwd
/root/geniso/Packages
# rm -f /root/geniso/Packages/*
Search for below xml inside repodata directory
/root/geniso/repodata
# ls
211ef0fc36ef7d137b1568cda9de2c7a551633b04260624153506c85fb03e226-primary.sqlite.bz2
4650720b42b35d199c8eb6520e301c182331bce1d1855d93a01282db26be7c20-productid.gz
5d580d3677a32816bfe7c5e53c050a9679e3194b26c39ac986899ad99e6a258a-other.xml.gz
7ee8d6ba4218ef10cbfb95da1eff204bbe43d76332bfc316f87398a9ea64a1e9-comps-Server.x86_64.xml
8c38f297395ffac87645682f245154e73ac05dbffde238782ee5bd4cfb29c340-primary.xml.gz
b72258747300ae8f5d6195382942ef5b1e22913ca590ccc5cddc7957fe172468-filelists.xml.gz
c098a816e33b2b2ac0be14d42d9a2a28caf6800fa6d68350764fb45b40edbd62-other.sqlite.bz2
c542e4cf37dd210de68877b53f41d92dc7686c6e1b35ca4b1852f2e62fca2c72-comps-Server.x86_64.xml.gz
c92d15710455d42727585e36beef547c254e0c677d695f64dfaaf66a2f85f3cd-filelists.sqlite.bz2
productid
repomd.xml
TRANS.TBL

Open the file with *comps-Server.x86_64.xml which will contain the mandatory list of packages for a Minimal setup
Search for group core as below in the xml file

 <group>
    <id>core</id>
    <name>Core</name>
which will be followed by mandatory package list
    <packagelist>
      <packagereq type="mandatory">Red_Hat_Enterprise_Linux-Release_Notes-7-en-US</packagereq>
      <packagereq type="mandatory">audit</packagereq>
      <packagereq type="mandatory">basesystem</packagereq>
      <packagereq type="mandatory">bash</packagereq>
      <packagereq type="mandatory">biosdevname</packagereq>
      <packagereq type="mandatory">btrfs-progs</packagereq>
      <packagereq type="mandatory">coreutils</packagereq>
      <packagereq type="mandatory">cronie</packagereq>
      <packagereq type="mandatory">curl</packagereq>
      ....
    </packagelist>
  </group>

You can create a script which will sort the above list with only rpm names and copy those rpms inside the Packages directory from the original DVD
Next we have to create a repodata for this new content so install createrepo rpm on your build server

# rpm -Uvh createrepo-0.9.9-26.el7.noarch.rpm
warning: createrepo-0.9.9-26.el7.noarch.rpm: Header V3 RSA/SHA256 Signature, key ID fd431d51: NOKEY
Preparing...                          ################################# [100%]
Updating / installing...
1:createrepo-0.9.9-26.el7          ################################# [100%]

There might be some dependency like python-deltarpm, deltarpm, libxml2-python so make sure those are also installed. The same can be installed using 'yum' assuming you have a configured yum repository.

Next update the repodata for Packages directory

# createrepo Packages/
Spawning worker 0 with 469 pkgs
Workers Finished
Saving Primary metadata
Saving file lists metadata
Saving other metadata
Generating sqlite DBs
Sqlite DBs complete

This will create a repodata directory with the needed comps xml file inside Packages.
NOTE: Here there might have dependency issues for many rpms which we can identify individually using below command

# rpm -qpR createrepo-0.9.9-26.el7.noarch.rpm
warning: createrepo-0.9.9-26.el7.noarch.rpm: Header V3 RSA/SHA256 Signature, key ID fd431d51: NOKEY
/bin/sh
/usr/bin/python
deltarpm
libxml2-python
pyliblzma
python >= 2.1
python(abi) = 2.7
python-deltarpm
rpm >= 4.1.1
rpm-python
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(FileDigests) <= 4.6.0-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1
yum >= 3.4.3-4
yum-metadata-parser
rpmlib(PayloadIsXz) <= 5.2-1

Next execute below command to get the dependency rpm for individual file for example

# yum whatprovides */python-deltarpm*
Loaded plugins: product-id, search-disabled-repos, subscription-manager
This system is not registered to Red Hat Subscription Management. You can use subscription-manager to register.
python-deltarpm-3.6-3.el7.x86_64 : Python bindings for deltarpm
Repo        : Server
Matched from:
Filename    : /usr/share/doc/python-deltarpm-3.6/LICENSE.BSD
Filename    : /usr/share/doc/python-deltarpm-3.6
python-deltarpm-3.6-3.el7.x86_64 : Python bindings for deltarpm
Repo        : installed
Matched from:
Filename    : /usr/share/doc/python-deltarpm-3.6/LICENSE.BSD
Filename    : /usr/share/doc/python-deltarpm-3.6/

Here as we can see the dependency rpm name for python-deltarpm
I always prefer to use the package list from a Red Hat installed with "Minimal" Group which shall give me the list of 'Core' and dependency rpms ( rpm -qa | sort ) and the same can be used for all future customization as the base package list,
 

4. Modifying GRUB Menu

Change directory to

# pwd
/root/geniso/isolinux

Look out for the below lines in the 'isolinux.cfg' file

# vim isolinux.cfg
label linux
menu label ^Install Red Hat Enterprise Linux 7.3
kernel vmlinuz
append initrd=initrd.img inst.stage2=hd:LABEL=RHEL-7.3x20Server.x86_64 quiet

Here change it to

label linux
menu label ^Install Red Hat Enterprise Linux 7.3
kernel vmlinuz
append initrd=initrd.img inst.repo=cdrom ks=cdrom:/ks.cfg net.ifnames=0 biosdevname=0

NOTE: Here I am using additional bootmenu parameter "net.ifnames=0 biosdevname=0" to disable consistent network device naming as I prefer to use "ethXX" name for my interfaces. This is optional and you can choose not to use this in your system
Here ks.cfg is the kickstart file we created in Step 2 of this article.
 

5. Create ISO

We are almost done with all the needed configuration. Install 'genisoimage' rpm to build the ISO

# rpm -Uvh libusal-1.1.11-23.el7.x86_64.rpm genisoimage-1.1.11-23.el7.x86_64.rpm
warning: libusal-1.1.11-23.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID fd431d51: NOKEY
Preparing...                          ################################# [100%]
Updating / installing...
1:libusal-1.1.11-23.el7            ################################# [ 50%]
2:genisoimage-1.1.11-23.el7        ################################# [100%]

Next execute the below command to create a ISO in /tmp/ with the name 'new.iso'

# cd /root/geniso
# mkisofs -o /tmp/new.iso -b isolinux/isolinux.bin -c isolinux/boot.cat --no-emul-boot --boot-load-size 4 --boot-info-table -J -R -V "RHEL-7.3x20Server.x86_64" .

Here "RHEL-7.3x20Server.x86_64" is the label I am assigning to my DVD which you can modify as per your requirement

....
98.02% done, estimate finish Sun May 21 16:18:13 2017
99.22% done, estimate finish Sun May 21 16:18:13 2017
Total translation table size: 2048
Total rockridge attributes bytes: 296100
Total directory bytes: 602112
Path table size(bytes): 1802
Max brk space used 322000
418288 extents written (816 MB)

So our ISO is ready which can be used for automated installation.
Please let us know how this works out for you.