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:
+43
-1
@@ -304,9 +304,51 @@ class TestGenerateServerConf:
|
||||
}
|
||||
out = nginx.generate_server_conf(cfg)
|
||||
assert "proxy_pass http://127.0.0.1:9090;" in out
|
||||
assert "add_header X-Content-Type-Options" not in out
|
||||
# Server-level security headers come from Flask, not nginx
|
||||
assert "Strict-Transport-Security" not in out
|
||||
assert "Referrer-Policy" not in out
|
||||
assert "wall_mgmt_access.log" in out
|
||||
|
||||
def test_management_static_location(self, temp_data_dir):
|
||||
cfg = {
|
||||
"domain": "mgmt.example.com",
|
||||
"paths": {
|
||||
"/": {
|
||||
"backend": {"host": "127.0.0.1", "port": 9090, "proto": "http"},
|
||||
"is_management": True,
|
||||
},
|
||||
"/ws": {
|
||||
"backend": {"host": "127.0.0.1", "port": 9091, "proto": "http"},
|
||||
"is_websocket": True,
|
||||
},
|
||||
},
|
||||
"force_ssl": True,
|
||||
"cert": "acme",
|
||||
}
|
||||
out = nginx.generate_server_conf(cfg)
|
||||
static_root = str(nginx.PROJECT_DIR / "webui" / "static")
|
||||
assert "location /static/ {" in out
|
||||
assert f"alias {static_root}/;" in out
|
||||
assert 'add_header Cache-Control "no-cache" always;' in out
|
||||
assert "add_header X-Content-Type-Options nosniff always;" in out
|
||||
assert (
|
||||
"add_header Content-Security-Policy \"default-src 'none'\" always;" in out
|
||||
)
|
||||
|
||||
def test_static_location_only_for_management_root(self, temp_data_dir):
|
||||
cfg = {
|
||||
"domain": "app.example.com",
|
||||
"paths": {
|
||||
"/": {
|
||||
"backend": {"host": "10.0.0.1", "port": 80, "proto": "http"},
|
||||
}
|
||||
},
|
||||
"force_ssl": True,
|
||||
"cert": "acme",
|
||||
}
|
||||
out = nginx.generate_server_conf(cfg)
|
||||
assert "location /static/" not in out
|
||||
|
||||
def test_websocket_path(self, temp_data_dir):
|
||||
cfg = {
|
||||
"domain": "mgmt.example.com",
|
||||
|
||||
Reference in New Issue
Block a user