Chapter 4. Automated Ubuntu Installs

After you have gone through the Ubuntu Server install a few times, you start to realize that you generally pick the same options for a majority of the install no matter what type of server it is. It might be OK to manually enter install options when you have a single server to set up, but what if you have ten or a hundred? At some point you start to wish you could hire an intern or build a robot to press Next for you. While this might work, Ubuntu has provided a cheaper (free) option with two automated installation methods: preseeding and Kickstart. Preseeding is derived from the Debian Linux distribution, and Kickstart has been ported from Red Hat and is often referred to as Kickseed under Ubuntu. Both have their advantages and shortcomings, and in fact the recommended method of automating Ubuntu server installs is a combination of both.

In this chapter I cover both automation methods and how to use them to supplement a CD-ROM install. Finally, I show how to use Kickstart and preseeding together for a fully automated installer over the network. By the end you will be able to set up one or a hundred Ubuntu servers with about the same amount of effort. Then your robot can focus on more important tasks like bringing you coffee.

There are two different ways you can read this chapter. I discuss preseeding first because it is the classic way to automate Debian and Ubuntu installs, and even if you Kickstart servers, you often need to supplement it with preseed values. So if you are interested in how the entire process works and fits together, you should read the chapter straight through. You’ll gain a good foundation on preseeding so that when you learn about Kickstart, you can truly see how it eases the process. On the other hand, if you just want to get started with an automated installer, I recommend skipping ahead to the Kickstart section in the chapter. There is a lot of useful information in the preseed section, but preseeding is a pretty vast and complex topic, especially if you are new to it. If you just want to get things working on a basic automated install, you will want to use Kickstart. The Kickstart section will get you up and running, and then if you need to do anything that isn’t yet possible in Kickstart, you can return to the preseeding section.

Preseeding

The concept behind preseeding is pretty simple. Every possible option in the Ubuntu install is represented by a variable. Once you discover what those variables are, you can set them ahead of time in a file and instruct the Ubuntu installer to load and apply them for you. The key to preseeding, of course, is to know what those values are. There are a number of good online guides for preseeding, but I’ve noticed that most of them, even those for Ubuntu, still seem to provide Debian-based examples.

The main way to discover all of the available preseeding options is to use debconf-get-selections (included in the debconf-utils package). For instance, to find out all of the current preseed settings from your current install, you would run

$ debconf-get-selections --installer > alloptions.cfg

This would dump all of the installation-focused preseeding options into alloptions.cfg. The great thing about preseeding, though, is that it isn’t limited to installer options—every package you install that asks you questions can have its answers preseeded. To dump the entire debconf database containing these values into the same alloptions.cfg file, run

debconf-get-selections >> alloptions.cfg

Now you might be tempted to simply use this alloptions.cfg file as your preseed.cfg file. The problem is that this file contains all of the configuration options, including many that should not be preseeded. Use this file only as a guide when you want to discover a particular preseeding option that you want to set; instead I recommend that you start with the base preseed.cfg I will provide below and tweak that.

Basic Preseed Configuration for CD-ROM

There are a few different ways to introduce preseeding options to the installer, but probably the simplest way is to use the default Ubuntu install CD while specifying options directly at the boot prompt and putting a preseed.cfg file full of options on a local Web server. It turns out that if you are going to use preseeding by itself, there are certain options that you can’t set strictly in a preseed file—either they must go on the boot prompt, or you will have to answer the questions manually.

The first step is to set up a default preseed.cfg file. Below is a basic preseed file that describes a default Ubuntu server install for a system in the United States. Copy these settings into a file named preseed.cfg:

## Options to set on the command line
d-i debian-installer/locale string en_US
d-i console-setup/ask_detect boolean false
# the keyboard preseed has changed for 12.04
#d-i console-setup/layoutcode string us
d-i keyboard-configuration/layoutcode string us
d-i netcfg/get_hostname string unassigned-hostname
d-i netcfg/get_domain string unassigned-domain

d-i netcfg/choose_interface select auto
d-i netcfg/wireless_wep string

d-i base-installer/kernel/override-image    string linux-server
d-i clock-setup/utc-auto   boolean true
d-i clock-setup/utc boolean true
d-i time/zone string US/Pacific
d-i clock-setup/ntp boolean true

d-i mirror/country string US
d-i mirror/http/proxy string
d-i pkgsel/install-language-support boolean false
d-i pkgsel/update-policy select none
tasksel tasksel/first multiselect standard, ubuntu-server

d-i partman-auto/method string regular
d-i partman-auto/purge_lvm_from_device boolean true
d-i partman-lvm/confirm boolean true
d-i partman-auto/choose_recipe select atomic
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i passwd/user-fullname string Ubuntu User
d-i passwd/username string ubuntu
d-i passwd/user-password password insecure
d-i passwd/user-password-again password insecure
user-setup-udeb user-setup/encrypt-home boolean false

d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
d-i finish-install/reboot_in_progress note

Keep in mind that with these default settings the installer will find the first disk it can, format over it, and install the base system on top of it, so make sure before you try this on a server that you are willing to lose all of the data on its disks. Otherwise, if you do want to preserve data on a server, be sure to first check out the Partitioning section in this chapter to find out how to tweak the default settings.

Once you have set up the preseed.cfg file, put it on a local Web server that your test server can access. For this example let’s assume the server is at www.example.net and you put the file in the main document root so it can be found at www.example.net/preseed.cfg. Now boot the server on which you wish to install Ubuntu off of the Ubuntu Server install CD. Once you answer the language prompt, hit F6 and then Esc so you can edit the default boot arguments, as shown in Figure 4-1. Use the arrow keys to move to the left past the initrd= argument and backspace over the file=/cdrom/preseed/ubuntu-server.seed section of the prompt. That is actually Ubuntu’s own preseed file that it uses for the install, but we will replace it with our own. To do that we use the url option to point to our Web server, so type

url=http://www.example.net/preseed.cfg

Image

Figure 4-1 Ubuntu install boot screen with boot arguments

Of course, change that to point to the path to your actual Web server and preseed file. Unfortunately we will need to specify a few extra options on the command line so the installer can get past the initial phase of the install, get on the network, and retrieve the rest of its settings. If you look at the top of my example preseed.cfg file, you will see that I set apart a few options to go on the boot prompt:

d-i debian-installer/locale string en_US
d-i console-setup/ask_detect boolean false
# the keyboard preseed has changed for 12.04
#d-i console-setup/layoutcode string us
d-i keyboard-configuration/layoutcode string us
d-i netcfg/get_hostname string unassigned-hostname
d-i netcfg/get_domain string unassigned-domain

To add these to the boot prompt, just type the full path to a particular option (such as debian-installer/locale), an = sign, and then the option to set it to. After you are finished, your complete boot prompt, including all the options that were there before that you need to keep, will look like this:

url=http://www.example.net/preseed.cfg
  debian-installer/locale=en_US console-setup/ask_detect=false
  keyboard-configuration/layoutcode=us
  netcfg/get_hostname=unassigned-hostname
  netcfg/get_domain=unassigned-domain
  initrd=/install/initrd.gz quiet --

Now that’s quite a bit of typing, but Ubuntu provides some shorthand for some of the options. For instance, debian-installer/locale can be replaced with just locale, netcfg/get_hostname can be replaced with hostname, and netcfg/get_domain can be replaced with domain. With all of the shortcuts in place the command line looks a bit more manageable:

url=http://www.example.net/preseed.cfg locale=en_US
  console-setup/ask_detect=false keyboard-configuration/layoutcode=us
  hostname=unassigned-hostname domain=unassigned-domain vga=788
  initrd=/install/initrd.gz quiet --

If you have used preseed for previous Ubuntu releases, you will notice that console-setup/layoutcode has been changed to keyboard-configuration/layoutcode. Once you have typed all of these values into the boot prompt, press Enter and go get a cup of coffee. The installer should get its own IP address over Dynamic Host Control Protocol (DHCP), retrieve the preseed.cfg file from your Web server, complete the install, and then reboot. When you get back, you should be welcomed with a default Ubuntu login prompt. Of course, if everything didn’t go smoothly, you might see, for instance, an error retrieving the preseed.cfg file. If that is the case, try retrieving the same file from a Web browser on the same network and make sure your path is correct and your Web server is configured correctly.

Other things that might halt the installation process could be the installer not being able to get a DHCP lease or, as is most often the case, a mistake in the preseed.cfg file. As you will discover, a good preseed file is something you get after a lot of trial and error. When you set an option incorrectly, the installer will simply stop at that point in the install and prompt you for that particular option. As you create more sophisticated preseed.cfg files, you might run through the same install multiple times before you get it exactly right.

The default preseed.cfg will get you started, but where do you go from there? This chapter digs into each of the main configuration categories and explains how to customize the default preseed.cfg file for your needs.

Networking Options

In my sample preseed.cfg I introduced the following networking options:

d-i netcfg/get_hostname string unassigned-hostname
d-i netcfg/get_domain string unassigned-domain
d-i netcfg/choose_interface select auto
d-i netcfg/wireless_wep string

The first two options I used on the boot prompt with their aliases hostname and domain to set the hostname and domain, respectively, for this machine. The choose_interface option allows you to choose which network interface to use on the machine for the install. In my case I chose auto, which will pick the first interface that has link, if possible. Instead of choosing auto, you could also set this option to a specific interface, such as eth1. Now if eth1 were a wireless card and your wireless network uses Wireless Encryption Protocol (WEP), you could use the wireless_wep option to set the WEP key. Even though I didn’t use a wireless connection in my example, I still set this to a dummy value so I wouldn’t get prompted for a WEP key during the install.

With these default settings the installer will attempt to get all of its network settings via DHCP, which is probably what most administrators want, especially if they intend to install a machine over PXE. However, you might want to statically assign an IP for a machine, and in that case there are a number of preseed options you will need to use. The following is an example set of preseed options that will set up a network manually:

d-i netcfg/disable_dhcp boolean true
d-i netcfg/get_nameservers string 192.168.1.1
d-i netcfg/get_ipaddress string 192.168.1.50
d-i netcfg/get_netmask string 255.255.255.0
d-i netcfg/get_gateway string 192.168.1.1
d-i netcfg/confirm_static boolean true

Most of these options are pretty self-explanatory, so you can just replace the IPs listed here with the IPs you want to use for your name server, the IP of the host itself, gateway, and netmask. The disable_dhcp option is what tells the installer to skip DHCP and use the static settings, and confirm_ static simply confirms all of the static options you set; otherwise the installer would prompt you to confirm all of the settings in the middle of the install.


Note: Workaround for DHCP Timeouts

One downside to installation with DHCP is that on many installs the machine could potentially attempt to get a lease multiple times. When this happens, you will often notice that the actual physical port will also reset. Now, depending on how your networking equipment is configured, it could take longer than 30 seconds (the default DHCP time-out) for the port to come back up because the switch needs to perform spanning tree calculations. I’ve had installs that seemed to get a lease only one out of ten or more times because of a race for the port to come back up before DHCP timed out. A Red Hat install I had got a new lease three or four times, and it was basically impossible to win the race that many times in a row. If you notice this is the case, you have a few options. For one, you (or your network administrator) can enable PortFast on that particular switch port so that it speeds up the spanning tree calculations, or you can set the netcfg/dhcp_timeout preseed option to a longer value, such as

d-i netcfg/dhcp_timeout string 60,

to set the DHCP time-out to 60 seconds.


There might be some circumstances when you have a preseed file you want to use both on a network with DHCP and on a network without it. There is a way to configure that with preseeding. Combine all of the netcfg options you would use for a DHCP install with the static settings, except comment out or remove the disable_dhcp option. Finally, add the following options to the preseed:

d-i netcfg/dhcp_failed note
d-i netcfg/dhcp_options select Configure network manually

With these options in place, on networks where DHCP works, the install will use those settings. In case DHCP fails, the installer will then fall back to your static settings.

Partitioning

As you saw in the installation chapter (Chapter 1), there are many different ways to partition an Ubuntu server, and so it should be no surprise that there are also many preseeding options for partitioning. First, let’s break down the options I used in my default preseed.cfg:

d-i partman-auto/method string regular
d-i partman-auto/purge_lvm_from_device boolean true
d-i partman-lvm/confirm boolean true
d-i partman-auto/choose_recipe select atomic
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true

With these options, the installer will simply select the first available disk it can find and then overwrite it with one big / partition and a swap partition. For something so basic it certainly took quite a few options, so let’s go through these options one by one and discuss what part each plays.

Image partman-auto/method

This option defines what partitioning method to use for the automatic partitioning and can accept the options regular, lvm, or crypto. As the options suggest, the latter two set up partitions as logical volume manager (LVM) or encrypted volumes.

Image partman-auto/purge_lvm_from_device and partman-lvm/confirm

These options just save you from having to acknowledge warning prompts in the installer. The former option will purge any old LVM configuration from the disk if it exists, and the latter confirms writing any LVM partitions you may have set up.

Image partman-auto/choose_recipe

The Ubuntu partition program allows a few different predefined partitioning recipes from which the user can choose. The atomic recipe sets up everything on a single partition. If you set the option to home, it will separate out a /home partition as well, and if you set it to multi, you will get separate /home, /usr, /var, and /tmp partitions.

Image partman-partitioning/confirm_write_new_label, partman/choose_partition, partman/confirm, and partman/confirm_nooverwrite

These are more options that tell the installer to proceed with partitioning without extra user confirmation. If you did, however, want to require a user to confirm settings (possibly while debugging your preseed file), you could comment out one of these options.

Expert Partition Recipes

The predefined partition recipes that Ubuntu provides are probably adequate for basic servers, but most administrators I know have strong feelings about how their systems are partitioned. In these cases you will likely want to create your own custom partitioning scheme for your servers. In the preseeding world this is known as an expert_recipe. Custom partitioning recipes can get rather complex as there are a lot of options available. The complete documentation is available at http://d-i.alioth.debian.org/svn/debian-installer/installer/doc/devel/partman-auto-recipe.txt, but even that can be difficult to follow without some examples.

First let’s examine a basic partitioning recipe that is provided in a lot of the example preseed files in official documentation. The following recipe will set up a small /boot partition and a swap partition and use up the rest of the disk for /:

d-i partman-auto/expert_recipe string            \
boot-root ::                                     \
     40 50 100 ext3                              \
           $primary{ } $bootable{ }              \
           method{ format } format{ }            \
           use_filesystem{ } filesystem{ ext3 }  \
           mountpoint{ /boot }                   \
     .                                           \
     500 10000 1000000000 ext3                   \
           method{ format } format{ }            \
           use_filesystem{ } filesystem{ ext3 }  \
           mountpoint{ / }                       \
     .                                           \
     64 512 300% linux-swap                      \
           method{ swap } format{ }              \
     .

Note that this entire configuration is intended to appear on a single line, so to make it more readable you are allowed to use the \ symbol at the end of the line to extend your options to multiple lines, as with shell scripts.

The first line labels this recipe as boot-root, and all of the rest of the lines are considered part of the boot-root recipe. The way that the lines are formatted separates each of the three partitions from the others. First let’s take a closer look at the /boot partition:

40 50 100 ext3                                   \
      $primary{ } $bootable{ }                   \
      method{ format } format{ }                 \
      use_filesystem{ } filesystem{ ext3 }       \
      mountpoint{ /boot }                        \
      .

The first line has four different fields corresponding to the minimal size of the partition, the priority, the maximal size, and the parted-style file system. All disk sizes in this file are in megabytes, so here we would have a disk that is at least 40Mb but if available could grow up to 100Mb, and it is a standard Linux partition with an ext3 file system. If you compare this line with the corresponding line for the swap partition, you will notice that for its maximal field it uses 300%. When a percentage is used, the size is determined based on the amount of RAM on the system. What 300% means in this case is that the swap partition’s maximal size will be 300% of the amount of RAM on the machine.

The priority field deserves a little extra description. It also represents a partition size in megabytes and usually is set to some value between the minimal and maximal values. The installer uses the priority setting of each partition when it decides how much of the available space to give each partition. The higher the priority compared to that of other partitions, the more likely it is that a particular partition is going to get the space. In fact, for some small partitions you might even want to set the priority higher than the maximal value if you want to make sure it gets enough space (or you could just increase the minimum value). Ultimately this is another exercise in trial and error to make sure all of your partitions get enough space.

The next line, $primary{ } $bootable{ }, sets two different options for the partition. The first makes the partition the primary partition (if that is possible; it’s worth noting that the partitions will be created in the order in which they are listed in this file). The second option sets the bootable flag on this partition.

The third line tells the partition to be formatted. The method{ } setting takes three different arguments: format (formats the partition), swap (formats the partition as swap space), or keep (keep the current partition as it is and do not format it). The format{ } argument is also needed to ensure that this partition is formatted. If the file system you have chosen for a partition supports labels, you can also add the label{ } option here to assign it a label. So to label a partition boot you could add label{ boot }.

Whereas the third line defines that the partition should be formatted, the fourth line defines how it should be formatted. The use_filesystem{ } option tells the installer that this partition will have a partition on it, and the filesystem{ } option defines which file system to format it with.

The fifth line contains the mountpoint{ } option, which tells the partitioner where to mount this partition on the final installed system. The installer will take care of the /etc/fstab settings for you, but you can also add special mounting options for a partition if you wish. For instance, to mount a partition read-only, you would typically add ro to the list of mount options in /etc/fstab. In this case you would add a line containing options/ro{ ro }. If you wanted to add the noatime option as well (stops the logging of A time on a file system, which can boost its performance), you would add options/noatime{ noatime }.

Note that each partition uses a single period at the end of the last line to signify the end of its options and separate it from any other partitions you might define.

LVM

You can also configure LVM partitions in your preseed file with only a few changes. First, change the partman-auto/method option from regular to lvm and then add lvmok{ } to each partition you want to use LVM. However, you will get a warning in your install if you haven’t configured a non-LVM /boot partition. A simple way to solve this is to be sure that your expert recipe has a /boot partition configured without lvmok{ } in it. A simple example uses the expert recipe I listed above and adds an extra option lvmok{ } to the root and swap partitions. The final expert recipe will look like the following:

d-i partman-auto/expert_recipe string             \
    boot-root-lvm ::                              \
      40 50 100 ext3                              \
            $primary{ } $bootable{ }              \
            method{ format } format{ }            \
            use_filesystem{ } filesystem{ ext3 }  \
            mountpoint{ /boot }                   \
      .                                           \
      500 10000 1000000000 ext3                   \
            method{ format } format{ }            \
            use_filesystem{ } filesystem{ ext3 }  \
            mountpoint{ / }                       \
            lvmok{ }                              \
      .                                           \
      64 512 300% linux-swap                      \
            method{ swap } format{ }              \
            lvmok{ }                              \
      .

Finally, you can create your own custom LVM physical volume in your partition recipe. Here is an example physical volume that will consume the entire /dev/sda drive and set up a volume group named vg00. Note that the method{ } option is set to lvm in this case.

100 1000 1000000000 ext3       \
       $defaultignore{ }       \
       $primary{ }             \
       method{ lvm }           \
       device{ /dev/sda }      \
       vg_name{ vg00 } .

When you set up your own volume groups, you might also want to control which volume groups particular partitions are a part of. Use the in_vg{ } option to specify which volume group a particular partition is a member of and lv_name{ } to name the logical volume. For instance, if I wanted the swap partition I created above to be a part of vg00 explicitly and labeled testswap, I could change it to the following:

64 512 300% linux-swap                   \
      method{ swap } format{ }           \
      lvmok{ }                           \
      invg{ vg00 }                       \
      lv_name{ testswap }                \
.


Note: Partition Declaration Formatting

Since each line in these partition declarations is a continuation of the previous line, you can separate each option on its own line or put multiple options on the same line—it’s up to you. Just make sure to use the \ symbol at the end of each line and use a . at the end of each partition.


Packages and Mirrors

What differentiates one type of server from another is what services it runs, and on Ubuntu that ultimately comes down to which packages you decide to install on the system. While you could certainly use the installer just to set up a base install image and then go in and add packages yourself later, if your goal is complete automation you should look into the package settings built into the preseed process.

The basic package options in the preseed file are pretty straightforward and self-explanatory. Let’s start with the options I used in my default preseed file:

d-i mirror/country string US
d-i mirror/http/proxy string
d-i pkgsel/install-language-support boolean false
d-i pkgsel/update-policy select none
tasksel tasksel/first multiselect standard, ubuntu-server

The first option defines what Ubuntu mirror to use to retrieve packages and updates (the default mirror for the United States). I don’t use a proxy, but I also didn’t want to get prompted for it, so I left this option blank. I didn’t want to install additional language packages, so I set pkgsel/install-language-support to false. The pkgsel/update-policy option lets you define whether the system will apply updates automatically. We set this to none so any updates must be applied manually. Finally, you can see that you can also preseed values for tasksel; in my case I told it to select the standard packages along with the ubuntu-server task. I could have also added other tasks that are available via the manual tasksel process such as lamp-server if I had wanted. Sometimes the extra packages you want to install aren’t part of specific tasks. In that case you can use the pkgsel/include option to add a list of specific packages the installer will add for you. This example adds the openssh-server and build-essential packages:

d-i pkgsel/include string openssh-server build-essential

Custom Package Repositories

In addition to the standard settings you need to define, there are a number of tweaks you can make to the APT configuration within your preseed file. For instance, you can choose which repositories to include within APT. The values below include the restricted, universe, and backports repositories:

d-i apt-setup/restricted boolean true
d-i apt-setup/universe boolean true
d-i apt-setup/backports boolean true

You might also decide you want to specify from which Ubuntu mirror to retrieve packages, because either you have your own repository or you know of a repository that is faster for you. In either case, all you need to do is set the mirror/country option to manual and then specify which mirror to use. I left the mirror/http/proxy value blank, but if you use an HTTP proxy you would put its value there. The following is an example of how to specify a local repository:

d-i mirror/country string manual
d-i mirror/http/hostname string us.archive.ubuntu.com
d-i mirror/http/directory string /ubuntu
d-i mirror/http/proxy string

d-i apt-setup/local0/repository string \
      http://apt.example.net/ubuntu &releasename; main
d-i apt-setup/local0/comment string local server
d-i apt-setup/local0/source boolean true
d-i apt-setup/local0/key string http://apt.example.net/key

This example adds the local repository stored at apt.example.net and labels it local server. The apt-setup/local0/source line will also add the source code repository, and the apt-setup/local0/key points to the GPG key that your APT repository must have set up to sign packages. If you don’t have this key set up and available, the install will complain.

User Settings

One thing you will find in just about every Linux install program is a section to configure user accounts. As with every other part of the installer, this section can be preseeded. Here’s what I used in my default preseed.cfg file:

d-i passwd/user-fullname string Ubuntu User
d-i passwd/username string ubuntu
d-i passwd/user-password password insecure
d-i passwd/user-password-again password insecure
user-setup-udeb user-setup/encrypt-home boolean false

Each field is pretty self-explanatory, but note that I had to specify the password twice. The last option lets you define whether the home directory is encrypted. Since this is a server and we want the install to be automated, it’s simplest if we just set that option to false. Now if you don’t want to list the password in plain text, substitute the following in the user-password lines:

d-i passwd/user-password-crypted password [MD5 hash]

If you are sure how to figure out an MD5 hash for a particular password, change the password for a user on another system to the password you want to use and then see what MD5 hash is set for it in the /etc/shadow file.

Ubuntu will set up the default user with a default UID and group membership. You can also use preseeded values to override that:

d-i passwd/user-uid string 1010
d-i passwd/user-default-groups string audio cdrom video

One thing Ubuntu does to increase security is to disable the root account by default. Instead of a root account, users use the sudo program to gain superuser privileges. You can override this with preseeded values, of course. The following set of options will set up the root user along with the default user:

d-i passwd/root-login boolean true
d-i passwd/root-password password insecure
d-i passwd/root-password-again password insecure
# or encrypted using an MD5 hash.
#d-i passwd/root-password-crypted password [MD5 hash]

Finally, if you want, you can disable the default user account altogether and stick with only root, although I would discourage this:

d-i passwd/make-user boolean false

GRUB

For the most part you will probably want to stick with the default GRUB setup for your server, in which case the entries in my sample preseed.cfg would be all that you need:

d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
d-i finish-install/reboot_in_progress note

The first option will install GRUB to the MBR if no other operating system is found. The grub-installer/with_other_os option will additionally set up any other operating systems it finds on the host. You typically don’t have a dual-boot setup with a server, but this is probably safe to keep as is. The final option will preseed away the prompt to reboot the machine so that it automatically reboots to the newly installed OS.

In some situations you might not want GRUB to be installed on the MBR but instead at the beginning of a particular partition or to multiple disks (handy for software RAID). In these cases you can specify the boot device for GRUB to use:

d-i grub-installer/only_debian boolean false
d-i grub-installer/with_other_os boolean false
d-i grub-installer/bootdev  string (hd0,0)

The first two lines disable the default behavior of installing to the MBR, and the final line specifies which boot device GRUB should use. Note that it uses GRUB’s syntax for boot devices and not /dev entries. Also, if you want to install GRUB across multiple devices, just list them one after another, separated by spaces on the grub-installer/bootdev line.

If you want extra security for your server to protect against users tweaking GRUB to boot into single-user mode, for instance, you have the option of password-protecting GRUB. This option can also be automated with preseed:

d-i grub-installer/password password insecure
d-i grub-installer/password-again password insecure

As with the user password settings, you can either use a plain-text password as shown above, or use an MD5 hash as shown below.

d-i grub-installer/password-crypted password [MD5 hash]

Miscellaneous

Finally, there are a few extra options that aren’t critical but could prove useful, depending on your environment. For instance, by default Ubuntu will eject the CD after the install has finished. Well, if you are installing Ubuntu remotely and are thousands of miles away, that means you have just one shot at getting the install right before you have to get someone to reinsert the CD. Instead, you can set a preseed option to disable that:

d-i cdrom-detect/eject boolean false

Alternatively, you may not want to immediately reboot into the new system but instead halt it. This could be useful, for instance, if you wanted to build out a cluster of machines ahead of time but won’t be using them immediately. The following setting will tell the installer to halt instead of reboot:

d-i debian-installer/exit/halt boolean true

Dynamic Preseeding

There is incredible flexibility in preseeding your install process, but as you develop your preseed infrastructure, ultimately you will find that one preseed configuration file won’t work for all of the different servers you want to install. At that point you will probably develop a second, third, or fourth preseed.cfg file for those circumstances and then manually point to those files on the command line. That works, but it can be a pain to maintain over time. A better approach is to take advantage of the installer’s ability to dynamically load preseed settings at different points of the install. That way you can maintain a base preseed.cfg file that contains options that work for all of the installs, and then create custom preseed files that contain only what needs to be changed from the base config. Then you can load those files, and their changes will ultimately overwrite the settings in your main preseed.cfg.

Chain Loading Preseed Files

One way to manage all of your preseed settings is to separate the differences into multiple files. You could, for instance, put all of the partitioning information into a partition.cfg file, or if you have different network settings for each network, you might have the same base preseed.cfg file but different network.cfg files. Each of these networks might have its own preseed server that shares the preseed.cfg but has a custom network.cfg. Within the preseed.cfg file you would then add a line like the following:

d-i preseed/include string network.cfg

The installer would then retrieve the network.cfg file from the same location where it found the original preseed file. You can specify multiple files on this line; just separate them with spaces. You can also use relative directory paths if you wish. The installer will start from the directory where the main preseed file was retrieved and move from there. So if my preseed.cfg file was at http://example.com/preseed.cfg but my network file was at http://example.com/network/network.cfg, my include string would read:

d-i preseed/include string network/network.cfg

Using static include files such as this can help with organization, but often what you’ll find is that you want to include only certain preseed files for certain situations. In this case there is an include_command preseed option that allows you to write a short shell script that will output the filename to include. This allows you to choose custom preseed files based on the environment. This simplistic example just echoes a particle filename:

d-i preseed/include_command \
     string echo partition.cfg

This feature actually can open up a lot of options for an administrator. For instance, let’s say that you manage three data centers in New York, London, and Tokyo. As a good administrator, you have set up a unique subdomain for each of these data centers, so the New York, London, and Tokyo servers use ny.example.net, london.example.net, and tokyo.example.net respectively as their domains. More likely than not you would have custom preseed options you would want to set for each of the different data centers, so you could then put these custom options in ny.example.net.cfg, london.example.net.cfg, and tokyo.example.net.cfg on the same server as your generic preseed.cfg file. Then in your preseed.cfg file you could add:

d-i preseed/include_command \
     string echo `hostname -d`.cfg

The hostname -d command will output a host’s domain name, so if a host in New York’s fully qualified domain name is web1.ny.example.net, then hostname -d would output ny.example.net. Our preseed command would then echo ny.example.net.cfg.

Run Custom Commands During the Install

If you find yourself writing longer and longer one-liners into the include_command option, you will definitely want to look into some of the other preseed options that allow you to run entire scripts. There are three different preseed options that allow you to execute commands within the installer environment.

The first option I will mention is in a way an extension of the include options. Where those options allowed you to list an additional preseed file to download and read in, the preseed/run option will instead download and execute a script of your choice:

d-i preseed/run string command.sh

This command.sh script could perform all of the shell logic you might have used before in include_command, only now it’s much easier to organize. You can even read and set preseed values within your script. Just use the debconf-get and debconf-set commands to get and set different preseed settings for your install. For instance, if I wanted to retrieve the preseeded hostname, I could run

debconf-get netcfg/get_hostname

If I wanted to set that option to web1 from within my script, I would run

debconf-set netcfg/get_hostname web1

The next preseed option that allows you to run commands within your install is preseed/early_command. This option allows you to specify a command that will be run as early as possible in the install—basically as soon as the preseed file is read. This is similar to the preseed/run command, except that you don’t have to retrieve a script to execute—you can run shell commands directly from the install environment. If you want, though, you can make preseed/early_command behave just like preseed/run with the use of the preseed_fetch command that will retrieve a file from your preseed server for you. Its first argument is the file to retrieve, and the second argument tells the command where to place the file it has downloaded. In the following example I have re-created the functionality of my earlier preseed/run example.

d-i preseed/early_command string preseed_fetch command.sh \
/tmp/command.sh; sh /tmp/command.sh

Whereas the preseed/early_command is executed as soon as possible in the install process, the preseed/late_command is executed as late as possible. The early_command is useful to set dynamic values for the installer before it has started the bulk of the installation process, but the late_command is handy for running your own custom programs after you know the base image has been installed. At this phase in the installer, the server’s root partition is still mounted under the /target directory, so you could potentially chroot into that directory and run commands as though you were running them directly from the installed server.

As with the early_command, you can use preseed_fetch to retrieve scripts to run. Some of the most common things you might want to do have been automated for you with the in-target and apt-install commands. The in-target command automates the chroot process for you so that any command you list after in-target will be executed within the installed environment. The apt-install command will use APT to install any extra packages you might want to add. The following example will install the Mutt e-mail client on your server and then dump the current set of environment variables into a file in your /root directory:

d-i preseed/late_command string apt-install mutt; \
in-target set > /root/environment

While you can certainly list extra packages to install in other parts of your preseed.cfg file, the real power here is that with late_command you could download a script that dynamically pulls information from the environment and then decides which packages it wants to install based on that. Later in the chapter, when I discuss how to deploy servers with PXE booting using both Kickstart and preseeding, I will go into more detail on how to leverage dynamic scripting in your automated installs.

Kickstart

The second main method you can use to automate Ubuntu server installs is with Kickstart. Kickstart is a technology used originally by Red Hat that has been ported for use by Ubuntu. Like preseeding, Kickstart works via a configuration file with answers to the installer’s questions that you can grab over HTTP or FTP or from a local file. Kickstart is arguably easier to use, and since many administrators already have a Kickstart environment in place for their Red Hat machines, it makes sense to stick with Kickstart as your main automation tool for Ubuntu as well. Most of the Kickstart features, but not all, have been ported, but because of the ease of configuration and the benefit of a graphical configuration management tool, I advocate starting with Kickstart and supplementing it with a preseed file where necessary.

Basic Kickstart Configuration for CD-ROM

As in the preseeding section of this chapter, I will start with the simplest Kickstart scenario: a default Ubuntu server installed from the CD-ROM. Even though the Kickstart file’s options are pretty self-explanatory, the great news is that there is a graphical program available for Ubuntu that will help you generate a Kickstart file. Not only does this save you from looking up the syntax for every Kickstart option, it saves you time—something a sysadmin never has enough of. As someone who has created plenty of Kickstart configuration files by hand, I recommend using the GUI as much as you can to set up your base template and then tweaking the resulting configuration file by hand only if you need to.

The Kickstart GUI tool is not installed by default on a typical Ubuntu desktop system, so use your preferred package management tool to install the system-config-kickstart package. Once it is installed, you can find the program in Applications->System Tools->Kickstart. As you launch the program, you will see that it is broken up into a basic two-pane window, as shown in Figure 4-2. On the left side is the set of configuration categories you can change, and as you select one of them, the right pane changes to list the options you can configure. Go through each of the categories and make sure the default settings match what you want on your server. Also be sure to go to the Partition Information category and set up your partitions. Unlike with preseeding, you do have to give the Kickstart file some sort of guide on how to partition the system. Once you finish your settings in the GUI, click File->Save and save the file to ks.cfg.

Image

Figure 4-2 Kickstart Configurator program


Tip

A great thing about the GUI program is that it already knows the correct syntax to use for each option, so it makes a great reference for Ubuntu Kickstart options. Just start the program and set the options you want to look up, and then click File->Preview. The tool will then output a sample ks.cfg to the screen so you can see the syntax.


As I mentioned, I will need to tweak this file so that it will create a default Ubuntu server. Essentially it needs a few server-specific preseed options that the Ubuntu Server CD-ROM typically provides, along with the list of default tasks to install. To add those preseed options to the Kickstart file, I would just add lines to the main configuration section, starting the lines with the word preseed followed by the particular preseed option I want to set. All of the extra tasks and packages you need to install on a server are defined in a %packages section (the different sections of a Kickstart file are named with a % at the beginning). The two tasks we need for this server are standard and ubuntu-server. The following is the combined preseed options and %packages section I appended to the end of the ks.cfg file the GUI tool created:

preseed base-installer/kernel/override-image string linux-server
preseed pkgsel/language-pack-patterns string
preseed pkgsel/install-language-support boolean false
%packages
@ standard
@ ubuntu-server

Notice that the tasks begin with an @ sign, so if I wanted to select LAMP Server during the install I could add @ lamp-server to this list. If you want a complete list of available tasks, run tasksel --list-tasks. You can also add individual packages to this list; just put each package name on its own line without the @ in front of it. Since I just wanted a default Ubuntu install that mirrored my previous preseed.cfg file, I didn’t add any extra packages. Here is the resulting ks.cfg file:

#Generated by Kickstart Configurator
#platform=x86

#System language
lang en_US
#Language modules to install
langsupport en_US
#System keyboard
keyboard us
#System mouse
mouse
#System timezone
timezone America/Los_Angeles
#Root password
rootpw --disabled
#Initial user
user ubuntu --fullname "ubuntu" --password insecure
#Reboot after installation
reboot
#Use text mode install
text
#Install OS instead of upgrade
install
#Use CDROM installation media
cdrom
#System bootloader configuration
bootloader --location=mbr
#Clear the Master Boot Record
zerombr yes
#Partition clearing information
clearpart --all --initlabel
#Disk partitioning information
part / --fstype ext4 --size 1 --grow --asprimary
part swap --recommended
#System authorization information
auth  --useshadow  --enablemd5
#Firewall configuration
firewall --disabled
#Do not configure the X Window System
skipx
# This section was added by hand
preseed base-installer/kernel/override-image string linux-server
preseed pkgsel/language-pack-patterns string
preseed pkgsel/install-language-support boolean false
%packages
@ standard
@ ubuntu-server

As I did with the preseed file, I set up a single / partition with the --grow option so that it filled the entire drive (actually I just toggled that setting within the GUI). Then I copied this ks.cfg file to the same Web server I used before. One other great thing about using this Kickstart file is that I don’t have to manually enter a lot of settings at boot. In fact, all I need to do is replace the file preseed option that is already there with an option that points to my Kickstart file. Boot the CD-ROM and then press F6 to see the full list of boot options as before. This time, though, after you backspace over the file= option, use the ks= option to point to your Kickstart file. Your final boot prompt will look like this:

ks=http://example.net/ks.cfg vga=788 initrd=/install/initrd.gz quiet --

Of course, replace http://example.net/ks.cfg with the URL for your Kickstart file. As with preseeding, the install should complete without any interaction from you unless any typos or other mistakes were introduced to the Kickstart file.

Changes and Limitations in Ubuntu Kickstart

As I mentioned before, Kickstart in Ubuntu is a port of the original Red Hat system. Since Red Hat and Ubuntu are quite different from each other, not every feature of Red Hat’s Kickstart currently works the same way with Ubuntu. Also, some additional options needed to be added for Ubuntu. Next I will discuss some of the major differences you will find between the two Kickstart systems.

New Options

The Ubuntu kickstart adds a few new options compared to traditional kickstart files to allow settings that are specific to Ubuntu.

Image Preseed

I already mentioned the new preseed option in my example ks.cfg file. The basic syntax is

preseed [--owner pkgowner] package/question type value

Honestly, the main difference you will see here as opposed to the syntax in a standard preseed file is that each line starts with preseed instead of the typical d-i. I said “typical” because while most of the preseed options you set are installer options, some preseed options actually don’t start with d-i in a preseed file because they are owned by a different package. If you run into a setting like that, use the --owner option set to the argument that would have gone at the beginning of a preseed file. For instance, tasks that you select in a preseed file look like the following:

tasksel tasksel/first multiselect standard, ubuntu-server

These settings already are managed in a Kickstart file in the %packages section, but if they weren’t you could set this setting with

preseed --owner tasksel tasksel/first multiselect \
standard, ubuntu-server

Image Account options

Two other major additions in the Kickstart syntax provide extra options for account management. Because Ubuntu disables the root user by default, the rootpw Kickstart command now can take the --disabled option. This disables the root password and gives the first user root privileges from sudo.

The Ubuntu Kickstart file has also added a user command to add settings for the initial user. The syntax for this command is

user --disabled | username [--fullname "Firstname Lastname"] \
--password insecure [--iscrypted]

This command either takes the --disabled option to disable an initial user altogether or takes as options the username, optionally the user’s full name, and finally the password. By default the password is in plain text, but you can optionally list the MD5 hash for the password; just be sure to add the --iscrypted option so that Kickstart knows.

Limitations

Currently the Ubuntu Kickstart does not implement the full range of features of Red Hat’s Kickstart. These limitations are one reason for advocating a combination of Kickstart and preseeding. The Kickstart file can take care of the base configuration, and where features aren’t available, they can be supplemented by preseed options. Here is a list of major features not yet implemented in Ubuntu’s Kickstart:

Image There is no LDAP, Kerberos 5, Hesios, or Samba authentication.

Image There are no bootloader --linear, --nolinear, or --lba32 options for lilo.

Image There is no lilocheck command.

Image Ubuntu handles system upgrades outside of the Kickstart file.

Image Partitioning in Ubuntu’s Kickstart is not yet as full-featured. As a result, if you have more sophisticated partitioning in mind, I recommend supplementing this with preseed options. Here are the current partitioning limitations:

Image Can partition only the main disk

Image No LVM configuration

Image No bad sector checks

Image No RAID partitions

Image You cannot restrict a partition to a particular disk or specify the starting or ending cylinder.

Image There is no support for a supplemental driver disk during install.

Image The device command is not supported.

Image Firewall configuration is not supported.

Image There is no automated discovery of Kickstart source via DHCP; you must specify Kickstart source explicitly.

Image NFS and local disk are not supported as installation sources.

Image The xconfig --monitor option is not supported (selects a specific monitor name).

Image Package groups in Ubuntu and Red Hat have different names and under Ubuntu, package groups reference Ubuntu tasks instead.

Image You cannot exclude packages in the %packages section.

Image You can use shell scripts only in pre- and post-installation sections.

Run Custom Commands during the Install

Like preseeding, Kickstart supports running custom commands during the install. Also like preseeding, this is done via a pre-install and a post-install script. In a Kickstart file these are defined in %pre and %post sections. Any shell commands you place in those sections will be executed immediately before or after the install process, respectively. A nice feature of these scripts in Kickstart is that they can span multiple lines as long as they stay within the %pre or %post section.


Tip

Even though it’s easy to put a large shell script in the %pre and %post sections, once your scripts get to a certain complexity, you should probably consider putting them into a script on your Kickstart server that you then retrieve via wget and execute. An extra benefit of this approach is that you could perform logic within the %pre and %post sections to pull custom shell scripts based on the type of server you are installing.


Probably the simplest way to start your pre- or post-installation script is with the Kickstart Configurator tool. Each installation script is set up as a category in the left pane with a section in the right pane where you can write your script. You can even specify a custom interpreter for your shell script via the GUI. Even the GUI provides a warning in these sections that a mistake in these shell scripts can cause the Kickstart as a whole to fail.

An important distinction to make between the pre- and post-installation scripts is that the pre-installation script is run within the installer environment whereas the post-install script by default is run within a chroot environment inside the installed system. That means that you can install additional packages or perform other custom tweaks within the installed system in the post-install section, while in the pre-install section you would mostly want to tweak preseed settings.

PXE Boot Server Deployment

While the examples I gave earlier are a good place to start, when you want to automate server installs, you ultimately need to set up a PXE boot server to manage the process. After all, do you really want to manually go to each new server and insert a CD-ROM? What if your server is on another continent or you have 200 servers to install? With PXE boot deployment, your new server boots from the network, pulls down its install image from the PXE server, and then starts the installation process—all over the network.

There are a number of different services you need for PXE boot deployment to work:

Image A DHCP server configured for PXE booting to give each install an IP address and point it to the TFTP server and boot program to load

Image A TFTP server to serve the PXE boot loader program over the network

Image A Web server to host any Kickstart and preseed files and any custom scripts

While each of these services could reside on totally different hosts, for my example I will step you through how to set up each of them, starting with the same default Ubuntu image with the same default settings I have been using so far in this chapter. In this example I will set up a PXE server with an IP address of 10.1.1.5. On this network my gateway router is at 10.1.1.1 and my DNS servers are at 10.1.1.2 and 10.1.1.3.

DHCP

The first step is to set up a DHCP server that can hand out IP addresses to each installation candidate. First install the dhcp3-server package:

$ sudo apt-get install dhcp3-server

By default your DHCP server won’t be configured. All you really need to get everything started is a short subnet section in the /etc/dhcp/dhcpd .conf file (/etc/dhcp3/dhcpd.conf in older releases). When you open the file with your preferred text editor, you will notice there is a basic skeleton of a configuration there along with many commented-out examples. Just go to the bottom of the file and add the following subnet section:

subnet 10.1.1.1 netmask 255.255.255.0 {
    option domain-name-servers 10.1.1.2, 10.1.1.3;
    option routers 10.1.1.1;
    range dynamic-bootp 10.1.1.50 10.1.1.99;
    next-server 10.1.1.5;
    filename "pxelinux.0";
}

I will go more into overall DHCP configuration in Chapter 5, but here I have told DHCP to answer queries on the 10.1.1.0/255.255.255.0 subnet. I have listed the two DNS servers on that network along with the router to use. The dynamic-bootp line defines the range of IP addresses this DHCP server will hand out. In this case I will hand out addresses between 10.1.1.50 and 10.1.1.99. The next-server line configures the IP address for TFTPD server to use so that any hosts that PXE boots can retrieve their boot loader (in this case this server) via TFTPD. The filename line tells it the name of the file to retrieve. More on that in the next section.

Obviously, tweak this sample configuration so that it matches the settings on your particular network, and then start the DHCP server by typing sudo start isc-dhcp-server. You should see

$ sudo start isc-dhcp-server
isc-dhcp-server start/running, process 1558

If the service fails to start because of an error in the configuration file, you should see a reference to the error in the output on the screen. Track down that line in the dhcpd.conf file and look for missing semicolons at the end of lines or any other syntax errors you might have made.

TFTPD

Once the DHCP server is set up, the next step is to install a TFTPD service on your PXE boot server. Type

$ sudo apt-get install tftpd-hpa

and APT will pull down the TFTPD server and the openbsd-inetd package it depends on. As this service is managed by inetd, it will be started only when a user accesses UDP port 69 on this machine. The firewall is disabled by default in my Kickstart and preseed examples, but if you have enabled it, be sure to open a port for UDP port 69.

Configure Pxelinux

Now that TFTPD is installed, you are ready to install and configure pxelinux. Pxelinux is a component of the syslinux package that provides a boot loader for CD-ROMs (including Ubuntu) called isolinux, a standard boot loader known as syslinux, and a PXE boot loader called pxelinux. While you could install the syslinux package and copy the files yourself, Ubuntu has already provided a netboot configuration ready for you to use on its mirrors. All you have to do is go to the TFTPD root directory and then download and extract the netboot tarball for your Ubuntu version. For instance, here are the steps to grab a netboot tarball for Lucid Lynx:

$ cd /var/lib/tftpboot
$ sudo wget http://us.archive.ubuntu.com/ubuntu/dists/precise/main/
  installer-i386/current/images/netboot/netboot.tar.gz
$ sudo tar -xzvf netboot.tar.gz

This tarball already contains the pxelinux.0 binary, an excellent sample pxelinux configuration file in pxelinux.cfg/default, and a series of boot menus and help documents under the ubuntu-installer directory.

When pxelinux loads, it will search the pxelinux.cfg directory on the TFTP server for a number of different configuration files. You can take advantage of this search order to pass custom settings to particular hosts. Pxelinux will search for files in the following order:

Image Files named 01-MACADDRESS with hyphens between each hex pair. So for a server with a MAC address of 88:99:AA:BB:CC:DD, a configuration file that would target just that machine would be named 01-88-99-aa-bb-cc-dd (and I’ve noticed that it does matter that it is lowercase).

Image Files named after the host’s IP address in hex. Here pxelinux will drop a digit from the end of the hex IP and try again as each file search fails. This is often used when an administrator buys a lot of the same brand of machine, which will often have very similar MAC addresses. The administrator can then configure DHCP to assign a certain IP range to those MAC addresses. Then a boot option can be applied to all of that group.

Image Finally, if no specific files can be found, pxelinux will look for a file named default and use it.

For this example we stick with the pxelinux.cfg/default configuration file provided by netboot.tar.gz. You can always create a custom configuration file later on for specific servers, and there are a number of different ways to organize and define separate install options, as I discuss next.

The great thing about using Ubuntu’s netboot tarball is that it sets up a nice PXE boot environment out of the box with menus, help, and most of the hard work already done for you. All you need to do is tweak the default so that it points to your Kickstart file. Open /var/lib/tftpboot/ubuntu-installer/i386/boot-screens/txt.cfg in your favorite text editor and you will see the following lines:

default install
label install
     menu label ^Install
     menu default
     kernel ubuntu-installer/i386/linux
     append vga=788 initrd=ubuntu-installer/i386/initrd.gz -- quiet
label cli
     menu label ^Command-line install
     kernel ubuntu-installer/i386/linux
     append tasks=standard pkgsel/language-pack-patterns=pkgsel/
       install-language-support=false vga=788 initrd=ubuntu-
       installer/i386/initrd.gz -- quiet

The default line tells pxelinux that the section labeled install is the one to load by default if a user just presses Enter at the boot prompt. The label install section defines a particular configuration a user can select. Every section that begins with label defines another configuration. As you look through this configuration file, you will see a number of different labels already set up for you. You can choose between different sections by typing the specific label at the boot prompt and pressing Enter. So while I could just hit Enter to select the section above labeled install, since it is set as the default, I could also type install at the boot prompt and press Enter. All we need to do to this file is change the default boot options so that they also point to our Kickstart file. To do this I just add my options to the append line. The beginning section of the file changed from this:

default install
label install
     menu label ^Install
     menu default
     kernel ubuntu-installer/i386/linux
     append vga=normal initrd=ubuntu-installer/i386/initrd.gz -- quiet

to this:

default install
label install
     menu label ^Install
     menu default
     kernel ubuntu-installer/i386/linux
     append vga=normal initrd=ubuntu-installer/i386/initrd.gz
       ks=http://10.1.1.5/ks.cfg -- quiet

Save your changes and exit your text editor. Pxelinux is now configured and you are ready to move on to the final step.

Web

The last step in the process is to set up a Web server to host your Kickstart and preseed files and any custom scripts you might have. First install the apache2 package, which provides your Web server software and all its necessary dependencies:

$ sudo apt-get install apache2

The apache2 package will automatically set up and start a functioning Web server for you with a default docroot of /var/www. I won’t cover any more advanced configuration of Apache here because all we want is a Web server to host a few text files. At this point all you need to do is move your ks.cfg file to /var/www on this server. You will need to make only one change to your ks.cfg file for PXE booting. Since we won’t be pulling down files from the CD-ROM, we need to tell our Kickstart file to grab files from an Ubuntu mirror, so find and change the line that reads

cdrom

to

url --url http://us.archive.ubuntu.com/ubuntu


Tip

Since this server will be pulling all of its packages directly from an Ubuntu mirror, the installation will undoubtedly take longer than it would from the CD-ROM. If you plan to do a lot of automated Ubuntu installs, you will probably want to consider setting up your own mirror of the Ubuntu archive.


Before you go any further, try to access your ks.cfg file either from a Web browser or using a tool like wget and make sure that Apache is serving that file. If you can’t access the file, check the Apache error logs in /var/log/ apache2/error.log for clues as to what may be wrong, and check the permissions on ks.cfg to make sure that it is world-readable. If not, type chmod a+r ks.cfg to make it so.

Test Your PXE Server

At this point you should be ready to test your PXE server with a new install. There’s no single instruction on how to set a server to PXE boot. On some servers it’s just a matter of hitting a function key to get a list of boot options and then selecting network boot. On other servers you need to go into the BIOS and change the boot order. Still other servers provide a function key that automatically netboots. You will need to research how to tell your particular server to netboot and also make sure that it is connected to the same subnet as your PXE boot server.

Once you PXE-boot your server, you will be greeted with an Ubuntu menu as shown in Figure 4-3. Since we set up the default label to boot with our Kickstart options, all we need to do here is press Enter.

Image

Figure 4-3 Ubuntu PXE boot menu


Note: Why This System Isn’t Fully Automated

Even this automated install requires some user interaction since you need to at least hit Enter at every boot prompt to start the installation process. This is a safety mechanism in a sense, since what happens if six months from now one of your servers reboots and then netboots? If everything were completely automated, that server would start the installation process again and potentially overwrite all of its current files—which could be a very bad thing. Now for some organizations this is desirable behavior (particularly for large clusters of identical machines). They have put in appropriate safeguards so that no machine boots on the network unless they want it to, and if it does, they definitely want it to perform a reinstall. If you want a 100% automated install that requires no user interaction apart from turning on the server, then just locate the TIMEOUT value in the pxelinux.cfg/default file. By default it is set to 0, which means that it will never time out. Just change that to a 1 and any server that boots and grabs that configuration file will automatically load the default label after a one-second time-out.


Customize Automated Installs

You will quickly realize that one size does not fit all when it comes to automated server deployments. As you start to expand your base Kickstart file into multiple offshoots, you will find that there are about as many ways to organize such a process as there are sysadmins. Instead of covering all of them, I discuss some of the more popular ways to organize your Kickstart server for multiple install types.

Two popular ways to customize your automated installs involve changing settings to the pxelinux menu, and using DHCP to bypass the menu altogether and automatically install Ubuntu on any server that boots on the network. Each approach has its advantages and disadvantages. With the pxelinux menu approach, the administrator can provide one final confirmation before a machine starts the installation process. That way you prevent a machine that accidentally boots over the network from overwriting its settings. Another advantage is that you don’t have to look up a machine’s MAC address ahead of time and set up a special configuration option for each server. You get to specify at the boot prompt what sort of server you wish to install and it goes from there.

The main advantage to the DHCP approach is that it provides you with a completely unattended install. All you have to do is set up DHCP ahead of time, and then any time in the future once the server is powered on and netboots for the first time, it will automatically install itself. Some administrators even get the list of MAC addresses from their vendor ahead of time so their DHCP server is ready before the machines even arrive. Once they arrive and are physically installed, anyone could then power on the machines when it’s time for them to go live.

Multiple Kickstart Files

Since the default pxelinux configuration file that Ubuntu includes already sets up a menuing system for you, why not use it to your advantage? You can set up any number of different boot options and assign them different labels. Let’s say, for instance, that you wanted to be able to choose among three different Kickstart files: the default, one specifically for a Web server, and one for a MySQL server. We already have the default Kickstart configured:

default install
label install
       menu label ^Install
       menu default
       kernel ubuntu-installer/i386/linux
       append vga=normal initrd=ubuntu-installer/i386/initrd.gz
         ks=http://10.1.1.5/ks.cfg -- quiet

So all we need to do is clone the label install section twice and tweak each section to point to our other Kickstart files:

default install
label install
       menu label ^Install
       menu default
       kernel ubuntu-installer/i386/linux
       append vga=normal initrd=ubuntu-installer/i386/initrd.gz
         ks=http://10.1.1.5/ks.cfg -- quiet
label web
       menu label ^Install
       menu default
       kernel ubuntu-installer/i386/linux
       append vga=normal initrd=ubuntu-installer/i386/initrd.gz
         ks=http://10.1.1.5/ks-web.cfg -- quiet
label mysql
        menu label ^Install
        menu default
        kernel ubuntu-installer/i386/linux
        append vga=normal initrd=ubuntu-installer/i386/initrd.gz
          ks=http://10.1.1.5/ks-mysql.cfg -- quiet

As you can see, the only real change I made in each section was to point to a different Kickstart file I had created. Now once I boot, I can type web or mysql and then hit Enter to select those particular options.

Boot Cheat Codes

Another useful way to differentiate between different install types is to use cheat codes (a name I’m borrowing from Knoppix). A cheat code is an option you type on the command line that gets passed down to the system. The interesting thing about the boot prompt is that everything that is on the boot prompt is accessible during the install via the /proc/cmdline file. If you want to create custom cheat codes, all you need to do is provide a pre- or post-install script that parses /proc/cmdline and stores those settings into environment variables. Then you can refer to those environment variables in your script and make custom changes to the pre- or post-install process based on them.

For instance, let’s say we want to define two cheat codes at the boot prompt: mytype, to label what type of server it is (Web, MySQL, DNS), and myraid, which tells whether it should use RAID or not. Instead of setting up three different Kickstart files, I could use a single Kickstart file that then has logic in its pre-installation script to parse these options and change settings based on them. Here’s a sample pre-install script that would parse the /proc/cmdline file, set the environment variables, and then perform special actions based on them:

%pre
for i in `cat /proc/cmdline` ; do
       echo $i | grep -iq MYTYPE=
       if [ $? -eq 0 ] ; then
          export MYTYPE=`echo $i | awk -F"=" '{ print $2; }'`
       fi
       echo $i | grep -iq MYRAID=
       if [ $? -eq 0 ] ; then
          export MYRAID=`echo $i | awk -F"=" '{ print $2; }'`
       fi
done

if [ $MYTYPE == "web" ]; then
# Change preseed settings for web servers
elif [ $MYTYPE == "mysql" ]; then
# Change preseed settings for mysql servers
elif [ $MYTYPE == "dns" ]; then
# Change preseed settings for dns servers
fi

if [ $MYRAID ]; then
# Change preseed settings for RAID
fi

Now at the boot prompt if I wanted to set up a MySQL server with RAID, I could type install mytype=mysql myraid=1. Again, the advantage to this approach is that it allows you to maintain a single Kickstart file that has the logic within it to change settings for specific server types. The downside to this approach is that the resulting Kickstart file can get rather large and complex over time.

DHCP Selection

This approach works particularly well if your organization uses static DHCP assignment for your servers. With static DHCP leases, a DHCP server assigns each host that requests an IP address the same IP address every time based on that host’s MAC address. Typically in this setup your server will automatically install a new image whenever it boots off of the network. You will find this approach popular for large clusters or in environments where servers are physically installed sometime before they may ever get an operating system. Once you change the pxelinux configuration to automatically install the default image, your DHCP configuration file controls what each server will ultimately become. All an administrator needs to do to install Ubuntu on a server is connect it to the network and turn on the power.

In this scenario you create multiple directories under /var/lib/tftpboot, each of which contains an extracted copy of the netboot.tar.gz. For instance, if I wanted to set up three separate configurations for Web, MySQL, and DNS servers, I could do the following:

$ cd /var/lib/tftpboot
$ sudo wget http://archive.ubuntu.com/ubuntu/dists/precise/main/
  installer-i386/current/images/netboot/netboot.tar.gz
$ sudo mkdir web mysql dns
$ cd web
$ sudo tar -xzvf netboot.tar.gz
$ cd ../mysql
$ sudo tar -xzvf netboot.tar.gz
$ cd ../dns
$ sudo tar -xzvf netboot.tar.gz

Now I would change the pxelinux.cfg/default files in each of those subdirectories to reflect any special settings I wanted to change. In addition, if I wanted this installation to require no user interaction at all, I would edit the pxelinux.cfg/default configuration files and set the TIMEOUT value to 1 so that after one second it will automatically start the installation process. Again, if you use this method, put appropriate safeguards in place so that a machine doesn’t accidentally netboot and overwrite its settings.

Once my TFTP server is set up, the bulk of the configuration occurs on the DHCP server. I need to modify my /etc/dhcp3/dhcpd.conf file so that, depending on which MAC address requests a lease, it gets pointed to a particular pxelinux.0 file under one of those subdirectories. For each MAC address I would add an individual section. Below I have added three specific servers, each pointing to a Web, MySQL, or DNS configuration:

host web1 {
   hardware ethernet 00:0c:c0:ff:ee:00;
   option host-name "web1";
   fixed-address 10.1.1.101;
   filename "/web/pxelinux.0";
}
host mysql1 {
   hardware ethernet 00:0c:c0:ff:ee:01;
   option host-name "mysql1";
   fixed-address 10.1.1.101;
   filename "/mysql/pxelinux.0";
}
host dns1 {
   hardware ethernet 00:0c:c0:ff:ee:02;
   option host-name "dns1";
   fixed-address 10.1.1.101;
   filename "/dns/pxelinux.0";
}

The main downside to this approach is that it means you have to maintain multiple pxelinux configuration files on your TFTP server. Another downside is that you have to look up the MAC address for each server before you can install anything on it. Of course, if you use static DHCP, that is a step you already have to perform anyway.

One benefit to this method, especially if you set the time-out so that a machine that PXE boots automatically starts installation, is that once you set up your environment, when a new host appears you just grab its MAC address and set up the dhcpd.conf file. Anyone at that point can set up Ubuntu on the server just by connecting to the network and powering on the server (provided it is set up to boot from the network by default).

DHCP Selection by Subnet

One variation on this method takes advantage of the fact that many networks segregate different server types into their own subnets. If your network is like this, then all you need to do is set up different subnet sections in your dhcpd.conf file for each type of server. For instance, let’s say that all of my Web servers are on the 10.1.1.0 subnet and all of my MySQL servers are on the 10.1.2.0 subnet. Instead of setting up a specific section for each and every server, I could just point each subnet to a particular file:

subnet 10.1.1.0 netmask 255.255.255.0 {
    option domain-name-servers 10.1.1.2, 10.1.1.3;
    option routers 10.1.1.1;
    range dynamic-bootp 10.1.1.50 10.1.1.99;
    next-server 10.1.1.5;
    filename "/web/pxelinux.0";
}
subnet 10.1.2.0 netmask 255.255.255.0 {
    option domain-name-servers 10.1.1.2, 10.1.1.3;
    option routers 10.1.2.1;
    range dynamic-bootp 10.1.2.50 10.1.2.99;
    next-server 10.1.1.5;
    filename "/mysql/pxelinux.0";
}

Notice that the next-server section stayed the same in both subnets since I’m still using the same TFTP server. I’m also assuming that this single DHCP server serves leases for both subnets. With this type of install environment, I can control what gets installed on a server simply by controlling which subnet it is a member of. Depending on your environment, this might be as simple as changing a setting on a switch.