70p4z / ha-reverse-proxy-path

Home assistant reverse proxy script to allow for sub path chrooting (http://acme.com/ha/). Which was not possible with bare home assistant
9 stars 2 forks source link

nginx standalone solution #2

Open diorcety opened 1 year ago

diorcety commented 1 year ago

After multiple hours of attempt:

        location /ha/ {
                set $prefix '/ha';

                proxy_pass http://127.0.0.1:8123/;
                proxy_redirect ~^/(.*) $scheme://$host$prefix/$1;
                proxy_set_header Accept-Encoding "";

                # These configuration options are required for WebSockets to work.
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "Upgrade";

                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-Host $http_host;
                proxy_set_header X-Forwarded-Proto $scheme;

                sub_filter_types text/html text/css application/javascript text/javascript;
                sub_filter_once off;
                sub_filter '/frontend_' '$prefix/frontend_';
                sub_filter '/static/' '$prefix/static/';
                sub_filter '/local/' '$prefix/local';
                sub_filter '/auth/' '$prefix/auth/';
                sub_filter '/api/' '$prefix/api/';
                sub_filter '/service_worker.js' '$prefix/service_worker.js';
                sub_filter '/manifest.json' '$prefix/manifest.json';

                subs_filter_types text/html text/css application/javascript text/javascript;
                subs_filter '[a-zA-Z0-9_\.]*history\.pushState' 'hpS' 'gr';
                subs_filter '[a-zA-Z0-9_\.]*history\.replaceState' 'hrS' 'gr';
                subs_filter '([a-zA-Z0-9_\.]*location.pathname)' 'lpn($1)' 'gr';
                subs_filter '\.\.\.history\.state([^a-zA-Z0-9_])' '...window.history_state$1' 'gr';
                subs_filter '[a-zA-Z0-9_\.]*history\.state([^a-zA-Z0-9_])' 'window.history_state$1' 'gr';
                sub_filter '</body>' '<script> function hpS(d,o,url){console.log("push "+url);window.history_state=url;window.history.pushState(d,o,"$prefix"+url)}; function hrS(d,o,url){console.log("replace "+url);window.history_state=url;window.history.replaceState(d,o,"$prefix"+url)} function lpn(e){ const prefix = "$prefix"; const hasPrefix = e.indexOf(prefix) === 0; return hasPrefix ? e.substr(prefix.length):e;}</script></body>';
        }
70p4z commented 1 year ago

Oh wow, that's so nice! I've been trying to perform that a long time ago for another project, but never got the hang of it. I still have patches to perform for the settings to work out of the box though. It's great that we can ditch the python part with your approach. Let me test it and I'll merge it in.

OvidiuFilip commented 1 year ago

I have tweaked the config from above and it works for me (not 100%) :D Here is my config:

location /hass {
  return 301 $scheme://$host/hass/;
}

location /hass/ {
  set $prefix '/hass';

  rewrite /hass/(.*) /$1 break;

  proxy_pass http://192.168.x.x:8123/;
  proxy_redirect ~^/(.*) $scheme://$host$prefix$1;
  proxy_set_header Accept-Encoding "";
  # These configuration options are required for WebSockets to work.
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "Upgrade";

  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-Host $http_host;
  proxy_set_header X-Forwarded-Proto $scheme;

  sub_filter_types text/css application/javascript text/javascript;
  sub_filter_once off;
  sub_filter '/frontend_' '$prefix/frontend_';
  sub_filter '/static/' '$prefix/static/';
  sub_filter '/local/' '$prefix/local';
  sub_filter '/auth/' '$prefix/auth/';
  sub_filter '/api/' '$prefix/api/';
  sub_filter '/service_worker.js' '$prefix/service_worker.js';
  sub_filter '/manifest.json' '$prefix/manifest.json';

  sub_filter '/lovelace' '$prefix/lovelace';
  sub_filter '/hacsfiles' '$prefix/hacsfiles';
}
thomashamain commented 1 year ago

I have tweaked the config from above and it works for me (not 100%) :D Here is my config:

Hi, worked for me without any change. Thanks a lot! To @70p4z the initiator of this repo for being annoyed by the fact that only subdomains natively work with HA, to @diorcety for taking the time to dig into nginx config, and to @OvidiuFilip for refining the solution, thanks :)

I looked up "sub_filter" module, and from what I understand, it simply substitutes one part of the URL by another, and with "sub_filter_once off" we get it in both ways, client and server.

So I naively tried to use a "double" path, such as /myhome/ha (instead of just /ha) aaand, it didn't work. I did a ctrl+replace of "hass" with "myhome/ha" (like y did for my "simple" path that is different from "hass").

Honestly, not sure that will dig further. I am a Sunday hobbyist, the solution to go with myhome-ha works for me :) But if someone finds a solution, hopefully, it will also help other ;)

thomashamain commented 1 year ago

Hi, I added proxy_redirect to the first location, worked even better and I was able to use double path, plus I dropped all the sub_filter modules

location /myhome/ha {
  proxy_redirect $scheme://$host/myhome/ha/ /;
}
location /myhome/ha/ {
  set $prefix '/myhome/ha';

  rewrite /myhome/ha/(.*) /$1 break;

  proxy_pass http://192.168.x.x:8123/;
  proxy_redirect ~^/(.*) $scheme://$host$prefix$1;
  proxy_set_header Accept-Encoding "";
  # These configuration options are required for WebSockets to work.
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "Upgrade";

  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-Host $http_host;
  proxy_set_header X-Forwarded-Proto $scheme;

#  sub_filter_types text/css application/javascript text/javascript;
#  sub_filter_once off;
#  sub_filter '/frontend_' '$prefix/frontend_';
#  sub_filter '/static/' '$prefix/static/';
#  sub_filter '/local/' '$prefix/local';
#  sub_filter '/auth/' '$prefix/auth/';
#  sub_filter '/api/' '$prefix/api/';
#  sub_filter '/service_worker.js' '$prefix/service_worker.js';
#  sub_filter '/manifest.json' '$prefix/manifest.json';

#  sub_filter '/lovelace' '$prefix/lovelace';
#  sub_filter '/hacsfiles' '$prefix/hacsfiles';
}

I did not test minimal working configuration.

I am now having another battle. It is described in https://github.com/home-assistant/android/issues/3478. I closed it since it is not related to their app, but the issue persists, and I suspect that it is something with nginx...

diorcety commented 1 year ago

The issue is the android application, they strip the prefix from the url you give. The sub_filter is needed in order to use the web application, otherwise you don't have the good url accorded to the page you are currently in (after the # in the url)

diorcety commented 1 year ago

https://github.com/diorcety/ha-android/commit/bb3eb826b9512a27b634799250e641b024aeb833

70p4z commented 1 year ago

You're right, on various client, the subpath is not taken into account. Hence I stayed on web based version. Following your patch on the android app, wouldn't it be time for our little task force team to add the subpath feature into the home assistant repository?

shanester64 commented 4 months ago

After multiple hours of attempt:

        location /ha/ {
                set $prefix '/ha';

                proxy_pass http://127.0.0.1:8123/;
                proxy_redirect ~^/(.*) $scheme://$host$prefix/$1;
                proxy_set_header Accept-Encoding "";

                # These configuration options are required for WebSockets to work.
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "Upgrade";

                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-Host $http_host;
                proxy_set_header X-Forwarded-Proto $scheme;

                sub_filter_types text/html text/css application/javascript text/javascript;
                sub_filter_once off;
                sub_filter '/frontend_' '$prefix/frontend_';
                sub_filter '/static/' '$prefix/static/';
                sub_filter '/local/' '$prefix/local';
                sub_filter '/auth/' '$prefix/auth/';
                sub_filter '/api/' '$prefix/api/';
                sub_filter '/service_worker.js' '$prefix/service_worker.js';
                sub_filter '/manifest.json' '$prefix/manifest.json';

                subs_filter_types text/html text/css application/javascript text/javascript;
                subs_filter '[a-zA-Z0-9_\.]*history\.pushState' 'hpS' 'gr';
                subs_filter '[a-zA-Z0-9_\.]*history\.replaceState' 'hrS' 'gr';
                subs_filter '([a-zA-Z0-9_\.]*location.pathname)' 'lpn($1)' 'gr';
                subs_filter '\.\.\.history\.state([^a-zA-Z0-9_])' '...window.history_state$1' 'gr';
                subs_filter '[a-zA-Z0-9_\.]*history\.state([^a-zA-Z0-9_])' 'window.history_state$1' 'gr';
                sub_filter '</body>' '<script> function hpS(d,o,url){console.log("push "+url);window.history_state=url;window.history.pushState(d,o,"$prefix"+url)}; function hrS(d,o,url){console.log("replace "+url);window.history_state=url;window.history.replaceState(d,o,"$prefix"+url)} function lpn(e){ const prefix = "$prefix"; const hasPrefix = e.indexOf(prefix) === 0; return hasPrefix ? e.substr(prefix.length):e;}</script></body>';
        }

I have tried using this syntax (as well as all of the suggestions in this thread. I can log in, however, it just returns the HA logo.

location /ha/ { set $prefix '/ha';

            proxy_pass http://192.168.0.77:8123/;
            proxy_redirect ~^/(.*) $scheme://$host$prefix/$1;
            proxy_set_header Accept-Encoding "";

            # These configuration options are required for WebSockets to work.
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";

            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-Host $http_host;
            proxy_set_header X-Forwarded-Proto $scheme;

            sub_filter_types text/html text/css application/javascript text/javascript;
            sub_filter_once off;
            sub_filter '/frontend_' '$prefix/frontend_';
            sub_filter '/static/' '$prefix/static/';
            sub_filter '/local/' '$prefix/local';
            sub_filter '/auth/' '$prefix/auth/';
            sub_filter '/api/' '$prefix/api/';
            sub_filter '/service_worker.js' '$prefix/service_worker.js';
            sub_filter '/manifest.json' '$prefix/manifest.json';

            subs_filter_types text/html text/css application/javascript text/javascript;
            subs_filter '[a-zA-Z0-9_\.]*history\.pushState' 'hpS' 'gr';
            subs_filter '[a-zA-Z0-9_\.]*history\.replaceState' 'hrS' 'gr';
            subs_filter '([a-zA-Z0-9_\.]*location.pathname)' 'lpn($1)' 'gr';
            subs_filter '\.\.\.history\.state([^a-zA-Z0-9_])' '...window.history_state$1' 'gr';
            subs_filter '[a-zA-Z0-9_\.]*history\.state([^a-zA-Z0-9_])' 'window.history_state$1' 'gr';
            sub_filter '</body>' '<script> function hpS(d,o,url){console.log("push "+url);window.history_state=url;window.history.pushState(d,o,"$prefix"+url)}; function hrS(d,o,url){console.log("replace "+url);window.history_state=url;window.history.replaceState(d,o,"$prefix"+url)} function lpn(e){ const prefix = "$prefix"; const hasPrefix = e.indexOf(prefix) === 0; return hasPrefix ? e.substr(prefix.length):e;}</script></body>';
    }