___________________________________________ title: Gemini Capsule in a FreeBSD Jail running on a RaspberryPi tags: freebsd hack gemini 100daystooffload date: 2021-04-25 ___________________________________________ Intro 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. [recent release of FreeBSD 13]: https://www.freebsd.org/releases/13.0R/announce/ [old Kubernetes cluster]: https://www.ecliptik.com/Raspberry-Pi-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. [FreeBSD Jails]: https://docs.freebsd.org/en/books/handbook/jails/ [operating system virtualization]: https://en.wikipedia.org/wiki/OS-level_virtualization 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. [Gemini]: https://gemini.circumlunar.space Installing FreeBSD on a RaspberryPi 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. [downloading the FreeBSD 13 RPI image]: https://download.freebsd.org/ftp/releases/arm64/aarch64/ISO-IMAGES/13.0/ A few post-install things I did to secure the host more, - Add another non-root user other than freebsd - Disable password logins and require ssh-keys - Setup doas for new user - Remove the freebsd user with doas rmuser freebsd - Set strong root password [doas]: https://man.openbsd.org/doas Setting up NTP 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 Setting up the Jail Creating the Jail 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. [Jails guide]: https://docs.freebsd.org/en/books/handbook/jails/ [using ezjail]: https://docs.freebsd.org/en/books/handbook/jails/#jails-ezjail 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 Setting up the Jail 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 Setting up a Gemini Capsule 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]: https://tildegit.org/solderpunk/molly-brown Building Molly Brown 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, - Config in /etc/molly.conf - Logs in /var/log/molly - TLS certs in /usr/jails/thesours/etc/ssl/gemini - Document root in /var/gemini/ - Run as 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/ Molly Brown Configuration 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" Creating a Molly Brown Service 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 Running Example The gemini capsule gemini://thesours.ecliptik.com is running Molly Brown in a FreeBSD jail. [gemini://thesours.ecliptik.com]: gemini://thesours.ecliptik.com