Compare commits

..

1 Commits

Author SHA1 Message Date
claude[bot]
088e4b214f docs(filebrowser_quantum): stop advertising direct access on port 8071
config.yaml declares ingress_port: 8071 but no ports: key, so nothing is
published to the host network. ingress_port is the internal port the
Supervisor ingress proxy connects to on the add-on's private IP, and Home
Assistant only renders the Network card for add-ons that declare ports:.
Direct access at <your-ip>:8071 has therefore never worked; the README was
carried over from the sibling filebrowser add-on, which does declare
ports: 8080/tcp: 8071.

Correct the three README claims rather than publishing a port, since the
app is configured with server.baseURL set to the ingress entry and would
not serve correctly on a plain published port without further work.

Closes #2978

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 03:30:05 +00:00
76 changed files with 107 additions and 2123 deletions

View File

@@ -228,7 +228,7 @@ jobs:
echo "... done"
- name: Commit if needed
uses: EndBug/add-and-commit@v11.0.0
uses: EndBug/add-and-commit@v10
with:
message: "GitHub bot : README updated"
default_author: github_actions

View File

@@ -125,7 +125,7 @@ jobs:
- name: Analyse and fix
if: steps.batch.outputs.count != '0'
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange. The scheduled path

View File

@@ -237,7 +237,7 @@ jobs:
# Get stars evolution
wget -S -O .github/starsevol.svg "https://api.star-history.com/svg?repos=alexbelgium/hassio-addons&type=Date" || true
- name: Commit if needed
uses: EndBug/add-and-commit@v11.0.0
uses: EndBug/add-and-commit@v10
with:
message: "GitHub bot : graphs updated"
default_author: github_actions

View File

@@ -64,7 +64,7 @@ jobs:
fetch-depth: 1
- name: Run Claude Code
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.

View File

@@ -135,7 +135,7 @@ jobs:
- name: Execute the plan
if: steps.bundle.outputs.has_plan == 'true'
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange, which 401s whenever

View File

@@ -59,7 +59,7 @@ jobs:
# Remove issues list
rm issueslist
- name: Commit if needed
uses: EndBug/add-and-commit@v11.0.0
uses: EndBug/add-and-commit@v10
with:
message: "Github bot : issues linked to readme"
default_author: github_actions

View File

@@ -166,7 +166,7 @@ jobs:
id: classify
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
continue-on-error: true
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Without this the action falls back to the OIDC -> Claude App token

View File

@@ -79,7 +79,7 @@ jobs:
- name: Address CodeRabbit comments
if: steps.claim.outputs.go == 'true'
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange, which 401s whenever

View File

@@ -95,7 +95,7 @@ jobs:
- name: Commit sanitize changes
id: sanitize_commit
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: EndBug/add-and-commit@v11.0.0
uses: EndBug/add-and-commit@v10
with:
commit: -u
message: "GitHub bot: sanitize (spaces + LF endings) & chmod [nobuild]"
@@ -410,7 +410,7 @@ jobs:
done
- name: Commit changelog changes
uses: EndBug/add-and-commit@v11.0.0
uses: EndBug/add-and-commit@v10
with:
commit: -u
message: "GitHub bot: changelog [nobuild]"

View File

@@ -18,7 +18,7 @@ jobs:
uses: erclu/check-crlf@v1
- name: Commit if needed
uses: EndBug/add-and-commit@v11.0.0
uses: EndBug/add-and-commit@v10
with:
message: "Github bot : CRLF corrected"
default_author: github_actions
@@ -50,7 +50,7 @@ jobs:
dos2unix -k "$f"
done
- name: Commit if needed
uses: EndBug/add-and-commit@v11.0.0
uses: EndBug/add-and-commit@v10
with:
message: "Github bot : CRLF corrected"
default_author: github_actions

View File

@@ -31,7 +31,7 @@ jobs:
- name: Commit if needed
if: steps.calibre.outputs.markdown != ''
uses: EndBug/add-and-commit@v11.0.0
uses: EndBug/add-and-commit@v10
with:
message: "Github bot : image compressed"
default_author: github_actions

View File

@@ -109,7 +109,7 @@ jobs:
#TOTAL3="$(awk '{SUM+=$2}END{print SUM}' Stats)"
- name: Commit if needed
uses: EndBug/add-and-commit@v11.0.0
uses: EndBug/add-and-commit@v10
with:
default_author: github_actions
message : "Github bot : stats updated"

View File

@@ -56,7 +56,7 @@ If you want to do add the repository manually, please follow the procedure highl
### Number of addons
- In the repository : 143
- In the repository : 141
- Installed : 361103
### Top 3
@@ -309,16 +309,6 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-badge]
![amd64][amd64-badge]
&#10003; ![image](https://api.iconify.design/mdi/book-open-page-variant.svg) [Comicarr](comicarr/) : Automated comic book and manga downloader and library manager with a modern React UI
&emsp;&emsp;![Version](https://img.shields.io/badge/dynamic/yaml?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fcomicarr%2Fconfig.yaml)
![Update](https://img.shields.io/badge/dynamic/json?label=Updated&query=%24.last_update&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fcomicarr%2Fupdater.json)
![aarch64][aarch64-badge]
![amd64][amd64-badge]
![ingress][ingress-badge]
![smb][smb-badge]
![localdisks][localdisks-badge]
&#10003; ![image](https://api.iconify.design/mdi/book-open.svg) [Comixed](comixed/) : managing digital comics
&emsp;&emsp;![Version](https://img.shields.io/badge/dynamic/yaml?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fcomixed%2Fconfig.yaml)
@@ -570,16 +560,6 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-badge]
![amd64][amd64-badge]
&#10003; ![image](https://api.iconify.design/mdi/book-multiple.svg) [Kapowarr](zzz_archived_kapowarr/) : Comic book library manager, fitting in the *arr suite of software
&emsp;&emsp;![Version](https://img.shields.io/badge/dynamic/yaml?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fzzz_archived_kapowarr%2Fconfig.yaml)
![Update](https://img.shields.io/badge/dynamic/json?label=Updated&query=%24.last_update&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fzzz_archived_kapowarr%2Fupdater.json)
![aarch64][aarch64-badge]
![amd64][amd64-badge]
![ingress][ingress-badge]
![smb][smb-badge]
![localdisks][localdisks-badge]
&#10003; [Kometa](kometa/) : Python script to update metadata information for movies, shows, and collections as well as automatically build collections
&emsp;&emsp;![Version](https://img.shields.io/badge/dynamic/yaml?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fkometa%2Fconfig.yaml)

View File

@@ -1,84 +1,4 @@
## 07308545.3 (19-08-2026)
- Fix an incompletely installed Codex CLI, which silently broke every Codex tool call. Since
codex-cli 0.147.0 the CLI does not execute shell commands or file reads itself; it delegates
them to a companion `codex-code-mode-host` binary that it looks up next to its own executable.
`81-codex_cli.sh` downloaded the `codex-<target>.tar.gz` release asset, which contains only the
`codex` executable, so that companion binary was never installed. Measured on the running add-on
(codex-cli 0.147.0, `/data/codex/bin` holding only `.version`, the launcher and `codex-real`):
`codex exec` starts, authenticates and answers, but every tool call fails with
`failed to spawn code-mode host /data/codex/bin/codex-code-mode-host: No such file or directory`
and the run still exits 0 — so Codex answered from the prompt text alone and the failure looked
like success. `--disable code_mode` does not avoid it.
- The installer now downloads the `codex-package-<target>.tar.gz` release asset, which is the
complete package tree upstream's own installer uses, and installs all of it. Not a list of
known file names: whatever the archive contains is moved into place by position, so a helper
added by a future release arrives beside the entrypoint on its own instead of being extracted
and then dropped — cherry-picking today's two binaries works today, but it is the same mistake
at a smaller scale. For release 0.148.0 that means the entrypoint as
`/data/codex/bin/codex-real`, `codex-code-mode-host` beside it, and `codex-package.json`,
`codex-resources/` (bundled bubblewrap and zsh) and `codex-path/` (bundled ripgrep) in
`/data/codex`. The installed tree grows from ~246 MB to ~300 MB, and `/data/codex` is now
explicitly add-on-owned in its entirety: every path the new release ships replaces the
installed copy of that path outright rather than merging into it, so nothing should be kept
there by hand. A path upstream stops shipping altogether is not pruned — it is left behind as
dead weight that the new entrypoint no longer looks for. Codex's own state stays in
`~/.codex` and is never touched.
- An incomplete install is no longer advertised. `82-claude_tools.sh` registers the Codex MCP
server whenever the launcher at `/data/codex/bin/codex` is executable and re-checks nothing
else, and both the launcher and the package tree persist in `/data` independently of each
other. The launcher is therefore now written only for an install that has its executable, its
code-mode host, its package manifest and its version stamp, and is removed together with the
`/usr/local/bin/codex` symlink otherwise. The stamp is part of that test because it is deleted
before the first file of a replacement is moved and written after the last, so a stamp-less
prefix is exactly the tree that may mix two releases. This covers the cases that reach the
launcher without a fresh install: a boot that cannot reach the release metadata and finds a
pre-existing incomplete install, and a launcher left behind by an interrupted replacement.
Nothing under `/data/codex` is deleted beyond that launcher — the executable, the package
tree and the ChatGPT sign-in stay, so a later boot completes the install without another
download or another login.
- That layout is load-bearing, so the install prefix was chosen to satisfy it rather than
changed. Codex canonicalises its own executable path, requires the parent directory to be
named `bin`, and reads the manifest and helper directories from that directory's parent — the
existing `/data/codex/bin` prefix already matches, and the executable's file name is not part
of the contract, so `codex-real` and the subscription-only `codex` launcher wrapping it are
both unchanged, as is the `/usr/local/bin/codex` symlink and the MCP registration.
- Existing installs repair themselves. The "already installed, skip the download" test now also
requires the code-mode host and the package manifest to be present, so an add-on that already
has a working `codex-real` and no helpers reinstalls on the next start instead of staying
quietly broken.
- `claude-tools-doctor.sh` now reports whether the package layout is complete, because the
failure mode this fixes is invisible in `codex --version`, in the version stamp and in the
exit code.
- Known limitation, unchanged by this release and not caused by it: Codex's own Linux sandbox
cannot start in this container. Running both the system `bwrap` 0.8.0 and the bundled one
directly with `--dev-bind / / --unshare-net /bin/true` fails identically with
`bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted`, so it is a container capability
limitation rather than a packaging one. With the shipped `codex_sandbox_mode: workspace-write`
default, tool calls therefore still fail with that bwrap error; `danger-full-access` is the only
mode that currently executes commands, and the container is already the security boundary.
## 07308545.1 (17-08-2026)
- Minor bugs fixed
## 07308543.1 (17-08-2026)
- Fix the "For your security, sign in again" prompt recurring on every restart again. The v1.37
`safeStorage` patch (`86-claude_safestorage.sh` / `claude-safestorage-patch.js`) only knew how
to inject its opt-in after a leading `"use strict"` directive in the app's main bundle, and
refused to patch anything else. Confirmed live on the running add-on (Claude Desktop
1.30096.1): the shipped main bundle (`.vite/build/index.pre.js`) no longer opens with a
`"use strict"` directive at all — it now opens directly with a bare IIFE — so the patcher has
been silently refusing to patch on every boot, `safeStorage.isEncryptionAvailable()` stayed
`false`, and the app's own log kept showing `Encryption not available, returning empty env
vars` exactly as before v1.37. `applyPatch()` now falls back to inserting the opt-in as the
bundle's first real statement when no directive is present, skipping past any leading BOM,
hashbang, or banner comment first so a directive hidden behind a comment is still found and
protected rather than pushed out of position zero. Verified by copying the live production
`app.asar` and running the patcher against it directly: the previously-refused bundle now
patches successfully, the marker lands correctly, a second run reports "Already patched", and
targeted unit tests cover the bare-IIFE, comment-hidden-directive, hashbang, and
unterminated-comment cases.
- One-time step after upgrading, same as v1.37: the previously-stored session is already stale,
so complete one sign-in from a computer; it then persists across restarts.
## 07308545 (2026-08-15)
- Update to latest version from aaddrick/claude-desktop-debian (changelog : https://github.com/aaddrick/claude-desktop-debian/releases)
- Upstream tag : v3.2.2+claude1.30096.1

View File

@@ -209,25 +209,14 @@ registers `codex mcp-server` in both Claude Code and Claude Desktop. A Claude
session can therefore delegate a task to ChatGPT Codex and read its result back
through MCP.
Codex is not baked into the image because its Linux distribution is large and
the feature is off by default. At each startup, the add-on resolves the latest
stable upstream release. It downloads the architecture-specific package into
persistent `/data/codex` only when the installed release is missing, incomplete
or outdated, verifies the GitHub-published SHA-256 digest before extraction or
execution, and validates the staged package with `--version` before it replaces
the installed one. The complete upstream package is installed, not just the
`codex` executable: Codex delegates every shell and file-read tool call to a
companion `codex-code-mode-host` binary that it looks up next to itself, so an
executable installed on its own can answer but can never run anything. If
release metadata or the download is unavailable, startup continues and a
previously working installation is retained.
`/data/codex` belongs to the add-on: everything below it — `bin/`,
`codex-package.json`, `codex-resources/` and `codex-path/` — is replaced as a
unit whenever a new release is installed, so it is not a place to keep files by
hand. Codex's own state (`auth.json`, `config.toml`) lives in `~/.codex` and is
never touched by an install. The installed package is roughly 300 MB, and an
upgrade briefly needs room for the archive and both releases at once.
Codex is not baked into the image because its Linux binary is large and the
feature is off by default. At each startup, the add-on resolves the latest
stable upstream release. It downloads the architecture-specific binary into
persistent `/data/codex/bin` only when the installed release is missing or
outdated, verifies the GitHub-published SHA-256 digest before extraction or
execution, validates the staged binary with `--version`, and replaces the
existing binary atomically. If release metadata or the download is unavailable,
startup continues and a previously working installation is retained.
### Signing in with a ChatGPT subscription

View File

@@ -13,10 +13,8 @@ streamed desktop.
offline until a fresh sign-in was done from a computer). v1.35 switched to
`--password-store=basic` plus a cont-init script that re-syncs the persistent openbox
`autostart` from the image on every boot — **but that flag alone does nothing**, and the bug
survived it untouched. v1.37 added the application-side opt-in the `basic` backend requires;
see "Why v1.35 did not work" below. That patcher then regressed silently when upstream's
bundle output changed shape — fixed again in 07308543.1; see "Why v1.37 stopped working"
below.
survived it untouched. Actually fixed in v1.37, which adds the application-side opt-in the
`basic` backend requires; see "Why v1.35 did not work" below.
- **Planned only:** Problem A (in-desktop browser for OAuth) is intentionally not implemented.
The image ships no browser; complete the login with the user-side workaround below.
@@ -172,40 +170,10 @@ The third row is the one that matters: it is the restart survival this add-on ne
reaches upgrades, not just fresh installs.
4. `gnome-keyring` stays out of the Dockerfile.
### Why v1.37 stopped working
`claude-safestorage-patch.js` only knew how to inject its opt-in *after* a leading
`"use strict"` directive in the app's main bundle, and refused to patch (leaving the app
unpatched and the session un-persisted) if that directive wasn't there. Confirmed live on the
running add-on: Claude Desktop 1.30096.1's main bundle (`.vite/build/index.pre.js`) no longer
opens with a `"use strict"` directive — it now opens directly with a bare IIFE
(`(function(){try{var e=typeof window...`). Upstream's build output changed shape at some point
after v1.37 shipped, the patcher's one injection point stopped existing, and it had been
silently refusing to patch on every boot since — the app's `main.log` kept showing exactly the
same `Encryption not available, returning empty env vars` warning documented above, and the
session went back to not surviving restarts.
`applyPatch()` now falls back to inserting the opt-in as the bundle's first real statement when
no `"use strict"` directive is found, rather than refusing outright. It skips past any leading
BOM, hashbang, or banner comment first (`skipPrologue()`), so a directive hidden behind a
comment is still found and protected instead of being pushed out of the first-statement
position by a naive prepend — Vite/esbuild banners commonly put a license comment ahead of the
directive. A bundle with no directive at all has nothing to protect, so prepending the opt-in
there is unconditionally safe: the injected code is a complete `try{}catch(e){}` statement, and
a statement can never merge with what follows it via ASI the way a bare expression could.
Verified by copying the live production `app.asar` and running the patcher against it directly
(outside the container's boot sequence): the previously-refused bundle now patches
successfully, the marker lands at the front of the main entry, a second run correctly reports
"Already patched" (idempotent), and unit tests cover the bare-IIFE, comment-hidden-directive,
hashbang, and unterminated-comment cases.
### One-time step after upgrading
The previously-stored session is already stale. Complete **one** sign-in from a computer
(mobile still can't finish the OAuth flow itself, per Problem A) — the session then persists
normally and dispatch stays online regardless of which device connects first afterward. This
applies again after the 07308543.1 fix above, since the affected sessions were never persisted
in the first place.
normally and dispatch stays online regardless of which device connects first afterward.
---
@@ -217,10 +185,7 @@ in the first place.
- `claude_desktop/rootfs/etc/cont-init.d/86-claude_safestorage.sh` and
`claude_desktop/rootfs/usr/local/bin/claude-safestorage-patch.js` — new in v1.37; the
app-side `safeStorage` opt-in that makes `--password-store=basic` actually take effect.
`claude-safestorage-patch.js` updated again in 07308543.1 to also patch bundles with no
leading `"use strict"` directive, and to look past leading comments/hashbang when deciding
whether one is present.
- `claude_desktop/Dockerfile` — corrected stale comment (gnome-keyring is not installed).
- `claude_desktop/CHANGELOG.md` / `config.yaml` — v1.35, then v1.37, then 07308543.1.
- `claude_desktop/CHANGELOG.md` / `config.yaml` — v1.35, then v1.37.
Problem A (in-desktop browser for OAuth) remains planned-only; not touched by this change.

View File

@@ -136,5 +136,5 @@ schema:
slug: claude_desktop
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "07308545.3"
version: "07308545"
video: true

View File

@@ -10,20 +10,10 @@ set -o pipefail
# The install prefix is /data/codex, NOT $HOME/.codex/bin: /data is persistent regardless of the
# configurable data_location, and the managed MCP merge treats commands under $HOME as
# user-installed. Codex state (auth.json, config.toml) remains in the runtime user's home.
#
# Codex is distributed as a package tree, not as a lone executable: since 0.147.0 every shell and
# file-read tool call is executed by a companion binary, codex-code-mode-host, that Codex looks up
# next to itself. Upstream publishes that tree as the codex-package-<target> release asset, and the
# whole tree is installed here. Its layout is load-bearing and must not be flattened: Codex
# canonicalises its own executable path, requires the parent directory to be named `bin`, and then
# reads codex-package.json, codex-resources/ and codex-path/ from that directory's parent. The
# executable's file name is not part of that contract, which is why codex-real keeps its name.
CODEX_ROOT="/data/codex"
CODEX_PREFIX="${CODEX_ROOT}/bin"
CODEX_BIN="${CODEX_PREFIX}/codex"
CODEX_REAL="${CODEX_PREFIX}/codex-real"
CODEX_HOST="${CODEX_PREFIX}/codex-code-mode-host"
CODEX_MANIFEST="${CODEX_ROOT}/codex-package.json"
CODEX_STAMP="${CODEX_PREFIX}/.version"
CODEX_LINK="/usr/local/bin/codex"
CODEX_RELEASE_API="https://api.github.com/repos/openai/codex/releases/latest"
@@ -38,60 +28,6 @@ run_as_runtime_user() {
s6-setuidgid abc env HOME="$RUNTIME_HOME" CODEX_HOME="$RUNTIME_HOME/.codex" "$@"
}
# What "installed" means, in one place. A Codex that is missing its code-mode host, its package
# manifest or its version stamp still starts, authenticates and answers — it simply cannot run a
# single tool call — so presence of the executable alone is not a usable install. The stamp counts
# because it is removed before the first file of a replacement is moved and written after the last,
# so its absence next to an executable means the tree may mix two releases.
codex_install_is_complete() {
[ -x "$CODEX_REAL" ] \
&& [ -x "$CODEX_HOST" ] \
&& [ -f "$CODEX_MANIFEST" ] \
&& [ -f "$CODEX_STAMP" ]
}
# Move a verified package tree from staging into the install prefix. Called only from an `if`
# condition, where `set -e` does not apply, so every step reports failure explicitly.
#
# Whatever the package ships is installed, rather than the file names this add-on happens to know
# about today: a helper added by a future release has to arrive beside codex-real on its own, or it
# fails exactly the way the missing code-mode host did. Only paths the archive actually contains are
# touched — /data/codex also holds this install's staging directory, so the tree below it is never
# cleared wholesale.
#
# The long, failure-prone part of an install — the download and its digest check — is already done
# by the time this runs; what is left is same-filesystem renames of an already validated tree. They
# are not one atomic operation, so the version stamp is removed first: any interruption leaves a
# stamp-less prefix, which the next boot treats as "not installed" and replaces wholesale. The
# entrypoint is moved last, so a prefix whose codex-real is the new release is a prefix whose
# helper binaries are the new release too.
install_codex_package() {
local staged="$1"
local entry name
rm -f -- "$CODEX_STAMP" || return 1
# Everything beside bin/ first — the manifest and the helper directories (codex-resources/ and
# codex-path/ today, holding bubblewrap, zsh and ripgrep) — then everything the package puts in
# bin/ except the entrypoint, then the entrypoint. The existing launcher and version stamp are
# never matched: the launcher is skipped by name and the stamp is a dot file.
for entry in "${staged}"/*; do
name="${entry##*/}"
if [ ! -e "$entry" ] || [ "$name" = "bin" ]; then
continue
fi
rm -rf -- "${CODEX_ROOT:?}/${name}" || return 1
mv -f -- "$entry" "${CODEX_ROOT}/${name}" || return 1
done
for entry in "${staged}"/bin/*; do
name="${entry##*/}"
if [ ! -e "$entry" ] || [ "$name" = "codex" ]; then
continue
fi
rm -rf -- "${CODEX_PREFIX:?}/${name}" || return 1
mv -f -- "$entry" "${CODEX_PREFIX}/${name}" || return 1
done
mv -f -- "${staged}/bin/codex" "$CODEX_REAL" || return 1
}
if ! bashio::config.true 'install_codex_cli'; then
# Non-destructive: preserve the binary and completed ChatGPT sign-in for a later re-enable.
# 82-claude_tools.sh removes only the MCP registration and managed guidance.
@@ -108,7 +44,7 @@ case "$(uname -m)" in
;;
esac
CODEX_ASSET="codex-package-${CODEX_TARGET}.tar.gz"
CODEX_ASSET="codex-${CODEX_TARGET}.tar.gz"
mkdir -p "$CODEX_PREFIX"
# Migrate the PR's earlier direct-binary layout to the enforced wrapper layout without another
@@ -180,47 +116,38 @@ PY
fi
if [ -z "$release_info" ]; then
if codex_install_is_complete && run_as_runtime_user "$CODEX_REAL" --version > /dev/null 2>&1; then
if [ -x "$CODEX_REAL" ] && run_as_runtime_user "$CODEX_REAL" --version > /dev/null 2>&1; then
bashio::log.warning "Unable to resolve the latest verified Codex release; keeping the existing install"
else
bashio::log.warning "Unable to resolve the latest verified Codex release; the installed Codex is missing or incomplete and stays unavailable until a boot can reach the release metadata"
bashio::log.warning "Unable to resolve the latest verified Codex release; Codex is unavailable this boot"
exit 0
fi
else
IFS=$'\t' read -r CODEX_WANTED CODEX_SHA256 CODEX_URL <<< "$release_info"
# An install is complete only if the code-mode host and the package manifest are there too:
# every install made before this add-on switched to the package asset has a working codex-real
# and no helpers, and repairs itself here rather than needing a fresh /data. Running the binary
# also rejects one built for another architecture, which a restored backup could leave behind.
if codex_install_is_complete \
if [ -x "$CODEX_REAL" ] \
&& [ "$(cat "$CODEX_STAMP" 2> /dev/null || true)" = "$CODEX_WANTED" ] \
&& run_as_runtime_user "$CODEX_REAL" --version > /dev/null 2>&1; then
bashio::log.info "Codex CLI ${CODEX_WANTED} already installed (latest stable)"
else
bashio::log.info "Installing latest stable Codex CLI ${CODEX_WANTED} (${CODEX_TARGET}); this is a large one-time download"
archive="${codex_tmp}/${CODEX_ASSET}"
staged="${codex_tmp}/package"
extracted="${codex_tmp}/codex-${CODEX_TARGET}"
# Fail open for add-on startup but fail closed for the candidate release: its official
# Fail open for add-on startup but fail closed for the candidate binary: its official
# release digest must match before extraction or execution, and replacement happens only
# after the staged tree is complete and its entrypoint successfully runs. The candidate is
# exercised in staging with its own codex-package.json and helper directories in place, so
# the layout Codex will resolve at runtime is the layout that was validated.
if mkdir -p "$staged" \
&& chmod 0755 "$staged" \
&& curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 10 --max-time 600 \
-o "$archive" "$CODEX_URL" \
# after the staged binary successfully runs.
if curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 10 --max-time 600 \
-o "$archive" "$CODEX_URL" \
&& printf '%s %s\n' "$CODEX_SHA256" "$archive" | sha256sum -c - > /dev/null \
&& tar -xzf "$archive" -C "$staged" \
&& [ -f "${staged}/codex-package.json" ] \
&& [ -f "${staged}/bin/codex" ] \
&& [ -f "${staged}/bin/codex-code-mode-host" ] \
&& chmod 0755 "${staged}/bin/codex" "${staged}/bin/codex-code-mode-host" \
&& run_as_runtime_user "${staged}/bin/codex" --version > /dev/null 2>&1 \
&& install_codex_package "$staged"; then
&& tar -xzf "$archive" -C "$codex_tmp" \
&& [ -f "$extracted" ] \
&& chmod 0755 "$extracted" \
&& run_as_runtime_user "$extracted" --version > /dev/null 2>&1 \
&& mv -f "$extracted" "$CODEX_REAL"; then
printf '%s' "$CODEX_WANTED" > "$CODEX_STAMP"
bashio::log.info "Codex CLI installed: $("$CODEX_REAL" --version 2> /dev/null || echo unknown)"
elif codex_install_is_complete; then
elif [ -x "$CODEX_REAL" ]; then
bashio::log.warning "Verified Codex ${CODEX_WANTED} installation failed; keeping the existing install"
else
bashio::log.warning "Verified Codex ${CODEX_WANTED} installation failed; Codex is unavailable this boot"
@@ -228,16 +155,7 @@ else
fi
fi
# The launcher is the add-on's single "Codex is usable" signal: 82-claude_tools.sh registers the
# Codex MCP server when it is executable and re-checks nothing else. Write it only for a complete
# install, and remove it — together with the PATH symlink — for an incomplete one. Both the launcher
# and the package tree live in /data and survive restarts independently, so a launcher left from an
# earlier boot would otherwise outlive the install it was written for and advertise a Codex whose
# every tool call fails. The executable, the package tree and the ChatGPT sign-in are all left in
# place: a later boot completes the install without another download or another login.
if ! codex_install_is_complete; then
rm -f -- "$CODEX_BIN" "$CODEX_LINK"
bashio::log.warning "Codex is not completely installed; not registering it this boot"
if [ ! -x "$CODEX_REAL" ]; then
exit 0
fi

View File

@@ -128,98 +128,24 @@ function integrityOf(buf, blockSize) {
return { algorithm: 'SHA256', hash: sha256(buf), blockSize, blocks };
}
// Any of the four ECMAScript LineTerminator code points — not just "\n". A //-comment or an ASI
// boundary ends at the first of these, and using a bare "\n" search for that would let a CR- or
// U+2028/U+2029-terminated line swallow real code as "still the comment/still on this line" and
// misplace the insertion point deep inside the bundle instead of before it.
const LINE_TERMINATOR = /[\n\r\u2028\u2029]/;
/* Skip a leading BOM and hashbang line. Only meaningful at byte 0 — called once, before any
* directive scanning. */
function skipBomAndHashbang(source) {
let i = source.charCodeAt(0) === 0xfeff ? 1 : 0; // BOM
if (source.startsWith('#!', i)) {
const m = LINE_TERMINATOR.exec(source.slice(i));
i += m ? m.index + 1 : source.length - i;
}
return i;
}
/* Skip whitespace and comments starting at i. Returns the next index, or -1 for an unterminated
* block comment (caller refuses rather than guesses). */
function skipWhitespaceAndComments(source, i) {
for (;;) {
const rest = source.slice(i);
const ws = /^\s+/.exec(rest);
if (ws) {
i += ws[0].length;
continue;
}
if (rest.startsWith('//')) {
const m = LINE_TERMINATOR.exec(rest);
i += m ? m.index + 1 : rest.length;
continue;
}
if (rest.startsWith('/*')) {
const end = rest.indexOf('*/');
if (end === -1) return -1;
i += end + 2;
continue;
}
return i;
}
}
// A single-line string literal: no raw line terminator in its content (a real one would need an
// escaped line continuation, which this deliberately doesn't special-case — failing to match
// just means the prologue scan below stops there, which is always safe, see applyPatch).
const STRING_LITERAL = /^(['"])(?:\\.|(?!\1)[^\\\n\r\u2028\u2029])*\1/;
/* Scan the bundle's full leading directive prologue: every consecutive ExpressionStatement made
* of nothing but a string literal, per how ECMAScript directives actually work. A directive
* prologue can hold more than one entry, and "use strict" only has to appear *somewhere* in it,
* not first — so this treats every leading directive as needing protection, not just one
* specifically named "use strict". Returns the index right after the full prologue (which is
* also correct as "no prologue, insert here" when there wasn't one), or -1 when a leading string
* literal isn't cleanly terminated as its own statement — ambiguous whether it's a directive at
* all, refused rather than guessed at. */
function scanDirectivePrologue(source, start) {
let i = start;
for (;;) {
const next = skipWhitespaceAndComments(source, i);
if (next === -1) return -1;
const rest = source.slice(next);
const m = STRING_LITERAL.exec(rest);
if (!m) return next; // not a directive; prologue (possibly empty) ends here
const after = rest.slice(m[0].length);
if (after[0] === ';') {
i = next + m[0].length + 1;
} else if (after === '' || LINE_TERMINATOR.test(after[0])) {
i = next + m[0].length;
} else {
return -1; // "use strict" + x and friends: not unambiguously a directive
}
}
}
/* Insert the opt-in right after the bundle's full leading directive prologue (BOM/hashbang, then
* any run of string-literal-only statements — "use strict" among them if present). It must go
* *after* the whole prologue, not just after the first entry: a directive prologue only takes
* effect when its members are the very first statements, so inserting between two of them, or
* ahead of all of them, would silently drop the file out of strict mode just as surely as
* inserting ahead of a lone "use strict" would.
/* Insert the opt-in after the bundle's leading "use strict" directive. It must go *after* it: a
* directive prologue only takes effect as the very first statement, so prepending would silently
* drop the whole main process out of strict mode.
*
* When there is no prologue at all (observed from Claude Desktop 1.30096.1 onward, whose main
* entry opens with a bare IIFE instead), there is nothing to preserve: PATCH lands at the same
* position anyway, as the file's first real statement. A `try{}catch(e){}` statement can never
* merge with whatever follows via ASI — unlike a bare expression, a statement is not a valid
* left-hand side for anything a following token could continue — so this is unconditionally
* safe once placed after any banner comment / hashbang / directive prologue. */
* Returns null — meaning "refuse to patch" — for anything that is not unambiguously a directive.
* `"use strict" + x` is an expression, not a directive, and injecting into it would produce a
* syntax error, so the directive is only accepted when it is terminated by its own semicolon, a
* line break, or end of input. */
function applyPatch(source) {
const start = skipBomAndHashbang(source);
const end = scanDirectivePrologue(source, start);
if (end === -1) return null;
return source.slice(0, end) + PATCH + source.slice(end);
const m = /^\s*(['"])use strict\1(;?)/.exec(source);
if (!m) return null;
const rest = source.slice(m[0].length);
const terminated = m[2] === ';' || rest === '' || /^[\r\n]/.test(rest);
if (!terminated) return null;
// Supply the terminator when the directive relied on ASI; without it the injected code would
// continue the string-literal expression instead of following it.
const sep = m[2] === ';' ? '' : ';';
return source.slice(0, m[0].length) + sep + PATCH + rest;
}
function writeAll(fd, buf) {
@@ -277,7 +203,7 @@ function main() {
const patchedSource = applyPatch(original);
if (patchedSource === null) {
fail(`${mainRel} opens with an ambiguous "use strict"-like string literal; refusing to patch`);
fail(`${mainRel} does not begin with a recognized "use strict" directive; refusing to patch`);
}
const patched = Buffer.from(patchedSource, 'utf8');

View File

@@ -183,16 +183,6 @@ if bashio::config.true 'install_codex_cli'; then
if [ -x "$codex_bin" ]; then
printf '%-30s %s\n' "installed" "$("$codex_bin" --version 2> /dev/null || echo 'FAILED TO RUN')"
printf '%-30s %s\n' "installed version stamp" "$(cat /data/codex/bin/.version 2> /dev/null || echo 'MISSING')"
# Codex runs every shell and file-read tool call through this companion binary. When it is
# absent the CLI still starts, authenticates and answers, but each tool call fails and the
# run still exits 0 — so report it explicitly rather than leaving it to be inferred.
if [ -x /data/codex/bin/codex-code-mode-host ] \
&& [ -f /data/codex/codex-package.json ] \
&& [ -f /data/codex/bin/.version ]; then
printf '%-30s %s\n' "package layout" "complete"
else
printf '%-30s %s\n' "package layout" "INCOMPLETE - tool calls will fail; restart the add-on to reinstall"
fi
printf '%-30s %s\n' "release policy" "latest stable, SHA-256 verified"
printf '%-30s %s\n' "authentication policy" "ChatGPT subscription only"

View File

@@ -1,3 +0,0 @@
## 0.34.0 (20-08-2026)
- Initial release, based on upstream frankieramirez/comicarr 0.34.0 (changelog : https://github.com/frankieramirez/comicarr/releases)
- Home Assistant ingress support through a bundled nginx reverse proxy

View File

@@ -1,124 +0,0 @@
#============================#
# ALEXBELGIUM'S DOCKERFILE #
#============================#
# _.------.
# _.-` ('>.-`"""-.
# '.--'` _'` _ .--.)
# -' '-.-';` `
# ' - _.' ``'--.
# '---` .-'""`
# /`
#=== Home Assistant Addon ===#
#################
# 1 Build Image #
#################
ARG BUILD_FROM
ARG BUILD_VERSION
ARG BUILD_UPSTREAM="0.34.0"
FROM ghcr.io/frankieramirez/comicarr:${BUILD_UPSTREAM}
##################
# 2 Modify Image #
##################
USER root
# No S6_* tuning here : the upstream image is a plain python:3.12-slim with no
# s6-overlay, so the vars the other addons set would be read by nobody
##################
# 3 Install apps #
##################
# Add rootfs
# Absolute paths on purpose : the upstream image sets WORKDIR /opt/comicarr, so
# the relative "find ." used by the other addons would miss /etc entirely
COPY rootfs/ /
RUN find /etc/cont-init.d /etc/services.d -type f \( -name "*.sh" -o -name "run" \) -print -exec chmod +x {} \;
# Uses /bin for compatibility purposes
# hadolint ignore=DL4005
RUN if [ ! -f /bin/sh ] && [ -f /usr/bin/sh ]; then ln -s /usr/bin/sh /bin/sh; fi && \
if [ ! -f /bin/bash ] && [ -f /usr/bin/bash ]; then ln -s /usr/bin/bash /bin/bash; fi
# Modules
ARG MODULES="00-banner.sh 00-global_var.sh 01-custom_script.sh 00-local_mounts.sh 00-smb_mounts.sh"
# Automatic modules download
# Runs before the apps installer on purpose (the repo-wide order) : this script
# bootstraps bash, curl and ca-certificates itself, which the slim base lacks,
# and the apps installer below decides what to install by grepping the modules
# it downloads here
COPY ha_automodules.sh /ha_automodules.sh
RUN chmod 744 /ha_automodules.sh && /ha_automodules.sh "$MODULES" && rm /ha_automodules.sh
# Manual apps
ENV PACKAGES="nginx"
# Automatic apps & bashio
COPY ha_autoapps.sh /ha_autoapps.sh
RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps.sh
################
# 4 Entrypoint #
################
# The upstream image ships no s6-overlay, so ha_entrypoint runs as pid 1 : it
# executes /etc/cont-init.d, then supervises /etc/services.d. This replaces the
# upstream /entrypoint.sh, which services.d/comicarr/run still calls when the
# user asks for an unprivileged uid.
COPY ha_entrypoint.sh /ha_entrypoint.sh
RUN chmod 0755 /ha_entrypoint.sh
ENTRYPOINT ["/ha_entrypoint.sh"]
# Install bashio
COPY bashio-standalone.sh /usr/local/lib/bashio-standalone.sh
RUN chmod 0755 /usr/local/lib/bashio-standalone.sh
############
# 5 Labels #
############
ARG BUILD_ARCH
ARG BUILD_DATE
ARG BUILD_DESCRIPTION
ARG BUILD_NAME
ARG BUILD_REF
ARG BUILD_REPOSITORY
ARG BUILD_VERSION
ENV BUILD_VERSION="${BUILD_VERSION}"
LABEL \
io.hass.name="${BUILD_NAME}" \
io.hass.description="${BUILD_DESCRIPTION}" \
io.hass.arch="${BUILD_ARCH}" \
io.hass.type="addon" \
io.hass.version=${BUILD_VERSION} \
maintainer="alexbelgium (https://github.com/alexbelgium)" \
org.opencontainers.image.title="${BUILD_NAME}" \
org.opencontainers.image.description="${BUILD_DESCRIPTION}" \
org.opencontainers.image.vendor="Home Assistant Add-ons" \
org.opencontainers.image.authors="alexbelgium (https://github.com/alexbelgium)" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.url="https://github.com/alexbelgium" \
org.opencontainers.image.source="https://github.com/${BUILD_REPOSITORY}" \
org.opencontainers.image.documentation="https://github.com/${BUILD_REPOSITORY}/blob/main/README.md" \
org.opencontainers.image.created=${BUILD_DATE} \
org.opencontainers.image.revision=${BUILD_REF} \
org.opencontainers.image.version=${BUILD_VERSION}
#################
# 6 Healthcheck #
#################
# First boot runs the alembic migrations against a cold sqlite database, which
# is slow on a low-end arm board : leave it time to settle before failing
ENV HEALTH_PORT="8090" \
HEALTH_URL="/api/health"
HEALTHCHECK \
--interval=30s \
--retries=5 \
--start-period=180s \
--timeout=25s \
CMD curl -A "HealthCheck: Docker/1.0" -s -f "http://127.0.0.1:${HEALTH_PORT}${HEALTH_URL}" >/dev/null 2>&1 || exit 1

View File

@@ -1,95 +0,0 @@
# Home Assistant Add-on: Comicarr
Automated comic book and manga downloader and library manager with a modern React UI.
[Comicarr](https://comicarr.com) is a fork of Mylar3 rebuilt around a React frontend and a
FastAPI backend. You add series, and it watches for new issues, sends them to your download
client, tags them and files them into your library.
## About
- Track comic series and manga, and grab new issues as they are released
- Works with SABnzbd, NZBGet, blackhole and torrent clients
- Metadata from ComicVine and Metron, with automatic tagging
- One-command migration from an existing Mylar3 installation
- OPDS feed for third-party readers
## Installation
1. Add this repository to Home Assistant.
2. Install the **Comicarr** add-on.
3. Start the add-on and open it from the sidebar (ingress), or on port `8090` at
`http://homeassistant:8090`.
4. Complete the first-run setup when the web interface asks for it.
5. Point Comicarr's library and download folders at a persistent location such as
`/media/comics` and `/share/downloads`.
The first start takes longer than usual: the database migrations run against a cold SQLite
database.
## Configuration
| Option | Description |
|--------|-------------|
| `PUID` / `PGID` | Ownership applied to the add-on configuration directory. Defaults to `0` (root). See the note below before changing it. |
| `TZ` | Timezone, e.g. `Europe/Paris`. |
| `localdisks` | Local disks to mount, e.g. `sda1` or a disk label. |
| `networkdisks` | SMB shares to mount, e.g. `//192.168.1.2/comics`. Mounted under `/mnt`. |
| `cifsusername` / `cifspassword` / `cifsdomain` | Credentials for the SMB shares. |
| `smbv1` | Allow the legacy SMBv1 protocol. |
| `env_vars` | Extra environment variables passed to Comicarr. See the [wiki](https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2). |
`COMICARR_LOG_LEVEL` (`0`, `1` or `2`) is a useful `env_vars` entry: it overrides the log
verbosity chosen in Settings on every restart.
With the default `PUID`/`PGID` of `0`, Comicarr runs as root, which is what lets it write to
Home Assistant's root-owned `/media` and `/share`. Setting `PUID` to any other value hands
startup to the upstream entrypoint, which creates a matching user and drops privileges — the
library and download folders then have to be writable by that user. Switching an existing
installation from `0` to an unprivileged uid also leaves the files already written under
`/config/comicarr` owned by root; chown them yourself, or Comicarr will fail the first time it
writes its configuration or database.
The web interface port is fixed at `8090`. Changing **Settings → Interface → port** has no
effect: the add-on forces `8090` on startup, because ingress and the health check are built
around it.
## Ingress and URLs
Comicarr has no url-base setting, so the add-on bundles an nginx reverse proxy that rewrites the
absolute `/assets`, `/api` and `/cache` urls in the served HTML, JavaScript and CSS onto the
ingress path, and replaces the upstream `X-Frame-Options: DENY` and `frame-ancestors 'none'`
headers, which would otherwise leave the panel blank.
Two consequences worth knowing:
- The app's client-side router does not know about the ingress prefix. It rewrites the panel's
address to `/` shortly after loading. Everything keeps working, because every request url is
rewritten to an absolute ingress path — but reloading the panel frame itself (rather than
reopening it from the sidebar) shows Home Assistant instead of Comicarr.
- Two places in the app navigate with `window.location` rather than the router: finishing the
first-run setup, and a session expiring while the dashboard is open. Both leave the panel;
reopening Comicarr from the sidebar recovers.
External clients — OPDS readers in particular — must use the direct `http://homeassistant:8090`
url. Ingress is browser-session based, so those clients cannot authenticate through it.
Do not enable HTTPS inside Comicarr's own settings: the add-on's proxy talks plain HTTP to it on
`127.0.0.1`, and ingress would stop working.
## Data
Comicarr's `config.ini`, database, logs and cover cache live in `/config/comicarr` inside the
add-on, which Home Assistant maps to this add-on's own configuration directory —
`/addon_configs/<repository_id>_comicarr`, browsable with the Filebrowser add-on. They survive
add-on updates. That is the same layout as the upstream `./config:/config` compose volume, so an
existing installation can be copied in as is.
Comic and download folders are **not** stored there. Point them at `/media`, `/share` or a
mounted disk. The `/comics`, `/manga` and `/downloads` paths used by the upstream docker image
are not persistent in Home Assistant — do not use them.
## Support
- [Comicarr upstream project](https://github.com/frankieramirez/comicarr)
- [Add-on repository issues](https://github.com/alexbelgium/hassio-addons/issues)

View File

@@ -1,68 +0,0 @@
#include <tunables/global>
profile comicarr_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,
}

View File

@@ -1,6 +0,0 @@
{
"build_from": {
"aarch64": "ghcr.io/frankieramirez/comicarr:latest",
"amd64": "ghcr.io/frankieramirez/comicarr:latest"
}
}

View File

@@ -1,105 +0,0 @@
arch:
- aarch64
- amd64
description:
Automated comic book and manga downloader and library manager with a modern
React UI
devices:
- /dev/dri
- /dev/dri/card0
- /dev/dri/card1
- /dev/dri/renderD128
- /dev/vchiq
- /dev/video10
- /dev/video11
- /dev/video12
- /dev/video13
- /dev/video14
- /dev/video15
- /dev/video16
- /dev/ttyUSB0
- /dev/sda
- /dev/sdb
- /dev/sdc
- /dev/sdd
- /dev/sde
- /dev/sdf
- /dev/sdg
- /dev/nvme
- /dev/nvme0
- /dev/nvme0n1
- /dev/nvme0n1p1
- /dev/nvme0n1p2
- /dev/nvme0n1p3
- /dev/nvme1n1
- /dev/nvme1n1p1
- /dev/nvme1n1p2
- /dev/nvme1n1p3
- /dev/nvme2n1
- /dev/nvme2n1p1
- /dev/nvme2n1p2
- /dev/nvme2n3p3
- /dev/mmcblk
- /dev/fuse
- /dev/sda1
- /dev/sdb1
- /dev/sdc1
- /dev/sdd1
- /dev/sde1
- /dev/sdf1
- /dev/sdg1
- /dev/sda2
- /dev/sdb2
- /dev/sdc2
- /dev/sdd2
- /dev/sde2
- /dev/sdf2
- /dev/sdg2
- /dev/sda3
- /dev/sdb3
- /dev/sda4
- /dev/sdb4
- /dev/sda5
- /dev/sda6
- /dev/sda7
- /dev/sda8
- /dev/nvme0
- /dev/nvme1
- /dev/nvme2
image: ghcr.io/alexbelgium/comicarr-{arch}
ingress: true
init: false
map:
- addon_config:rw
- media:rw
- share:rw
name: Comicarr
options:
env_vars: []
PGID: 0
PUID: 0
panel_icon: mdi:book-open-page-variant
ports:
8090/tcp: 8090
ports_description:
8090/tcp: Web interface and OPDS feed
privileged:
- SYS_ADMIN
- DAC_READ_SEARCH
schema:
env_vars:
- name: match(^[A-Za-z0-9_]+$)
value: str?
PGID: int
PUID: int
TZ: str?
cifsdomain: str?
cifspassword: str?
cifsusername: str?
localdisks: str?
networkdisks: str?
smbv1: bool?
slug: comicarr
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/comicarr
version: "0.34.0"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -1,28 +0,0 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
# Comicarr keeps its config.ini, sqlite database, logs and cover cache in the
# datadir it is started with. /config is the addon_config mount, so using
# /config/comicarr reproduces the layout of the upstream compose file's
# "./config:/config" volume : an existing installation can be copied in as is.
CONFIG_LOCATION="/config/comicarr"
bashio::log.info "Config stored in $CONFIG_LOCATION"
mkdir -p "$CONFIG_LOCATION"
# Numbered 20- on purpose : it must sort after 00-global_var.sh, which is what
# exports PUID/PGID from the addon options. The upstream image sets neither, so
# the fallbacks only apply when the module is absent.
# Not recursive : the cover cache under $CONFIG_LOCATION grows to thousands of
# files, and walking it on every boot would delay startup for no gain. This is
# what the upstream entrypoint does too.
chown "${PUID:-0}:${PGID:-0}" /config "$CONFIG_LOCATION"
# The upstream entrypoint installs the timezone when it runs, and the default
# path in services.d/comicarr/run bypasses it, so do it here for both paths.
if [ -n "${TZ:-}" ] && [ -f "/usr/share/zoneinfo/${TZ}" ]; then
ln -sf "/usr/share/zoneinfo/${TZ}" /etc/localtime
echo "${TZ}" > /etc/timezone
fi

View File

@@ -1,17 +0,0 @@
#!/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

@@ -1,96 +0,0 @@
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

@@ -1 +0,0 @@
resolver 127.0.0.11 ipv6=off;

View File

@@ -1,56 +0,0 @@
# 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

@@ -1,82 +0,0 @@
server {
listen %%interface%%:%%port%% default_server;
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:8090;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# The dashboard subscribes to /api/events/stream over SSE ; buffering
# would hold every event back until the buffer fills.
proxy_buffering off;
proxy_connect_timeout 30m;
proxy_send_timeout 30m;
proxy_read_timeout 30m;
# Comicarr refuses to be framed : SecurityHeadersMiddleware sends
# X-Frame-Options: DENY and a CSP carrying frame-ancestors 'none', which
# on their own leave the ingress panel blank. Replace both with the same
# policy narrowed to the Home Assistant origin that serves the panel.
# The CSP below is upstream's list verbatim except for two directives :
# frame-ancestors becomes 'self', and img-src takes any https origin
# instead of the metadata-provider allowlist upstream compiles into the
# header -- that allowlist grows with upstream releases, and a stale copy
# kept here would silently stop covers from loading.
proxy_hide_header X-Frame-Options;
proxy_hide_header Content-Security-Policy;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'" always;
# FastAPI's redirect-slash Location headers are built against the address
# nginx talks to and carry no ingress prefix ; the second rule covers an
# already relative Location.
absolute_redirect off;
proxy_redirect http://127.0.0.1:8090/ %%ingress_entry%%/;
proxy_redirect / %%ingress_entry%%/;
# Keep the session cookie on the ingress path rather than the Home
# Assistant root, so it is not sent to Home Assistant itself nor to any
# other add-on's ingress panel. Cookies are matched against the request
# path, and every request the app makes is rewritten below to sit under
# the ingress entry, so this does not cost the session.
proxy_cookie_path / %%ingress_entry%%/;
# Comicarr has no url-base setting of any kind : vite emits /assets/...
# with no base, and the api client, the SSE hook and the cover <img>
# tags all build absolute /api/... and /cache/... urls. Ingress strips
# its own prefix before forwarding, so the prefix has to be put back
# into what the browser sees. Only html (implicit), javascript and css
# are scanned -- json responses, cover images and archive bodies stream
# through untouched.
proxy_set_header Accept-Encoding "";
sub_filter_once off;
sub_filter_types application/javascript text/javascript text/css;
sub_filter '"/assets/' '"%%ingress_entry%%/assets/';
sub_filter "'/assets/" "'%%ingress_entry%%/assets/";
sub_filter 'url(/assets/' 'url(%%ingress_entry%%/assets/';
sub_filter '"/api/' '"%%ingress_entry%%/api/';
sub_filter "'/api/" "'%%ingress_entry%%/api/";
sub_filter '`/api/' '`%%ingress_entry%%/api/';
sub_filter '"/cache/' '"%%ingress_entry%%/cache/';
sub_filter "'/cache/" "'%%ingress_entry%%/cache/";
sub_filter '`/cache/' '`%%ingress_entry%%/cache/';
sub_filter '"/favicon.ico"' '"%%ingress_entry%%/favicon.ico"';
# Rewritten javascript and css must not be kept under upstream's one
# year immutable policy for /assets : those file names are content
# hashed upstream, so a change to the rules above would otherwise never
# reach a browser that already holds the old transformed bundle. Every
# other response is already sent as no-cache by the app, so this
# overrides nothing else.
proxy_hide_header Cache-Control;
add_header Cache-Control "no-cache" always;
}
}

View File

@@ -1,31 +0,0 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
# ==============================================================================
# The upstream /entrypoint.sh creates a "comicarr" user out of PUID/PGID and
# gosu's to it, but it runs "useradd -u $PUID" under set -e : with PUID=0 -- the
# default in this repo, and the only value that can write Home Assistant's
# root-owned /media and /share -- useradd refuses the duplicate uid and takes
# the whole container down. So the default path starts the app directly as root,
# the same choice the komga add-on makes, and the upstream entrypoint is used
# only when the user asks for an unprivileged uid.
#
# --port is forced on both paths. HTTP_PORT is writable from the Settings page,
# and changing it there would silently break nginx's proxy_pass and the
# healthcheck, leaving an add-on that looks healthy and serves nothing.
umask "${UMASK:-002}"
if [ "${PUID:-0}" != "0" ]; then
bashio::log.info "Starting Comicarr as ${PUID}:${PGID:-0} ..."
exec /entrypoint.sh --port 8090
fi
bashio::log.info "Starting Comicarr..."
cd /opt/comicarr
exec python /opt/comicarr/Comicarr.py \
--nolaunch \
--datadir /config/comicarr \
--port 8090

View File

@@ -1,35 +0,0 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
# ==============================================================================
# Wait for Comicarr to answer before nginx starts serving ingress. First boot
# runs the alembic migrations against a cold database, so leave a wide margin,
# but poll rather than call bashio::net.wait_for : bashio takes (port host
# timeout) while the bundled bashio-standalone.sh takes (host port timeout), and
# picking the wrong one would either fail instantly or block for the whole
# timeout.
# The per probe timeouts keep the 15 minute ceiling real : without them a half
# open connection would hang a single probe, and the loop, forever.
# A wall clock deadline, not an attempt count : a failed probe costs up to
# max-time on top of the sleep, so counting attempts would stretch the wait to
# roughly twice the advertised ceiling.
comicarr_ready=false
deadline=$((SECONDS + 900))
while [ "$SECONDS" -lt "$deadline" ]; do
if curl -sf --connect-timeout 2 --max-time 5 -o /dev/null "http://127.0.0.1:8090/api/health"; then
comicarr_ready=true
break
fi
sleep 5
done
# Deliberately not fatal : nginx serving a 502 tells the user something is wrong
# and starts working by itself once Comicarr finally answers, while refusing to
# start would take ingress down for good after ha_entrypoint gives up retrying.
if [ "$comicarr_ready" != true ]; then
bashio::log.warning "Comicarr did not answer within 15 minutes. Starting NGinx anyway : ingress will return 502 until it does."
fi
bashio::log.info "Starting NGinx..."
exec nginx

View File

@@ -1,10 +0,0 @@
{
"github_beta": "false",
"github_fulltag": false,
"last_update": "2026-08-20",
"repository": "alexbelgium/hassio-addons",
"slug": "comicarr",
"source": "github",
"upstream_repo": "frankieramirez/comicarr",
"upstream_version": "0.34.0"
}

View File

@@ -1,18 +1,7 @@
## 1.5.1.2 (2026-08-19)
- Fix direct access on port 8071, which was broken in 1.5.1.1: the root
redirect pointed at the container-internal port 8072 instead of the
published one, and the page it led to referenced assets under a path the
add-on did not serve, so every asset returned 404. Requests are now passed
through unchanged, with the bare root and the two previously documented
`/filebrowser_quantum` URLs redirected to the app's configured base path.
## 1.5.1.1 (2026-08-16)
- Expose the web UI on host port 8071, reachable at `<your-ip>:8071`
(redirects to `/filebrowser_quantum/`). Direct access is served by a new,
separate nginx vhost that proxies to the same backend Ingress already uses;
Ingress itself, and the app's own base URL, are unchanged.
## 1.5.1.1 (2026-08-17)
- Documentation: the add-on is Ingress-only and does not publish port 8071, so the README no longer advertises direct access at `<your-ip>:8071`
## 1.5.1 (2026-08-08)
- Update to latest version from gtsteffaniak/filebrowser (changelog : https://github.com/gtsteffaniak/filebrowser/releases)

View File

@@ -42,11 +42,11 @@ comparison to installing any other Home Assistant add-on.
1. Click the `Save` button to store your configuration.
1. Start the add-on.
1. Check the logs of the add-on to see if everything went well.
1. Access the web UI through the sidebar or at `<your-ip>:8071`.
1. Access the web UI through the Home Assistant sidebar.
## Configuration
The web UI can be found at `<your-ip>:8071` or through the Home Assistant sidebar when using Ingress. Direct access redirects to the add-on's configured base path, so the address bar will show a longer URL than the one you typed.
The web UI is reached through the Home Assistant sidebar (Ingress). This add-on does not publish a port on your network, so it has no **Network** section in its configuration page and is not reachable at `<your-ip>:8071`.
**Default credentials:**
- Username: `admin`
@@ -69,7 +69,7 @@ The web UI can be found at `<your-ip>:8071` or through the Home Assistant sideba
## Setup
1. Start the add-on and wait for it to initialize.
1. Access the web interface through the Home Assistant sidebar or at `<your-ip>:8071`.
1. Access the web interface through the Home Assistant sidebar.
1. Log in using the default credentials:
- Username: `admin`
- Password: `admin`

View File

@@ -97,10 +97,6 @@ options:
default_user_scope: "/"
panel_admin: false
panel_icon: mdi:file-search
ports:
8072/tcp: 8071
ports_description:
8072/tcp: Web UI port
privileged:
- SYS_ADMIN
- DAC_READ_SEARCH
@@ -118,4 +114,4 @@ schema:
slug: filebrowser_quantum
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "1.5.1.2"
version: "1.5.1.1"

View File

@@ -43,9 +43,6 @@ declare ingress_interface
declare ingress_port
#declare keyfile
# The app's own baseURL is the Supervisor ingress-entry path, unchanged from
# before: FileBrowser emits that prefix as absolute links in its HTML and JS,
# so it is also the path direct ip:port access has to use (see direct.conf).
FB_BASEURL=$(bashio::addon.ingress_entry)
export FB_BASEURL
@@ -62,15 +59,6 @@ sed -i "s|%%protocol%%|${ADDON_PROTOCOL}|g" /etc/nginx/servers/ingress.conf
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|%%subpath%%|${FB_BASEURL}/|g" /etc/nginx/servers/ingress.conf
# --- Direct ip:port access (separate vhost, ingress untouched) ---
# Listens on 8072, published to the host as 8071 by config.yaml's `ports:`.
# Requests are passed through unchanged; the bare root and the two legacy
# /filebrowser_quantum paths are redirected to the app's baseURL, which is what
# its own links already point at.
sed -i "s|%%protocol%%|${ADDON_PROTOCOL}|g" /etc/nginx/servers/direct.conf
sed -i "s|%%subpath%%|${FB_BASEURL}/|g" /etc/nginx/servers/direct.conf
mkdir -p /var/log/nginx && touch /var/log/nginx/error.log
############################

View File

@@ -1,40 +0,0 @@
server {
listen 0.0.0.0:8072 default_server;
include /etc/nginx/includes/server_params.conf;
include /etc/nginx/includes/proxy_params.conf;
client_max_body_size 0;
# nginx listens on 8072 inside the container but is published to the host
# as 8071. An absolute redirect would be built from $server_port and send
# the browser to :8072, which is not published and therefore unreachable.
absolute_redirect off;
# FileBrowser serves under its baseURL (the Supervisor ingress entry) and
# emits that prefix as absolute links in its HTML/JS, so the browser must
# use that same path here. The bare root and the two legacy paths below
# redirect to it; every other request is proxied through untouched, which
# keeps asset, API and websocket URLs working without response rewriting.
location = / {
return 302 %%subpath%%;
}
# 1.5.1.1 briefly documented /filebrowser_quantum/ as the direct URL. The
# app never served that path itself, so send those bookmarks on instead of
# letting them fall through to a 404.
location = /filebrowser_quantum {
return 302 %%subpath%%;
}
location = /filebrowser_quantum/ {
return 302 %%subpath%%;
}
location / {
proxy_connect_timeout 30m;
proxy_send_timeout 30m;
proxy_read_timeout 30m;
proxy_pass %%protocol%%://backend;
}
}

View File

@@ -1,7 +1,4 @@
## 3.1.0.1 (2026-08-17)
- Fix `password authentication failed for user` when `DB_PASSWORD` contains special characters. Passwords are now URI-encoded before being used in the psql connection string, and SQL-escaped before being used in `CREATE`/`ALTER USER` statements
## 3.1.0 (2026-08-01)
- Update to latest version from immich-app/immich (changelog : https://github.com/immich-app/immich/releases)

View File

@@ -141,6 +141,6 @@ slug: immich
udev: true
url: https://github.com/alexbelgium/hassio-addons
usb: true
version: "3.1.0.1"
version: "3.1.0"
video: true
webui: http://[HOST]:[PORT:8080]

View File

@@ -98,11 +98,10 @@ setup_root_user() {
fi
# Check if the root user exists.
if ! psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" -tAc "SELECT 1 FROM pg_roles WHERE rolname='root'" | grep -q 1; then
if ! psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" -tAc "SELECT 1 FROM pg_roles WHERE rolname='root'" | grep -q 1; then
bashio::log.info "Root user does not exist. Creating root user with DB_ROOT_PASSWORD..."
local root_password_sql="${DB_ROOT_PASSWORD//\'/\'\'}"
psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF
CREATE ROLE root WITH LOGIN SUPERUSER CREATEDB CREATEROLE PASSWORD '${root_password_sql}';
psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF
CREATE ROLE root WITH LOGIN SUPERUSER CREATEDB CREATEROLE PASSWORD '${DB_ROOT_PASSWORD}';
EOF
else
bashio::log.info "Root user exists with a non-default password. No migration needed."
@@ -114,10 +113,10 @@ setup_database() {
bashio::log.info "Setting up external PostgreSQL database..."
# Create the database if it does not exist
if ! psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}/postgres" -tAc \
if ! psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}/postgres" -tAc \
"SELECT 1 FROM pg_database WHERE datname='${DB_DATABASE_NAME}';" | grep -q 1; then
bashio::log.info "Database does not exist. Creating it now..."
psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF
psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF
CREATE DATABASE ${DB_DATABASE_NAME};
EOF
else
@@ -125,21 +124,20 @@ EOF
fi
# Ensure the user exists and update its password
local db_password_sql="${DB_PASSWORD//\'/\'\'}"
psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF
psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${DB_USERNAME}') THEN
CREATE USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${db_password_sql}';
CREATE USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
ELSE
ALTER USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${db_password_sql}';
ALTER USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
END IF;
END
\$\$;
EOF
# Ensure the user has full privileges on the database
psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF
psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF
GRANT ALL PRIVILEGES ON DATABASE ${DB_DATABASE_NAME} TO ${DB_USERNAME};
EOF
@@ -149,7 +147,7 @@ EOF
# Function to check if the vectors (pgvecto.rs) extension is available on the server
check_vector_extension() {
echo "Checking if 'vectors' extension is available for database '${DB_DATABASE_NAME}'..."
RESULT=$(psql "postgres://$DB_USERNAME_URI:$DB_PASSWORD_URI@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vectors';")
RESULT=$(psql "postgres://$DB_USERNAME:$DB_PASSWORD@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vectors';")
if [[ "$RESULT" == "1" ]]; then
echo "✅ 'vectors' extension is available."
return 0
@@ -165,7 +163,7 @@ check_vector_extension() {
# itself on first startup; checking pg_extension would false-warn on every fresh install.
check_vchord_extension() {
echo "Checking if 'vchord' extension is available for database '${DB_DATABASE_NAME}'..."
RESULT=$(psql "postgres://$DB_USERNAME_URI:$DB_PASSWORD_URI@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vchord';")
RESULT=$(psql "postgres://$DB_USERNAME:$DB_PASSWORD@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vchord';")
if [[ "$RESULT" == "1" ]]; then
echo "✅ 'vchord' extension is available."
return 0
@@ -189,14 +187,6 @@ export DB_PORT="$(bashio::config 'DB_PORT')"
export JWT_SECRET="$(bashio::config 'JWT_SECRET')"
export DB_HOSTNAME="$(bashio::config 'DB_HOSTNAME')"
# libpq percent-decodes the userinfo part of a postgres:// URI, so credentials
# containing reserved characters (% @ / : ? #) are misread and every psql call
# below fails with "password authentication failed". Encode them once here and
# use the encoded copies in URIs only - the app itself still gets the raw value
# through export_db_env. Same approach as the postgres_15/postgres_17 addons.
export DB_USERNAME_URI="$(jq -rn --arg x "$DB_USERNAME" '$x|@uri')"
export DB_PASSWORD_URI="$(jq -rn --arg x "$DB_PASSWORD" '$x|@uri')"
if bashio::config.true 'VIPS_NOVECTOR'; then
export VIPS_NOVECTOR="1"
fi

View File

@@ -1,7 +1,4 @@
## 1.26.3.1 (19-08-2026)
- Fix : tapping `Read` in the Home Assistant companion app opened the reader in an external browser, which carries no ingress session cookie, so Home Assistant answered `401 Unauthorized` before Komga was reached ([#2994](https://github.com/alexbelgium/hassio-addons/issues/2994)). Komga opens the reader with `window.open(url, '_blank')` ; nginx now injects a script that turns that popup into a navigation of the ingress panel itself. Only http(s) urls below Komga's own base path are affected, so the OAuth2 login popup and links out of Komga keep their own window
## 1.26.3 (2026-08-13)
- Update to latest version from gotson/komga (changelog : https://github.com/gotson/komga/releases)
## 1.26.1.4 (12-08-2026)

View File

@@ -101,4 +101,4 @@ schema:
slug: komga
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/komga
version: "1.26.3.1"
version: "1.26.3"

View File

@@ -60,25 +60,6 @@ server {
# Only the json/xml document types are added here, so book pages are
# never scanned.
sub_filter "http://127.0.0.1:25600/komga" "%%ingress_entry%%/komga";
# Komga opens the reader with window.open(url, '_blank'). In the Home
# Assistant companion apps the ingress panel is a webview, which hands
# such a popup to an external browser : that browser carries no ingress
# session cookie, so Home Assistant answers 401 before Komga is even
# reached. Turn that popup into a navigation of the panel itself, but
# only for the call shape Komga uses (name _blank, no feature string)
# and only for http(s) urls below window.resourceBaseUrl. That leaves
# the OAuth2 login popup (window.open(url, 'oauth2Login', '<features>'),
# which needs its own window), blob urls and links out of Komga alone,
# and if Komga ever stopped setting resourceBaseUrl the popup is left
# untouched rather than widened to the whole Home Assistant origin,
# which ingress shares. Anchored on the single page app mount point :
# both Komga ui shells carry it once, and only a book served as
# text/html rather than the xhtml the epub spec mandates could collide
# with it -- the same exposure the /komga filter above already has, and
# Komga sends script-src 'none' on that endpoint.
sub_filter "<div id=\"app\">" "<script>(function(){var o=window.open;window.open=function(u,n,f){try{var b=window.resourceBaseUrl;if(u&&n==='_blank'&&!f&&b){if(b.slice(-1)!=='/')b+='/';var t=new URL(u,location.href);if((t.protocol==='http:'||t.protocol==='https:')&&t.origin===location.origin&&t.pathname.indexOf(b)===0){location.assign(t.href);return window}}}catch(e){}return o.apply(window,arguments)}})();</script><div id=\"app\">";
sub_filter_types application/json application/webpub+json
application/divina+json application/opds+json
application/atom+xml;

View File

@@ -1,7 +1,4 @@
## v0.9.3.1 (2026-08-18)
- Fix startup crash: `collector-once` failed with `s6-svwait: fatal: unable to subscribe to events for /run/service/scrutiny` because the add-on entrypoint never started real s6 supervision. Keep the upstream image's own `/init` as PID 1, same as `scrutiny_original` (#2991) and `scrutiny`/`scrutiny_fa` (#2878).
## v0.9.3 (2026-08-13)
- Update to latest version from analogj/scrutiny (changelog : https://github.com/analogj/scrutiny/releases)

View File

@@ -33,8 +33,7 @@ ENV S6_CMD_WAIT_FOR_SERVICES=1 \
# Add rootfs
COPY rootfs/ /
RUN find . -type f \( -name "*.sh" -o -name "run" -o -name "finish" \) -print -exec chmod +x {} \; && \
if [ -d /command ]; then ln -sf /command/* /usr/bin/; fi
RUN find . -type f \( -name "*.sh" -o -name "run" -o -name "finish" \) -print -exec chmod +x {} \;
# Uses /bin for compatibility purposes
# hadolint ignore=DL4005
@@ -62,35 +61,19 @@ RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps.
# 4 Entrypoint #
################
# Keep the repository initialization hook, but return after it has prepared the
# cont-init scripts. Upstream s6 remains responsible for supervising services.
# Add entrypoint
ENV S6_STAGE2_HOOK=/ha_entrypoint.sh
COPY ha_entrypoint.sh /ha_entrypoint.sh
RUN chmod 0755 /ha_entrypoint.sh && \
awk '!inserted && $0 == "if $PID1; then" { \
print "if ! $PID1; then"; \
print " echo \"Initialization hook complete\""; \
print " exit 0"; \
print "fi"; \
inserted=1 \
} { print }' /ha_entrypoint.sh > /ha_entrypoint.sh.tmp && \
mv /ha_entrypoint.sh.tmp /ha_entrypoint.sh && \
chmod 0755 /ha_entrypoint.sh
RUN chmod 777 /ha_entrypoint.sh
# Install bashio
COPY bashio-standalone.sh /usr/local/lib/bashio-standalone.sh
RUN chmod 0755 /usr/local/lib/bashio-standalone.sh
RUN test -x /init && \
test -x /ha_entrypoint.sh && \
bash -n /ha_entrypoint.sh && \
grep -q 'Initialization hook complete' /ha_entrypoint.sh
RUN sed -i "1a if ! bashio::require.unprotected; then bashio::addon.stop; fi" /etc/cont-init.d/90-run.sh
# Scrutiny's image already includes s6-overlay and defines all services under
# /etc/services.d. Keep /init as PID 1 so service readiness and s6-svc calls work.
ENTRYPOINT [ "/init" ]
ENTRYPOINT [ "/usr/bin/env" ]
CMD [ "/ha_entrypoint.sh" ]
############
# 5 Labels #

View File

@@ -45,4 +45,4 @@ schema:
slug: scrutiny_fa_original
udev: true
url: https://github.com/analogj/scrutiny
version: "v0.9.3.1"
version: "v0.9.3"

View File

@@ -1,7 +1,4 @@
## v0.9.3.1 (2026-08-18)
- Fix startup crash: `collector-once` failed with `s6-svwait: fatal: unable to subscribe to events for /run/service/scrutiny` because the add-on entrypoint never started real s6 supervision. Keep the upstream image's own `/init` as PID 1 (same fix already shipped for `scrutiny`/`scrutiny_fa` in #2878). Closes #2989.
## v0.9.3 (2026-08-13)
- Update to latest version from analogj/scrutiny (changelog : https://github.com/analogj/scrutiny/releases)

View File

@@ -33,8 +33,7 @@ ENV S6_CMD_WAIT_FOR_SERVICES=1 \
# Add rootfs
COPY rootfs/ /
RUN find . -type f \( -name "*.sh" -o -name "run" -o -name "finish" \) -print -exec chmod +x {} \; && \
if [ -d /command ]; then ln -sf /command/* /usr/bin/; fi
RUN find . -type f \( -name "*.sh" -o -name "run" -o -name "finish" \) -print -exec chmod +x {} \;
# Uses /bin for compatibility purposes
# hadolint ignore=DL4005
@@ -62,33 +61,17 @@ RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps.
# 4 Entrypoint #
################
# Keep the repository initialization hook, but return after it has prepared the
# cont-init scripts. Upstream s6 remains responsible for supervising services.
# Add entrypoint
ENV S6_STAGE2_HOOK=/ha_entrypoint.sh
COPY ha_entrypoint.sh /ha_entrypoint.sh
RUN chmod 0755 /ha_entrypoint.sh && \
awk '!inserted && $0 == "if $PID1; then" { \
print "if ! $PID1; then"; \
print " echo \"Initialization hook complete\""; \
print " exit 0"; \
print "fi"; \
inserted=1 \
} { print }' /ha_entrypoint.sh > /ha_entrypoint.sh.tmp && \
mv /ha_entrypoint.sh.tmp /ha_entrypoint.sh && \
chmod 0755 /ha_entrypoint.sh
RUN chmod 777 /ha_entrypoint.sh
# Install bashio
COPY bashio-standalone.sh /usr/local/lib/bashio-standalone.sh
RUN chmod 0755 /usr/local/lib/bashio-standalone.sh
RUN test -x /init && \
test -x /ha_entrypoint.sh && \
bash -n /ha_entrypoint.sh && \
grep -q 'Initialization hook complete' /ha_entrypoint.sh
# Scrutiny's image already includes s6-overlay and defines all services under
# /etc/services.d. Keep /init as PID 1 so service readiness and s6-svc calls work.
ENTRYPOINT [ "/init" ]
ENTRYPOINT [ "/usr/bin/env" ]
CMD [ "/ha_entrypoint.sh" ]
############
# 5 Labels #

View File

@@ -113,4 +113,4 @@ schema:
slug: scrutiny_original
udev: true
url: https://github.com/analogj/scrutiny
version: "v0.9.3.1"
version: "v0.9.3"

View File

@@ -1,15 +1,4 @@
## 3.4.1.3 (2026-08-18)
- Fixed the `404: Not Found` on **Discover** persisting for browsers that had already opened Seerr through ingress, even after 3.4.1.1 and 3.4.1.2 were installed (#2975). Seerr serves its JavaScript bundle with `Cache-Control: public, max-age=31536000, immutable`, and the add-on's nginx rewrites that bundle to carry the ingress prefix - which strips the `ETag` and `Last-Modified` a browser would revalidate with. Since every add-on version served the same upstream build, the chunk URLs never changed either, so a browser kept replaying the broken 3.4.1/3.4.1.1 JavaScript from its own cache for up to a year and no fix could reach it. That is why the report persisted on the origin the reporter uses daily (`https://<domain>/`) while a browser that had never cached it (`http://<ip>:8123/`) already showed the fixed behaviour. The asset paths now carry the add-on version, so each release has its own URLs and the first page load after an update fetches the current bundle. Only ingress was affected; the directly published port 5055 always worked.
## 3.4.1.2 (2026-08-18)
- Fixed **Discover** in the sidebar still failing through ingress after 3.4.1.1 (#2975). The trailing slash added in 3.4.1.1 was also applied to the copy of the link inside Seerr's JavaScript bundle, and Next.js' client-side router strips a trailing slash before navigating: it then sent the click to a URL Home Assistant does not route, so it either landed on the same `404: Not Found` or threw `Invariant: attempted to hard navigate to the same URL` and did nothing at all. The bundle is no longer rewritten, so **Discover** routes inside the app exactly like **Requests**, **Issues** and **Settings** already did. The server-rendered link keeps its trailing slash. Only ingress was affected; the directly published port 5055 always worked.
## 3.4.1.1 (2026-08-16)
- Fixed `404: Not Found` when clicking **Discover** in the sidebar through ingress (#2975). Seerr's Discover link points at `/`, which nginx rewrote to the ingress entry without a trailing slash; Home Assistant only routes ingress on `/api/hassio_ingress/<token>/…`, so the request was rejected by Home Assistant before reaching the add-on. Only ingress was affected; the directly published port 5055 always worked.
## 3.4.1 (2026-08-01)
- Update to latest version from seerr-team/seerr (changelog : https://github.com/seerr-team/seerr/releases)
## 3.3.0.1 (2026-07-28)

View File

@@ -96,4 +96,4 @@ schema:
slug: seerr
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/seerr
version: "3.4.1.3"
version: "3.4.1"

View File

@@ -12,27 +12,10 @@ ingress_port=$(bashio::addon.ingress_port)
ingress_interface=$(bashio::addon.ip_address)
ingress_entry=$(bashio::addon.ingress_entry)
# Cache-busting marker for the rewritten JavaScript bundle.
#
# Seerr serves /_next/static/ as "public, max-age=31536000, immutable", and
# nginx's sub_filter strips ETag and Last-Modified off every response it
# rewrites, while the HTML naming those chunks is served "no-store" and keeps
# naming the same URLs. A browser therefore pins the bundle this add-on rewrote
# on its first visit for a year, with no request left that could deliver a
# later change to the sub_filter rules below - which is how #2975 outlived two
# fixes. Folding the version into the asset path gives every release its own
# URLs. njs/ingress.js strips the marker again before proxying.
#
# BUILD_VERSION is the add-on version baked in at build time (it is also what
# bashio::addon.version returns). Only [A-Za-z0-9-] survives: the marker ends up
# inside a regex literal in Seerr's own bundle, where a dot would be a wildcard.
asset_tag="ha-$(printf '%s' "${BUILD_VERSION:-0}" | tr -c 'A-Za-z0-9' '-')"
# Update ingress.conf with actual values
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
sed -i "s|%%ingress_entry_escaped%%|${ingress_entry//\//\\\\\/}|g" /etc/nginx/servers/ingress.conf
sed -i "s|%%asset_tag%%|${asset_tag}|g" /etc/nginx/servers/ingress.conf
bashio::log.info "Nginx ingress configured on ${ingress_interface}:${ingress_port} (asset tag ${asset_tag})"
bashio::log.info "Nginx ingress configured on ${ingress_interface}:${ingress_port}"

View File

@@ -47,27 +47,11 @@ function encodePart(part) {
}
/*
* The cache-busting marker servers/ingress.conf inserts in front of every
* rewritten "/_next" path, e.g. "/ha-3-4-1-3/_next/static/chunks/x.js". It
* gives each add-on release its own asset URLs - Seerr serves /_next/static/ as
* immutable for a year and sub_filter strips the validators, so identical URLs
* would pin the rewritten bundle in the browser forever. Seerr knows nothing
* about the marker, so it is removed again here, on the way in.
*
* Any marker is accepted, not just the one this container serves: a tab opened
* before an add-on update keeps requesting its dynamic chunks under the marker
* it was handed, and those have to keep working until it is reloaded. The
* lookahead keeps a real Seerr path that merely starts with "ha-" untouched.
*/
var ASSET_TAG = /^\/ha-[0-9A-Za-z-]+(?=\/_next(\/|$))/;
/*
* Returns the request URI with the path untouched byte-for-byte apart from the
* cache-busting marker, and only the query string repaired. Used as the
* proxy_pass target.
* Returns the request URI with the path untouched byte-for-byte and only the
* query string repaired. Used as the proxy_pass target.
*/
function uri(r) {
var raw = r.variables.request_uri.replace(ASSET_TAG, "");
var raw = r.variables.request_uri;
var split = raw.indexOf("?");
if (split < 0) {

View File

@@ -43,48 +43,14 @@ server {
sub_filter_once off;
# Do not rewrite every response type blindly. text/html is implicit and
# must not be listed - nginx pre-seeds it and warns "duplicate MIME type"
# on every config load if it appears here as well.
sub_filter_types application/javascript text/javascript application/json;
# Do not rewrite every response type blindly.
sub_filter_types text/html application/javascript text/javascript application/json;
# Seerr's "Discover" sidebar entry, the header logo and the 404 and
# error pages are all <Link href="/">. The server-rendered anchor has to
# carry the ingress prefix *with* a trailing slash: Home Assistant routes
# ingress on "/api/hassio_ingress/{token}/{path:.*}", so a slash-less
# entry matches no route and Home Assistant answers its own plain-text
# "404: Not Found" before the request ever reaches this add-on (#2975).
sub_filter 'href="/"' 'href="$app/"';
sub_filter 'href="/"' 'href="$app"';
sub_filter 'href="/login"' 'href="$app/login"';
# A matching rule for 'href:"/"' - the form those same links take once
# compiled into the JS bundle - used to sit here. It is gone on purpose
# and must not come back: it fed the ingress prefix into Next.js' own
# route table, and next/link resolves a pushed href through
# normalizePathTrailingSlash(), which drops a trailing slash while
# `trailingSlash` is false (Seerr sets no override). Next therefore hard
# navigated to the slash-less URL and recreated the same 404; on the root
# page it instead threw "Invariant: attempted to hard navigate to the
# same URL" and the click did nothing. That is the state PR #2976 left
# #2975 in. Left alone the href stays "/", which removeTrailingSlash()
# preserves, so the router matches its own "/" route and transitions
# in-app - the path every other sidebar entry ("/requests", "/issues",
# "/users", "/settings") already takes. Prefixing belongs in the rendered
# anchor, never in the router's route table.
#
# Note that none of these rules are response-type scoped - sub_filter_types
# includes JavaScript - so the anchor rule above avoids the bundle only
# because the compiled output spells the prop 'href:"/"' and not
# 'href="/"'. These are textual substitutions over someone else's minified
# output: recheck them whenever Seerr or Next.js is upgraded.
# "%%asset_tag%%" is a cache-busting marker carrying the add-on version,
# substituted by 32-nginx_ingress.sh - which explains why it is needed.
# In short: without it a browser replays the bundle this file produced at
# the version it first loaded, for a year, and no later change to any
# rule here can reach it. njs/ingress.js strips the marker back off
# before proxying; the two belong together, do not change one alone.
sub_filter '\/_next' '%%ingress_entry_escaped%%\/%%asset_tag%%\/_next';
sub_filter '/_next' '$app/%%asset_tag%%/_next';
sub_filter 'href:"/"' 'href:"$app"';
sub_filter '\/_next' '%%ingress_entry_escaped%%\/_next';
sub_filter '/_next' '$app/_next';
sub_filter '/api/v1' '$app/api/v1';
sub_filter '/login/plex/loading' '$app/login/plex/loading';
sub_filter '/images/' '$app/images/';

View File

@@ -1,10 +0,0 @@
## 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

View File

@@ -1,112 +0,0 @@
#============================#
# 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

View File

@@ -1,81 +0,0 @@
# 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)

View File

@@ -1,68 +0,0 @@
#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,
}

View File

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

View File

@@ -1,107 +0,0 @@
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"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,35 +0,0 @@
#!/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

@@ -1,17 +0,0 @@
#!/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

@@ -1,96 +0,0 @@
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

@@ -1 +0,0 @@
resolver 127.0.0.11 ipv6=off;

View File

@@ -1,56 +0,0 @@
# 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

@@ -1,56 +0,0 @@
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

@@ -1,36 +0,0 @@
#!/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

@@ -1,36 +0,0 @@
#!/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

View File

@@ -1,10 +0,0 @@
{
"github_beta": "false",
"last_update": "2026-08-19",
"paused": true,
"repository": "alexbelgium/hassio-addons",
"slug": "kapowarr",
"source": "github",
"upstream_repo": "Casvt/Kapowarr",
"upstream_version": "1.3.1"
}