Background

I run 3 radio stations—with no cap on listeners, bandwidth, or bitrate; with access over port 80 and SSL—for only €12 a month.

You can too if the linux command line doesn’t completely scare you off.  Also, my stations only shuffle music, I don’t do live sets.  It is definitely possible, but I won’t cover it here.

VPS Requirements

You’ll most likely need a VPS with unlimited bandwidth. Also, if you plan to broadcast the same stream using multiple bitrates or codecs, you’ll need a decent CPU because Liquidsoap only uses one CPU core per instance. (There may be some ways around this limitation, but I am currently doing fine running 3 different stations, each at 2 different bit rates. I’m using the 4-core VPS at Scaleway (C2S), so each station is using 1 core for its Liquidsoap instance, and the CPU load on each core is always under 50%.)

System Prep

Choose a VPS provider (I use Scaleway) and spin up a new VPS running the latest stable Debian or Ubuntu OS.  They should all have some help on getting started, logging in for the first time, etc.

If you haven’t already, go to your domain registrar and point the domain (or subdomain) to the IP of your new VPS.

SSH into the VPS and check for updates:

apt update && apt -y upgrade

Create a user, such as “radio” and add to the sudo group for root privileges:

adduser radio
adduser radio sudo

Exit and log back in as your new user.

Install Shoutcast

Additional info can be found at wiki.winamp.com/wiki/SHOUTcast_Broadcaster.

mkdir ~/sc
cd ~/sc
wget http://download.nullsoft.com/shoutcast/tools/sc_serv2_linux_x64-latest.tar.gz
tar -xvf sc_serv2_linux_x64-latest.tar.gz
rm -f sc_serv2_linux_x64-latest.tar.gz

Setup Shoutcast

You could use the setup script:

chmod u+x setup.sh
./setup.sh

Then point your browser to the IP of you VPS plus the port, e.g. http://111.111.111.111:8000/setup

Or just copy the basic config example and start editing it:

cp examples/sc_serv_basic.conf shoutcast.conf
nano shoutcast.conf

Here is my config file:

logfile=logs/sc_serv.log
w3clog=logs/sc_w3c.log
banfile=control/sc_serv.ban
ripfile=control/sc_serv.rip

maxuser=2000
songhistory=25

portbase=80

password=password-used-by-sources
adminpassword=password-for-admin-interface

streamid_1=1
streampath_1=/station1-128
streamauthhash_1=yourAuthHashFromRmo.shoutcast.com

streamid_2=2
streampath_2=/station1-256
streamauthhash_2=yourAuthHashFromRmo.shoutcast.com

streamid_3=3
streampath_3=/station2-128
streamauthhash_3=secondAuthHashFromRmo.shoutcast.com

streamid_4=4
streampath_4=/station2-256
streamauthhash_4=secondAuthHashFromRmo.shoutcast.com

I am using port 80 because of some listeners behind firewalls.  Because 80 is special, I have to start Shoutcast as root.  We’re logged in with the radio user, so use sudo:

sudo ~/sc/sc_serv daemon ~/sc/shoutcast.conf

The daemon option will keep sc_serv running in the background.

Get some Music

mkdir ~/music
cd ~/music

Begin uploading music with an SFTP client, such as Cyberduck.

If you happen to be moving from an existing radio host, and all your music is already on their server, you can transfer directly using ncftp.

sudo apt install ncftp
ncftpget -RTvu 'username' server.oldhost.com ~/music/ /media

(Replace /media with the path to your music folder on the old host.  /media is the normal folder for CentovaCast setups.  ~/music/ is where you’re moving the music TO on your VPS.)

This will probably take a long time, and you don’t want to ncftp process to stop if your connection drops, so it is best to use screen for this.  Screen will allow it to continue in the background.

Simply enter screen and you’ll get a new prompt.  Start the ncftp command, and then leave the screen instance with control + a then d (for detach).  You can return to the screen by typing screen -r

Install Liquidsoap

Liquidsoap recommends the OPAM installation method.  For additional options, see liquidsoap.fm/download.html.

Be sure to login as the radio user for the Liquidsoap install.  When using OPAM to install Liquidsoap, it will only run for the user who installed it.

sudo apt install opam

When using OPAM for the first time, you should run

opam init

and answer y (for yes) to the question it asks about whether you want OPAM to modify some configuration files (this will put the directory where Liquidsoap in your path).

Install dependencies and then liquidsoap:

opam install depext
opam depext taglib mad lame vorbis cry liquidsoap
opam install taglib mad lame vorbis cry liquidsoap

Setup Liquidsoap

Liquidsoap is extremely flexible and there’s a lot to learn if you want to dig into the docs.  I’m just going to walk through my config file.

Liquidsoap configs end in .liq, so let’s make one for the first station:

mkdir -p ~/liquidsoap/logs
nano ~/liquidsoap/station1.liq

And here’s my config:

# run in the background
set("init.daemon",true)
# set the location for the pid file which the daemon creates
set("init.daemon.pidfile.path","~/liquidsoap/<script>.pid")
# set a log file location
# (<script> will be filled in automatically with the name of this file)
set("log.file.path","~/liquidsoap/logs/<script>.log")

# TuneIn AIR API function
# see http://tunein.com/broadcasters/api/
# This handles sending live metatdata updates to tunein.com, if you use that
# Replace with your partner id, partner key, and station id
def send_meta(m) =
        partnerId  = "?partnerId=xxxxxxx"
        partnerKey = "&partnerKey=xxxxxxx"
        id         = "&id=s111111"
        api_server = "http://air.radiotime.com/Playing.ashx"
        title      = "&title=" ^ url.encode(m["title"])
        artist     = "&artist=" ^ url.encode(m["artist"])
        album      = "&album=" ^ url.encode(m["album"])
        uri =( api_server ^ partnerId ^ partnerKey ^ id ^ artist ^ title ^ album )
        ignore(http.get(uri))
end
# end TuneIn code, remove if not using.

# This creates the main playlist by pulling all media in the path (including subfolders)
# the "watch" mode refreshes the playlist any time a new file is added
# song order is randomized by default
standard = playlist(reload_mode="watch","/home/radio/music/station1")

radio = standard
# add crossfade
radio = crossfade(start_next=10.0,fade_out=0.5,fade_in=0.5,radio)
# make the source "infallible" by adding silence if the source does fail
radio = mksafe(radio)

# Send Meta to TuneIn AIR API
# also remove this line if not using
radio = on_metadata(send_meta, radio)

# Stream it out
# Mostly self-explanitory values
# the password is what we set in shoutcast.conf (the "password" not the "adminpassword")
# the icy_id corresponds to the "stream_id" in shoutcast.conf
output.shoutcast(%mp3(bitrate=128),
  port = 80,
  password = 'pwdFrompwdFromShoutcast.conf',
  name = "Ambient Sleeping Pill",
  url = "https://ambientsleepingpill.com",
  genre = "Ambient",
  icy_id = 1,
  radio)
output.shoutcast(%mp3(bitrate=256),
  port = 80,
  password = 'pwdFromShoutcast.conf',
  name = "Ambient Sleeping Pill",
  url = "https://ambientsleepingpill.com",
  genre = "Ambient",
  icy_id = 2,
  radio)

Once you save the .liq file, you can start it up:

liquidsoap ~/liquidsoap/station1.liq

At this point the station should be up and running.  If you visit your domain or IP in a browser, you should see the shoutcast summary page with the connected source, metadata, and a play button!  If not, check on both the liquidsoap logs and the shoutcast logs for some clues.

SSL

It probably doesn’t make a lot of sense to encrypt connections to the radio stream, and probably adds some overhead, but I wanted to stream my radio on my home page which has SSL (to take advantage of http/2 speed and because Google likes https sites).

One of Shoutcast’s many weaknesses compared to Icecast is lack of SSL support.  Still, it’s really easy to enable SSL using stunnel.

You’ll need an SSL certificate. You can issue a free certificate from Let’s Encrypt using certbot.

Install both:

sudo apt install stunnel certbot

Issue the certificate:

sudo certbot certonly --standalone

Configure stunnel:

sudo nano /etc/stunnel/stunnel.conf

In this example config, I am using the standard SSL port 443 and connecting to port 80. You can use a different port for SSL if you want. If you did not use port 80 for Shoutcast, be sure to change that to match what you did use.

Replace radio.example.com with the domain you entered during the certbot install. You can check the path is correct by listing the contents of the live directory (ls /etc/letsencrypt/live/).

[shoutcast]
accept=443
connect=80
cert = /etc/letsencrypt/live/radio.example.com/fullchain.pem
key = /etc/letsencrypt/live/radio.example.com/privkey.pem

Now fire up stunnel:

sudo stunnel4

Go ahead and test it by visiting https://radio.example.com. You should see a nice green lock or whatever in the URL bar.

Let’s Encrypt certificates expire every 90 days, so you should set up a cron job to renew them.

First, do a dry run of the renewal command to make sure it is working properly.

Because we are not running a web server, certbot has to spin up a temporary web server to handle the challenge-response authentication. (That’s what the --standalone option did during install.) If you used the standard port 443 for stunnel, it will block certbot from starting a web server on that port as well. To get around it, you need to stop stunnel, and restart it after the renewal. The pre-hook and post-hook options allow you to issue commands before and after renewal:

sudo certbot renew --pre-hook "killall stunnel4" --post-hook "stunnel4" --dry-run

If the dry run succeeds, add the command to crontab. Remove the --dry-run option and add the quiet option -q.

sudo crontab -e

In my case, I am setting it to run at 5 am on the first of every month:

0 5 1 * * certbot renew --pre-hook "killall stunnel4" --post-hook "stunnel4" -q

Multiple Stations

You can run additional stations by making additional .liq files and starting them up.  If you are encoding each station to multiple bitrates and/or codecs, I’d recommend only one station per CPU core.  Obviously CPUs vary, so be sure to monitor the system load and upgrade your VPS if needed.

You might want to make a little bash script to help you start everything up if you ever need to restart:

nano ~/start.sh

Fill in your config files:

sudo stunnel4
sudo sc/sc_serv daemon sc/shoutcast.conf
liquidsoap /liquidsoap/station1.liq
liquidsoap /liquidsoap/station2.liq
liquidsoap /liquidsoap/station3.liq

Make the script executable:

chmod u+x start.sh

Now you can run it like ./start.sh after a system reboot and everything will be up and running.

Maintenance

There shouldn’t be much maintenance to do, but you should definitely keep an eye on the system load and memory usage for a while.

Install htop:

sudo apt install htop

and run it:

htop

Here you can keep an eye on the CPU usage (you will see meters at the top, one for each core) and the memory.  If any of these is staying above 70% or so I would be worried that the system could crash.

The “Load average” shows 3 numbers.  The first is an average for the past 1 minute, the second is the past 5 minutes, and the third is 15.  The number corresponds to the number of CPU cores you have.  I have 4 cores, so if the average was 4, I’d be at 100% usage and should be concerned.  For reference, my 15-minute is always hovering around 1 so the system doesn’t have much stress running 3 stations with 2 bitrates each.

I like to use “Tree” view (F5 or just click on “Tree”).  If you ever need to stop one of the processes just select it and click “Kill” (F9).

Exit htop with q.

Those are just some hints, but read up on htop on your own.

Reloading Config Changes

If you ever change the config for Shoutcast, you can reload the config from the admin web interface—no restart required.

Unfortunately, Liquidsoap needs to be restarted if you change the .liq configs.  You can use htop to kill the correct Liquidsoap instance and then restart it.

2 Comments

  1. I am setting up a new shoutcast server on Ubuntu 18.04, and the copy of sc_trans I have will not run in that environment (through it runs just fine on my existing Ubuntu 14.04 server. So since I can’t use the very simple-to-set up sc_trans (I have used it 24 x 7 for 4 years….) I now have to try and use the much more complicated liquidsoap as my source.

    I have sc_serv running OK on my Ubuntu 18.04 box, and it appears that my liquidsoap install went ok
    I used your config as a guide, but I am just using a text playlist so I changed that, and I’m not using the TuneIn stuff, so I commented that out.

    HOWEVER when I start it up ~/liquidsoad/station1.liq I get the following error”
    At line 31 char 18 – line 32 char 0: the variable standard used here has not been previously defined
    I assume the error is referring to lines 31 & 32 in station1.liq, and NOT lines in my playlist.

    Can you quickly look over this .liq configuration and help me figure out what’s wrong? I looked at lines 31…32 and don’t see what the error means by “variable standard”

    run in the background
    set(“init.daemon”,true)

    # set the location for the pid file which the daemon creates
    set(“init.daemon.pidfile.path”,”~/liquidsoap/.pid”)

    # set a log file location
    # ( will be filled in automatically with the name of this file)
    set(“log.file.path”,”~/liquidsoap/logs/.log”)

    # TuneIn AIR API function
    # see http://tunein.com/broadcasters/api/
    # This handles sending live metatdata updates to tunein.com, if you use that
    # Replace with your partner id, partner key, and station id
    #def send_meta(m) =
    # partnerId = “?partnerId=xxxxxxx”
    # partnerKey = “&partnerKey=xxxxxxx”
    # id = “&id=s111111”
    # api_server = “http://air.radiotime.com/Playing.ashx”
    # title = “&title=” ^ url.encode(m[“title”])
    # artist = “&artist=” ^ url.encode(m[“artist”])
    # album = “&album=” ^ url.encode(m[“album”])
    # uri =( api_server ^ partnerId ^ partnerKey ^ id ^ artist ^ title ^ album )
    # ignore(http.get(uri))
    #end
    # end TuneIn code, remove if not using.

    # Main playlist
    music1 = playlist(“/home/noir/content/Playlists/noirlist1.txt”,mode=”sequential”)

    radio = standard
    # add crossfade
    #radio = crossfade(start_next=10.0,fade_out=0.5,fade_in=0.5,radio)
    # make the source “infallible” by adding silence if the source does fail
    radio = mksafe(radio)

    # Send Meta to TuneIn AIR API
    # also remove this line if not using
    #radio = on_metadata(send_meta, radio)

    # Stream it out
    # Mostly self-explanitory values
    # the password is what we set in shoutcast.conf (the “password” not the “adminpassword”)
    # the icy_id corresponds to the “stream_id” in shoutcast.conf
    output.shoutcast(%mp3(bitrate=48),
    host=”localhost”, port = 8000,
    password = ‘redacted’,
    name = “Audio Noir TEST”,
    url = “https://AudioNoir.com”,
    genre = “Old Time Radio”,
    icy_id = 1,
    radio)

    1. andrewklimek

      You haven’t defined the variable “standard” that’s on the line “radio = standard”
      It seems you’ve used “music1” instead, so change that to “radio = music1”

Leave a Reply

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