Create a bootable ISO with kickstart#

Installing Linux on a physical or virtual machine can be fun for a couple of time, but configuring a system can be combursome after a couple of times. Red Hat developed Kickstart to install and configure machines via the network, but also as part of an ISO file that can used in a virtual CD-ROM drive or on a bootable USB-drive.

Manual procedure#

The following steps are required to create a bootable ISO file with a kickstart file and when the ISO file is booted, the kickstart file is used to install and configure the system. Creating a bootable ISO file is not a difficult task, but it is a manual procedure. The following steps are required to create a bootable ISO file with a kickstart file.

Installing all required packages in Red Hat based systems:

Install required packages on CentOS, Fedora or Red Hat#
# dnf install epel-release
# dnf install p7zip p7zip-plugins isomd5sum genisoimage

Or installing all required packages in Debian based systems:

Install required packages on Debian or Ubuntu#
# apt install genisoimage isomd5sum p7zip-full

Download NetInstall ISO and validate checksum:

Download netinstall ISO#
$ curl http://ftp.nluug.nl/ftp/pub/os/Linux/distr/CentOS/7/isos/x86_64/CentOS-7-x86_64-NetInstall-1708.iso --output /tmp/some.iso
$ sha256sum --ignore-missing -c sha256sum.txt

Prepare new directory tree based on ISO:

Extract ISO#
mkdir /tmp/bootiso /tmp/bootisoks
mount -o loop /tmp/some.iso /tmp/bootiso
cp -r /tmp/bootiso/* /tmp/bootisoks/
umount /tmp/bootiso && rmdir /tmp/bootiso
chmod -R u+w /tmp/bootisoks

Add kickstart file and update configuration for the bootloader:

Adding kickstart and isolinux configuration#
cp kickstart.cfg /tmp/bootisoks/isolinux/ks.cfg
cp isolinux.cfg /tmp/bootisoks/isolinux/isolinux.cfg

Build ISO image and add MD5 signature:

Creating the ISO and plant the MD5 signature#
cd /tmp/bootisoks && mkisofs -o /tmp/boot.iso -b isolinux.bin -c boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -V "CentOS 7 x86_64" -R -J -v -T isolinux/. .
implantisomd5 /tmp/boot.iso

Remove build directory structure:

Removing build directory structure#
cd /tmp && rm -rf /tmp/bootisoks

File boot.iso can now be used by KVM, VMWare, bootable USB, etc. Two files are required to create a bootable ISO file, a kickstart file and a configuration file for the bootloader. The kickstart file is used to install and configure the system and the configuration file for the bootloader is used to boot the system and use the kickstart file. Below is an example of a kickstart file for CentOS 7:

kickstart.ks#
#version=DEVEL
auth --enableshadow --passalgo=sha512
cdrom
text
firstboot --enable
ignoredisk --only-use=sda
keyboard --vckeymap=us --xlayouts='us'
lang en_US.UTF-8

network --bootproto=static --device=ens3 --ip=192.168.105.104 --netmask=255.255.255.0 --gateway=192.168.105.1 --ipv6=auto --nameserver=8.8.8.8,8.8.4.4 --activate
network --hostname=server.homelab.example.org
firewall --enabled --ssh

rootpw --lock
timezone Europe/Amsterdam --isUtc
bootloader --location=mbr --boot-drive=sda
autopart --type=lvm
clearpart --all --initlabel
reboot --eject

user --groups=wheel --homedir=/home/sysadmin --name=sysadmin --password=cangetin --plaintext --gecos="System Administration Account"

url  --url http://mirror.centos.org/centos/$releasever/os/$basearch/
repo --name=extras --baseurl=http://mirror.centos.org/centos/$releasever/extras/$basearch/
repo --name=epel   --baseurl=http://download.fedoraproject.org/pub/epel/$releasever/$basearch/

%packages --excludedocs --nobase
@core --nodefaults
openscap
openscap-scanner
openssh-server
scap-security-guide
yum-cron
-aic94xx-firmware*
-alsa-*
-ivtv*
-iwl*firmware
-libertas*
-kexec-tools
-plymouth*
-postfix
%end

%post --log=/root/ks-post.log
sed -i 's/update_cmd.*/update_cmd=minimal-security/' /etc/yum/yum-cron.conf
sed -i 's/apply_updates.*/apply_updates=yes/' /etc/yum/yum-cron.conf
yum -y update-minimal --security
%end

%addon org_fedora_oscap
    content-type = scap-security-guide
    profile = standard
%end

%addon com_redhat_kdump --disable --reserve-mb='auto'

%end

%anaconda
pwpolicy root --minlen=8 --minquality=50 --strict --nochanges --notempty
pwpolicy user --minlen=8 --minquality=50 --strict --nochanges --notempty
pwpolicy luks --minlen=8 --minquality=50 --strict --nochanges --notempty
%end

The second file is the configuration file for the bootloader, in this case isolinux. The configuration file is used to boot the system and use the kickstart file. Below is an example of a configuration file for isolinux to start the installation process automatically when the ISO is used:

isolinux.cfg#
default vesamenu.c32
timeout 100

display boot.msg

# Clear the screen when exiting the menu, instead of leaving the menu displayed.
# For vesamenu, this means the graphical background is still displayed without
# the menu itself for as long as the screen remains in graphics mode.
menu clear
menu background splash.png
menu title CentOS 7
menu vshift 8
menu rows 18
menu margin 8
#menu hidden
menu helpmsgrow 15
menu tabmsgrow 13

# Border Area
menu color border * #00000000 #00000000 none

# Selected item
menu color sel 0 #ffffffff #00000000 none

# Title bar
menu color title 0 #ff7ba3d0 #00000000 none

# Press [Tab] message
menu color tabmsg 0 #ff3a6496 #00000000 none

# Unselected menu item
menu color unsel 0 #84b8ffff #00000000 none

# Selected hotkey
menu color hotsel 0 #84b8ffff #00000000 none

# Unselected hotkey
menu color hotkey 0 #ffffffff #00000000 none

# Help text
menu color help 0 #ffffffff #00000000 none

# A scrollbar of some type? Not sure.
menu color scrollbar 0 #ffffffff #ff355594 none

# Timeout msg
menu color timeout 0 #ffffffff #00000000 none
menu color timeout_msg 0 #ffffffff #00000000 none

# Command prompt text
menu color cmdmark 0 #84b8ffff #00000000 none
menu color cmdline 0 #ffffffff #00000000 none

# Do not display the actual menu unless the user presses a key. All that is displayed is a timeout message.

menu tabmsg Press Tab for full configuration options on menu items.

menu separator # insert an empty line
menu separator # insert an empty line

label linux
  menu label ^Install CentOS 7
  menu default
  kernel vmlinuz
  append initrd=initrd.img inst.stage2=hd:LABEL=CentOS\x207\x20x86_64 inst.ks=cdrom:/ks.cfg

label check
  menu label Test this ^media & install CentOS 7
  kernel vmlinuz
  append initrd=initrd.img inst.stage2=hd:LABEL=CentOS\x207\x20x86_64 rd.live.check quiet

menu separator # insert an empty line

# utilities submenu
menu begin ^Troubleshooting
  menu title Troubleshooting

label vesa
  menu indent count 5
  menu label Install CentOS 7 in ^basic graphics mode
  text help
        Try this option out if you're having trouble installing
        CentOS 7.
  endtext
  kernel vmlinuz
  append initrd=initrd.img inst.stage2=hd:LABEL=CentOS\x207\x20x86_64 xdriver=vesa nomodeset quiet

label rescue
  menu indent count 5
  menu label ^Rescue a CentOS system
  text help
    If the system will not boot, this lets you access files
    and edit config files to try to get it booting again.
  endtext
  kernel vmlinuz
  append initrd=initrd.img inst.stage2=hd:LABEL=CentOS\x207\x20x86_64 rescue quiet

label memtest
  menu label Run a ^memory test
  text help
    If your system is having issues, a problem with your
    system's memory may be the cause. Use this utility to
    see if the memory is working correctly.
  endtext
  kernel memtest

menu separator # insert an empty line

label local
  menu label Boot from ^local drive
  localboot 0xffff

menu separator # insert an empty line
menu separator # insert an empty line

label returntomain
  menu label Return to ^main menu
  menu exit

menu end

Building an image with GitHub#

The manual procedure above is fine for a one-off build, but if you want to automate this to get the latest ISO, you can use GitHub Actions to build the image for you. The following example will build a new image every time a commit is pushed to the master branch, or a pull request is opened against the master branch. The image will be uploaded as an artifact to the GitHub repository and will be available for download from the Actions tab.

Example workflow file .github/workflows/build.yml for GitHub Actions#
---
name: Build

on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master

env:
  URL: https://download.rockylinux.org/pub/rocky/9/isos/x86_64/Rocky-9.1-x86_64-boot.iso

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Build
        run: |
          sudo apt install -y genisoimage isomd5sum p7zip-full
          curl -L -o /tmp/source.iso ${{ env.URL }}
          mkdir bootiso
          7z x /tmp/source.iso -o./bootiso
          rm -rf bootiso/\[BOOT\]
          cp isolinux.cfg bootiso/isolinux/isolinux.cfg
          cp kickstart.ks bootiso/isolinux/ks.cfg
          cd bootiso
          mkisofs -o ../boot.iso -b isolinux.bin -c boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -V "Custom Boot ISO" -R -J -v -T isolinux/. .
          implantisomd5 ../boot.iso

      - name: Upload Artifact
        uses: actions/upload-artifact@v3
        with:
          name: boot.iso
          path: boot.iso
          retention-days: 1

Building an image with GitLab#

Like GitHub Actions, GitLab CI can be used to build an image for you. The following example will build a new image every time a commit is pushed to the master branch, or a pull request is opened against the master branch. The image will be uploaded as an artifact to the GitLab repository and will be available for download from the CI/CD tab.

Example configuration file gitlab-ci.yml for GitLab CI#
---
stages:
  - build

variables:
  CENTOS: "7.6.1810"

build:
  image: centos:${CENTOS}
  stage: build
  before_script:
    - yum install -y epel-release
    - yum install -y p7zip p7zip-plugins isomd5sum genisoimage
  script:
    - curl http://mirror.centos.org/centos/${CENTOS}/os/x86_64/images/boot.iso --output ${CI_PROJECT_DIR}/source.iso
    - mkdir ${CI_PROJECT_DIR}/bootiso
    - 7z x -o${CI_PROJECT_DIR}/bootiso source.iso
    - rm -rf ${CI_PROJECT_DIR}/bootiso/\[BOOT\]
    - cp kickstart.ks ${CI_PROJECT_DIR}/bootiso/isolinux/ks.cfg
    - cp isolinux.cfg ${CI_PROJECT_DIR}/bootiso/isolinux/isolinux.cfg
    - cd ${CI_PROJECT_DIR}/bootiso
    - mkisofs -o ${CI_PROJECT_DIR}/boot.iso -b isolinux.bin -c boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -V "CentOS 7 x86_64" -R -J -v -T isolinux/. .
    - implantisomd5 ${CI_PROJECT_DIR}/boot.iso
  artifacts:
    paths:
      - boot.iso
    expire_in: 1 day

Customizing Kickstart files#

The example kickstart file provided in this post is a very basic one. It will install a minimal CentOS 7 system with a single user account. You can customize this file to suit your needs. The CentOS documentation has a great guide on how to customize kickstart files and the CentOS/Community-Kickstarts repository has a lot of examples that you can use as a starting point. The examples can be adopted for Red Hat Enterprise Linux, Rocky Linux or Fedora with minimal changes.