This page looks best with JavaScript enabled

Access GDrive KODI via HTTP

 ·  🎃 kr0m

We have previously explained how to access GDrive content through the KODI addon and how to import content via FTP . The problem with accessing 4K content through the KODI addon is that it is very slow, causing problems such as momentary freezes or unwanted delays. Combining PlexDrive and exporting content via FTP works reasonably well, but exporting via HTTP will improve performance. Additionally, if the server is NATed, it will be easier to use it from the outside since HTTP uses a single port.

In this article, we will use PlexDrive and Nginx to serve GDrive content to a KODI installed on AndroidTV.

The article consists of several steps:


PlexDrive:

We must keep in mind that access to GDrive from PlexDrive is read-only:

Please note that plexdrive doesn't currently support writes (adding new files or modifications), it only supports reading existing files and deletion.

In my case, I will use a jail under IOCage from which I will import GDrive content and serve it via FTP.

We mount the file system via FUSE in the parent and pass it to the jail so that we do not have to modify special permissions in it.

We install the necessary software:

pkg install fusefs-lkl

We load the fusefs module:

kldload fusefs

Now there should be a device called fuse:

ls -la /dev/fuse

crw-rw-rw-  1 root  operator  0xe3 Nov 30 23:28 /dev/fuse

We configure the OS to load the module automatically at each boot:

vi /etc/rc.conf

kld_list="fusefs ..."

We find out the drive-id by copying it from the browser URL shown when we access GDrive. In my case, the URL looks like this:

https://drive.google.com/drive/folders/0ANN6pzUlNUgXUk9PVA

The drive-id is: 0ANN6pzUlNUgXUk9PVA

We download PlexDrive, which we can see the available versions at this github link

We create the directory where we will mount the GDrive unit:

mkdir /mnt/gdrive

We manually start PlexDrive:

/root/plexdrive-freebsd-amd64 mount -o allow_other -v 3 –drive-id=0ANN6pzUlNUgXUk9PVA /mnt/gdrive

1. Please go to https://console.developers.google.com/
2. Create a new project
3. Go to library and activate the Google Drive API
4. Go to credentials and create an OAuth client ID
5. Set the application type to 'other'
6. Specify some name and click create
7. Enter your generated client ID: 

When we access the shown URL, we will see the following interface where we must click on the button on the right “CREATE PROJECT”:

We indicate a name and click on create:

We already have the project created:

We activate the Google Drive API in the project, for this we access “APIs & Services” -> Library:

We search for “Google Drive API” and click on it:

We click on enable:

We will see the following window from which we must create the access credentials:

We click on “CREATE CREDENTIALS”:

We select “OAuth client ID”:

We will see the following window where we must click on “CONFIGURE CONSENT SCREEN”:

In User Type, we indicate: External and click on “CREATE”:

We fill in the mandatory form data and click on “SAVE AND CONTINUE”:


We don’t need any special permission, so we click on “SAVE AND CONTINUE”:

We add a user, click on “ADD USER”:

We indicate the user and click on “ADD”:

It should appear on the list, click on “SAVE AND CONTINUE”:

A summary will appear, click on “BACK TO DASHBOARD”:


It will take us to the “OAuth Consent Screen”, from here we access the “Credentials” section:

Click on “CREATE CREDENTIALS” again:

Select “OAuth client ID”:

In the application type, select “Desktop App”, give it a name and click on “CREATE”:

It will show us the following information:

We can see the OAuth client:

We continue with the PlexDrive console:

1. Please go to https://console.developers.google.com/
2. Create a new project
3. Go to library and activate the Google Drive API
4. Go to credentials and create an OAuth client ID
5. Set the application type to 'other'
6. Specify some name and click create
7. Enter your generated client ID: XXXXXXXXXXXXXXXXXXXXXXX
8. Enter your generated client secret: YYYYYYYYYYYYYYYYYYY
Go to the following link in your browser https://accounts.google.com/o/oauth2/auth?access_type=offline&client_id=ZZZZZZZZZZZZ-270hoqea0pinv0nc3sbpfkuja68alask.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&state=state-token

We access the URL indicated by PlexDrive using the previously authorized user:

The following message will appear where we click on continue:

We click on “Continue”:

It will show us a code:

We paste that code into the console:

Paste the authorization code: XXXXXXXXXXXXXXXXXXXX

We press Ctrl+C and demonize the mounting of the GDrive unit:

vi /usr/local/etc/rc.d/gdrive

#! /bin/sh
#
# $FreeBSD$
#

# PROVIDE: gdrive
# REQUIRE: DAEMON
# KEYWORD: shutdown 

. /etc/rc.subr

name="gdrive"
rcvar="${name}_enable"
extra_commands="status"

start_cmd="${name}_start"
stop_cmd="${name}_stop"
status_cmd="${name}_status"

gdrive_start(){
    echo "Clearing cache files: ${name}"
    rm /root/.plexdrive/cache.bolt
    echo "Starting service: ${name}"
    /usr/sbin/daemon -S -p /var/run/${name}.pid -T gdrive -u root /root/plexdrive-freebsd-amd64 mount -o allow_other -v 3 --drive-id=0ANN6pzUlNUgXUk9PVA /mnt/gdrive
}

gdrive_stop(){
    if [ -f /var/run/${name}.pid ]; then
        echo "Stopping service: ${name}"
        kill -s INT $(cat /var/run/${name}.pid)
        sleep 3
    else
        echo "It appears ${name} is not running."
    fi
}

gdrive_status(){
    if [ -f /var/run/${name}.pid ]; then
        echo "${name} running with PID: $(cat /var/run/${name}.pid)"
    else
        echo "It appears ${name} is not running."
    fi
}


load_rc_config ${name}
run_rc_command "$1"

We assign the necessary permissions:

chmod 555 /usr/local/etc/rc.d/gdrive
chown root:wheel /usr/local/etc/rc.d/gdrive

We enable the service and start it:

sysrc gdrive_enable="yes"
service gdrive start

Now we will import the /mnt/gdrive directory from the parent to the jail.

We create the directory in the jail:

iocage console GDrive
mkdir /mnt/gdrive
exit

We import the directory using the IOCage fstab command:

iocage fstab -a GDrive “/mnt/gdrive /mnt/gdrive nullfs rw 0 0”

Successfully added mount to GDrive's fstab

Nginx:

Now we access the jail and mount the server. Access from the outside will be via HTTPS since we will require authentication to access the content and we do not want these credentials to be compromised. However, it would also be convenient to use HTTPS from the LAN, but for this we would have to use the public domain name from the LAN computers, which causes problems due to Hairpinning . To solve this, there are three options:

  • Mount an internal DNS server with RPZ and configure internal clients to use that DNS.
  • Modify the /etc/hosts file on clients so that the domain resolves to the LAN IP, but in my case it is not possible because it is an AndroidTV.
  • Use HTTP (the option I chose).

We install Nginx and Socat since it is a dependency of ACME, the tool with which we will issue the HTTPS certificate:

pkg install nginx socat

We install ACME:

We issue the certificate:

/root/.acme.sh/acme.sh –issue -d gdrive.alfaexploit.com -w /usr/local/www/nginx –renew-hook ‘service nginx restart’

The Nginx configuration is vanilla but with an additional include:

egrep -v ‘^($|[[:space:]]*#|;)’ /usr/local/etc/nginx/nginx.conf

worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       gdrive.alfaexploit.conf;
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name gdrive.alfaexploit.com;
        location / {
            root   /usr/local/www/nginx;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/local/www/nginx-dist;
        }
    }
}

Our specific configuration:

vi /usr/local/etc/nginx/gdrive.alfaexploit.conf

server {
    listen 443 ssl default_server proxy_protocol;
    server_name gdrive.alfaexploit.com;

    set_real_ip_from 192.168.69.11;
    real_ip_header proxy_protocol;

    root   /mnt/gdrive;

    location / {
	autoindex on;
	auth_basic "Login Required";
	auth_basic_user_file /usr/local/etc/nginx/.htpasswd;
    }

    ssl_certificate "/root/.acme.sh/gdrive.alfaexploit.com/fullchain.cer";
    ssl_certificate_key "/root/.acme.sh/gdrive.alfaexploit.com/gdrive.alfaexploit.com.key";
}

# Direct IoT devices wich cant change /etc/hosts and evade hairpinning problem
server {
    listen       80 default_server;
    server_name  _;

    root   /mnt/gdrive;

    location / {
        autoindex on;
        auth_basic "Login Required";
        auth_basic_user_file /usr/local/etc/nginx/.htpasswd;
    }
}

# LetsEncrypt cert renew
server {
    listen       81 default_server;
    server_name  _;

    root   /usr/local/www/nginx;
}

We have Nginx clear on port 80/81, the first one is for IoT devices on the LAN such as TVs that cannot change the /etc/hosts file and therefore hairpinning causes problems, they access directly to 80. On the other hand, if we leave it like this, LetsEncrypt will not be able to renew the certificate since the docroot of port 80 is RO, in HaProxy we will send LetsEncrypt traffic to port 81 which is associated with a RW docroot.

We generate the credentials file:

openssl passwd -apr1

The final result should be very similar to this:

cat /usr/local/etc/nginx/.htpasswd

kr0m:$apr1$gypxo60s$6vKdmpNM3J8gJ7pdcLkeg/

We restart the service:

service nginx restart


HaProxy:

In my case, the traffic is balanced by a HaProxy where the traffic will be sent by HTTP in case of ACME renewing the certificate or by HTTPS otherwise, when it is by HTTPS, ProxyProtocol will be used to assign the source IP of the requests:

cat /usr/local/etc/haproxy.conf
frontend HTTP
    bind :80
    option forwardfor

    # Allow http access only for LetsEncrypt:
    acl letsencrypt path_beg /.well-known/acme-challenge/
    http-request redirect scheme https unless letsencrypt

    acl gdrive hdr(host) -i gdrive.alfaexploit.com
    http-request deny if !gdrive
    use_backend gdrive if gdrive

frontend HTTP-SSL
    bind :443
    mode tcp

    acl gdrive_ssl req.ssl_sni -i gdrive.alfaexploit.com

    tcp-request inspect-delay 2s
    tcp-request content reject if !gdrive_ssl
    use_backend gdrive_ssl if gdrive_ssl

# GDrive is a special case:
# Nginx:80 -> Direct IoT devices wich cant change /etc/hosts and evade hairpinning problem docroot: /mnt/gdrive RO
# Nginx:81 -> Letsencrypt docroot: /usr/local/www/nginx RW
backend gdrive
    server gdrive 192.168.69.14:81 check

backend gdrive_ssl
    mode tcp
    option ssl-hello-chk
    server gdrive 192.168.69.14:443 check sni req.ssl_sni send-proxy-v2

KODI:

Now it’s time to add the source in KODI, we access the configuration parameters:

Contents:

Videos:

Add videos:

Search:

Add network site:

We add our HTTP or HTTPS server if we access from outside the LAN, if we access from inside we will put the gdrive.alfaexploit.com domain name, if we do it directly from the LAN we will put the Nginx server IP address, in all cases we must indicate the credentials defined in the htaccess:

Now it will appear in the resource list:

We browse the HTTP server and add the desired directory:

We click OK:

We configure how we want to “scrape” the directory and click OK:

From here we can already see the movies in the corresponding section of KODI.


Expired Token:

Google generates tokens for unpublished apps in “Testing” mode:

  - A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of "Testing" is issued a refresh token expiring in 7 days.

This causes the following error in PlexDrive every 7 days:

Response: {
  "error": "invalid_grant",
  "error_description": "Token has been expired or revoked."
}

Publishing the app would make this limitation disappear, but to publish an app many additional steps are required such as configuring DNS with specific Google entries and others.

A simple but manual way to solve it is to enter every week and regenerate the user’s token, for this we access the credentials section of the project:
https://console.developers.google.com/apis/credentials

We edit the previously created client ID:

Click on “RESET SECRET”:

Confirm that you want to reset the secret:

Now we need to reset PlexDrive from scratch. In my case, I first stop the jail that serves the content:

iocage stop GDrive

Stop the service:

service gdrive stop

Delete the PlexDrive configuration:

rm -rf /root/.plexdrive/

Manually start PlexDrive where it will ask for access data again:

/root/plexdrive-freebsd-amd64 mount -o allow_other -v 3 –drive-id=0ANN6pzUlNUgXUk9PVA /mnt/gdrive

1. Please go to https://console.developers.google.com/
2. Create a new project
3. Go to library and activate the Google Drive API
4. Go to credentials and create an OAuth client ID
5. Set the application type to 'other'
6. Specify some name and click create
7. Enter your generated client ID: XXXXXXXXXXXXXXXXXXXXX
8. Enter your generated client secret: YYYYYYYYYYYYYYYYYYYY

Go to the following link in your browser https://accounts.google.com/o/oauth2/auth?access_type=offline&client_id=ZZZZZZZZZZZZ-270hoqea0pinv0nc3sbpfkuja68alask.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&state=state-token
Paste the authorization code: 4/1AX4XfWgwVi61FfxtHN0WpBUh_b3UvGhXfWscjb_B-oNQyl4zVniUtdqbQQg

When it has connected correctly, stop it:

Ctrl+C

Start the service:

service gdrive start

Finally, start the jail:

iocage start GDrive

If you liked the article, you can treat me to a RedBull here