3. Maintaining Linux

System administrators are usually facing tasks (critical or not) that must be accomplished periodically (hourly, daily, weekly ...) and/or from time to time. Some of them may concern all the linux boxes or only specific ones. In this section, we will address some situations such as updating the system, adding or removing packages, changing configuration files and so on.

3.1. Updating installed packages

We said in requirement 1 that every new installation must be an up to date installation and that's what we did in section 2. The problem is that if new update rpms become available after the linux boxes are installed and are running, these new update rpms are not automatically integrated in the running systems. So what we need here is an updater.

The native updater in RedHat is called up2date. It is tightly coupled with Red Hat Network (RHN) and you must subscribe to use it. Many other updaters exist and are listed in http://www.rpm.org/software/updaters/ (future releases of this document may include a detailed comparison). Daily updating was critical for me until I discovered yum. Since I tested it, I found it very interesting and I use it.

yum (Yellow dog Updater, Modified) is more than an updater. It is also a package installer/remover for rpm systems. It automatically computes dependencies and figures out what things should occur to install packages. It makes it easier to maintain groups of machines without having to manually update each one using rpm. It is very simple to configure and use. It works in a similar way like apt-get for Debian.

yum uses a repository of rpms on a server, gets rpms headers in the local yum cache directory, and determines what needs to be installed/upgraded/removed. yum is fast because it manipulates small amounts of data (headers) and reliable because it relies on the rpm program itself (dependency calculations ...). yum is written in python.

The default installation of yum let's a machine get headers from a remote repository and store them in the /var/cache/yum/ directory. These headers will be compared to the headers of the rpms installed on the machine. yum will then download update rpms if any, check dependencies and install them.

In our case, since we want to update many linux boxes, we choose to create our own repository on the deployment server and make it available to the linux boxes. This repository contains the headers generated from the update rpms downloaded by mirror in the updates/ directory, and a link to the rpms themselves. All the job will be done on the server and we prevent linux boxes from maintaining a local cache directory to save time and disk space. The linux boxes will just daily compare the headers of the installed rpms against those provided in the repository and update if necessary.

Download yum rpm in /tmp/ and then install it:

     bash# rpm -Uvh /tmp/yum-1.0-1_80.noarch.rpm
    

To be sure that yum is activated, run the following commands:

     bash# chkconfig --add yum
     bash# service yum start
    

Integrate the yum rpm in the updated installation tree to be installed on the linux boxes:

     bash# cp /tmp/yum-1.0-1_80.noarch.rpm updates/extra/noarch
     bash# chmod 644 updates/extra/noarch/yum-1.0-1_80.noarch.rpm
     bash# ./extra/scripts/update_release.py
    

Create the yum repository with two directories named base/ and updates/. They will contain the headers and a link to the rpms respectively for the official installation tree and the updated installation tree:

     bash# mkdir -p yum_repository/base
     bash# yum-arch official_release/RedHat/RPMS/
     bash# mv official_release/RedHat/RPMS/headers/ yum_repository/base/
     bash# mv yum_repository/base/headers/header.info yum_repository/base/
     bash# (cd yum_repository/base/ ; ln -s ../../official_release/RedHat/RPMS/ packages)
     bash# mkdir -p yum_repository/updates
     bash# yum-arch updates
     bash# mv updates/headers/ yum_repository/updates/
     bash# mv yum_repository/updates/headers/header.info yum_repository/updates/
     bash# (cd yum_repository/updates/ ; ln -s ../../updated_release/RedHat/RPMS/ packages)
    

As the updated installation tree may change every day then we must update the repository accordingly. The script /etc/cron.daily/update_release.cron will become:

    
#! /bin/sh

usage() {
        echo "usage: $0 [-M] [-U] [-Y] [release ...]"
        echo "-M:	don't run mirror"
        echo "-U:	don't update the install tree"
        echo "-Y:	don't update the yum repository"
        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=
Yoption=
while getopts MUY option; do
case $option in
	M)	Moption=M
		;;
	U)	Uoption=U
		;;
	Y)	Yoption=Y
		;;
	*)	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

	# Update the yum repository
	if [ "${Yoption}" != "Y" ]; then
		if [ "${Moption}" = "M" -o \( "${Moption}" != "M" -a "`grep '^Got ' /tmp/mirror.txt 2> /dev/null`" != "" \) ]; then
			rm -rf ${BASE_DIR}/${release}/yum_repository/updates/header*
			yum-arch -q ${BASE_DIR}/${release}/updates
			mv ${BASE_DIR}/${release}/updates/headers/ ${BASE_DIR}/${release}/yum_repository/updates/
			mv ${BASE_DIR}/${release}/yum_repository/updates/headers/header.info ${BASE_DIR}/${release}/yum_repository/updates/
		fi
	fi
done
    
    

To allow the deployment server itself to be updated just point the cache directory to the repository:

     bash# rm -rf /var/cache/yum
     bash# ln -s `pwd`/yum_repository/ /var/cache/yum
    

and prevent the cache from any modification by adding the -C flag to the yum commands in the file /etc/cron.daily/yum.cron:

    
#!/bin/sh

if [ -f /var/lock/subsys/yum ]; then
        /usr/bin/yum -C -R 10 -e 0 -d 0 -y update yum
        /usr/bin/yum -C -R 120 -e 0 -d 0 -y update
fi
    
    

Note

With the commands above, yum will update only the installed rpms and doesn't add new ones except in two situations:

  • an rpm update depends on a new rpm which was not installed before.

  • the rpm updates are kernel updates. In this case, the new kernels are added and the old ones are never updated and remain on the machine.

Since yum automatically installs new kernels and modifies the boot loader accordingly so that the new kernel is in the default entry, add the following lines just after the yum commands in the file /etc/cron.daily/yum.cron to reboot whenever a new kernel is installed:

    
# Compare the current kernel version with the version in the default
# entry in grub.conf and reboot in case of a new kernel
entry=`cat /boot/grub/grub.conf | grep '^default' | cut -d '=' -f2`
entry=`expr $entry + 1`
if [ "`cat /boot/grub/grub.conf | grep '^title' | tail +$entry | head -1 | sed -e 's/.*(\(.*\)).*/\1/'`" != "`uname -r`" ]; then
        sleep 10 ; reboot
fi
    
    

To allow the linux boxes to be updated daily after they have been installed, follow the steps below:

Add yum in the section %packages in the kickstart files so that yum will be added during the installation process:

    
...
%packages --resolvedeps
@ GNOME Desktop Environment
mozilla
ntp
yum
...
    
    

Point the cache directory of the linux boxes to the repository on the deployment server and prevent the linux boxes from attempting to modify the remote repository by adding the -C flag to the yum commands in the file /etc/cron.daily/yum.cron. Simply add the following lines in the %post section in the kickstart files just before unmounting /mnt/tmp:

    
...
echo "+++ configuring the machine to use yum"
chkconfig --add yum
echo "/import /etc/auto.import --timeout=60" >> /etc/auto.master
echo "redhat         -fstype=nfs,soft,intr   192.168.1.254:/export/install/redhat" >> /etc/auto.import
rm -rf /var/cache/yum
ln -s /import/redhat/8.0/yum_repository/ /var/cache/yum
ln -s /import/redhat/8.0/official_release/ /var/cache/official_release
ln -s /import/redhat/8.0/updated_release/ /var/cache/updated_release
rm /etc/cron.daily/yum.cron
cp -p /mnt/tmp/8.0/extra/scripts/yum.cron /etc/cron.daily/
...
    
    

Don't forget to run this command on the deployment server:

     bash# cp /etc/cron.daily/yum.cron extra/scripts/
    

Note

If you have installed non RedHat (own or third party) rpms, whenever you got updates, just place them (manually if you can't automate this procedure) according to the architecture under the updates/extra/ directory and drop the old ones, then update the installation tree and the yum repository. If you leave some old rpms under the updates/extra/ directory for which you have recent updates, they will be simply ignored.

As an example, consider the yum rpm itself which is a third party rpm. Since we have installed yum-1.0-1_80.noarch.rpm, a new version yum-1.0.1-1_80.noarch.rpm was released. We have to download it in the updates/extra/noarch/ directory and run the following commands:

     bash# rm -rf updates/extra/noarch/yum-1.0-1_80.noarch.rpm
     bash# ../update_release.cron -M 8.0
    

Now we have an enhanced environment in which installation and updating are done automatically but what should we do in those situations that every administrator may encounter on all or specific running hosts? Here's some examples among many others:

  • add one or more packages

  • remove one or more packages

  • add/remove/modify files or directories

  • execute a command or a script

  • configure hardware resources

  • manipulate processes or services

  • ...

There's more than one way to accomplish such common administration tasks. The worst case is to sit down in front of each running host and do the dirty job. In general, every administrator has his own tricks to automate any action that must be taken but usually the intentions are similar or the same for all. In our case, to meet our needs we use cfengine.

3.2. Managing the linux boxes

cfengine is a set of components for setting up and maintaining computer systems (not specifically linux systems). It aims to create a single central system configuration which will define how every host on the network should be configured. cfengine's philosophy is to specify what we would like to do rather than how to do the job and that's why we choose it.

cfengine incorporates a declarative language in which a single statement can result in a lot of operations being performed on multiple hosts with different architectures. Here's how it works:

Periodically on every host, a wrapper called cfexecd which can run as a daemon or can be started with cron will locally run an interpreter called cfagent. cfagent looks for a configuration file named cfagent.conf which is stored locally and parses it. The configuration of the local host is then checked against this file and actions are taken if necessary to bring back the host in the desired state. Thus, cfagent.conf contains statements which tell cfagent what to do. We don't need to mention every host specifically by name in order to configure it, instead, we can refer to the properties which are common to the hosts such as architecture, os type, geographical location and so forth. These properties allow us to define what cfengine calls classes or groups. Some classes are predefined in cfengine and can be used directly (see the cfengine documentation).

In practice, the file cfagent.conf is changed centrally on a master server and then it is copied on every host using one of the two methods below:

  • The pull method: every time cfagent is run on a host, it executes the statements in a file named update.conf before parsing the file cfagent.conf. update.conf can contain any statements you want but it is mainly used to copy (pull) the configuration files from the master server and hence obtain the last modified cfagent.conf. The master server must run a component called cfservd to act as a file server.

    It is recommended to use update.conf only for this purpose to avoid changing it frequently. In the case where a buggy (syntaxically speaking) cfagent.conf is copied by all the hosts (this can happen as we change this file to meet our needs), then according to update.conf cfagent is able to get a correct cfagent.conf the next time it executes.

  • The push method: running cfagent regularly by cron ensures that cfagent will be run even if you are unable to log onto a host to run it yourself. Sometimes however you will want to run cfagent immediately in order to take an action as quickly as possible. It is not convenient to wait for the next cron nor to log onto every host to do the job. cfengine has a component called cfrun which contacts each host specified on the command line and in a file named cfrun.hosts and tells it to run cfagent and sending back the output on the screen (somehow like the rsh command). This way, we distribute (push) the configuration files to the hosts of our choice. For security reasons, cfrun can only tell remote hosts to run cfagent and never tell them what to do. Every host must run cfservd to act as a remote activation service.

Let's go through the different steps to make cfengine working in our environment. Start by downloading cfengine rpm in /tmp/ on the deployment server and then install it:

     bash# rpm -Uvh /tmp/cfengine-2.0.6-0.fdr.2.rh80.i386.rpm
    

this will also automatically run cfkey (another component of cfengine) to make a key pair (localhost.pub and localhost.priv) for cfengine in the /var/cfengine/ppkeys/ directory. These keys are necessary to trust the machines to each other and must be generated on each host.

Create the file /var/cfengine/inputs/cfservd.conf needed by cfservd:

    
# config file for cfservd

groups:

	# Define classes based on some criteria
	redhat			= ( FileExists(/etc/redhat-release) )

control:

	# Our domain name
	domain 			= ( domain.net )

	# Allow only our hosts to establish a connection on this host
	AllowConnectionsFrom	= ( 192.168.1.0/24 )

	# Allow only some users to establish a connection on this host
	AllowUsers		= ( root )

	# Allow only our hosts to talk with cfservd on this host
	TrustKeysFrom		= ( 192.168.1.0/24 )

	# Keep track of all connections to this host
	LogAllConnections	= ( true )

	# The command to be invoked remotely by cfrun
	redhat::
		cfrunCommand	 	= ( "/usr/sbin/cfagent" )

	# Minimum time between two successive actions (default is 1 mn)
	IfElapsed 		= ( 1 )

	# Maximum time before killing a running cfagent (default is 2 h)
	ExpireAfter 		= ( 120 )

	# Maximum number of forked daemons (default is 10)
	MaxConnections		= ( 50 )

grant:

	# Grant access to the directory containing config files
	/var/cfengine/inputs	*.domain.net

	# This line is necessary in order for cfagent to run remotely
	# via cfrun
	redhat::
		/usr/sbin/cfagent	*.domain.net
    
    

and start the cfservd service:

     bash# chkconfig --add cfservd
     bash# service cfservd start
    

Create the file /var/cfengine/inputs/update.conf:

    
# We use update.conf only to get the latest 
# configuration files from a central location

control:

	# What are the actions to be executed
	actionsequence		= ( copy )

	# Our domain name
	domain			= ( domain.net )

	# Lighten the load on the server
	#SplayTime		= ( 10 )

	# Hostname of the central server and
	# the location of the config files on it
	master_host		= ( deploysrv.domain.net )
	master_inputs_dir	= ( /var/cfengine/inputs )

	# where to put the config files on each host
	local_inputs_dir	= ( /var/cfengine/inputs )

	# Where to put disabled and backed up files
	Repository		= ( /var/cfengine/repository )

copy:

	# Copy on all the hosts except the master host
	# which is the source of the copy 
	any.!deploysrv::
		$(master_inputs_dir)	dest=$(local_inputs_dir)
					recurse=inf
					mode=700
					type=binary
					server=$(master_host)
    
    

Now we must do some changes on the deployment server to include cfengine in the installation tree.

Put the rpm in the updates/ directory, and regenerate hdlists and yum headers:

     bash# cp /tmp/cfengine-2.0.6-0.fdr.2.rh80.i386.rpm updates/extra/i386/
     bash# chmod 644 updates/extra/i386/cfengine-2.0.6-0.fdr.2.rh80.i386.rpm
     bash# /etc/cron.daily/update_release.cron -M 8.0
    

Create the directory extra/config/cfengine/ where we put only one configuration file: update.conf. This file will be placed in the /var/cfengine/inputs/ directory on every linux box during the installation process. When the linux box is started for the first time, we run cfagent against the file update.conf as a starting point to get all the other configuration files including a possibly modified update.conf file itself. Before copying anything from the server, the linux box should trust the master server by accepting its public key. Rather than adding the statement "trustkey = true" in the file update.conf, we choose to copy the public key of the server on the linux box during the installation process directly. This ensures that the linux box will talk to the right server later (requirement 4). Run the following commands:

     bash# mkdir extra/config/cfengine
     bash# cd extra/config/cfengine
     bash# cp /var/cfengine/ppkeys/localhost.pub root-192.168.1.254.pub
     bash# chmod 644 root-192.168.1.254.pub
     bash# cp /var/cfengine/inputs/update.conf .
     bash# cd ../../..
    

Add cfengine in the section %packages in the kickstart files so that cfengine will be added during the installation process:

    
...
%packages --resolvedeps
@ GNOME Desktop Environment
mozilla
ntp
yum
cfengine
...
    
    

Add the following lines just after the lines that save ssh keys in the %pre section in the kickstart files if you want to keep the key pair of a linux box when you decide to reinstall it. This is very important otherwise if you reinstall a linux box, cfengine will be reinstalled and a new key pair will be generated. Every communication with the master server will fail because the master server has an old copy of the public key of the linux box. Another solution consists of deleting it manually on the server but we may forget to do it:

    
...
###############
# try to keep cfengine keys (if any) among reinstalls. This is done by
# saving the contents of /var/cfengine/ppkeys dir

# locate the partition where /var/cfengine/ppkeys dir resides. Here's different possibilities:
# - a separate partition containing only /var/cfengine/ppkeys
# - a /var/cfengine partition containing ppkeys dir
# - a /var partition containing cfengine/ppkeys dir
# - a / partition containing var/cfengine/ppkeys dir

if [ -s /tmp/devs.$$ ]; then
        ppkeys_dir='' ; ppkeys_dev=`grep '/var/cfengine/ppkeys$' /tmp/devs.$$`
        [ "$ppkeys_dev" = "" ] && {
                ppkeys_dir='ppkeys' ; ppkeys_dev=`grep '/var/cfengine$' /tmp/devs.$$`
        }
        [ "$ppkeys_dev" = "" ] && {
                ppkeys_dir='cfengine/ppkeys' ; ppkeys_dev=`grep '/var$' /tmp/devs.$$`
        }
        [ "$ppkeys_dev" = "" ] &&  {
                ppkeys_dir='var/cfengine/ppkeys' ; ppkeys_dev=`grep '/$' /tmp/devs.$$`
        }
        set $ppkeys_dev
        echo "+++ /var/cfengine/ppkeys *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/$ppkeys_dir ]; then
                echo "+++ saving ppkeys files in /tmp/ppkeys ..."
                mkdir /tmp/ppkeys
                cp -a /mnt/tmp/$ppkeys_dir/localhost* /tmp/ppkeys
        else
                echo "+++ can't find any ppkeys dir"
        fi
        echo "+++ unmounting /mnt/tmp ..."
        umount /mnt/tmp
else
        echo "+++ can't find any partition on this system"
fi
...
    
    

In the %post section in the kickstart files, add the following lines just before the chroot command to restore the cfengine key pair if it was saved previously:

    
...
# put back cfengine keys
[ -d /tmp/ppkeys ] && cp -a /tmp/ppkeys/* /mnt/sysimage/var/cfengine/ppkeys
...
    
    

Add the following lines just before unmounting /mnt/tmp to copy the public key of the server and the file update.conf, make cfagent running hourly via the cfexecd wrapper, and setup the file /etc/rc.local to run cfengine in order to get the other cfengine files and perform the needed tasks:

    
...
echo "+++ configuring the machine to use cfengine"
cp -p /mnt/tmp/8.0/extra/config/cfengine/root-192.168.1.254.pub /var/cfengine/ppkeys/
cp -p /mnt/tmp/8.0/extra/config/cfengine/update.conf /var/cfengine/inputs/
cp -p /mnt/tmp/8.0/extra/scripts/cfengine.cron /etc/cron.hourly
echo 'echo "Executing cfengine"' >> /etc/rc.local
echo "(/usr/sbin/cfagent -f /var/cfengine/inputs/update.conf ; /bin/sleep 2m ; /usr/sbin/cfexecd -F) &" >> /etc/rc.local
...
    
    

Create the file extra/scripts/cfengine.cron and make it executable:

    
#! /bin/sh

# This command will execute cfagent which is part of cfengine
/usr/sbin/cfexecd -F
    
    

     bash# chmod 755 extra/scripts/cfengine.cron
    

Now we must create the other cfengine files on the deployment server to be copied by each host. The most important file is /var/cfengine/inputs/cfagent.conf. Here's an example:

    
# This is the cfagent.conf file which instructs
# each host running cfengine on the tasks to be
# accomplished

groups:

	# Define classes based on some criteria
	redhat			= ( FileExists(/etc/redhat-release) )

control:

	# What are the actions to be executed
	actionsequence		= (
				    processes
				  )

	# Our domain name
	domain			= ( domain.net )

	# Lighten the load on the server
	#SplayTime		= ( 10 )

	# The mail server and the email address used 
	# by cfengine to send email
	smtpserver		= ( localhost )
	sysadm			= ( root@localhost )

	# Hostname of the central server and
	# the location of the config files on it
	master_host		= ( deploysrv.domain.net )
	master_inputs_dir	= ( /var/cfengine/inputs )

	# where to put the config files on each host
	local_inputs_dir	= ( /var/cfengine/inputs )

processes:

	redhat::
		"cfservd" restart "/sbin/chkconfig --add cfservd ; /sbin/service cfservd start"
    
    

This file is used to make cfservd up and running on each host where it doesn't actually run. Each host will parse all the file and then executes all the actions in the order specified by the actionsequence keyword. Here we want to execute the contents of the action processes:. cfengine will look for a process named cfservd in the list of running processes (this is the result of the ps command). If no such process is running, cfengine will start it using the /sbin/chkconfig --add cfservd ; /sbin/service cfservd start commands only on the machines in the class redhat. For more information, see the cfengine documentation.

At this point, the deployment server is ready to be used to automatically install the linux boxes so that they will be able to update themselves with yum and achieve any post deployment tasks you need with cfengine. The next sections present some situations where cfengine is very helpful to manage remotely from a central server (requirement 3) the hosts that are already up and running. We can distinguish three groups of tasks which may affect all or only subsets of the running hosts: recurring tasks which must be done all the time or only for a period of time, occasional tasks that must be run immediately, and occasional tasks which don't need to be run immediately.

Note

It is important that the hosts have time synchronized otherwise cfengine will not work correctly.

3.2.1. Updating /etc/hosts

If you have no other way than using /etc/hosts to resolve your internal hostnames, then you have to update this file regularly to reflect the machines on your network especially if you add new entries. For this purpose, we add a copy section to the cfagent file and put a file named hosts in /var/cfengine/inputs/. From now, every change to this file will be propagated to all the machines.

    
...
control:
	...
	actionsequence		= ( 
				    processes
				    copy
				  )
	...
...
copy:
        any::
                $(master_inputs_dir)/hosts      dest=/etc/hosts
                                                owner=root
                                                group=root
                                                mode=644
                                                type=binary
                                                server=$(master_host)
...
    
    

3.2.2. Cleaning filesystems

To clean filesystems from unwanted files, we can use the tidy keyword in cfengine. Suppose that we want to delete all files that have not been accessed since a week in the /tmp/ directory on every host, and core files from the users' home directories. If the home server is named homesrv and the users' home directories are under /home/ then the tidy section can be added to the file cfagent.conf as shown below:

    
...
control:
	...
	actionsequence		= (
				    processes
				    copy
				    tidy
				  )
	...
	mountpattern		= ( / )
	homepattern		= ( home )
...
tidy:

	any::
		/tmp pattern=* age=7 recurse=inf
	homesrv::
		home pattern=core age=0 recurse=inf
...
    
    

3.2.3. Fixing /root/anaconda-ks.cfg permissions

With all the installation methods, the installer ends with creating a file named anaconda-ks.cfg in the /root/ directory containing all the information used during the installation process including the encrypted passwd for user root. The file is world readable by default which is not a good idea. To fix this, we use the files section:

    
...
control:
	...
	actionsequence		= (
				    processes
				    copy
				    tidy
				    files
				  )
	...
...
files:

        redhat::
                /root/anaconda-ks.cfg mode=0600 action=fixall
...
    
    

3.2.4. Protecting the single-user mode

The single-user mode is generally used for system maintenance. On RedHat systems, you can boot as root into single-user mode without supplying any password:

  • If the installed boot loader (LILO or grub) is not protected with a password, then you can give some options to the kernel which take you directly into single-user mode.

  • If the machine is configured to boot from a floppy or CDROM, then one can use a bootable floppy or CDROM to boot into single-user mode and bypass the installed boot loader even if it is protected with a password.

To protect the single-user mode with a password, you can add a special line in the file /etc/inittab and restart the process control initialization. Note that this hack will only prevent from booting into single-user mode without supplying a password and doesn't prevent from using special disks such as rescue disks.

We use the editfiles section of cfengine to edit the file /etc/inittab and the processes section to restart the init process only if the file /etc/inittab was modified. For this, we define a run-time class named inittab when editing the file /etc/inittab to trigger the restart of the init process. Also, we define pass1 and pass2 run-time classes to control the flow of execution of some parts of the processes section and we use the AddInstallable directive to help cfengine determine classes which may be declared at run-time:

    
...
control:

        # Classes which may be defined at run time
        AddInstallable          = ( pass1 pass2 inittab )
	...
	actionsequence		= (
				    processes.pass1
				    copy
				    tidy
				    files
				    editfiles
				    processes.pass2
				  )
	...
...
editfiles:

        redhat::
                { /etc/inittab
                LocateLineMatching "id:.:initdefault:"
                IncrementPointer "1"
                BeginGroupIfNoMatch "~~:S:wait:/sbin/sulogin"
                        IncrementPointer "-1"
                        InsertLine "~~:S:wait:/sbin/sulogin"
                        DefineInGroup "inittab"
                EndGroup
                }
...
processes:

        pass1.redhat::
                "cfservd" restart "/sbin/chkconfig --add cfservd ; /sbin/service cfservd start"
        pass2.redhat.inittab::
                "init" signal=hup
...
    
    

3.2.5. Adding packages

Suppose that our users complain because they receive some .doc and .xls documents by mail and they are not able to read them. They also have problems when they access some web sites with mozilla because the required plugins are not installed, and some users want to use Acrobat Reader instead of xpdf. Finally, suppose that the host linbox1 contains a CD burner and we have not installed the appropriate software to use it.

To satisfy these requests we will use cfengine to tell only linbox1 to install cdrecord and all the linux boxes to install openoffice, Acrobat Reader, and some mozilla plugins (Java plugin, Acrobat Reader plugin, and Macromedia Flash plugin). Among these packages, only openoffice and cdrecord are part of the RedHat distribution and the others are third party packages.

3.2.5.1. Adding redhat packages

Usually, you use the rpm -Uvh or rpm -i command to install a package. If you try to install a package using the rpm command you may be facing dependencies problems and you have to "manually" add all the packages on which the initial package depends in order to have things working as expected. This can become very tedious in some cases.

Instead of using the rpm command we use yum as a package installer. With yum, whenever you want to install a package included in the RedHat distribution, you just have to mention the name of the package you want to install and yum will do the whole job including dependencies resolution.

We use the shellcommands section in cfengine to add openoffice and cdrecord using yum as mentioned below:

    
...
control:

	...
	actionsequence		= (
				    processes.pass1
				    copy
				    tidy
				    files
				    editfiles
				    processes.pass2
				    shellcommands
				  )
	...
...
shellcommands:

	redhat::
		"/usr/bin/yum -C -y -e 1 install openoffice"
	linbox1::
		"/usr/bin/yum -C -y -e 1 install cdrecord"
...
    
    
3.2.5.2. Adding own or third party packages

With third party packages, we have to do some preparation tasks:

  1. Download the rpms. If no rpms exist, then you have to build them ;-) For more information on building rpms, see Maximum RPM.

  2. If these rpms depend on other rpms which are not included in the RedHat distribution then you must also get them.

  3. Perform the installation of the packages on a running test machine to see if things will work correctly on the hosts that are up and running. You may find that some packages need user interaction during installation which is not good for automation. This is the case for Macromedia flash and Real Player plugins for example.

  4. Put the rpms in the updates dir, merge them in the installation tree, include their names in a kickstart file, and perform a new installation on a test machine to see if things will work correctly during future (re)installations. After the first reboot, verify the logging information from the installer program in the file /root/install.log. Some rpms are not build correctly and may install with errors if they are used this way. This is the case for java for example.

3.2.5.2.1. Acrobat Reader

The latest version of Acrobat Reader can be found in ftp://download.adobe.com/pub/adobe/acrobatreader/unix/ but the rpms are not there. Try to find them on google or use the rpms provided by RedHat in the contrib area.

3.2.5.2.2. Java plugin

The latest version of Java plugin can be found in http://java.sun.com/plugin/.

3.2.5.2.3. Macromedia Flash plugin

The latest version of flash plugin can be found in http://macromedia.mplug.org/.

3.2.5.2.4. RealPlayer plugin

The latest version of RealPlayer plugin can be found in http://forms.real.com/real/player/unix/unix.html.

If third party rpms install well on a running machine and when performing a new installation, then you can merge them in the installation tree ...

For packages which install ...

3.2.6. Removing packages

Since new kernels are always added by yum, we can be in a situation where many kernels are installed on the boxes. After experiencing the new kernels enough, it is safe to delete old ones to save disk space and to avoid to accidentally use them especially if they contain security holes.

We directly use the rpm command instead of yum because the actual version of yum is not able to remove a specified kernel. This is because a kernel removal is particular in the sense that it is done by giving the version of the kernel to be removed. Also, we define some other classes to distinguish between RedHat releases because usually a kernel is tied up to a specific release:

    
...
groups:
	...
	redhat_80		= ( `/bin/grep ' 8.0 ' /etc/redhat-release` )
	redhat_9		= ( `/bin/grep ' 9 ' /etc/redhat-release` )

control:
	...
	actionsequence		= ( module:cfservd processes tidy shellcommands )
	...

...
shellcommands:
	Hr01.redhat_80::
		"/bin/rpm -q kernel-2.4.20-13.8 > /dev/null && { /bin/rpm -e kernel-2.4.20-13.8 ; /sbin/reboot ; }"

	Hr01.redhat_9::
		"/bin/rpm -q kernel-2.4.20-13.9 > /dev/null && { /bin/rpm -e kernel-2.4.20-13.9 ; /sbin/reboot ; }"
...
    
    

3.2.7. aa aaa

    
    
    
     bash#