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:
- Set the time with rdate
- Install cfengine and db4 packages
- Generate cfengine keys
- Install update.conf so cfagent knows from where to get policy
- Run cfagent
You could just do all other customizations directly in install.site though,
if you were so inclined.