xbmc / chorus2

Official Kodi Web Interface
GNU General Public License v2.0
333 stars 116 forks source link

Document alternative methods of accessing Chorus (Reverse proxy, port forwarding, via addons path, etc) #133

Open lozbrown opened 7 years ago

lozbrown commented 7 years ago

Whilst there are configuration to "Enable Reverse Proxy Support" because of the use of web sockets standard configurations do not appear to work for this.

Would it be possible to update the readme or wiki for this project to include suggested configurations for common server such as nginx and apache.

I seem to remember some versions of apache do not support websocket proxy.

This site becomes much more useful when available outside the home, particularly if it can be access securely over SSL which could be achieved with the proxy

lozbrown commented 7 years ago

For example I found the following is close but does not get the streaming working, Something needs to be corrected with the vfs path. Furthermore image paths could probably be redirected properly by someone who knows how to write rewrite rules.

         location /kodi {
                rewrite           ^/kodi$      https://home.example.com/kodi/ permanent;
                rewrite           ^/kodi/(.*)  /$1  break;
                proxy_redirect  http://localhost:5555   /kodi/;
                proxy_set_header Host $http_host;
                proxy_redirect off;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Scheme $scheme;
                proxy_pass http://localhost:5555;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
       }

        location /image
            {
                proxy_pass          http://localhost:5555;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
            }

         location /vfs
            {
                proxy_pass          http://localhost:5555;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
            }

        location /jsonrpc
            {
                proxy_pass          http://localhost:5555;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
            }
tommyknows commented 7 years ago

I'd be interested in that too! Trying the same with Nginx - so that it is possibly to use my HTTPS-Webserver from outside the LAN.

jez500 commented 7 years ago

If anyone wants to submit some nice documentation on how to do this I will gladly add it to the help section in Chorus. Ideally configuration settings for Nginx, Apache & IIS

Rouzax commented 7 years ago

Hi @jez500, I also created a ticket with Kodi and they say that it is something you need to add to the web service.

We actually store the HTTP (or any other paths) we got the image from in case we want to refresh it at some point. To make it accessible we add the "image://" VFS protocol in front of it to know that it's a cached image. Webinterfaces and other remotes that want to access these images over the webserver need to URL-encode the whole image:// path which already encodes all the remaining slashes etc. So Chorus2 needs to add that URL encoding step to be fully compatible with how it should be.

lozbrown commented 7 years ago

I'm pretty close for nginx but don't really understand the rewrite rules so have asked the question in a couple of places that will hopefully yield answers and I'll get back to you if i get it working properly.

http://stackoverflow.com/questions/40704417/nginx-reverse-proxy-config-for-kodi-rewrite-rule

https://forum.nginx.org/read.php?11,271074

lozbrown commented 7 years ago

I've eventually worked out that requests passed to nginx as home.example.com/kodi/vfs/%2fmedia%2fVirtual%2fVideos%2fMovies%2f10_Items_or_Less.%5b2006%5d.avi get passed to the server as /vfs/media/Virtual/Videos/Movies/10_Items_or_Less.[2006].avi which fails

but somehow requests passed to nginx as https://home.example.com/vfs/%2fmedia%2fVirtual%2fVideos%2fMovies%2f10_Items_or_Less.%5b2006%5d.avi gets passed to the kodi webserver as /vfs/%2fmedia%2fVirtual%2fVideos%2fMovies%2f10_Items_or_Less.%5b2006%5d.avi

which works.... so somehow its unencodeing the vfs path, but the rewrite rule is passing it to the correct place

lozbrown commented 7 years ago

I have it for nginx, I must have tried hundreds of configurations and manuals etc. But finally I have it working.

    location /kodi{
            rewrite           ^ $request_uri;
            rewrite           ^/kodi/(.*)  /$1  break;
            proxy_redirect  http://localhost:5555   /kodi/;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Scheme $scheme;
            proxy_pass http://localhost:5555$uri;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
   }
    location /image
        {
            proxy_pass          http://localhost:5555;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    location /jsonrpc
        {
            proxy_pass          http://localhost:5555;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

I may raise a request to make the references to jsonrpc and image/ relative so that the second two blocks are not required

war59312 commented 7 years ago

Nicely done.

I'm sure I'm not the only one who would like SSL support (HTTP/2 would be ideal). ;)

Drsela commented 7 years ago

Hey @lozbrown

When I try to use your configuration from November 25th, it just gives me an error 500 when I go to www.mydomain.com/kodi

My setup in nginx for /kodi /image /jsonrpc is exactly as yours, except I use port 8585 instead so I just replaced 5555 with 8585. Do you know if I've done anything wrong?

Zelaf commented 7 years ago

Hello, I'm trying to do this as well and I can't find a way to do it. Anyone know a way? @lozbrown your config gave me the same error as @Drsela.

Thanks for any help.

druchoo commented 7 years ago

@lozbrown, This probably wasn't available when you posted your config but there is reverse proxy support now.

Settings -> Web interface -> Reverse proxy support

If enabled, /jsonrpc and /image locations aren't needed (doesn't seem to work?). Alternatively the 2 locations can be combined to save some typing.

location ~ ^/(image|jsonrpc) {
    proxy_pass          http://localhost:5555;
    proxy_http_version  1.1;
    proxy_set_header    Upgrade $http_upgrade;
    proxy_set_header    Connection "upgrade";
}

@Drsela, @Zelaf, your 500 errors are likely due to no trailing slash in your URL.

Instead of:

www.mydomain.com/kodi

try:

www.mydomain.com/kodi/

Alternatively, you can add another rewrite rule to add trailing slash.

...
    rewrite  ^ $request_uri;
    rewrite  ^([^.]*[^/])$ $1/ permanent;
    rewrite  ^/kodi/(.*)  /$1  break;
...

@war59312, you should be able to use standard method to enabled SSL in nginx. As for WebSocket over HTTP/2.0, my understanding is it is not yet supported (pending RFC?).

To wrap everything up, here is a mostly complete example with trailing slash rewrite and SSL support.

upstream kodi {
  server    my-kodi-server:8080;
  keepalive 512;
}

# Redirect http -> https
server {
  listen 80;
  return 301 https://$host$request_uri;
}

server {
  listen              443 ssl;
  server_name         192.168.1.100 kodi kodi.tld.org;

  # Do SSL stuff...
  ssl_certificate     /etc/nginx/ssl/nginx.crt;
  ssl_certificate_key /etc/nginx/ssl/nginx.key;
  ssl_session_timeout 5m;
  ssl_ciphers         "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";

  # Optionally add basic auth
  auth_basic "Authorization Restricted";
  auth_basic_user_file /etc/nginx/.htpasswd;

  locaton /kodi {
    rewrite            ^ $request_uri;
    rewrite            ^([^.]*[^/])$ $1/ permanent;
    rewrite            ^/kodi/(.*)  /$1  break;
    proxy_redirect     http://kodi   /kodi/;
    proxy_set_header   Host $http_host;
    proxy_redirect     off;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Scheme $scheme;
    proxy_pass         http://kodi$uri;
    proxy_http_version 1.1;
    proxy_set_header   Upgrade $http_upgrade;
    proxy_set_header   Connection "upgrade";
  }

  # _Shouldn't_ need this if you turned on proxy headers? (Settings -> Web interface -> Reverse proxy support)
  location ~ ^/(image|jsonrpc) {
    proxy_pass         http://kodi;
    proxy_http_version 1.1;
    proxy_set_header   Upgrade $http_upgrade;
    proxy_set_header   Connection "upgrade";
  }
}

Thanks again @lozbrown!

EDIT: commas should be spaces in server_name

Zelaf commented 7 years ago

HUGE thanks for this @druchoo but can you edit everything that needs to be edited with something like "edit-this" as a place holder because when setting it up I only got to the nginx on debian page.

Thanks again.

EDIT: found out why, was being stupid, do you know how to make it on root and not on /kodi ?

druchoo commented 7 years ago

@Zelaf, Unless you want SSL and/or want to change the port (<=1024) then you really don't need nginx.

Nevertheless, here's a solution (most likely can be done better) which should work:

...
location / {
    proxy_set_header    Host $http_host;
    proxy_set_header    X-Real-IP $remote_addr;
    proxy_set_header    X-Scheme $scheme;
    proxy_pass          http://kodi$uri;
    proxy_http_version  1.1;
    proxy_set_header    Upgrade $http_upgrade;
    proxy_set_header    Connection "upgrade";
}
location ~ ^/(image|jsonrpc) {
    proxy_pass          http://kodi;
    proxy_http_version  1.1;
    proxy_set_header    Upgrade $http_upgrade;
    proxy_set_header    Connection "upgrade";
}
..
Zelaf commented 7 years ago

Thanks for the config @druchoo but I can't connect to the websocket for some reason, know why?

Thanks.

lozbrown commented 7 years ago

@druchoo I tested adding that setting this morning and removing my location settings for image and jsonrpc and it simply didn't work, I didn't get much time to investigate so added in your neater combined config, thanks for that.

One question, are you sure server_name needs the commas between names? I've found space separated examples.

druchoo commented 7 years ago

@Zelaf, Hmm not sure. Seemed to work before but not now.

@lozbrown, my mistake. commas should be spaces (previous comment edited). And yea, 'Reverse proxy support' seemed to work before but not now.

Either something was cached or I just totally screwed up my testing for those 2 cases.

lozbrown commented 7 years ago

@druchoo I'm, pretty sure it worked in the original chorus but not chorus 2, hence why i raised https://github.com/xbmc/chorus2/issues/140 which is all that's really required to make this work

Drsela commented 7 years ago

@druchoo

Thanks, that did the trick! I also enabled reverse proxy support within the web interface and still used the image and jsonrpc paths just to be sure. Everything is working flawlessy :)

Zelaf commented 7 years ago

@druchoo Now I'm not getting web socket on the /kodi config and the / config... Am I missing something?

Also the first config has a typo

locaton /kodi {

instead of

location /kodi {

Kl0use commented 7 years ago

Hi, thanks for the configs, but i can't get the websocket to work.

config:

upstream kodi {
    server 192.168.1.155:8080;
}
server {
  listen 80;

 location / {
    proxy_set_header    Host $http_host;
    proxy_set_header    X-Real-IP $remote_addr;
    proxy_set_header    X-Scheme $scheme;
    proxy_pass          http://kodi$uri;
    proxy_http_version  1.1;
    proxy_set_header    Upgrade $http_upgrade;
    proxy_set_header    Connection "upgrade";
}

location ~ ^/(image|jsonrpc) {
    proxy_pass          http://kodi;
    proxy_http_version  1.1;
    proxy_set_header    Upgrade $http_upgrade;
    proxy_set_header    Connection "upgrade";
}

Error - Console output: WebSocket connection to 'ws://home:9090/jsonrpc?kodi' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED

After adding following lines, it takes longer to take a timeout error, see: 1. pending 2. TimeOut

server {
listen 9090;
location / {
   access_log off;
   proxy_pass http://192.168.1.155:9090;
   proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # WebSocket support (nginx 1.4)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
}
}

Thank you.

Regards,

Klaus

IIIdefconIII commented 5 years ago

I cant get it to work, this is my nginx reverse file? can anyone tell me what i need to edit?


map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {

        listen 80 default_server;
        listen [::]:80 default_server;
        server_name xxx 10.3.1.2;
        return 301 https://$server_name$request_uri;    
}
server {

 # SSL configuration

 listen 443 ssl http2 default_server;
 listen [::]:443 ssl http2 default_server;
 include /etc/nginx/snippets/strong-ssl.conf;
 ssl_certificate /etc/letsencrypt/live/xxx/fullchain.pem;
 ssl_certificate_key /etc/letsencrypt/live/xxx/privkey.pem;
 error_log    /var/log/nginx/xxx.error.log;

 # Root location
 root /var/www/html;

 # Add index.php to the list if you are using PHP
 index index.html index.htm index.nginx-debian.html;

 # Basic Auth to protect the site
# auth_basic "Restricted";
 auth_basic_user_file /etc/nginx/.htpasswd;

 # Change the client side error pages (4xx) to prevent some information disclosure
 error_page 401 403 404 /404.html;

 # First attempt to serve request as file, then as directory,
 # then fall back to displaying a 404.

# location / {
#          try_files $uri $uri/ =404;
# }

 # Deny access to .htaccess files, if Apache's document
 # root concurs with nginx's one

 location ~ /\.ht {
          deny all;
 }

# Let's Encrypt Webroot plugin location -- allow access

 location ^~ /.well-known/acme-challenge/ {
          auth_basic off;
          autoindex on;
       }

# Location settings for reverse proxy; enable those you wish to use
# by removing the # from the section between the location line and the last }
# SABnzb
 location /sabnzb {                                    
    proxy_pass http://127.0.0.1:9190;
    proxy_redirect default;       
  }
# Sonarr
 location /sonarr {
    proxy_pass http://127.0.0.1:8989;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
# Radarr
 location /radarr {
    proxy_pass http://127.0.0.1:7878;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
# Homeassistant
 location / {
    proxy_pass http://localhost:8123;
    proxy_set_header Host $host;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
# Homeassistant websocket
 location /api/websocket {
    proxy_pass http://localhost:8123/api/websocket;
    proxy_set_header Host $host;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
# Kodi
 location /kodi{
    rewrite           ^ $request_uri;
    rewrite           ^/kodi/(.*)  /$1  break;
    proxy_redirect  http://localhost:8180   /kodi/;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Scheme $scheme;
    proxy_pass http://localhost:5555$uri;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}
gurabli commented 4 years ago

Bumping this up, as I would need a reverse proxy for Kodi but using LE certs in nginx. Actually I only need it to enable remote https access to my Kodi boxes to be able to connect Sonarr/Radarr notifications (to trigger a library update). I guess I don't need websocket port for that. Any help would be appreciates. Running LE 9.0.2 on Rpi.

pcross616 commented 4 years ago

I feel this issue can be solved pretty easily, kodi needs a configuration value for the internal port (which it has) and an external port (which is needs). The host value is there already the rest is a configuration in haproxy / nginx / apache

Here is a setup (mine but you can extrapolate to something else)

If the UI would honor the websocket connections based on the configured external hostname and port (not the internal one) this solution would just work with any configuration or reverse proxy.

ileodo commented 3 years ago

Here is my nginx config which works fine so far. hope this helps.

#
# Kodi Web
#
location /kodi {
    rewrite           ^ $request_uri;
    rewrite           ^/kodi/(.*)  /$1  break;
    proxy_redirect  http://KODI_IP:8080   /kodi/;
    proxy_set_header Host $http_host;

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Scheme $scheme;
    proxy_pass http://KODI_IP:8080$uri;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    location /kodi/jsonrpc
    {
        rewrite        ^/kodi/(.*)  /$1  break;
        proxy_pass     http://KODI_IP:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location /kodi/image
    {
    rewrite           ^ $request_uri;
        rewrite        ^/kodi/(.*)  /$1  break;
        proxy_pass     http://KODI_IP:8080$uri;
        proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    }

}
jtbgroup commented 3 years ago

Hi,

Trying to configure nginx for the same purpose. Copied ileodo's config in my file, but still getting the 404 for jsonrpc and images. Is it normal to have the /kodi/jsonrpc under /kodi and not at the same level?

Or does kodi needs to be configured in a special way?

ileodo commented 3 years ago

@jmbreuer can you paste the entire config file ?

jtbgroup commented 3 years ago

I assume you asked it to me ;-)

this is what my file looks like

      server {
        listen 80;

       location /kodi {
          rewrite           ^ $request_uri;
          rewrite           ^/kodi/(.*)  /$1  break;
          proxy_redirect  http://192.168.1.12:8080   /kodi/;
          proxy_set_header Host $http_host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Scheme $scheme;
          proxy_pass http://192.168.1.12:8080$uri;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";

             location /kodi/jsonrpc
             {
             rewrite        ^/kodi/(.*)  /$1  break;
                 proxy_pass     http://192.168.1.12:8080;
                 proxy_http_version 1.1;
                 proxy_set_header Upgrade $http_upgrade;
                 proxy_set_header Connection "upgrade";
             }

             location /kodi/image
             {   
             rewrite           ^ $request_uri;
                 rewrite        ^/kodi/(.*)  /$1  break;
                 proxy_pass     http://192.168.1.12:8080$uri;
                 proxy_http_version 1.1;
             proxy_set_header Upgrade $http_upgrade;
             proxy_set_header Connection "upgrade";
              }
          }
      }
ileodo commented 3 years ago

have you enable the "reverse proxy" in the web ui settings?

jtbgroup commented 3 years ago

Hi, Sorry for my late reply. Didn't see your last comment.

Yes, option is active in Web ui.

After several attemps, I tried your config again, which gives this :

upstream KODI_IP {
    server  192.168.1.2:8080;
}

# Redirect http -> https
server {
    if ($host = mydomain.abc.org){
        return 301 https://$host$request_uri;
    }
    listen          8080;
    server_name     mydomain.abc.org;
    return 404;
}

server {
    listen                  443 ssl;
    server_name             mydomain.abc.org;

    # Do SSL stuff...
    ssl_certificate         /etc/letsencrypt/live/mydomain.abc.org/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/mydomain.abc.org/privkey.pem; 
    include                 /etc/letsencrypt/options-ssl-nginx.conf; 
    ssl_dhparam             /etc/letsencrypt/ssl-dhparams.pem; 

    location /kodi {
        rewrite             ^ $request_uri;
        rewrite             ^/kodi/(.*)  /$1  break;
        proxy_redirect      http://KODI_IP   /kodi/;
        proxy_set_header Host $http_host;

        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_pass http://KODI_IP$uri;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        location /kodi/jsonrpc {
            rewrite             ^/kodi/(.*)  /$1  break;
            proxy_pass          http://KODI_IP;
            proxy_http_version  1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

        location /kodi/image {
            rewrite             ^ $request_uri;
            rewrite             ^/kodi/(.*)  /$1  break;
            proxy_pass          http://KODI_IP$uri;
            proxy_http_version  1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    }
}

But browsers keep giving the following messages:

Firefox can’t establish a connection to the server at wss://mydomain.abc.org:9090/jsonrpc?kodi.

somini commented 2 years ago

The following setup works well, except for the WebSocket situation:

server { # Kodi Chorus2
    server_name <SERVER_NAME>;
    listen 443 ssl;
    listen [::]:443 ssl;

    location ~ ^/(image|vfs)/ {
        proxy_pass http://<UPSTREAM>;
    }

    location / {
        proxy_pass http://<UPSTREAM>/;
    }

    # Proxy Settings
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Script-Name /; # Same from root "location" above
    proxy_redirect off;

    # WebSockets
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    # Basic Authentication
    auth_basic "<UPSTREAM-NAME>";
    auth_basic_user_file <SERVER_NAME>.auth;

    # Logging
    # - Write per-host logs, on the usual location
    # - gzip the logs
    # - Flush every 1h, losing logs is fine by me
    access_log /var/log/nginx/<SERVER_NAME>.log.gz combined gzip flush=1h;

    # TLS
    ssl_certificate /etc/letsencrypt/live/<SERVER_NAME>/fullchain.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/<SERVER_NAME>/chain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<SERVER_NAME>/privkey.pem;
}
onknows commented 1 year ago

I noticed that the advanced options for the web interface for example "Reverse proxy support" is stored using browser local storage. That is a very strange way to store these type of settings.

I think the conclusion should be that reverse proxy is just not supported / working currently.

There are a great number of issues with the current setup. That aside, the fact that reverse proxy settings are stored locally is a showstopper I think.

somini commented 1 year ago

I think the biggest papercut with the web interface is not being possible disallowing access to the remote control functionality, that is, only remote viewing AKA pink Chorus.

jtru commented 1 year ago

The problem

So I have been banging my head against this particular wall for a while, and I think the solution that needs to be achieved to make reverse proxying-associated problems go away involves not only Chorus2, but also either Kodi's built-in HTTP daemon itself (using available libmicrohttpd functionality), or a third-party, external reverse proxy that covers both Kodi's JSONRPC-over-TCP-Port as well as Kodi's HTTP-for-Chorus2-Port.

The core defect/problem is that Chorus seems to expect to make a second, websockets-dedicated TCP connection to an endpoint that is available to the User Agent just like the HTTP port is. In many scenarios that is impossible, or at the very least unwise. It is also unnecessary, since HTTP in general (and afaict, also libmicrohttpd in particular) allows for the well-estyablished Upgrade-Request-Header based mechanism to negotiate a websocket connection for any HTTP URL/request, if the HTTP server so grants it.

The proper solution

Imho, the "most proper" way for Kodi to handle and implement this on its own is to

  1. Use libmicrohttpd to set up an URL namespace/prefix on its Chorus-serving webserver that will serve as the websocket request handler for any clients that want to initiate a websocket connection (i.e., a GET request that will match /jsonrpc?kodi would trigger the behavior described below)
  2. Use that URL namespace/prefix to negotiate a properly reverse-proxied websockets connection to localhost:9090 (or whereever the "remote control"-JSONRPC-TCP-Socket is listening on from the Kodi processes' perspective)
  3. Hard-code the assumption that websockets-server == http-server and websockets-port == http-port into Chorus2, along with the URL namespace/prefix for getting to the websocket handler in Kodi's modified HTTP daemon.

Unfortunately, I don't have enough experience with either Kodi's codebase (and C++ in general) nor libmicrohttpd's API to pull this out of my hat.

I did arrive at an alternative solution, however.

Alternative, hackier solution

If the above is too much of a change to be coordinated in too many places, one could achieve a similarly clean setup when involving an external HTTP proxy server, but would need to teach Chorus2 an additional trick to make it work smoothly and transparently in all cases.

Right now, the Websockets host setting accepts a magic value of auto, that will make Chorus2 assume the UA's current HTTP origin hostname as the hostname for the websocket connection, too. If the Websocket port setting would accept a similar magic value to make it assume the same TCP port as the current HTTP(S) port, a reverse proxy configuration like the below for caddy would always lead to the expected result of working websockets, as long as Chorus2 is configured to use the same websockets port as the current HTTP(S) port is used to serve it via the proxy instance:

{
        debug
        on_demand_tls {
                interval 60s
                burst 2
        }
}

:80 :443 {
        tls internal {
                on_demand
        }
        @websockets {
                header Connection *Upgrade*
                header Upgrade    websocket
        }
        reverse_proxy @websockets 127.0.0.1:9090
        reverse_proxy * 127.0.0.1:8090
}

Of course, Chorus2 would also have to inherit whether or not the connection is supposed to negotiate TLS from the protocol/scheme of the HTTP(S) origin it is being served from.

For this example to work, the caddy instance (configured with the snippet above saved as its Caddyfile) will have to run on the same host as Kodi does, with Kodi's webserver having been reconfigured to listen on TCP port 8090 instead of TCP Port 80.

Demo / PoC

To conveniently try this yourself on a LibreELEC box (tested on 11.0 on a Raspberry Pi 4 only, but I think it should work on any aarch64 or amd64 host running LE 10 or later), you may want to configure Kodi as described above (Webserver binding to Port 8090) and use this script to bootstrap caddy with a suitable config:

#!/bin/sh
LC_ALL=C
TZ=UTC
set -x
set -u
umask 0077
dir="${CADDYDIR:-/tmp/caddy-proxy-test}"
ver="${CADDYVER:-2.6.4}"

mkdir -p "${dir}"
cd "${dir}" || exit 1

case "$(uname -m)" in
  amd64)
    arch=amd64
  ;;
  aarch64)
    arch=arm64
  ;;
  *)
    echo "FATAL - Unexpected CPU arch - sorry." >&2
    exit 1
  ;;
esac

if ! netstat -ntlp | awk '/.*/{if($4 ~ /:(443|80)$/){exit 1}}'
then
  echo "FATAL - You already have listeners on TCP Port 80 and/or 443. Please stop/reconfigure these." >&2
  exit 1
fi

if netstat -ntlp | awk -v s=1 '/kodi.bin$/{if($4 ~ /:8090$/){s=0}} END{exit s}' >/dev/null
then
  : OK, Kodi on Port 8090
else
  echo "FATAL - Please configure Kodi's internal webserver (for Chorus2) to listen on TCP Port 8090." >&2
  exit 1
fi

: Downloading Caddy release
curl --fail -o ./caddy.tar.gz -L 'https://github.com/caddyserver/caddy/releases/download/v'"${ver}"'/caddy_'"${ver}"'_linux_'"${arch}"'.tar.gz'

: Extracting caddy executable
tar xzf caddy.tar.gz || exit 1

cat >Caddyfile <<EOF
{
        debug
        on_demand_tls {
                interval 60s
                burst 2
        }
}

:80 :443 {
        tls internal {
                on_demand
        }
        @websockets {
                header Connection *Upgrade*
                header Upgrade    websocket
        }
        reverse_proxy @websockets 127.0.0.1:9090
        reverse_proxy * 127.0.0.1:8090
}
EOF

chmod +x caddy || exit 1

: Executing caddy, serving HTTP and HTTPS, with Caddyfile from ${PWD} ...
: Hit Ctrl+C to stop.
exec ./caddy run

After that, make sure to set Chorus2's Websocket host to "auto", and to match the "Websocket port" to the HTTP(S) port you use (that is, either 80 or 443) to access the caddy reverse proxy.

(Please note that the Caddyfile used enables production-unsafe use of dynamic TLS certificate minting. Do not use this verbatim if the resulting caddy instance is available on a non-private network!)