diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index e9bca61..c9b506a 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -26,7 +26,7 @@ jobs: env: DEPLOY_PATH: /var/www/html/heos SERVICE: heos-panel - PANEL_URL: http://127.0.0.1:5005/ # WEB_PORT in config.py + PANEL_URL: http://127.0.0.1:5443/ # WEB_PORT in config.py steps: - name: Checkout diff --git a/README.md b/README.md index 8ca37c9..efa501b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ pip install -r requirements.txt python3 app.py ``` -Then open `http://:5005/`. +Then open `http://:5443/`. The virtual environment is not optional on a current Raspberry Pi OS: `pip install` straight into the system Python is refused there with @@ -49,7 +49,7 @@ below calls the environment's Python directly, so it does not care. ## Configure -Open `http://:5005/api/targets` and copy the exact `name` HEOS reports +Open `http://:5443/api/targets` and copy the exact `name` HEOS reports for each device into `TARGETS` in `config.py`. The names come from whatever you typed in the HEOS app, so they rarely match the model names. @@ -158,18 +158,27 @@ deployed copy. ## Behind a reverse proxy, at /heos -`deploy/heos.conf` reverse-proxies `/heos` to the panel with Apache: +`deploy/heos.apache.conf` reverse-proxies `/heos` to the panel with Apache: ```bash sudo a2enmod proxy proxy_http headers -sudo cp deploy/heos.conf /etc/apache2/conf-available/heos.conf +sudo cp deploy/heos.apache.conf /etc/apache2/conf-available/heos.conf sudo a2enconf heos sudo apachectl configtest && sudo systemctl reload apache2 ``` -`deploy/heos.nginx.conf` is the same thing for nginx: copy it to -`/etc/nginx/snippets/heos.conf`, `include snippets/heos.conf;` inside the -`server` block, then `sudo nginx -t && sudo systemctl reload nginx`. +`deploy/heos.nginx.conf` is the same thing for nginx — a whole `server` +block for `rpioffice.nest.domain.com`, ready for `sites-available`: + +```bash +sudo cp deploy/heos.nginx.conf /etc/nginx/sites-available/heos +sudo ln -s /etc/nginx/sites-available/heos /etc/nginx/sites-enabled/heos +sudo nginx -t && sudo systemctl reload nginx +sudo certbot --nginx -d rpioffice.nest.domain.com # optional, adds the 443 block +``` + +It serves the panel at `/heos` and refuses everything else on that host; +the file ends with the two-line change that puts it at the root instead. Both ship restricted to the local network — this controls the speakers, and it usually hangs off a host with a public certificate. Delete the @@ -179,7 +188,7 @@ up. The app works at either address without being told which. The proxy sends `X-Forwarded-Prefix: /heos`, and every URL the app generates — stylesheet, icons, the manifest's `start_url`, every `fetch` — picks up that prefix. -Serve it straight from port 5005 and the same URLs come out as `/...`. +Serve it straight from port 5443 and the same URLs come out as `/...`. That header is what the `headers` module is for; without it the page loads and nothing on it works. @@ -187,7 +196,7 @@ Two things worth knowing: - The proxy block takes `/heos` away from the filesystem, so the source under `/var/www/html/heos` stops being served as static files. -- The panel still answers directly on `:5005`. Start it with +- The panel still answers directly on `:5443`. Start it with `--host 127.0.0.1` if you want Apache to be the only way in. ## How the grouping actually works diff --git a/app.py b/app.py index 4714d6d..cf8c9ca 100644 --- a/app.py +++ b/app.py @@ -2,7 +2,7 @@ """HEOS panel: a phone-sized web remote plus the HTTP bridge it runs on. pip3 install -r requirements.txt - python3 app.py # http://:5005/ + python3 app.py # http://:5443/ Everything the UI does goes through /api/*. The flatter, query-string endpoints from the original heos_bridge.py (/volume/up?target=..., and @@ -22,7 +22,7 @@ from heos import HeosError app = Flask(__name__) -# Served straight from port 5005 this changes nothing. Behind a reverse +# Served straight from port 5443 this changes nothing. Behind a reverse # proxy that mounts us on a sub-path (Apache at /heos, say) it reads the # X-Forwarded-Prefix that proxy sets, so every URL the app generates is # /heos/... instead of /..., and the page works either way. diff --git a/config.py b/config.py index 6b821b4..42694f6 100644 --- a/config.py +++ b/config.py @@ -19,7 +19,7 @@ AVR_HOST = "192.168.0.10" AVR_PORT = 23 # Port the panel itself listens on. -WEB_PORT = 5005 +WEB_PORT = 5443 # --- Rooms ------------------------------------------------------------ # key -> how to find it on the network, and how to label it in the UI. diff --git a/deploy/heos.conf b/deploy/heos.apache.conf similarity index 76% rename from deploy/heos.conf rename to deploy/heos.apache.conf index a05162e..8412bae 100644 --- a/deploy/heos.conf +++ b/deploy/heos.apache.conf @@ -1,13 +1,13 @@ # HEOS panel behind Apache, at /heos # # sudo a2enmod proxy proxy_http headers -# sudo cp /var/www/html/heos/deploy/heos.conf /etc/apache2/conf-available/heos.conf +# sudo cp /var/www/html/heos/deploy/heos.apache.conf /etc/apache2/conf-available/heos.conf # sudo a2enconf heos # sudo apachectl configtest && sudo systemctl reload apache2 # -# Apache reaches the panel on port 5005 (WEB_PORT in config.py). If you +# Apache reaches the panel on port 5443 (WEB_PORT in config.py). If you # want it reachable ONLY through Apache, start it with --host 127.0.0.1; -# by default it also answers directly on the LAN at :5005. +# by default it also answers directly on the LAN at :5443. # This block also takes /heos away from the filesystem, so the source in # /var/www/html/heos stops being reachable as static files. @@ -26,6 +26,6 @@ # page loads and nothing on it works. RequestHeader set X-Forwarded-Prefix /heos - ProxyPass http://127.0.0.1:5005 - ProxyPassReverse http://127.0.0.1:5005 + ProxyPass http://127.0.0.1:5443 + ProxyPassReverse http://127.0.0.1:5443 diff --git a/deploy/heos.nginx.conf b/deploy/heos.nginx.conf index 241d1e4..6e9af45 100644 --- a/deploy/heos.nginx.conf +++ b/deploy/heos.nginx.conf @@ -1,57 +1,101 @@ -# HEOS panel behind nginx, at /heos +# The HEOS panel at https://domain.com/heos # -# sudo cp /var/www/html/heos/deploy/heos.nginx.conf /etc/nginx/snippets/heos.conf -# then inside the server { } block that serves the site: -# include snippets/heos.conf; +# sudo cp /var/www/html/heos/deploy/heos.nginx.conf /etc/nginx/sites-available/heos +# sudo ln -s /etc/nginx/sites-available/heos /etc/nginx/sites-enabled/heos # sudo nginx -t && sudo systemctl reload nginx # -# nginx reaches the panel on port 5005 (WEB_PORT in config.py). If you want -# it reachable ONLY through nginx, start it with --host 127.0.0.1; by -# default it also answers directly on the LAN at :5005. - -# A bare /heos would miss the location below and fall through to the -# filesystem, so send it to the slashed form first. -location = /heos { - return 301 /heos/; -} - -location /heos/ { - # The panel controls the speakers, and it usually hangs off a host - # with a public certificate. Keep it to the house unless you mean - # otherwise: drop these four lines to let it answer from anywhere. - allow 192.168.0.0/24; - allow 127.0.0.1; - allow ::1; - deny all; - - # The trailing slash on proxy_pass is what strips /heos/ back off - # before the request reaches the app. - proxy_pass http://127.0.0.1:5005/; - - # Tells the app it is mounted on a sub-path, so every link, icon and - # fetch it generates is /heos/... rather than /... Without this the - # page loads and nothing on it works. - proxy_set_header X-Forwarded-Prefix /heos; - - 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_http_version 1.1; - proxy_set_header Connection ""; -} - -# Giving it a host of its own instead? Then it is not on a sub-path, and -# the X-Forwarded-Prefix line above is the one thing to leave out: +# server_name and the certificate paths have to agree: the paths are the +# directory certbot made for that name. # -# server { -# server_name heos.example.com; -# location / { -# proxy_pass http://127.0.0.1:5005; -# 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; -# } -# } +# nginx reaches the panel on port 5443 (WEB_PORT in config.py). To make +# nginx the only way in, add --host 127.0.0.1 to ExecStart in +# deploy/heos-panel.service; by default the panel also answers directly on +# the LAN at :5443. + +server { + listen 80; + listen [::]:80; + + server_name domain.com; + + # Nothing is served in the clear. certbot's nginx plugin works through + # this block when it renews, so the redirect does not get in its way. + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + # http2 on; # nginx 1.25.1+. Older builds: listen 443 ssl http2; + + server_name domain.com; + + ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem; + + # certbot's own settings, kept current by it -- the same thing the maui + # Apache vhost does with options-ssl-apache.conf. Both files appear when + # certbot configures a host; if this cert came another way (DNS + # challenge, standalone, copied from elsewhere) they may not exist and + # nginx -t will say so. Then drop these two lines for: + # ssl_protocols TLSv1.2 TLSv1.3; + # ssl_session_cache shared:SSL:10m; + # ssl_session_timeout 1d; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + + # This host is the panel and nothing else, so refuse the rest rather + # than falling back on nginx's default root and serving whatever + # happens to sit there. Drop this block if the machine serves more. + location / { + return 404; + } + + # A bare /heos misses the location below -- it would fall through to + # the 404 above -- so send it to the slashed form first. + location = /heos { + return 301 /heos/; + } + + location /heos/ { + # The panel controls the speakers, and this hostname may well + # resolve from outside. Keep it to the house unless you mean + # otherwise: drop these four lines to let it answer from anywhere. + allow 192.168.0.0/24; + allow 127.0.0.1; + allow ::1; + deny all; + + # The trailing slash is what strips /heos/ back off before the + # request reaches the app. + proxy_pass http://127.0.0.1:5443/; + + # Tells the app it is mounted on a sub-path, so every link, icon and + # fetch it generates is /heos/... rather than /... Without this the + # page loads and nothing on it works. + proxy_set_header X-Forwarded-Prefix /heos; + + 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_http_version 1.1; + proxy_set_header Connection ""; + } +} + +# Want the panel at the root of this host instead of under /heos? Replace +# the three location blocks in the 443 server with the one below, and leave +# X-Forwarded-Prefix out of it -- the app is not on a sub-path then, and +# generates /static/... and /api/... just as it does on port 5443. +# +# location / { +# proxy_pass http://127.0.0.1:5443; +# 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_http_version 1.1; +# proxy_set_header Connection ""; +# }