# Reverse-proxy Maven's own web UI plus the three sibling admin UIs. Drop into # your nginx sites (or the nginx-panel app) and reload. Assumes the compose # publishes each service on 127.0.0.1:. Add TLS (certbot / your existing # cert block) per server. # # NOTE: hexis. previously pointed at the MCP tool — repoint that # elsewhere first (the app now owns hexis.*). # # 10.42.0.1 and 192.168.1.104 below are THIS BOX's WireGuard and LAN # addresses (homesrv) — these admin UIs have no auth of their own, so the # explicit bind + allow/deny below is what keeps them off the open internet. # On a different box, replace both addresses with that box's wg and LAN IPs. # Do NOT "fix" a failed bind by reverting to `listen 80` (all interfaces) — # that removes the only access control these containers have. # # BEFORE YOU RELOAD — two ways this file takes nginx down, both host-side. # These blocks are for the HOST nginx, not for anything inside the compose; # the containers publish on 127.0.0.1 and have no nginx of their own. # # 1. Binding an address that does not exist yet. `listen 10.42.0.1:80` fails # with EADDRNOTAVAIL if wg0 is down, and nginx exits rather than starting # without it — so a reboot that brings nginx up before WireGuard leaves the # box with no web at all. Allow the bind to succeed regardless: # sysctl -w net.ipv4.ip_nonlocal_bind=1 # echo 'net.ipv4.ip_nonlocal_bind = 1' > /etc/sysctl.d/99-nginx-bind.conf # Ordering nginx after the wg interface works too, but only until the next # time the tunnel restarts. # # 2. A duplicate $connection_upgrade map. That is a fatal config error, so the # map now lives in nginx-upgrade-map.conf and is installed separately — # read the note at the top of that file first. # # `nginx -t` catches the second and not the first. Run it anyway, every time. # # BLOCK ORDER IS LOAD-BEARING. nginx serves the first block for a given listen # address when no server_name matches, so whichever block comes first here # answers requests with an unknown or absent Host header. That must not be # mavweb: it is the one surface in this file that can define and run argv. The # nexus block is marked default_server so the choice is explicit rather than a # consequence of file order, and the maven block sits last as a second guard. # If you add a block, do not put it above nexus. If another site file already # claims default_server on 10.42.0.1:80 or 192.168.1.104:80, nginx refuses to # start with "a duplicate default server" — drop the two keywords here and rely # on the block order instead. # # Every server_name below is a literal for kvmx.ru even though the comments # write maven.. This file reads like a template and is not one. server { listen 10.42.0.1:80 default_server; listen 192.168.1.104:80 default_server; server_name nexus.kvmx.ru; allow 10.42.0.0/24; allow 192.168.1.0/24; deny all; location / { proxy_pass http://127.0.0.1:9740; 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; } } server { listen 10.42.0.1:80; listen 192.168.1.104:80; server_name praxis.kvmx.ru; allow 10.42.0.0/24; allow 192.168.1.0/24; deny all; location / { 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; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 10.42.0.1:80; listen 192.168.1.104:80; server_name hexis.kvmx.ru; allow 10.42.0.0/24; allow 192.168.1.0/24; deny all; location / { proxy_pass http://127.0.0.1:9741; 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; } } # maven. → mavweb (docker-compose.yml publishes it on 127.0.0.1:9201). # Same bind + ACL as the siblings, and for a stronger reason: mavweb serves # POST /tools, which defines argv that internal/tool EXECUTES, plus POST # /routines, /api/revert, /api/chat, /api/ptt and /ws (Vikunja #317). Without # -webauthn-origin/-webauthn-rpid mavweb has no auth of its own, so this block # is the auth. If you add TLS and a basic-auth/oauth2-proxy layer, keep the # allow/deny anyway — belt and braces on an RCE surface. # # WebSocket upgrade matters here: /ws carries push-to-talk audio, so the # Upgrade/Connection headers below are required, not decoration. They reference # $connection_upgrade, which this file does NOT define — see # nginx-upgrade-map.conf and point 2 above. server { listen 10.42.0.1:80; listen 192.168.1.104:80; server_name maven.kvmx.ru; allow 10.42.0.0/24; allow 192.168.1.0/24; deny all; # push-to-talk uploads raw PCM; the default 1m is enough for a short # utterance but not for a long one. client_max_body_size 32m; # A 32m upload over a slow link outlives the 60s default on the two body # timeouts, and nginx cuts it at exactly 60s with a 408 or a 504 that looks # like the turn failed. Raise them with the size, not just proxy_read_timeout. client_body_timeout 300s; location / { proxy_pass http://127.0.0.1:9201; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $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-Proto $scheme; proxy_send_timeout 300s; # pushing the PCM upstream, same reason proxy_read_timeout 300s; # an LLM turn can take minutes on the iGPU } }