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 0000000000..dbad99bb82 Binary files /dev/null and b/comicarr/icon.png differ diff --git a/comicarr/logo.png b/comicarr/logo.png new file mode 100644 index 0000000000..4fccc2899b Binary files /dev/null and b/comicarr/logo.png differ 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" +}