Disclaimer

Before we begin:

The information in this post is presented as-is for educational purposes only and comes WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. If anything breaks you get to keep all the pieces! I accept no responsibility whatsoever for what you do with this information.

That being said, let’s go on to the main event.

Background

So, the last few years I’ve been enjoying the sounds emitted from a Marantz NA-8005. Part of the fun involves listening to internet radio stations from all corners of the globe. When I bought it years ago, I knew the risk of being dependent on a cloud service (VTuner) for key functionality of a consumer product. I figured I could probably hack a self-hosted solution when the time came. That time has come, because as of lately VTuner started demanding a (seemingly reasonable) fee for its services. For me, that was a sign to look at the alternatives. Luckily for me someone else has already done the heavy lifting and I only had to string together the pieces of the puzzle.

You see, I’ve been running a Pi-hole for several years now. It keeps me, my family and our internet connection safe from a lot of things an average netizen should not be concerned with. The Raspberry Pi that’s been running my personal DNS filtering service still has some CPU cycles left to run YCast on the side.

A quick web-attack led me to this page, which suggests moving the Pi-hole web server to a different port (88) and running YCast on port 80. The fundamental problem with that approach is that Pi-hole needs to run on port 80 to serve dummy-content as a placeholder for ads. Effectively you’re crippling your ad-filter. Still, the post contained a great starting point for my adventure.

Basic strategies

To achieve having a HTTP server for Pi-hole and YCast running on the same hardware I figured there were 2 basic strategies to work from:

  • Configure a virtual network interface and bind YCast to a separate IP address. This page gave a nice starting point for that approach;
  • Alternatively, you can serve YCast behind the Pi-hole lighttp server, through mod_proxy or fastcgi. That way YCast can listen on 127.0.0.1 and a high port number, with access regulated by a proper HTTP server.

Because I already had a HTTP server running, I chose the latter strategy.

Starting point

My Pi-hole had been chugging away for a long time. It got its regular updates, but a dist-upgrade had’t happened in that period. To chase away the cobwebs from the trusty Raspberry Pi I decided on a fresh install of Buster. (Raspbian Jessie, as it turns out, will give you grief with Pillow dependencies.)

As a reminder for my future self, here are some notes on re-creating an ad-devouring Pi.

(Re-)install Raspbian

Getting Raspbian on a SD card is as simple as following the instruction manual. Get the image, flash it, re-size the partition with gnome-disk-utility, and boot it.

Set up SSH

Setting up ssh gave me a little bit of grief (because I forgot that it’s off by default). The manual will tell you that sudo raspi-config is all you need. Note that adding a file called ssh onto the boot partition will do the trick as well.

Install Pi-hole

Again, the manual is very comprehensive. Installation can be done with curl -sSL https://install.pi-hole.net | bash and a reboot. The password for the web interface can be set on the command line with sudo pihole -a -p.

Optional

Teleporter is a feature that you can find in the Pi-hole web interface that can help you migrate those carefully crafted white- and blacklists.

Install Ycast

YCast takes a little more care in setting up. First off, one of the dependencies (Pillow) needs a few libraries:

sudo apt-get install libtiff5
sudo apt-get install libopenjp2-7

After that you can install YCast through PIP:

sudo apt install python3-pip
sudo pip3 install ycast

Configure the service

Next, we’ll need a user to run the YCast service as:

sudo  useradd ycast

With that user we can add a stations.yaml that contains the entries for the ‘My Stations’ feature of YCast. A quick sudo mkdir /etc/ycast and sudo nano /etc/ycast/stations.yml will do the trick. For now add the content of the example, you can alter it to your tastes later. Make it accessible, e.g. with sudo chown ycast:ycast /etc/ycast/stations.yml.

Next, with some help from the example in the source, we can run YCast as a service on port 8010. sudo nano /etc/systemd/system/ycast.service and add the following content:

[Unit]
Description=YCast internet radio service
After=network.target

[Service]
Type=simple
User=ycast
Group=ycast
ExecStart=/usr/bin/python3 -m ycast -l 127.0.0.1 -p 8010 -c /etc/ycast/stations.yml

[Install]
WantedBy=multi-user.target

Enable the service:

sudo systemctl enable ycast.service
sudo systemctl start ycast.service

And check for any errors with systemctl status ycast.service.

Configure the webserver

At this point there are 2 more things to do: divert traffic from vtuner.com to our http server and have the server forward the request to YCast.

DNS entry

Diverting the traffic is as simple as sudo nano /etc/dnsmasq.d/vtuner.conf and mapping the vtuner domains that are applicable to your situation (e.g. radioyamaha.vtuner.com) to your Pi-hole’s IP address (192.168.x.y). For example:

address=/radiomarantz.vtuner.com/192.168.x.y
address=/vtuner.com/192.168.x.y

NOTE: You’ll need to change this example. Afterwards, restart the DNS service, e.g. with the Pi-hole web interface.

mod_proxy

Finally we’ll configure lighttpd. This background information will give you a good idea on the possibilities. As an exercise to the reader: build on the configuration below and have YCast (running on Flask) use FastCGI for an even more robust setup.

Enable mod_proxy with sudo lighttpd-enable-mod.

This will create a separate config-file that we’ll have to edit. sudo nano /etc/lighttpd/conf-enabled/10-proxy.conf and replace the content with the following configuration (or edit appropriately):

# /usr/share/doc/lighttpd/proxy.txt

server.modules   += ( "mod_proxy" )

## Balance algorithm, possible values are: "hash", "round-robin" or "fair" (default)
# proxy.balance     = "hash" 


## Redirect all queries to files ending with ".php" to 192.168.0.101:80 
#proxy.server     = ( ".php" =>
#                     ( 
#                       ( "host" => "192.168.0.101",
#                         "port" => 80
#                       )
#                     )
#                    )

## Redirect all connections on www.example.com to 10.0.0.1{0,1,2,3}
#$HTTP["host"] == "www.example.com" {
#  proxy.balance = "hash"
#  proxy.server  = ( "" => ( ( "host" => "10.0.0.10" ),
#                            ( "host" => "10.0.0.11" ),
#                            ( "host" => "10.0.0.12" ),
#                            ( "host" => "10.0.0.13" ) ) )
#}


#YCAST
$HTTP["host"] =~ "vtuner.com" {
        proxy.server = ( "" =>  ( (
                                "host" => "127.0.0.1",
                                "port" => "8010"
                                 ) ) )
        proxy.forwarded = (
                        "for"          => 1,
                        "proto"        => 1,
                        "host"        => 1,
                        #"by"          => 1,
                        #"remote_user" => 1
    )

}

After that it’s a mere restart of lighttpd with:

sudo service lighttpd force-reload
systemctl status lighttpd.service

And all should be set!

Postscript

First of all: I’m very happy with this setup. The Community Radio Browser index is pretty comprehensive. Anything that is not available there can be remedied by editing stations.yml, which is very straight-forward. It allows me to add any station I want in a self-determined category, without being dependent on a third party.

Bonus: my music streamer is far more responsive with this setup.

Getting all the components to work together was a bit of a puzzle, but in the end it proved do-able.

More tk