Compare commits

...

56 Commits

Author SHA1 Message Date
alexbelgium
38d6a3b2db fix: auto-fix linting issues 2026-09-13 00:51:50 +00:00
github-actions
daa146a444 GitHub bot: changelog [nobuild] 2026-09-12 17:59:36 +00:00
Alexandre
cda4232fa1 Update version to 20260912.3 in config.yaml 2026-09-12 19:59:06 +02:00
Alexandre
194f220224 Update version to 20260912.2 in config.yaml 2026-09-12 19:53:18 +02:00
alexbelgium
be69aed455 Updater bot : librespeed updated to 6.3.0 (upstream 6.3.0) 2026-09-12 19:27:23 +02:00
alexbelgium
502800f9bf Updater bot : jackett updated to 0.24.2572 (upstream 0.24.2572) 2026-09-12 19:27:03 +02:00
alexbelgium
1ea89fc150 Updater bot : immich_openvino updated to 3.2.0.2 (upstream 3.2.0) 2026-09-12 19:26:53 +02:00
alexbelgium
69efa09f54 Updater bot : immich_noml updated to 3.2.0.2 (upstream 3.2.0) 2026-09-12 19:26:49 +02:00
alexbelgium
ae1f85229b Updater bot : immich_cuda updated to 3.2.0.2 (upstream 3.2.0) 2026-09-12 19:26:41 +02:00
alexbelgium
3dbf317fa0 Updater bot : immich updated to 3.2.0.2 (upstream 3.2.0) 2026-09-12 19:26:37 +02:00
alexbelgium
2c42b37819 Updater bot : epicgamesfree updated to 2026.09.12 (upstream debian-2026-09-12) 2026-09-12 19:25:53 +02:00
github-actions
358f6da616 GitHub bot: changelog [nobuild] 2026-09-12 16:40:15 +00:00
Alexandre
2273c360fc Update config.yaml 2026-09-12 18:34:08 +02:00
Alexandre
9443356b36 docs(skill): ask "is this the simplest way possible" before implementing (#3062)
* docs(skill): ask "is this the simplest way possible" before implementing

Every question the loop asks — of me and of Codex — is about what could go
wrong, and each answer argues for more code. Nothing pushed the other way
until the maintainer asked, and by then the code existed.

On PR #3061 that cost a 309-line Python registry client to read one OCI
label, in a script whose neighbouring branch does the same kind of job in
inline curl and jq. The plan review and the code review both passed it.
Asked outright, Codex said to delete all 309 lines; the inline version came
to 21 and returned byte-identical output on every image tested. It is the
second time the question has had to come from the maintainer.

Step 3 now asks it before any code exists, of myself in writing and of Codex
as a question of its own with the smaller alternative sketched out to argue
for. Step 5 gains an Idiom check, on whether a hunk looks like the code it
sits beside. Adding a new file or a new mechanism now means the full loop
however small the diff looks, and codex-review.md records that Codex will
never raise proportionality unprompted but answers it bluntly when asked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(skill): give the proportionality self-question a home on the light path

Three independent reviewers on #3062 (CodeRabbit, Codex, Copilot) all caught
the same gap: the self-question and its written answer lived only inside step
3, marked full-loop-only, while the light path went straight from scope to
implement. A light task following the documented sequence never reached the
instruction telling it to ask "is this the simplest way?" at all.

Moved the self-question (and the written answer it requires) into the shared
triage section, before the light/full-loop split, so both paths ask it before
implementing. Step 3 now only adds Codex to that question, for full loop.

Also: clarified that a version bump plus its CHANGELOG entry is release
bookkeeping, not a "new file", so it does not trigger the light-to-full-loop
escalation on its own (Codex's second finding on #3062, which read the
one-file threshold as counting every file the PR touches rather than whether
a new mechanism was added). Fixed step 5's stale "six questions" to seven,
after Idiom was added in the prior commit. Fixed simplify.md's case study to
say the 309-line client was "initially proposed", not shipped, matching the
prior PR's own body.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(skill): split "test the smaller candidate" by whether the diff exists yet

CodeRabbit on #3062 caught that step 3's "build the smaller one and run it"
never says to compare it against the larger candidate, only to run it. As
written it applied at plan time, before either candidate has code — where the
larger candidate is a plan, not a diff, so there is nothing to compare against
without building both in full, which defeats the point of picking the cheaper
one first. Testing the smaller sketch against the actual requirement is the
correct and complete check there.

The comparison CodeRabbit is describing is real, but it happens at step 6, not
step 3: by the time code review runs, the larger candidate already exists as
the diff, so sketching the smaller one and diffing its output against the
shipped code costs nothing extra and is exactly what settled #3061. Moved the
build-and-compare instruction there instead of applying it, unchanged, to a
step where nothing exists to compare against yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 17:04:54 +02:00
Alexandre
500c083c5e fix(updater): publish the version the image holds, not the app's newest release (#3061)
The immich addons do not build immich, they build FROM
ghcr.io/imagegenius/immich, a rebuild that lags immich upstream by days to
weeks. updater.json tracked immich-app/immich, so the addon was published as
3.2.0 two days after that release while the image behind the floating :3 tag
still carried immich 3.1.0 (issue #3060), and because the stored upstream
version already read 3.2.0, nothing would have rebuilt the addon once the
image caught up.

Add source: container, which resolves the exact image reference the addon
builds from and reads org.opencontainers.image.version out of it, and point
the four immich addons at it. The manifest digest is recorded alongside the
version so that a publisher republishing the same version under new content
is not mistaken for no change. An image with nothing readable leaves the
addon untouched.

Written as inline curl and jq, in the style of the dockerhub branch above it.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 16:49:40 +02:00
alexbelgium
e06b6d1c68 Updater bot : flaresolverr updated to 3.5.2 (upstream 3.5.2) 2026-09-12 04:48:45 +02:00
alexbelgium
660982e641 Updater bot : claude_desktop updated to 07308545.8 (upstream v3.2.4+claude1.52386.3) 2026-09-12 04:47:49 +02:00
alexbelgium
38eac4c510 Updater bot : zzz_archived_code-server updated to 4.137.0 (upstream 4.137.0) 2026-09-12 01:33:26 +02:00
alexbelgium
b97824acfc Updater bot : wger updated to 2.8.0-dev.0 (upstream 2.8.0-dev.0) 2026-09-12 01:33:05 +02:00
alexbelgium
6d78111022 Updater bot : webtop_kde updated to 4.16.0.97 (upstream 4.16-r0-ls93) 2026-09-12 01:32:59 +02:00
alexbelgium
626991ec4d Updater bot : tdarr updated to 2.87.01 (upstream 2.87.01) 2026-09-12 01:31:57 +02:00
alexbelgium
439f1e0e8f Updater bot : tandoor_recipes updated to 2.6.15 (upstream 2.6.15) 2026-09-12 01:31:54 +02:00
alexbelgium
f2fef2e816 Updater bot : spotify_to_plex updated to 1.0.109 (upstream 1.0.109) 2026-09-12 01:31:46 +02:00
alexbelgium
3089be5346 Updater bot : sonarr updated to 4.0.20.3012 (upstream 4.0.20.3012) 2026-09-12 01:31:39 +02:00
alexbelgium
55f20e87b7 Updater bot : social_to_mealie updated to 1.7.3 (upstream 1.7.3) 2026-09-12 01:31:34 +02:00
alexbelgium
147ebeb3bc Updater bot : signalk updated to 2.32.0 (upstream 2.32.0) 2026-09-12 01:31:30 +02:00
alexbelgium
620dadf8fa Updater bot : scrutiny_fa updated to v1.74.1 (upstream v1.74.1) 2026-09-12 01:31:12 +02:00
alexbelgium
7bfa34980b Updater bot : scrutiny updated to v1.74.1 (upstream v1.74.1) 2026-09-12 01:31:08 +02:00
alexbelgium
7fa4b08359 Updater bot : sabnzbd updated to 5.1.3 (upstream 5.1.3) 2026-09-12 01:31:02 +02:00
alexbelgium
8338f5767c Updater bot : prowlarr updated to 2.6.4.5611.15 (upstream nightly-2.6.4.5611-ls15) 2026-09-12 01:30:42 +02:00
alexbelgium
6424f68cff Updater bot : plex updated to 1.43.4.10903.324 (upstream 1.43.4.10903-e5521bd8c-ls324) 2026-09-12 01:30:30 +02:00
alexbelgium
fe392aba1a Updater bot : nzbget updated to v26.3.263 (upstream v26.3-ls263) 2026-09-12 01:30:09 +02:00
alexbelgium
287e95e5ab Updater bot : nextcloud updated to 34.0.4 (upstream 34.0.4) 2026-09-12 01:30:04 +02:00
alexbelgium
edf585e652 Updater bot : manyfold updated to 0.149.0 (upstream 0.149.0) 2026-09-12 01:29:38 +02:00
alexbelgium
aa85189f69 Updater bot : maintainerr updated to 3.28.0 (upstream 3.28.0) 2026-09-12 01:29:33 +02:00
alexbelgium
d79cb11c50 Updater bot : linkwarden updated to 2.16.3 (upstream 2.16.3) 2026-09-12 01:29:28 +02:00
alexbelgium
81d10f3be8 Updater bot : joplin updated to 3.7.2 (upstream 3.7.2) 2026-09-12 01:29:11 +02:00
alexbelgium
49c6fbca2d Updater bot : jellyfin updated to 12.0 (upstream 12.0) 2026-09-12 01:29:04 +02:00
alexbelgium
8ad14ad6ce Updater bot : jackett updated to 0.24.2558 (upstream 0.24.2558) 2026-09-12 01:28:59 +02:00
alexbelgium
05f394d511 Updater bot : immich_openvino updated to 3.2.0 (upstream 3.2.0) 2026-09-12 01:28:50 +02:00
alexbelgium
92ee1438dd Updater bot : immich_noml updated to 3.2.0 (upstream 3.2.0) 2026-09-12 01:28:45 +02:00
alexbelgium
c51c4c00a0 Updater bot : immich_cuda updated to 3.2.0 (upstream 3.2.0) 2026-09-12 01:28:36 +02:00
alexbelgium
2ed1fdcdc6 Updater bot : immich updated to 3.2.0 (upstream 3.2.0) 2026-09-12 01:28:31 +02:00
alexbelgium
c2bb8209ba Updater bot : grav updated to 2.1.2 (upstream 2.1.2) 2026-09-12 01:28:24 +02:00
alexbelgium
d807435165 Updater bot : free_games_claimer updated to 2.1.1 (upstream 1.9) 2026-09-12 01:28:10 +02:00
alexbelgium
116131efd4 Updater bot : epicgamesfree updated to 2026.09.11 (upstream debian-2026-09-11) 2026-09-12 01:27:45 +02:00
alexbelgium
c66c41b596 Updater bot : ente updated to 4.4.28 (upstream 1.7.29) 2026-09-12 01:27:41 +02:00
alexbelgium
ac19165c2f Updater bot : emby_beta updated to 4.10.0.40 (upstream 4.10.0.40) 2026-09-12 01:27:30 +02:00
alexbelgium
11706236ca Updater bot : emby updated to 4.10.0.40 (upstream 4.10.0.40) 2026-09-12 01:27:25 +02:00
alexbelgium
b53dd0556e Updater bot : comicarr updated to 0.38.15 (upstream 0.38.15) 2026-09-12 01:27:15 +02:00
alexbelgium
2135aa10f2 Updater bot : codex updated to 2.3.0 (upstream 2.3.0) 2026-09-12 01:27:08 +02:00
alexbelgium
15c9da9fb3 Updater bot : claude_desktop updated to 07308545.7 (upstream v3.2.4+claude1.49585.0) 2026-09-12 01:26:56 +02:00
alexbelgium
8b742240b2 Updater bot : changedetection.io updated to 0.60.4 (upstream 0.60.4) 2026-09-12 01:26:52 +02:00
alexbelgium
962c3b736d Updater bot : browserless_chrome updated to 2.56.7 (upstream 2.56.7) 2026-09-12 01:26:40 +02:00
alexbelgium
e8dc044d99 Updater bot : browser_brave updated to 1.95.101-ls128 (upstream 1.95.101-ls128) 2026-09-12 01:26:25 +02:00
alexbelgium
4e79ac84e5 Updater bot : autobrr updated to 1.86.0 (upstream 1.86.0) 2026-09-12 01:25:18 +02:00
400 changed files with 3338 additions and 2852 deletions

View File

@@ -33,8 +33,20 @@ Triage first, then one of two paths:
the plan → implement → simplify → Codex reviews the code → **simplify again** → PR → resolve the plan → implement → simplify → Codex reviews the code → **simplify again** → PR → resolve
comments → verify in production → report. comments → verify in production → report.
Escalate mid-flight if a light task grows — touches a default, needs a new script or service, or Before implementing, on **either** path, ask yourself the proportionality question and answer
reveals a deeper problem. it in the plan (a one-line plan, for a light task) — in writing, naming the smaller version you
rejected and why:
> What is the smallest thing that solves this? What would you delete from what I just proposed?
> If it had to be written in ten lines, in the idiom of the code it sits beside, what would be
> lost — and is any of that a case I have actually demonstrated?
Escalate mid-flight if a light task grows — touches a default, needs a new script or service,
reveals a deeper problem, or turns out to need more than the one file once you have honestly
answered that question. A light task never skips it; it skips **Codex** only while the answer
stays inside one file. Adding a **new file or a new mechanism** puts it on the full loop, however
small the diff looks — a version bump and its CHANGELOG entry are release bookkeeping, not a new
file for this purpose, and never trigger it on their own.
**Standing rule:** ship the simplest solution that works, and build it out of what already **Standing rule:** ship the simplest solution that works, and build it out of what already
exists — a `.templates/` module, an existing cont-init script, the pattern a sibling add-on exists — a `.templates/` module, an existing cont-init script, the pattern a sibling add-on
@@ -113,6 +125,16 @@ plan:
Levels 4-6 need a reason that survives being said out loud ("upstream has no knob for this, and I Levels 4-6 need a reason that survives being said out loud ("upstream has no knob for this, and I
checked" is one; "it felt cleaner" is not) and mean full loop. checked" is one; "it felt cleaner" is not) and mean full loop.
**Full loop adds Codex to the proportionality question already asked above** — put it to Codex as
a numbered question of its own, with the smaller alternative sketched out for it to argue for.
Every other question in the loop asks what could go wrong, which only ever argues for more code;
this is the one place that pushes the other way, and it is worthless unless asked outright — see
`references/codex-review.md`. Nothing is built yet at this step, so "test" means against the real
requirement, not against the other candidate — sketch the smaller one and check it against what
the add-on actually needs; that sketch is what you go on to implement if it holds up, not one of
two things you build. A sketch costs minutes and settles the argument with a measurement instead
of a preference.
Attack your own plan before implementing: Attack your own plan before implementing:
- What does this do on a host **unlike this one** — no GPU, small `/dev/shm`, aarch64, a VM? - What does this do on a host **unlike this one** — no GPU, small `/dev/shm`, aarch64, a VM?
- What happens on **upgrade** to someone who configured this by hand? - What happens on **upgrade** to someone who configured this by hand?
@@ -123,7 +145,8 @@ Attack your own plan before implementing:
where that input arrives (the standing rule above) and go and look, before you write it. where that input arrives (the standing rule above) and go and look, before you write it.
Full loop only, before writing code: get Codex's independent read on the plan, delegated as above — Full loop only, before writing code: get Codex's independent read on the plan, delegated as above —
`references/codex-review.md` has the invocation and how to write the prompt. `references/codex-review.md` has the invocation and how to write the prompt. The proportionality
question above goes in that prompt every time, and in the step 6 prompt too.
## 4. Implement ## 4. Implement
@@ -145,7 +168,7 @@ path.
## 5. Simplify ## 5. Simplify
Six questions over your own diff, before anyone else reads it: Seven questions over your own diff, before anyone else reads it:
- **Level** — did the diff stay at the ladder level chosen in step 3, or creep up one? - **Level** — did the diff stay at the ladder level chosen in step 3, or creep up one?
- **Deletion** — can this be solved by deleting instead of adding? - **Deletion** — can this be solved by deleting instead of adding?
@@ -153,6 +176,10 @@ Six questions over your own diff, before anyone else reads it:
- **Reuse** — does any hunk reimplement what `.templates/`, another script in this add-on, or a - **Reuse** — does any hunk reimplement what `.templates/`, another script in this add-on, or a
sibling add-on already does? If a future add-on hits this problem, will it find one way to solve sibling add-on already does? If a future add-on hits this problem, will it find one way to solve
it or two? Fold near-duplicates in, or justify the divergence in the PR body. it or two? Fold near-duplicates in, or justify the divergence in the PR body.
- **Idiom** — does this look like the code it sits beside? The same kind of job done two ways in
one file costs every later reader more than the nicer of the two ways saves. Reuse asks whether
the mechanism already exists; Idiom asks whether you wrote yours the way the neighbours are
written.
- **Depth** — is this a special case bolted onto shared infrastructure? Fix the shared mechanism - **Depth** — is this a special case bolted onto shared infrastructure? Fix the shared mechanism
instead once more than one add-on hits it; generalising from a single case is how bespoke instead once more than one add-on hits it; generalising from a single case is how bespoke
designs get built, so below that bar the special case is the right call. designs get built, so below that bar the special case is the right call.
@@ -177,6 +204,12 @@ you have now demonstrated, or one you have merely been told about? Taking a
correctness objection often deletes the code that made it necessary, and a fix that collapses back correctness objection often deletes the code that made it necessary, and a fix that collapses back
to fewer lines than you started the review with is the normal outcome, not a suspicious one. to fewer lines than you started the review with is the normal outcome, not a suspicious one.
Then ask the proportionality question again, of the diff this time — it is cheap, and by now
there is real code to point at rather than a plan. This is where "build the smaller one and test
both" is actually free: the larger candidate already exists as the diff, so sketch the smaller
alternative and diff its output against the shipped code on the same inputs, the way #3061 was
settled (`references/simplify.md`) — rather than against the requirement alone.
These edits land after step 4's checks already ran, so re-run them: `scripts/validate.sh <addon> These edits land after step 4's checks already ran, so re-run them: `scripts/validate.sh <addon>
--vs-master` plus the behavioural tests, over the final diff. Deleting a branch is exactly the --vs-master` plus the behavioural tests, over the final diff. Deleting a branch is exactly the
kind of edit that leaves a stray `fi` behind. kind of edit that leaves a stray `fi` behind.

View File

@@ -27,6 +27,10 @@ codex exec --model gpt-5.6-sol --sandbox read-only --skip-git-repo-check \
- **Plan review (step 3):** include the files to read, your measurements **with numbers**, the - **Plan review (step 3):** include the files to read, your measurements **with numbers**, the
proposed changes, and explicit instructions to challenge you. Ask direct questions ("is this proposed changes, and explicit instructions to challenge you. Ask direct questions ("is this
really add-on-fixable?", "give the precise flag set") rather than "review this". really add-on-fixable?", "give the precise flag set") rather than "review this".
- **The proportionality question, in both reviews, as a numbered question of its own:** "is this
the simplest way possible, and what exactly would you delete?" Sketch the smaller alternative in
the prompt and ask it to argue for that, and quote the repo's standing simplicity rule so it has
the bar to hold you to. Ask for lines and functions to delete, not a direction.
- **Code review (step 6):** point it at `git diff origin/master...HEAD` plus the reasoning behind - **Code review (step 6):** point it at `git diff origin/master...HEAD` plus the reasoning behind
each hunk. Ask specifically what breaks: upgrade paths, hosts unlike this one, users who each hunk. Ask specifically what breaks: upgrade paths, hosts unlike this one, users who
configured things by hand. Ask directly whether a simpler mechanism would achieve the same configured things by hand. Ask directly whether a simpler mechanism would achieve the same
@@ -41,3 +45,8 @@ confirmations with the same scepticism as its objections — especially about th
**Its objections only ever argue for more code** — it is asked what could go wrong, never whether **Its objections only ever argue for more code** — it is asked what could go wrong, never whether
the branch it wants is reachable. Sort them before writing anything; SKILL.md step 6 is that pass. the branch it wants is reachable. Sort them before writing anything; SKILL.md step 6 is that pass.
**Unprompted it will never say "this is too much".** Asked outright it will, bluntly and usefully:
on PR #3061 it answered "delete all 309 lines" of a helper both its earlier reviews had passed
without comment. Expect it to reverse an earlier position when you ask it to re-examine one — a
stance it drops that easily was never strongly held, which is itself the answer.

View File

@@ -30,6 +30,17 @@ Being able to build the complicated thing is not a reason to.
It took the maintainer asking "is this the simplest way possible?" to run the pass that step 6 It took the maintainer asking "is this the simplest way possible?" to run the pass that step 6
now requires. now requires.
- The immich updater fix (issue #3060, PR #3061) initially proposed a **309-line Python registry
client** to read one OCI label, into a script whose neighbouring `dockerhub` branch does the same kind of
job in a handful of inline `curl | jq` lines. Both the plan review and the code review passed
it, because both were asked what could break rather than what it should cost. Asked "is this the
simplest way possible?", Codex answered "delete all 309 lines"; the inline version came to 21
and was measured to return byte-identical output on all five images. About a third of what went
was defending cases never observed in this repo — an architecture mismatch, a hostile label, an
unsupported registry — each of which already failed closed on its own. Second time the
maintainer has had to ask the question, which is why step 3 now asks it before any code exists
and step 5 asks whether the hunk looks like its neighbours.
## Where SKILL.md step 5's questions get hard ## Where SKILL.md step 5's questions get hard
**Deletion is not automatically the safe direction.** The `/dev/shm` case in `evidence.md` is a **Deletion is not automatically the safe direction.** The `/dev/shm` case in `evidence.md` is a

View File

@@ -30,8 +30,14 @@ VAR="${1:?usage: env_trace.sh <VAR> [process-name-or-pid]}"
TARGET="${2:-}" TARGET="${2:-}"
# VAR is interpolated into grep/sed patterns below — restrict it to a valid env var name # VAR is interpolated into grep/sed patterns below — restrict it to a valid env var name
case "$VAR" in case "$VAR" in
[A-Za-z_]*) [ -z "${VAR//[A-Za-z0-9_]/}" ] || { echo "invalid env var name: $VAR" >&2; exit 1; } ;; [A-Za-z_]*) [ -z "${VAR//[A-Za-z0-9_]/}" ] || {
*) echo "invalid env var name: $VAR" >&2; exit 1 ;; echo "invalid env var name: $VAR" >&2
exit 1
} ;;
*)
echo "invalid env var name: $VAR" >&2
exit 1
;;
esac esac
echo "== tracing ${VAR} ==" echo "== tracing ${VAR} =="
@@ -80,7 +86,8 @@ echo "3. s6 container_environment (only read by services using #!/usr/bin/with-c
seen3=0 seen3=0
for d in /var/run/s6/container_environment /run/s6/container_environment; do for d in /var/run/s6/container_environment /run/s6/container_environment; do
if [ -f "$d/$VAR" ]; then if [ -f "$d/$VAR" ]; then
echo " $d/$VAR = [$(cat "$d/$VAR")]"; seen3=1 echo " $d/$VAR = [$(cat "$d/$VAR")]"
seen3=1
fi fi
done done
[ "$seen3" -eq 0 ] && echo " not present in either envdir" [ "$seen3" -eq 0 ] && echo " not present in either envdir"
@@ -122,8 +129,8 @@ else
done done
fi fi
# What it was actually launched with beats any theory about its environment. # What it was actually launched with beats any theory about its environment.
tr '\0' '\n' < "/proc/$pid/cmdline" 2> /dev/null | tail -n +2 | tr '\0' '\n' < "/proc/$pid/cmdline" 2> /dev/null | tail -n +2 \
grep -iE "res|screen|${VAR}" | head -3 | sed 's/^/ argv: /' | grep -iE "res|screen|${VAR}" | head -3 | sed 's/^/ argv: /'
done done
fi fi
fi fi

View File

@@ -22,10 +22,17 @@ set -uo pipefail
REPO="${HASSIO_REPO:-}" REPO="${HASSIO_REPO:-}"
[ -z "$REPO" ] && REPO=$(gh repo view --json nameWithOwner --jq .nameWithOwner 2> /dev/null) [ -z "$REPO" ] && REPO=$(gh repo view --json nameWithOwner --jq .nameWithOwner 2> /dev/null)
[ -z "$REPO" ] && { echo "cannot determine repo; set HASSIO_REPO=owner/name" >&2; exit 1; } [ -z "$REPO" ] && {
echo "cannot determine repo; set HASSIO_REPO=owner/name" >&2
exit 1
}
echo "repo: $REPO" >&2 echo "repo: $REPO" >&2
CMD="${1:-}"; PR="${2:-}" CMD="${1:-}"
[ -z "$CMD" ] || [ -z "$PR" ] && { sed -n '2,20p' "$0" | sed 's/^# \?//'; exit 1; } PR="${2:-}"
[ -z "$CMD" ] || [ -z "$PR" ] && {
sed -n '2,20p' "$0" | sed 's/^# \?//'
exit 1
}
case "$CMD" in case "$CMD" in
list) list)
@@ -43,7 +50,8 @@ status)
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | "\(if .isResolved then "resolved" else "OPEN " end) \(.id) \(.comments.nodes[0].author.login) \(.path)"' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | "\(if .isResolved then "resolved" else "OPEN " end) \(.id) \(.comments.nodes[0].author.login) \(.path)"'
;; ;;
reply) reply)
ID="${3:?comment id}"; BODY="${4:?text or @file}" ID="${3:?comment id}"
BODY="${4:?text or @file}"
if [ "${BODY#@}" != "$BODY" ]; then if [ "${BODY#@}" != "$BODY" ]; then
out=$(gh api "repos/$REPO/pulls/$PR/comments/$ID/replies" -F body=@"${BODY#@}" --jq '.id' 2>&1) out=$(gh api "repos/$REPO/pulls/$PR/comments/$ID/replies" -F body=@"${BODY#@}" --jq '.id' 2>&1)
rc=$? rc=$?
@@ -69,8 +77,12 @@ resolve)
echo "answered each one — a resolved-but-wrong thread is worse than an open one." echo "answered each one — a resolved-but-wrong thread is worse than an open one."
gh api graphql -f query="{repository(owner:\"${REPO%%/*}\",name:\"${REPO##*/}\"){pullRequest(number:$PR){reviewThreads(first:100){nodes{isResolved path comments(first:1){nodes{author{login} body}}}}}}}" \ gh api graphql -f query="{repository(owner:\"${REPO%%/*}\",name:\"${REPO##*/}\"){pullRequest(number:$PR){reviewThreads(first:100){nodes{isResolved path comments(first:1){nodes{author{login} body}}}}}}}" \
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | " - \(.comments.nodes[0].author.login) \(.path): \(.comments.nodes[0].body[0:90])"' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | " - \(.comments.nodes[0].author.login) \(.path): \(.comments.nodes[0].body[0:90])"'
printf 'Type "yes" to resolve all: '; read -r ok printf 'Type "yes" to resolve all: '
[ "$ok" = "yes" ] || { echo "aborted"; exit 1; } read -r ok
[ "$ok" = "yes" ] || {
echo "aborted"
exit 1
}
ids="" ids=""
elif [ -z "$ids" ]; then elif [ -z "$ids" ]; then
echo "usage: pr_review.sh resolve <PR> <THREAD_ID...> (or --all, with confirmation)" >&2 echo "usage: pr_review.sh resolve <PR> <THREAD_ID...> (or --all, with confirmation)" >&2
@@ -81,7 +93,10 @@ resolve)
ids=$(gh api graphql -f query="{repository(owner:\"${REPO%%/*}\",name:\"${REPO##*/}\"){pullRequest(number:$PR){reviewThreads(first:50){nodes{id isResolved}}}}}" \ ids=$(gh api graphql -f query="{repository(owner:\"${REPO%%/*}\",name:\"${REPO##*/}\"){pullRequest(number:$PR){reviewThreads(first:50){nodes{id isResolved}}}}}" \
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .id') --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .id')
fi fi
[ -z "$ids" ] && { echo "nothing unresolved"; exit 0; } [ -z "$ids" ] && {
echo "nothing unresolved"
exit 0
}
rfail=0 rfail=0
for id in $ids; do for id in $ids; do
r=$(gh api graphql -f query="mutation{resolveReviewThread(input:{threadId:\"$id\"}){thread{isResolved}}}" \ r=$(gh api graphql -f query="mutation{resolveReviewThread(input:{threadId:\"$id\"}){thread{isResolved}}}" \
@@ -101,7 +116,9 @@ watch)
# CI later counts as blocking until someone puts it here on purpose. # CI later counts as blocking until someone puts it here on purpose.
ADVISORY_CHECKS="Codacy Static Code Analysis" # one per line if more are ever added ADVISORY_CHECKS="Codacy Static Code Analysis" # one per line if more are ever added
wfail=2 # not 0: running out of minutes with checks still pending is not a pass wfail=2 # not 0: running out of minutes with checks still pending is not a pass
c=""; bstates=""; adv="" c=""
bstates=""
adv=""
for i in $(seq 1 "$MINS"); do for i in $(seq 1 "$MINS"); do
# gh pr checks emits TAB-separated columns with no header when its output is not a TTY, # gh pr checks emits TAB-separated columns with no header when its output is not a TTY,
# which inside this $(... | awk) it never is. (Attached to a terminal it prints a wholly # which inside this $(... | awk) it never is. (Attached to a terminal it prints a wholly
@@ -121,7 +138,8 @@ watch)
# Normal in the first minutes after `gh pr create`, and also whenever gh errors. # Normal in the first minutes after `gh pr create`, and also whenever gh errors.
# Calling that "settled" would report success for checks that never ran. # Calling that "settled" would report success for checks that never ran.
echo "[$i] no checks reported yet (gh returned nothing) — still waiting" echo "[$i] no checks reported yet (gh returned nothing) — still waiting"
sleep 60; continue sleep 60
continue
fi fi
c=$(printf '%s\n' "$rows" | cut -f2 | tr '\n' ' ') c=$(printf '%s\n' "$rows" | cut -f2 | tr '\n' ' ')
echo "[$i] $c" echo "[$i] $c"
@@ -131,12 +149,14 @@ watch)
adv=$(printf '%s\n' "$rows" | awk -F'\t' '$1 == "A" { print $2 }' | tr '\n' ' ') adv=$(printf '%s\n' "$rows" | awk -F'\t' '$1 == "A" { print $2 }' | tr '\n' ' ')
if [ -z "$bstates" ]; then if [ -z "$bstates" ]; then
echo " only advisory checks have reported — no blocking check has run yet" echo " only advisory checks have reported — no blocking check has run yet"
sleep 60; continue sleep 60
continue
fi fi
# Allowlist the good states rather than denylisting the bad ones: an unrecognised state # Allowlist the good states rather than denylisting the bad ones: an unrecognised state
# must land in the failure branch, because falling through to "passing" is this command's # must land in the failure branch, because falling through to "passing" is this command's
# worst outcome. # worst outcome.
nbad=0; npend=0 nbad=0
npend=0
for s in $bstates; do for s in $bstates; do
case "$s" in case "$s" in
pass | skipping) ;; pass | skipping) ;;
@@ -146,13 +166,17 @@ watch)
done done
if [ "$nbad" -gt 0 ]; then if [ "$nbad" -gt 0 ]; then
echo "settled — blocking checks FAILED:" echo "settled — blocking checks FAILED:"
printf '%s\n' "$rows" | printf '%s\n' "$rows" \
awk -F'\t' '$1 == "B" && $3 != "pass" && $3 != "skipping" && $3 != "pending" { print " " $2 }' | awk -F'\t' '$1 == "B" && $3 != "pass" && $3 != "skipping" && $3 != "pending" { print " " $2 }'
wfail=1; break wfail=1
break
elif [ "$npend" -gt 0 ]; then elif [ "$npend" -gt 0 ]; then
sleep 60; continue sleep 60
continue
else else
echo "settled — blocking checks passing"; wfail=0; break echo "settled — blocking checks passing"
wfail=0
break
fi fi
done done
[ "$wfail" -eq 2 ] && echo "gave up after ${MINS}m, checks still unsettled — NOT a pass" [ "$wfail" -eq 2 ] && echo "gave up after ${MINS}m, checks still unsettled — NOT a pass"
@@ -163,5 +187,8 @@ watch)
echo "note: long queues here are usually account runner contention, not your diff." echo "note: long queues here are usually account runner contention, not your diff."
exit "$wfail" exit "$wfail"
;; ;;
*) echo "unknown: $CMD"; exit 1 ;; *)
echo "unknown: $CMD"
exit 1
;;
esac esac

View File

@@ -59,7 +59,10 @@ if [ -n "${BUILD_VERSION:-}" ]; then
if [ -z "$SLUG" ] && [ -n "${HOSTNAME:-}" ]; then if [ -z "$SLUG" ] && [ -n "${HOSTNAME:-}" ]; then
base=$(printf '%s' "$HOSTNAME" | sed 's/^[0-9a-f]\{8\}-//') base=$(printf '%s' "$HOSTNAME" | sed 's/^[0-9a-f]\{8\}-//')
for cand in "$(printf '%s' "$base" | tr '-' '_')" "$base"; do for cand in "$(printf '%s' "$base" | tr '-' '_')" "$base"; do
[ -f "$REPO/$cand/config.yaml" ] && { SLUG="$cand"; break; } [ -f "$REPO/$cand/config.yaml" ] && {
SLUG="$cand"
break
}
done done
[ -z "$SLUG" ] && SLUG="$base" [ -z "$SLUG" ] && SLUG="$base"
fi fi
@@ -81,8 +84,8 @@ if [ -n "${BUILD_VERSION:-}" ]; then
else else
echo " MISMATCH — this branch is NOT what is running." echo " MISMATCH — this branch is NOT what is running."
git fetch origin master --quiet 2> /dev/null git fetch origin master --quiet 2> /dev/null
master=$(git show origin/master:"$SLUG/config.yaml" 2> /dev/null | master=$(git show origin/master:"$SLUG/config.yaml" 2> /dev/null \
grep -E '^version:' | head -1 | tr -d "\"'" | awk '{print $2}') | grep -E '^version:' | head -1 | tr -d "\"'" | awk '{print $2}')
echo " origin/master version = ${master:-unknown}" echo " origin/master version = ${master:-unknown}"
echo " -> work from origin/master; analysing this branch will mislead you." echo " -> work from origin/master; analysing this branch will mislead you."
echo echo

View File

@@ -12,7 +12,10 @@
set -uo pipefail set -uo pipefail
TRAPS="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/references/traps.md" TRAPS="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/references/traps.md"
[ -f "$TRAPS" ] || { echo "not found: $TRAPS" >&2; exit 1; } [ -f "$TRAPS" ] || {
echo "not found: $TRAPS" >&2
exit 1
}
# The Contents list duplicates the headings; grep the headings themselves so the list cannot drift. # The Contents list duplicates the headings; grep the headings themselves so the list cannot drift.
list() { list() {
@@ -20,13 +23,16 @@ list() {
grep '^## ' "$TRAPS" | grep -v '^## Contents' | sed 's/^## / /' grep '^## ' "$TRAPS" | grep -v '^## Contents' | sed 's/^## / /'
} }
[ $# -eq 0 ] && { list; exit 0; } [ $# -eq 0 ] && {
list
exit 0
}
# awk over exact heading text: section names contain '/' and other characters that would need # awk over exact heading text: section names contain '/' and other characters that would need
# escaping in a sed address, and a keyword matching several headings should be an error, not a # escaping in a sed address, and a keyword matching several headings should be an error, not a
# silent pick of the first. # silent pick of the first.
mapfile -t matches < <(grep '^## ' "$TRAPS" | grep -v '^## Contents' | mapfile -t matches < <(grep '^## ' "$TRAPS" | grep -v '^## Contents' \
grep -iF -- "$1" | sed 's/^## //') | grep -iF -- "$1" | sed 's/^## //')
case "${#matches[@]}" in case "${#matches[@]}" in
0) 0)

View File

@@ -13,22 +13,31 @@ set -uo pipefail
if root=$(git rev-parse --show-toplevel 2> /dev/null); then cd "$root" || exit 1; fi if root=$(git rev-parse --show-toplevel 2> /dev/null); then cd "$root" || exit 1; fi
ADDON="${1:-}" ADDON="${1:-}"
[ "${ADDON:-}" = "--vs-master" ] && { ADDON=""; set -- --vs-master; } [ "${ADDON:-}" = "--vs-master" ] && {
ADDON=""
set -- --vs-master
}
VS_MASTER=false VS_MASTER=false
for a in "$@"; do [ "$a" = "--vs-master" ] && VS_MASTER=true; done for a in "$@"; do [ "$a" = "--vs-master" ] && VS_MASTER=true; done
if [ -z "$ADDON" ]; then if [ -z "$ADDON" ]; then
mapfile -t _dirs < <(git diff --name-only origin/master...HEAD 2> /dev/null | mapfile -t _dirs < <(git diff --name-only origin/master...HEAD 2> /dev/null \
cut -d/ -f1 | sort -u | grep -vE '^\.' ) | cut -d/ -f1 | sort -u | grep -vE '^\.')
if [ "${#_dirs[@]}" -gt 1 ]; then if [ "${#_dirs[@]}" -gt 1 ]; then
echo "several changed dirs: ${_dirs[*]}" echo "several changed dirs: ${_dirs[*]}"
echo "pass one explicitly: validate.sh <addon-dir>"; exit 1 echo "pass one explicitly: validate.sh <addon-dir>"
exit 1
fi fi
ADDON="${_dirs[0]:-}" ADDON="${_dirs[0]:-}"
fi fi
[ -z "$ADDON" ] && { echo "usage: validate.sh <addon-dir> [--vs-master]"; exit 1; } [ -z "$ADDON" ] && {
echo "usage: validate.sh <addon-dir> [--vs-master]"
exit 1
}
git rev-parse --verify origin/master > /dev/null 2>&1 || { git rev-parse --verify origin/master > /dev/null 2>&1 || {
echo "origin/master missing — run: git fetch origin master"; exit 1; } echo "origin/master missing — run: git fetch origin master"
exit 1
}
export PYTHONDONTWRITEBYTECODE=1 export PYTHONDONTWRITEBYTECODE=1
echo "== validating $ADDON ==" echo "== validating $ADDON =="
@@ -36,14 +45,22 @@ fail=0
note() { printf ' %-13s %s\n' "$1" "$2"; } note() { printf ' %-13s %s\n' "$1" "$2"; }
# execline `run`/`finish` files are not shell (25 of them here, across 21 add-ons). Neither # execline `run`/`finish` files are not shell (25 of them here, across 21 add-ons). Neither
# linter below can read one, so anything either says about it is noise. # linter below can read one, so anything either says about it is noise.
is_execline() { local l; IFS= read -r l < "$1" 2> /dev/null; [[ $l == '#!'*execlineb* ]]; } is_execline() {
local l
IFS= read -r l < "$1" 2> /dev/null
[[ $l == '#!'*execlineb* ]]
}
# Shell: bash -n then shellcheck -x (follows sourced files, as CI does). One list for both. # Shell: bash -n then shellcheck -x (follows sourced files, as CI does). One list for both.
files=() files=()
while IFS= read -r f; do is_execline "$f" || files+=("$f"); done \ while IFS= read -r f; do is_execline "$f" || files+=("$f"); done \
< <(find "$ADDON" -type f \( -name '*.sh' -o -name 'run' -o -name 'finish' -o -name 'autostart' \) 2> /dev/null) < <(find "$ADDON" -type f \( -name '*.sh' -o -name 'run' -o -name 'finish' -o -name 'autostart' \) 2> /dev/null)
for f in "${files[@]}"; do for f in "${files[@]}"; do
if ! out=$(bash -n "$f" 2>&1); then note "bash -n" "FAIL $f"; echo "$out" | sed 's/^/ /'; fail=1; fi if ! out=$(bash -n "$f" 2>&1); then
note "bash -n" "FAIL $f"
echo "$out" | sed 's/^/ /'
fail=1
fi
done done
[ "$fail" -eq 0 ] && note "bash -n" "${#files[@]} file(s) checked" [ "$fail" -eq 0 ] && note "bash -n" "${#files[@]} file(s) checked"
@@ -65,13 +82,16 @@ command -v hadolint > /dev/null 2>&1 && [ -f "$ADDON/Dockerfile" ] && {
if [ -f "$ADDON/config.yaml" ]; then if [ -f "$ADDON/config.yaml" ]; then
# path passed as argv, never interpolated into Python source # path passed as argv, never interpolated into Python source
python3 - "$ADDON/config.yaml" <<'PY' || { note "config.yaml" "FAIL parse"; fail=1; } python3 - "$ADDON/config.yaml" << 'PY' || {
import yaml,sys import yaml,sys
d=yaml.safe_load(open(sys.argv[1])) d=yaml.safe_load(open(sys.argv[1]))
print(' %-13s ok (version=%s, %d options)' % ('config.yaml', d.get('version'), len(d.get('options') or {}))) print(' %-13s ok (version=%s, %d options)' % ('config.yaml', d.get('version'), len(d.get('options') or {})))
missing=[k for k in (d.get('options') or {}) if k not in (d.get('schema') or {})] missing=[k for k in (d.get('options') or {}) if k not in (d.get('schema') or {})]
if missing: print(' %-13s options with no schema entry: %s' % ('WARN', missing)); sys.exit(0) if missing: print(' %-13s options with no schema entry: %s' % ('WARN', missing)); sys.exit(0)
PY PY
note "config.yaml" "FAIL parse"
fail=1
}
command -v yamllint > /dev/null 2>&1 && { command -v yamllint > /dev/null 2>&1 && {
yl=$(yamllint -f parsable "$ADDON/config.yaml" 2>&1 | grep -c .) yl=$(yamllint -f parsable "$ADDON/config.yaml" 2>&1 | grep -c .)
note "yamllint" "$yl finding(s) (compare with --vs-master)" note "yamllint" "$yl finding(s) (compare with --vs-master)"
@@ -79,7 +99,10 @@ PY
fi fi
while IFS= read -r f; do while IFS= read -r f; do
python3 -m py_compile "$f" 2> /dev/null || { note "py_compile" "FAIL $f"; fail=1; } python3 -m py_compile "$f" 2> /dev/null || {
note "py_compile" "FAIL $f"
fail=1
}
done < <(find "$ADDON" -type f -name '*.py' 2> /dev/null) done < <(find "$ADDON" -type f -name '*.py' 2> /dev/null)
$VS_MASTER && command -v npx > /dev/null 2>&1 && [ -f "$ADDON/CHANGELOG.md" ] && { $VS_MASTER && command -v npx > /dev/null 2>&1 && [ -f "$ADDON/CHANGELOG.md" ] && {
@@ -96,7 +119,8 @@ if git diff --name-only origin/master...HEAD 2> /dev/null | grep -Fxq "$ADDON/CH
note "CHANGELOG" "updated" note "CHANGELOG" "updated"
else else
# This one IS gated: onpr_check-pr.yaml exits 1 without it. # This one IS gated: onpr_check-pr.yaml exits 1 without it.
note "CHANGELOG" "NOT UPDATED for $ADDON — CI hard-gates this"; fail=1 note "CHANGELOG" "NOT UPDATED for $ADDON — CI hard-gates this"
fail=1
fi fi
if git diff origin/master...HEAD -- "$ADDON/config.yaml" 2> /dev/null | grep -q '^+version:'; then if git diff origin/master...HEAD -- "$ADDON/config.yaml" 2> /dev/null | grep -q '^+version:'; then
note "version" "bumped" note "version" "bumped"
@@ -110,7 +134,8 @@ note "docker build" "NOT tested locally (dockerd unavailable) — CI is the only
if $VS_MASTER; then if $VS_MASTER; then
echo echo
echo "== findings ADDED by this diff (pre-existing ones filtered out) ==" echo "== findings ADDED by this diff (pre-existing ones filtered out) =="
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
added=0 added=0
# Fed by process substitution, not a pipe: a pipeline runs this in a subshell, where the # Fed by process substitution, not a pipe: a pipeline runs this in a subshell, where the
# findings below could never reach $fail and the verdict would contradict the list. # findings below could never reach $fail and the verdict would contradict the list.
@@ -125,14 +150,26 @@ if $VS_MASTER; then
# cancel it out and report a false clean. # cancel it out and report a false clean.
case "$f" in case "$f" in
*.sh | *autostart | */run | */finish) *.sh | *autostart | */run | */finish)
command -v shellcheck > /dev/null 2>&1 || { echo " $f: SKIPPED (shellcheck not installed)"; continue; } command -v shellcheck > /dev/null 2>&1 || {
cmd() { shellcheck -x -f gcc "$1" 2>&1 | sed 's/^[^:]*:[0-9]*:[0-9]*://'; } ;; echo " $f: SKIPPED (shellcheck not installed)"
continue
}
cmd() { shellcheck -x -f gcc "$1" 2>&1 | sed 's/^[^:]*:[0-9]*:[0-9]*://'; }
;;
*.yaml | *.yml) *.yaml | *.yml)
command -v yamllint > /dev/null 2>&1 || { echo " $f: SKIPPED (yamllint not installed)"; continue; } command -v yamllint > /dev/null 2>&1 || {
cmd() { yamllint -f parsable "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*:[0-9]*//'; } ;; echo " $f: SKIPPED (yamllint not installed)"
continue
}
cmd() { yamllint -f parsable "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*:[0-9]*//'; }
;;
*Dockerfile) *Dockerfile)
command -v hadolint > /dev/null 2>&1 || { echo " $f: SKIPPED (hadolint not installed)"; continue; } command -v hadolint > /dev/null 2>&1 || {
cmd() { hadolint "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*//'; } ;; echo " $f: SKIPPED (hadolint not installed)"
continue
}
cmd() { hadolint "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*//'; }
;;
*) continue ;; *) continue ;;
esac esac
b=$(cmd "$tmp/base" | sort) b=$(cmd "$tmp/base" | sort)
@@ -141,7 +178,8 @@ if $VS_MASTER; then
[ -n "$new" ] && { [ -n "$new" ] && {
echo " $f: $(printf '%s\n' "$new" | grep -c .) NEW finding(s)" echo " $f: $(printf '%s\n' "$new" | grep -c .) NEW finding(s)"
printf '%s\n' "$new" | sed 's/^/ /' | head -5 printf '%s\n' "$new" | sed 's/^/ /' | head -5
added=1; fail=1 added=1
fail=1
} }
done < <(git diff --name-only origin/master...HEAD -- "$ADDON" 2> /dev/null) done < <(git diff --name-only origin/master...HEAD -- "$ADDON" 2> /dev/null)
[ "$added" -eq 0 ] && echo " (none — this diff introduced no new lint findings)" [ "$added" -eq 0 ] && echo " (none — this diff introduced no new lint findings)"

View File

@@ -28,7 +28,10 @@ if [ -n "$RAW" ]; then
# Directory list without checking out any of them. # Directory list without checking out any of them.
git ls-tree -d --name-only HEAD > "$OUT/dirs.txt" git ls-tree -d --name-only HEAD > "$OUT/dirs.txt"
for guess in "$CAND" "${CAND//_/-}" "${CAND//_/.}"; do for guess in "$CAND" "${CAND//_/-}" "${CAND//_/.}"; do
if grep -qxF "$guess" "$OUT/dirs.txt"; then ADDON="$guess"; break; fi if grep -qxF "$guess" "$OUT/dirs.txt"; then
ADDON="$guess"
break
fi
done done
# Separator-insensitive exact match: a title like "[Calibre-web]" (hyphen) # Separator-insensitive exact match: a title like "[Calibre-web]" (hyphen)
# against a directory named calibre_web (underscore) matches neither exact # against a directory named calibre_web (underscore) matches neither exact
@@ -38,7 +41,10 @@ if [ -n "$RAW" ]; then
if [ -z "$ADDON" ]; then if [ -z "$ADDON" ]; then
CAND_STRIPPED=$(tr -d '_.-' <<< "$CAND") CAND_STRIPPED=$(tr -d '_.-' <<< "$CAND")
while IFS= read -r dir; do while IFS= read -r dir; do
if [ "$(tr -d '_.-' <<<"$dir")" = "$CAND_STRIPPED" ]; then ADDON="$dir"; break; fi if [ "$(tr -d '_.-' <<< "$dir")" = "$CAND_STRIPPED" ]; then
ADDON="$dir"
break
fi
done < "$OUT/dirs.txt" done < "$OUT/dirs.txt"
fi fi
# Last resort: longest directory name contained in the candidate. # Last resort: longest directory name contained in the candidate.

2
.gitignore vendored
View File

@@ -0,0 +1,2 @@
# Bytecode of the python helpers in the addons, which rootfs/ copies into the image
__pycache__/

View File

@@ -19,8 +19,8 @@ if ! bashio::supervisor.ping 2>/dev/null; then
if [ ! -f /usr/bin/bashio ]; then if [ ! -f /usr/bin/bashio ]; then
cp -rf /usr/local/lib/bashio-standalone.sh /usr/bin/bashio cp -rf /usr/local/lib/bashio-standalone.sh /usr/bin/bashio
fi fi
grep -rl "^#!.*bashio" /etc | grep -rl "^#!.*bashio" /etc \
while IFS= read -r f; do | while IFS= read -r f; do
grep -qF "source /usr/local/lib/bashio-standalone.sh" "$f" && continue grep -qF "source /usr/local/lib/bashio-standalone.sh" "$f" && continue
sed -i '1a source /usr/local/lib/bashio-standalone.sh' "$f" sed -i '1a source /usr/local/lib/bashio-standalone.sh' "$f"
done done

View File

@@ -152,8 +152,8 @@ if [[ "${1:-}" == "--self-test" ]]; then
done done
fi fi
[[ "$self_test_rc" -eq 0 ]] && [[ "$self_test_rc" -eq 0 ]] \
echo "${#self_test_values[@]} values round-tripped unchanged (export block + dotenv)" && echo "${#self_test_values[@]} values round-tripped unchanged (export block + dotenv)"
exit "$self_test_rc" exit "$self_test_rc"
fi fi
@@ -193,7 +193,10 @@ SECRETSOURCE=""
resolve_secret() { resolve_secret() {
local v="$1" name line local v="$1" name line
[[ "$v" =~ ^[[:space:]]*\!secret[[:space:]]+(.+)$ ]] || { printf '%s' "$v"; return; } [[ "$v" =~ ^[[:space:]]*\!secret[[:space:]]+(.+)$ ]] || {
printf '%s' "$v"
return
}
name="${BASH_REMATCH[1]}" name="${BASH_REMATCH[1]}"
[[ -n "$SECRETSOURCE" ]] || bashio::exit.nok "Secrets not mounted" [[ -n "$SECRETSOURCE" ]] || bashio::exit.nok "Secrets not mounted"

View File

@@ -11,8 +11,14 @@ pick_exec_dir() {
for d in /dev/shm /run /var/run /mnt /root /; do for d in /dev/shm /run /var/run /mnt /root /; do
if [ -d "$d" ] && [ -w "$d" ]; then if [ -d "$d" ] && [ -w "$d" ]; then
local t="${d%/}/.exec_test_$$" local t="${d%/}/.exec_test_$$"
printf '#!/bin/sh\necho ok\n' >"$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; } printf '#!/bin/sh\necho ok\n' > "$t" 2> /dev/null || {
chmod 700 "$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; } rm -f "$t" 2> /dev/null || true
continue
}
chmod 700 "$t" 2> /dev/null || {
rm -f "$t" 2> /dev/null || true
continue
}
if "$t" > /dev/null 2>&1; then if "$t" > /dev/null 2>&1; then
rm -f "$t" 2> /dev/null || true rm -f "$t" 2> /dev/null || true
echo "$d" echo "$d"

View File

@@ -46,7 +46,8 @@ _bashio_color() {
} }
_bashio_log() { _bashio_log() {
local c="${1:-}"; shift || true local c="${1:-}"
shift || true
printf '%s%s%s\n' "$(_bashio_color "$c")" "$*" "$(_bashio_color reset)" printf '%s%s%s\n' "$(_bashio_color "$c")" "$*" "$(_bashio_color reset)"
} }
@@ -246,7 +247,10 @@ bashio::info.supervisor() { printf '%s' "standalone"; }
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
bashio::config() { bashio::config() {
local key="${1:-}" local key="${1:-}"
[ -n "$key" ] || { printf '%s' ""; return 0; } [ -n "$key" ] || {
printf '%s' ""
return 0
}
local v="" local v=""
if _bashio_env_get "$key" > /dev/null 2>&1; then if _bashio_env_get "$key" > /dev/null 2>&1; then
@@ -356,7 +360,10 @@ bashio::host.hostname() {
# bashio::services.available "mqtt" # bashio::services.available "mqtt"
bashio::services() { bashio::services() {
local svc="${1:-}" key="${2:-}" local svc="${1:-}" key="${2:-}"
[ -n "$svc" ] && [ -n "$key" ] || { printf '%s' ""; return 0; } [ -n "$svc" ] && [ -n "$key" ] || {
printf '%s' ""
return 0
}
local upper svc_upper var v="" local upper svc_upper var v=""
upper="$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')" upper="$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')"
@@ -419,7 +426,11 @@ bashio::homeassistant.token() {
# Exit helpers # Exit helpers
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
bashio::exit.ok() { exit 0; } bashio::exit.ok() { exit 0; }
bashio::exit.nok() { local m="${1:-}"; [ -n "$m" ] && bashio::log.red "$m"; exit 1; } bashio::exit.nok() {
local m="${1:-}"
[ -n "$m" ] && bashio::log.red "$m"
exit 1
}
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Core config check shim # Core config check shim

View File

@@ -76,8 +76,14 @@ pick_exec_dir() {
if [ -d "$d" ] && [ -w "$d" ]; then if [ -d "$d" ] && [ -w "$d" ]; then
# Create a tiny test executable to confirm "exec" works # Create a tiny test executable to confirm "exec" works
local t="${d%/}/.exec_test_$$" local t="${d%/}/.exec_test_$$"
printf '#!/bin/sh\necho ok\n' >"$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; } printf '#!/bin/sh\necho ok\n' > "$t" 2> /dev/null || {
chmod 700 "$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; } rm -f "$t" 2> /dev/null || true
continue
}
chmod 700 "$t" 2> /dev/null || {
rm -f "$t" 2> /dev/null || true
continue
}
if "$t" > /dev/null 2>&1; then if "$t" > /dev/null 2>&1; then
rm -f "$t" 2> /dev/null || true rm -f "$t" 2> /dev/null || true
echo "$d" echo "$d"
@@ -257,8 +263,8 @@ if $PID1 && { [ -x /command/with-contenv ] || [ -x /usr/bin/with-contenv ]; }; t
# accepts a surviving symlink-to-directory and would let the dump follow it. rm does not traverse # accepts a surviving symlink-to-directory and would let the dump follow it. rm does not traverse
# a symlink, but it would empty anything bind-mounted at this exact path -- not a configuration # a symlink, but it would empty anything bind-mounted at this exact path -- not a configuration
# any add-on uses, and not one s6 would tolerate either. # any add-on uses, and not one s6 would tolerate either.
if rm -rf "$S6_CONTAINER_ENV" "$S6_CONTAINER_ENV.tmp" && mkdir -p "$S6_CONTAINER_ENV.tmp" && if rm -rf "$S6_CONTAINER_ENV" "$S6_CONTAINER_ENV.tmp" && mkdir -p "$S6_CONTAINER_ENV.tmp" \
s6-dumpenv -- "$S6_CONTAINER_ENV.tmp" && mv "$S6_CONTAINER_ENV.tmp" "$S6_CONTAINER_ENV"; then && s6-dumpenv -- "$S6_CONTAINER_ENV.tmp" && mv "$S6_CONTAINER_ENV.tmp" "$S6_CONTAINER_ENV"; then
echo "Populated $S6_CONTAINER_ENV for with-contenv" echo "Populated $S6_CONTAINER_ENV for with-contenv"
else else
# Leaves the directory absent, which is exactly how this fails today -- so the failure mode is # Leaves the directory absent, which is exactly how this fails today -- so the failure mode is

View File

@@ -1,4 +1,8 @@
- Migrate legacy add-on configuration map names to current app configuration terminology. - Migrate legacy add-on configuration map names to current app configuration terminology.
## 2026.09 (2026-09-12)
- New `source: container` for addons that build `FROM` an image published by someone else. The version is read from the `org.opencontainers.image.version` label of the exact image reference in `updater.json`, instead of from the releases of the application's own repository. Written against `ghcr.io`; an image with no such label leaves the addon untouched
- `source: container` also records the manifest digest in `upstream_digest` and rebuilds when it moves, so a publisher republishing the same version under a new image is no longer invisible
## 2026.08 (2026-08-01) ## 2026.08 (2026-08-01)
- Addon versions written in config.yaml now always comply with Home Assistant versioning: an upstream tag Home Assistant cannot order (`version-bf9e0b4f`, `ubuntu-2026-06-01`, ...) or would sort as older (`1.2.3-2`, `1.2.3+4`) no longer lands in config.yaml. The addon number is incremented instead, while the raw upstream tag stays in updater.json so the same release is never published twice - Addon versions written in config.yaml now always comply with Home Assistant versioning: an upstream tag Home Assistant cannot order (`version-bf9e0b4f`, `ubuntu-2026-06-01`, ...) or would sort as older (`1.2.3-2`, `1.2.3+4`) no longer lands in config.yaml. The addon number is incremented instead, while the raw upstream tag stays in updater.json so the same release is never published twice

View File

@@ -67,15 +67,49 @@ You can add the following tags in the file :
- repository: 'name/repo' coming from github - repository: 'name/repo' coming from github
- paused: true # Pauses the updates - paused: true # Pauses the updates
- slug: the slug name from your addon - slug: the slug name from your addon
- source: dockerhub/github,gitlab,bitbucket,pip,hg,sf,website-feed,local,helm_chart,wiki,system,wp,codeberg (Codeberg is supported via its Gitea API, which is configured automatically) - source: container/dockerhub/github,gitlab,bitbucket,pip,hg,sf,website-feed,local,helm_chart,wiki,system,wp,codeberg (Codeberg is supported via its Gitea API, which is configured automatically)
- upstream_repo: name/repo, example is 'linuxserver/docker-emby' - upstream_repo: name/repo, example is 'linuxserver/docker-emby'. With `source: container` it is instead a full image reference, example is 'ghcr.io/imagegenius/immich:3'
- upstream_version: automatically populated, corresponds to the current upstream version referenced in the addon - upstream_version: automatically populated, corresponds to the current upstream version referenced in the addon
- dockerhub_by_date: in dockerhub, uses the last_update date instead of the version - dockerhub_by_date: in dockerhub, uses the last_update date instead of the version
- dockerhub_list_size: in dockerhub, how many containers to consider for latest version - dockerhub_list_size: in dockerhub, how many containers to consider for latest version
### Addons built on someone else's image
An addon that does not build the application itself, but builds `FROM` an image a third party
publishes, must publish the version that image holds and not the newest release of the
application. `ghcr.io/imagegenius/immich:3` is a rebuild of immich lagging its upstream by days
to weeks: tracking `immich-app/immich` published a version the addon did not contain, and left
the addon with no reason to rebuild once the image finally caught up.
`source: container` reads the version out of the image itself. Set `upstream_repo` to the exact
image reference used in `build.json`, tag included:
```json
{
"source": "container",
"upstream_repo": "ghcr.io/imagegenius/immich:3"
}
```
The version comes from the image's `org.opencontainers.image.version` label, read from the first
linux image the tag publishes, and the addon is left alone when there is no label to read. Written
against `ghcr.io`, which serves anonymous pull tokens from `https://ghcr.io/token`; a registry
authenticating differently, such as Docker Hub, yields no version and the addon is skipped.
The manifest digest is recorded next to the version, in `upstream_digest`, and the addon is
rebuilt when either of the two moves. A publisher rebuilding the same version, for a base image
security fix or a packaging revision, repoints the tag at new content under an unchanged label,
and the digest is the only thing that says so. `upstream_digest` is populated automatically; seed
it by hand when migrating an addon to this source, or the first run counts the unknown digest as
a change and rebuilds once for nothing.
The label is metadata the publisher chooses. Some images carry none, and some record their own
packaging revision rather than the application version, so this source is opt-in per addon and
never a default.
### Addon version numbering ### Addon version numbering
The `version` written in the addon `config.yaml` is the one Home Assistant compares to decide whether an update is available. Home Assistant hides the update when it can order both versions and the new one is not strictly newer (`1.2.3` -> `1.2.3-2` is a semver pre-release, so it is *older*), and it cannot order tags such as `version-bf9e0b4f` or `ubuntu-2026-06-01` at all. The `version` written in the addon `config.yaml` is the one Home Assistant compares to decide whether an update is available. Home Assistant hides the update when it can order both versions and the new one is not strictly newer (`1.2.3` -> `1.2.3-2` is a semver pre-release, so it is _older_), and it cannot order tags such as `version-bf9e0b4f` or `ubuntu-2026-06-01` at all.
The addon version is therefore derived from the upstream tag: The addon version is therefore derived from the upstream tag:

View File

@@ -30,4 +30,4 @@ schema:
slug: updater slug: updater
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/addons_updater url: https://github.com/alexbelgium/hassio-addons/tree/master/addons_updater
version: "2026.08" version: "2026.09"

View File

@@ -109,6 +109,7 @@ for f in */; do
EXCLUDE_TEXT="${EXCLUDE_TEXT:-zzzzzzzzzzzzzzzz}" EXCLUDE_TEXT="${EXCLUDE_TEXT:-zzzzzzzzzzzzzzzz}"
PAUSED=$(jq -r .paused updater.json) PAUSED=$(jq -r .paused updater.json)
DATE="$(date "$DATE_FORMAT")" DATE="$(date "$DATE_FORMAT")"
LASTDIGEST=""
BYDATE=$(jq -r .dockerhub_by_date updater.json) BYDATE=$(jq -r .dockerhub_by_date updater.json)
# Number of elements to check in dockerhub # Number of elements to check in dockerhub
@@ -195,6 +196,45 @@ for f in */; do
&& LASTVERSION="$LASTVERSION-$DATE" && LASTVERSION="$LASTVERSION-$DATE"
LOGINFO="... $SLUG : bydate is true, version is $LASTVERSION" && if [ "$VERBOSE" = true ]; then bashio::log.info "$LOGINFO"; fi LOGINFO="... $SLUG : bydate is true, version is $LASTVERSION" && if [ "$VERBOSE" = true ]; then bashio::log.info "$LOGINFO"; fi
elif [[ "$SOURCE" = container ]]; then
# The addon builds FROM an image someone else publishes rather
# than from the application itself, so the version to publish is
# the one inside that image and not the newest release of the
# application repository, which the image can lag by weeks
LOGINFO="... Source is container" && if [ "$VERBOSE" = true ]; then bashio::log.info "$LOGINFO"; fi
ACCEPT="application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.v2+json"
REGISTRY="${UPSTREAM%%/*}"
IMAGE="${UPSTREAM#*/}"
IMAGEREPO="${IMAGE%:*}"
BASEURL="https://$REGISTRY/v2/$IMAGEREPO"
HEADERS="$(mktemp)"
# The digest the tag resolves to is the one the registry answers
# with, and it moves whenever anything behind the tag does
TOKEN="$(curl -f -L -s "https://$REGISTRY/token?scope=repository:$IMAGEREPO:pull&service=$REGISTRY" | jq -r '.token // empty')" || true
MANIFEST="$(curl -f -L -s -D "$HEADERS" -H "Authorization: Bearer $TOKEN" -H "Accept: $ACCEPT" "$BASEURL/manifests/${IMAGE##*:}")" || true
LASTDIGEST="$(sed -n 's/^[Dd]ocker-[Cc]ontent-[Dd]igest:[[:space:]]*//p' "$HEADERS" | tr -d '\r')"
rm -f "$HEADERS"
# A multi architecture tag answers with an index, whose first
# image is read: the attestations buildx attaches alongside
# describe no architecture and hold no image configuration
CHILD="$(echo "$MANIFEST" | jq -r 'first(.manifests[]? | select(.platform.os == "linux" and .platform.architecture != "unknown") | .digest) // empty')" || true
if [ -n "$CHILD" ]; then
MANIFEST="$(curl -f -L -s -H "Authorization: Bearer $TOKEN" -H "Accept: $ACCEPT" "$BASEURL/manifests/$CHILD")" || true
fi
# Publishers record the application version of an image in its
# OCI label, which is what the addon actually ships
LASTVERSION="$(curl -f -L -s -H "Authorization: Bearer $TOKEN" "$BASEURL/blobs/$(echo "$MANIFEST" | jq -r '.config.digest // empty')" \
| jq -r '.config.Labels["org.opencontainers.image.version"] // empty')" || true
if [ -z "$LASTVERSION" ] || [ -z "$LASTDIGEST" ]; then
bashio::log.warning "... $SLUG : no version could be read from $UPSTREAM, skipping"
continue
fi
else else
# Use source as upstream # Use source as upstream
@@ -344,8 +384,19 @@ for f in */; do
continue continue
fi fi
# What the addon was last built against. A container source adds the
# digest of the image: a publisher rebuilding the same version, for a
# base image fix or a packaging revision, repoints the tag at new
# content under an unchanged label, and only the digest says so
CURRENTSTATE="$CURRENT"
LASTSTATE="$LASTVERSION"
if [[ "$SOURCE" = container ]]; then
CURRENTSTATE="$CURRENT $(jq -r '.upstream_digest // ""' updater.json)"
LASTSTATE="$LASTVERSION $LASTDIGEST"
fi
# Update if needed # Update if needed
if [ "${CURRENT}" != "${LASTVERSION}" ]; then if [ "${CURRENTSTATE}" != "${LASTSTATE}" ]; then
LOGINFO="... $SLUG : update from ${CURRENT} to ${LASTVERSION}" && if [ "$VERBOSE" = true ]; then bashio::log.info "$LOGINFO"; fi LOGINFO="... $SLUG : update from ${CURRENT} to ${LASTVERSION}" && if [ "$VERBOSE" = true ]; then bashio::log.info "$LOGINFO"; fi
ADDONFOLDER="/data/${BASENAME}/${SLUG}" ADDONFOLDER="/data/${BASENAME}/${SLUG}"
@@ -405,7 +456,9 @@ for f in */; do
# Replace upstream tag and date, keeping the file intact if jq # Replace upstream tag and date, keeping the file intact if jq
# fails as a truncated updater.json would lose the addon source # fails as a truncated updater.json would lose the addon source
if ! UPDATERJSON="$(jq --arg version "$LASTVERSION" --arg date "$DATE" '.upstream_version = $version | .last_update = $date' "$ADDONFOLDER/updater.json")"; then if ! UPDATERJSON="$(jq --arg version "$LASTVERSION" --arg date "$DATE" --arg digest "${LASTDIGEST:-}" \
'.upstream_version = $version | .last_update = $date | if $digest == "" then . else .upstream_digest = $digest end' \
"$ADDONFOLDER/updater.json")"; then
bashio::log.error "... $SLUG : updater.json could not be updated, reverting" bashio::log.error "... $SLUG : updater.json could not be updated, reverting"
git checkout -- "$ADDONFOLDER" git checkout -- "$ADDONFOLDER"
continue continue

View File

@@ -1,4 +1,7 @@
## 1.86.0 (2026-09-12)
- Update to latest version from autobrr/autobrr (changelog : https://github.com/autobrr/autobrr/releases)
## 1.85.0 (2026-08-29) ## 1.85.0 (2026-08-29)
- Update to latest version from autobrr/autobrr (changelog : https://github.com/autobrr/autobrr/releases) - Update to latest version from autobrr/autobrr (changelog : https://github.com/autobrr/autobrr/releases)

View File

@@ -16,7 +16,7 @@
ARG BUILD_FROM ARG BUILD_FROM
ARG BUILD_VERSION ARG BUILD_VERSION
ARG BUILD_UPSTREAM="1.85.0" ARG BUILD_UPSTREAM="1.86.0"
FROM ${BUILD_FROM} FROM ${BUILD_FROM}
################## ##################

View File

@@ -108,4 +108,4 @@ schema:
slug: autobrr slug: autobrr
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/autobrr url: https://github.com/alexbelgium/hassio-addons/tree/master/autobrr
version: "1.85.0" version: "1.86.0"

View File

@@ -1,8 +1,8 @@
{ {
"last_update": "2026-08-29", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "autborr", "slug": "autborr",
"source": "github", "source": "github",
"upstream_repo": "autobrr/autobrr", "upstream_repo": "autobrr/autobrr",
"upstream_version": "1.85.0" "upstream_version": "1.86.0"
} }

0
bazarr/rootfs/etc/services.d/nginx/run Normal file → Executable file
View File

0
bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/run Normal file → Executable file
View File

View File

@@ -1,3 +1,7 @@
## 20260912.2 (12-09-2026)
- Minor bugs fixed
## 20260912 (12-09-2026)
- Minor bugs fixed
## 20260910.4 (11-09-2026) ## 20260910.4 (11-09-2026)
- Minor bugs fixed - Minor bugs fixed
## 20260910.3 (11-09-2026) ## 20260910.3 (11-09-2026)

View File

@@ -75,7 +75,7 @@ Requires a second model to confirm the **first** detection of each bird species
The first detection of a species in a day is the weakest evidence the pipeline produces, and it is the one that creates a "new species today" entry. Asking two models to agree on just that one detection removes most spurious new-species entries without slowing anything else down. The first detection of a species in a day is the weakest evidence the pipeline produces, and it is the one that creates a "new species today" entry. Asking two models to agree on just that one detection removes most spurious new-species entries without slowing anything else down.
**Off by default.** Turn it on in the web UI under *Settings → Filters → First Daily Detection Consensus*, or in `config.yaml`: **Off by default.** Turn it on in the web UI under _Settings → Filters → First Daily Detection Consensus_, or in `config.yaml`:
```yaml ```yaml
realtime: realtime:
@@ -89,7 +89,7 @@ It deliberately does **nothing** in these cases, all of which keep today's singl
- you run only one bird model (the default) — a second opinion does not exist, so the rule can never trigger - you run only one bird model (the default) — a second opinion does not exist, so the rule can never trigger
- the species is not a bird — bats and the non-bird sound classes Perch reports (insects, amphibians, mammals, `power_tool`, and so on) - the species is not a bird — bats and the non-bird sound classes Perch reports (insects, amphibians, mammals, `power_tool`, and so on)
- the species is not known to *every* active bird model analyzing that audio source — a species only one of them can name could never reach two confirmations. With several sources running different model combinations, this is decided per source, not add-on-wide - the species is not known to _every_ active bird model analyzing that audio source — a species only one of them can name could never reach two confirmations. With several sources running different model combinations, this is decided per source, not add-on-wide
- a dynamic threshold has actually lowered the bar for that species, meaning you asked for a more permissive gate - a dynamic threshold has actually lowered the bar for that species, meaning you asked for a more permissive gate
- the taxonomy or the database cannot be consulted — it fails open and accepts the detection - the taxonomy or the database cannot be consulted — it fails open and accepts the detection

View File

@@ -127,5 +127,5 @@ slug: birdnet-go-dev
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons url: https://github.com/alexbelgium/hassio-addons
usb: true usb: true
version: "20260910.4" version: "20260912.3"
video: true video: true

View File

@@ -33,7 +33,10 @@ TARGET_DIR=""
while [ "$#" -gt 0 ]; do while [ "$#" -gt 0 ]; do
case "$1" in case "$1" in
--check) CHECK_ONLY=1 ;; --check) CHECK_ONLY=1 ;;
-*) echo "unknown option: $1" >&2; exit 64 ;; -*)
echo "unknown option: $1" >&2
exit 64
;;
*) *)
# Last-one-wins would silently clone into the wrong directory if a caller ever # Last-one-wins would silently clone into the wrong directory if a caller ever
# appended an argument; the pre-flag script used "${1}", so refuse rather than # appended an argument; the pre-flag script used "${1}", so refuse rather than

View File

@@ -9,9 +9,9 @@ Here are some example tests I did (the whole threads are really interesting also
**My recommendation:** **My recommendation:**
- **Best entry system (< 50€):** Boya By-lm40 (30€) + deadcat (10€) - **Best entry system (< 50 €):** Boya By-lm40 (30 €) + deadcat (10 €)
- **Best middle end system (< 150€):** Clippy EM272 TRS/TRRS (55€) + Rode AI micro trs/trrs to usb (70€) + Rycote deadcat (27€) - **Best middle end system (< 150 €):** Clippy EM272 TRS/TRRS (55 €) + Rode AI micro trs/trrs to usb (70 €) + Rycote deadcat (27 €)
- **Best high end system (<400€):** Clippy EM272 XLR (85€) or LOM Ucho Pro (75€) + Focusrite Scarlet 2i2 4th Gen (200€) + Bubblebee Pro Extreme deadcat (45€) - **Best high end system (<400 €):** Clippy EM272 XLR (85 €) or LOM Ucho Pro (75 €) + Focusrite Scarlet 2i2 4th Gen (200 €) + Bubblebee Pro Extreme deadcat (45 €)
**Sources for high end microphones in Europe:** **Sources for high end microphones in Europe:**

View File

@@ -112,6 +112,7 @@ def automatic_mqtt_publish(file, detection, path):
mqttc.publish(mqtt_topic, json_bird, 1) mqttc.publish(mqtt_topic, json_bird, 1)
log.info("Posted to MQTT: ok") log.info("Posted to MQTT: ok")
# Create MQTT client using legacy callback API when available for # Create MQTT client using legacy callback API when available for
# compatibility with paho-mqtt >= 2.0 # compatibility with paho-mqtt >= 2.0
callback_api = getattr(mqtt, "CallbackAPIVersion", None) callback_api = getattr(mqtt, "CallbackAPIVersion", None)

View File

@@ -9,9 +9,9 @@ Here are some example tests I did (the whole threads are really interesting also
**My recommendation:** **My recommendation:**
- **Best entry system (< 50€):** Boya By-lm40 (30€) + deadcat (10€) - **Best entry system (< 50 €):** Boya By-lm40 (30 €) + deadcat (10 €)
- **Best middle end system (< 150€):** Clippy EM272 TRS/TRRS (55€) + Rode AI micro trs/trrs to usb (70€) + Rycote deadcat (27€) - **Best middle end system (< 150 €):** Clippy EM272 TRS/TRRS (55 €) + Rode AI micro trs/trrs to usb (70 €) + Rycote deadcat (27 €)
- **Best high end system (<400€):** Clippy EM272 XLR (85€) or LOM Ucho Pro (75€) + Focusrite Scarlet 2i2 4th Gen (200€) + Bubblebee Pro Extreme deadcat (45€) - **Best high end system (<400 €):** Clippy EM272 XLR (85 €) or LOM Ucho Pro (75 €) + Focusrite Scarlet 2i2 4th Gen (200 €) + Bubblebee Pro Extreme deadcat (45 €)
**Sources for high end microphones in Europe:** **Sources for high end microphones in Europe:**

View File

@@ -112,6 +112,7 @@ def automatic_mqtt_publish(file, detection, path):
mqttc.publish(mqtt_topic, json_bird, 1) mqttc.publish(mqtt_topic, json_bird, 1)
log.info("Posted to MQTT: ok") log.info("Posted to MQTT: ok")
# Create MQTT client using legacy callback API when available for # Create MQTT client using legacy callback API when available for
# compatibility with paho-mqtt >= 2.0 # compatibility with paho-mqtt >= 2.0
callback_api = getattr(mqtt, "CallbackAPIVersion", None) callback_api = getattr(mqtt, "CallbackAPIVersion", None)

View File

@@ -73,7 +73,7 @@ map:
name: BirdNET-PiPy name: BirdNET-PiPy
options: options:
env_vars: [] env_vars: []
ICECAST_PASSWORD: '' ICECAST_PASSWORD: ""
data_location: /config/data data_location: /config/data
panel_icon: mdi:bird panel_icon: mdi:bird
ports: ports:

0
birdnet-pipy/rootfs/etc/services.d/api/run Normal file → Executable file
View File

0
birdnet-pipy/rootfs/etc/services.d/icecast/run Normal file → Executable file
View File

0
birdnet-pipy/rootfs/etc/services.d/main/run Normal file → Executable file
View File

0
birdnet-pipy/rootfs/etc/services.d/model/run Normal file → Executable file
View File

0
birdnet-pipy/rootfs/etc/services.d/nginx/run Normal file → Executable file
View File

0
bitwarden/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run Normal file → Executable file
View File

0
bitwarden/rootfs/etc/s6-overlay/s6-rc.d/nginx/run Normal file → Executable file
View File

View File

View File

@@ -1,3 +1,6 @@
## 1.95.101-ls128 (2026-09-12)
- Update to latest version from linuxserver/docker-brave (changelog : https://github.com/linuxserver/docker-brave/releases)
- Migrate legacy add-on configuration map names to current app configuration terminology. - Migrate legacy add-on configuration map names to current app configuration terminology.
## 1.94.121-ls127 (2026-09-05) ## 1.94.121-ls127 (2026-09-05)

View File

@@ -69,5 +69,5 @@ slug: brave
tmpfs: true tmpfs: true
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons url: https://github.com/alexbelgium/hassio-addons
version: "1.94.121-ls127" version: "1.95.101-ls128"
video: true video: true

View File

@@ -1,9 +1,9 @@
{ {
"github_fulltag": "true", "github_fulltag": "true",
"last_update": "2026-09-05", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "brave", "slug": "brave",
"source": "github", "source": "github",
"upstream_repo": "linuxserver/docker-brave", "upstream_repo": "linuxserver/docker-brave",
"upstream_version": "1.94.121-ls127" "upstream_version": "1.95.101-ls128"
} }

1
browser_chromium/rootfs/etc/cont-init.d/20-folders.sh Executable file → Normal file
View File

@@ -7,7 +7,6 @@ set -e
PUID=$(bashio::config "PUID") PUID=$(bashio::config "PUID")
PGID=$(bashio::config "PGID") PGID=$(bashio::config "PGID")
# Create cache # Create cache
#mkdir -p /.cache #mkdir -p /.cache
#chmod 755 /.cache #chmod 755 /.cache

View File

@@ -1,3 +1,6 @@
## 2.56.7 (2026-09-12)
- Update to latest version from browserless/chrome (changelog : https://github.com/browserless/chrome/releases)
- Migrate legacy add-on configuration map names to current app configuration terminology. - Migrate legacy add-on configuration map names to current app configuration terminology.
## 2.56.3 (2026-09-05) ## 2.56.3 (2026-09-05)

View File

@@ -86,5 +86,5 @@ schema:
slug: browserless_chrome slug: browserless_chrome
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/browserless_chrome url: https://github.com/alexbelgium/hassio-addons/tree/master/browserless_chrome
version: "2.56.3" version: "2.56.7"
webui: "[PROTO:ssl]://[HOST]:[PORT:3000]/docs" webui: "[PROTO:ssl]://[HOST]:[PORT:3000]/docs"

View File

@@ -1,9 +1,9 @@
{ {
"github_tagfilter": "v", "github_tagfilter": "v",
"last_update": "2026-09-05", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "browserless_chrome", "slug": "browserless_chrome",
"source": "github", "source": "github",
"upstream_repo": "browserless/chrome", "upstream_repo": "browserless/chrome",
"upstream_version": "2.56.3" "upstream_version": "2.56.7"
} }

View File

@@ -99,7 +99,7 @@ This addon supports mounting both local drives and remote SMB shares:
### Optional Calibre-Web features ### Optional Calibre-Web features
Calibre-Web documents optional extras that a manual installation adds with `pip install calibreweb[metadata]` and similar. **You do not need to install anything here**: the LinuxServer base image this add-on builds on installs Calibre-Web's `requirements.txt` *and* its full `optional-requirements.txt` into the application's virtualenv, so the gdrive, gmail, goodreads, ldap, oauth, metadata, comics and kobo dependencies are all present already. Running `pip install calibreweb[...]` inside the container is not a supported way to enable them: it installs the PyPI distribution of Calibre-Web over an installation that already has those dependencies, and it can disturb the versions the base image pinned. It is also thrown away, because the Supervisor recreates the add-on container on restart. Calibre-Web documents optional extras that a manual installation adds with `pip install calibreweb[metadata]` and similar. **You do not need to install anything here**: the LinuxServer base image this add-on builds on installs Calibre-Web's `requirements.txt` _and_ its full `optional-requirements.txt` into the application's virtualenv, so the gdrive, gmail, goodreads, ldap, oauth, metadata, comics and kobo dependencies are all present already. Running `pip install calibreweb[...]` inside the container is not a supported way to enable them: it installs the PyPI distribution of Calibre-Web over an installation that already has those dependencies, and it can disturb the versions the base image pinned. It is also thrown away, because the Supervisor recreates the add-on container on restart.
Optional features are switched on in the Calibre-Web web interface, not in the add-on options, under `Admin` -> `Basic Configuration` -> `Feature Configuration` (for example `Enable Uploads`, `Enable Kobo sync`, `Use Goodreads`). Optional features are switched on in the Calibre-Web web interface, not in the add-on options, under `Admin` -> `Basic Configuration` -> `Feature Configuration` (for example `Enable Uploads`, `Enable Kobo sync`, `Use Goodreads`).

View File

@@ -30,16 +30,16 @@ else
# duplicates it can leave behind are entries calibre-web skips or already trusts. # duplicates it can leave behind are entries calibre-web skips or already trusts.
# The column only exists once calibre-web 0.6.27+ has migrated app.db and cont-init runs # The column only exists once calibre-web 0.6.27+ has migrated app.db and cont-init runs
# before calibre-web, so a failure here is not fatal : the next start applies it. # before calibre-web, so a failure here is not fatal : the next start applies it.
trusted_ips_error=$(sqlite3 /config/app.db "update settings set config_reverse_proxy_trusted_ips='127.0.0.1,::1,::ffff:127.0.0.1,172.30.32.0/23,::ffff:172.30.32.0/119,'||coalesce(config_reverse_proxy_trusted_ips,'') where coalesce(config_reverse_proxy_trusted_ips,'') not like '%::ffff:172.30.32.0/119%'" 2>&1) || trusted_ips_error=$(sqlite3 /config/app.db "update settings set config_reverse_proxy_trusted_ips='127.0.0.1,::1,::ffff:127.0.0.1,172.30.32.0/23,::ffff:172.30.32.0/119,'||coalesce(config_reverse_proxy_trusted_ips,'') where coalesce(config_reverse_proxy_trusted_ips,'') not like '%::ffff:172.30.32.0/119%'" 2>&1) \
bashio::log.warning "Could not set the ingress trusted ip list, it will be applied at next start (${trusted_ips_error})" || bashio::log.warning "Could not set the ingress trusted ip list, it will be applied at next start (${trusted_ips_error})"
# Calibre-web autodetects kepubify only while this setting is still NULL. On every install # Calibre-web autodetects kepubify only while this setting is still NULL. On every install
# that predates the Dockerfile fix above, that detection already ran, found nothing usable and # that predates the Dockerfile fix above, that detection already ran, found nothing usable and
# stored an empty string, so it is never retried. Put an empty value back to NULL and # stored an empty string, so it is never retried. Put an empty value back to NULL and
# calibre-web detects /opt/kepubify itself when it starts, a few seconds after this runs. # calibre-web detects /opt/kepubify itself when it starts, a few seconds after this runs.
# A path the user set by hand is not empty and is left alone. # A path the user set by hand is not empty and is left alone.
kepubify_error=$(sqlite3 /config/app.db "update settings set config_kepubifypath = NULL where config_kepubifypath = ''" 2>&1) || kepubify_error=$(sqlite3 /config/app.db "update settings set config_kepubifypath = NULL where config_kepubifypath = ''" 2>&1) \
bashio::log.warning "Could not reset the kepubify path, it will be applied at next start (${kepubify_error})" || bashio::log.warning "Could not reset the kepubify path, it will be applied at next start (${kepubify_error})"
fi fi
bashio::log.info "Default username:password is admin:admin123" bashio::log.info "Default username:password is admin:admin123"

View File

@@ -1,4 +1,7 @@
## 0.60.4 (2026-09-12)
- Update to latest version from linuxserver/docker-changedetection.io (changelog : https://github.com/linuxserver/docker-changedetection.io/releases)
## 0.60.3 (2026-09-05) ## 0.60.3 (2026-09-05)
- Update to latest version from linuxserver/docker-changedetection.io (changelog : https://github.com/linuxserver/docker-changedetection.io/releases) - Update to latest version from linuxserver/docker-changedetection.io (changelog : https://github.com/linuxserver/docker-changedetection.io/releases)

View File

@@ -34,4 +34,4 @@ schema:
slug: changedetection.io slug: changedetection.io
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/changedetection.io url: https://github.com/alexbelgium/hassio-addons/tree/master/changedetection.io
version: "0.60.3" version: "0.60.4"

0
changedetection.io/rootfs/etc/services.d/nginx/run Normal file → Executable file
View File

View File

@@ -1,9 +1,9 @@
{ {
"github_fulltag": "false", "github_fulltag": "false",
"last_update": "2026-09-05", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "changedetection.io", "slug": "changedetection.io",
"source": "github", "source": "github",
"upstream_repo": "linuxserver/docker-changedetection.io", "upstream_repo": "linuxserver/docker-changedetection.io",
"upstream_version": "0.60.3" "upstream_version": "0.60.4"
} }

View File

@@ -1,3 +1,11 @@
## 07308545.8 (2026-09-12)
- Update to latest version from aaddrick/claude-desktop-debian (changelog : https://github.com/aaddrick/claude-desktop-debian/releases)
- Upstream tag : v3.2.4+claude1.52386.3
## 07308545.7 (2026-09-12)
- Update to latest version from aaddrick/claude-desktop-debian (changelog : https://github.com/aaddrick/claude-desktop-debian/releases)
- Upstream tag : v3.2.4+claude1.49585.0
- Migrate legacy add-on configuration map names to current app configuration terminology. - Migrate legacy add-on configuration map names to current app configuration terminology.
## 07308545.6 (2026-09-05) ## 07308545.6 (2026-09-05)

View File

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

View File

@@ -338,8 +338,7 @@ ln -sfn "$CODEX_BIN" "$CODEX_LINK"
CODEX_SANDBOX_MODE="$(bashio::config 'codex_sandbox_mode' 'danger-full-access')" CODEX_SANDBOX_MODE="$(bashio::config 'codex_sandbox_mode' 'danger-full-access')"
run_as_runtime_user mkdir -p "$RUNTIME_HOME/.codex" run_as_runtime_user mkdir -p "$RUNTIME_HOME/.codex"
CODEX_SANDBOX_MODE="$CODEX_SANDBOX_MODE" RUNTIME_HOME="$RUNTIME_HOME" \ CODEX_SANDBOX_MODE="$CODEX_SANDBOX_MODE" RUNTIME_HOME="$RUNTIME_HOME" \
run_as_runtime_user python3 - <<'PY' \ run_as_runtime_user python3 - << 'PY' || bashio::log.warning "Unable to update the managed Codex configuration block"
|| bashio::log.warning "Unable to update the managed Codex configuration block"
import os import os
import re import re
import tomllib import tomllib

0
claude_desktop/rootfs/usr/local/bin/codex-login Executable file → Normal file
View File

12
claude_desktop/rootfs/usr/local/bin/ha-cli Executable file → Normal file
View File

@@ -133,7 +133,9 @@ def rest(method, base, token, path, body=None):
if body is not None: if body is not None:
data = json.dumps(body).encode("utf-8") data = json.dumps(body).encode("utf-8")
headers["Content-Type"] = "application/json" headers["Content-Type"] = "application/json"
req = urllib.request.Request(_url(base, path), data=data, method=method, headers=headers) req = urllib.request.Request(
_url(base, path), data=data, method=method, headers=headers
)
try: try:
with urllib.request.urlopen(req, timeout=30) as resp: with urllib.request.urlopen(req, timeout=30) as resp:
text = resp.read().decode("utf-8") text = resp.read().decode("utf-8")
@@ -141,7 +143,9 @@ def rest(method, base, token, path, body=None):
detail = exc.read().decode("utf-8", "replace").strip() detail = exc.read().decode("utf-8", "replace").strip()
sys.exit(f"ha-cli: HTTP {exc.code} {exc.reason} on {method} {path}\n{detail}") sys.exit(f"ha-cli: HTTP {exc.code} {exc.reason} on {method} {path}\n{detail}")
except urllib.error.URLError as exc: except urllib.error.URLError as exc:
sys.exit(f"ha-cli: cannot reach Home Assistant ({exc.reason}) on {method} {path}") sys.exit(
f"ha-cli: cannot reach Home Assistant ({exc.reason}) on {method} {path}"
)
try: try:
return json.loads(text) if text.strip() else None return json.loads(text) if text.strip() else None
except ValueError: except ValueError:
@@ -214,7 +218,9 @@ def main(argv):
sys.exit("usage: ha-cli call <domain.service> [BODY]") sys.exit("usage: ha-cli call <domain.service> [BODY]")
domain, service = args[0].split(".", 1) domain, service = args[0].split(".", 1)
body = _read_body(args[1]) if len(args) > 1 else None body = _read_body(args[1]) if len(args) > 1 else None
_print(rest("POST", rest_base, token, f"services/{domain}/{service}", body or {})) _print(
rest("POST", rest_base, token, f"services/{domain}/{service}", body or {})
)
elif cmd == "states": elif cmd == "states":
path = f"states/{args[0]}" if args else "states" path = f"states/{args[0]}" if args else "states"
_print(rest("GET", rest_base, token, path)) _print(rest("GET", rest_base, token, path))

View File

@@ -76,7 +76,11 @@ def main() -> int:
# Find big string/array fields before paying the headroom import cost. # Find big string/array fields before paying the headroom import cost.
def is_string_array(value): def is_string_array(value):
return isinstance(value, list) and len(value) > ARRAY_KEEP and all(isinstance(v, str) for v in value) return (
isinstance(value, list)
and len(value) > ARRAY_KEEP
and all(isinstance(v, str) for v in value)
)
if isinstance(response, str): if isinstance(response, str):
string_candidates = ["__whole__"] if len(response) >= MIN_CHARS else [] string_candidates = ["__whole__"] if len(response) >= MIN_CHARS else []
@@ -85,10 +89,14 @@ def main() -> int:
string_candidates = [ string_candidates = [
key key
for key, value in response.items() for key, value in response.items()
if key not in SKIP_KEYS and isinstance(value, str) and len(value) >= MIN_CHARS if key not in SKIP_KEYS
and isinstance(value, str)
and len(value) >= MIN_CHARS
] ]
array_candidates = [ array_candidates = [
key for key, value in response.items() if key not in SKIP_KEYS and is_string_array(value) key
for key, value in response.items()
if key not in SKIP_KEYS and is_string_array(value)
] ]
else: else:
string_candidates = [] string_candidates = []

View File

@@ -4,6 +4,6 @@
"github_fulltag": true, "github_fulltag": true,
"slug": "claude_desktop", "slug": "claude_desktop",
"paused": false, "paused": false,
"upstream_version": "v3.2.3+claude1.40609.1", "upstream_version": "v3.2.4+claude1.52386.3",
"last_update": "2026-09-05" "last_update": "2026-09-12"
} }

View File

@@ -1,8 +1,7 @@
arch: arch:
- aarch64 - aarch64
- amd64 - amd64
description: description: Automatically removes stuck and unwanted downloads from your *arr and download clients
Automatically removes stuck and unwanted downloads from your *arr and download clients
devices: devices:
- /dev/dri - /dev/dri
- /dev/dri/card0 - /dev/dri/card0

View File

@@ -1,3 +1,6 @@
## 2.3.0 (2026-09-12)
- Update to latest version from ajslater/codex (changelog : https://github.com/ajslater/codex/releases)
- Migrate legacy add-on configuration map names to current app configuration terminology. - Migrate legacy add-on configuration map names to current app configuration terminology.
## 2.2.11 (2026-08-29) ## 2.2.11 (2026-08-29)

View File

@@ -101,4 +101,4 @@ schema:
slug: codex slug: codex
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons url: https://github.com/alexbelgium/hassio-addons
version: "2.2.11" version: "2.3.0"

View File

@@ -1,9 +1,9 @@
{ {
"github_beta": "true", "github_beta": "true",
"last_update": "2026-08-29", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "codex", "slug": "codex",
"source": "github", "source": "github",
"upstream_repo": "ajslater/codex", "upstream_repo": "ajslater/codex",
"upstream_version": "2.2.11" "upstream_version": "2.3.0"
} }

View File

@@ -1,4 +1,7 @@
## 0.38.15 (2026-09-12)
- Update to latest version from frankieramirez/comicarr (changelog : https://github.com/frankieramirez/comicarr/releases)
## 0.38.7 (2026-09-05) ## 0.38.7 (2026-09-05)
- Update to latest version from frankieramirez/comicarr (changelog : https://github.com/frankieramirez/comicarr/releases) - Update to latest version from frankieramirez/comicarr (changelog : https://github.com/frankieramirez/comicarr/releases)

View File

@@ -16,7 +16,7 @@
ARG BUILD_FROM ARG BUILD_FROM
ARG BUILD_VERSION ARG BUILD_VERSION
ARG BUILD_UPSTREAM="0.38.7" ARG BUILD_UPSTREAM="0.38.15"
FROM ghcr.io/frankieramirez/comicarr:${BUILD_UPSTREAM} FROM ghcr.io/frankieramirez/comicarr:${BUILD_UPSTREAM}
################## ##################

View File

@@ -102,4 +102,4 @@ schema:
slug: comicarr slug: comicarr
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/comicarr url: https://github.com/alexbelgium/hassio-addons/tree/master/comicarr
version: "0.38.7" version: "0.38.15"

0
comicarr/rootfs/etc/services.d/comicarr/run Normal file → Executable file
View File

0
comicarr/rootfs/etc/services.d/nginx/run Normal file → Executable file
View File

View File

@@ -1,10 +1,10 @@
{ {
"github_beta": "false", "github_beta": "false",
"github_fulltag": false, "github_fulltag": false,
"last_update": "2026-09-05", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "comicarr", "slug": "comicarr",
"source": "github", "source": "github",
"upstream_repo": "frankieramirez/comicarr", "upstream_repo": "frankieramirez/comicarr",
"upstream_version": "0.38.7" "upstream_version": "0.38.15"
} }

View File

@@ -1,3 +1,6 @@
## 4.10.0.40 (2026-09-12)
- Update to latest version from linuxserver/docker-emby (changelog : https://github.com/linuxserver/docker-emby/releases)
- Migrate legacy add-on configuration map names to current app configuration terminology. - Migrate legacy add-on configuration map names to current app configuration terminology.
## 4.9.5.0 (2026-05-19) ## 4.9.5.0 (2026-05-19)

View File

@@ -121,5 +121,5 @@ schema:
slug: emby slug: emby
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/emby url: https://github.com/alexbelgium/hassio-addons/tree/master/emby
version: "4.9.5.0" version: "4.10.0.40"
video: true video: true

0
emby/rootfs/etc/services.d/nginx/run Normal file → Executable file
View File

View File

@@ -1,9 +1,9 @@
{ {
"github_beta": "false", "github_beta": "false",
"last_update": "2026-05-19", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "emby", "slug": "emby",
"source": "github", "source": "github",
"upstream_repo": "linuxserver/docker-emby", "upstream_repo": "linuxserver/docker-emby",
"upstream_version": "4.9.5.0" "upstream_version": "4.10.0.40"
} }

View File

@@ -1,3 +1,6 @@
## 4.10.0.40 (2026-09-12)
- Update to latest version from linuxserver/docker-emby (changelog : https://github.com/linuxserver/docker-emby/releases)
- Migrate legacy add-on configuration map names to current app configuration terminology. - Migrate legacy add-on configuration map names to current app configuration terminology.
## 4.10.0.30 (2026-09-05) ## 4.10.0.30 (2026-09-05)

View File

@@ -16,7 +16,7 @@
ARG BUILD_FROM ARG BUILD_FROM
ARG BUILD_VERSION ARG BUILD_VERSION
ARG BUILD_UPSTREAM="4.10.0.30" ARG BUILD_UPSTREAM="4.10.0.40"
FROM ${BUILD_FROM} FROM ${BUILD_FROM}
################## ##################

View File

@@ -122,5 +122,5 @@ schema:
slug: emby_nas slug: emby_nas
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/emby url: https://github.com/alexbelgium/hassio-addons/tree/master/emby
version: "4.10.0.30" version: "4.10.0.40"
video: true video: true

View File

@@ -1,9 +1,9 @@
{ {
"github_beta": "true", "github_beta": "true",
"last_update": "2026-09-05", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "emby", "slug": "emby",
"source": "github", "source": "github",
"upstream_repo": "linuxserver/docker-emby", "upstream_repo": "linuxserver/docker-emby",
"upstream_version": "4.10.0.30" "upstream_version": "4.10.0.40"
} }

View File

@@ -1,3 +1,7 @@
## 4.4.28 (2026-09-12)
- Update to latest version from ente/ente (changelog : https://github.com/ente/ente/releases)
- Upstream tag : 1.7.29
- Migrate legacy add-on configuration map names to current app configuration terminology. - Migrate legacy add-on configuration map names to current app configuration terminology.
## 4.4.27 (2026-09-05) ## 4.4.27 (2026-09-05)

View File

@@ -131,6 +131,6 @@ schema:
slug: ente slug: ente
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons url: https://github.com/alexbelgium/hassio-addons
version: "4.4.27" version: "4.4.28"
video: true video: true
webui: http://[HOST]:[PORT:3000] webui: http://[HOST]:[PORT:3000]

View File

@@ -1,9 +1,9 @@
{ {
"github_beta": "false", "github_beta": "false",
"last_update": "2026-09-05", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "ente", "slug": "ente",
"source": "github", "source": "github",
"upstream_repo": "ente/ente", "upstream_repo": "ente/ente",
"upstream_version": "1.7.28" "upstream_version": "1.7.29"
} }

View File

@@ -1,4 +1,12 @@
## 2026.09.12 (2026-09-12)
- Update to latest version from charlocharlie/epicgames-freegames
- Upstream tag : debian-2026-09-12
## 2026.09.11 (2026-09-11)
- Update to latest version from charlocharlie/epicgames-freegames
- Upstream tag : debian-2026-09-11
## 2026.08.29 (2026-08-29) ## 2026.08.29 (2026-08-29)
- Update to latest version from charlocharlie/epicgames-freegames - Update to latest version from charlocharlie/epicgames-freegames
- Upstream tag : debian-2026-08-29 - Upstream tag : debian-2026-08-29

View File

@@ -88,5 +88,5 @@ schema:
slug: epicgamesfree slug: epicgamesfree
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons url: https://github.com/alexbelgium/hassio-addons
version: "2026.08.29" version: "2026.09.12"
webui: "[PROTO:ssl]://[HOST]:[PORT:3000]" webui: "[PROTO:ssl]://[HOST]:[PORT:3000]"

View File

@@ -2,10 +2,10 @@
"dockerhub_by_date": true, "dockerhub_by_date": true,
"dockerhub_list_size": 2, "dockerhub_list_size": 2,
"github_exclude": "-", "github_exclude": "-",
"last_update": "2026-08-29", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "epicgamesfree", "slug": "epicgamesfree",
"source": "dockerhub", "source": "dockerhub",
"upstream_repo": "charlocharlie/epicgames-freegames", "upstream_repo": "charlocharlie/epicgames-freegames",
"upstream_version": "debian-2026-08-29" "upstream_version": "debian-2026-09-12"
} }

View File

@@ -4,9 +4,17 @@ set -e
# Source environment variables for cron context # Source environment variables for cron context
# shellcheck disable=SC1091 # shellcheck disable=SC1091
if [ -f /etc/environment ]; then set -a; . /etc/environment; set +a; fi if [ -f /etc/environment ]; then
set -a
. /etc/environment
set +a
fi
# shellcheck disable=SC1091 # shellcheck disable=SC1091
if [ -f /.env ]; then set -a; . /.env; set +a; fi if [ -f /.env ]; then
set -a
. /.env
set +a
fi
# Use persisted value instead of calling bashio::config (www-data may lack API access) # Use persisted value instead of calling bashio::config (www-data may lack API access)
CONFIGSOURCE="${CONFIG_LOCATION}" CONFIGSOURCE="${CONFIG_LOCATION}"

View File

@@ -1,4 +1,7 @@
## 3.5.2 (2026-09-12)
- Update to latest version from FlareSolverr/FlareSolverr (changelog : https://github.com/FlareSolverr/FlareSolverr/releases)
## 3.5.0 (2026-05-30) ## 3.5.0 (2026-05-30)
- Update to latest version from FlareSolverr/FlareSolverr (changelog : https://github.com/FlareSolverr/FlareSolverr/releases) - Update to latest version from FlareSolverr/FlareSolverr (changelog : https://github.com/FlareSolverr/FlareSolverr/releases)

View File

@@ -79,5 +79,5 @@ schema:
slug: flaresolverr slug: flaresolverr
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons url: https://github.com/alexbelgium/hassio-addons
version: "3.5.0" version: "3.5.2"
webui: "[PROTO:ssl]://[HOST]:[PORT:8191]" webui: "[PROTO:ssl]://[HOST]:[PORT:8191]"

View File

@@ -1,8 +1,8 @@
{ {
"last_update": "2026-05-30", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "flaresolverr", "slug": "flaresolverr",
"source": "github", "source": "github",
"upstream_repo": "FlareSolverr/FlareSolverr", "upstream_repo": "FlareSolverr/FlareSolverr",
"upstream_version": "3.5.0" "upstream_version": "3.5.2"
} }

View File

@@ -1,3 +1,7 @@
## 2.1.1 (2026-09-12)
- Update to latest version from P-Adamiec/Free-Games-Claimer-Remaster (changelog : https://github.com/P-Adamiec/Free-Games-Claimer-Remaster/releases)
- Upstream tag : 1.9
- Migrate legacy add-on configuration map names to current app configuration terminology. - Migrate legacy add-on configuration map names to current app configuration terminology.
## 2.1.0 (2026-08-24) ## 2.1.0 (2026-08-24)

View File

@@ -17,7 +17,7 @@ ARG BUILD_REF
ARG BUILD_REPOSITORY ARG BUILD_REPOSITORY
ARG BUILD_VERSION ARG BUILD_VERSION
ARG UPSTREAM_REPOSITORY="P-Adamiec/Free-Games-Claimer-Remaster" ARG UPSTREAM_REPOSITORY="P-Adamiec/Free-Games-Claimer-Remaster"
ARG BUILD_UPSTREAM="1.6" ARG BUILD_UPSTREAM="1.9"
ENV S6_CMD_WAIT_FOR_SERVICES="1" \ ENV S6_CMD_WAIT_FOR_SERVICES="1" \
S6_CMD_WAIT_FOR_SERVICES_MAXTIME="0" \ S6_CMD_WAIT_FOR_SERVICES_MAXTIME="0" \

View File

@@ -96,5 +96,5 @@ schema:
slug: free_games_claimer slug: free_games_claimer
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons url: https://github.com/alexbelgium/hassio-addons
version: "2.1.0" version: "2.1.1"
webui: "[PROTO:ssl]://[HOST]:[PORT:6080]" webui: "[PROTO:ssl]://[HOST]:[PORT:6080]"

View File

@@ -2,14 +2,14 @@
# Complete option reference: # Complete option reference:
# https://github.com/P-Adamiec/Free-Games-Claimer-Remaster#configuration # https://github.com/P-Adamiec/Free-Games-Claimer-Remaster#configuration
HEIGHT=720
LOGIN_TIMEOUT=180
NOVNC_PORT=6080
# Browser and noVNC # Browser and noVNC
SHOW=1 SHOW=1
WIDTH=1280
HEIGHT=720
TIMEOUT=60 TIMEOUT=60
LOGIN_TIMEOUT=180
VNC_LOGIN_TIMEOUT=180 VNC_LOGIN_TIMEOUT=180
NOVNC_PORT=6080 WIDTH=1280
# Public address used in notification links when behind a reverse proxy. # Public address used in notification links when behind a reverse proxy.
# VNC_URL=https://fgc.example.tld # VNC_URL=https://fgc.example.tld

View File

@@ -47,8 +47,10 @@ def normalize_timestamp(value: Any) -> str:
return parsed.isoformat(sep=" ", timespec="seconds") return parsed.isoformat(sep=" ", timespec="seconds")
except ValueError: except ValueError:
pass pass
return datetime.now(timezone.utc).replace(tzinfo=None).isoformat( return (
sep=" ", timespec="seconds" datetime.now(timezone.utc)
.replace(tzinfo=None)
.isoformat(sep=" ", timespec="seconds")
) )
@@ -99,8 +101,7 @@ def iter_records(store: str, payload: Any, source: Path) -> Iterator[dict[str, A
def ensure_schema(connection: sqlite3.Connection) -> None: def ensure_schema(connection: sqlite3.Connection) -> None:
connection.executescript( connection.executescript("""
"""
CREATE TABLE IF NOT EXISTS claimed_games ( CREATE TABLE IF NOT EXISTS claimed_games (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
store VARCHAR(32) NOT NULL, store VARCHAR(32) NOT NULL,
@@ -117,8 +118,7 @@ def ensure_schema(connection: sqlite3.Connection) -> None:
CREATE INDEX IF NOT EXISTS ix_claimed_games_store ON claimed_games (store); CREATE INDEX IF NOT EXISTS ix_claimed_games_store ON claimed_games (store);
CREATE INDEX IF NOT EXISTS ix_claimed_games_user ON claimed_games (user); CREATE INDEX IF NOT EXISTS ix_claimed_games_user ON claimed_games (user);
CREATE INDEX IF NOT EXISTS ix_claimed_games_game_id ON claimed_games (game_id); CREATE INDEX IF NOT EXISTS ix_claimed_games_game_id ON claimed_games (game_id);
""" """)
)
def migrate() -> int: def migrate() -> int:
@@ -209,7 +209,12 @@ def migrate() -> int:
connection.commit() connection.commit()
log(f"Imported {source_imported} record(s) from {source}") log(f"Imported {source_imported} record(s) from {source}")
except (OSError, ValueError, json.JSONDecodeError, sqlite3.Error) as err: except (
OSError,
ValueError,
json.JSONDecodeError,
sqlite3.Error,
) as err:
connection.rollback() connection.rollback()
message = f"Failed to import {source}: {err}" message = f"Failed to import {source}: {err}"
errors.append(message) errors.append(message)

View File

@@ -3,11 +3,11 @@
"dockerhub_list_size": 2, "dockerhub_list_size": 2,
"github_exclude": "d", "github_exclude": "d",
"github_fulltag": false, "github_fulltag": false,
"last_update": "24-08-2026", "last_update": "2026-09-12",
"paused": false, "paused": false,
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "free_games_claimer", "slug": "free_games_claimer",
"source": "github", "source": "github",
"upstream_repo": "P-Adamiec/Free-Games-Claimer-Remaster", "upstream_repo": "P-Adamiec/Free-Games-Claimer-Remaster",
"upstream_version": "1.6" "upstream_version": "1.9"
} }

View File

@@ -1,4 +1,7 @@
## 2.1.2 (2026-09-12)
- Update to latest version from linuxserver/docker-grav (changelog : https://github.com/linuxserver/docker-grav/releases)
## 2.0.24 (2026-09-05) ## 2.0.24 (2026-09-05)
- Update to latest version from linuxserver/docker-grav (changelog : https://github.com/linuxserver/docker-grav/releases) - Update to latest version from linuxserver/docker-grav (changelog : https://github.com/linuxserver/docker-grav/releases)

View File

@@ -16,7 +16,7 @@
ARG BUILD_FROM ARG BUILD_FROM
ARG BUILD_VERSION ARG BUILD_VERSION
ARG BUILD_UPSTREAM="2.0.24" ARG BUILD_UPSTREAM="2.1.2"
FROM ${BUILD_FROM} FROM ${BUILD_FROM}
################## ##################

View File

@@ -89,5 +89,5 @@ schema:
slug: grav slug: grav
udev: true udev: true
url: https://github.com/alexbelgium/hassio-addons url: https://github.com/alexbelgium/hassio-addons
version: "2.0.24" version: "2.1.2"
webui: "[PROTO:ssl]://[HOST]:[PORT:80]" webui: "[PROTO:ssl]://[HOST]:[PORT:80]"

View File

@@ -1,9 +1,9 @@
{ {
"github_beta": false, "github_beta": false,
"last_update": "2026-09-05", "last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons", "repository": "alexbelgium/hassio-addons",
"slug": "grav", "slug": "grav",
"source": "github", "source": "github",
"upstream_repo": "linuxserver/docker-grav", "upstream_repo": "linuxserver/docker-grav",
"upstream_version": "2.0.24" "upstream_version": "2.1.2"
} }

Some files were not shown because too many files have changed in this diff Show More