Project One-Click: All-in-One Installations of Docker Extensions and CDVR Custom Channels

Now that there are a host of excellent Docker-based extensions for Channels DVR, @mjitkop and @bnhf thought it was about time we had an excellent way to install them! Specifically, an all-in-one "one-click" approach that would spin-up the container and do the Custom Channels setup as well.

OliveTin and Portainer were perfect for this project, and are prerequisites. OliveTin provides the user interface for the minimal set of values you need to provide and Portainer, with its API, allows for the creation of a stack that can be modified in the future if needed. A CDVR Custom Channels source is created with a custom starting channel number (if desired), and guide data is downloaded.

From that first click in OliveTin, to watching TV using your new source is typically a matter of a few minutes. No searching the forum for the bits and pieces need to get it running. :slight_smile:

The following extensions are currently supported:

  • ADBTuner
  • ah4c
  • cc4c
  • EPlusTV
  • Filebot
  • FrndlyTV-for-Channels
  • MediaInfo
  • MLB.tv-for-Channels
  • Organizr
  • Plex-for-Channels
  • Pluto-for-Channels
  • Samsung-TVPlus-for-Channels
  • Stirr-for-Channels
  • Tubi-for-Channels
  • VLC-Bridge-Fubo
  • VLC-Bridge-PBS
  • Watchtower

Here are few images of the process for FrndlyTV:

OliveTin Actions menu:

FrndlyTV Action button:

OliveTin "stdout" showing success with the stack and custom channels source creation:

The new frndlytv-for-channels Docker container up-and-running:

The Channels DVR Custom Channels setup (including the special two source setup for FrndlyTV) done for you, with guide data on its way:

And, the guide populated:

Watching FrndlyTV:

Elapsed time -- maybe two minutes!

To get this going for yourself, you'll need to install Portainer and OliveTin. In addition, you'll need to create a Portainer API Token:

OliveTin installation described here:

Portainer API Token is just a couple of steps. First go to My account:

Next create a token with the name OliveTin, and copy it -- this is the only time you'll see it:

Your OliveTin Portainer-Stack requires a couple of new environment variables, PORTAINER_TOKEN and PORTAINER_HOST:

version: '3.9'
services:
  olivetin: # This docker-compose requires little or no editing. Set the variables in the Environment section of Portainer-Stacks.
    # GitHub home for this project: https://github.com/bnhf/OliveTin
    # Docker container home for this project with setup instructions: https://hub.docker.com/repository/docker/bnhf/olivetin
    image: bnhf/olivetin:${TAG} # Add the tag like latest or test to the environment variables below
    container_name: olivetin
    hostname: olivetin
    dns_search: ${DOMAIN} # For Tailscale users using Magic DNS, add your Tailnet (tailxxxxx.ts.net) to use hostnames for remote nodes, otherwise use your local domain name
    ports:
      - ${HOST_PORT}:1337
    environment:
      - CHANNELS_DVR=${CHANNELS_DVR_HOST}:${CHANNELS_DVR_PORT} # Add your Channels DVR server in the form CHANNELS_DVR_HOST=<hostname or ip> and CHANNELS_DVR_PORT=<port>
      - CHANNELS_DVR_ALTERNATES=${CHANNELS_DVR_ALTERNATES} # Space separated list of alternate Channels DVR servers to choose from in the form hostname:port or ip:port
      - CHANNELS_CLIENTS=${CHANNELS_CLIENTS} # Space separated list of Channels DVR clients you'd like notifications sent to in the form hostname or IP
      - UPDATE_YAMLS=${UPDATE_YAMLS} # Set this to true to update config.yaml
      - UPDATE_SCRIPTS=${UPDATE_SCRIPTS} # Set this to true to update all included scripts
      - TZ=${TZ} # Add your local timezone in standard linux format. E.G. US/Eastern, US/Central, US/Mountain, US/Pacific, etc
      - PORTAINER_TOKEN=${PORTAINER_TOKEN} # Generate via <username> dropdown (upper right of WebUI), "My account", API tokens
      - PORTAINER_HOST=${PORTAINER_HOST} # Hostname or IP of the Docker host you're running Portainer on.
    volumes:
      - ${HOST_DIR}/olivetin:/config # Add the parent directory on your Docker you'd like to use
      - ${DVR_SHARE}:/mnt/${CHANNELS_DVR_HOST}-${CHANNELS_DVR_PORT} # This can either be a Docker volume or a host directory that's connected via Samba or NFS to your Channels DVR network share
      #- ${DVR2_SHARE}:/mnt/remote-server-8089 # Note that these volume mounts should always be to /mnt/hostname-port or /mnt/ip-port (dash rather than a colon between)
    restart: unless-stopped

  static-file-server:
    image: halverneus/static-file-server:latest
    container_name: static-file-server
    dns_search: ${DOMAIN}
    ports:
      - ${HOST_SFS_PORT}:8080
    environment:
      - FOLDER=${FOLDER}
    volumes:
      - ${HOST_DIR}/olivetin/data:${FOLDER}
    restart: unless-stopped

#volumes: # use this section if you've setup a docker volume named channels-dvr, with CIFS or NFS, to bind to /mnt/dvr inside the container. Set ${HOST_DIR} to channels-dvr (HOST_DIR=channels_dvr) in that example
  #channels-dvr:
    #external: true

Sample env vars:

TAG=latest
DOMAIN=tailxxxxx.ts.net
HOST_PORT=1337
CHANNELS_DVR_HOST=local-server
CHANNELS_DVR_PORT=8089
CHANNELS_DVR_ALTERNATES=another-server:8089
CHANNELS_CLIENTS=appletv4k-den firestick-bedroom
UPDATE_YAMLS=true
UPDATE_SCRIPTS=true
TZ=US/Mountain
HOST_DIR=/data
DVR_SHARE=/mnt/dvr
HOST_SFS_PORT=8080
FOLDER=/web
PORTAINER_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PORTAINER_HOST=docker-host
6 Likes

Awesome work @bnhf and @mjitkop !

One question. Does it create the stacks using network_mode: "bridge" or network_mode: "host"
I use bridge for everything except for Channels DVR Servers, which now require network_mode: "host"

1 Like

Bridge mode is the Docker default, and what we're using. Host mode is not typically the recommended approach as it reduces the isolation benefit of containerization. Basically, one doesn't want to use host mode unless there's a compelling reason to do so.

Having said that, I can't say we actually thought of having a host mode option, since all of the these extensions work in bridge mode -- and I don't think Docker Desktop for Windows even supports host mode.

Thanks, that's what I thought.

It doesn't, and Docker Desktop for Mac doesn't either.

2 Likes

@bnhf deserves 99% of the credits.

I contacted him originally with the idea and was requesting his help with UI creation in OliveTin.
My plan was to write some of the backend code but I couldn't find enough time to work on it.
In the end, he programmed everything himself with very little help from me.

So kudos to him for doing all the work!
This looks great! :+1:

2 Likes

Another good example: Say you don't have an MLB.tv subscription, but you'd still like to be able to have the free game of the day available. With Project One-Click it's easy days:

Change a variable or two, and put it your MLB.tv credentials -- no subscription required:

And minute or two later, tomorrow's game is available to watch or record:

3 Likes

This is absolutely ridiculous! The ability to potentially have all of these items "just work" due to your hard work in wrangling the config items, etc, is insane. Well done! I'm going to give this a whirl right now!

One question in my head - how will this setup manage updates to the various "child" extensions? And, if it's agnostic to them, what would a best practice be for maintenance?

1 Like

If I'm understanding your question correctly, Watchtower is one of the extensions you can install with Project One-Click.

We're configuring Watchtower in "run once" mode, so whenever you want to check if there are updates to containers, tick the box next to Watchtower in Portainer and click start. Any running container will get updated.

Watchtower sits in "exited" mode in your Portainer-Containers list ready to run.

Thanks!

One oddity - not seeing an "account" item anywhere on my portainer installation (Docker on Mac). I probably borked the installation on docker, but wondering if this differs on Mac?

Edit - i think it's Docker Desktop - my screen looks similar to this old shot:

So this is using the Portainer Docker Desktop extension? I haven't used that before, but I should be able to install it on my laptop, in place of the WebUI version I normally use.

Hopefully token creation is just somewhere else in the UI.

I think it's lack of user: I'm going with this:

I was right. For some reason:

  1. the users menu isn't available on the extension, by default.
  2. The default password isn't what i see all over the place

I got a Users menu by doing this, and then navigating to the web UI on the https port:

# stop the existing Portainer container
docker container stop portainer

# run the helper using the same bind-mount/volume for the data volume
docker run --rm -v portainer_data:/data portainer/helper-reset-password
2020/06/04 00:13:58 Password succesfully updated for user: admin
2020/06/04 00:13:58 Use the following password to login: itwillgiveyouonehere

# restart portainer and use the password above to login
docker container start portainer

Have a token - now on to figuring out mounts...

1 Like

I'm wondering if the extension even has a WebUI. Are you able to access Portainer by URL from another system on your LAN?

1 Like

That's great. Is the WebUI available on http:// as well? The current version of Project One-Click uses the http port, but it wouldn't be difficult to add https as well.

Looks like only https, at least at first blush

I'll work on https support. In the meantime, if you don't mind tweaking a script, portainerstack.sh should work with these two lines modded:

Current script:

#!/bin/bash

set -x

stackName="$1"
portainerHost="$PORTAINER_HOST"
portainerURL="http://$portainerHost:9000/api/stacks?type=2&method=string&endpointId=2"
portainerToken="$PORTAINER_TOKEN"
cp /config/$stackName.yaml /tmp
stackFile="/tmp/$stackName.yaml"
envFile="/tmp/$stackName.env"

dockerVolume=$(grep 'DVR_SHARE=' $envFile | grep -v '/' | awk -F'=' '{print $2}')
volumeExternal=$(grep 'VOL_EXTERNAL=' $envFile | grep -v '#' | awk -F'=' '{print $2}')
volumeName=$(grep 'VOL_NAME=' $envFile | grep -v '#' | awk -F'=' '{print $2}')

if [[ -n $dockerVolume ]]; then
  sed -i 's/#volumes:/volumes:/' $stackFile
  sed -i 's/#channels-dvr:/'$dockerVolume':/' $stackFile
  [[ -n $volumeExternal ]] && sed -i 's/#external:/external:/' $stackFile
  [[ -n $volumeName ]] && sed -i 's/#name:/name:/' $stackFile
fi

stackContent=$(awk '{printf "%s\\n", $0}' "$stackFile" | sed 's/"/\\"/g')
stackEnvVars="["

while IFS='=' read -r key value
do
  stackEnvVars="${stackEnvVars}{\"name\": \"$key\", \"value\": \"$value\"},"
done < "$envFile"

stackEnvVars="${stackEnvVars%,}]"

stackJSON=$(cat <<EOF
{
  "Name": "$stackName",
  "SwarmID": "",
  "StackFileContent": "$stackContent",
  "Env": $stackEnvVars
}
EOF
)

curl -X POST -H "Content-Type: application/json" -H "X-API-Key: ${portainerToken}" -d "$stackJSON" "$portainerURL"

Lines with changes needed:

portainerURL="https://$portainerHost:9443/api/stacks?type=2&method=string&endpointId=2"
curl -k -X POST -H "Content-Type: application/json" -H "X-API-Key: ${portainerToken}" -d "$stackJSON" "$portainerURL"

Untested, but should work...

EDIT: The script will be in the directory you have bound to /config -- typically, /data/olivetin on your host system.

I'll try that, once i figure out bindings. Honestly pulling my hair out a bit on them...

1 Like

Are your Channels DVR files on this same Mac, or elsewhere on your LAN?

On Mac

Linux and Windows for me, but I would think:

HOST_DIR=/data
DVR_SHARE=<absolute path to your CDVR files>

And then change this line in the docker-compose:

      - ${DVR_SHARE}:/mnt/<hostname or ip of your Mac>-8089 # This can either be a Docker volume or a host directory that's connected via Samba or NFS to your Channels DVR network share
1 Like