In this section, we will see how to use a deployment server to automatically install RedHat Linux via the network using kickstart.
Domain name: domain.net
Network: 192.168.1.0/24
Deployment server name: deploysrv
Deployment server IP address: 192.168.1.254
Linux boxes names: linbox1, linbox2 ...
Linux boxes IP addresses: 192.168.1.1, 192.168.1.2 ...
Linux boxes hardware (MAC) addresses: 11:11:11:11:11:11, 22:22:22:22:22:22 ...
Network installation type: NFS (HTTP and FTP are not covered yet)
Gateway IP address: 192.168.1.253
DNS servers IP addresses: 192.168.1.251, 192.168.1.252
To have a working deployment server, use the CD-ROMs to install RedHat Linux so that the following services will be available: DHCP, NFS, tftp, ntp. Be sure that the deployment server has an Internet connectivity.
Firstable, we need to create a RedHat installation tree. We assume that the base dir is /export/install/redhat/ and we want to create two installation trees. The first one is RedHat 8.0 (psyche) for our production environment and the second is RedHat 9 (shrike) for tests:
bash# mkdir -p /export/install/redhat/{8.0,9}
bash# cd /export/install/redhat/8.0/
bash# mkdir official_release
|
Insert the CD-ROM number one and type the following:
bash# mount /mnt/cdrom
bash# /bin/cp -var /mnt/cdrom/* official_release/
bash# eject
|
Insert the CD-ROM number two and type the following (note that we use /bin/cp instead of cp to avoid the prompt for replacing the files EULA, GPL, README, RedHat/RPMS/TRANS.TBL, RedHat/TRANS.TBL, RPM-GPG-KEY, TRANS.TBL and .discinfo):
bash# mount /mnt/cdrom
bash# /bin/cp -var /mnt/cdrom/* official_release/
bash# eject
|
Insert the CD-ROM number three and type the following:
bash# mount /mnt/cdrom
bash# /bin/cp -var /mnt/cdrom/* official_release/
bash# eject
|
If you don't have the CD-ROMs but you have downloaded the iso files, say in /tmp/, then type the following commands:
bash# for n in 1 2 3; do
> mount -o fstype=iso9660,ro,loop /tmp/psyche-i386-disc${n}.iso /mnt/cdrom/
> /bin/cp -var /mnt/cdrom/* official_release/
> umount /mnt/cdrom/
> done
|
Follow all the previous steps to create an installation tree for RedHat 9 (shrike).
To meet requirement 1, we should have an updated installation tree. This can be done in two steps: getting the rpm updates and then integrating them into the installation tree.
To get the updates, several methods exist like wget, rsync, rdist, and so on. In this howto we choose to use mirror which is a package written in Perl that uses the FTP protocol to duplicate a directory hierarchy between the machine it is run on and a remote host. Download mirror rpm in /tmp/ and then install it:
bash# rpm -Uvh /tmp/mirror-2.9-11.noarch.rpm
|
mirror breaks under Redhat 9 because of a parsing problem. Try to copy the patch mirror.patch in /tmp/ and then run the following command:
bash# patch -p0 < /tmp/mirror.patch
|
edit /etc/mirror.defaults and customize it. Note that we exclude the deletion of a directory named extra/ which will contain our own or third party updated rpms which are not managed by RedHat:
# This is the default mirror settings used by my site:
# sunsite.org.uk (193.63.255.4)
# This is home of SunSITE Northern Europe.
#
# Lee McLoughlin <lmjm@icparc.ic.ac.uk>
# You should be able to use this at other sites. You should only have
# to change bits that reference my site (sunsite).
package=defaults
# The LOCAL hostname - if not the same as `hostname`
# (I advertise the name sunsite.org.uk but the machine is
# really swallow.sunsite.org.uk.)
hostname=deploysrv.domain.net
# Keep all local_dirs relative to here
local_dir=/
# The local_dir must exist FIRST
#local_dir_check=true
#remote_password=wizards@sunsite.org.uk
mail_to=sysadmin@domain.net
# Don't mirror file modes. Set all dirs/files to these
dir_mode=0755
file_mode=0444
# By defaults files are owned by root.zero
user=0
group=0
# # Keep a log file in each updated directory
# update_log=.mirror
update_log=.mirror
# Don't overwrite my mirror log with the remote one.
# Don't pull back any of their mirror temporary files.
# nor any FSP or gopher files...
exclude_patt=(^|/)(\.mirror$|\.mirror\.log|core$|\.cap|\.in\..*\.$|MIRROR\.LOG|#.*#|\.FSP|\.cache|\.zipped|\.notar|\.message|lost\+found/|Network Trash Folder)|suky.mpe?g
# Do not to compress anything
compress_patt=
compress_prog=compress
# Don't compress information files, files that don't benifit from
# being compressed, files that tell ftpd, gopher, wais... to do things,
# the sources for compression programs...
# (Note this is the only regexp that is case insensitive.)
# z matches compress/pack/gzip, gz for gzip. (built into perl)
# taz/tgz is compressed or gzipped tar files
# arc, arj, lzh, zip and zoo are pc and/or amiga archives.
# sea are mac archives.
# vms used -z instead of .z. stupid vms.
# shk is multimedia? used on apple2s.
# rpm and deb are package formats used on RedHat and Debian Linux
compress_excl+|-z(\d+)?$|\.tgz|_tgz|\.tar\.Z|\.tar\.gz|\.taz$|\.arc$|\.zip$|\.lzh$|\.zoo$|\.exe$|\.lha$|\.zom$|\.gif$|\.jpeg$|\.jpg$|\.mpeg$|\.au$|\.shk$|rpm$|deb$|read.*me|index|info|faq|gzip|compress|(^|/)\.\.?$
# Don't delete own mirror log, .notar or .cache files (incl in subdirs)
# delete_excl=(^|/)\.(mirror|notar|cache)$
delete_excl=extra
# Ignore any local readme and .mirror files
local_ignore=README.doc.ic|(^|/)\.(mirror|notar)$
# Automatically delete local copies of files that the
# remote site has zapped
do_deletes=true
max_delete_files=50%
max_delete_dirs=50%
timeout=300
#failed_gets_excl=\:\ Permission denied\.$
|
create a file updates.redhat.com-redhat-8.0 which instructs mirror how to get the updates. In our case, we get everything in /export/install/redhat/8.0/updates/ except source rpms:
package=redhat-updates-8.0
comment=Redhat 8.0 updates
# ftp server
site=updates.redhat.com
# source directory
remote_dir=/8.0/en/os/
# directories to exclude
exclude_patt=(SRPMS/)
# destination directory
local_dir=/export/install/redhat/8.0/updates/
|
test to see if mirror would do the correct job:
bash# mirror -n updates.redhat.com-redhat-8.0
|
if all is ok, run this command to get the updates:
bash# mirror -d updates.redhat.com-redhat-8.0
|
If you have previously dowloaded some update rpms then put them in the /export/install/redhat/8.0/updates/ directory and before using the -d option of mirror, use -T to force the time-stamps of the downloaded rpms to be reset to be the same as the remote ones. This will save you much time.
Create the extra/ directory in the updates/ dir and populate it with your own or third party rpms if you want to integrate them in the updated installation tree:
bash# mkdir -p updates/extra/{athlon,i386,i486,i586,i686,noarch,SRPMS}
|
Merging the updates into the distribution tree will result in some modifications: replacing old rpms by new ones, and regenerating hdlists otherwise the new rpms will not be seen during the installation process. To do this, we create another directory named /export/install/redhat/8.0/updated_release/ which contains hard links (to save disk space) to the contents of /export/install/redhat/8.0/official_release/. Only /export/install/redhat/8.0/updated_release/RedHat/base/ is a copy of /export/install/redhat/8.0/official_release/RedHat/base/ because the hdlist files will be modified.
bash# mkdir updated_release
bash# for src in `find ./official_release/* -name base -prune -o -print`; do
> dst=./updated_release/`echo $src | sed -e 's+\./official_release/\(.*\)+\1+'`
> if [ -d $src ]; then
> mkdir -p $dst
> else
> ln $src $dst
> fi
> done
bash# cp -var ./official_release/RedHat/base ./updated_release/RedHat
|
To merge the downloaded update rpms into the updated installation tree, we use a python script which we called /export/install/redhat/8.0/extra/scripts/update_release.py. The use of python let us use the rpm module which is itself written in python by RedHat. Hence, accuracy in some operations like rpm version comparison is garanteed:
bash# mkdir -p extra/scripts
|
#!/usr/bin/python
#
# update_release.py 2.0 - merge rpm updates and third party rpms
# into a RedHat distro and check for
# dependencies/conflicts problems
# Copyright (C) 2003 - Zouhir Hafidi (Zouhir.Hafidi@agat.univ-lille1.fr)
#
# 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
#
# Usage (some examples):
# - get help:
# update_release.py -h
# - run in test mode and use current dir as a base dir
# update_release.py -n
# update_release.py -n -v
# - run in test mode and use /export/install/redhat/8.0 as a base dir
# update_release.py -n -d /export/install/redhat/8.0
# update_release.py -n -v -d /export/install/redhat/8.0
# - use current dir as a base dir. The installation dir and updates dir
# are respectively by default updated_release/RedHat/RPMS/ and updates/
# update_release.py
# update_release.py -v
# - use /export/install/redhat/8.0 as a base dir. The installation dir and
# updates dir are respectively by default updated_release/RedHat/RPMS/ and updates/
# update_release.py -d /export/install/redhat/8.0
# update_release.py -v -d /export/install/redhat/8.0
# - use /export/install/redhat/8.0 as a base dir and only check for
# dependencies/conflicts under updated_release/RedHat/RPMS
# update_release.py -c -d /export/install/redhat/8.0
# update_release.py -c -v -d /export/install/redhat/8.0
# - use /export/install/redhat/8.0 as a base dir and only check for
# dependencies/conflicts under some/relative/dir
# update_release.py -c -d /export/install/redhat/8.0 -i some/relative/dir
# update_release.py -c -v -d /export/install/redhat/8.0 -i some/relative/dir
import os
import sys
import getopt
import commands
import rpm
def usage():
print "Usage: %s [-h] | [[-n] [-v] [-d dir] [-i dir] [-u dir] [-c] [-H]]" % sys.argv[0]
print "-h, --help\t\t\tprint this message"
print "-n, --dryrun\t\t\tdry run mode (show a trace of what would be done)"
print "-v, --verbose\t\t\tprint more information when running"
print "-d dir, --basedir=dir\t\tbase dir (current dir by default)"
print "-i dir, --installdir=dir\tinstallation dir (updated_release/RedHat/RPMS/ by default)"
print "-u dir, --updatesdir=dir\tupdates dir (updates/ by default)"
print "-c, --checkonly\t\t\tcheck for dependencies/conflicts in the installation dir"
print "-H, --nohdlist\t\t\tdon't regenerate hdlists"
# Description:
# construct a dependency string (this function comes from anaconda)
# Argument(s):
# - rpm name
# - rpm version
# - dependency flags
# Return value(s):
# - dependency string
def formatRequire (name, version, flags):
string = name
if flags:
if flags & (rpm.RPMSENSE_LESS | rpm.RPMSENSE_GREATER | rpm.RPMSENSE_EQUAL):
string = string + " "
if flags & rpm.RPMSENSE_LESS:
string = string + "<"
if flags & rpm.RPMSENSE_GREATER:
string = string + ">"
if flags & rpm.RPMSENSE_EQUAL:
string = string + "="
string = string + " %s" % version
return string
# Description:
# construct a dictionary containing the headers of a set of rpms.
# If a same rpm exists with different versions, then take into
# account only the recent one. Source rpms are not processed.
# Argument(s):
# - a list of rpms filenames
# Return value(s):
# - a dictionary of the form:
# {(rpm_name, rpm_arch):(rpm_header, rpm_filename), ...}
# - a list containing source rpms and old rpms
def getRpmsHeaders(rpms):
headers = {}
bad_rpms = []
ts = rpm.TransactionSet("/", ~(rpm._RPMVSF_NOSIGNATURES))
ts.closeDB()
for rpm_file in rpms:
fdno = os.open(rpm_file, os.O_RDONLY)
header = ts.hdrFromFdno(fdno)
os.close(fdno)
if header[rpm.RPMTAG_SOURCEPACKAGE]:
bad_rpms.append(rpm_file)
if verbose_flag:
print " skip %s which is a source package" % os.path.basename(rpm_file)
else:
key = (header[rpm.RPMTAG_NAME], header[rpm.RPMTAG_ARCH])
if headers.has_key(key):
cmp = rpm.versionCompare(headers[key][0], header)
if not cmp in [-1, 1]:
print " rpm.versionCompare() returned error code %d while comparing %s and %s" % (cmp, os.path.basename(headers[key][1]), os.path.basename(rpm_file))
sys.exit(cmp)
if cmp == -1:
bad_rpms.append(headers[key][1])
if verbose_flag:
print " use %s instead of %s" % (os.path.basename(rpm_file), os.path.basename(headers[key][1]))
headers[key] = (header, rpm_file)
else:
bad_rpms.append(rpm_file)
if verbose_flag:
print " skip %s which is older than %s" % (os.path.basename(rpm_file), os.path.basename(headers[key][1]))
else:
headers[key] = (header, rpm_file)
return (headers, bad_rpms)
# Description:
# delete unwanted entries from a list of rpms filenames
# Argument(s):
# - list of rpms filenames
# - list of entries to delete
# Return value(s):
# None
def skipRpms(rpms_list, skip_list):
for item in skip_list:
del rpms_list[rpms_list.index(item)]
# Description:
# check if a given set of rpms put together doesn't have any
# dependencies nor conflicts problems
# Argument(s):
# - a dictionary of the form:
# {(rpm_name, rpm_arch):(rpm_header, rpm_filename), ...}
# Return value(s):
# - a list of errors if any
def checkDepsAndConflicts(headers):
ts = rpm.TransactionSet("/", ~(rpm._RPMVSF_NOSIGNATURES))
ts.closeDB()
for key in headers.keys():
ts.addInstall(headers[key][0], key, 'i')
return ts.check()
def main():
# Processing command line arguments
try:
opts, args = getopt.getopt(sys.argv[1:], 'hnvd:i:u:cH', \
["help", "dryrun", "verbose", "basedir=", "installdir=", \
"updatesdir=", "checkonly", "nohdlist"])
except getopt.GetoptError:
usage()
sys.exit(1)
if args != []:
usage()
sys.exit(1)
global verbose_flag
verbose_flag = 0
dryrun_flag = 0
base_dir = os.getcwd()
updated_release_dir = "updated_release/RedHat/RPMS"
updates_dir = "updates"
checkonly_flag = 0
nohdlist_flag = 0
for o, a in opts:
if o in ("-h", "--help"):
usage()
sys.exit()
if o in ("-n", "--dryrun"):
dryrun_flag = 1
if o in ("-v", "--verbose"):
verbose_flag = 1
if o in ("-d", "--basedir"):
base_dir = a
if o in ("-i", "--installdir"):
updated_release_dir = a
if o in ("-u", "--updatesdir"):
updates_dir = a
if o in ("-c", "--checkonly"):
checkonly_flag = 1
if o in ("-H", "--nohdlist"):
nohdlist_flag = 1
# Some initializations
updated_release_dir = os.path.join(base_dir, updated_release_dir)
updates_dir = os.path.join(base_dir, updates_dir)
old_rpms_dir = os.path.join(base_dir, "old_rpms")
regenerate_hdlist = 0
if dryrun_flag:
verbose_flag = 1
# The updated release dir must exist
if not os.path.isdir(updated_release_dir):
print "can't access %s, nothing to do" % updated_release_dir
sys.exit(2)
if verbose_flag:
print "processing rpms under %s ..." % updated_release_dir
# Get all rpm names under updated_release_dir
exit_status, output = commands.getstatusoutput("find %s -name '*\.rpm' | grep -v '.src.rpm'" % updated_release_dir)
if exit_status != 0:
sys.exit(3)
updated_release_rpms = output.split()
# Get headers of all rpms under updated_release_dir
updated_release_headers, bad_rpms = getRpmsHeaders(updated_release_rpms)
if bad_rpms != []:
print " ERROR: the following rpms are either source rpms or older rpms. Please remove them:"
for rpm_file in bad_rpms:
print " %s" % rpm_file
sys.exit(4)
# Before merging, check if the current updated release dir contains
# any dependencies or conflicts problems and exit if so
errors = checkDepsAndConflicts(updated_release_headers)
if errors:
print "ERROR: the installation tree already contains conflicts and/or dependencies problems"
for ((name, version, release), (reqname, reqversion), \
flags, suggest, sense) in errors:
if sense==rpm.RPMDEP_SENSE_REQUIRES:
print " depcheck: package %s needs %s" % ( name, formatRequire(reqname, reqversion, flags))
elif sense==rpm.RPMDEP_SENSE_CONFLICTS:
print " depcheck: package %s conflicts with %s" % (name, reqname)
sys.exit(4)
if verbose_flag:
print "done"
if checkonly_flag:
if errors:
print "dependencies/conflicts problems exist in %s" % updated_release_dir
else:
print "no dependencies/conflicts problems in %s" % updated_release_dir
sys.exit()
# The updates dir must exist
if not os.path.isdir(updates_dir):
print "can't access %s, nothing to do" % updates_dir
sys.exit(2)
# If necessary, create the dir to put old rpms into
if not os.path.isdir(old_rpms_dir):
os.mkdir(old_rpms_dir)
if verbose_flag:
print "processing rpms under %s ..." % updates_dir
# Get all rpm names under updates_dir
exit_status, output = commands.getstatusoutput("find %s -name '*\.rpm' | grep -v '.src.rpm'" % updates_dir)
if exit_status != 0:
sys.exit(3)
updates_rpms = output.split()
# Get headers of all rpms under updates_dir
updates_headers, bad_rpms = getRpmsHeaders(updates_rpms)
# Ignore unwanted rpms
skipRpms(updates_rpms, bad_rpms)
if bad_rpms != []:
print " WARNING: the following rpms are either source rpms or older rpms. I will ignore them:"
for rpm_file in bad_rpms:
print " %s" % rpm_file
if verbose_flag:
print "done"
# Take the entries in the updates_headers dictionary and merge them
# with the entries in the updated_release_headers dictionary:
# - for every entry in the updates_headers dictionary
# - if a corresponding entry with an old version exists
# in the updated_release_headers dictionary then drop it
# in the old_rpms_headers dictionary and replace it with
# the new one
# - if there is no corresponding entry with an old version
# then simply add the new one in the updated_release_headers
# dictionary
old_rpms_headers = {}
for key in updates_headers.keys():
if updated_release_headers.has_key(key):
cmp = rpm.versionCompare(updated_release_headers[key][0], updates_headers[key][0])
if cmp == 1:
if verbose_flag:
print "skip %s which is older than %s" % (os.path.basename(updates_headers[key][1]), os.path.basename(updated_release_headers[key][1]))
elif cmp == 0:
if verbose_flag:
print "skip %s which is the same as %s" % (os.path.basename(updates_headers[key][1]), os.path.basename(updated_release_headers[key][1]))
elif cmp == -1:
if verbose_flag:
print "exchange %s with %s" % (os.path.basename(updated_release_headers[key][1]), os.path.basename(updates_headers[key][1]))
old_rpms_headers[key] = updated_release_headers[key]
updated_release_headers[key] = updates_headers[key]
else:
print " rpm.versionCompare() returned error code %d while comparing %s and %s" % (cmp, os.path.basename(updated_release_headers[key][1]), os.path.basename(updates_headers[key][1]))
sys.exit(cmp)
else:
updated_release_headers[key] = updates_headers[key]
if verbose_flag:
print "add %s" % os.path.basename(updates_headers[key][1])
# At this point, the merging process is done and the updated_release_headers
# dictionary should be up to date. Before applying the changes to the updated
# release dir, we must make sure that there's no dependencies nor conflicts
# problems. If so, we do a "backtracking" on the headers which make problems
# until a state without any dependencies/conflicts is reached.
while 1:
deps_and_conflicts = 0
errors = checkDepsAndConflicts(updated_release_headers)
if errors:
# Construct a list containing the names of the rpms that
# make problems. If a same rpm exists for different
# architectures, then all of them will not be used.
# NOTE: we don't know how to discard only problematic rpms
# since the architecture is not reported by the check()
# method (is there a fix ?)
if verbose_flag:
print "merging will result in the following conflicts and/or dependencies problems:"
name_list = []
for ((name, version, release), (reqname, reqversion), \
flags, suggest, sense) in errors:
if sense==rpm.RPMDEP_SENSE_REQUIRES:
if verbose_flag:
print " depcheck: package %s needs %s" % ( name, formatRequire(reqname, reqversion, flags))
deps_and_conflicts = 1
if not name in name_list:
name_list.append(name)
elif sense==rpm.RPMDEP_SENSE_CONFLICTS:
if verbose_flag:
print " depcheck: package %s conflicts with %s" % (name, reqname)
deps_and_conflicts = 1
if not name in name_list:
name_list.append(name)
# Discard entries with dependencies/conflicts problems in
# the updated_release_headers dictionary
if verbose_flag:
print "backtracking:"
for (name, arch) in updated_release_headers.keys():
if name in name_list :
if os.path.dirname(updated_release_headers[(name, arch)][1]) == updated_release_dir:
print "CRITICAL ERROR: attempt to discard %s" % updated_release_headers[(name, arch)][1]
sys.exit(4)
if verbose_flag:
print " discard %s" % updated_release_headers[(name, arch)][1]
del updated_release_headers[(name, arch)]
# Put back those entries which have been replaced by recent
# entries which resulted in dependencies/conflicts problems
for (name, arch) in old_rpms_headers.keys():
if name in name_list :
if verbose_flag:
print " reuse %s" % old_rpms_headers[(name, arch)][1]
updated_release_headers[(name, arch)] = old_rpms_headers[(name, arch)]
del old_rpms_headers[(name, arch)]
# The infinite loop *should* be broken after some iterations
if deps_and_conflicts == 0:
break
# At this point, the updated_release_headers dictionary contains
# entries with the rpms filenames that should be in the updated
# release dir so that we have no dependencies/conflicts problems.
# The old_rpms_headers dictionary contains entries with the rpms
# filenames that should be moved from the updated release dir to
# the old rpms dir. Just reflect these changes on the directories
# themselves
for key in updated_release_headers.keys():
if os.path.dirname(updated_release_headers[key][1]) == updated_release_dir:
continue
regenerate_hdlist = 1
if not dryrun_flag:
os.link(updated_release_headers[key][1], \
os.path.join(updated_release_dir, os.path.basename(updated_release_headers[key][1])))
else:
print "should",
if key in old_rpms_headers.keys():
print "update %s with %s" % (os.path.basename(old_rpms_headers[key][1]), \
os.path.basename(updated_release_headers[key][1]))
if not dryrun_flag:
os.rename(old_rpms_headers[key][1], \
os.path.join(old_rpms_dir, os.path.basename(old_rpms_headers[key][1])))
else:
print "add %s" % os.path.basename(updated_release_headers[key][1])
# Regenerate hdlists if necessary
if (not nohdlist_flag) and regenerate_hdlist:
if not dryrun_flag:
os.system("/usr/lib/anaconda-runtime/genhdlist %s/../.." % updated_release_dir)
else:
if verbose_flag:
print "should",
if verbose_flag:
print "regenerate hdlist"
if __name__ == "__main__":
main()
|
Mainly, this script will merge a set of rpms into an existing installation tree. It is a complete rewrite of the updated_release.py v1.0 script. It will only merge those rpms so that the resulting installation tree will have no dependencies nor conflicts problems. This is very useful in some situations:
The ftp connection to the remote site is broken before all the update rpms are downloaded.
You want to customize the installation tree and add your own or third party rpms.
Alternatively, the script can be used just to check if the contents of a given directory have dependencies/conflicts problems or not.
Now do:
bash# chmod 755 ./extra/scripts/update_release.py
bash# ./extra/scripts/update_release.py
|
In order to meet requirement 5 just daily get the updates and merge them into the updated installation tree by putting the following update_release.cron file in the /etc/cron.daily/ directory:
#! /bin/sh
usage() {
echo "usage: $0 [-M] [-U] [release ...]"
echo "-M: don't run mirror"
echo "-U: don't update the install tree"
exit 1
}
md5_check() {
for rpm in `find $1 -name '*.rpm' -print`; do
if rpm -K --nosignature $rpm > /dev/null 2>&1; then
continue
else
echo "removing corrupted rpm: $rpm"
rm -rf $rpm
fi
done
}
BASE_DIR=/export/install/redhat
# Parse command line arguments
Moption=
Uoption=
while getopts MU option; do
case $option in
M) Moption=M
;;
U) Uoption=U
;;
*) usage
;;
esac
done
shift `expr $OPTIND - 1`
if [ "$*" = "" ]; then
RELEASES="8.0 9"
else
RELEASES=$*
fi
# Do the job for the specified releases
for release in $RELEASES; do
if [ ! -d ${BASE_DIR}/${release} ]; then
echo "can't access ${BASE_DIR}/${release}"
continue
fi
# Get new update rpms by mirroring the remote dir on
# the local dir. If we can't establish a connection
# to the remote site then do some retries.
if [ "${Moption}" != "M" ]; then
for delay in 0s 5m 30m 1h; do
echo "sleep for $delay ..."
sleep $delay
if [ -f /tmp/mirror.txt ]; then
rm -rf /tmp/mirror.txt
fi
mirror -d ${BASE_DIR}/${release}/updates.redhat.com-redhat-${release} > /tmp/mirror.txt 2>&1
status=$?
cat /tmp/mirror.txt
if [ $status -eq 0 ]; then
break
fi
done
fi
# Remove corrupted rpms if any
md5_check ${BASE_DIR}/${release}/updates
# Merge new update rpms if any into the updated_release tree
# and regenerate hdlists if necessary
if [ "${Uoption}" != "U" ]; then
if [ "${Moption}" = "M" -o \( "${Moption}" != "M" -a "`grep '^Got ' /tmp/mirror.txt 2> /dev/null`" != "" \) ]; then
${BASE_DIR}/${release}/extra/scripts/update_release.py -d ${BASE_DIR}/${release}
fi
fi
done
|
bash# chmod 755 /etc/cron.daily/update_release.cron
|
To "ensure" that the installation tree is not accessed while the mirroring/updating/merging process is in progress, then modify the file /etc/crontab to start daily cron jobs about two hours earlier than the default (4h02 am), or move the file /etc/cron.daily/update_release.cron to another directory (say in /export/install/redhat/) and create a cron job which starts the script update_release.cron at 2h02 am for example:
bash# mv /etc/cron.daily/update_release.cron /export/install/redhat/
bash# echo "02 2 * * * /export/install/redhat/update_release.cron" >> /var/spool/cron/root
bash# chmod 600 /var/spool/cron/root
|
At this point, the updated installation tree is a good starting point for those who want to create iso CDs. See Creating updated iso CDs.
To make these installation trees available to linux boxes then add the following line to the file /etc/exports :
/export/install/redhat 192.168.1.0/24(ro)
|
and start the NFS daemon:
bash# chkconfig --level 345 nfs on
bash# service nfs start
|
Global time synchronisation is very important for some services to work correctly so we use the Network Time Protocol to help synchronize the system clock with an accurate time source.
In general, it is a good practice to have two servers (named timesrv1.domain.net and timesrv2.domain.net for example) on the local network acting as the time servers or relay servers for all the other hosts (see Time Precision HOWTO). Relay servers will keep time accurate with respect to "stratum 1" or "stratum 2" time servers (see http://www.eecis.udel.edu/~mills/ntp/servers.html for available public servers).
In our case, to simplify we assume that we have only one relay server which is our deployment server itself. To setup the relay server, follow these steps:
bash# service ntpd stop
|
put the following lines in the file /etc/ntp.conf so that the relay server uses its own system clock as the accurate time and permits hosts on the 192.168.1.0 network to synchronize with it :
restrict default ignore
restrict 127.0.0.1
restrict 192.168.1.0 mask 255.255.255.0 notrust nomodify notrap
# restrict xxx.xxx.xxx.xxx mask 255.255.255.255 nomodify notrap noquery
# restrict yyy.yyy.yyy.yyy mask 255.255.255.255 nomodify notrap noquery
# restrict zzz.zzz.zzz.zzz mask 255.255.255.255 nomodify notrap noquery
# server xxx.xxx.xxx.xxx prefer
# server yyy.yyy.yyy.yyy
# server zzz.zzz.zzz.zzz
server 127.127.1.0 # local clock
fudge 127.127.1.0 stratum 10
driftfile /etc/ntp/drift
broadcastdelay 0.008
|
and restart the ntp service:
bash# service ntpd start
|
If you want the relay server to synchronize with public time servers, then replace xxx.xxx.xxx.xxx, yyy.yyy.yyy.yyy and zzz.zzz.zzz.zzz with IP addresses from the list of public time servers which are close to your geographical location, and uncomment the corresponding lines. Before starting the ntpd service, make sure that the UDP traffic on source/destination port number 123 is allowed (especially if you are behind a firewall), and initialize the system clock of the relay server to avoid significant deviations which prevent the ntpd service from starting. Use a command like this:
bash# ntpdate -b xxx.xxx.xxx.xxx
|
Run this command to be sure that the ntpd service will start after every reboot:
bash# chkconfig --level 345 ntpd on
|
To allow linux boxes to be installed automatically (requirement 5) and remotely via kickstart we need to configure the DHCP service and create the kickstart files. Edit the file /etc/dhcpd.conf so it looks like this (put the correct hardware addresses of your network cards):
# Sample configuration file for ISCD dhcpd
#
# Make changes to file and copy it to /etc/dhcpd.conf
#
not authoritative;
ddns-update-style none;
default-lease-time 21600;
max-lease-time 21600;
option subnet-mask 255.255.255.0;
option broadcast-address 192.168.1.255;
option routers 192.168.1.253;
option domain-name-servers 192.168.1.251, 192.168.1.252;
option domain-name "domain.net";
shared-network WORKSTATIONS {
subnet 192.168.1.0 netmask 255.255.255.0 {
}
}
group {
use-host-decl-names on;
option log-servers 192.168.1.254;
host linbox1 {
hardware ethernet 11:11:11:11:11:11;
fixed-address 192.168.1.1;
if substring (option vendor-class-identifier, 0, 9) = "PXEClient" {
filename "rh-install/pxelinux.0";
} else if substring (option vendor-class-identifier, 0, 9) = "Etherboot" {
filename "rh-install/vmlinuz-8.0.nbi";
}
}
host linbox2 {
hardware ethernet 22:22:22:22:22:22;
fixed-address 192.168.1.2;
if substring (option vendor-class-identifier, 0, 9) = "PXEClient" {
filename "rh-install/pxelinux.0";
} else if substring (option vendor-class-identifier, 0, 9) = "Etherboot" {
filename "rh-install/vmlinuz-8.0.nbi";
}
}
host linbox3 {
hardware ethernet 33:33:33:33:33:33;
fixed-address 192.168.1.3;
if substring (option vendor-class-identifier, 0, 9) = "PXEClient" {
filename "rh-install/pxelinux.0";
} else if substring (option vendor-class-identifier, 0, 9) = "Etherboot" {
filename "rh-install/vmlinuz-8.0.nbi";
}
}
host linbox4 {
hardware ethernet 44:44:44:44:44:44;
fixed-address 192.168.1.4;
if substring (option vendor-class-identifier, 0, 9) = "PXEClient" {
filename "rh-install/pxelinux.0";
} else if substring (option vendor-class-identifier, 0, 9) = "Etherboot" {
filename "rh-install/vmlinuz-8.0.nbi";
}
}
}
|
Turn on the DHCP service and start it:
bash# chkconfig --level 345 dhcpd on
bash# service dhcpd start
|
The file /etc/dhcpd.conf contains all the information that will be sent to dhcp clients upon request. These information include IP address, network mask, domain name, default router and so on. In our case, the appropriate information is sent to every client according to its MAC address. The presence of the if statement is needed in some situations (see the section Installing linux boxes).
Kickstart files contain all the information needed during the installation process. They are named in conjonction with linux boxes IP addresses. For example, kickstart file 192.168.1.1-kickstart will be used to install the linux box with IP address 192.168.1.1 and this IP address is associated with hardware (MAC) address 11:11:11:11:11:11 in /etc/dhcpd.conf.
Create a kickstart directory:
bash# mkdir kickstart
|
and populate it with kickstart files. Kickstart files can be generated using several methods:
Edit a copy of the sample.ks file found in the RH-DOCS/ directory of the RedHat Linux documentation CD.
Edit a copy of the anaconda-ks.cfg file in the /root/ directory of an already installed linux box. This is very helpful particularly if the hardware configuration of your boxes are identical.
Use the kickstart configurator which is a GUI application that can be started with the command /usr/sbin/ksconfig. This is very useful especially for people who don't know the different keywords used by kickstart.
Use a text editor and type in from scratch all the information needed by anaconda during the installation process. This suppose that you know the syntax of a kickstart file.
Below is an example of a kickstart file for the linux box with IP address 192.168.1.1. The contents are self explanatory and if you encouter some problems you can read more about kickstart (see Resources). Just a few comments:
For instance, we assume that every linux box must have gnome and mozilla installed. You can add or exclude some packages if you want.
There is at least two ways to generate MD5 encrypted passwords so you haven't to use your own scripts for this:
bash# grub-md5-crypt
|
or, if you have openssl installed:
bash# openssl passwd -1
|
We recommend that you protect grub with a password to be sure that no one can give extra options to the kernel (requirement 4).
The /usr/bin/chvt command is used to change to the virtual terminal where the output from the various commands is dumped.
The %pre and %post sections are optional and we used them respectively to save and restore ssh keys in case of reinstalling a machine so that users who connected previously to this machine using ssh don't have to change the keys stored in the file ~/.ssh/known_hosts.
Note that we use the --nochroot option with the %post keyword to have access to what we saved under /mnt/tmp/ otherwise we will loose that. The whole installed tree starting from / is under /mnt/sysimage/ before chrooting.
You have to use the nolock option with the mount command in the %post section otherwise the mount command will take a long time before it executes.
The lines to be appended to the file /etc/rc.local aim to initialize the system clock with time on the internal relay servers.
# New install not an upgrade
install
# Don't run in graphical mode
text
# Language, keyboard and mouse
lang fr_FR
langsupport --default fr_FR.UTF-8 fr_FR.UTF-8
keyboard fr-latin1
mouse genericps/2 --device psaux --emulthree
# Configuration of the video card. Use "xconfig" without arguments to let
# anaconda autodetect and autoconfigure your video card if you don't know
# what to do
#xconfig --card "ATI Mach64" --videoram 8192 --hsync 31-69 --vsync 56-75 --resolution 1024x768 --depth 32 --startxonboot
xconfig --startxonboot
# Configuration of the network card. We use a static address
network --device eth0 --bootproto static --ip 192.168.1.1 --netmask 255.255.255.0 --gateway 192.168.1.253 --nameserver 192.168.1.252 --hostname linbox1.domain.net
# Use "grub-md5-crypt" or "openssl passwd -1" to generate the MD5 encrypted passwd
rootpw --iscrypted $1$P20f7/E.$R176cFX0T.aQC3n9y0Us..
# Don't activate ipchains. Use iptables instead
firewall --disabled
# Enable shadow and MD5 passwds
authconfig --enableshadow --enablemd5
# Time
timezone Europe/Paris
# Use "grub-md5-crypt" or "openssl passwd -1" to generate the MD5 encrypted passwd
bootloader --location=mbr --md5pass=$1$uogZn/$zgx4e3ZQ/Qe8S/JcDGEg6/
# Partitions
clearpart --linux
part /boot --fstype ext3 --size=100 --ondisk=hda
part swap --size=1024 --ondisk=hda
part /tmp --fstype ext3 --size=1024 --ondisk=hda
part / --fstype ext3 --size=1 --grow --ondisk=hda
# Automatically reboot after the install
reboot
# nfs install
nfs --server 192.168.1.254 --dir /export/install/redhat/8.0/updated_release
# List of packages we want to install
%packages --resolvedeps
@ GNOME Desktop Environment
mozilla
ntp
# What to do before installation
%pre
#!/bin/sh
###############
# change to virtual terminal number 3
#
/usr/bin/chvt 3
###############
# try to keep ssh keys (if any) among reinstalls. This is done by
# saving the contents of /etc/ssh dir
# get the list of existing partitions and their corresponding mount points
for dev in `fdisk -l | cut -d' ' -f1 | grep /dev`; do
mount_point=`e2label $dev 2> /dev/null`
if [ "$mount_point" != "" ]; then
echo $dev $mount_point
fi
done > /tmp/devs.$$
# locate the partition where /etc/ssh dir resides. Here's different possibilities:
# - a separate partition containing only /etc/ssh
# - a /etc partition (NORMALLY NOT POSSIBLE) containing ssh dir
# - a / partition containing etc/ssh dir
if [ -s /tmp/devs.$$ ]; then
ssh_dir='' ; ssh_dev=`grep '/etc/ssh$' /tmp/devs.$$`
[ "$ssh_dev" = "" ] && {
ssh_dir='ssh' ; ssh_dev=`grep '/etc$' /tmp/devs.$$`
}
[ "$ssh_dev" = "" ] && {
ssh_dir='etc/ssh' ; ssh_dev=`grep '/$' /tmp/devs.$$`
}
set $ssh_dev
echo "+++ /etc/ssh *should* be in partition $1 labeled $2"
echo "+++ mounting $1 on /mnt/tmp ..."
[ ! -d /mnt/tmp ] && mkdir /mnt/tmp
mount $1 /mnt/tmp
if [ -d /mnt/tmp/$ssh_dir ]; then
echo "+++ saving ssh files in /tmp/ssh ..."
mkdir /tmp/ssh
cp -a /mnt/tmp/$ssh_dir/* /tmp/ssh
else
echo "+++ can't find any ssh dir"
fi
echo "+++ unmounting /mnt/tmp ..."
umount /mnt/tmp
else
echo "+++ can't find any partition on this system"
fi
###############
# go back to virtual terminal number 1 (7 if you install in graphical mode)
#
{ sleep 30 ; /usr/bin/chvt 1 ; } &
# What to do after installation
%post --nochroot
#!/bin/sh
###############
# change to virtual terminal number 3
#
/usr/bin/chvt 3
# put back ssh keys
[ -d /tmp/ssh ] && cp -a /tmp/ssh/* /mnt/sysimage/etc/ssh
# other stuff
chroot /mnt/sysimage << EOF 2>&1 | tee -a /tmp/anaconda.cfg
echo "+++ mounting 192.168.1.254:/export/install/redhat on /mnt/tmp"
mkdir /mnt/tmp
mount -o ro,nolock 192.168.1.254:/export/install/redhat /mnt/tmp
echo "+++ copying the file 'hosts' in /etc"
cp -p /mnt/tmp/8.0/extra/config/hosts /etc
echo "+++ copying the file 'ntp.conf' in /etc"
cp -p /mnt/tmp/8.0/extra/config/ntp.conf /etc
echo "+++ setting /etc/rc.local for ntp"
cat >> /etc/rc.local << END
. /etc/rc.d/init.d/functions
echo "Adjusting the clock"
if status ntpd > /dev/null 2>&1; then
service ntpd stop
fi
ntpdate -b 192.168.1.254
service ntpd start
END
echo "+++ unmounting /mnt/tmp"
umount /mnt/tmp
echo
echo "installation completed, the system will reboot in few seconds"
EOF
###############
# keep track of system and installer logs
#
cp /tmp/{syslog,anaconda.log} /mnt/sysimage/root
###############
# go back to virtual terminal number 1 (7 if you install in graphical mode)
#
chroot /mnt/sysimage /bin/sleep 10 ; /usr/bin/chvt 1
|
give the right permissions to kickstart files:
bash# chmod 644 kickstart/*
|
Now we create the directory extra/config/ and put the files hosts and ntp.conf into it:
bash# mkdir -p extra/config
|
The hosts file will be used only if the name servers you use don't resolve your internal hostnames. This is typically the case if the name servers are those of your ISP. The hosts file will contain something like this:
# Do not remove the following line, or various programs
# that require network functionality will fail.
127.0.0.1 localhost.localdomain localhost
192.168.1.1 linbox1.domain.net linbox1
192.168.1.2 linbox2.domain.net linbox2
192.168.1.3 linbox3.domain.net linbox3
192.168.1.4 linbox4.domain.net linbox4
192.168.1.254 deploysrv.domain.net deploysrv
|
The ntp.conf file must contain a reference to our relay servers (only one in our case but it is recommended to add at least another one):
restrict default ignore
restrict 127.0.0.1
restrict 192.168.1.254 mask 255.255.255.255 nomodify notrap noquery
#restrict 192.168.1.??? mask 255.255.255.255 nomodify notrap noquery
server 192.168.1.254
#server 192.168.1.???
server 127.127.1.0 # local clock
fudge 127.127.1.0 stratum 10
driftfile /etc/ntp/drift
broadcastdelay 0.008
|
Network installation of linux boxes via kickstart can be done by using a network installation floppy, a network installation CD, PXE, or etherboot.
To generate a network installation floppy, insert a blank floppy and type the following commands (for RedHat 9, replace bootnet.img with bootdisk.img):
bash# cat updated_release/images/bootnet.img > /dev/fd0
bash# mount /mnt/floppy
bash# vi -n /mnt/floppy/syslinux.cfg
|
Modify the file /mnt/floppy/syslinux.cfg to boot directly from the floppy in kickstart mode without prompting the user:
default ks
label ks
kernel vmlinuz
append ks=nfs:192.168.1.254:/export/install/redhat/8.0/kickstart/ initrd=initrd.img lang= devfs=nomount ramdisk_size=9216
|
bash# umount /mnt/floppy
|
The file bootnet.img contains mainly a kernel and an initial ramdisk with only a part of all the network drivers supported by RedHat (this is because a kernel and an initial ramdisk with the whole network drivers don't fit on a single floppy). Then, in some cases you need to generate a second floppy with supplemental network drivers. If so, insert a second blank floppy after creating the first one and type the following command:
bash# cat updated_release/images/drvnet.img > /dev/fd0
|
The problem of requiring supplemental drivers don't apply to network drivers only but to block device drivers too. The supplemental block device drivers floppy can be created using the image file named drvblock.img. In future releases of this document, we will include the procedure to create a single floppy containing just the drivers you need otherwise if you want to use a driver on the (network or block device) supplemental floppy then you will be prompted to insert it (remember, in requirement 5 we need maximum automation). Another solution consists of using a CD, PXE, or etherboot (see below).
To create a network installation CD, we use isolinux this way:
bash# mkdir /tmp/bootCD
bash# cp -r updated_release/isolinux /tmp/bootCD
bash# vi /tmp/bootCD/isolinux/isolinux.cfg
|
Modify the file /tmp/bootCD/isolinux/isolinux.cfg to boot directly from the CD in kickstart mode without prompting the user:
default ks
label ks
kernel vmlinuz
append ks=nfs:192.168.1.254:/export/install/redhat/8.0/kickstart/ initrd=initrd.img lang= devfs=nomount ramdisk_size=9216
|
You can customize as you want but make sure that the files have the right permissions:
bash# chmod u+w /tmp/bootCD/isolinux/*
|
Use mkisofs to produce an iso image of the CD:
bash# mkisofs -o /tmp/boot.iso -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -R -J -V -T /tmp/bootCD/
|
Finally, burn the bootable CD using the cdrecord command below (use the appropriate device ID for you) or any tool you usually use to burn CDs:
bash# cdrecord -v speed=4 dev=0,0,0 -eject /tmp/boot.iso
|
By using a CD, you have a reliable media and you overcome the floppy disk space problem but you leave 99% of the CD space unused (about 4 Mbytes only are needed on the CD). We recommand to use rewritable CDs if you will regenerate the iso image and burn it again.
If the network cards on your linux boxes support PXE then we still need a kernel and an initial ramdisk with network drivers as above. We also need a file called pxelinux.0 part of the syslinux rpm. The filename will be sent by DHCP (see /etc/dhcpd.conf in a previous section) to linux boxes and the file itself is downloaded via tftp.
Instead of extracting the kernel and the initial ramdisk from the file bootnet.img (by mounting it using the loop device), RedHat provides a kernel and an initial ramdisk with all the network drivers in the directory images/pxeboot/ in the first installation CD. Follow these steps:
bash# mkdir -p /tftpboot/rh-install/pxelinux.cfg
bash# cp updated_release/images/pxeboot/initrd.img /tftpboot/rh-install/initrd-8.0.img
bash# cp updated_release/images/pxeboot/vmlinuz /tftpboot/rh-install/vmlinuz-8.0
bash# cp /usr/lib/syslinux/pxelinux.0 /tftpboot/rh-install/
bash# cd /tftpboot/rh-install/pxelinux.cfg
bash# ln -s remote_install-8.0 C0A80101
bash# ln -s remote_install-8.0 C0A80102
bash# ln -s remote_install-8.0 C0A80103
bash# ln -s remote_install-8.0 C0A80104
bash# cd /export/install/redhat/8.0/
|
Edit the file /tftpboot/rh-install/pxelinux.cfg/remote_install-8.0 so it looks like this:
default ks
label ks
kernel vmlinuz-8.0
append ks=nfs:192.168.1.254:/export/install/redhat/8.0/kickstart/ initrd=initrd-8.0.img lang= devfs=nomount ramdisk_size=9216
|
bash# chmod 644 /tftpboot/rh-install/pxelinux.cfg/remote_install-8.0
|
C0A80101, C0A80102, C0A80103, and C0A80104 are respectively the IP addresses 192.168.1.1, 192.168.1.2, 192.168.1.3, and 192.168.1.4 of the linux boxes in hex format and without the dots.
To do such conversions, look for a program called gethostip or use the following script:
#! /bin/sh
# ip2hex.sh convert an IP address to an hex format
# without the dots
# Author: Zouhir HAFIDI <Zouhir.Hafidi@agat.univ-lille1.fr>
#
usage() {
echo "usage: $0 IP_address"
exit 1
}
get_hex_ip() {
OLD_IFS=$IFS
IFS=.
set $1
IFS=$OLD_IFS
hex_ip=
bytes=0
for i in $*; do
[ "`echo $i | grep '^[0-9]*$'`" = "" ] && {
echo "invalid IP address"
usage
}
[ $i -gt 255 -o $i -lt 0 ] && {
echo "invalid IP address"
usage
}
[ $i -lt 16 ] && hex_ip=$hex_ip"0"
hex_ip=$hex_ip`echo "ibase=10; obase=16; $i" | bc`
bytes=`expr $bytes + 1`
done
[ $bytes -ne 4 ] && {
echo "invalid IP address"
usage
}
echo $hex_ip
}
[ $# -eq 0 ] && usage
get_hex_ip $1
|
Network installation using PXE relies on the tftp protocol. Type the following command to enable tftp:
bash# chkconfig --level 345 tftp on
|
If the network cards on your linux boxes don't have PXE boot roms then you can use etherboot by creating a special bootable floppy. Go to http://www.rom-o-matic.net/ and simply follow the instructions:
Choose your network card
Select "Floppy Bootable ROM image (.lzdsk)"
Click on "Configure" and change the value of "ASK_BOOT" from 3 to 0 to boot directly without timeout
Click on "Get ROM"
Make the bootable floppy as shown in the example
This bootable floppy will load an image to perform the installation. The image must be created and tagged using mknbi. Download mknbi rpm in /tmp/ and then install it:
bash# rpm -Uvh /tmp/mknbi-1.4.0-1.noarch.rpm
|
To create the tagged image, we need the kernel and the initial ramdisk provided by RedHat to do a network installation. As for PXE, we can use the kernel and the initial ramdisk provided in the directory images/pxeboot/ in the first installation CD. Follow these steps:
bash# mkdir -p /tftpboot/rh-install
bash# mknbi-linux --append="ks=nfs:192.168.1.254:/export/install/redhat/8.0/kickstart/ lang= devfs=nomount ramdisk_size=9216" updated_release/images/pxeboot/vmlinuz updated_release/images/pxeboot/initrd.img > /tftpboot/rh-install/vmlinuz-8.0.nbi
|
The etherboot floppy works for a specific type of network card and hence you may create several floppies if you have several kinds of network cards. On the other side, the network installation floppy can be used for different kinds of network cards (those for which a network driver is included in the network installation floppy).
Before deploying, make sure that everything below /export/install/redhat/ is world readable and that all scripts permissions have the execution bit set.
Now to install a linux box, just power it on and immediately enable PXE (this is generally done in the bios setup or in the network card boot setup), or boot up using the network installation CD or the floppy (network install or etherboot). If things are set correctly, the installation will proceed automagically:
In the case of a network installation floppy or CD, the linux box loads the kernel on the floppy/CD, initializes the ramdisk, detects the network card and loads the appropriate module, and broadcasts a DHCP request containing its MAC address. The DHCP server responds by sending the networking information (IP address, mask, ...) to the linux box according to the MAC address in the DHCP request. From then, the linux box will use these information to communicate on the network.
The file syslinux.cfg on the floppy/CD instructs the linux box to get its kickstart file by doing a NFS mount of the /export/install/redhat/8.0/kickstart/ directory on the deployment server. Since the file is not explicitly mentioned (the path ends with a slash), the linux box looks for a file of the form <ip_addr>-kickstart where <ip_addr> is the IP address of the linux box received from the DHCP server. The kickstart file will be copied in the ramdisk as /tmp/ks.cfg and the installation will then be guided by the information it contains. As said previously, during the installation the /mnt/sysimage/ directory reflects the future contents of the file systems when the box is up and running.
In the case of PXE, the linux box starts by broadcasting a DHCP request containing its MAC address. The DHCP server responds by sending the networking information (IP address, mask, ...) and the filename rh-install/pxelinux.0 to the linux box according to the MAC address in the DHCP request. The linux box will use tftp to load the file rh-install/pxelinux.0 from the directory /tftpboot/ on the deployment server and executes it. rh-install/pxelinux.0 will look for a file within the directory /tftpboot/rh-install/pxelinux.cfg/ with a name that corresponds to the IP address of the linux box in hex format. This file tells the linux box to load the kernel, initialize the ramdisk, and get its kickstart file by doing a NFS mount of the /export/install/redhat/8.0/kickstart/ directory on the deployment server. The installation will then be guided by the information available in the kickstart file.
In the case of etherboot, the linux box boots from the etherboot floppy and broadcasts a DHCP request containing its MAC address. The DHCP server responds by sending the networking information (IP address, mask, ...) and the filename rh-install/vmlinuz-8.0.nbi to the linux box according to the MAC address in the DHCP request. The linux box will use tftp to load the file rh-install/vmlinuz-8.0.nbi (a tagged kernel and initial ramdisk) from the directory /tftpboot/ on the deployment server. The linux box will get its kickstart file by doing a NFS mount of the /export/install/redhat/8.0/kickstart/ directory on the deployment server. The installation will then be guided by the information available in the kickstart file.
Don't forget to eject the floppy or CD just after booting otherwise the linux box will install, reboot, reinstall, reboot ... This applies to PXE as well, so don't forget to disable it after the first reboot. You can comment the command "reboot" in kickstart files to prevent linux boxes from rebooting at the end of the installation.
During the installation, you can see a log of the installer messages and a log of the kernel messages respectively in terminals number 3 and 4. This can be very helpful in case things go wrong. If you want to retreive these logs later, we added some lines in the kickstart files to save them as /root/anaconda.log and /root/syslog.