Eric Radman : a Journal

Automated OpenBSD Installation

OpenBSD has a very good story for automated installations, which includes features such as:

Mirroring Sets

OpenBSD project kindly makes rsync mirrors available aid in mainting a local mirror

cd /var/www/htdocs/pub/OpenBSD
mkdir -p 7.4/amd64 syspatch/7.4/amd64

cd /var/www/htdocs/pub/OpenBSD
openrsync -rv rsync:// 7.4/
openrsync -rv rsync:// syspatch/7.4/


The first task of booting via PXE is to hand out an address and filename to fetch over TFTP

# /etc/dhcpd.conf
option  domain-name "";
option  domain-name-servers;

subnet netmask {
    option routers;

    host db1 {
        hardware ethernet fe:e1:bb:d1:cd:29;
        filename "auto_install";
        option host-name "db1";
    host t460s {
        hardware ethernet c8:5b:76:0d:1c:f3;
        filename "efi/auto_install";
        option host-name "t460s";


A basic layout for /tftpboot may look like this:

|-- auto_install -> pxeboot
|-- bsd -> bsd.rd
|-- bsd.rd
|-- efi
|   |-- BOOTX64.EFI
|   |-- auto_install -> BOOTX64.EFI
`-- pxeboot

Where pxeboot is used for a BIOS boot, and BOOTX64.EFI is used for UEFI.

auto_install is not an arbitrary filename: this triggers to the install in bsd.rd to start the automated install by pulling configuration over HTTP.

If the system is headless, console parameters may be provided in /tftpboot/etc/boot.conf

stty com0 115200
set tty com0

Per-Host Install Options

The next-server entry specified by the DHCP server points to the path where answers file can be found. This file contains strings which match the questions from the installer

# /var/www/htdocs/fe:e1:bb:d1:cd:29-install.conf
System hostname = db1
Password for root = 123456
Network interfaces = run0
IPv4 address for run0 = dhcp
Setup a user = eradman
Password for user eradman = 123456
Public ssh key for user = ssh-ed25519 XYZ123...
Which disk is the root disk = sd0
What timezone are you in = US/Eastern
Unable to connect using https. Use http instead = yes
Location of sets = http
Server =
Set name(s) = -all bsd* base* etc* man* site* comp*

Plain text passwords are accepted, or generate encrypted passwords using

printf "123456" | encrypt


Custom partition layouts are documented under the disklabel(8) man page. This is all we need to add to the autoinstall script:

URL to autopartitioning template for disklabel =

Disklabel slices are calculated based on a minimum size within each range, then remaining space is split between the partitions based on a percentage:

/           250M
swap        80M-256M  10%
/tmp        120M-4G    8%
/var        80M-4G    13%
/usr        900M-2G    5%
/usr/local  2G-10G    10%
/pg_data    1G-*      45%


To make this mechanism portable I run these services from my laptop. First I enable the hotplug daemon

# rcctl enable hotplugd

Next create /etc/hotplug/attach to add any USB-to-Ethernet adapter to a local bridge that is serving DHCP requests



case $DEVCLASS in
        ifconfig $DEVNAME up
        ifconfig bridge0 add $DEVNAME

Where DEVCLASS 3 is a network interface. Similarly, /etc/hotplug/detach disables these services using the opposite actions.

Custom Sets

OpenBSD allows for custom software to be installed by adding a site-specific tgz file. If index.txt includs the new file it will appear in the menu.

To make this easy, verification for siteNN.tgz may be skipped

Checksum test for site71.tgz failed. Continue anyway = yes
Unverified sets: site71.tgz. Continue without verification = yes

One of the most interesting files that can be installed with siteNN.tgz is /etc/rc.firsttime. This is executed the first time a system boots up in multi-user mode, and is a very convenient way to make sure some bits of essential post-install configuration occur.

Autoinstalling OpenBSD on VMM

In addition to PXE-booting a bare-metal system we can install local VMs in almost the exact same manner. The first step is to define the VMs.

switch "local" {
    add vether0

vm "db1" {
    memory 512M
    boot "/bsd.db1"
    disk "/vm/db1.img"
    interface {
        switch "local"
        lladdr fe:e1:bb:d1:cd:29

vm "db2" {
    memory 512M
    boot "/bsd.db2"
    disk "/vm/db2.img"
    interface {
        switch "local"
        lladdr fe:e1:bb:d1:cd:30

VMM's boot option can be a BIOS or a kernel image. The trick to auto-installing a VM is to temporarily swap in bsd.rd to get to the installer. Now use vmctl start db1 -c and press A. We can have tmux automate this for us

#!/bin/sh -ex
# - Reset all VMs
# - Start each VM and select Autoinstall
# - Set the boot kernel to bsd.sp

for vm in $*; do
    ln -f /bsd.rd /bsd.$vm

rcctl restart vmd

for vm in $*; do
    tmux new-session -s autoinstall -d
    tmux send-keys -t autoinstall:0 "doas vmctl start $vm -c" C-m
    sleep 10
    tmux send-keys -t autoinstall:0 "A" C-m
    sleep 2
    tmux kill-session -t autoinstall
    ln -f /bsd.sp /bsd.$vm
doas vmctl status

Signing Custom Packages

Once we can spin up fresh VMs or bare-metal installations we can build custom packages without having to run pkg_add with -Dunsigned. To do this we need to generate a signing key

signify -G -n -s /etc/signify/custombuild-pkg.sec -p /etc/signify/

After building our custom packages sign each of them

pkg_sign -C -v \
  -s signify2 -s /etc/signify/custombuild-pkg.sec \
  -S /usr/ports/packages/amd64/all \
  -o /var/www/htdocs/pub/OpenBSD/7.4/packages/amd64

Adding -C to pkg_sign will take care of updating the checksum file (SHA256) in the target directory.