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 192.168.0.0 netmask 255.255.255.0 {
    option routers 192.168.0.7;
    option domain-name-servers 192.168.0.3;
    range 192.168.0.64 192.168.0.254;
    next-server 192.168.2.20;

    host T460S        {
        hardware ethernet c8:5b:76:0d:1c:f3;
        fixed-address 192.168.0.11;
        filename "loader.efi";
        option root-path "tftp://192.168.2.20/freebsd14";
        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-14.1-RELEASE-amd64-disc1.iso
mount_cd9660 /dev/vnd1c /var/www/htdocs/pub/freebsd14

Distribution files can now be pulled from this http://${next_server}/pub/freebsd14/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-14.1-STABLE-amd64.iso
mount_cd9660 /dev/vnd0c /tftpboot/freebsd14

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

mfs_load="YES"
mfs_type="mfs_root"
mfs_name="/mfsroot"
ahci_load="YES"
vfs.root.mountfrom="ufs:/dev/md0"

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. Get a copy of the latest build

fetch https://github.com/mmatuska/mfsbsd/archive/refs/heads/master.tar.gz
tar -zxf master.tar.gz
cd mfsbsd-master

Then edit conf/rc.local.sample

# sample rc.local
#
export PATH=/usr/local/bin:$PATH

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

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

# Get bsdinstall(8) configuration by hostname and launch in a tmux session
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

Mount a copy of the install image

mdconfig -f FreeBSD-14.1-RELEASE-amd64-disc1.iso
mkdir $HOME/14.1-RELEASE
mount -t cd9660 /dev/md0 $HOME/14.1-RELEASE

To build:

cd mfsbsd-master
make clean-all BASE=$HOME/14.1-RELEASE/usr/freebsd-dist RELEASE=14.1
make BASE=$HOME/14.1-RELEASE/usr/freebsd-dist RELEASE=14.1
make iso BASE=$HOME/14.1-RELEASE/usr/freebsd-dist RELEASE=14.1

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

PARTITIONS="nda0 { 64G freebsd-swap, auto freebsd-ufs / }"
export BSDINSTALL_DISTSITE="http://172.16.0.1/pub/freebsd14/usr/freebsd-dist/"
export BSDINSTALL_DISTDIR="/usr/freebsd-dist"

#!/bin/sh
cat >> /etc/rc.conf <<CONF
ifconfig_DEFAULT=DHCP
sshd_enable=YES
ntpd_enable=YES
CONF

# set timezone
ln -s /usr/share/zoneinfo/US/Eastern /etc/localtime

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

# add 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...  eradman@t470s.eradman.com" > /home/eradman/.ssh/authorized_keys
chown eradman:eradman /home/eradman/.ssh/authorized_keys

# set passwords
sed -i -e 's/:passwd_format=sha512:/:passwd_format=blf:/g' /etc/login.conf
chpass -p '$2b$ ...' root
chpass -p '$2b$ ...' eradman

# activate FreeBSD boot option
efibootmgr | awk '/Boot.+ FreeBSD/ { print substr($1, 5, 4) }' | xargs -n1 efibootmgr -o

echo "rebooting in 10 seconds"
sleep 10
reboot