Use virt-install to fully automate the install for CentOS/Fedora with kickstart

Here is my kickstart file for CentOS 7. I deploy VMs into my kvm environment with a oneliner, using this kickstart file.

How I use this

I define a variable, and plug it into the important parts.

vm=c7-04a ; time sudo virt-install -n "${vm}" --memory 2048 \
   --vcpus=1 --os-variant=centos7.0 --accelerate -v \
   --disk path=/var/lib/libvirt/images/"${vm}".qcow2,size=20 \
   -l /mnt/public/Support/SetupsBig/Linux/CentOS-7-x86_64-Minimal-1810.iso \
   --initrd-inject=/mnt/public/Support/Platforms/CentOS7/centos7-ks.cfg \
   --extra-args "ks=file:/centos7-ks.cfg SERVERNAME=${vm}" \
   --debug --network type=bridge,source=br0 --noautoconsole

Some thoughts

I had to download the 1810 release of the iso, because there was something wrong with the repos or perhaps files in the previous isos, with how they interacted with either the virtual environment or the network or something. But the CentOS-7-x86_64-minimal-1810.iso was important.
I found the SERVERNAME trick on the Internet. You can iterate over /proc/cmdline and react to values you find there, in the %pre or %post scripts.
You will see that I use my own local repositories for the regular CentOS repos, and I add my own internal one (smith122/repo/rpm). Obviously you should find a suitable set of repos for your own.
You will also see that I attempt to download my CA certificates at various points. I’m pretty sure the %pre effort fails, because the system is not on the network yet.


The kickstart file

# File: /mnt/public/Support/Platforms/CentOS7/centos7-ks.cfg
# Locations:
#    /mnt/public/Support/Platforms/CentOS7/centos7-ks.cfg
# Author: bgstack15
# Startdate: 2017-06-02
# Title: Kickstart for CentOS 7 for
# Purpose: To provide an easy installation for VMs and other systems in the Mersey network
# History:
#    2017-06 I learned how to use kickstart files for the RHCSA EX-200 exam
#    2017-08-08 Added notifyemail to --extra-args
#    2017-10-29 major revision to use local repository
# Usage with virt-install:
#    vm=c7-04a ; time sudo virt-install -n "${vm}" --memory 2048 --vcpus=1 --os-variant=centos7.0 --accelerate -v --disk path=/var/lib/libvirt/images/"${vm}".qcow2,size=20 -l /mnt/public/Support/SetupsBig/Linux/CentOS-7-x86_64-Minimal-1810.iso --initrd-inject=/mnt/public/Support/Platforms/CentOS7/centos7-ks.cfg --extra-args "ks=file:/centos7-ks.cfg SERVERNAME=${vm}" --debug --network type=bridge,source=br0 --noautoconsole
#    vm=c7-04a; sudo virsh destroy "${vm}"; sudo virsh undefine --remove-all-storage "${vm}";
# Reference:
#    /mnt/public/Support/Platforms/CentOS7/install-vm.txt

#platform=x86, AMD64, or Intel EM64T
# Install OS instead of upgrade
# Keyboard layouts
keyboard 'us'
# Root password
rootpw --plaintext SOMETHINGSTRONGHERE
# my user
user --groups=wheel --name=bgstack15-local --password=$6$.gh0u7vg2HDPJPX/$g4Y1l.q76fs7i0UK8t6h83bDIo2YnGGj/1DGeUzzbMTd0pBh4of6jNYWxxws/937sUiPgETqPsYFI5XNrkAle. --iscrypted --gecos="bgstack15-local"

# System language
lang en_US.UTF-8
# Firewall configuration
firewall --enabled --ssh
# Reboot after installation
# Network information
#attempting to put it in the included ks file that accepts hostname from the virsh command.
#network  --bootproto=dhcp --device=eth0 --ipv6=auto --activate
%include /tmp/network.ks
# System timezone
timezone America/New_York --utc
# System authorization information
auth  --useshadow  --passalgo=sha512
# Use network installation instead of CDROM installation media
url --url=""

# Use text mode install
# SELinux configuration
selinux --enforcing
# Do not configure the X Window System

# Use all local repositories
# Online repos
repo --name=smith122rpm --baseurl=
repo --name=base --baseurl=$releasever/os/$basearch/
repo --name=updates --baseurl=$releasever/updates/$basearch/
repo --name=extras --baseurl=$releasever/extras/$basearch/
repo --name=epel --baseurl=$releasever/$basearch

# Offline repos

firstboot --disabled

# System bootloader configuration
bootloader --location=mbr
# Partition clearing information
clearpart --all --initlabel
# Disk partitioning information
autopart --type=lvm

echo "network  --bootproto=dhcp --device=eth0 --ipv6=auto --activate --hostname" > /tmp/network.ks
for x in $( cat /proc/cmdline );
   case $x in
         eval $x
         echo "network  --bootproto=dhcp --device=eth0 --ipv6=auto --activate --hostname ${SERVERNAME}" > /tmp/network.ks
         eval $x
         echo "${NOTIFYEMAIL}" > /mnt/sysroot/root/notifyemail.txt
cp -p /run/install/repo/ /etc/pki/ca-trust/source/anchors/ 2>/dev/null || :
wget -O /etc/pki/ca-trust/source/anchors/ || :
update-ca-trust || :

   # Set temporary hostname
   #hostnamectl set-hostname;

   # Get local mirror root ca certificate
   wget -O /etc/pki/ca-trust/source/anchors/ && update-ca-trust

   # Get local mirror repositories
   wget -O /etc/yum.repos.d/smith122rpm.repo;
   wget -O /etc/yum.repos.d/smith122rpm.mirrorlist
   distro=centos7 ; wget${distro}.repo -O /etc/yum.repos.d/smith122-bundle-${distro}.repo && grep -oP "(? /boot/grub2/grub.cfg

   # postfix is already started by default on centos7
   # Send IP address to myself
   thisip="$( ifconfig 2>/dev/null | awk '/Bcast|broadcast/{print $2}' | tr -cd '[^0-9\.\n]' | head -n1 )"
      echo "${SERVER} has IP ${thisip}."
      echo "system finished kickstart at $( date "+%Y-%m-%d %T" )";
   } | /usr/share/bgscripts/ -f "root@$( hostname --fqdn )" \
      -h -s "${SERVER} is ${thisip}" $( cat /root/notifyemail.txt 2>/dev/null )

   # No changes to graphical boot

   # fix the mkhomedir problem
   systemctl enable oddjobd.service && systemctl start oddjobd.service

   # Personal customizations
   mkdir -p /mnt/bgstack15 /mnt/public
   su bgstack15-local -c "sudo /usr/share/bgconf/"

) >> /root/install.log 2>&1


Add custom kickstart file and root ca certificates to iso file

Introduction and goals

This is intended to be one of my longer posts. This article describes how to accomplish the following tasks:

  1. Insert custom kickstart files into an iso file
  2. Insert custom root CA certificates into the initrd.img of an iso file, so you can fetch a custom repository over https
  3. Write a sample kickstart file
  4. Open up the initrd.img to add more files

The example file used is Fedora-Workstation-netinst-x86_64-27-1.6.iso available from

The files

You will need a few files, including:

  1. kickstart file
  2. Root certificate

Kickstart files

My 2 different kickstart files are
fc27c-ks.cfg (saved to WordPress as a .doc, but it is truly just a plain text file)
Quite a few things to note about the content:
I had to use http for all my local repositories, even though I got the ca certficate loaded. I think how my ISP bounces back my https traffic causes enough slowdown on the ssl handshake it prevents anaconda from using it correctly. It was working earlier in the day but I had to disable it.
Observe in the %pre scriptlet the lines

cp -p /run/install/repo/ /etc/pki/ca-trust/source/anchors 2>/dev/null || :
update-ca-trust || :

These 2 lines load up the root certificate authority cert into the running initrd trusted keys, so the ssl connections are trusted.
Please see the attached or indicated files.

Root certificate

A root certificate is the certificate that signs other certificates for that namespace. I use my own in my ipa domain, and I use it on my web server. So to connect with ssl because I want to encrypt everything possible, I need this cert in the runtime environment on the iso disc image. My root ca file is
not shared on this blog. Go get your own!

The steps

Mount original iso

mkdir -p /mnt/originaliso
mount -v -o loop /mnt/public/Support/SetupsBig/Linux/Fedora-Workstation-netinst-x86_64-27-1.6.iso /mnt/originaliso/

Copy contents to work directory

mkdir -p /mnt/newiso ; cd /mnt/
time cp -pr originaliso/* newiso/

Copy in kickstart files

cp -pf /mnt/public/Support/Platforms/Fedora/fc27{x,c}-ks.cfg /mnt/newiso/
chown root:root /mnt/newiso/*ks.cfg
echo done

Tell disc to use new ks file

This task:

  • Adds xfce and cinnamon menu options
  • Find all the append= lines, and add to the end this attribute: ks=hd:LABEL=fc26:/fc26x-ks.cfg

The important piece is to have the LABEL= the volume name that you give the mkisofs -V “label” a few commands later in this article. If you really want to use a file:/ks.cfg, then you have to open up the initrd, which Appendix A demonstrates.

Fedora 27 xfce and cinnamon
sed -r -e "/append/{s/LABEL=([A-Za-z0-9_\-]*)(\s|:)/LABEL=${label}\2/;s/quiet//;};" -e '/label linux/,/^\s*$/H;' -e '/^\s*$/{x;};' "${tf}" | \
awk "BEGIN{a=0;b=0;labels[1]=\"xfce\";labels[2]=\"cinnamon\";} /^label [^l]/{b=b+1} b < 1 && /label linux/{a=a+1;\$0=\$0\" \"labels[a];} b < 1 && /menu label/{\$0=\$0\" \"labels[a];} b < 1 && /append/{\$0=\$0\"ks=hd:LABEL=${label}:/${label}\"substr(labels[a],1,1)\"-ks.cfg\";} {print;}" > "${tf}.$$"
mv -f "${tf}.$$" "${tf}"
Centos 7
sed -r -e "/append/{s/LABEL=([A-Za-z0-9_\-]*)(\s|:)/LABEL=${label}\2/;s/quiet//;};" "${tf}" | \
awk "BEGIN{a=0;b=0;labels[1]=\"with my bgstack15 custom kickstart\";} /^label [^l]/{b=b+1} b < 1 && /label linux/{a=a+1;\$0=\$0\" \"labels[a];} b < 1 && /menu label/{\$0=\$0\" \"labels[a];} b < 1 && /append/{\$0=\$0\"ks=hd:LABEL=${label}:/${label}-ks.cfg\";} {print;}" > "${tf}.$$"
mv -f "${tf}.$$" "${tf}"

Copy in certificate file

This will be used by the kickstart file and injected into the running initrd so https connections can be trusted to download the repos.

/bin/cp -pf /mnt/public/www/smith122/certs/ /mnt/newiso/
chown root:root /mnt/newiso/*.crt

Make new iso

Fedora 27
ti="${label}manual.iso"; cd /mnt/newiso;
rm -f /mnt/newiso/"${ti:-NOTHINGTODELETE}" ; __func() { mkisofs -V "${label}" -m '*.iso' -o "../${ti}" -b isolinux/isolinux.bin -c isolinux/ -no-emul-boot -boot-load-size 4 -boot-info-table -r -J -v -T . ; implantisomd5 "/mnt/${ti}" ; } ; time __func
CentOS 7
ti=centos7manual.iso ; cd /mnt/newiso ;
rm -f /mnt/newiso/"${ti:-NOTHINGTODELETE}" ; __func() { mkisofs -V "${label}" -m '*.iso' -o "../${ti}" -b isolinux/isolinux.bin -c isolinux/ -no-emul-boot -boot-load-size 4 -boot-info-table -r -J -v -T . ; implantisomd5 "/mnt/${ti}" ; } ; time __func

Copy to server so vm1 can access

time su bgstack15 -c "cp -pf /mnt/${ti} /mnt/public/Support/SetupsBig/Linux/";
echo done

Next steps

After that, the iso is ready to be burned to disc or used by virt-install. I have not actually tried burning a disc or usb drive, but I assume it’s pretty similar to a regular Live iso.
For virt-install, I was simply unable to get my fancy customized iso to work fully automatically. For a regular, unattended vm install, I use the regular Fedora netinstall iso and I inject my kickstart file.

vm=fc27x-02a ; time sudo virt-install -n "${vm}" --memory 2048 --vcpus=1 --os-variant=fedora25 --accelerate -v --disk path=/var/lib/libvirt/images/"${vm}".qcow2,size=20 -l /mnt/public/Support/SetupsBig/Linux/Fedora-Workstation-netinst-x86_64-27-1.6.iso  --initrd-inject=/mnt/public/Support/Platforms/Fedora/fc27x-ks.cfg --extra-args "ks=file:/fc27x-ks.cfg SERVERNAME=${vm}" --debug --network type=direct,source=eno1

And to destroy that vm when I’m done with it:

vm=fc27x-02a; sudo virsh destroy "${vm}"; sudo virsh undefine --remove-all-storage "${vm}";

But this custom iso that we built is ready to be inserted into a vm, where you can manually select the xfce or the cinnamon option. After that initial menu choice, everything else is automatic and unattended.


Appendix A: Modify initrd.img file

Right after step “Copy in certificate file,” if you want to modify the initrd.img file, you can use these steps:

Open initrd.img xz file

mkdir -p /mnt/initrd1; cd /mnt/initrd1; time xzcat /mnt/originaliso/isolinux/initrd.img | cpio -d -i -m

Perform any file modifications to that filesystem in /mnt/initrd1.

Assemble new initrd.img file

cd /mnt/initrd1 ; time find . | cpio -o -H newc | xz --check=crc32 --x86 --lzma2=dict=512KiB > /mnt/newiso/isolinux/initrd.img




Internal documents

~/2017/Systems/guides/Add custom kickstart to iso file.odt

Inject hostname into kickstart

The story

I have been learning how to automate my centos installations in my virtual environment. I’ve learned how to use the virsh command line to spin up a new vm the way I like, and to feed it a kickstart file. I also learned how to use kickstarts.

Set hostname automatically with a kickstart

In the main area of the kickstart file, include this line:

%include /tmp/network.ks

Include in your %pre section this section:

echo "network  --bootproto=dhcp --device=eth0 --ipv6=auto --activate --hostname" > /tmp/network.ks
for x in $( cat /proc/cmdline );
   case $x in SERVERNAME*)
      eval $x
      echo "network  --bootproto=dhcp --device=eth0 --ipv6=auto --activate --hostname ${SERVERNAME}" > /tmp/network.ks

To paraphrase the post I’m duplicating for myself, you need the first echo redirection to the file in case there was no SERVERNAME= parameter given to the kernel.
When you boot, you need to include on the kernel command (usually the “linux” one), the value SERVERNAME=myhostname.

For my virsh command, that is:

vm=centos7-02a ; virt-install -n "${vm}" --memory 2048 --vcpus=1 --os-variant=rhel7.2 --accelerate -v --disk path=/var/lib/libvirt/images/"${vm}".qcow2,size=20 -l /mnt/public/Support/SetupsBig/CentOS-7-x86_64-Minimal-1511.iso  --initrd-inject=/mnt/public/Public/centos7-ks.cfg --extra-args "ks=file:/centos7-ks.cfg SERVERNAME=${vm}" --debug --network type=direct,source=eno1


  1. Install system-config-kickstart on Fedora 25
    sudo dnf install python-kickstart system-config-kickstart;