Tuesday, March 21, 2006

Hacking the Hack of Hacking Red Hat Kickstart

O'Reilly publishing has released a series of books called the Hacks Series. They define hack as "A clever solution to an interesting problem."

I'm a big fan.

I've read:

Google Hacks
Linux Server Hacks
Linux Server Hacks, Volume Two (Electric Boogaloo)
Mind Performance Hacks (just bought this one yesterday in fact)
Network Security Hacks

They're all really cool and eminently useful books.

Last Friday, I was assigned the task of creating a single CD based install of Red Hat Enterprise Linux for the purpose of unattended installation (Kickstart). This is opposed to the option of physically swapping all four CDs as released by Red Hat. Until now, we, and our customers, had been leveraging Kickstart using the stock RHEL Update 6 CD 1 to do network based installations. In my opinion, going over the network is more elegant than hacking up a custom CD any day. However, our "Partner" got our "Marketing Department" to agree that we would provide CD media for the entire installation of our product. As such, the network approache, while technically greatly superior, would have to take a back seat.

My starting point was an excellent article written by

This article, unfortunately, is based on Red Hat 8.0, which is now obsoleted and past its end of life. Luckily, other Red Hat Enterprise customers have updated the doc with their tweaks to support all the latest RHEL distros. In this post, I will consolidate all the work done previously by Brett and add in the fixes/tweaks as added by the community. This follows the O'Reilly approach covered in their Hacks series "Hacking the Hack."

As Slick Rick says, "Here we Go."

First, get your hands on the 4 CDs for the RHEL release in question. For me, this was RHEL ES3 Update 6. Here are the CDs and their md5sums. (Yes, I know RHEL 3 Update 7 is out, so is RHEL 4 Update 3, and Fedora Core 5.) Again, full credit to Brett for all of these steps. They've just been tweaked as necessary to support newer RH releases and documented end-to-end here.

[root@kickstart iso]# ls -lah rhel-3-u6-i386-es-disc*
-rw-r--r-- 1 root root 152M Sep 21 11:54 rhel-3-u6-i386-es-disc1.iso
-rw-r--r-- 1 root root 627M Sep 21 11:43 rhel-3-u6-i386-es-disc2.iso
-rw-r--r-- 1 root root 637M Sep 21 11:48 rhel-3-u6-i386-es-disc3.iso
-rw-r--r-- 1 root root 282M Dec 6 16:38 rhel-3-u6-i386-es-disc4.iso

[root@kickstart iso]# md5sum rhel-3-u6-i386-es-disc*
2a695a0dc773b2172b35f8164b10f2f3 rhel-3-u6-i386-es-disc1.iso
68e7b2f34cb1903c24da02e25bcf5462 rhel-3-u6-i386-es-disc2.iso
8aa48608434065fb481d462d8495583c rhel-3-u6-i386-es-disc3.iso
3b35b450ecec27c5a9c63300f7518d3f rhel-3-u6-i386-es-disc4.iso

[root@kickstart iso]# mkdir Update6
[root@kickstart iso]# mkdir -p CD{1,2,3,4}
[root@kickstart iso]# mkdir onecd

Let's mount these badboys, and I don't want to hear any shit about the FHS right now either.

mount -o loop /kickstart/iso/rhel-3-u6-i386-es-disc1.iso /kickstart/ES3/Update6/CD1/
mount -o loop /kickstart/iso/rhel-3-u6-i386-es-disc2.iso /kickstart/ES3/Update6/CD2/
mount -o loop /kickstart/iso/rhel-3-u6-i386-es-disc3.iso /kickstart/ES3/Update6/CD3/
mount -o loop /kickstart/iso/rhel-3-u6-i386-es-disc4.iso /kickstart/ES3/Update6/CD4/

Copy the RPMS:

cp -a CD1/* onecd/
cp -a CD{2,3,4}/RedHat/RPMS/* onecd/RedHat/RPMS/

Now the tricky part, coming up with a complete set of RPMs that fits on one CD and is internally consistent. Let's use Brett's python scripts to get this started.

cd /kickstart/ES3/Update6/onecd/

getGroupPkgs.py comps.xml > /kickstart/ES3/Update6/pkglist
syncRpms.py onecd/RedHat/RPMS/ pkglist > pkgs_rem

(Note, I had to make a few changes to the syncRPMS.py script to reflect the newer arcitecture) Here's the modified script:

#!/usr/bin/python

#
# Removes packages that are not part of a package list from
# a given directory. This is used to remove RPMs that are
# not needed in a custom distro. The package list specified
# is just a text file with a list of package names, one per
# line.
#
# syncRpms.py /some/path/to/rpms/ /tmp/pkglist
#
# Copyright (C) 2003 Brett Schwarz (brett_schwarz@yahoo.com)
#
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# 02111-1307 USA
#
# Version History
# ---------------
# 0.2 08-04-2003 Added fix from Alain Tauch for accepting
# /path instead of /path/
# 0.1 22-03-2003 Original release

import rpm
import sys, os, glob

if len(sys.argv) != 3:
sys.stderr.write("Usage\n")
sys.stderr.write("%s \n" % (sys.argv[0],))
sys.exit(1)

tgtdir = sys.argv[1]
pkglist = sys.argv[2]

#
# get pkg name to file name mapping
#
name2file = {}
ts = rpm.TransactionSet("", rpm._RPMVSF_NOSIGNATURES)
for fname in glob.glob(tgtdir + '/*.rpm'):
fd = os.open(fname, os.O_RDONLY)
hdr = ts.hdrFromFdno(fd)
name2file[hdr[rpm.RPMTAG_NAME]] = fname
os.close(fd)

#
# Read in packages from package list
#
fd = open(pkglist, "r")
pkgs = {}
for l in fd.readlines():
if l[-1]=='\n':
l = l[:-1]
pkgs[l] = 1

fd.close()

#
# Remove unwanted packages
#
for n, f in name2file.items():
if not pkgs.has_key(n):
os.remove(f)
print n

#
# Check to see if there are pkgs not in tgt dir
#
for p in pkgs.keys():
if not name2file.has_key(p):
print "Package not in tgt dir: ", p

OK. Now to test the resultant set. Again, cool trick Brett.

mkdir /tmp/testdb
rpm --initdb --dbpath /tmp/testdb
rpm --test --dbpath /tmp/testdb -Uvh *.rpm

For whatever reason, Brett's script didn't do a very good job of gettin the right set together. No matter, the rpm command tells you about failed dependencies so all you have to do is go locate the missing RPMs and copy them into the oncecd tree. I ended up building up a third file called pkgsadd and then ran:

for i in $(cat pkgsadd); do src=$(find CD* -name $i); /bin/cp -f $src onecd/RedHat/RPMS/; done

Eventually, the set was consistent.
[root@kickstart RPMS]# rpm --test --dbpath /tmp/testdb -Uvh *.rpm
warning: acl-2.2.3-1.i386.rpm: V3 DSA signature: NOKEY, key ID db42a60e
warning: package glibc = 2.3.2-95.37 was already added, replacing with glibc <= 2.3.2-95.37 warning: package kernel = 2.4.21-37.EL was already added, replacing with kernel <= 2.4.21-37.EL warning: package kernel-smp = 2.4.21-37.EL was already added, replacing with kernel-smp <= 2.4.21-37.EL Preparing... ########################################### [100%]

One more test, for good measure: rpm -K *.rpm | grep "NOT *OK" Now, here's where my steps differed from the initial doc.

Pay attention.

First, you need anaconda and anaconda-devel.

up2date anaconda anaconda-runtime
export PYTHONPATH=/usr/lib/anaconda
/usr/lib/anaconda-runtime/pkgorder /kickstart/ES3/Update6/onecd/ i386 > /kickstart/ES3/Update6/onecd/RedHat/base/pkgorder
/usr/lib/anaconda-runtime/genhdlist --withnumbers --fileorder /kickstart/ES3/Update6/onecd/RedHat/base/pkgorder --hdlist /kickstart/ES3/Update6/onecd/RedHat/base/hdlist /kickstart/ES3/Update6/onecd/

Now, I'm going to add all the ks.cfg files to the CD repository.

[root@kickstart onecd]# http://satellite.priv.nuasis.com/kickstart/ks/label/ncc-3.0
[root@kickstart onecd]# wget http://satellite.priv.nuasis.com/kickstart/ks/label/ncc-3.0-ide
[root@kickstart onecd]# wget http://satellite.priv.nuasis.com/kickstart/ks/label/ncc-3.0-md

This almost bit me:

[root@kickstart CD1]# cp .discinfo /kickstart/ES3/Update6/onecd/

One more fix I had to do. My ks.cfgs out there on the satellite serve all point to a network based install. To make this new kickstart go off of the cdrom, I need to point to cdrom instead of url inside each of ncc-3.0, ncc-3.0-ide, and ncc-3.0-md.

The big step:

[root@kickstart root]# mkisofs -r -T -J -V "ncc-3.0-rhel-3-u6-i386-es.t1" -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -v -o /kickstart/iso/ncc-3.0-rhel-3-u6-i386-es.t1.iso /kickstart/ES3/Update6/onecd

154528 extents written (301 MB)

301 MB, not bad.
[root@kickstart root]# /usr/lib/anaconda-runtime/implantisomd5 /kickstart/iso/ncc-3.0-rhel-3-u6-i386-es.t1.iso
Inserting md5sum into iso image...
md5 = 12bf4c82d5d59b854e00d343b02d7cc6
Setting supported flag to 0

Cool.

Sphere: Related Content

2 comments:

Anonymous said...

hi dmourati,

was reading ur KS hack. I also had a simillar problem and was reading brett howto in LJ. For some reason am not able to download the 'py' scripts from bretts home page. if u have brett's original scripts please send it to me.

thanks
gnuyoga@gmail.com

Anonymous said...

I verified that these steps also work with RedHat Enterprise AS (in addition to ES). No changes were requird.