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>
This commit is contained in:
Alexandre
2026-08-20 15:37:26 +02:00
committed by GitHub
parent 8ddd7957f8
commit d0ef2fec48
17 changed files with 757 additions and 0 deletions

3
comicarr/CHANGELOG.md Normal file
View File

@@ -0,0 +1,3 @@
## 0.34.0 (20-08-2026)
- Initial release, based on upstream frankieramirez/comicarr 0.34.0 (changelog : https://github.com/frankieramirez/comicarr/releases)
- Home Assistant ingress support through a bundled nginx reverse proxy

124
comicarr/Dockerfile Normal file
View File

@@ -0,0 +1,124 @@
#============================#
# ALEXBELGIUM'S DOCKERFILE #
#============================#
# _.------.
# _.-` ('>.-`"""-.
# '.--'` _'` _ .--.)
# -' '-.-';` `
# ' - _.' ``'--.
# '---` .-'""`
# /`
#=== Home Assistant Addon ===#
#################
# 1 Build Image #
#################
ARG BUILD_FROM
ARG BUILD_VERSION
ARG BUILD_UPSTREAM="0.34.0"
FROM ghcr.io/frankieramirez/comicarr:${BUILD_UPSTREAM}
##################
# 2 Modify Image #
##################
USER root
# No S6_* tuning here : the upstream image is a plain python:3.12-slim with no
# s6-overlay, so the vars the other addons set would be read by nobody
##################
# 3 Install apps #
##################
# Add rootfs
# Absolute paths on purpose : the upstream image sets WORKDIR /opt/comicarr, so
# the relative "find ." used by the other addons would miss /etc entirely
COPY rootfs/ /
RUN find /etc/cont-init.d /etc/services.d -type f \( -name "*.sh" -o -name "run" \) -print -exec chmod +x {} \;
# Uses /bin for compatibility purposes
# hadolint ignore=DL4005
RUN if [ ! -f /bin/sh ] && [ -f /usr/bin/sh ]; then ln -s /usr/bin/sh /bin/sh; fi && \
if [ ! -f /bin/bash ] && [ -f /usr/bin/bash ]; then ln -s /usr/bin/bash /bin/bash; fi
# Modules
ARG MODULES="00-banner.sh 00-global_var.sh 01-custom_script.sh 00-local_mounts.sh 00-smb_mounts.sh"
# Automatic modules download
# Runs before the apps installer on purpose (the repo-wide order) : this script
# bootstraps bash, curl and ca-certificates itself, which the slim base lacks,
# and the apps installer below decides what to install by grepping the modules
# it downloads here
COPY ha_automodules.sh /ha_automodules.sh
RUN chmod 744 /ha_automodules.sh && /ha_automodules.sh "$MODULES" && rm /ha_automodules.sh
# Manual apps
ENV PACKAGES="nginx"
# Automatic apps & bashio
COPY ha_autoapps.sh /ha_autoapps.sh
RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps.sh
################
# 4 Entrypoint #
################
# The upstream image ships no s6-overlay, so ha_entrypoint runs as pid 1 : it
# executes /etc/cont-init.d, then supervises /etc/services.d. This replaces the
# upstream /entrypoint.sh, which services.d/comicarr/run still calls when the
# user asks for an unprivileged uid.
COPY ha_entrypoint.sh /ha_entrypoint.sh
RUN chmod 0755 /ha_entrypoint.sh
ENTRYPOINT ["/ha_entrypoint.sh"]
# Install bashio
COPY bashio-standalone.sh /usr/local/lib/bashio-standalone.sh
RUN chmod 0755 /usr/local/lib/bashio-standalone.sh
############
# 5 Labels #
############
ARG BUILD_ARCH
ARG BUILD_DATE
ARG BUILD_DESCRIPTION
ARG BUILD_NAME
ARG BUILD_REF
ARG BUILD_REPOSITORY
ARG BUILD_VERSION
ENV BUILD_VERSION="${BUILD_VERSION}"
LABEL \
io.hass.name="${BUILD_NAME}" \
io.hass.description="${BUILD_DESCRIPTION}" \
io.hass.arch="${BUILD_ARCH}" \
io.hass.type="addon" \
io.hass.version=${BUILD_VERSION} \
maintainer="alexbelgium (https://github.com/alexbelgium)" \
org.opencontainers.image.title="${BUILD_NAME}" \
org.opencontainers.image.description="${BUILD_DESCRIPTION}" \
org.opencontainers.image.vendor="Home Assistant Add-ons" \
org.opencontainers.image.authors="alexbelgium (https://github.com/alexbelgium)" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.url="https://github.com/alexbelgium" \
org.opencontainers.image.source="https://github.com/${BUILD_REPOSITORY}" \
org.opencontainers.image.documentation="https://github.com/${BUILD_REPOSITORY}/blob/main/README.md" \
org.opencontainers.image.created=${BUILD_DATE} \
org.opencontainers.image.revision=${BUILD_REF} \
org.opencontainers.image.version=${BUILD_VERSION}
#################
# 6 Healthcheck #
#################
# First boot runs the alembic migrations against a cold sqlite database, which
# is slow on a low-end arm board : leave it time to settle before failing
ENV HEALTH_PORT="8090" \
HEALTH_URL="/api/health"
HEALTHCHECK \
--interval=30s \
--retries=5 \
--start-period=180s \
--timeout=25s \
CMD curl -A "HealthCheck: Docker/1.0" -s -f "http://127.0.0.1:${HEALTH_PORT}${HEALTH_URL}" >/dev/null 2>&1 || exit 1

95
comicarr/README.md Normal file
View File

@@ -0,0 +1,95 @@
# Home Assistant Add-on: Comicarr
Automated comic book and manga downloader and library manager with a modern React UI.
[Comicarr](https://comicarr.com) is a fork of Mylar3 rebuilt around a React frontend and a
FastAPI backend. You add series, and it watches for new issues, sends them to your download
client, tags them and files them into your library.
## About
- Track comic series and manga, and grab new issues as they are released
- Works with SABnzbd, NZBGet, blackhole and torrent clients
- Metadata from ComicVine and Metron, with automatic tagging
- One-command migration from an existing Mylar3 installation
- OPDS feed for third-party readers
## Installation
1. Add this repository to Home Assistant.
2. Install the **Comicarr** add-on.
3. Start the add-on and open it from the sidebar (ingress), or on port `8090` at
`http://homeassistant:8090`.
4. Complete the first-run setup when the web interface asks for it.
5. Point Comicarr's library and download folders at a persistent location such as
`/media/comics` and `/share/downloads`.
The first start takes longer than usual: the database migrations run against a cold SQLite
database.
## Configuration
| Option | Description |
|--------|-------------|
| `PUID` / `PGID` | Ownership applied to the add-on configuration directory. Defaults to `0` (root). See the note below before changing it. |
| `TZ` | Timezone, e.g. `Europe/Paris`. |
| `localdisks` | Local disks to mount, e.g. `sda1` or a disk label. |
| `networkdisks` | SMB shares to mount, e.g. `//192.168.1.2/comics`. Mounted under `/mnt`. |
| `cifsusername` / `cifspassword` / `cifsdomain` | Credentials for the SMB shares. |
| `smbv1` | Allow the legacy SMBv1 protocol. |
| `env_vars` | Extra environment variables passed to Comicarr. See the [wiki](https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2). |
`COMICARR_LOG_LEVEL` (`0`, `1` or `2`) is a useful `env_vars` entry: it overrides the log
verbosity chosen in Settings on every restart.
With the default `PUID`/`PGID` of `0`, Comicarr runs as root, which is what lets it write to
Home Assistant's root-owned `/media` and `/share`. Setting `PUID` to any other value hands
startup to the upstream entrypoint, which creates a matching user and drops privileges — the
library and download folders then have to be writable by that user. Switching an existing
installation from `0` to an unprivileged uid also leaves the files already written under
`/config/comicarr` owned by root; chown them yourself, or Comicarr will fail the first time it
writes its configuration or database.
The web interface port is fixed at `8090`. Changing **Settings → Interface → port** has no
effect: the add-on forces `8090` on startup, because ingress and the health check are built
around it.
## Ingress and URLs
Comicarr has no url-base setting, so the add-on bundles an nginx reverse proxy that rewrites the
absolute `/assets`, `/api` and `/cache` urls in the served HTML, JavaScript and CSS onto the
ingress path, and replaces the upstream `X-Frame-Options: DENY` and `frame-ancestors 'none'`
headers, which would otherwise leave the panel blank.
Two consequences worth knowing:
- The app's client-side router does not know about the ingress prefix. It rewrites the panel's
address to `/` shortly after loading. Everything keeps working, because every request url is
rewritten to an absolute ingress path — but reloading the panel frame itself (rather than
reopening it from the sidebar) shows Home Assistant instead of Comicarr.
- Two places in the app navigate with `window.location` rather than the router: finishing the
first-run setup, and a session expiring while the dashboard is open. Both leave the panel;
reopening Comicarr from the sidebar recovers.
External clients — OPDS readers in particular — must use the direct `http://homeassistant:8090`
url. Ingress is browser-session based, so those clients cannot authenticate through it.
Do not enable HTTPS inside Comicarr's own settings: the add-on's proxy talks plain HTTP to it on
`127.0.0.1`, and ingress would stop working.
## Data
Comicarr's `config.ini`, database, logs and cover cache live in `/config/comicarr` inside the
add-on, which Home Assistant maps to this add-on's own configuration directory —
`/addon_configs/<repository_id>_comicarr`, browsable with the Filebrowser add-on. They survive
add-on updates. That is the same layout as the upstream `./config:/config` compose volume, so an
existing installation can be copied in as is.
Comic and download folders are **not** stored there. Point them at `/media`, `/share` or a
mounted disk. The `/comics`, `/manga` and `/downloads` paths used by the upstream docker image
are not persistent in Home Assistant — do not use them.
## Support
- [Comicarr upstream project](https://github.com/frankieramirez/comicarr)
- [Add-on repository issues](https://github.com/alexbelgium/hassio-addons/issues)

68
comicarr/apparmor.txt Normal file
View File

@@ -0,0 +1,68 @@
#include <tunables/global>
profile comicarr_addon flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
capability chown,
capability dac_override,
capability dac_read_search,
capability fowner,
capability setgid,
capability setuid,
capability sys_chroot,
capability sys_admin,
file,
signal,
mount,
umount,
remount,
network udp,
network tcp,
network dgram,
network stream,
network inet,
network inet6,
network netlink raw,
network unix dgram,
# Entrypoint stack
/init ix,
/run/{s6,s6-rc*,service}/** ix,
/package/** ix,
/command/** ix,
/run/{,**} rwk,
/dev/tty rw,
/bin/** ix,
/usr/bin/** ix,
/usr/lib/bashio/** ix,
/etc/s6/** rix,
/run/s6/** rix,
/etc/services.d/** rwix,
/etc/cont-init.d/** rwix,
/etc/cont-finish.d/** rwix,
/init rix,
/var/run/** mrwkl,
/var/run/ mrwkl,
/dev/i2c-1 mrwkl,
# Files required
/dev/fuse mrwkl,
/dev/sda1 mrwkl,
/dev/sdb1 mrwkl,
/dev/nvme0 mrwkl,
/dev/nvme1 mrwkl,
/dev/mmcblk0p1 mrwkl,
/dev/* mrwkl,
/tmp/** mrkwl,
# Data access
/data/** rw,
# suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
ptrace (trace,read) peer=docker-default,
# docker daemon confinement requires explicit allow rule for signal
signal (receive) set=(kill,term) peer=/usr/bin/docker,
}

6
comicarr/build.json Normal file
View File

@@ -0,0 +1,6 @@
{
"build_from": {
"aarch64": "ghcr.io/frankieramirez/comicarr:latest",
"amd64": "ghcr.io/frankieramirez/comicarr:latest"
}
}

105
comicarr/config.yaml Normal file
View File

@@ -0,0 +1,105 @@
arch:
- aarch64
- amd64
description:
Automated comic book and manga downloader and library manager with a modern
React UI
devices:
- /dev/dri
- /dev/dri/card0
- /dev/dri/card1
- /dev/dri/renderD128
- /dev/vchiq
- /dev/video10
- /dev/video11
- /dev/video12
- /dev/video13
- /dev/video14
- /dev/video15
- /dev/video16
- /dev/ttyUSB0
- /dev/sda
- /dev/sdb
- /dev/sdc
- /dev/sdd
- /dev/sde
- /dev/sdf
- /dev/sdg
- /dev/nvme
- /dev/nvme0
- /dev/nvme0n1
- /dev/nvme0n1p1
- /dev/nvme0n1p2
- /dev/nvme0n1p3
- /dev/nvme1n1
- /dev/nvme1n1p1
- /dev/nvme1n1p2
- /dev/nvme1n1p3
- /dev/nvme2n1
- /dev/nvme2n1p1
- /dev/nvme2n1p2
- /dev/nvme2n3p3
- /dev/mmcblk
- /dev/fuse
- /dev/sda1
- /dev/sdb1
- /dev/sdc1
- /dev/sdd1
- /dev/sde1
- /dev/sdf1
- /dev/sdg1
- /dev/sda2
- /dev/sdb2
- /dev/sdc2
- /dev/sdd2
- /dev/sde2
- /dev/sdf2
- /dev/sdg2
- /dev/sda3
- /dev/sdb3
- /dev/sda4
- /dev/sdb4
- /dev/sda5
- /dev/sda6
- /dev/sda7
- /dev/sda8
- /dev/nvme0
- /dev/nvme1
- /dev/nvme2
image: ghcr.io/alexbelgium/comicarr-{arch}
ingress: true
init: false
map:
- addon_config:rw
- media:rw
- share:rw
name: Comicarr
options:
env_vars: []
PGID: 0
PUID: 0
panel_icon: mdi:book-open-page-variant
ports:
8090/tcp: 8090
ports_description:
8090/tcp: Web interface and OPDS feed
privileged:
- SYS_ADMIN
- DAC_READ_SEARCH
schema:
env_vars:
- name: match(^[A-Za-z0-9_]+$)
value: str?
PGID: int
PUID: int
TZ: str?
cifsdomain: str?
cifspassword: str?
cifsusername: str?
localdisks: str?
networkdisks: str?
smbv1: bool?
slug: comicarr
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/comicarr
version: "0.34.0"

BIN
comicarr/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
comicarr/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,28 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
# Comicarr keeps its config.ini, sqlite database, logs and cover cache in the
# datadir it is started with. /config is the addon_config mount, so using
# /config/comicarr reproduces the layout of the upstream compose file's
# "./config:/config" volume : an existing installation can be copied in as is.
CONFIG_LOCATION="/config/comicarr"
bashio::log.info "Config stored in $CONFIG_LOCATION"
mkdir -p "$CONFIG_LOCATION"
# Numbered 20- on purpose : it must sort after 00-global_var.sh, which is what
# exports PUID/PGID from the addon options. The upstream image sets neither, so
# the fallbacks only apply when the module is absent.
# Not recursive : the cover cache under $CONFIG_LOCATION grows to thousands of
# files, and walking it on every boot would delay startup for no gain. This is
# what the upstream entrypoint does too.
chown "${PUID:-0}:${PGID:-0}" /config "$CONFIG_LOCATION"
# The upstream entrypoint installs the timezone when it runs, and the default
# path in services.d/comicarr/run bypasses it, so do it here for both paths.
if [ -n "${TZ:-}" ] && [ -f "/usr/share/zoneinfo/${TZ}" ]; then
ln -sf "/usr/share/zoneinfo/${TZ}" /etc/localtime
echo "${TZ}" > /etc/timezone
fi

View File

@@ -0,0 +1,17 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
#################
# NGINX SETTING #
#################
declare ingress_interface
declare ingress_port
declare ingress_entry
ingress_port=$(bashio::addon.ingress_port)
ingress_interface=$(bashio::addon.ip_address)
ingress_entry=$(bashio::addon.ingress_entry)
sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf
sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf
sed -i "s|%%ingress_entry%%|${ingress_entry}|g" /etc/nginx/servers/ingress.conf

View File

@@ -0,0 +1,96 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

View File

@@ -0,0 +1 @@
resolver 127.0.0.11 ipv6=off;

View File

@@ -0,0 +1,56 @@
# Run nginx in foreground.
daemon off;
# This is run inside Docker.
user root;
# Pid storage location.
pid /var/run/nginx.pid;
# Set number of worker processes.
worker_processes 1;
# Enables the use of JIT for regular expressions to speed-up their processing.
pcre_jit on;
# Write error log to Hass.io add-on log.
error_log /proc/1/fd/1 error;
# Load allowed environment vars
env HASSIO_TOKEN;
# Load dynamic modules.
include /etc/nginx/modules-enabled/*.conf;
# Max num of simultaneous connections by a worker process.
events {
worker_connections 512;
}
http {
include /etc/nginx/includes/mime.types;
log_format hassio '[$time_local] $status '
'$http_x_forwarded_for($remote_addr) '
'$request ($http_user_agent)';
access_log /proc/1/fd/1 hassio;
client_max_body_size 4G;
default_type application/octet-stream;
gzip on;
keepalive_timeout 65;
sendfile on;
server_tokens off;
tcp_nodelay on;
tcp_nopush on;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
include /etc/nginx/includes/resolver.conf;
include /etc/nginx/servers/*.conf;
}

View File

@@ -0,0 +1,82 @@
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;
}
}

View File

@@ -0,0 +1,31 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
# ==============================================================================
# The upstream /entrypoint.sh creates a "comicarr" user out of PUID/PGID and
# gosu's to it, but it runs "useradd -u $PUID" under set -e : with PUID=0 -- the
# default in this repo, and the only value that can write Home Assistant's
# root-owned /media and /share -- useradd refuses the duplicate uid and takes
# the whole container down. So the default path starts the app directly as root,
# the same choice the komga add-on makes, and the upstream entrypoint is used
# only when the user asks for an unprivileged uid.
#
# --port is forced on both paths. HTTP_PORT is writable from the Settings page,
# and changing it there would silently break nginx's proxy_pass and the
# healthcheck, leaving an add-on that looks healthy and serves nothing.
umask "${UMASK:-002}"
if [ "${PUID:-0}" != "0" ]; then
bashio::log.info "Starting Comicarr as ${PUID}:${PGID:-0} ..."
exec /entrypoint.sh --port 8090
fi
bashio::log.info "Starting Comicarr..."
cd /opt/comicarr
exec python /opt/comicarr/Comicarr.py \
--nolaunch \
--datadir /config/comicarr \
--port 8090

View File

@@ -0,0 +1,35 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
# ==============================================================================
# Wait for Comicarr to answer before nginx starts serving ingress. First boot
# runs the alembic migrations against a cold database, so leave a wide margin,
# but poll rather than call bashio::net.wait_for : bashio takes (port host
# timeout) while the bundled bashio-standalone.sh takes (host port timeout), and
# picking the wrong one would either fail instantly or block for the whole
# timeout.
# The per probe timeouts keep the 15 minute ceiling real : without them a half
# open connection would hang a single probe, and the loop, forever.
# A wall clock deadline, not an attempt count : a failed probe costs up to
# max-time on top of the sleep, so counting attempts would stretch the wait to
# roughly twice the advertised ceiling.
comicarr_ready=false
deadline=$((SECONDS + 900))
while [ "$SECONDS" -lt "$deadline" ]; do
if curl -sf --connect-timeout 2 --max-time 5 -o /dev/null "http://127.0.0.1:8090/api/health"; then
comicarr_ready=true
break
fi
sleep 5
done
# Deliberately not fatal : nginx serving a 502 tells the user something is wrong
# and starts working by itself once Comicarr finally answers, while refusing to
# start would take ingress down for good after ha_entrypoint gives up retrying.
if [ "$comicarr_ready" != true ]; then
bashio::log.warning "Comicarr did not answer within 15 minutes. Starting NGinx anyway : ingress will return 502 until it does."
fi
bashio::log.info "Starting NGinx..."
exec nginx

10
comicarr/updater.json Normal file
View File

@@ -0,0 +1,10 @@
{
"github_beta": "false",
"github_fulltag": false,
"last_update": "2026-08-20",
"repository": "alexbelgium/hassio-addons",
"slug": "comicarr",
"source": "github",
"upstream_repo": "frankieramirez/comicarr",
"upstream_version": "0.34.0"
}