From d0ef2fec480aa9e5517837a0ba4f497eae3c975e Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Thu, 20 Aug 2026 15:37:26 +0200 Subject: [PATCH] feat(comicarr): new add-on for Comicarr with ingress support (#3001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * docs(comicarr): note that switching PUID leaves existing files root-owned Co-Authored-By: Claude Opus 5 * 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 * 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 --------- Co-authored-by: Claude Opus 5 --- comicarr/CHANGELOG.md | 3 + comicarr/Dockerfile | 124 ++++++++++++++++++ comicarr/README.md | 95 ++++++++++++++ comicarr/apparmor.txt | 68 ++++++++++ comicarr/build.json | 6 + comicarr/config.yaml | 105 +++++++++++++++ comicarr/icon.png | Bin 0 -> 6188 bytes comicarr/logo.png | Bin 0 -> 19280 bytes .../etc/cont-init.d/20-config_location.sh | 28 ++++ .../etc/cont-init.d/32-nginx_ingress.sh | 17 +++ comicarr/rootfs/etc/nginx/includes/mime.types | 96 ++++++++++++++ .../rootfs/etc/nginx/includes/resolver.conf | 1 + comicarr/rootfs/etc/nginx/nginx.conf | 56 ++++++++ .../rootfs/etc/nginx/servers/ingress.conf | 82 ++++++++++++ comicarr/rootfs/etc/services.d/comicarr/run | 31 +++++ comicarr/rootfs/etc/services.d/nginx/run | 35 +++++ comicarr/updater.json | 10 ++ 17 files changed, 757 insertions(+) create mode 100644 comicarr/CHANGELOG.md create mode 100644 comicarr/Dockerfile create mode 100644 comicarr/README.md create mode 100644 comicarr/apparmor.txt create mode 100644 comicarr/build.json create mode 100644 comicarr/config.yaml create mode 100644 comicarr/icon.png create mode 100644 comicarr/logo.png create mode 100644 comicarr/rootfs/etc/cont-init.d/20-config_location.sh create mode 100644 comicarr/rootfs/etc/cont-init.d/32-nginx_ingress.sh create mode 100644 comicarr/rootfs/etc/nginx/includes/mime.types create mode 100644 comicarr/rootfs/etc/nginx/includes/resolver.conf create mode 100644 comicarr/rootfs/etc/nginx/nginx.conf create mode 100644 comicarr/rootfs/etc/nginx/servers/ingress.conf create mode 100644 comicarr/rootfs/etc/services.d/comicarr/run create mode 100644 comicarr/rootfs/etc/services.d/nginx/run create mode 100644 comicarr/updater.json diff --git a/comicarr/CHANGELOG.md b/comicarr/CHANGELOG.md new file mode 100644 index 0000000000..e2b201c73d --- /dev/null +++ b/comicarr/CHANGELOG.md @@ -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 diff --git a/comicarr/Dockerfile b/comicarr/Dockerfile new file mode 100644 index 0000000000..94b4d0f57d --- /dev/null +++ b/comicarr/Dockerfile @@ -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 diff --git a/comicarr/README.md b/comicarr/README.md new file mode 100644 index 0000000000..554004500c --- /dev/null +++ b/comicarr/README.md @@ -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/_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) diff --git a/comicarr/apparmor.txt b/comicarr/apparmor.txt new file mode 100644 index 0000000000..de86715d8a --- /dev/null +++ b/comicarr/apparmor.txt @@ -0,0 +1,68 @@ + +#include + +profile comicarr_addon flags=(attach_disconnected,mediate_deleted) { + #include + + 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, + +} diff --git a/comicarr/build.json b/comicarr/build.json new file mode 100644 index 0000000000..0eb7bb88c6 --- /dev/null +++ b/comicarr/build.json @@ -0,0 +1,6 @@ +{ + "build_from": { + "aarch64": "ghcr.io/frankieramirez/comicarr:latest", + "amd64": "ghcr.io/frankieramirez/comicarr:latest" + } +} diff --git a/comicarr/config.yaml b/comicarr/config.yaml new file mode 100644 index 0000000000..646faca41e --- /dev/null +++ b/comicarr/config.yaml @@ -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" diff --git a/comicarr/icon.png b/comicarr/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dbad99bb8288bd234ecd74e67a328d5b2dd0d155 GIT binary patch literal 6188 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 Xts4tbiiN>{0U-K1#;8(ln~47bTy3e| literal 0 HcmV?d00001 diff --git a/comicarr/logo.png b/comicarr/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4fccc2899bbfb3026fb6700925bee18d3dbb22d3 GIT binary patch literal 19280 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 literal 0 HcmV?d00001 diff --git a/comicarr/rootfs/etc/cont-init.d/20-config_location.sh b/comicarr/rootfs/etc/cont-init.d/20-config_location.sh new file mode 100644 index 0000000000..66a72f9efb --- /dev/null +++ b/comicarr/rootfs/etc/cont-init.d/20-config_location.sh @@ -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 diff --git a/comicarr/rootfs/etc/cont-init.d/32-nginx_ingress.sh b/comicarr/rootfs/etc/cont-init.d/32-nginx_ingress.sh new file mode 100644 index 0000000000..175e665b40 --- /dev/null +++ b/comicarr/rootfs/etc/cont-init.d/32-nginx_ingress.sh @@ -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 diff --git a/comicarr/rootfs/etc/nginx/includes/mime.types b/comicarr/rootfs/etc/nginx/includes/mime.types new file mode 100644 index 0000000000..7c7cdef2d1 --- /dev/null +++ b/comicarr/rootfs/etc/nginx/includes/mime.types @@ -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; +} diff --git a/comicarr/rootfs/etc/nginx/includes/resolver.conf b/comicarr/rootfs/etc/nginx/includes/resolver.conf new file mode 100644 index 0000000000..70f4982b9b --- /dev/null +++ b/comicarr/rootfs/etc/nginx/includes/resolver.conf @@ -0,0 +1 @@ +resolver 127.0.0.11 ipv6=off; diff --git a/comicarr/rootfs/etc/nginx/nginx.conf b/comicarr/rootfs/etc/nginx/nginx.conf new file mode 100644 index 0000000000..2ffda7816e --- /dev/null +++ b/comicarr/rootfs/etc/nginx/nginx.conf @@ -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; +} diff --git a/comicarr/rootfs/etc/nginx/servers/ingress.conf b/comicarr/rootfs/etc/nginx/servers/ingress.conf new file mode 100644 index 0000000000..e69435b4eb --- /dev/null +++ b/comicarr/rootfs/etc/nginx/servers/ingress.conf @@ -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 + # 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; + } +} diff --git a/comicarr/rootfs/etc/services.d/comicarr/run b/comicarr/rootfs/etc/services.d/comicarr/run new file mode 100644 index 0000000000..34beee67b9 --- /dev/null +++ b/comicarr/rootfs/etc/services.d/comicarr/run @@ -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 diff --git a/comicarr/rootfs/etc/services.d/nginx/run b/comicarr/rootfs/etc/services.d/nginx/run new file mode 100644 index 0000000000..020bee09a6 --- /dev/null +++ b/comicarr/rootfs/etc/services.d/nginx/run @@ -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 diff --git a/comicarr/updater.json b/comicarr/updater.json new file mode 100644 index 0000000000..e50c106773 --- /dev/null +++ b/comicarr/updater.json @@ -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" +}