Installing CloudTube, a YouTube frontend

CloudTube is an alternative YouTube web frontend developed by cadence. This is in my opinion much welcomed as the experience of browsing YouTube was heavily degraded recently (the lighter classic layout was removed in March 2020, and Google keep adding popups nagging you to log in and accept tracking cookies).

CloudTube is a better alternative to Invidious, a similar and older project, for the following reasons:

  • decoupled frontend CloudTube and backend NewLeaf (reimplementation of Invidious API ; originally named Second)
  • respectively in nodejs and python, making them easier to attract external contributors (Invidious being developed in the Crystal programming language, people are often seen blocked by the language itself when they want to help the project) and
  • simple to deploy, as detailed below
  • the backend “NewLeaf” leverages the youtube-dlc library to obtain videos information instead of rolling its own implementation (that would then need to be fixed manually each time YouTube JS changes instead of waiting for the broader OSS community to implement a fix)
  • better polished UX in general

Nevertheless, keep in mind that it’s a young project and as of today in December 2020, this is not a full replacement for Invidious (or YouTube interface), at least for the features I’m interested in. Notably, comments are not displayed under videos, deployment documentation is lacking, autoplay with URL parameter is not implemented, video embeds are not working, some shortcuts for playback conflict with browser navigation.

Even without documentation, and because it doesn’t use anything fancy (python, nodejs+sqlite), I could deploy it on my server in less than 1 hour. Here’s how I’ve deployed the service, hopefully it will serve as an example for the official documentation.

Prerequisites

  1. a server you can access via the command line (ssh if remote), with sudo rights. Here, a Debian 9 armv7 host.
  2. an existing nginx deployment (nginx install won’t be detailed here)
  3. nodejs and python3 installed (I usually download manually nodejs to the homedir and add its bin dir to the user $PATH)
  4. systemd is assumed as init

Preparing the install with a limiter user

We will create a limited user so not everything runs as root or my usual account which is in the sudoers.

sudo adduser cloudtube

No need to specify a lot of things, Ctrl-C as soon as it starts asking boring questions (the user will still be created).

Installing NewLeaf and CloudTube on the host

We will then sudo to this account

sudo -u cloudtube -i

… and retrieve the source code of NewLeaf and CloudTube in cloudtube homedir

git clone https://git.sr.ht/~cadence/NewLeaf
git clone https://git.sr.ht/~cadence/cloudtube

We are then going to install CloudTube and NewLeaf.

CloudTube

As cloudtube user

cd ~/cloudtube
npm install # takes a while on an arm host, as it needs to compile sqlite. You can continue to the NewLeaf section in the meantime

Add config

cd ~/cloudtube
cat <<EOF > config/config.js
module.exports = {
        /*
          Override options from `utils/constants.js`.
        */

        user_settings: {
                instance: {
                        type: "string",
                        default: "https://yt.example.com"
                }
        }
}
EOF

You can then check that CloudTube frontend starts up with npm start and check curl localhost:10412

Add a systemd service so CloudTube (re)starts automatically. As an account with sudo rights (not cloudtube)

sudo cat <<EOF | sudo tee /etc/systemd/system/cloudtube.service >/dev/null
[Unit]
Description=CloudTube
After=network.target

[Service]
Type=simple
ExecStart=/home/cloudtube/node-v12.18.3-linux-armv7l/bin/node /home/cloudtube/cloudtube/server.js
WorkingDirectory=/home/cloudtube/cloudtube
# Restart timing
Restart=always
RestartSec=60

# Disable logs
StandardOutput=null
StandardError=null
SyslogIdentifier=cloudtube

# User & group to run service as
User=cloudtube
Group=cloudtube

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl start cloudtube.service

Check that CloudTube runs with sudo systemctl status cloudtube.service and with the previous curl command.

NewLeaf

Create a virtualenv so NewLeaf dependencies are installed only for this application and not globally

Still as cloudtube user

cd ~
python3 -m venv newleaf-venv # install apt dependencies if needed
. newleaf-venv/bin/activate
pip3 install -r NewLeaf/requirements.txt

You can then check that NewLeaf starts up without issues with python3 index.py, and check curl localhost:3000 (you will see a CherryPy stacktrace as / is not an implemented route).

Add config

cd ~/NewLeaf
cat <<EOF > configuration.py
# ==============================
#  You MUST set these settings.
# ==============================

# A URL that this site can be accessed on. Do not include a trailing slash.
website_origin = "https://yt.example.com"
EOF

Add a systemd service so NewLeaf (re)starts automatically. As an account with sudo rights (not cloudtube)

sudo cat <<EOF | sudo tee /etc/systemd/system/newleaf.service >/dev/null
[Unit]
Description=NewLeaf (CloudTube backend)
After=network.target

[Service]
Type=simple
ExecStart=/home/cloudtube/newleaf-venv/bin/python3 /home/cloudtube/NewLeaf/index.py
WorkingDirectory=/home/cloudtube/NewLeaf
# Restart timing
Restart=always
RestartSec=60

# Disable logs
StandardOutput=null
StandardError=null
SyslogIdentifier=newleaf

# User & group to run service as
User=cloudtube
Group=cloudtube

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl start newleaf.service

Check that NewLeaf runs with sudo systemctl status newleaf.service and with the previous curl command.

nginx reverse proxy

We previously added a CNAME to the subdomain we want cloudtube to be accessible from, yt.example.com. We will be hosting both CloudTube frontend and NewLeaf HTTP API on the same domain with some nginx magic.

This is an older nginx configuration back form when certbot was only supporting auto-configuring Apache, today you should use certbot nginx plugin.

Create nginx config and then enable it. As an account with sudo rights (not cloudtube)

cat <<EOF | sudo tee /etc/nginx/sites-available/yt.example.com >/dev/null
server {                                                                                                                                                                                                                                                                       
    listen   80;                                                                                                                                                                                                                                                               
    server_name yt.example.com;                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                               
    root     /var/www/yt.example.com/public_html/;                                                                                                                                                                                                                             

    location /.well-known/acme-challenge {
        default_type "text/plain";
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name yt.example.com;

    access_log /var/www/yt.example.com/logs/access.log;
    error_log /var/www/yt.example.com/logs/error.log;

    ssl_certificate     /etc/letsencrypt/live/yt.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yt.example.com/privkey.pem;

    include /etc/nginx/ssl_params;

    location / {
        include /etc/nginx/security-headers;
        add_header X-Robots-Tag "noindex, nofollow";
        more_set_headers Content-Security-Policy "";
        proxy_pass http://127.0.0.1:10412;
    }

    location ~ "^/embed/([A-Za-z0-9_\-]{11})(\?.+)?$" { # handle Privacy Redirect /embed/$ID links
        rewrite "^/embed/([A-Za-z0-9_\-]{11})(\?.+)?$" /watch?v=$1$2 redirect;
    }
    
    location /api/ {
        include /etc/nginx/security-headers;
        add_header X-Robots-Tag "noindex, nofollow";
        more_set_headers Content-Security-Policy "";
        proxy_pass http://127.0.0.1:3000;
    }
}
EOF
cd /etc/nginx/sites-enabled
sudo ln -s ../sites-available/yt.example.com
sudo nginx -t && sudo nginx -s reload

You have to comment out the SSL part the 1st time though (certificates are not there yet), and then enable certbot with sudo certbot certonly --webroot -w /var/www/yt.example.com/public_html/ -d yt.example.com, and then enable the SSL part and reload nginx.

You should now have a running instance on https://yt.example.com/, enjoy!

Updates

Updates should be as simple as the following commands with the cloudtube user

cd ~/NewLeaf
git pull
# update dependencies?
. ~/newleaf-venv/bin/activate
pip3 install -U -r requirements.txt

cd ~/cloudtube
git pull
npm install

And then restarting both services with a user with sudoers rights (not cloudtube)

sudo systemctl restart cloudtube.service
sudo systemctl restart newleaf.service

Troubleshooting

Nothing specific to CloudTube really, check out logs with sudo journalctl -u cloudtube.service -f, sudo journalctl -u newleaf.service -f, run the service interactively like before, etc.

 Share!