Distribution Hacking 101: disecting anaconda

a.k.a. tearing apart the Red Hat 7.2 installer

Note: if this page interests you, you should also take a look at http://cambuca.ldhs.cetuc.puc-rio.br/RedHat7-CDs-HowTo.html which covers adding Reiserfs support to Red Hat 7.0

Intro:

There are many reasons why one would want to generate their own custom distribution images, ranging from incorporating all of the latest bug fixes, to adding new packages, to completely altering the behavior of the installer.

As a real-life example, we will walk through what is required to incorporate the Linux Terminal Server Project into Red Hat 7.2. We will update a couple of the standard Red Hat packages, include the LTSP packages, and finally alter the installer so that it will be as "newbie-proof" as possible.

 
	-------------               ---------------           -----------
	|           |     eth0      |             |    eth1   |         |
	| Terminals |---------------| LTSP Server |-----------| Lan/Wan |
	|           | 192.168.0.254 |             |    DHCP   |         |
	-------------               ---------------           -----------
	               no firewall                  firewall all
	                                            but DHCP/SSH

Prereqs:

Basics:

1) updating RPMS

	zlib-1.1.3-23.i386.rpm
	==== ======== ====
	 |       |    architecture type
	 |       | 
	 |   version number
	 |
	package name
If you are doing is updating a package to a new version, say updating to zlib-1.2.0-2.i386.rpm, all you should need to do is replace the old package with the new package and then run this shell script:

	#!/bin/sh
	 
	# current working directory
	BASE=`pwd`

	# generate hdlists*
	/usr/lib/anaconda-runtime/genhdlist --withnumbers $BASE/disc1 $BASE/disc2
	
	# generate the package ordering
	PYTHONPATH=/usr/lib/anaconda /usr/lib/anaconda-runtime/pkgorder $BASE/disc1 i386 > filelist

	# now generate hdlists* with the correct ordering
	/usr/lib/anaconda-runtime/genhdlist --withnumbers --fileorder ./filelist $BASE/disc1 $BASE/disc2

	# build disk 1
	cd disc1
	mkisofs -r -J -T -b dosutils/autoboot/cdboot.img -c .boot.cat -o ../1.iso .

	# build disk 2
	cd ../disc2
	mkisofs -r -J -T -o ../2.iso .
	cd ..

	# done!

2) Adding/Removing RPMS and altering package groups

./disc1/RedHat/base/comps contains the general package groups, what is contained with in those groups, and whether each group's default selection.

The syntax of the comps file is pretty simple:

	1 Group One {
		package-one
		package-two
	}
	
	0 Group Two { 
		package-three
		package-four
	}

	0 --hide Hidden Group of Groups
		@ Group One
		@ Group Two
		package-five
	}

3) hacking anaconda itself

This is where things get nasty!

/usr/lib/anaconda/ contains all of the source code for the anaconda installer

/usr/lib/anaconda-runtime/ contains all of the tools required to build new installer images.

I generally hack the stage2.img directly... the tools to regenerate the entire installer are complex and slow. ./disc1/RedHat/base/stage2.img is a cramfs image. To hack on it directly rather than regenerating it:

	cd ./disc1/RedHat/base/
	mkdir mnt
	mkdir new
	mount -o loop,ro -t cramfs ./stage2.img ./mnt
	cp -av ./mnt/* ./new/
	# <hack on ./new/* as described below>
	cd ./disc1/RedHat/base/
	umount ./mnt
	rmdir ./mnt
	cd ./new
	mkcramfs . ../stage2.img
	rm -rfv new
In Red Hat 7.1, stage2.img was an ext2 formated so you could hack on the image directly. Earlier version were ISO9660 formated, IIRC.

The primary hacking occurs in ./disc1/RedHat/base/new/usr/lib/anaconda/installclasses

Each install class is defined here. The first thing we're going to do is copy workstation.py to ltsp.py - the Workstation install is close to what we want so we'll use that as our base.

Next we're going to hack on ltsp.py into shape. In the ltsp.py file listed below, the original workstation.py code is in blue and the lines modifiled/added for ltsp.py are in red. The comments describe what each change does:

	from installclass import BaseInstallClass
	from translate import N_
	import os
	import iutil
	from partitioning import *
	from fsset import *
	
	class InstallClass(BaseInstallClass):
	
	    name = N_("LTSP")   # <- name this install class "LTSP"
	    pixmap = "ltsp.png" # <- icon for the "LTSP" install option
	    sortPriority = 1    # <- this makes LTSP the first install option
	    arch = 'i386'
	
	    def setSteps(self, dispatch):
	        BaseInstallClass.setSteps(self, dispatch);
	        dispatch.skipStep("authentication")
	
	    def setGroupSelection(self, comps):
	        BaseInstallClass.__init__(self, comps)
	
	        # change the default package group options & automatically select
	        # "Workstation Common" and "LTSP" package groupings:
	        self.showGroups(comps, [ "KDE", ("GNOME", 1),
	                                 "Software Development",
	                                 "Web Server", "Open Office",
	                                 "SQL Database Server", "Dialup Support",
	                                 "Education" ] )
	        comps["Workstation Common"].select()
	        comps["LTSP"].select()
	
	    def setInstallData(self, id):
	        BaseInstallClass.setInstallData(self, id)
	
	        autorequests = [ ("/", None, 1100, None, 1, 1) ]

	        bootreq = getAutopartitionBoot()
	        if bootreq:
	            autorequests.append(bootreq)
	
	        (minswap, maxswap) = iutil.swapSuggestion()
	        autorequests.append((None, "swap", minswap, maxswap, 1, 1))
	
	        id.partitions.autoClearPartType = CLEARPART_TYPE_LINUX
	        id.partitions.autoClearPartDrives = None
	        id.partitions.autoPartitionRequests = autoCreatePartitionRequests(autorequests)
	
	        # Modify the firewall defaults: make "eth0" a trusted device and
	        # permit SSH:
	        id.firewall.trustdevs = ["eth0"]
	        id.firewall.ssh = 1

	        # if eth0 exists, set its defaults to 192.168.0.254/255.255.255.0
	        # and if eth1 exists, set its default to DHCP
	        dev = id.network.available()
	        if dev.has_key("eth0"):
	            dev["eth0"].set(('bootproto', 'none'))
	            dev["eth0"].set(('ipaddr', '192.168.0.254'))
	            dev["eth0"].set(('netmask', '255.255.255.0'))
	            dev["eth0"].set(('onboot', 'yes'))
	        if dev.has_key("eth1"):
	            dev["eth1"].set(('bootproto', 'dhcp'))
	            dev["eth1"].set(('onboot', 'yes'))
	
	    def __init__(self, expert):
	        BaseInstallClass.__init__(self, expert)
	

Now all we need to do is generate a new stage2.img and then generate the ISOs. That's it!

Danger! Danger!

There are a few things to watch out for/be aware of: