diff --git a/kapowarr/CHANGELOG.md b/kapowarr/CHANGELOG.md new file mode 100644 index 0000000000..6ffaf1fef7 --- /dev/null +++ b/kapowarr/CHANGELOG.md @@ -0,0 +1,10 @@ +## 1.3.1 (19-08-2026) + +- Initial release, based on upstream Kapowarr 1.3.1 +- Home Assistant ingress support: Kapowarr is started with `--UrlBase /kapowarr` and nginx rewrites + that prefix onto the ingress path, so the sidebar panel works without any user configuration +- The host, port and URL base are re-applied on every start, so a hosting setting changed by hand + in the web interface is repaired by restarting the add-on rather than breaking it permanently +- Database and logs stored in the add-on configuration directory, so they survive updates +- Temporary downloads redirected to persistent storage (`/config/temp_downloads`) +- `PUID`/`PGID`, `TZ`, `env_vars`, local disk and SMB share mounting supported diff --git a/kapowarr/Dockerfile b/kapowarr/Dockerfile new file mode 100644 index 0000000000..c686b43c4e --- /dev/null +++ b/kapowarr/Dockerfile @@ -0,0 +1,112 @@ +#============================# +# ALEXBELGIUM'S DOCKERFILE # +#============================# +# _.------. +# _.-` ('>.-`"""-. +# '.--'` _'` _ .--.) +# -' '-.-';` ` +# ' - _.' ``'--. +# '---` .-'""` +# /` +#=== Home Assistant Addon ===# + +################# +# 1 Build Image # +################# + +ARG BUILD_FROM +ARG BUILD_VERSION +ARG BUILD_UPSTREAM="1.3.1" +FROM ${BUILD_FROM} + +################## +# 2 Modify Image # +################## + +# No S6_* tuning here : the upstream image is a plain python:slim image 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 /app, 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 {} \; + +# 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 +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 has 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 (/app/entrypoint.sh), which is called again from +# rootfs/etc/services.d/kapowarr/run so its PUID/PGID handling is kept +COPY ha_entrypoint.sh /ha_entrypoint.sh +RUN chmod 777 /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 # +################# + +# Kapowarr is hosted under the /kapowarr url base, see the addon documentation +ENV HEALTH_PORT="5656" \ + HEALTH_URL="/kapowarr/" +HEALTHCHECK \ + --interval=30s \ + --retries=5 \ + --start-period=120s \ + --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/kapowarr/README.md b/kapowarr/README.md new file mode 100644 index 0000000000..7819490f88 --- /dev/null +++ b/kapowarr/README.md @@ -0,0 +1,81 @@ +# Home Assistant Add-on: Kapowarr + +Build and manage a comic book library, fitting in the \*arr suite of software. + +[Kapowarr](https://casvt.github.io/Kapowarr/) tracks the volumes you own, finds the issues you are +missing, downloads them through GetComics and your download clients, and keeps the files renamed +and converted the way you want them. + +## About + +- Import an existing comic collection and match it against ComicVine metadata +- Monitor volumes and automatically search for missing issues +- Direct downloads and Mega links, plus torrent and Usenet clients +- Automatic renaming, converting and file management + +## Installation + +1. Add this repository to Home Assistant. +2. Install the **Kapowarr** add-on. +3. Start the add-on and open it from the sidebar (ingress), or on port `5656` at + `http://homeassistant:5656/kapowarr` — note the `/kapowarr` suffix, see *Ingress and URLs* below. +4. Enter a ComicVine API key under *Settings > Metadata*; Kapowarr cannot search without one. +5. Add a root folder under *Settings > Media Management*, for example `/media/comics` or + `/share/comics`. + +## Configuration + +| Option | Description | +|--------|-------------| +| `PUID` / `PGID` | Ownership applied to the add-on configuration directory, and the user Kapowarr runs as. Defaults to `0` (root). | +| `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 Kapowarr. See the [wiki](https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2). | + +Everything else — root folders, download clients, naming, the ComicVine key — is configured in +Kapowarr's own web interface, not in the add-on options. + +The *host*, *port* and *URL base* fields under *Settings > General* are reserved by the add-on and +should not be changed. The add-on is built around Kapowarr listening on `0.0.0.0:5656` under the +`/kapowarr` URL base, and it sets all three back to those values every time it starts. Changing any +of them breaks the sidebar panel and the direct port until the next add-on restart, which repairs +them. + +When `PUID`/`PGID` are not `0`, Kapowarr runs as that user and can only read and write the root +folders and download folders that user already has access to. The add-on only fixes ownership of +its own configuration directory. + +## Ingress and URLs + +Kapowarr is served from the `/kapowarr` subpath so that it works behind Home Assistant ingress: + +- from the Home Assistant sidebar: ingress, no extra setup +- directly: `http://homeassistant:5656/kapowarr` — `http://homeassistant:5656/` on its own returns + a 404, the subpath is not optional + +External clients that talk to Kapowarr's API must use the direct +`http://homeassistant:5656/kapowarr` url. Ingress is browser-session based, so they cannot +authenticate through it. + +## Data + +Kapowarr's database (`Kapowarr.db`) and logs live in `/config` inside the add-on, which Home +Assistant maps to this add-on's own configuration directory — +`/addon_configs/_kapowarr`, browsable with the Filebrowser add-on. They survive +add-on updates. + +Temporary downloads go to `/config/temp_downloads` by default, so an interrupted download is not +lost when the add-on restarts. That directory is on the Home Assistant data disk: if space there is +tight, point *Settings > Download > Direct download temporary folder* at somewhere roomier such as +`/share/kapowarr_downloads` or a disk mounted through `localdisks`. + +Your comics themselves stay where you put them, under `/media`, `/share` or a mounted disk. + +## Support + +- [Kapowarr upstream project](https://github.com/Casvt/Kapowarr) +- [Kapowarr documentation](https://casvt.github.io/Kapowarr/) +- [Add-on repository issues](https://github.com/alexbelgium/hassio-addons/issues) diff --git a/kapowarr/apparmor.txt b/kapowarr/apparmor.txt new file mode 100644 index 0000000000..47dff27b9d --- /dev/null +++ b/kapowarr/apparmor.txt @@ -0,0 +1,68 @@ + +#include + +profile kapowarr_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/kapowarr/build.json b/kapowarr/build.json new file mode 100644 index 0000000000..b492925980 --- /dev/null +++ b/kapowarr/build.json @@ -0,0 +1,6 @@ +{ + "build_from": { + "aarch64": "mrcas/kapowarr:v1.3.1", + "amd64": "mrcas/kapowarr:v1.3.1" + } +} diff --git a/kapowarr/config.yaml b/kapowarr/config.yaml new file mode 100644 index 0000000000..4147996381 --- /dev/null +++ b/kapowarr/config.yaml @@ -0,0 +1,107 @@ +arch: + - aarch64 + - amd64 +backup_exclude: + - "**/logs/*" + - "**/temp_downloads/*" +description: Comic book library manager, fitting in the *arr suite of software +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/nvme2n1p3 + - /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/kapowarr-{arch} +ingress: true +ingress_entry: kapowarr +init: false +map: + - addon_config:rw + - media:rw + - share:rw +name: Kapowarr +options: + env_vars: [] + PGID: 0 + PUID: 0 +panel_icon: mdi:book-multiple +ports: + 5656/tcp: 5656 +ports_description: + 5656/tcp: Web interface (path /kapowarr) +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: kapowarr +udev: true +url: https://github.com/alexbelgium/hassio-addons/tree/master/kapowarr +version: "1.3.1" diff --git a/kapowarr/icon.png b/kapowarr/icon.png new file mode 100644 index 0000000000..6334b5e81e Binary files /dev/null and b/kapowarr/icon.png differ diff --git a/kapowarr/logo.png b/kapowarr/logo.png new file mode 100644 index 0000000000..6334b5e81e Binary files /dev/null and b/kapowarr/logo.png differ diff --git a/kapowarr/rootfs/etc/cont-init.d/20-config_location.sh b/kapowarr/rootfs/etc/cont-init.d/20-config_location.sh new file mode 100755 index 0000000000..2b002c63d4 --- /dev/null +++ b/kapowarr/rootfs/etc/cont-init.d/20-config_location.sh @@ -0,0 +1,35 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e + +# Kapowarr keeps its database, its logs and its temporary downloads outside the +# image so that they survive the container being recreated. +# +# The database and log folders are passed on the command line (see +# /etc/services.d/kapowarr/run). The temporary download folder is not: upstream +# re-applies --TempDownloadFolder on every start Kapowarr makes, including the +# self-restarts it performs after a hosting change, so passing it would keep +# undoing a folder the user picked in Settings > Download. Symlinking upstream's +# default onto persistent storage gives the same persistence and leaves the +# setting itself entirely to the user. + +CONFIG_LOCATION="/config" +bashio::log.info "Config stored in $CONFIG_LOCATION" + +mkdir -p "$CONFIG_LOCATION/logs" "$CONFIG_LOCATION/temp_downloads" + +# Compared against the target rather than just testing for a symlink, so that a +# link left pointing somewhere else -- by a future upstream image, or by hand -- +# is repaired instead of silently kept. +if [ "$(readlink /app/temp_downloads)" != "$CONFIG_LOCATION/temp_downloads" ]; then + rm -rf /app/temp_downloads + ln -s "$CONFIG_LOCATION/temp_downloads" /app/temp_downloads +fi + +# 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 both to 0, +# so the fallbacks only apply when the module is absent. +# Recursive because a user raising PUID after the first run would otherwise +# leave Kapowarr.db, its -wal/-shm sidecars and the logs owned by the previous +# uid, which sqlite then cannot write. +chown -R "${PUID:-0}:${PGID:-0}" "$CONFIG_LOCATION" diff --git a/kapowarr/rootfs/etc/cont-init.d/32-nginx_ingress.sh b/kapowarr/rootfs/etc/cont-init.d/32-nginx_ingress.sh new file mode 100755 index 0000000000..175e665b40 --- /dev/null +++ b/kapowarr/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/kapowarr/rootfs/etc/nginx/includes/mime.types b/kapowarr/rootfs/etc/nginx/includes/mime.types new file mode 100644 index 0000000000..7c7cdef2d1 --- /dev/null +++ b/kapowarr/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/kapowarr/rootfs/etc/nginx/includes/resolver.conf b/kapowarr/rootfs/etc/nginx/includes/resolver.conf new file mode 100644 index 0000000000..70f4982b9b --- /dev/null +++ b/kapowarr/rootfs/etc/nginx/includes/resolver.conf @@ -0,0 +1 @@ +resolver 127.0.0.11 ipv6=off; diff --git a/kapowarr/rootfs/etc/nginx/nginx.conf b/kapowarr/rootfs/etc/nginx/nginx.conf new file mode 100644 index 0000000000..2ffda7816e --- /dev/null +++ b/kapowarr/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/kapowarr/rootfs/etc/nginx/servers/ingress.conf b/kapowarr/rootfs/etc/nginx/servers/ingress.conf new file mode 100644 index 0000000000..44df44560d --- /dev/null +++ b/kapowarr/rootfs/etc/nginx/servers/ingress.conf @@ -0,0 +1,56 @@ +server { + listen %%interface%%:%%port%% default_server; + + client_max_body_size 0; + + # Kapowarr is mounted under the /kapowarr url base, so Werkzeug's + # DispatcherMiddleware answers 404 at / . Home Assistant opens the ingress + # panel at / unless config.yaml's ingress_entry moves it, so + # bounce / to the url base whichever way the panel was opened. + # absolute_redirect off keeps the Location relative to the HA host instead + # of nginx's own listen address. + location = / { + absolute_redirect off; + return 302 %%ingress_entry%%/kapowarr/; + } + + location / { + add_header Access-Control-Allow-Origin *; + proxy_connect_timeout 30m; + proxy_send_timeout 30m; + proxy_read_timeout 30m; + proxy_pass http://127.0.0.1:5656; + + # Kapowarr streams queue, task and download progress over socket.io at + # /api/socket.io, which must not be buffered or the UI stops + # updating until the buffer fills + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Werkzeug redirects /kapowarr to /kapowarr/ , and the Location it + # produces is absolute against the upstream address nginx talks to. + # proxy_redirect puts it back on the ingress path (the second rule + # covers an already relative Location). + absolute_redirect off; + proxy_redirect http://127.0.0.1:5656/ %%ingress_entry%%/; + proxy_redirect / %%ingress_entry%%/; + + # Kapowarr renders every link and asset url as {{url_base}}/... and + # general.js reads the same value back out of + # , so rewriting the html + # moves the whole SPA -- including its fetch() and socket.io urls -- + # onto the ingress prefix that Home Assistant strips before forwarding. + # sub_filter cannot rewrite a compressed body, hence Accept-Encoding "". + proxy_set_header Accept-Encoding ""; + sub_filter_once off; + # text/html is always filtered ; the pwa manifest is added because + # /manifest.json embeds the url base in start_url, scope, id and icons, + # and it is served as application/manifest+json. Json is deliberately + # not filtered : api payloads carry user file paths that must not be + # rewritten. + sub_filter_types application/manifest+json; + sub_filter "/kapowarr" "%%ingress_entry%%/kapowarr"; + } +} diff --git a/kapowarr/rootfs/etc/services.d/kapowarr/run b/kapowarr/rootfs/etc/services.d/kapowarr/run new file mode 100755 index 0000000000..70321b03f3 --- /dev/null +++ b/kapowarr/rootfs/etc/services.d/kapowarr/run @@ -0,0 +1,36 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e +# ============================================================================== + +# Kapowarr is started through the upstream entrypoint, which is what implements +# PUID/PGID (groupmod -o / usermod -o, then gosu). ha_entrypoint.sh replaced it +# as the container entrypoint so that cont-init.d and nginx could run too, so it +# is called again here rather than reimplemented. Both usermod and groupmod are +# given -o upstream, so a PUID that collides with an existing account is not an +# error. +# +# --UrlBase is what makes ingress work: Kapowarr renders absolute urls, Home +# Assistant strips its own ingress prefix before forwarding, and nginx adds it +# back by rewriting this fixed prefix. See rootfs/etc/nginx/servers/ingress.conf. +# +# --Host and --Port are passed for the same reason, even though they are already +# the upstream defaults: all three are stored in the database, and Kapowarr reads +# the stored value when the flag is absent. Without them, a host or port changed +# in Settings > General would survive every restart and upgrade while nginx and +# the healthcheck stayed pointed at 127.0.0.1:5656 -- a permanent 502 with no way +# back except editing the database. Passing all three makes the whole hosting +# section self repairing. +# +# Upstream applies these three only on a startup, never on the restarts Kapowarr +# performs itself, so they are re-applied once per container start and do not +# fight the user in between. + +bashio::log.info "Starting Kapowarr (served on the /kapowarr path, see the addon documentation)" + +exec /app/entrypoint.sh python3 /app/Kapowarr.py \ + --DatabaseFolder /config \ + --LogFolder /config/logs \ + --Host 0.0.0.0 \ + --Port 5656 \ + --UrlBase /kapowarr diff --git a/kapowarr/rootfs/etc/services.d/nginx/run b/kapowarr/rootfs/etc/services.d/nginx/run new file mode 100755 index 0000000000..87081c8a2f --- /dev/null +++ b/kapowarr/rootfs/etc/services.d/nginx/run @@ -0,0 +1,36 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e +# ============================================================================== + +# Wait for Kapowarr to answer before nginx starts serving ingress. The first +# boot creates the database and runs its migrations, 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 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. +# The probe asks for /kapowarr/ rather than / , because / is served by the empty +# app that DispatcherMiddleware mounts beside the url base and always answers. +kapowarr_ready=false +deadline=$((SECONDS + 300)) +while [ "$SECONDS" -lt "$deadline" ]; do + if curl -sf --connect-timeout 2 --max-time 5 -o /dev/null "http://127.0.0.1:5656/kapowarr/"; then + kapowarr_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 Kapowarr finally answers, while refusing to +# start would take ingress down for good after ha_entrypoint gives up retrying. +if [ "$kapowarr_ready" != true ]; then + bashio::log.warning "Kapowarr did not answer within 5 minutes. Starting NGinx anyway : ingress will return 502 until it does." +fi + +bashio::log.info "Starting NGinx..." +exec nginx diff --git a/kapowarr/updater.json b/kapowarr/updater.json new file mode 100644 index 0000000000..2f8923a107 --- /dev/null +++ b/kapowarr/updater.json @@ -0,0 +1,9 @@ +{ + "github_beta": "false", + "last_update": "2026-08-19", + "repository": "alexbelgium/hassio-addons", + "slug": "kapowarr", + "source": "github", + "upstream_repo": "Casvt/Kapowarr", + "upstream_version": "1.3.1" +}