nginx: serve mgmt /static/ assets from disk (no Flask round-trip)

Generated management-domain server blocks now include a
location /static/ aliasing webui/static/ with Cache-Control: no-cache
(ETag revalidation -> 304), nosniff, and a restrictive CSP, so SPA
asset requests no longer reach Flask. Flask's static route remains
the dev-mode fallback.

- lib/nginx.py + daemon/handlers/nginx.py: static_root render context
  (the handler renders the template directly, so both paths need it)
- template: alias uses a trailing slash (nginx concatenates the
  location remainder onto the alias value)
- install.sh: a+rX on webui/static plus execute-up-the-parent-chain
  so the nginx worker (www-data) can traverse repo-in-$HOME installs
- tests: mgmt static location assertions (positive + non-mgmt negative)
- docs: AGENTS.md, architecture.md, security.md static-serving notes
This commit is contained in:
2026-09-03 14:58:57 +00:00
parent d78b90db00
commit fc478a016e
8 changed files with 69 additions and 6 deletions
+10
View File
@@ -53,6 +53,16 @@ server {
{% endif %}
{% for ppath, pcfg in paths.items() %}
{% if pcfg.is_management and ppath == '/' %}
# SPA static assets — served from disk, no Flask round-trip.
# no-cache: browsers revalidate every load; unchanged files are 304s.
location /static/ {
alias {{ static_root }}/;
add_header Cache-Control "no-cache" always;
add_header X-Content-Type-Options nosniff always;
add_header Content-Security-Policy "default-src 'none'" always;
}
{% endif %}
{% if pcfg.is_websocket %}
# {{ ppath }} -> {{ pcfg.backend.host }}:{{ pcfg.backend.port }} (WebSocket)
location {{ ppath }} {