Saturday 4 May 2019

Part 2 - Streaming externally

This is Part 2. Part 1 is here.

In the first part a HD stream was created from the raspberry pi, turned into RTMP with ffmpeg, and then published and consumed through nginx. In this part I'll add an external host that can proxy this stream to the outside world. It prevents any connections to this stream to my home internet connection.

We need a couple of things:
  • An external host that you can ssh into with a public key from the raspberry pi.
  • An nginx instance running on your external ssh host
Next we need to create a couple of things which is what I'll cover below:
  • Script on the external host which will keep the SSH connection alive
  • Systemd unit that will restart the ssh tunnel if dropped
  • Nginx config to use the SSH tunnel to connect to the nginx instance running on the raspberry pi.
So why do it this way? There's one thing I didn't want - When no one is viewing the stream I do not want the stream to consume my home internet upload bandwidth. If I wasn't concerned with this  I'd just publish to an S3 bucket instead.

Keep Alive script

This short script will keep some random traffic flowing over the tty part of the SSH connection alive so that the port mapped connection stays up.

For this I'm using a script I've used in other places that just outputs a random set of words every few seconds to keep the connection up. If I don't do this the ssh connection will drop the connection after a timeout when  the terminal has had no traffic, if it does drop the connection will re-establish thanks to systemd.

This is the contents of ${HOME}/keepalive.sh on the external host:
DICT=/usr/share/dict/words
RANGE=$(wc -l ${DICT} | cut -d\  -f1)
while true; do
sleep 5
number=$(awk 'BEGIN{srand();print int(rand()*'${RANGE}')}') # or shuf, or jot, but awk is everywhere
let "number %= $RANGE"
sed ${number}'!d' $DICT
done

It's pretty simple, but the logic is:
  1. Get the number of words in the dictionary file on the local system
  2. Wait for 5 seconds
  3. Using awk, get a random number up to number of lines in the dictionary
  4. Display that word
  5. Go to step 2.

SSH Tunnel Systemd Unit

This one is also pretty simple, it belongs on the raspberry pi running the webcam. This service will:
  1. Wait for nginx to start
  2. Open a ssh connection to the external host
  3. Create a tunnel over that ssh connection from the local nginx server to a port on the external host that the external nginx host can proxy to.
  4. Start the keepalive.sh script above
You need to create a file called /etc/systemd/system/sshtunnel.service with this contents (remember to update the ssh line with your external user and host name):
[Unit]

Description=sshtunnel
After=nginx.service
After=systemd-user-sessions.service
After=rc-local.service
Before=getty.target

[Service]
ExecStart=/usr/bin/ssh externaluser@externalproxyhostname -R 8080:127.0.0.1:80 /home/dime/keepalive.sh
Type=simple
Restart=always
RestartSec=5
StartLimitIntervalSec=0
User=pi
Group=pi

[Install]
WantedBy=multi-user.target
Once the config is in place you need to enable it with:
systemctl enable sshtunnel

Once it starts it'll create that tunnel so the external host can proxy though to the raspberry pi. If the connection ever drops it'll restart, and if nginx ever restarts it'll make sure the tunnel is restarted also.

Nginx configuration for the external host

Assuming you might have an existing nginx setup externally, you'll need to add a section like this to your existing nginx conf:
server { # in any existing server config
  # Streaming webcam location
  location /window/ {
    proxy_pass http://127.0.0.1:8080/;
  }
}

And that's it. There is some improvement to be made around caching so when two or more viewers are watching the stream only one *.ts file is transferred, for now though keep it simple.

Starting

Once it's all in place you need to `systemctl restart nginx` on the raspberry pi webcam, this will:
- Restart nginx
- Restart the webcam stream
- Start the sshtunnel service

If all goes to plan you should now be able to access your webcam via your external proxy at
http://externalproxyhostname/webcam/

Next Time

For Part 3, some parameters will be adjusted in raspbivid and ffmpeg to allow us to store 24 hours of video.