server { listen %%interface%%:%%port%% default_server; client_max_body_size 0; location / { proxy_pass http://127.0.0.1:8090; proxy_http_version 1.1; proxy_set_header Host $http_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_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; # The dashboard subscribes to /api/events/stream over SSE ; buffering # would hold every event back until the buffer fills. proxy_buffering off; proxy_connect_timeout 30m; proxy_send_timeout 30m; proxy_read_timeout 30m; # Comicarr refuses to be framed : SecurityHeadersMiddleware sends # X-Frame-Options: DENY and a CSP carrying frame-ancestors 'none', which # on their own leave the ingress panel blank. Replace both with the same # policy narrowed to the Home Assistant origin that serves the panel. # The CSP below is upstream's list verbatim except for two directives : # frame-ancestors becomes 'self', and img-src takes any https origin # instead of the metadata-provider allowlist upstream compiles into the # header -- that allowlist grows with upstream releases, and a stale copy # kept here would silently stop covers from loading. proxy_hide_header X-Frame-Options; proxy_hide_header Content-Security-Policy; add_header X-Frame-Options "SAMEORIGIN" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'" always; # FastAPI's redirect-slash Location headers are built against the address # nginx talks to and carry no ingress prefix ; the second rule covers an # already relative Location. absolute_redirect off; proxy_redirect http://127.0.0.1:8090/ %%ingress_entry%%/; proxy_redirect / %%ingress_entry%%/; # Keep the session cookie on the ingress path rather than the Home # Assistant root, so it is not sent to Home Assistant itself nor to any # other add-on's ingress panel. Cookies are matched against the request # path, and every request the app makes is rewritten below to sit under # the ingress entry, so this does not cost the session. proxy_cookie_path / %%ingress_entry%%/; # Comicarr has no url-base setting of any kind : vite emits /assets/... # with no base, and the api client, the SSE hook and the cover # tags all build absolute /api/... and /cache/... urls. Ingress strips # its own prefix before forwarding, so the prefix has to be put back # into what the browser sees. Only html (implicit), javascript and css # are scanned -- json responses, cover images and archive bodies stream # through untouched. proxy_set_header Accept-Encoding ""; sub_filter_once off; sub_filter_types application/javascript text/javascript text/css; sub_filter '"/assets/' '"%%ingress_entry%%/assets/'; sub_filter "'/assets/" "'%%ingress_entry%%/assets/"; sub_filter 'url(/assets/' 'url(%%ingress_entry%%/assets/'; sub_filter '"/api/' '"%%ingress_entry%%/api/'; sub_filter "'/api/" "'%%ingress_entry%%/api/"; sub_filter '`/api/' '`%%ingress_entry%%/api/'; sub_filter '"/cache/' '"%%ingress_entry%%/cache/'; sub_filter "'/cache/" "'%%ingress_entry%%/cache/"; sub_filter '`/cache/' '`%%ingress_entry%%/cache/'; sub_filter '"/favicon.ico"' '"%%ingress_entry%%/favicon.ico"'; # Rewritten javascript and css must not be kept under upstream's one # year immutable policy for /assets : those file names are content # hashed upstream, so a change to the rules above would otherwise never # reach a browser that already holds the old transformed bundle. Every # other response is already sent as no-cache by the app, so this # overrides nothing else. proxy_hide_header Cache-Control; add_header Cache-Control "no-cache" always; } }