Automatic OpenBSD Installation

I have wanted a Jumpstart-style procedure for installing OpenBSD servers for a long time. A standard OpenBSD install requires manual response to upwards of twenty questions, most of which will be identical within a particular site. Specifically, I wanted a system where I could add host information to the DNS and then only answer one question during install: the hostname. I have managed to get my system to this point.

The method I have developed is neither elegant nor general, but it is simple and works well for me. I only manage i386 machines, so I have not attempted to make this architecture independent.

My system has 4 parts: web server, custom install CD, install script on the network, and install.site finish script. I try to have each step do as little as possible to ease debugging. I also use cfengine post-install (and during normal operation) to push out local configuration.

Web Server

I mirror the most recent release on my local network, both to be nice to the main server, and to speed local installs. A mirror is also required to host the siteXX.tgz and replacement install script.

I use the following shell script to make the mirror:

cd <DocumentRoot> BASE=ftp://ftp.openbsd.org/pub/OpenBSD/3.5 PATH=$PATH:/usr/local/bin # wget is in /usr/local/bin ARGS="--passive-ftp -r -N -nh" wget $ARGS -l inf $BASE/i386/ wget $ARGS -l inf $BASE/packages/i386/ wget $ARGS -l 1 $BASE/

This will replicate the directory structure of the main server.

Custom Install CD

The "custom" install CD is actually identical to the official OpenBSD install CD (cdXX.iso) save one file: root's .profile. On cdXX.iso, the .profile is used to start the install script, the upgrade script, or a shell. My hacked .profile configures the network with DHCP, downloads an install script from the network (on my network, the location of this script is http://openbsd/install), and executes the install script.

This step is not supposed to do any more than the official install would do. That is, it is only supposed to give the new host a network identity and get it to a state where other tools can further customize it.

The install script is on the network for several reasons. One, it is annoying to have to burn a new CD for each modification. Two, the way I know to generate a customized version of cdXX.iso involves a full build as described in release(8), so it is really annoying (because it takes so long). Shortcuts may exist, but this is the safe way.

The source relevant to the boot CD is in distrib/miniroot. Edit dot.profile to modify what the CD will do on boot. My modified version looks like this:

PATH=/sbin:/bin:/usr/bin:/usr/sbin; export PATH TERM=vt100; export TERM umask 022 set -o emacs echo 'erase ^?, werase ^W, kill ^U, intr ^C, status ^T' stty newcrt werase ^W intr ^C kill ^U erase ^? status ^T mount -u ${rootdisk:-/dev/rd0a} / # Network config ifconfig lo0 inet 127.0.0.1 # Find all ethernet interfaces A=`dmesg | grep 'address ..:..:..:..:..:..' | sed 's/^\(.*\) at .*/\1/'` # Find the first interface with cable plugged in for i in $A; do if ifconfig $i | grep 'status: active'; then IFACE=$i; export IFACE break fi done if test "$IFACE"; then echo Network interface is $IFACE else echo No network interface located. Aborting. exec sh fi cat >/etc/dhclient.conf <<EOF initial-interval 1; request subnet-mask, broadcast-address, routers, domain-name, domain-name-servers, host-name; EOF if ! dhclient -1 $IFACE; then echo Automatic network configuration failed. Aborting. exec sh fi cd /tmp if ! ftp -m http://openbsd/install; then echo Failed to download install script. Aborting. exec sh fi sh install || exec sh reboot

You can also find it here.

Now follow release(8), to get a modified cdXX.iso. Steps 1 and 2 can be skipped since we are working from the release source code. We also don't care about X. This is what I do:

# First, make sure your source is installed and clean rm -rf /usr/src/* for i in {src,sys}.tar.gz; do ftp -o - -m http://openbsd/pub/OpenBSD/3.5/$i | tar xzf - -C /usr/src done # Make local modifications vi /usr/src/distrib/miniroot/dot.profile # These next few steps take a long time rm -rf /usr/obj/* cd /usr/src && nice make obj cd /usr/src/distrib/crunch && make obj depend all install cd /usr/src && nice time make build # These steps need to be done to regenerate cd35.iso export DESTDIR=/var/tmp/dest export RELEASEDIR=/var/tmp/release rm -rf $DESTDIR $RELEASEDIR mkdir -p $DESTDIR $RELEASEDIR cd /usr/src/etc && nice time make release cd /usr/src/distrib/sets && sh checkflist # Now $RELEASEDIR/cd35.iso should exist with your modifications

In case further modifications are desired, the files that make up the official installer are:

/usr/src/distrib/miniroot/install.{sh,sub} /usr/src/distrib/i386/common/install.md # "machine dependent" /usr/src/distrib/miniroot/dot.profile # Executes installer

Install Script on the Network

This script is stored at the root of my mirror. I have removed references to my machines. Note that you will certainly want to edit the disk partitioning section. A more general version of this script might be useful. It may seem long, but it is really quite straight forward.

#!/bin/sh MAJ=3 # Version major number MIN=5 # Version minor number TZ=US/Central # Time zones are in /usr/share/zoneinfo SETS="comp man misc" # comp man misc game x{base,font,serv,share} site # bsd{,.rd} base etc selected automatically DISK=wd0 MIRROR=http://openbsd/pub/OpenBSD/$MAJ.$MIN/i386 NAMESERVER="192.168.69.2 192.168.69.3" # Up to three GATE=192.168.69.1 MASK=255.255.255.0 DOMAIN=example.com SEARCH="$DOMAIN other.example.com" ROOTPW='*' # cfagent will change this to the real cryptstring # End of user configuration # This next part is redundant with dot.profile, becuase I forgot to # export IFACE. That will require another system build, so this is a # temporary workaround # Find all ethernet interfaces A=`dmesg | grep 'address ..:..:..:..:..:..' | sed 's/^\(.*\) at .*/\1/'` # Find the first interface with cable plugged in for i in $A; do if ifconfig $i | grep 'status: active'; then IFACE=$i; export IFACE break fi done if test "$IFACE"; then echo Network interface is $IFACE else echo No network interface located. Aborting. exec sh fi # End of redundant code cat <<EOF Welcome to the Automated OpenBSD installer. Please remove the CD now. Nothing has been written to disk yet. If installing OpenBSD is not what you want, you can still turn off the machine RIGHT NOW to avoid data loss. If you do anything else, you have passed the point of no return and all data on this machine is forfeit. When this script is finished, if it was successful in installing OpenBSD, the machine will reboot. You will need to configure the BIOS to boot from the hard disk. EOF while :; do echo -n "Enter hostname (or sh to start a shell): " read HOSTNAME test "$HOSTNAME" = sh && { sh; continue; } PINGOUT=`ping -c 1 -w 1 $HOSTNAME` echo $PINGOUT | grep ^PING >/dev/null 2>&1 && break echo Error finding IP address for $HOSTNAME. Please try again. done IPADDR=`echo $PINGOUT | sed 's/^PING .*(\(.*\)).*/\1/'` echo IP address is $IPADDR hostname $HOSTNAME.$DOMAIN # Taken from md_prep_fdisk in /usr/src/distrib/i386/common/install.md fdisk -e $DISK <<EOF reinit update write quit EOF TMPDEV=${DISK}d USRDEV=${DISK}e VARDEV=${DISK}f # See md_prep_disklabel in /usr/src/distrib/i386/common/install.md disklabel -d $DISK >/tmp/default_label disklabel -R $DISK /tmp/default_label # Put a default label on first EDITOR=ed disklabel -e $DISK <<EOF /^ a: c a: 2097585 63 4.2BSD 2048 16384 328 b: 2097585 2097648 swap . + a d: 8388576 4195233 4.2BSD 2048 16384 328 e: 18971440 12583809 4.2BSD 2048 16384 328 f: 8388576 31555249 4.2BSD 2048 16384 328 . w q EOF newfs -q /dev/r${DISK}a newfs -q /dev/r$TMPDEV newfs -q /dev/r$USRDEV newfs -q /dev/r$VARDEV mount /dev/${DISK}a /mnt mkdir -p /mnt/{etc,tmp,usr,var} mount /dev/$TMPDEV /mnt/tmp; chmod 1777 /mnt/tmp mount /dev/$USRDEV /mnt/usr mount /dev/$VARDEV /mnt/var # Install files for i in bsd bsd.rd; do ftp -o /mnt/$i -m $MIRROR/$i done for i in base etc $SETS; do ftp -o - -m $MIRROR/$i$MAJ$MIN.tgz | tar zxphf - -C /mnt done # Localize /etc cat >/mnt/etc/fstab <<EOF /dev/${DISK}a / ffs rw,softdep 1 1 /dev/${DISK}b none swap sw 0 0 /dev/$TMPDEV /tmp ffs rw,softdep,nodev,nosuid 1 2 /dev/$USRDEV /usr ffs rw,softdep,nodev 1 2 /dev/$VARDEV /var ffs rw,softdep,nodev,nosuid 1 2 EOF echo inet $IPADDR $MASK NONE >/mnt/etc/hostname.$IFACE hostname >/mnt/etc/myname echo $GATE >/mnt/etc/mygate cat >/mnt/etc/hosts <<EOF ::1 localhost.$DOMAIN localhost 127.0.0.1 localhost.$DOMAIN localhost $IPADDR `hostname -s`.$DOMAIN `hostname -s` EOF cat >/mnt/etc/resolv.conf <<EOF lookup file bind search $SEARCH `for i in $NAMESERVER; do echo nameserver $i; done` EOF # See install.sh near the bottom # I suppose $ROOTPW may contain / but not @ ed /mnt/etc/master.passwd <<EOF 1,s@^root::@root:$ROOTPW:@ w q EOF /mnt/usr/sbin/pwd_mkdb -p -d /mnt/etc /etc/master.passwd ln -sf /usr/share/zoneinfo/$TZ /mnt/etc/localtime # See finish_up in install.sub /mnt/sbin/swapctl -a /dev/${DISK}b ( cd /mnt/dev && sh MAKEDEV all ) dd if=/mnt/dev/urandom of=/mnt/var/db/host.random bs=1024 count=64 chmod 600 /mnt/var/db/host.random # Taken from md_installboot in /usr/src/distrib/i386/common/install.md cat /usr/mdec/boot >/mnt/boot /usr/mdec/installboot -v /mnt/boot /usr/mdec/biosboot ${DISK} test -x /mnt/install.site && /mnt/usr/sbin/chroot /mnt /install.site rm -f /mnt/install.site exit 0

And you can find it here also.

I used aide to verify that a system as installed by this script is identical, to the degree that I care about, to a system installed by the official installer.

install.site Finish Script

Further site customization is done by an install.site script. See the OpenBSD FAQ here for the official documentation. My install.site does the following tasks:

You could just do all other customizations directly in install.site though, if you were so inclined.