With the recent release of FreeBSD 13, I wanted to test it out on a spare RaspberryPi 3 that was part of my old Kubernetes cluster.
In particular, FreeBSD Jails have always interested me, although I’ve never used them in practice. Over the years I’ve managed operating system virtualization through Solaris Zones and Docker containers, and Jails seem like and good middle ground between the two - easier to manage than zones and closer to the OS than Docker.
I also want to run my own Gemini capsule locally to use some of the features that my other hosted capsules don’t have (like SCGI/CGI) and setting up a capsule in a Jail is a good way to learn both at the same time.
Installing FreeBSD on a RaspberryPi is relatively easy, downloading the FreeBSD 13 RPI image and booting from the SD card to get started. Everything will come up automatically, and you can ssh in with the default user:pass of freebsd:freebsd
.
A few post-install things I did to secure the host more,
freebsd
freebsd
user with doas rmuser freebsd
Since the RPI doesn’t have a real-time clock, setting up NTP is crucial for accurate time, which if not set can cause all sorts of issues with TLS and other commands.
# Enabe ntpd
host$ echo 'ntpd_enable="YES"' | doas tee -a /etc/rc.conf
# Force sync time
host$ doas ntpdate pool.ntp.org
# Start ntpd
host$ doas service ntpd onestart
The Jails guide is straightforward, but contains two different methods of configuring jails. The built-in jail
commands or ezjail
. I ended up using ezjail which seems more robust and featureful.
Following the instructions first add the second loopback interface,
host$ echo 'cloned_interfaces="lo1"' | doas tee -a /etc/rc.conf
host$ doas service netif cloneup
Then install ezjail and a few other packages we’ll need later on,
host$ doas pkg install ezjail ca_root_nss openssl
host$ echo 'ezjail_enable="YES"' | doas tee -a /etc/rc.conf
Create a new jail named thesours
, using the new second loopback and a new LAN IP on the interface em0
,
host$ doas ezjail-admin create thesours 'lo1|127.0.1.1,em0|192.168.7.223'
This installs a FreeBSD 13 (default version is the host version) jail filesystem in /usr/jails/thesours/
and will take a while to download and extract.
Once complete, list the new jail,
host$ doas ezjail-admin list
STA JID IP Hostname Root Directory
--- ---- --------------- ------------------------------ ------------------------
DR 1 127.0.1.1 thesours /usr/jails/thesours
1 ue0|192.168.7.223
Now that there’s a running jail, connect to it’s console to start setting it up.
doas ezjail-admin console thesours
Many of the directories are shared with the basejail and are immutable, but adding users and packages, configuring services, and /etc
are all independent of the host OS.
Add a new non-root user using adduser
, install doas
and set up this user for root privileges. Enabling sshd
also allows ssh sessions into the jail,
jail$ echo 'sshd_enable="YES"' | doas tee -a /etc/rc.conf
Now that the jail is setup, the next step is installing and configuring the Gemini server Molly Brown, which has a lot of features such as ~
support for user gemini folders and SCGI/CGI scripting.
Molly Brown requires go
, which was built in the host and not the jail in order to keep jail packages to a minimum.
host$ doas pkg install go
Build Molly Brown,
host$ mkdir ~/go
host$ export GOPATH=~/go
host$ go get tildegit.org/solderpunk/molly-brown
Copy the resulting ~/go/bin/molly-brown
binary to the jail,
host$ doas cp molly-brown /usr/jails/thesours/usr/local/sbin/
Also create the TLS certs that molly brown will require later, and copy them to the jail,
host$ doas mkdir -p /usr/jails/thesours/etc/ssl/gemini/
host$ cd /usr/jails/thesours/etc/ssl/gemini/
host$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 1826 -nodes -subj '/CN=thesours.ecliptik.com'
Go back into the jail and setup a few configurations for Molly Brown with the following assumptions,
/etc/molly.conf
/var/log/molly
/usr/jails/thesours/etc/ssl/gemini
/var/gemini/
daemon
Create the required paths, create/copy files and set the proper permissions for daemon
,
jail$ doas mkdir -p /var/log/molly /var/gemini/
jail$ doas chown -R daemon:daemon /var/log/molly /usr/jails/thesours/etc/ssl/gemini /var/gemini/
Create configuration in /etc/molly.conf
,
## Molly basic settings
Port = 1965
Hostname = "thesours.ecliptik.com"
CertPath = "/etc/ssl/gemini/cert.pem"
KeyPath = "/etc/ssl/gemini/key.pem"
DocBase = "/var/gemini/"
HomeDocBase = "users"
GeminiExt = "gmi"
DefaultLang = "en"
AccessLog = "/var/log/molly/access.log"
ErrorLog = "/var/log/molly/error.log"
Create etc/rc.d/molly
to manage the service and have it start when the jail does. It will run as the daemon
user to improve security.
#!/bin/sh
#
# $FreeBSD$
#
# PROVIDE: molly
# REQUIRE: networking
# KEYWORD: shutdown
. /etc/rc.subr
name="molly"
desc="Gemini Protocol daemon"
rcvar="molly_enable"
command="/usr/local/sbin/molly-brown"
command_args="-c /etc/molly.conf"
molly_brown_user="daemon"
pidfile="/var/run/${name}.pid"
required_files="/etc/molly.conf"
start_cmd="molly_start"
stop_cmd="molly_stop"
status_cmd="molly_status"
molly_start() {
/usr/sbin/daemon -P ${pidfile} -r -f -u $molly_brown_user $command
}
molly_stop() {
if [ -e "${pidfile}" ]; then
kill -s TERM `cat ${pidfile}`
else
echo "${name} is not running"
fi
}
molly_status() {
if [ -e "${pidfile}" ]; then
echo "${name} is running as pid `cat ${pidfile}`"
else
echo "${name} is not running"
fi
}
load_rc_config $name
run_rc_command "$1"
Enable the service,
jail$ echo 'molly_enable="YES"' | doas tee -a /etc/rc.conf
Add a default /var/gemini/index.gmi
file with some basic gemtext and start the molly
service,
jail$ doas service molly start
The gemini capsule gemini://thesours.ecliptik.com is running Molly Brown in a FreeBSD jail.