From 4f8c0f2ed12fe9eda6ca2596255622da93001fe8 Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Wed, 19 Aug 2026 21:21:55 +0200 Subject: [PATCH] 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. --- kapowarr/CHANGELOG.md | 10 ++ kapowarr/Dockerfile | 112 ++++++++++++++++++ kapowarr/README.md | 81 +++++++++++++ kapowarr/apparmor.txt | 68 +++++++++++ kapowarr/build.json | 6 + kapowarr/config.yaml | 107 +++++++++++++++++ kapowarr/icon.png | Bin 0 -> 20667 bytes kapowarr/logo.png | Bin 0 -> 20667 bytes .../etc/cont-init.d/20-config_location.sh | 35 ++++++ .../etc/cont-init.d/32-nginx_ingress.sh | 17 +++ kapowarr/rootfs/etc/nginx/includes/mime.types | 96 +++++++++++++++ .../rootfs/etc/nginx/includes/resolver.conf | 1 + kapowarr/rootfs/etc/nginx/nginx.conf | 56 +++++++++ .../rootfs/etc/nginx/servers/ingress.conf | 56 +++++++++ kapowarr/rootfs/etc/services.d/kapowarr/run | 36 ++++++ kapowarr/rootfs/etc/services.d/nginx/run | 36 ++++++ kapowarr/updater.json | 9 ++ 17 files changed, 726 insertions(+) create mode 100644 kapowarr/CHANGELOG.md create mode 100644 kapowarr/Dockerfile create mode 100644 kapowarr/README.md create mode 100644 kapowarr/apparmor.txt create mode 100644 kapowarr/build.json create mode 100644 kapowarr/config.yaml create mode 100644 kapowarr/icon.png create mode 100644 kapowarr/logo.png create mode 100755 kapowarr/rootfs/etc/cont-init.d/20-config_location.sh create mode 100755 kapowarr/rootfs/etc/cont-init.d/32-nginx_ingress.sh create mode 100644 kapowarr/rootfs/etc/nginx/includes/mime.types create mode 100644 kapowarr/rootfs/etc/nginx/includes/resolver.conf create mode 100644 kapowarr/rootfs/etc/nginx/nginx.conf create mode 100644 kapowarr/rootfs/etc/nginx/servers/ingress.conf create mode 100755 kapowarr/rootfs/etc/services.d/kapowarr/run create mode 100755 kapowarr/rootfs/etc/services.d/nginx/run create mode 100644 kapowarr/updater.json 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 0000000000000000000000000000000000000000..6334b5e81e15136f9c6fcb572f37cda8b6ca405e GIT binary patch literal 20667 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 literal 0 HcmV?d00001 diff --git a/kapowarr/logo.png b/kapowarr/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6334b5e81e15136f9c6fcb572f37cda8b6ca405e GIT binary patch literal 20667 zcmY&=Wl&aK+cro_r*tUY-6h>E-QCSihqTh2(%sV1NF$xn-Q6YeE}oh1$2X2J>ILj; z?K6(repgbEL`J|zfPjENmX;Dz0iVCV{f37HAA1CihQTLTV_8Wth}XAYIqk&>5D=sg z(qh72JboOvyL+hh-M+pq78m|aAf7@D4h|+KC*`=Fi2I}h{Y9+#9c`X7HX;JG7o;+l zQh4B3()Lgc4Bp&e4EWOESbb@x;^XIkWccDxgfr!LSI-ZA#Kg`;RcTt@=A-ExHtQr$ zLH_5cFTaKE;a>m0KllfnyJMcON%;A7WRgo)25<(Fa?8Y<(!d8MQHPrclBPm^_(}~g zQb;a6`#FkSx*?EsIfq9UZNFaj+ZofB&k;(v`{Q?t_IOt|%Q(4E65o+}D$A zrF!UPNEI02UmvAa+9dLMHgJtBp_V&*m$Y(~u9JpL+sUO5-~W0InLP>^ z?qU%}+qAZ^*?M|Fh#2nc%t$rZ^pA#gc5(4W3_RzNS9yB1iTzrcN8NF_yPJIHjtTWDiln5g zdt5MSDfj`}=-sQR>vM_{oR5mKat$*xv(lA2#$}UxJ-KvkU|=9kJh^Z8Ylq{V1sQhg z>EWT*_1UL&2eYqFPd=!I5#-WDuT$ZL640e6(iI}H){qqNMWk$!7$d^5uZ1)4fj`;7 zRVsv`5Y_ve^__FEe%9AtWi})-yvlKPZYB&72_S!NtDHM<{g!vXj?k-Znm!D_h%{mL zl~O#26SMhCnHUDivb``(_@z3&@PhWovL`HRAG9P$sj|keBV8W^@u7vK(4aS@EiCqy zUXu_`HO>sQ@VcT1xW1^Xt4BZ!dmI?ulz*WWQx+?pUcs#TLK`<^x_|SSy+w}9Itml~ zI1P_{5f!-GBJ-JPhU-sZh3+*M+fDej?I2xXi&sfS=;C9MzYI)j+SgL)=~rtTM|Vge z)B*^Dkp07)lp(WtQsbh5f;w`1cMYk2z1YOc*W=S>I{0ojMn=Zgm*c>*u+BtC1Vlt4 zG38~G!9=E%VRYyXuAtW=b{QJPnoS+$ih0tZk|dmzp+1xMUJIoWj=QYeST9a3NeGrl zheF$=S@V8FC5s|NLHCRuX6lRmv1ptBBy_SxLrWZkQtpnNeyQc_Y{rwQNu z*tHgWEg>Pn&e>R@jnhkOI=l9%e4zyUn&;0{KjT!uFRzV=hVRRdh}ZTAJYw}hlauEj zLTRSv^zZYW3MA@1)G5+X))loWIX_@w@jtI6Bfen34vvl@gF}O(kap%|kC7=9$$ywd zkv0g;+qID{(PiLd=lsKYh8aR)yuSIL+-d{K^Zpy|?EMcPkZddPDu-Hv|#*C91;s zJ9+twL|NtZ)edF5?G379)8Q(=xw-lE;cNu>5i4ZuFKXbJs^tQWe4(Y|d91`U{L97a z(4XS$lgSSPbs0&vkB@aP_mm&mUdoZqw;;zpc5MqEY&-Kfdrr?CQRSXd!N$&>il}pH zXy|BA7}TmW`P5-rAD$W{$Hk#PKi-&}t+xA{s}jE#JU#zo>hTGmd?TVo*rrH=!VLpi&HUIi8N(6qtKI#jEo&0IiB2zoJlmBOO1*>OVw6@ z`uubc!wi;lZDuC1O0U)V{(M8Il%o?BUHIqq8tdem4w~3->3*d?HX89g13&y8wDA7P z=H?~Dt-tG&25!#JpA)jxf3x3dV@3HdE*-h@UV8~lT<=&nXoLDzZ^bq_fxOtgG3l;A zz3FbIi%|%wt9z?N-4@q_81@}h(T`yklD~DPx8vhVTNTcJN<6@s2kC(qS|Ht+jM>9TLei3o8Xf7oet-a2nYzw ztgPLOi;s7lJBk4csO3$lSIH~S)F zq~qPDO(Wb0k1kGR`?UY&mVJ@cL{HvH>hPfAQQ zG`ae0cjb@l%;O6GZZ^iJVzPT#(P)ZrLhTf5Tim4K8%JmHK8&ZQr``(^{%d_{K~9e0 zSL*P8{(42AIjaA1Dc%W%NXG}Omy6GNz-mp3v24iX+jiT0QtRB0DJh}e+}e6Y(?bXe z97>>?o|yO=6NB2((c$6YVQ6d|n3jf@oSbagpnZ6FSW;0@P*xTWmJ2HzTeIgCWwAnU z&fg=FUssmz10gzym2}`9`Z}W=NFbI36J$Tkz3^sk-N|gb#VpqA3ZseW>FUamaBK;< zzQm1;CT{OGVnA=OIx5m>tL(j~%&S@}XPd6C-)|moHNJt2efM7aR zFywkT^Ik`1X^l3C_?>ioDz_2$T$xzpY|uX1WWxNsW;7wsN=b=5 zPi8frtpVe}(4p2G4S#oH>qfgf7kfy8)0;(P0 z?M_^);UiiNo^?>zExIXa-uLO{x};F+>gk;g87L`%yJ(b|r$mj*X^Tt1X=~UMirhOe zU}9z#(%k%TX8rfDy0dqIHYDy@Y-Ds4kIfv(!1o4Qqe^!pKSuBqIr(;n=j`0iJ=yu6 zM=wFBu}*M#*|P~52Bd=#5;o8mlZ_AY;N< zuv*W&-QkDJVWxxGa-6lQe?M5DjMlQw0w|5Hlw;%LyOZS&ey$hdUwbJH`l%miYiV7~ zs46HzzjH?);^X6Ehj%nJH78hDks#XjV?-uv6`n5!NT@!33@{r`22coweF5QzfJ?e3 z8~J855v0ejlP;#5=>w0OBpQ0(d<-_si3rk!CS89qG3Yg)D`cUkb0}is)A=QKlS@w4NuildQXK2*H_Gm4~Aubk8p%!LJc-iS-z6Oze3J*qTq!xAWNciJI+9vB#kCX5>B?%vF5J2*H14YGc8G?o*f{6o+;UC{S&IidgW z-xLzR`+%L{l&$@J55?tD)#82JPsS$ia|38kmz#ssZSLKUhM7K0a}<0#?l+aNNiG7W0#Jtqn%8BM;}gfeNcOJcigx9tYY;OhI9+OLFK6 z(~s+a`w51!0qgISaHLA97`=!1no?3pD!*jnEBOgs(%tC|6cm)SynLS&_15>Szj*A- z;oIA$yzcgkdeQhC;i;*qyK9~OFYOt4AJpCtrm{3J*>t?jtaGk_J1)T2X*QOaU2N12 zfBA0&0wOIft&Q7`&_=MN>q-*cd<7*S6gH^bPNG}Q!{s~mV7WSc?W3fs$?(qQcx`QM zBuT^AS|3C_`FjA=#(dq-85EXoiqWha2MzQ|5%Ap810QcsWJN?oTrfaQgYBnXncO;y z@aWFCGo=4r%nhELi7BSt`|h$2$H36oxmr+op$Dc3U+Xze&(WDrR$ z`I{bt^%$R+C?+pI{Ut}GbbqDQ{i?CLNM4|l1HRaq)^4k z)HE0rKV`zl@K02VS-qn*_<9eNWk+({BC>gx!n8{mN(IikJ?e(O{z@r9xWfFN^)oiP|ks5f@|DZcOf4Vf^;>nT1s3(>`nKNl=X=`h%&Jd9|*mreN z@6bR19bo0Kx}7iIez53863{`L%=ob1!TGMRuu#t1h%n(FiLefKSjmv2)dFInxJ3m$ z4DIP^xlL(VnemirkLOmJNQ--{D!tI zPfe-Zo-GZrC4|);W;*{k$@qiuU!i4rbZ&XO$kICG7qwS@sF}GrR;O;qqUb>*Lw5nZ zhsu&VA(1=c_L`rr#{MHP-QC^k)2KweiL>PzU{-uSp2QW@dg6h!6IJG`-`$O}xjSi< zf1i6zBw*b9aphSxyE8cAn3%u(}O9e9G+-9*;9W=;6>%*sL`6c&dNqEMBq57H1G9**% z+fNxp%PH;@c;Rl()>Sq+#FWW-c<@3*$TyyXVtDub-*a%>1m+n}<`cg1z7J$xnCOi$ ztxwL*CSjCTsSYc)4XiMaDB!ZDGicMN+TMAlpYZVVnktefh$azI@O^d=s7_5wyI65w zLq|jFN>dG^p>Cdi`j2ggPw)HX zE2gW}R-SriUJ)r9ilY?RoJzqUxt-AE`Q*pefo4a&-zFc0&f4h+d@mxGMKi0gol9|j zSH$P4JBQPz!#s~>^<591IJs(VrH6S2_9smintd@bDFtP_oAZ(I9_!9i;^KZ^+~bOf zE~0-}4QUM67~FA|h{XMkGn^9ZD)12Stf7qTl5${{-QIxy+vig}?ZiY?HiPt(jOdr% z7*`ncxeBdUSR`Hp>;miHK&qSUiowJ++Up{_rN&>aty`z33MM9G@87?_<9N_-^I#G1 zy8es?ZEa2P%ZDKw1yX3qoq#~N_^az}pDb3=OuXwe{%SOh2j7bM%FAsV{}BTifnO<) zNQF8-ztquYDpixy%h{v{^Gi#b{P*c~0qgf@prubHMh!F9?1kV{v*x^V;+m_3gC zJ-}G4XFWU` zH;yVB5^;)+ZQL>tHOe*JElH#8_0F$sB34UK?ON<13xz=Sn3|fdZ*PmuWzsIU5%>IM zf)ANWl*Jj3PfHv98XyXKZ#ad?#1{HQluTN>BRr}S%0|O~Y^&!VCfZCKn7*4wN5f@m z<=>*C`{(9Dlju@h4!^t$&8yYMfOQ{Alr<0X2v!w+j;D|%w#K#k8UHj;t_!=O+3ZCT z5Ls|DRjH%ZZBd?pq~7P{;ekd#@GrynMT0H{&9pPHEEFV~pB#Rv%ohhMUG=(nR72su#7rH;6-*_l3G9~2Z9Z|&`cN#qMN zrnp@8qF1dA8t#sy!Rjy$o5h|7={jE#6#Xp~;&NT6!hrt$V8j^=%Lee_DW@=+1b)1F z#eI5`!5KNrETp#$1NJPdib|c7?6{X<$63d}=qv&67fOCk&gA9#QGTv{(lj1t1uFOc zN&Wy*1LU2gcaqg&$_x8Lk!cQ35)DtJQt`c$`C+A%jN*1@!I_I6q_JqI>9@N3#1awk z0s>xUX1;7}9?I^GWlmZ*0LZRtpc5)FEhTQo(MRFV!p6~~U?*;=$?f}y^0xPssDHWa zp@1d$iJ$)mD8Q|~rEul^Y-7-dwhOg=hS~$D0fi2K$TNp|gjIeC@{3~jx3skQz2-o} zGC*o+Y0V*oY|K<4#?02fTdH=6;KPuyMakP;oJO1qn-{(K0~G;qBj-YMn1*vVQ633^cV(P0JvKr;Na1TD{I;hea-li&IV{mxjHm%8?hJD8RE5 zfDaKbv|V)m+Va>T)+oJkr8V#?i;=9i6G|Fy_lx?hgt zpN|{^NyS;jQDI>NzSBTVKp_#7`_GJpw;hGeg>SLZPS(whv!`U zSkjLlgv%={PR`CExk~H1yRcu$ai^KQ&|2N?jkfnM2DjglNk;X|&SG<+zV5aF+8z`Y zg*-&ll*)BxC-`(urjQl6*M7GuiUzH#tNSe?0^mvtiD-Ppui#~D<|BryZJwd7bodO% z{CPN8_%+gW+N11)nkw-HS%3mapgJ2^pg}$D2aS^sS~sew)BMd;n4$9<86H~+qhYjH zbjxLM5bzCgd)b?`{P+^j^V_Z^PRF!2PL$8d@v?tuiNM0b0^na57?^^BS?FL!;PmKlVzI7b(3wS#U=+uadDWrxjAkg9;cf>X5^+{)Y2x~bu^4oNCu{-6Kwugq2c34cXkS~rXktb z*r=$g(l9a>)z_y2fS=#k@QLpqXLaWbZ)0QQ_3=W&X7^HWKXIP>*^7L>iS?gE8dc4D z%eap{T_NOyG>NiRAC|~7mA!)!J%kSJPKO~-lOX-trk+_~;UfcYdBf9ZYnzfqlo93h z{F>sAf3V7MJUi95Mb))u*-2J@mnRNWr!%zhN*kP`xkec;&!Oc}f43>6Dt9*3n_-}6^urhP4ypKH zk=*s}5Kzj??@=8T-Q3X0B%?**#q3x9D$-Lzn^P}3UiF5jf}@*rm=CV5 zW?FJTHkug}K3;1I+a}N`EuWrfb$0+0aym~zELVxyN0a^cZ%9oo?bw8bnD$C?S6iJD z%*re-DZd1AY0X;GA+Ea!|9vE&AbA_F+Y8S*hA(?(YBcZY7?rec$U=pVeEfm;`?2RO9}T0QJD z9G@TUzDW~GDA*G(hGHx)@<*-z!{~Rb9Q^aAdv!Idww4uOo|3Y*2$hfM3pIS|b0`p| z7JDk7;&z5{ENy2n@$jUDg(3A?-6**3P@J15Tp6A27!UB$e#mkwj+fXU3!(M6jLTDS zbK~mh=m0KD&BR0{B$RQ+|m#xiJXU3_?1d7tdOi%7!~PN{ES;w|4c|~8he@sP9o8EHxZY9){Vn5|B}n&wN-aF{rTmkNcrsM z#zufbanE=sGEZ|Kwc2iCV&c@i2eQAvzg#3tc!cc1ax`tiZt z-Mvb`L&nvWJt-+kQ%lRp#6((LoTjG6320Ss0Nw3$dFb5ed2ndxBR6;ImQ!>=p~a`% z?U_{v)YzJCQ>g*!)qZPqRnQ{nU0Wt3(ArvV>CcZ#v-BmUY9FH*CBCZNzJWA4le>hMA0D}X&0};q>NW}cO zl~o6}d5uNMkB?qJmIMk|x3FFJ7?DmYyC;5={Yv-Wzc^G>RFPP`3af&Bp~wUi2V#tR zyyH49doH)fT6y{T?-3BX=cRzf$kOU&-?y{`tinsrf0=jl9lm_ekEp*JlBhJQbq&F> zDC_xFGl=*?>9z#SC;)~utR&7KD|Lu{o-Ah6O_C(|p zw+k(CS~npH2?;(v{{CDg0q_Lj-z_XGXzJ*^G0wL3b`}e{e zCI)}x^QeIkIUmx7k;-!$q+vopsMe=Ux@p9GsirZ(q z7wX^G*oRPw*&*?AD^lD73~IevQ|So&-*96PA3l8W^Ll=UX)zF)FpoE&Phr>26n+;3 z)P58c`M}inc7cy4WDCUEGLoH|jQZDGH3NRP$a>l;!AZ)r2z4mzntqX)l8Qk2f5Tdp z>iN?Lr!3urgM(c_^h`~~1tSog$@b0;i|HU5uz|pc&}p)V{Ao2!3EUnL1qGClkdR0c zp{!Q-GYouU86dT=rbPz9p-$zB!d2-u2S(J9iH4z2@bIK<^n^{9s!|=FobY&EQ&TDY zAf=)CzPV{k?EmDXVz2+ZyE|ToII#BrWdXG6)(UcS1Io&1@j39@+Ms}tOiun|?;nGX zR)-G{%B?#8>yzuBGr=ckDG5&az~G$Lr~>xE{8L|xuW+ouD1qg1eTPy#bTn}{+|_0p_d4y8!fFRqRn;kDd%n35@%g)cA^Q}7o6@)HYHPv3+}s{Yxc6uf zL4yX<@OyN$z5ZTZZTi?pPKQ;wzg2q8{~dj}`7eq8W=8dkx*8yUvdYTn;Ap_im~ODq zz`(#zDxU>2BQ-DY)8pNdyq_=&s2^ZGCL|{I=8A^Jl79!a?*b;GzRyLUxmS2N0*!(^ zWO`K>0l}+neXH-1xw%~S*g;*2X49?B@^X7gDaWyjlsSr2{MVdTGc+jpz;!a|coDSV zwPOyFL7>U<;EAD=TB-bp=T07kx1It8&#t$Dk!V?;^E!)f40nz4G2$xN}v3aKn}7CQZh2e zx5o?H+uH!dOGrz<2S~-|`Q~rQto35Om8kE^%7-QYJq1n@M(uDgX|>XZJiLA}-&HPv z(o2ZTk}y|C0oq()-ewaU7>9{!Lk*mqcz@vd)~o`_rT=tyix8}H`GpcwLgV|?Q|`-d zdy1nf-eCe?mp3z-Va=g;wd}{C_RspZCT-=l-d}&K4T}Fenw$CiH~;TZM#_68CMFpf z87bLzX)P@l0ft%K);UuLF8gI};HWGTtrZ}cUQpqxX(XZ{8h zu_A@cKj3Zy-tv)=5d{$u5v+F!umvnvS~%3yuz?jC(Co7IdvY=kY*c4wXH8JEU`W0V zR^NYatNyRP_RGJ70bn?=((s?Y1qI-BcPg#rNwhT(Lj~l`^>xGO-YkjyB%^$L7_AV( z`OXNJJ%!0aSsaB-!7w>ADaBUl-AZ~+B5I}k2ZP^S^aNa7O<~$3HVxXc#7O|R|Fo2b z@j0BKVf-PaAnr+QZZ0u1`+e}c((>#Kn~!Be4_umDXz&m4P7Vp`sj&^s?4n5Dv$M}6 z#JPQ+H84|FQu=HrPGQ$RoXWCExHJ~|iTz1GLsi*&iU=A8h65F>cOC$x3Ar7=z7_KG z<9{=p{aDxsENpD~Kzs(Fh)*fQ3w0K#P_Rgrg7&9tok9p9k)Z6@&Yojxe*QEZh$hn1 z*1p^bBVo0i$SGoCql-Wldit40V{B}Ee&^70RNXn2BOEkg-SBkYgF2GI6*Dp-_YMl` zdrXXE@pO&fvlpZ8ug{){(NSW`y~`s%e%-r(Aq6hG>*?Xj^Qf{3EN+wEfiS(jy%UVw zKT<^pxScLPNlSA{OG&NYXcRNMJz?3KnZt8(7WuTeLJA9SZ0{}`pHEB%hc!L51laeM>YzzVq2oIsHgEP9FwsFTg0nrC%!dPP z2!JwlOc(e{_3;Lqzsy`*l9UO+lp6rktER55XKgJTw4ru`bzdYN8#4#T4iM%5a=U!_ zcV=R5uMk5lFmZIl&CC1t7pgisvVa#jIXTH?@+JX{qpO$cX!qaQM$-51?}21sxZd^A z)ZF}XQj%n2e_!hEbcF|yP5_OLmzrX*>D1A%vHQS;*J`lFKDD<;^7kPxrAc|P@8R|F zX)CFm&1-DvU!DBUX&3mj)_Q>;TaI$6-s57EuM@>Tr#CAlrRjsmqp($3Ss6DviRkXA zfa%U>oil__&(&ze5&~R{J<_6F*h4H|7n(FlTU-j5R2F)vDZ_fy7(B$RG zlb!SM?QHfZ^8o@(vdH#LKA0{}NKO3~76#Yub(2?7fyBkdwL6|Y9*M_v{)evmsuxp{ zoe4W%BI=WnP_}-%S6poDXH8Aq>h@cF^o;;$=+Lcg*J^HT68A_HqQHe;R(x-ojer-H zH=k_aQUk@oBXXdqNVAi`dsS7FDV6DxvZ{^#C|87t@Y3>flQ9=&H`CPqsug`~*w0vf z!uIy|aDGHD%y{d$xBkGiN5Y#SGW(~2`^4W%+>|R@ObqW0^CZ9??4g(EtqE1c08dAI zWI5tHXH@J8ELs-lZA_n*ZL+quwo{g)c)Bmap$$M40gkg|3~`}GwSI+udwOw}SNPAL z%wo!>`x8dC_ov>=%gfwgM+q zooM+Fbe2|-M7|No<<;A-qyt~Wx;z3Ho6pElVmHTqR3!dVVc(RZi3R#=&9ZClN3DzG ze1}L)lx1BUmAIrrSG`~KWV_0%J9r7<{gm(_3ShTq=@y)zRagn6WIN@p7ws5zX$sm{ zBFS)kc(OO8rL7p;=Kj;vZZ+CyBAQk|i%nDeR$0lw<*?ejx=QTt&xAt68yFMg;Bo;X z7apE1j?BB%Sy@@QczA&IhywQ+$XP&FQ24c7lSX6m%Wm=Hu9u(3zGLlB2XMhZYhgK5g@|Kc+;GO|BijOyexP+DDGXt$KJ zd;3L0qt5OKb}%~>WA}NMWtfP^=ok*_TyfPfZ|#F z#QPM0#O!^0+<|*(nLtWf{TTUt5&MEDYO0Ji)RHNy{b+|#y%embWdEFj`!QpHk+K%56c>}4S8wh}z3InZY9T5Py0P0Oa zNeOFWV)EAGATd-@S}9sxnd@trQ0NTeIBQipLtlkO9}Hgxz=eSbqKy3*!{z1he2oig zsG*pFCBZsXxOqO(idVHhkTEI^-~=6$lF5gdanu9fW3UcAeMIcvDIfBwF(xu1YWeBc z<26_D5}25gA}ud1fh7)P%}DC-QF{~rKmdd&IFQ@*$AQ>iQeExPaq(|A!@=0dNVwDQ zd9WiPh&o&@TQC#&jm9P>G_R0*0XM#N1R2LRV{}m zKp^`1`oUpgS3t>&O;7&=WC|kyML|AR>93Nar6x5IpPT9B_Rw;hX9Ae|MT*&DwPwR5 zB_-=%viZC|--$^`06uXxd5^d!+3IGS+aV0B*^13Ihqg#kYR}7|@BFXb!*|&0+JPZ> z@uYREnrENFjUzM)NGOZ&_P5@V4@t3eaju5W$$yU*?6$dtmASp*h7{5$y0 zDTne|YXrmyH0XddHVd%FMP6T?fgmLuPp z#>A=KVJ!=Un^aX)+Xdj*{!A$*iSIQ!2xS4+_Hs_gp~m$< z<AJ;!F#%+hy2R*bNO?c_A5YbcX;r z4&_v6etT+XRs(<{6Cg3pFD(IL362H@8ymar_2u6itFoPE2DA8RuCfQf^1(qV@%PtZ)CL~Q&?mxN^^{OoRKq-AcV!Oqs zIq2N777#eW6;s|$v&a@NRAKpdv+zYt4GhelKp4c5sw$wr);}=!_x1)O6L5munF#P{ zwCc=}fqC`zEmDsB?2gu zCRsl5Q?DBHi5VgZl1@9qnaB$v|O+ZBey2}6Fq=Yl8&`Q+x#qH<( z$)6q<7~n9iRv!0U(gBnVdQ?E`>QTc`^1@qQ1CnDBLx@l;q?j`M4+=HAFLu7BuzXuX zgM&Hs^@$NU3K!RGOy`<=_O^?^0`%MA8d5uQYvT5@{j(Myz3BbFOadgv>VB$Qug&25 zc>Q~RJ{9O$ZwSlMlKO2gwzd7(-?s!M4Cq2`RaLZVvBJj5$>@YpjB0GQB9KLhK7EpE z^GO3Dc3g6D|L-8Ui`(1Xt8t;?U%zm{_=80ul+xCwwX#b4!SD3rZ3G@4k4)xETFsVW zgE335`zsdkTNZEy!21vlMZybIpi|LWn0eI~w4IXNv{umMc?Z?V4oGAr1CWnFc_d}3bQTd&DjAY(<`vG5`NXT|&@Zo9?`GnVJ#sAa=%4yBX zXZ@?Em)8gQz=_S73mYjJ61k`w=Z1i>W{dIk=(=)(q9XYvDl#(FEI)I6fxJf+<84iP z`ja$nD-0X;>Kt&FWM%obS)XHqaz24vGKhtP5%s2<@@BqqLr|~_o&G{V0(esl2nBso z0Vxy_6{Vr4-v(>`O|8eo3@R_DySe!f3unjJ+FAx=TBHGY9*U>X1$QAv=mi&qJpdRA zFD<13$9%ln9?{<~pAPxnp`Kt=uz62!78 zj1+Wqh+uzH%BK$ka=p-OLeSjY49Fj9gSNbn4?h6-v9V|`H`V@esi}kWH70<}b&WS2 zlxJ`psR2agal2rl)nrff{rh)7ctSaB=5oH!O3TV_P2`FO2Sb}!I4sd0Kw*9OfPsrO z7J9~6XMch)!a#KY6jAMYw&FkCy4U0TIjjLj`Jmc6xIFWYNK{mFEXV9~JYQ}~!<6fL zU&of6VWC3FFAjn_jfo+MtGjh(uN#)fH1FQ^IJ()34@7Squhb!HSh#@gN6pAsm{%5m zdFia({1E%YCqzDj6Ey2@U>8ZN|JA_% z2G6Qg1DC&q>tqqe*lw$rD%+Wu-_Q>+1XL%Mc*F@PsX{Mkt2)|wj z_OC)4xk#k&BJsaxQ*V`S59s}KNbfI)>pYKZs@hnOkas)UzoMXLzG2_nTY1LMfk8dz zx9<%n2O@@s&!)RY_{gr>Yxf2?!OBMq=mfX0I}Gl@V+_x=7I0PcE~ zerqHt{F|l*l3#Cl0$O-|l-szp1uI*kQP#QU3Z1(B4dnJLc3`Hdw(=hPuyVx3N z_PW9N`0?cY`ua_W^!09OsO>9=#J59bz^w5=0V23T%+39-e|b%cMRs_ z$6$+Wyw{BKQ~k=W1qCK4%24kJ9Px|(=S?K7chLmeai+^^=XY31ji^eN&yTPp(L{WH z2@;j+SBpnyxmLF1EG%&V7*0)1y`{xZPfsK91p-c$pLwwA!p8P;D5fJW^ns%cblL(w1pgEDB!$XDmCHR478w_h0OPbeU6|{JTt=gSF%8N-B%n zehdOzoc}doSKK@ER4(&>4-el26Bg7BI8g;4m1xy$$_BpE3CihOUjCLPkUu!gVEXwr z>v*{VDiZ~Tga>8N$;Bnle@)Q!bSlWjA}0T@sLvDnpELEV+a(9G@J|3_%`Yrm0MZM@ zS3H47>FX9R6_u$8OLnmTR8@+0w4L8C@a%xIFtmfr?x08@FF!yS+J8Ac^nWiMpUkLL zyWQkwCDcvI2p;51t|NtiMwX_7XKhXy{b=-jS`?OsEDTJ&1S-W2iw^)&?)^zZ{MGFF z#;J&Su|mg+XWBiZ_aq=KT^Ce|%N33H+SVu1`LR*6UcGU%9ZR9q#~azvfQoFrks2k+JkLfFn$d zQi>w35PLsX9963p>iyYhq1Fu0=&}cQxe;NXHl0bbG9j=vO1{vJWebgi3S{9>JHOvD zNuLJM4(EeS9=XUQcvcWkdB7#0adNu>T#?crJk}8nPA@(qL-xP_I6!S-VPUa*@BeIU z>|a?S0#Z2;ezfJuc-un(q%|=mC!?>2YW9S=KAp|1Po%qVL>ZWxiU-q{o`E3^l>S@F z1|-RRE{CWRldz{bb2zN|EaDw>B}CS@hw8u(4hn2T;%|TE@%)4WCy5I5sgkm?Jpb31 zwyQBdB)sJqAP1-BZF*O0l4#LQhXmgw)Zf}5`O4F+%+B5fur&#%!)a*zE${M$`R+}c z8hAizbqU{ms=R8ID&#MyrY2-VcVsghHzcoBV+@nRWMIBOky~%`7X>IS+qIOUv&YA_ z9S`Q%G+fna7*koV^bdh!dU;avt)q#hY)&>iU2|uhpfnJDnv8(+S47C?_H7X5WWvTK z70@@Jh8oY7slCxvkZq0&fP!On-B)^gdY0yP4|P&R8R%JwXnBMMQ$M zd7~SqKZZon%Zt~#0lz_e6vR>-oxe=Cdn;ah1%P3UK|&JI-j3Mr{a?=?!cSx*J>A{O zzW<_QlRSysaqtj7(67Jow)=}MFbYRz_Okm1mfD+zd{#adM3qcr#CRqa&om0r2M8mb z=lA?RL#kA(_mMa~s?xYVG_{PTMAawpe`1-aC&$A2fPsbv9*4Nzp9}&#@y(b0JBDsq z|Bj$jXShiI2$#p1zIa?WZ`wp};inN)KtRAqmOw_nWQ@ueU1=F>1U2l!3cV(D|JU0{ zBEC3cfqYNDML7`K$}K4|)48pmAvYiS5fxX1$J!i!{|h*dZQWoI(=b&Fe=P5xR2~Jy z=;H1UtQ?*<;SoG@RC~4!_NAKIFRaenQUC?vKKDYyA`?l54OYEX!weU@GwCij z)7U+i!+4sUrX>WRe&9JvBfFDtDJgupCmD=>BOVCkY5D5xob!rQ&doksqSLtA^ETD( z$qdi_Cz+m_YO|{YLf+X>a2MZL;GM@Hp_K!~e*RFpj)yG@E#lcrwOX-JDCJq!V5@#? z)8b4uFS4@Y;utQ`uTD#ggBmmDR#KpLgn-$B7jYh2J$l}PRzQO5t^IfJN&I3|ePHxD zZ*$f#clHn4_#KKIfyfqOpSNukviU6a|W~gHUF$Rw88_6Z&bu|PA+DIx3R5>y%3ckv1 zrvJ^M8l_^EoS`8aP(M>1Z(PM1ZS~v`noCN)fpGj!|21OW!~KO{7cRWwO~y;NnLuHT z0dNk(z`#JR=rb7A+HUdO^z@NT9ruACjj6IWnwHZA!Rvb5y+KAp%}J+x!nb*BYtRbz zq#&Q$@&q3vt@?b!9(*UZj?%1%dWCL;#8xlx8xCeliR6QPak1R-HMU+YgR8=m8VLy5 zagnadX}8aJqk!^-k6nP966Tm);ma;Z@#iBA0bh6?v)i#|dPs0=>OCgNF@gM3h%b=l z0cg|r{SPH7D1+s&d5_2GlCaoMexopj(vgQL0S58I#L0KYNMeCN0GDdLZmdVl+0@IE z?(aQ7(Cc8Om1|>T<2pP>ygW&(^M!4*rN8E+wbIN)^Oqyz+);=oLO9@#9|b$8U{x_e z%(d%qwjAV&DS^+HXhIcd*4i=A=6Pkg)W`tvCIt=6d#$-z`{&MwgW^mez)&}PKFTb_PE2*me7qxT?pE9Q|W67;5$#%*p zK7gnH!DDqlPKl6q^+hMtDr_F~_{zn(^THjP)@Q5~$Py>ktynx#ESx>_P zJUmiZQj+Rop@_D^l6-vZ8jMCx`6BiA7dNpLc1Up;AGm(NgM!2)B@+NZ0s5HbObI%m z&48R!zqz15s4JeHZ1;9|J6p9A!n0MgoTHEY_Q43=h^!+;Pl%h0EMF#I&=)R-&FawR z2j4%$HM?gaz~pPRu>&(2;#T!86+$jsnv4?6npJ$eRLTmm`N4Oe=FB*ODf=13B+dw+9V4$w5P=pSZYE)XLRAN@J93 z_;=0YP+=0U2Ifnmu(-}k<{H6e-3M$qj&qBe%nq=_tFQ3x?o787CzMoFK_H8(wiA5Y{M+z;KF<3~B?KtR zjT;)(q8S)k{g7t7N|IUz`GfgQ4J-M;*eZ>UmHW;4J?R%7>?rl&xmR*W)*Gyq7PsA@ z`&BnHWE>p7mR<6z>01{$xPR>_!G6`m|j2i8h`iE5q za?9|w1&s|P1Rh;jVKfl&E{?DEkLEVIAL{toOrb+y9O!cXu=Gs2u`{&s`t;P3jcV^Cg=R;{Cg|6r9$)J<4OLaAXGdOoz2CBN02_wdcq1{&(m|ze8%b_#gS2%E%4I?+Ps!D-1%ZH9x5L$jB7@ z{DDya+%a)+gEKPVPES|q^&TNLuId&pPwt|hn*TVu1>QK=i0!3D%jADx+i~c8PG@9^ zIsdcV#EgsYIBWco{djCvb)6u8@;*8L`O6Y>_xSi3>(Av4gmhT=JAdnbq*zO}Q;(A; z;C5W!v~xRif1j^XjL*uF9_u3n;z5Z)C-HxJnYi4RnrT9Y;`^@nKnDI66SKD!BSZ;e zGe9Z+k@*E5E^M{7^#jN>8gJTzkipwv01B(gbP+W89)Ilkid@{siZhMDaqqkG@(T#O zx1ZWhmdk<+l88-88UyAWkMCo>z<>N*7j@o+as+_kKwNFMW<9mDb)w<3i*I(NjSkxp7h>r>=f>1V z&!eiw*>889PV>I-S=|ofjZAm_VwV(RF^SN+i#o-H1lMoH*?y1FKLVB(8m{0d71Cuu z76Q;{bKuznhdXaprnmpURIqmQd4lJyes(i@4prWEi7m@=2 zg&HX#6e&T5!}z0rbhM8CMz(l5*JX0Lz9t}c;sY=};@xQ?vQ-sUjdS$yGM9c6Fv*JC zq*5WKD17zxC!;MRyIJPv_Olh*8_VEvI=0Bm<6W+ZYa2CXO>z+X0?EM|bqN?6Z1R_4 zf{ef3i?I6kf2K6ZZ+7o+m=xWKkCw4XNKo~4ex*i^5EA=t8{t3RU#fNE@N-#1N380C z^wc|GqLMQ(R8D2J;d@+dMlfA=BQi1jJ-sci8cpXKO@u$bPS%r8MnF8iIh-{)U;phc zHM^#;_98meq-!)7`|-^K(rdkQxgDvDC;-nc7+)nm|kGaWG@AWqpa_;OIh75VO>=S3?h+!N!cnC>S`FPEZubGsT z6rOnEiE_^nlw!52?BBni`1p8EoH)UxNt5vM^rU-_Ugeh8I@^zT`}z6NIM9ft*8N;&|+_Sn_i1Xa28UyT;6!GZ{5%6bBC-qe+vd^zHjJW5$fZ)62Wk zb^){+ShYarA&-WN&FifSbN*ymX2i(n2@4CsrbuLFW+H@G>F4L?xC_Pa)MyF-ShQ%7 z^`)0y%8(?@d-i-btp9{}7T@`* zQBqREmMvS#=JrydXRu4afBNZK1`HTXQBe`|=FP*?)04u&E3|Ccy4*3HIeVV&-Qsxc zu`XP^m`R7|m?|B|gMxzDzI_{$r%c1w*O#m;GtN3!h7B9W&Yd@8c~etQgIY{_MmD*Z z%;erhKE>FvV+jpu#_-|8@A$?SLa_f}GB1o7N&|0ydiAV$8hB+a^zSaAc)ceJ z=c*I3Z)3X2WO7V${5I~|K3-9j2|zWRawTl;3uRd!Yy0eNRjH>-FT2trJcPs@-?Hh; zEe^G0wOU!RVg*rAQOuYzgF}Z7aWXZP?%lf+9v;rcix)8%O+-dkydG^&O>4>M(W40p ztopwW>{-t?MaJLcMXz4HuoPG-d^yd?$Y%KmE6BTik^KDfQ!StC3;!4lipT4c>RN3N z=p=)|u&TP1N~xA@TzUBU`JK+p%$y3m>u@DWgUxH1lTPVx{pS5-`i)mK2adskgB2cJ zsgud`;&8a8jWOp zX%m|^Z6YHhgDx=<1O)~#p#N|V9ZY8LzQg6#+y28IX3v^N+W8B3c(`-<@{J_WF#}-U z1PS3jO{n@o)!0A>pg>jC;f~nDRm5FC0RW6f;}Ri6m3z;(Ah}@mUgzZM9;GI{C#%)j zIyYGnm503%t}>N($mvP zNJwDn)Ty*@-=3J57#1&HT$bp${l}wp?%avdW5!_ct^6hLjYW%56lmAR-iIW?tbf3~ z37|eSQP|wZRrls2Nz&i_{QOQ;x85yn+)d@1Id|^dLrzXkha^b~s=D+|h!ULgUbLx& zTQq;4qe@?HtN9oP_3ueW))f{lTEutXeTSQyj`8ECmR~a+AsQLbWU2My&+^Kvq^(YgJWu0o6%b00i3nxvFIO zKwh~K<}9&$h{IvyIBqoGueJzs}b>zpMC5n?GJk; zroACZt9l!qfdl$b$F)AmM-HKp1qTm1p4E1@Jk_%+1qBwi@7R0G@4Iz^*QRQOkC#@c z9{-Y6{WCRcUy>v%(9>Wrd|L~{Eu+?-0Hyf)`i_+(X=9Bl%X(~{?`mz{8+a_*-@0eL z%+yhK)96b3H26|bP{88Fum7PpA7JyA@5#%%@ud-9gmrI9V$Eyd;}@qW#@#i(`G8PW zbxdvFe7J`JAW4#X{rdG`K#d2jxhN3}QF8{D%g zpcEm5)@(K}2cEr~b|fVmO~EpOWetTzinQ!gh1HwjW-aE?qlQp$?Ke)IJkHUhC+~9P z)g!zqj2i+^#Y#fR4Xx@!lbnUVKfHVh5XeIFlEYQ;$k1CQ%tv77IqYo5XwCUU0-zML**rlA@m8($s}>>`{|C8nU5WJD z0q65svY5T%Z&E{VJ>PEmnk>ikma2s;!^6Q4{U~&9C-Jv72qBzRNgpp1eSfX0i=vih z)yqJ<(P-RL8;w=Q9~uAv85tRUWm#S+gm7FIvkD|zCOKz6V%M=ybcG8=@0fJyyjdd> z_bbxLGjRF>oVfrQIpBx`F4)iH1^L-ivV#2~A{08d(okQopi)<%PL!&;_Ht5PyWwb} zrh^<-6s5nvzrSOead*id9smFrE?j7=(P%aR4yzngg~ejwi!VN>U*8_ocRqnsybFHY zd0n=i(@GR;0IW7y z^01-{6T;gGf;9GNy!Fr4M`M(a#=wXDBvs!xtHq>gP??1Z3+cBHXy@e1<0K=a>mOleYl77@^G^2ccd>j|$tj+%aj81PSJ3G6s zqA0U~$<)Fbg}(^dLWpTbqjBxs?C0Mt_eB7dlAfO4LYC!aK>I2S|8-G?5dQ`y)$|xw zZE{})0NB?Iva+&<2qFGSC61SWK?LxP5aJb+$y5!8<$KG07XYOQA!M`J+z*(EBepg7 zFDkH6Rn-N4etwQ82mV3aw*gR!5JEDW&GEp?faB_je?G;)2H<~RK@x8=iRkhtzMSpdN!L)FUzu<5JHh<`KqEQ7M)I42o!pIdpl;C_kagH-~kVK fzylue7v%o|ON#PmZI6Q$00000NkvXXu0mjftfHGM literal 0 HcmV?d00001 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" +}