feat(kapowarr): new add-on with Home Assistant ingress support (#2999)

* feat(kapowarr): new add-on with Home Assistant ingress support

Kapowarr is a comic book library manager in the *arr family. The add-on is
built on the upstream image (mrcas/kapowarr), with the repository's standard
nginx ingress scaffolding on top.

Ingress uses the pattern komga and bazarr already use here: Kapowarr renders
absolute urls from its url base, Home Assistant strips its own ingress prefix
before forwarding, so Kapowarr is started with --UrlBase /kapowarr and nginx
rewrites that fixed prefix back onto the ingress entry.

Database and logs go to the add-on configuration directory. Temporary
downloads are symlinked there rather than passed with --TempDownloadFolder,
which upstream re-applies on its own restarts and would keep overwriting a
folder chosen in Settings > Download.

* fix(kapowarr): review fixes from the codex pass

- repair a /app/temp_downloads symlink pointing at the wrong target instead of
  accepting any symlink
- exclude logs and temporary downloads from Home Assistant backups: the temp
  folder now lives in the add-on config directory and can hold gigabytes
- fix the /dev/nvme2n3p3 typo inherited from the copied device list (the
  partition is nvme2n1p3); the same typo is present in the other add-ons
- document that the url base must not be changed, and that a non-zero PUID
  only reaches folders that user can already access
- drop three dead Dockerfile lines (BASHIO_VERSION is overridden inside
  ha_automatic_packages.sh, USER root is a no-op on this image)

* fix(kapowarr): pin host and port too, not just the url base

Found by a Codex review that could read the upstream source.

Kapowarr stores host, port and url base in its database and reads the stored
value whenever the matching flag is absent. Only --UrlBase was passed, so a
host or port changed in Settings > General survived 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 by hand.

All three flags are startup-only upstream, so passing them re-applies the
add-on's values once per container start without fighting the self-restarts
Kapowarr performs after a settings change.
This commit is contained in:
Alexandre
2026-08-19 21:21:55 +02:00
committed by GitHub
parent 2fc1ea84be
commit 4f8c0f2ed1
17 changed files with 726 additions and 0 deletions

10
kapowarr/CHANGELOG.md Normal file
View File

@@ -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

112
kapowarr/Dockerfile Normal file
View File

@@ -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

81
kapowarr/README.md Normal file
View File

@@ -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/<repository_id>_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)

68
kapowarr/apparmor.txt Normal file
View File

@@ -0,0 +1,68 @@
#include <tunables/global>
profile kapowarr_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
kapowarr/build.json Normal file
View File

@@ -0,0 +1,6 @@
{
"build_from": {
"aarch64": "mrcas/kapowarr:v1.3.1",
"amd64": "mrcas/kapowarr:v1.3.1"
}
}

107
kapowarr/config.yaml Normal file
View File

@@ -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"

BIN
kapowarr/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
kapowarr/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -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"

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,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 <ingress_entry>/ 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
# <url_base>/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
# <meta id="url_base" data-value="/kapowarr">, 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";
}
}

View File

@@ -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

View File

@@ -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

9
kapowarr/updater.json Normal file
View File

@@ -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"
}