Build Bootable FreeBSD Image for Raspberry Pi 2 with Vagrant, Crochet, and Salt

An issue in FreeBSD 11-CURRENT - where make buildworld fails when run on RPi2 - prompted an investigation on automating the creation of bootable FreeBSD images from 11-CURRENT on a Mac OS X machine.

In an ideal situation, a build process can be kicked off at any time to produce an image that can be written to a Micro SD card for the RPi2 to boot from.

Things you need to get this project up and running:

  • git
  • VirtualBox
  • Vagrant
  • FreeBSD 11.0-CURRENT Vagrant box
  • Crochet
  • Salt
  • OS X host

UPDATE (2016-04-19): I have created a companion git repo that provides a quick start alternative to the manual steps described here.

First Steps

user@host:~ $ mkdir -p ~/devbox/projects/freebsd-rpi
user@host:~ $ cd ~/devbox/projects/freebsd-rpi
user@host:~/devbox/projects/freebsd-rpi $ git init

Salt

Create a Python (2.7) virtualenv and install Salt.

user@host:~/devbox/projects/freebsd-rpi $ virtualenv-2.7 pyvenv
user@host:~/devbox/projects/freebsd-rpi $ echo "pyvenv/" >> .gitignore
user@host:~/devbox/projects/freebsd-rpi $ git add .gitignore
user@host:~/devbox/projects/freebsd-rpi $ git commit -m "Add pyvenv/ to .gitignore"
user@host:~/devbox/projects/freebsd-rpi $ source pyvenv/bin/activate
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ pip install --upgrade pip

Create the master configuration file. There's a security no-no of accepting all minions in this config file but that's the easiest thing to do. Your outlook may be different.

(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ pip install salt
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ mkdir salt
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ vim salt/master
auto_accept: True
interface: 0.0.0.0
root_dir: /Users/user/devbox/projects/freebsd-rpi/salt
pki_dir: /Users/user/devbox/projects/freebsd-rpi/salt/pki
pidfile: /Users/user/devbox/projects/freebsd-rpi/salt/var/run/salt-master.pid
user: user
file_roots:
    base:
        - /Users/user/devbox/projects/freebsd-rpi/salt/states
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ git add salt/
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ git commit -m "Add master config file"
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ echo "salt/pki/" >> .gitignore
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ echo "salt/var/" >> .gitignore
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ git add .gitignore
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ git commit -m "Add salt/{pki,var} to .gitignore"

Delete accepted key for minion1 since it will be re-added as part of Vagrant provision

(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ salt-key -c salt -D minion1 -y

Now it is possible to start Salt master. Before you can provision using Vagrant the Salt master must be running.

(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ echo "*.log" >> .gitignore
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ git add .gitignore
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ git commit -m "Add *.log to .gitignore"
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ salt-master -c salt --log-file "./salt.log" --log-file-level "debug"

VirtualBox

Install it.

Vagrant

Download and install Vagrant.

Create a brand new Vagrant file.

user@host:~/devbox/projects/freebsd-rpi $ vagrant init
user@host:~/devbox/projects/freebsd-rpi $ git add Vagrantfile
user@host:~/devbox/projects/freebsd-rpi $ git commit -m "Add Vagrantfile"

Download FreeBSD 11.0-CURRENT "box" from HashiCorp ATLAS.

user@host:~/devbox/projects/freebsd-rpi $ vagrant box add --provider virtualbox --clean freebsd/FreeBSD-11.0-CURRENT

Use the downloaded box in Vagrantfile.

user@host:~/devbox/projects/freebsd-rpi $ sed -i .bak -e 's/  config.vm.box = "base"/  config.vm.box = "freebsd\/FreeBSD-11.0-CURRENT"/g' Vagrantfile
user@host:~/devbox/projects/freebsd-rpi $ echo "*.bak" >> .gitignore
user@host:~/devbox/projects/freebsd-rpi $ echo ".vagrant/" >> .gitignore
user@host:~/devbox/projects/freebsd-rpi $ git add .gitignore
user@host:~/devbox/projects/freebsd-rpi $ git commit -m "Create .gitignore"
user@host:~/devbox/projects/freebsd-rpi $ git add Vagrantfile
user@host:~/devbox/projects/freebsd-rpi $ git commit -m "Use FreeBSD-11.0-CURRENT box"

Configure required settings by adding these lines before end in Vagrantfile. Modify this information as needed, especially the IP address of Salt master.

config.vm.base_mac = "000AAAAAAAAA"
config.vm.guest = :freebsd
config.vm.hostname = "freebsd"
config.vm.synced_folder ".", "/vagrant_data", disabled: true
config.ssh.shell = "sh"
config.vm.provision "shell", inline: <<-SHELL
  pkg update
  pkg install -y py27-salt
  cp /usr/local/etc/salt/minion.sample /usr/local/etc/salt/minion
  sed -i .1.bak -e 's/^#master: salt$/master: 10.201.107.246/g' /usr/local/etc/salt/minion
  sed -i .2.bak -e 's/^#file_client: remote$/file_client: remote/g' /usr/local/etc/salt/minion
  sed -i .3.bak -e 's/^#id:$/id: minion1/g' /usr/local/etc/salt/minion
  sysrc salt_minion_enable="YES"
  service salt_minion start
SHELL

Commit keep these changes in git.

user@host:~/devbox/projects/freebsd-rpi $ git add Vagrantfile
user@host:~/devbox/projects/freebsd-rpi $ git commit -m "Configure VM"

Provision the VM. Since Salt is installed in a virtualenv we need to activate it first and then start the daemon if it isn't already.

user@host:~/devbox/projects/freebsd-rpi $ source pyvenv/bin/activate
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ salt-master -d -c salt --log-file "./salt.log" --log-file-level "debug"

Provision the VM

(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ vagrant up

Crochet

Create salt state files to install git.

(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ mkdir -p salt/states/{git,vim-lite}
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ vim salt/states/top.sls
base:
    'minion*':
        - git
        - vim-lite
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ vim salt/states/git/init.sls
git:
    pkg:
        - installed
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ vim salt/states/vim-lite/init.sls
vim-lite:
    pkg:
        - installed
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ git add salt/states
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ git commit -m "Add salt states"

Restart salt master

(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ pkill -F /Users/user/devbox/projects/freebsd-rpi/salt/var/run/salt-master.pid
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ salt-master -d -c salt --log-file "./salt.log" --log-file-level "debug"

Apply the configuration.

(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ mkdir -p salt/var/log/salt
(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ salt -c salt '*' state.highstate

On the FreeBSD VM build a Raspberry Pi 2 bootable image with Crochet.

(pyvenv) user@host:~/devbox/projects/freebsd-rpi $ vagrant ssh
vagrant@freebsd:~ % sudo pkg install vim-lite
vagrant@freebsd:~ % git clone https://github.com/freebsd/crochet.git
vagrant@freebsd:~ % cd crochet

Copy the sample config file ...

vagrant@freebsd:~/crochet % cp config.sh.sample config.rpi2.sh
vagrant@freebsd:~/crochet % vim config.rpi2.sh

... and apply this diff

vagrant@freebsd:~/crochet % diff config.sh.sample config.rpi2.sh
34c34
< #board_setup RaspberryPi2
---
> board_setup RaspberryPi2
55c55
< #option ImageSize 3900mb # for 4 Gigabyte card
---
> option ImageSize 3900mb # for 4 Gigabyte card
97c97
< #option Growfs
---
> option Growfs
115c115
< #option User <username>
---
> option User raspberry
126a127
> option SwapFile 768mb deferred file=/swapfile0
160c161
< #option Ntpd
---
> option Ntpd
164c165
< #KERNCONF=MYCONF
---
> KERNCONF=RPI2
171c172
< #FREEBSD_SRC=/usr/src
---
> FREEBSD_SRC=/usr/src
194c195
< #IMG=${WORKDIR}/FreeBSD-${KERNCONF}.img
---
> IMG=${WORKDIR}/FreeBSD-${KERNCONF}.img
202c203
< #FREEBSD_INSTALL_WORLD=y
---
> FREEBSD_INSTALL_WORLD=y

Download sources, install u-boot-rpi2, and build an image

vagrant@freebsd:~/crochet % sudo svnlite checkout svn://svn.freebsd.org/base/head /usr/src
vagrant@freebsd:~/crochet % sudo pkg install -y sysutils/u-boot-rpi2
vagrant@freebsd:~/crochet % sudo /bin/sh crochet.sh -c config.rpi2.sh

Take the resulting image, dd it to a micro SD card, insert into RPi2 and boot it.

TODO

Make this an automated process that requires minimal manual intervention.