Files
hassio-addons/comicarr/rootfs/etc/nginx/servers/ingress.conf
Alexandre d0ef2fec48 feat(comicarr): new add-on for Comicarr with ingress support (#3001)
* feat(comicarr): new add-on with Home Assistant ingress

Comicarr is a fork of Mylar3 with a React frontend and a FastAPI backend.
The upstream image is a plain python:3.12-slim with no s6-overlay, so
ha_entrypoint.sh runs as pid 1 and supervises both the app and nginx —
the same shape the komga add-on uses.

Ingress needs a reverse proxy because the app has no url-base support of
any kind: vite emits absolute /assets urls, the api client and the cover
img tags build absolute /api and /cache urls, and SecurityHeadersMiddleware
sends X-Frame-Options: DENY together with a CSP carrying
frame-ancestors 'none', which alone would leave the panel blank. The
bundled nginx rewrites those paths onto the ingress entry, replaces the
two framing headers with the same policy narrowed to the Home Assistant
origin, scopes the session cookie to the ingress path and drops upstream's
one-year immutable caching for the rewritten assets.

The app is started directly as root by default rather than through the
upstream /entrypoint.sh, which runs useradd -u "$PUID" under set -e and
would exit on this repo's PUID=0 default; that entrypoint is still used
when the user asks for an unprivileged uid. --port 8090 is forced because
the port is writable from the Settings page and changing it there would
silently break both the proxy and the health check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(comicarr): note that switching PUID leaves existing files root-owned

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(comicarr): drop ingress_port, the add-on linter rejects the default

8099 is the Supervisor default, and frenck/action-addon-linter fails with
"'ingress_port' should be removed, it uses a default value". komga omits it
for the same reason; nginx still binds whatever bashio::addon.ingress_port
reports.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(comicarr): 0755 on the entrypoint instead of 777

The rest of the repo uses 777 here, but this add-on is the one that offers a
non-root mode: with PUID set, the app runs as an unprivileged user that could
otherwise rewrite a file docker executes as root on the next start. Nothing
writes to /ha_entrypoint.sh at runtime, so 0755 costs nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 15:37:26 +02:00

83 lines
4.3 KiB
Plaintext

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 <img>
# 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;
}
}