Eric Radman : a Journal

OpenBSD Auto-Install

One of the features that may be underutilized is OpenBSD's uncomplicated auto-installer. I do not mean that all of the mechanisms that need to be in place for a real-world use case are simple, but the OpenBSD parts are simple.


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

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

subnet netmask {
    option routers;

    host openbsd1 {
        hardware ethernet fe:e1:bb:d1:cd:29;
        filename "auto_install";
        option host-name "openbsd1";

The filename auto_install is not arbitrary, this causes the install script in bsd.rd to start the automated install by pulling configuration over HTTP. The complete layout for /tftpboot might look like this:

drwxr-xr-x   2 root  eradman      512 May 16 15:54 .
drwxr-xr-x  16 root  wheel       1024 May 13 01:26 ..
lrwxr-xr-x   1 root  eradman       13 May 16 15:52 auto_install -> pxeboot.amd64
lrwxr-xr-x   1 root  eradman       12 May 16 15:54 bsd -> bsd.rd.amd64
-rw-r--r--   1 root  eradman  776.512 May 13 10:07 bsd.rd.amd64
-rw-r--r--   1 root  eradman    82300 May 13 10:07 pxeboot.amd64

Per-Host Install Options

The next-server entry specified by the DHCP server points to the path where answers file can be found:

default - - [16/May/2016:15:55:54 -0400]
  "GET /00:1e:c9:4c:69:59-install.conf?path=6.5/amd64 HTTP/1.0" 200 314

The answers file contains strings which match the questions from the installer

# /var/www/htdocs/00:1e:c9:4c:69:59-install.conf
System hostname = openbsd1
Password for root = 123456
Network interfaces = run0
IPv4 address for run0 = dhcp
Setup a user = eradman
Password for user eradman = zzzzzz
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*

If you don't specify a line then a default will be used. If more options are


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 =

Sane mount options are selected automatically. The minimum in a range is selected for each file system, and remaining space is split between the partitions based on the 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 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; we only need to select the new package in *install.conf

Set name(s) = site65.tgz

To make this easy I am allowing for this one package to be installed without being signed.

Checksum test for site65.tgz failed. Continue anyway = yes
Unverified sets: site65.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. This example fetches and installs ports on first boot

ftp -o - | tar -zxf - -C /usr

The autoinstaller allows you to skip checkums and signatures for the package, but it has to be listed in index.txt

(cd site65; tar -czvf ../amd64/site65.tgz .)
(cd amd64; ls -l > index.txt)

Another useful item to put in siteXX.tgz is the public signify(1) key. More on that later...

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 "/home/eradman/vm/db1.img"
    interface {
        switch "local"
        lladdr fe:e1:bb:d1:cd:29

vm "db2" {
    memory 512M
    boot "/bsd.db2"
    disk "/home/eradman/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 in order to get to the installer. Now you can type 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/6.5/packages/amd64

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

Uploading Disk Images

If you created a VM image using vmctl create you now have a disk image than can be used by some cloud on providers. Remove /etc/boot.conf so that the console is not redirected to com0

curl --request POST --user \
     --header 'Content-Type: application/octet-stream' \
     --upload-file ~/vm/www.img \

If your using CloudSigma make sure /etc/hostname.vio0 set to dhcp your new VM will get a public address.

Last updated on August 16, 2019