Eric Radman : a Journal

Automated FreeBSD Install

For FreeBSD, the ability to PXE boot BIOS and UEFI systems is not new, but documentation frequently assumes an NFS mounted root file system. In my view NFS is unworkable since file access is not logged, and a hung mount is difficult to diagnose.

By using TFTP to pivot to a minroot, then switching to HTTP the entire process can be audited and tailored for individual hosts. This methodology aims to borrow techniques used by an automated install for OpenBSD and Red Hat Linux.

DHCP Services

To PXE-boot FreeBSD there are three components we will need to three components

# /etc/dhcpd.conf

subnet netmask {
    option routers;
    option domain-name-servers;

    host T460S        {
        hardware ethernet c8:5b:76:0d:1c:f3;
        filename "loader.efi";
        option root-path "tftp://";
        option host-name "t460s";

HTTP Services

The install media can be copied, or mount the ISO as a read-only file system

vnconfig vnd1 /var/www/htdocs/FreeBSD-13.2-RELEASE-amd64-disc1.iso
mount_cd9660 /dev/vnd1c /var/www/htdocs/pub/freebsd13

Distribution files can now be pulled from this http://${next_server}/pub/freebsd13/usr/freebsd-dist/.

Copy loader.efi to /tftpboot/.

Memory File System

Sadly, FreeBSD does not ship a minroot, but we can fetch an image from the mfsBSD project. Mount this ISO in a location where the loader and access boot config and minroot

vnconfig vnd0 /var/www/htdocs/mfsbsd-13.2-STABLE-amd64.iso
mount_cd9660 /dev/vnd0c /tftpboot/freebsd13

This provides everything required to boot

|-- boot
|   |-- defaults
|   |   `-- loader.conf
|   |-- device.hints
|   |-- kernel
|   |   |-- acpi_*.ko
|   |   |-- ahci.ko
|   |   |-- kernel.gz
|   |   `-- linker.hints
|   |-- loader
|   |-- loader.conf
|   `-- lua
|       |-- cli.lua
|       |-- color.lua
|       |-- config.lua
|       |-- core.lua
|       |-- drawer.lua
|       |-- gfx-*.lua
|       |-- hook.lua
|       |-- loader.lua
|       |-- menu.lua
|       |-- password.lua
|       `-- screen.lua
|-- boot.config
`-- mfsroot.gz

boot/loader.conf provides the configuration for switching to a memory file system


For a standard build mfsroot.gz is about 70MB.

Building a Custom Miniroot

The mfsBSD root image can be modifed to kick off a custom install script. In the mfsbsd root, modify conf/rc.local.sample. The steps are:

export PATH=/usr/local/bin:$PATH

for op in `awk '/^(  |  option )(next-server|host-name|domain-name) / { gsub("-", "_", $(NF-1)); print $(NF-1)"="$NF }' /var/db/dhclient.leases.*`
    eval "$op"

mkdir /usr/freebsd-dist
cd /usr/freebsd-dist
for dist in MANIFEST kernel.txz base.txz; do
    fetch http://${next_server}/pub/freebsd13/usr/freebsd-dist/${dist}
cd -

fetch http://${next_server}/bsdinstall/${host_name}.cfg
echo "Starting autoinstall"
tmux new-session -s autoinstall -d
tmux send-keys -t autoinstall:0 "bsdinstall script ${host_name}.cfg" C-m

To build:

cd mfbsd-master
make clean-all BASE=/home/eradman/13.2-RELEASE/usr/freebsd-dist
make BASE=/home/eradman/13.2-RELEASE/usr/freebsd-dist
make iso BASE=/home/eradman/13.2-RELEASE/usr/freebsd-dist

Host Configuration

The installation bootstrap is based into the custom miniroot, but the host configuration is fetched from the installer set by the DHCP option

export BSDINSTALL_DISTDIR="/usr/freebsd-dist"

cat >> /etc/rc.conf <<CONF

# install packages
pkg install -y doas
echo "permit keepenv nopass :wheel" > /usr/local/etc/doas.conf
chmod 600 /usr/local/etc/doas.conf

# configure users
pw useradd eradman -m -u 1000 -s /bin/sh -G wheel
install -d -o eradman -g eradman -m 700 /home/eradman/.ssh
echo "ssh-ed25519 AAAA..." > /home/eradman/.ssh/authorized_keys
chown eradman:eradman /home/eradman/.ssh/authorized_keys

echo "rebooting in 10 seconds"
sleep 10