Using vmm(4) to target old OpenBSD releases
This server (the very one you are reading this post on), at the time of writing this post, runs OpenBSD 6.1-stable. It’s fully patched and updated and everything, so it’s a perfectly fine OS to run. But the VPS has limited memory and disk space, and the CPU isn’t very fast, so compiling large projects on it, especially Haskell ones, is impractical.
This post describes a way to build fully-functional, dynamically linked (so you get all those security updates, super important for a public-facing web service), native Haskell binaries with cabal-install, Stack and…OpenBSD’s (kind of) new native hypervisor, vmm.
Why can’t I just do this on my local machine?
Well, I don’t have any machines running 6.1-release or 6.1-stable, so that’s right out. The reason I don’t keep any around is that I need them all on -current to test ports and random patches I see on tech@. There have been a lot of major version bumps and not to mention the switch to clang and probably some other security features that all conspire to make 6.2-beta very binary incompatible with 6.1-stable.
Act 1: The setup
Very simple actually. I want to install 6.1 on a virtual machine with 4G of memory, 15G of disk (overkill, but disks are huge nowadays) and a network interface.
First, start up vmd:
# rcctl enable vmd # rcctl start vmd vmd(ok)
cd somewhere with more than 15G of free space (and get more than 4G of memory).
# vmctl create disk.img -s 15G
Get a ramdisk to boot from. We want the amd64 6.1 one:
$ ftp http://ftp.fr.openbsd.org/pub/OpenBSD/6.1/amd64/bsd.rd
Now spin up a VM (here we name it “vm61”):
# vmctl start vm61 -b bsd.rd -m 4G -d disk.img -i 1
You do need some networking if you want to get anything useful done. If you read the vmctl man page, you’ll notice that vmctl’s
-i option creates a network interface inside the VM which uses the vio(4) driver and a tap(4) interface in your host machine. Since you want the VM to access the real world, you probably want to bridge the tap and your real interface.
My real interface is re0, my tap interface is tap0. I created a bridge interface bridge0 as follows:
# cat > /etc/hostname.bridge0 <<EOF add re0 add tap0 up # sh /etc/netstart bridge0
Now the network is bridged and you can proceed as normal.
Act 2: Building the binaries
To get a console on the VM, run:
# vmctl console vm61
From here, you install as normal, using the vio interface to setup the network and proceed with the install as usual (look up how to do this elsewhere if you don’t already know).
You might recognise the console program as cu. Accordingly, look at the cu(1) man page for instructions on escape sequences to send files, exit the console and so on.
Note: since this is essentially a throwaway VM I’m just going to use to build Haskell binaries, I overrode the default partition layout and just made one huge 14G root partition with wxallowed on and a 1G swap partition. I don’t know how much space exactly cabal, stack and so on will take up (it’s a lot though) and I know they need wxallowed. A more elegant solution is possible where you allocate the bare minimum space (15G is way too much), but this works well enough.
After installing, you have to boot from the disk. We need to kill and start the VM again without the bsd.rd argument:
# vmctl stop vm61 # vmctl start vm61 -m 4G -d disk.img -i 1
You may also notice that the tap0 interface was destroyed when the VM halted. So add it back to the bridge:
# ifconfig bridge0 add tap0
And now re-enter the console (or SSH in).
Here’s what I did next (after adding
~/.cabal/bin to PATH):
$ doas pkg_add ghc cabal-install git $ cabal install stack < heaps of output > $ stack setup $ git clone git://github.com/kaashif/stargate-search.git $ cd stargate-search $ stack install
Surprisingly, the binaries installed to
~/.local/bin Just Worked (tm) on this server.
Perhaps that’s not so surprising, since they’re both OpenBSD 6.1 amd64 machines and all computers are the same nowadays.