LiquidSoap in a FreeBSD jail

Apparently this has become the “how to do obscure things in FreeNAS jails” blog. I’ll include some notes at the end on setting up everything with icecast and rc.d scripts, but I’m assuming you arrived here because you want to install liquidsoap, and it’s not going well.

We’ll start with the official installation instructions. As of this writing, you should run this:

curl -LO https://github.com/ocaml/opam/releases/download/2.1.2/opam-2.1.2-x86_64-freebsd
mv ./opam* /usr/local/bin/opam
chmod +x /usr/local/bin/opam
opam --version
pkg install -y gmake patch ocaml ocaml-opam
opam init --no-setup
opam switch create 4.12.0
eval $(opam env --switch=4.12.0)

The Liquidsoap instructions are a little outdated at this point. You can run opam depext, but ocaml says that opam install does that (installing package dependencies) for you now. But it doesn’t quite work, because you’re here, right? Still, let’s give it a try:

opam install -y liquidsoap
...
[ERROR] The compilation of liquidsoap.2.1.0-1 failed at "./configure --prefix /root/.opam/4.12.0 --sbindir=/root/.opam/4.12.0/lib/liquidsoap/sbin --libexecdir=/root/.opam/4.12.0/lib/liquidsoap/libexec
        --sysconfdir=/root/.opam/4.12.0/lib/liquidsoap/etc --sharedstatedir=/root/.opam/4.12.0/lib/liquidsoap/com --localstatedir=/root/.opam/4.12.0/lib/liquidsoap/var
        --libdir=/root/.opam/4.12.0/lib/liquidsoap/lib --includedir=/root/.opam/4.12.0/lib/liquidsoap/include --datarootdir=/root/.opam/4.12.0/lib/liquidsoap/share
        --with-bash-completion-dir=/root/.opam/4.12.0/lib/liquidsoap/etc/bash_completion.d --with-user=dummy --with-group=dummy".

#=== ERROR while compiling liquidsoap.2.1.0-1 =================================#
# context     2.1.2 | freebsd/x86_64 | ocaml-base-compiler.4.12.0 | https://opam.ocaml.org#49486377
# path        ~/.opam/4.12.0/.opam-switch/build/liquidsoap.2.1.0-1
# command     ~/.opam/4.12.0/.opam-switch/build/liquidsoap.2.1.0-1/./configure --prefix /root/.opam/4.12.0 --sbindir=/root/.opam/4.12.0/lib/liquidsoap/sbin --libexecdir=/root/.opam/4.12.0/lib/liquidsoap/libexec --sysconfdir=/root/.opam/4.12.0/lib/liquidsoap/etc --sharedstatedir=/root/.opam/4.12.0/lib/liquidsoap/com --localstatedir=/root/.opam/4.12.0/lib/liquidsoap/var --libdir=/root/.opam/4.12.0/lib/liquidsoap/lib --includedir=/root/.opam/4.12.0/lib/liquidsoap/include --datarootdir=/root/.opam/4.12.0/lib/liquidsoap/share --with-bash-completion-dir=/root/.opam/4.12.0/lib/liquidsoap/etc/bash_completion.d --with-user=dummy --with-group=dummy
# exit-code   1
# env-file    ~/.opam/log/liquidsoap-82430-595887.env
# output-file ~/.opam/log/liquidsoap-82430-595887.out
### output ###
# /root/.opam/4.12.0/.opam-switch/build/liquidsoap.2.1.0-1/./configure: 5.0.0}: not found
# Cannot find file ./{.
# fatal: not a git repository (or any of the parent directories): .git
# checking for a BSD-compatible install... /usr/bin/install -c
# checking for GNU make... gmake
# checking build system type... x86_64-unknown-freebsd13.1
# checking host system type... x86_64-unknown-freebsd13.1
# configure: error: "OS freebsd13.1 is not supported"

There are a bunch of errors in there that might make you think this is a path issue or possibly a git issue, but no. The real error is configure: error: "OS freebsd13.1 is not supported". Why is not supported? Long story short, because they said so. What if we bypass that check and build it ourselves?

opam install -y liquidsoap 2>&1 | tee liquidsoap.out # capture the output so we can use it with scripts
cat liquidsoap.out | grep "# path" | awk '{ print $3 }'
cat > configure.patch << EOF
--- configure.old	2022-07-07 14:38:18.172368441 -0500
+++ configure	2022-07-07 14:38:50.099099880 -0500
@@ -3132,6 +3132,9 @@
     darwin*)
         build_os="osx"
         ;;
+    freebsd*)
+        build_os="bsd"
+        ;;
     *)
         as_fn_error $? "\"OS $host_os is not supported\"" "$LINENO" 5
         ;;
EOF
patch $(cat liquidsoap.out | grep "# path" | awk '{ print $3 }' | sed "s|~|$HOME|")/configure < configure.patch
cd $(cat liquidsoap.out | grep "# path" | awk '{ print $3 }' | sed "s|~|$HOME|")
./configure

That’s right. It works just fine. Sigh. Oh, but look closely at the output:

 * Liquidsoap
   - version           : 2.1.0

 * Supported input formats
   - MP3               : no (requires mad)
   - AAC               : no (requires faad)
   - FFmpeg            : no (requires ffmpeg)
   - Flac (native)     : no (requires flac)
   - Flac (ogg)        : no (requires flac.ogg)
   - Lastfm            : no (requires lastfm)
   - Opus              : no (requires opus)
   - Speex             : no (requires speex)
   - Theora            : no (requires theora)
   - Vorbis            : no (requires vorbis)
   - XML playlists     : no (requires xmlplaylist)

 * Supported output formats
   - FDK-AAC           : detected at runtime
   - FFmpeg            : no (requires ffmpeg)
   - MP3               : detected at runtime
   - MP3 (fixed-point) : no (requires shine)
   - Opus              : no (requires opus)
   - SPEEX             : no (requires speex)
   - Theora            : no (requires theora)
   - Vorbis            : no (requires vorbis)

 * Tags
   - charset detection : yes
   - Taglib (ID3 tags) : no (requires taglib)
   - Vorbis            : no (requires vorbis)

 * Input / output
   - ALSA              : no (requires alsa)
   - AO                : no (requires ao)
   - FFmpeg            : no (requires ffmpeg)
   - Icecast/Shoutcast : no (requires cry)
   - GStreamer         : no (requires gstreamer)
   - JACK              : no (requires bjack)
   - OSS               : yes
   - Portaudio         : no (requires portaudio)
   - Pulseaudio        : no (requires pulseaudio)
   - SRT               : no (requires srt)

 * Audio manipulation
   - FFmpeg            : no (requires ffmpeg)
   - LADSPA            : no (requires ladspa)
   - Lilv              : no (requires lilv)
   - Samplerate        : no (requires samplerate)
   - SoundTouch        : no (requires soundtouch)

 * Video manipulation
   - camlimages        : no (requires camlimages)
   - camlimages.ft     : no (requires camlimages.freetype)
   - FFmpeg            : no (requires ffmpeg)
   - frei0r            : no (requires frei0r)
   - SDL-image         : no (requires tsdl-image)
   - SDL-ttf           : no (requires tsdl-ttf)

This supports… nothing. No input formats, no output formats. That’s not very useful. Lets install a few:

pkg install -y lame libsamplerate taglib libmad
opam install -y taglib mad lame vorbis cry samplerate ocurl

#=== ERROR while compiling lame.0.3.6 =========================================#
# context     2.1.2 | freebsd/x86_64 | ocaml-base-compiler.4.12.0 | https://opam.ocaml.org#49486377
# path        ~/.opam/4.12.0/.opam-switch/build/lame.0.3.6
# command     ~/.opam/4.12.0/bin/dune build -p lame -j 3 @install
# exit-code   1
# env-file    ~/.opam/log/lame-22106-b6dc09.env
# output-file ~/.opam/log/lame-22106-b6dc09.out
### output ###
# compiling c program:
# [...]
#  |   return 0;
#  | }
# run: cc -O2 -fno-strict-aliasing -fwrapv -fPIC  -I /root/.opam/4.12.0/lib/ocaml -o /tmp/build_70a624_dune/ocaml-configurator17c755/c-test-0/test.exe /tmp/build_70a624_dune/ocaml-configurator17c755/c-test-0/test.c -lm -lmp3lame -lm
# -> process exited with code 1
# -> stdout:
# -> stderr:
#  | /tmp/build_70a624_dune/ocaml-configurator17c755/c-test-0/test.c:2:10: fatal error: 'lame/lame.h' file not found
#  | #include <lame/lame.h>
#  |          ^~~~~~~~~~~~~
#  | 1 error generated.
# Fatal error: exception File "src/config/discover.ml", line 23, characters 6-12: Assertion failed

[ERROR] The compilation of conf-mad.1 failed at "pkg-config --exists mad".

#=== ERROR while compiling conf-mad.1 =========================================#
# context     2.1.2 | freebsd/x86_64 | ocaml-base-compiler.4.12.0 | https://opam.ocaml.org#49486377
# path        ~/.opam/4.12.0/.opam-switch/build/conf-mad.1
# command     /usr/local/bin/pkg-config --exists mad
# exit-code   1
# env-file    ~/.opam/log/conf-mad-98214-3f06d6.env
# output-file ~/.opam/log/conf-mad-98214-3f06d6.out

The lame issue is because liquidsoap’s search paths don’t include /usr/local. That’s easy to fix. But … what? We just installed… oh, we installed *libmad*, not *mad*. Really? How can we fix this…

n -s /usr/local/lib/libmp3lame.a /usr/lib/libmp3lame.a
ln -s /usr/local/lib/libmp3lame.so /usr/lib/libmp3lame.so
ln -s /usr/local/lib/libmp3lame.so.0 /usr/lib/libmp3lame.so.0
ln -s /usr/local/lib/libmp3lame.so.0.0.0 /usr/lib/libmp3lame.so.0.0.0
mkdir /usr/include/lame
ln -s /usr/local/include/lame/lame.h /usr/include/lame/lame.h

cp /usr/local/libdata/pkgconfig/libmad.pc /usr/local/libdata/pkgconfig/mad.pc
opam install -y mad lame

Well. That was a hack. Let’s try configure again:

./configure
...
checking for ocaml mm module >= 0.8.1... configure: error: Not found.

Sigh.

opam install -y mm
./configure
* Liquidsoap
   - version           : 2.1.0

 * Supported input formats
   - MP3               : yes
   - AAC               : no (requires faad)
   - FFmpeg            : no (requires ffmpeg)
   - Flac (native)     : no (requires flac)
   - Flac (ogg)        : no (requires flac.ogg)
   - Lastfm            : no (requires lastfm)
   - Opus              : no (requires opus)
   - Speex             : no (requires speex)
   - Theora            : no (requires theora)
   - Vorbis            : yes
   - XML playlists     : no (requires xmlplaylist)

 * Supported output formats
   - FDK-AAC           : detected at runtime
   - FFmpeg            : no (requires ffmpeg)
   - MP3               : yes
   - MP3 (fixed-point) : no (requires shine)
   - Opus              : no (requires opus)
   - SPEEX             : no (requires speex)
   - Theora            : no (requires theora)
   - Vorbis            : yes

 * Tags
   - charset detection : yes
   - Taglib (ID3 tags) : yes
   - Vorbis            : yes

 * Input / output
   - ALSA              : no (requires alsa)
   - AO                : no (requires ao)
   - FFmpeg            : no (requires ffmpeg)
   - Icecast/Shoutcast : yes
   - GStreamer         : no (requires gstreamer)
   - JACK              : no (requires bjack)
   - OSS               : yes
   - Portaudio         : no (requires portaudio)
   - Pulseaudio        : no (requires pulseaudio)
   - SRT               : no (requires srt)

 * Audio manipulation
   - FFmpeg            : no (requires ffmpeg)
   - LADSPA            : no (requires ladspa)
   - Lilv              : no (requires lilv)
   - Samplerate        : yes
   - SoundTouch        : no (requires soundtouch)

 * Video manipulation
   - camlimages        : no (requires camlimages)
   - camlimages.ft     : no (requires camlimages.freetype)
   - FFmpeg            : no (requires ffmpeg)
   - frei0r            : no (requires frei0r)
   - SDL-image         : no (requires tsdl-image)
   - SDL-ttf           : no (requires tsdl-ttf)

Finally! By no means do we support all the things, but at least we have mp3 and Vorbis. Let’s build this!

gmake -j2
gmake install
cd /
liquidsoap --version

Congratulations, you have built liquidsoap with a few useful plugins. Hopefully this will help someone else install liquidsoap on FreeBSD. Or at least point them in the right direction.

Full Solution

These steps will get you a working liquidsoap+icecast radio station, but they are intended as an example, not an exact solution for you. Modify as needed.

Jail

Jails->Add

Name, Jail Type: default, Release (choose the latest)

DHCP Autoconfigure IPv4, VNET

Submit


Start the jail

From shell:

jls
sudo jexec # /bin/sh where # is the JID of the jail you created
adduser
Username: media
Full name: media
Uid (Leave empty for default): 
Login group [media]: 
Login group is media. Invite media into other groups? []: 
Login class [default]: 
Shell (sh csh tcsh nologin) [sh]: 
Home directory [/home/media]: 
Home directory permissions (Leave empty for default): 
Use password-based authentication? [yes]: 
Use an empty password? (yes/no) [no]: yes
Lock out the account after creation? [no]:   
Username   : media
Password   : <blank>
Full Name  : media
Uid        : 1001
Class      : 
Groups     : media 
Home       : /home/media
Home Mode  : 
Shell      : /bin/sh
Locked     : no
OK? (yes/no): yes
adduser: INFO: Successfully added (media) to the user database.
Add another user? (yes/no): no
Goodbye!

Icecast

pkg install -y icecast mime-support
ln -s /usr/local/etc/mime.types /etc/mime.types
service icecast enable
cat > icecast.patch << EOF
--- /usr/local/etc/icecast.xml.sample	2022-06-30 16:53:40.000000000 -0500
+++ /usr/local/etc/icecast.xml	2022-07-08 10:18:31.352888230 -0500
@@ -234,11 +234,9 @@
 
     <security>
         <chroot>0</chroot>
-        <!--
         <changeowner>
-            <user>nobody</user>
-            <group>nogroup</group>
+            <user>media</user>
+            <group>media</group>
         </changeowner>
-        -->
     </security>
 </icecast>
EOF
cp /usr/local/etc/icecast.xml.sample /usr/local/etc/icecast.xml
patch /usr/local/etc/icecast.xml < icecast.patch
mkdir /var/log/icecast
chown media:media /var/log/icecast
service icecast start
service icecast status

LiquidSoap

curl -LO https://github.com/ocaml/opam/releases/download/2.1.2/opam-2.1.2-x86_64-freebsd
mv ./opam* /usr/local/bin/opam
chmod +x /usr/local/bin/opam
opam --version
pkg install -y gmake patch ocaml ocaml-opam
opam init --no-setup
opam switch create 4.12.0
eval $(opam env --switch=4.12.0)

pkg install -y lame libsamplerate taglib libmad
n -s /usr/local/lib/libmp3lame.a /usr/lib/libmp3lame.a
ln -s /usr/local/lib/libmp3lame.so /usr/lib/libmp3lame.so
ln -s /usr/local/lib/libmp3lame.so.0 /usr/lib/libmp3lame.so.0
ln -s /usr/local/lib/libmp3lame.so.0.0.0 /usr/lib/libmp3lame.so.0.0.0
mkdir /usr/include/lame
ln -s /usr/local/include/lame/lame.h /usr/include/lame/lame.h
cp /usr/local/libdata/pkgconfig/libmad.pc /usr/local/libdata/pkgconfig/mad.pc
opam install -y taglib vorbis cry samplerate ocurl
opam install -y mad lame
opam install -y liquidsoap 2>&1 | tee liquidsoap.out # this should fail
cat liquidsoap.out | grep "# path" | awk '{ print $3 }'
cat > configure.patch << EOF
--- configure.old	2022-07-07 14:38:18.172368441 -0500
+++ configure	2022-07-07 14:38:50.099099880 -0500
@@ -3132,6 +3132,9 @@
     darwin*)
         build_os="osx"
         ;;
+    freebsd*)
+        build_os="bsd"
+        ;;
     *)
         as_fn_error $? "\"OS $host_os is not supported\"" "$LINENO" 5
         ;;
EOF
patch $(cat liquidsoap.out | grep "# path" | awk '{ print $3 }' | sed "s|~|$HOME|")/configure < configure.patch
cd $(cat liquidsoap.out | grep "# path" | awk '{ print $3 }' | sed "s|~|$HOME|")
./configure
gmake -j3
gmake install
cd /
liquidsoap --version

Configure LiquidSoap

liquidsoap 'set("init.allow_root",true)
output.icecast(%mp3.vbr(internal_quality=0),
host = "localhost", port = 8000,
password = "hackme", mount="liq.mp3",
mksafe(playlist(mode="normal", reload=1, reload_mode="rounds", "playlist.pls")))'
#!/usr/local/bin/liquidsoap

set("init.allow_root",true)
# log.file.path.set("/var/log/streaming.log")

src = mksafe(playlist(mode="normal", reload=1, reload_mode="rounds", "playlist.pls"))

output.icecast(%mp3.vbr(internal_quality=0,id3v2=true),
host = "localhost", port = 8000,
password = "hackme", mount="classical.mp3",
src)

output.icecast(%mp3.vbr(internal_quality=4,samplerate=32000,id3v2=true),
host = "localhost", port = 8000,
password = "hackme", mount="classical-lofi.mp3",
src)
#!/bin/sh
# /usr/local/etc/rc.d/streaming

# PROVIDE: streaming
# REQUIRE: DAEMON icecast
# BEFORE:  LOGIN
# KEYWORD: shutdown

# Add the following line to /etc/rc.conf to enable `streaming'.
#
#streaming_enable="YES"
#

. /etc/rc.subr

name="streaming"
rcvar=streaming_enable

command="/streams.liq"
command_args=""
extra_commands=""

load_rc_config $name

run_rc_command "$1"

Final thoughts

If you’re hosting a radio station for yourself, I recommend installing tailscale in the jail (pkg install tailscale; service tailscaled enable; service tailscaled start; tailscale up; – it’s that easy!).

I also wrote a little go script to create a playlist by randomizing directories, in case that’s useful to anyone. That way the symphonies and other multi-part pieces stay together.

About Bion

I'm a software developer at Modo Payments, a mobile payment provider. When I'm not hacking away the office, you I'm usually at home hacking on something else. Or practicing Aikido. Anyway, I just post things here that Google couldn't help me with, so maybe it'll help you in the future. Since you're reading this, I guess it worked :)
This entry was posted in Technology and tagged . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *