GitOps website deployment with Forgejo and Jekyll on FreeBSD
Background
A while back I had an out-of-band discussion with a visitor to this website, where he pointed out that the procedure I had documented in my previous article on triggering a Jekyll build via a Git hook on FreeBSD didn’t really work anymore. I couldn’t replicate the issue with my setup, so although the report was valid, I didn’t follow up on my own server at that time.
Unfortunately, the underlying issue seems to do with a) how FreeBSD handles Ruby, and b) how specific Jekyll dependencies in Ruby seem to be built only for Linux nowadays. As could be expected, a recent update to Ruby on FreeBSD broke the Jekyll builds on my Git forge.
Possible solutions
From where I was, I saw two immediate ways forward: I could set up a new Forgejo server on some common Linux operating system, or I could set up a Linux based Forgejo runner instead of using Git hooks. And then @mpts pinged me a comment:
When it comes to the Linux-only SASS library, a solution might be to run it with #Linuxulator on #FreeBSD.
I had read about the Linuxulator but never implemented it before, and this sounded like a fun way to spend a Saturday, so I decided to attempt this approach.
Implementation steps
First off, I simply followed the instructions for installing the Linuxulator in the FreeBSD handbook. I elected to use Ubuntu, and of course chose Jammy (22.04 LTS) rather than Focal (20.04 LTS), as the latter is EOL. I would have preferred to use 24.04 simply for not having to touch the solution again in a while, but it isn’t yet supported by the Linuxulator.
I fixed the FreeBSD server’s /etc/fstab
as per instructions, and of course made the necessary changes to the apt
config in the Linuxulator environment.
The next step was to chroot
into the Linuxulator environment and install the necessary packages:
- build-essential
- git
- ruby
- ruby-dev
I also copied the lines for the git
user and group from the FreeBSD machine’s /etc/passwd
and /etc/group
into the corresponding files in the Linuxulator environment.
The next step was to create a home directory for the git user and populate it so that my git hook script (see further down) would work.
At this point, though, the Linuxulator could only be used as root, and that’s unworkable, for the very obvious reason that the Git hook script will run as the git
user - and of course because it will be executing the Ruby bundler, which isn’t happy running as root.
To allow non-root users to chroot, I added the following line to /etc/sysctl.conf.local
:
security.bsd.unprivileged_chroot=1
My current post-receive
git hook for the website repo looks like this:
#!/usr/bin/env sh
export WEBSERVER=websrv2.oxcrag.net
export PRE_CHROOT_HOME=/compat/ubuntu/usr/local/git
if [ ! -d $PRE_CHROOT_HOME/tmp ]; then
mkdir $PRE_CHROOT_HOME/tmp
fi
export PRE_CHROOT_TMP_GIT_CLONE=$PRE_CHROOT_HOME/tmp/oxcrag.net
export POST_CHROOT_PUBLIC_WWW=$PRE_CHROOT_TMP_GIT_CLONE/_site
# Verify target dir is really empty
rm -Rf $PRE_CHROOT_TMP_GIT_CLONE
git clone $GIT_DIR $PRE_CHROOT_TMP_GIT_CLONE
chroot -n /compat/ubuntu bin/bash -c '
export HOME=/usr/local/git
# Install Ruby Gems to ~/gems
export GEM_HOME=$HOME/gems
export PATH=$GEM_HOME/bin:$PATH
TMP_GIT_CLONE=$HOME/tmp/oxcrag.net
GEMFILE=$TMP_GIT_CLONE/Gemfile
PUBLIC_WWW=$TMP_GIT_CLONE/_site
gem install bundler --no-document
BUNDLE_GEMFILE=$GEMFILE bundle install
BUNDLE_GEMFILE=$GEMFILE bundle exec jekyll build -s $TMP_GIT_CLONE -d $PUBLIC_WWW
exit
'
if scp -r $POST_CHROOT_PUBLIC_WWW deployer@$WEBSERVER: ; then
echo "Website deployment finished"
else
echo "Website deployment failed"
exit
fi
rm -Rf $PRE_CHROOT_TMP_GIT_CLONE
exit