mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-09-17 15:19:08 +02:00
Compare commits
10 Commits
docs/skill
...
docs/skill
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
833d0d3758 | ||
|
|
138e7d7c33 | ||
|
|
8341e542fb | ||
|
|
a68ef0dde2 | ||
|
|
23fad482b8 | ||
|
|
5a87daf3e4 | ||
|
|
f12cfa9b78 | ||
|
|
afbab942a6 | ||
|
|
0cb360d04a | ||
|
|
7cd844ac9f |
@@ -116,9 +116,10 @@ Attack your own plan before implementing:
|
|||||||
- What is the **blast radius** if the assumption underneath it is wrong?
|
- What is the **blast radius** if the assumption underneath it is wrong?
|
||||||
- What am I **inferring** that I could instead **detect at runtime** or **record explicitly**?
|
- What am I **inferring** that I could instead **detect at runtime** or **record explicitly**?
|
||||||
Highest-yield question here — see `references/evidence.md`'s failure-mode section.
|
Highest-yield question here — see `references/evidence.md`'s failure-mode section.
|
||||||
- For every branch that exists **only to survive something going wrong**: what input reaches it,
|
- For every branch that exists **only to survive something going wrong**: name the image or host
|
||||||
on which image? Go and look. A fallback for a configuration you cannot find an instance of is
|
where that input actually arrives, and go and look. Naming is the bar, not reproducing it here —
|
||||||
not robustness, it is a second code path nobody will ever exercise or notice rotting.
|
`references/simplify.md` works the `/dev/shm` guard and the `s6-dumpenv` fallback through that
|
||||||
|
distinction.
|
||||||
|
|
||||||
Full loop only, before writing code: get Codex's independent read on the plan. Spawn a subagent
|
Full loop only, before writing code: get Codex's independent read on the plan. Spawn a subagent
|
||||||
whose prompt includes the path `references/codex-review.md` and tells it to follow that file's
|
whose prompt includes the path `references/codex-review.md` and tells it to follow that file's
|
||||||
@@ -158,13 +159,19 @@ defends one you have now demonstrated, or one you have merely been told about? T
|
|||||||
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
kind of edit that leaves a stray `fi` behind.
|
||||||
|
|
||||||
## 7. Open the PR
|
## 7. Open the PR
|
||||||
|
|
||||||
CI gates on a PR: **`CHANGELOG.md` updated** (hard fail), the **HA add-on linter**
|
CI gates on a PR: **`CHANGELOG.md` updated** (hard fail), the **HA add-on linter**
|
||||||
(`frenck/action-addon-linter`, blocking — not the weekly Super-Linter, which is non-blocking), and
|
(`frenck/action-addon-linter`, blocking — not the weekly Super-Linter, which is non-blocking), and
|
||||||
the **add-on image build**. Bump `version` anyway (`X.Y.Z.N`, never `X.Y.Z-N`, see
|
the **add-on image build**. Bump `version` anyway (`X.Y.Z.N`, never `X.Y.Z-N`, see
|
||||||
`references/traps.md#versioning`) — Supervisor won't offer a rebuild without it. Update
|
`references/traps.md#versioning`) — Supervisor won't offer a rebuild without it. Update
|
||||||
`README.md` if you added options; match the CHANGELOG heading format `## X.Y (DD-MM-YYYY)`.
|
`README.md` if you added options; write the CHANGELOG heading as `## <version> (<date>)`,
|
||||||
|
matching the date format already in that file — almost always ISO `YYYY-MM-DD`, see
|
||||||
|
`references/traps.md#ci-and-review-bots`.
|
||||||
|
|
||||||
Write the body to a file, `gh pr create --body-file`: state what was measured, what changed,
|
Write the body to a file, `gh pr create --body-file`: state what was measured, what changed,
|
||||||
**what is not verified**, and how to roll back the riskiest hunk alone.
|
**what is not verified**, and how to roll back the riskiest hunk alone.
|
||||||
@@ -185,7 +192,12 @@ work" — either it was exercised, or say plainly it wasn't.
|
|||||||
|
|
||||||
Light path: verification is `validate.sh` plus CI; anything beyond that is Assumed. Full loop: CI
|
Light path: verification is `validate.sh` plus CI; anything beyond that is Assumed. Full loop: CI
|
||||||
passing proves the build works, not that the change does anything — re-run the measurement that
|
passing proves the build works, not that the change does anything — re-run the measurement that
|
||||||
motivated the work once the rebuilt add-on is running. Real "merged and inert" examples, and what
|
motivated the work once the rebuilt add-on is running. After merge, `git fetch origin master`
|
||||||
|
(the tracking ref is stale otherwise), then confirm the *changes* survived — `git diff
|
||||||
|
origin/master -- <the paths you touched>` comes back empty. Ancestry is not the check: a revert
|
||||||
|
leaves your commit in history and undoes its tree, so `--contains` reports success either way. The
|
||||||
|
builder's revert-on-failure job can revert a merge for reasons unrelated to your diff (see
|
||||||
|
`references/traps.md#ci-and-review-bots`). Real "merged and inert" examples, and what
|
||||||
to do when a fix can't be self-verified: `references/evidence.md`.
|
to do when a fix can't be self-verified: `references/evidence.md`.
|
||||||
|
|
||||||
## 10. Calibrate and report
|
## 10. Calibrate and report
|
||||||
@@ -204,5 +216,9 @@ Risk + rollback — the riskiest hunk and how to revert it alone
|
|||||||
Lead with anything that did not work — a merged PR that achieved nothing is the single most
|
Lead with anything that did not work — a merged PR that achieved nothing is the single most
|
||||||
important sentence in the report. Give confidence per claim, not one blanket number.
|
important sentence in the report. Give confidence per claim, not one blanket number.
|
||||||
|
|
||||||
|
**Feed the skill.** When a shipped fix needed a follow-up PR, or a reviewer caught something this
|
||||||
|
skill should have, add the distilled lesson to the matching `references/` file in that follow-up
|
||||||
|
PR — one entry, with the PR numbers. That loop is what keeps this file short and the traps real.
|
||||||
|
|
||||||
Scripts are meant to be **run, not read** — each is cited at its point of use above; read one
|
Scripts are meant to be **run, not read** — each is cited at its point of use above; read one
|
||||||
only if its output surprises you.
|
only if its output surprises you.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Simplify — case studies
|
# Simplify — case studies
|
||||||
|
|
||||||
Evidence for why the mechanism ladder in SKILL.md step 3 exists, and why levels 4-6 need a reason
|
Evidence for why the mechanism ladder in SKILL.md step 3 exists, and why levels 4-6 need a reason
|
||||||
that survives being said out loud. In all three cases the simpler option existed and was skipped.
|
that survives being said out loud. In each case the simpler option existed and was skipped.
|
||||||
Being able to build the complicated thing is not a reason to.
|
Being able to build the complicated thing is not a reason to.
|
||||||
|
|
||||||
- A rejected PR spent a **388-line TCP proxy plus a 142-line monkeypatch of a private upstream
|
- A rejected PR spent a **388-line TCP proxy plus a 142-line monkeypatch of a private upstream
|
||||||
@@ -13,6 +13,13 @@ Being able to build the complicated thing is not a reason to.
|
|||||||
- A resolution cap shipped as a **new init script writing an s6 envdir** — the wrong mechanism
|
- A resolution cap shipped as a **new init script writing an s6 envdir** — the wrong mechanism
|
||||||
entirely (ladder level 4). Renaming the option to the env var the service already reads
|
entirely (ladder level 4). Renaming the option to the env var the service already reads
|
||||||
(level 1) would have worked, and the new script did not.
|
(level 1) would have worked, and the new script did not.
|
||||||
|
- A calibre-web trusted-ips fix injected the add-on's **per-boot IP**, which forced a
|
||||||
|
rewrite-every-boot design that erased user entries; preserving them then needed merge logic
|
||||||
|
plus a state file recording what was injected (+34 lines, PR #3009 — closed unmerged). Asking
|
||||||
|
"is there a constant that makes the rewrite unnecessary?" gave the shipped fix: trust the
|
||||||
|
static supervisor range `172.30.32.0/23`, one idempotent statement, **net −6 lines**
|
||||||
|
(PR #3010). Complexity spent working around a changing value is a sign to hunt for the
|
||||||
|
constant instead.
|
||||||
- A `.templates/ha_entrypoint.sh` fix went to review at 25 lines of code and merged at 10. Two
|
- A `.templates/ha_entrypoint.sh` fix went to review at 25 lines of code and merged at 10. Two
|
||||||
sources of the excess, and neither was caught by the loop: a **pure-bash fallback** written at
|
sources of the excess, and neither was caught by the loop: a **pure-bash fallback** written at
|
||||||
implement time for images shipping `with-contenv` but not `s6-dumpenv` — reasoned from the two
|
implement time for images shipping `with-contenv` but not `s6-dumpenv` — reasoned from the two
|
||||||
@@ -35,10 +42,16 @@ Being able to build the complicated thing is not a reason to.
|
|||||||
- **Is the fix bigger than the thing it fixes?** That is a smell, not a rule — but it usually
|
- **Is the fix bigger than the thing it fixes?** That is a smell, not a rule — but it usually
|
||||||
means the problem was framed one level too deep.
|
means the problem was framed one level too deep.
|
||||||
- **For each defensive branch: what input reaches it, on which image or host?** Go and check,
|
- **For each defensive branch: what input reaches it, on which image or host?** Go and check,
|
||||||
the way you would check a measurement. If you cannot produce the case, delete the branch — the
|
the way you would check a measurement. The bar is being able to **name** the case, not to
|
||||||
situation then fails the way it already fails today, visibly, instead of through a second path
|
reproduce it here: Docker's 64 MB `/dev/shm` default is documented behaviour that HA does not
|
||||||
that is never exercised and silently rots as the base images move. Write down in the PR body
|
override, so the bullet above keeps that guard even though this host measured 7.7 GB. Nobody
|
||||||
what you cut and why, so the next person does not re-add it from the same reasoning.
|
could name a single image shipping `with-contenv` without `s6-dumpenv`, so that fallback went.
|
||||||
|
If you cannot name the case, delete the branch — the situation then fails the way it already
|
||||||
|
fails today, visibly, instead of through a second path that is never exercised and silently
|
||||||
|
rots as the base images move. Weigh the cost too: a one-flag guard against a crash you cannot
|
||||||
|
rule out is cheap, a second code path that degrades to the pre-fix behaviour anyway is not.
|
||||||
|
Write down in the PR body what you cut and why, so the next person does not re-add it from the
|
||||||
|
same reasoning.
|
||||||
- **How does this fail in three years**, when the base image, Electron, or upstream has moved?
|
- **How does this fail in three years**, when the base image, Electron, or upstream has moved?
|
||||||
Code that reads a documented knob keeps working. Code that reaches into private internals
|
Code that reads a documented knob keeps working. Code that reaches into private internals
|
||||||
does not.
|
does not.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ workflows and lint rules — that is not repeated here.
|
|||||||
- [Environment and workspace](#environment-and-workspace)
|
- [Environment and workspace](#environment-and-workspace)
|
||||||
- [Measurement](#measurement)
|
- [Measurement](#measurement)
|
||||||
- [Passing values into base-image services](#passing-values-into-base-image-services)
|
- [Passing values into base-image services](#passing-values-into-base-image-services)
|
||||||
|
- [Writing into an app's own config](#writing-into-an-apps-own-config)
|
||||||
- [Shell and bashio](#shell-and-bashio)
|
- [Shell and bashio](#shell-and-bashio)
|
||||||
- [Dockerfile and architecture](#dockerfile-and-architecture)
|
- [Dockerfile and architecture](#dockerfile-and-architecture)
|
||||||
- [Versioning](#versioning)
|
- [Versioning](#versioning)
|
||||||
@@ -116,6 +117,26 @@ place for filesystem and permission setup.
|
|||||||
the openbox autostart. (ANGLE's OpenGL backend, for instance, fails with "Could not open the
|
the openbox autostart. (ANGLE's OpenGL backend, for instance, fails with "Could not open the
|
||||||
default X display".)
|
default X display".)
|
||||||
|
|
||||||
|
## Writing into an app's own config
|
||||||
|
|
||||||
|
**A field your cont-init script writes may also be user-editable in the app's UI.** An
|
||||||
|
unconditional `UPDATE`/overwrite on every boot silently erases whatever the user added there,
|
||||||
|
and containers are recreated on restart so it re-erases forever (calibre-web
|
||||||
|
`config_reverse_proxy_trusted_ips`, #3004 — flagged by two review bots, fixed in #3010).
|
||||||
|
Prepend/merge with an idempotence guard instead of assigning.
|
||||||
|
|
||||||
|
**Prefer values that are constant across boots.** The add-on's own IP changes every restart,
|
||||||
|
so injecting it forces a rewrite-every-boot design plus stale-entry cleanup (a stale trusted IP
|
||||||
|
can be handed to a *different* add-on later). Trusting the whole supervisor range
|
||||||
|
`172.30.32.0/23` is constant, written once. For dual-stack listeners the IPv4 form never
|
||||||
|
matches IPv4-mapped addresses — also list the mapped form (`::ffff:172.30.32.0/119`).
|
||||||
|
|
||||||
|
Constant is not free when the value gates **authentication**: trusting the whole range means any
|
||||||
|
add-on on the supervisor network can send the auth header and impersonate a user. #3010 shipped
|
||||||
|
that as an explicit, stated trade-off with the maintainer's sign-off. State the blast radius in
|
||||||
|
the PR body and get the maintainer's call before widening trust — never present it as a neutral
|
||||||
|
simplification.
|
||||||
|
|
||||||
## Shell and bashio
|
## Shell and bashio
|
||||||
|
|
||||||
**`bashio::config` for lists**: `while read ... < <(bashio::config ...)` silently yields an empty
|
**`bashio::config` for lists**: `while read ... < <(bashio::config ...)` silently yields an empty
|
||||||
@@ -195,6 +216,17 @@ All three hard gates below are matrixed over `check-addon-changes.outputs.change
|
|||||||
- **Version bump** — no workflow checks it. It is repo convention, and required for Supervisor to
|
- **Version bump** — no workflow checks it. It is repo convention, and required for Supervisor to
|
||||||
offer the rebuild, but it will not fail CI.
|
offer the rebuild, but it will not fail CI.
|
||||||
|
|
||||||
|
**"Merged" is not "on master".** The push builder's revert-on-failure job reverts the merge
|
||||||
|
commit when its prebuild step fails — including failures unrelated to your diff. A seerr fix
|
||||||
|
merged at 05:15 and was reverted one minute later because `EndBug/add-and-commit`'s floating
|
||||||
|
`v11` tag had moved to a broken release (#2993, reapplied verbatim in #2997). After merge,
|
||||||
|
`git fetch origin master` first — the remote-tracking ref is stale otherwise and would "confirm"
|
||||||
|
against pre-merge state — then check that `git diff origin/master -- <the paths you touched>` is
|
||||||
|
empty before declaring done. Scope it to your paths: master moves under you, so whole-tree
|
||||||
|
equality fails on unrelated commits. Ancestry is not the check either — this repo squash-merges,
|
||||||
|
so a merged PR head is never an ancestor of `master` (verified on #3010, whose fix is live), and
|
||||||
|
a revert leaves the original commit an ancestor anyway.
|
||||||
|
|
||||||
**CI rewrites your shell scripts.** `lint.yml` runs `shfmt -w -i 4 -ci -bn -sr` over every `*.sh`
|
**CI rewrites your shell scripts.** `lint.yml` runs `shfmt -w -i 4 -ci -bn -sr` over every `*.sh`
|
||||||
and `run`, plus a `chmod +x` pass, on schedule. Repo-wide reformatting commits land on master
|
and `run`, plus a `chmod +x` pass, on schedule. Repo-wide reformatting commits land on master
|
||||||
without your involvement — another reason a shared checkout goes stale mid-task.
|
without your involvement — another reason a shared checkout goes stale mid-task.
|
||||||
@@ -209,6 +241,18 @@ account. Note it and move on rather than guessing.
|
|||||||
**Resolving a review thread requires GraphQL** (`resolveReviewThread`); the REST API cannot do it.
|
**Resolving a review thread requires GraphQL** (`resolveReviewThread`); the REST API cannot do it.
|
||||||
`scripts/pr_review.sh` wraps fetch / reply / resolve.
|
`scripts/pr_review.sh` wraps fetch / reply / resolve.
|
||||||
|
|
||||||
|
**Most CHANGELOG heading dates are ISO, whatever the bots' defaults say.** Match the format
|
||||||
|
already in the add-on's file — a `DD-MM-YYYY` file stays `DD-MM-YYYY`. Where you have no
|
||||||
|
precedent, ISO is the house style: as of 2026-08-25, `## <version> (YYYY-MM-DD)` accounts for
|
||||||
|
7705 dated headings against 363 in `DD-MM-YYYY`, and the newest entry is ISO in 125 of 135
|
||||||
|
add-ons. Copilot flags an ISO file that gets a `DD-MM-YYYY` entry (#3019). `DD-MM-YYYY` is not
|
||||||
|
invented — it is what `onpush_builder.yaml` writes with `date '+%d-%m-%Y'` when it has to insert
|
||||||
|
a heading you forgot, and what the addons_updater bot writes when its `date_iso8601` option is
|
||||||
|
off (`99-run.sh`; it is on in production here) — but neither is a reason to write it yourself.
|
||||||
|
The builder's duplicate check is `grep -q "^## ${version} ("` — an unescaped BRE, so the dots in
|
||||||
|
a version match any character, and it does not look at the date at all. Either way an ISO heading
|
||||||
|
you wrote yourself still suppresses the bot's insertion.
|
||||||
|
|
||||||
**The repo's `.markdownlint.yaml` does not disable MD022/MD032**, so a CHANGELOG will show
|
**The repo's `.markdownlint.yaml` does not disable MD022/MD032**, so a CHANGELOG will show
|
||||||
dozens of pre-existing heading/list findings. They are noise because lint is `continue-on-error`,
|
dozens of pre-existing heading/list findings. They are noise because lint is `continue-on-error`,
|
||||||
not because the config exempts them — don't cite the config as a reason to ignore a finding.
|
not because the config exempts them — don't cite the config as a reason to ignore a finding.
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ resolve)
|
|||||||
;;
|
;;
|
||||||
watch)
|
watch)
|
||||||
MINS="${3:-180}" # the addon build alone has taken ~3h; 20 was far too short
|
MINS="${3:-180}" # the addon build alone has taken ~3h; 20 was far too short
|
||||||
|
wfail=2 # not 0: running out of minutes with checks still pending is not a pass
|
||||||
for i in $(seq 1 "$MINS"); do
|
for i in $(seq 1 "$MINS"); do
|
||||||
c=$(gh pr checks "$PR" 2> /dev/null | awk '{print $1"="$2}' | tr '\n' ' ')
|
c=$(gh pr checks "$PR" 2> /dev/null | awk '{print $1"="$2}' | tr '\n' ' ')
|
||||||
if [ -z "$c" ]; then
|
if [ -z "$c" ]; then
|
||||||
@@ -102,11 +103,14 @@ watch)
|
|||||||
case "$c" in
|
case "$c" in
|
||||||
*pending*) sleep 60 ;;
|
*pending*) sleep 60 ;;
|
||||||
*fail* | *error* | *cancel*) echo "settled — with FAILURES (see above)"; wfail=1; break ;;
|
*fail* | *error* | *cancel*) echo "settled — with FAILURES (see above)"; wfail=1; break ;;
|
||||||
*) echo "settled — all passing"; break ;;
|
*) echo "settled — all passing"; wfail=0; break ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
[ "$wfail" -eq 2 ] && echo "gave up after ${MINS}m, checks still unsettled — NOT a pass"
|
||||||
|
# A PR touching no */config.* skips the CHANGELOG, linter and build jobs outright (#3018).
|
||||||
|
case "${c:-}" in *skipping*) echo " ...of which some were SKIPPED — a skipped job tested nothing" ;; esac
|
||||||
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:-0}"
|
exit "$wfail"
|
||||||
;;
|
;;
|
||||||
*) echo "unknown: $CMD"; exit 1 ;;
|
*) echo "unknown: $CMD"; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ if [ -n "${BUILD_VERSION:-}" ]; then
|
|||||||
echo " $SLUG/config.yaml version = $here"
|
echo " $SLUG/config.yaml version = $here"
|
||||||
echo " running image BUILD_VERSION = $BUILD_VERSION"
|
echo " running image BUILD_VERSION = $BUILD_VERSION"
|
||||||
if [ "$here" = "$BUILD_VERSION" ]; then
|
if [ "$here" = "$BUILD_VERSION" ]; then
|
||||||
echo " MATCH — this checkout corresponds to the running image."
|
# version is bumped once per PR, so a later commit or a dirty tree matches here.
|
||||||
|
echo " VERSION MATCH — source revision itself is not verified."
|
||||||
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
|
||||||
|
|||||||
@@ -4,10 +4,13 @@
|
|||||||
# rather than implying the build was checked.
|
# rather than implying the build was checked.
|
||||||
#
|
#
|
||||||
# --vs-master re-lints each changed file at origin/master and prints only findings your diff
|
# --vs-master re-lints each changed file at origin/master and prints only findings your diff
|
||||||
# ADDED. Without it you will chase warnings that were already in the file.
|
# ADDED, and fails if there are any. Without it you will chase warnings that were already there.
|
||||||
#
|
#
|
||||||
# Usage: validate.sh [addon-dir] [--vs-master]
|
# Usage: validate.sh [addon-dir] [--vs-master]
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
# git diff prints repo-root-relative paths and $ADDON is a top-level directory name: neither
|
||||||
|
# resolves from a subdirectory, where the -f guard below would skip every file and report clean.
|
||||||
|
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; }
|
||||||
@@ -31,17 +34,21 @@ echo "== validating $ADDON =="
|
|||||||
|
|
||||||
fail=0
|
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
|
||||||
|
# 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* ]]; }
|
||||||
|
|
||||||
# Shell: bash -n then shellcheck -x (follows sourced files, as CI does).
|
# Shell: bash -n then shellcheck -x (follows sourced files, as CI does). One list for both.
|
||||||
while IFS= read -r f; do
|
files=()
|
||||||
[ -f "$f" ] || continue
|
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)
|
||||||
|
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 < <(find "$ADDON" -type f \( -name '*.sh' -o -name 'run' -o -name 'finish' -o -name 'autostart' \) 2> /dev/null)
|
done
|
||||||
[ "$fail" -eq 0 ] && note "bash -n" "ok"
|
[ "$fail" -eq 0 ] && note "bash -n" "${#files[@]} file(s) checked"
|
||||||
|
|
||||||
if command -v shellcheck > /dev/null 2>&1; then
|
if [ "${#files[@]}" -gt 0 ] && command -v shellcheck > /dev/null 2>&1; then
|
||||||
sc=$(find "$ADDON" -type f \( -name '*.sh' -o -name 'autostart' -o -name 'run' -o -name 'finish' \) -print0 2> /dev/null |
|
sc=$(shellcheck -x -f gcc "${files[@]}" 2>&1)
|
||||||
xargs -0 -r shellcheck -x -f gcc 2>&1)
|
|
||||||
if [ -n "$sc" ]; then
|
if [ -n "$sc" ]; then
|
||||||
note "shellcheck" "$(printf '%s\n' "$sc" | grep -c .) finding(s)"
|
note "shellcheck" "$(printf '%s\n' "$sc" | grep -c .) finding(s)"
|
||||||
printf '%s\n' "$sc" | sed 's/^/ /' | head -20
|
printf '%s\n' "$sc" | sed 's/^/ /' | head -20
|
||||||
@@ -50,7 +57,10 @@ fi
|
|||||||
|
|
||||||
command -v hadolint > /dev/null 2>&1 && [ -f "$ADDON/Dockerfile" ] && {
|
command -v hadolint > /dev/null 2>&1 && [ -f "$ADDON/Dockerfile" ] && {
|
||||||
hl=$(hadolint "$ADDON/Dockerfile" 2>&1)
|
hl=$(hadolint "$ADDON/Dockerfile" 2>&1)
|
||||||
[ -n "$hl" ] && { note "hadolint" "$(printf '%s\n' "$hl" | grep -c .) finding(s)"; printf '%s\n' "$hl" | sed 's/^/ /' | head -10; } || note "hadolint" "clean"
|
if [ -n "$hl" ]; then
|
||||||
|
note "hadolint" "$(printf '%s\n' "$hl" | grep -c .) finding(s)"
|
||||||
|
printf '%s\n' "$hl" | sed 's/^/ /' | head -10
|
||||||
|
else note "hadolint" "clean"; fi
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ -f "$ADDON/config.yaml" ]; then
|
if [ -f "$ADDON/config.yaml" ]; then
|
||||||
@@ -79,11 +89,14 @@ $VS_MASTER && command -v npx > /dev/null 2>&1 && [ -f "$ADDON/CHANGELOG.md" ] &&
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== CI requirements =="
|
echo "== CI requirements =="
|
||||||
if git diff --name-only origin/master...HEAD 2> /dev/null | grep -q "$ADDON/CHANGELOG.md"; then
|
# -Fxq, not -q: unanchored, seerr's is matched by zzz_archived_overseerr's, and . is a wildcard.
|
||||||
|
# Stricter than the gate itself, whose quoted =~ accepts that same collision
|
||||||
|
# (onpr_check-pr.yaml:75), so this can fail where CI passes — the wrong add-on's is still wrong.
|
||||||
|
if git diff --name-only origin/master...HEAD 2> /dev/null | grep -Fxq "$ADDON/CHANGELOG.md"; then
|
||||||
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 — this is the one CI hard-gate"; 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"
|
||||||
@@ -98,8 +111,15 @@ 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
|
||||||
git diff --name-only origin/master...HEAD -- "$ADDON" 2> /dev/null | while IFS= read -r f; do
|
added=0
|
||||||
git show "origin/master:$f" > "$tmp/base" 2> /dev/null || continue
|
# 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.
|
||||||
|
while IFS= read -r f; do
|
||||||
|
# Deleted: linting the path that is gone invents a finding. Added: no base, and an
|
||||||
|
# empty one says the right thing — every finding in it is one this diff added.
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
is_execline "$f" && continue
|
||||||
|
git show "origin/master:$f" > "$tmp/base" 2> /dev/null || : > "$tmp/base"
|
||||||
# A missing linter must be a visible skip, not a silent "no new findings":
|
# A missing linter must be a visible skip, not a silent "no new findings":
|
||||||
# its "command not found" error is identical for base and head, so comm would
|
# its "command not found" error is identical for base and head, so comm would
|
||||||
# cancel it out and report a false clean.
|
# cancel it out and report a false clean.
|
||||||
@@ -115,12 +135,16 @@ if $VS_MASTER; then
|
|||||||
cmd() { hadolint "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*//'; } ;;
|
cmd() { hadolint "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*//'; } ;;
|
||||||
*) continue ;;
|
*) continue ;;
|
||||||
esac
|
esac
|
||||||
cp "$tmp/base" "$tmp/base_f"; b=$(cmd "$tmp/base_f" | sort)
|
b=$(cmd "$tmp/base" | sort)
|
||||||
a=$(cmd "$f" | sort)
|
a=$(cmd "$f" | sort)
|
||||||
new=$(comm -13 <(printf '%s\n' "$b") <(printf '%s\n' "$a") | grep -c .)
|
new=$(comm -13 <(printf '%s\n' "$b") <(printf '%s\n' "$a"))
|
||||||
[ "$new" -gt 0 ] && { echo " $f: $new NEW finding(s)"; comm -13 <(printf '%s\n' "$b") <(printf '%s\n' "$a") | sed 's/^/ /' | head -5; }
|
[ -n "$new" ] && {
|
||||||
done
|
echo " $f: $(printf '%s\n' "$new" | grep -c .) NEW finding(s)"
|
||||||
echo " (nothing listed above = your diff introduced no new lint findings)"
|
printf '%s\n' "$new" | sed 's/^/ /' | head -5
|
||||||
|
added=1; fail=1
|
||||||
|
}
|
||||||
|
done < <(git diff --name-only origin/master...HEAD -- "$ADDON" 2> /dev/null)
|
||||||
|
[ "$added" -eq 0 ] && echo " (none — this diff introduced no new lint findings)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|||||||
2
.github/workflows/daily_README.yaml
vendored
2
.github/workflows/daily_README.yaml
vendored
@@ -228,7 +228,7 @@ jobs:
|
|||||||
echo "... done"
|
echo "... done"
|
||||||
|
|
||||||
- name: Commit if needed
|
- name: Commit if needed
|
||||||
uses: EndBug/add-and-commit@v11.0.0
|
uses: EndBug/add-and-commit@v11.1.1
|
||||||
with:
|
with:
|
||||||
message: "GitHub bot : README updated"
|
message: "GitHub bot : README updated"
|
||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
|
|||||||
2
.github/workflows/daily_ai_fix.yaml
vendored
2
.github/workflows/daily_ai_fix.yaml
vendored
@@ -125,7 +125,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Analyse and fix
|
- name: Analyse and fix
|
||||||
if: steps.batch.outputs.count != '0'
|
if: steps.batch.outputs.count != '0'
|
||||||
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
|
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Skip the OIDC -> Claude App token exchange. The scheduled path
|
# Skip the OIDC -> Claude App token exchange. The scheduled path
|
||||||
|
|||||||
2
.github/workflows/helper_stats_graphs.yaml
vendored
2
.github/workflows/helper_stats_graphs.yaml
vendored
@@ -237,7 +237,7 @@ jobs:
|
|||||||
# Get stars evolution
|
# Get stars evolution
|
||||||
wget -S -O .github/starsevol.svg "https://api.star-history.com/svg?repos=alexbelgium/hassio-addons&type=Date" || true
|
wget -S -O .github/starsevol.svg "https://api.star-history.com/svg?repos=alexbelgium/hassio-addons&type=Date" || true
|
||||||
- name: Commit if needed
|
- name: Commit if needed
|
||||||
uses: EndBug/add-and-commit@v11.0.0
|
uses: EndBug/add-and-commit@v11.1.1
|
||||||
with:
|
with:
|
||||||
message: "GitHub bot : graphs updated"
|
message: "GitHub bot : graphs updated"
|
||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
|
|||||||
2
.github/workflows/on_claude_mention.yml
vendored
2
.github/workflows/on_claude_mention.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
|||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Run Claude Code
|
- name: Run Claude Code
|
||||||
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
|
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.
|
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.
|
||||||
|
|||||||
2
.github/workflows/on_issue_approved.yaml
vendored
2
.github/workflows/on_issue_approved.yaml
vendored
@@ -135,7 +135,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Execute the plan
|
- name: Execute the plan
|
||||||
if: steps.bundle.outputs.has_plan == 'true'
|
if: steps.bundle.outputs.has_plan == 'true'
|
||||||
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
|
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||||
|
|||||||
2
.github/workflows/on_issues.yml
vendored
2
.github/workflows/on_issues.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
|||||||
# Remove issues list
|
# Remove issues list
|
||||||
rm issueslist
|
rm issueslist
|
||||||
- name: Commit if needed
|
- name: Commit if needed
|
||||||
uses: EndBug/add-and-commit@v11.0.0
|
uses: EndBug/add-and-commit@v11.1.1
|
||||||
with:
|
with:
|
||||||
message: "Github bot : issues linked to readme"
|
message: "Github bot : issues linked to readme"
|
||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
|
|||||||
2
.github/workflows/on_issues_ai_triage.yaml
vendored
2
.github/workflows/on_issues_ai_triage.yaml
vendored
@@ -166,7 +166,7 @@ jobs:
|
|||||||
id: classify
|
id: classify
|
||||||
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
|
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
|
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Without this the action falls back to the OIDC -> Claude App token
|
# Without this the action falls back to the OIDC -> Claude App token
|
||||||
|
|||||||
2
.github/workflows/on_pr_coderabbit.yml
vendored
2
.github/workflows/on_pr_coderabbit.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Address CodeRabbit comments
|
- name: Address CodeRabbit comments
|
||||||
if: steps.claim.outputs.go == 'true'
|
if: steps.claim.outputs.go == 'true'
|
||||||
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
|
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||||
|
|||||||
4
.github/workflows/onpush_builder.yaml
vendored
4
.github/workflows/onpush_builder.yaml
vendored
@@ -95,7 +95,7 @@ jobs:
|
|||||||
- name: Commit sanitize changes
|
- name: Commit sanitize changes
|
||||||
id: sanitize_commit
|
id: sanitize_commit
|
||||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||||
uses: EndBug/add-and-commit@v11.0.0
|
uses: EndBug/add-and-commit@v11.1.1
|
||||||
with:
|
with:
|
||||||
commit: -u
|
commit: -u
|
||||||
message: "GitHub bot: sanitize (spaces + LF endings) & chmod [nobuild]"
|
message: "GitHub bot: sanitize (spaces + LF endings) & chmod [nobuild]"
|
||||||
@@ -410,7 +410,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Commit changelog changes
|
- name: Commit changelog changes
|
||||||
uses: EndBug/add-and-commit@v11.0.0
|
uses: EndBug/add-and-commit@v11.1.1
|
||||||
with:
|
with:
|
||||||
commit: -u
|
commit: -u
|
||||||
message: "GitHub bot: changelog [nobuild]"
|
message: "GitHub bot: changelog [nobuild]"
|
||||||
|
|||||||
4
.github/workflows/weekly_crlftolf.yaml
vendored
4
.github/workflows/weekly_crlftolf.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
uses: erclu/check-crlf@v1
|
uses: erclu/check-crlf@v1
|
||||||
|
|
||||||
- name: Commit if needed
|
- name: Commit if needed
|
||||||
uses: EndBug/add-and-commit@v11.0.0
|
uses: EndBug/add-and-commit@v11.1.1
|
||||||
with:
|
with:
|
||||||
message: "Github bot : CRLF corrected"
|
message: "Github bot : CRLF corrected"
|
||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
dos2unix -k "$f"
|
dos2unix -k "$f"
|
||||||
done
|
done
|
||||||
- name: Commit if needed
|
- name: Commit if needed
|
||||||
uses: EndBug/add-and-commit@v11.0.0
|
uses: EndBug/add-and-commit@v11.1.1
|
||||||
with:
|
with:
|
||||||
message: "Github bot : CRLF corrected"
|
message: "Github bot : CRLF corrected"
|
||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
|
|||||||
2
.github/workflows/weekly_reduceimagesize.yml
vendored
2
.github/workflows/weekly_reduceimagesize.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Commit if needed
|
- name: Commit if needed
|
||||||
if: steps.calibre.outputs.markdown != ''
|
if: steps.calibre.outputs.markdown != ''
|
||||||
uses: EndBug/add-and-commit@v11.0.0
|
uses: EndBug/add-and-commit@v11.1.1
|
||||||
with:
|
with:
|
||||||
message: "Github bot : image compressed"
|
message: "Github bot : image compressed"
|
||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
|
|||||||
2
.github/workflows/weekly_stats.yaml
vendored
2
.github/workflows/weekly_stats.yaml
vendored
@@ -109,7 +109,7 @@ jobs:
|
|||||||
#TOTAL3="$(awk '{SUM+=$2}END{print SUM}' Stats)"
|
#TOTAL3="$(awk '{SUM+=$2}END{print SUM}' Stats)"
|
||||||
|
|
||||||
- name: Commit if needed
|
- name: Commit if needed
|
||||||
uses: EndBug/add-and-commit@v11.0.0
|
uses: EndBug/add-and-commit@v11.1.1
|
||||||
with:
|
with:
|
||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
message : "Github bot : stats updated"
|
message : "Github bot : stats updated"
|
||||||
|
|||||||
@@ -168,6 +168,59 @@ if [ -z "$shebang" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
#####################################
|
||||||
|
# Seed the s6 container environment #
|
||||||
|
#####################################
|
||||||
|
|
||||||
|
# s6-overlay's stage 1 dumps the container's environment into /run/s6/container_environment, and
|
||||||
|
# `with-contenv` reads it back (emptyenv -p; s6-envdir). Add-ons that override the base image's
|
||||||
|
# ENTRYPOINT ["/init"] with ENTRYPOINT ["/usr/bin/env"] plus CMD ["/ha_entrypoint.sh"] never run
|
||||||
|
# stage 1, so nothing creates that directory and every #!/usr/bin/with-contenv script outside the
|
||||||
|
# three globs whose shebang is rewritten below either exits non-zero before its first line
|
||||||
|
# (directory missing: s6-envdir errors) or runs against whatever a cont-init script happened to
|
||||||
|
# leave there -- measured at 16 variables instead of 110, SUPERVISOR_TOKEN among the missing.
|
||||||
|
# Neither says anything, so all that surfaces is what the caller makes of it: a HEALTHCHECK
|
||||||
|
# reporting "unhealthy", a cron job doing nothing. So dump the environment here instead.
|
||||||
|
#
|
||||||
|
# Deliberately after the shebang probe above, not next to the other PID 1 setup: the probe's first
|
||||||
|
# candidate is "/command/with-contenv bashio" and it fails today in exactly these add-ons, so the
|
||||||
|
# probe falls through to "/usr/bin/env bashio". Seeding earlier would make that candidate start
|
||||||
|
# succeeding and flip the shebang of every cont-init and service script here, so scripts launched
|
||||||
|
# directly would lose what an earlier sourced script exported -- a far larger change than this.
|
||||||
|
#
|
||||||
|
# It does switch on two dormant writes: 00-global_var.sh and 01-config_yaml.sh push their values
|
||||||
|
# into the envdir, but only `if [ -d ]`. That is what those lines are for, and it means out-of-glob
|
||||||
|
# scripts now see the user's configured options too.
|
||||||
|
|
||||||
|
S6_CONTAINER_ENV="/run/s6/container_environment"
|
||||||
|
|
||||||
|
# Only when this script is PID 1 -- under /init it is the stage-2 hook and stage 1 has already
|
||||||
|
# written the directory -- and only where with-contenv exists to care.
|
||||||
|
if $PID1 && { [ -x /command/with-contenv ] || [ -x /usr/bin/with-contenv ]; }; then
|
||||||
|
# Filled in a sibling and renamed into place, never written to live. A half-populated envdir is
|
||||||
|
# worse than an absent one: s6-envdir accepts it, so a with-contenv script starts and runs
|
||||||
|
# against an environment quietly missing SUPERVISOR_TOKEN, where an absent one stops it at its
|
||||||
|
# shebang. rename(2) means a concurrent reader -- a HEALTHCHECK can run alongside PID 1 -- sees
|
||||||
|
# the directory either absent or complete, never mid-dump.
|
||||||
|
#
|
||||||
|
# Cleared rather than written over: /run is not a tmpfs here, so an image layer can persist
|
||||||
|
# entries, and writing name by name would merge into them and leave variables PID 1 does not
|
||||||
|
# have, a stale SUPERVISOR_TOKEN among them. A failed rm has to abort the chain, because mkdir -p
|
||||||
|
# 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
|
||||||
|
# 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" &&
|
||||||
|
s6-dumpenv -- "$S6_CONTAINER_ENV.tmp" && mv "$S6_CONTAINER_ENV.tmp" "$S6_CONTAINER_ENV"; then
|
||||||
|
echo "Populated $S6_CONTAINER_ENV for with-contenv"
|
||||||
|
else
|
||||||
|
# Leaves the directory absent, which is exactly how this fails today -- so the failure mode is
|
||||||
|
# unchanged, not newly degraded. Never fatal, a read-only /run must still let the add-on boot,
|
||||||
|
# but never silent either, since the shebang failure it leaves behind says nothing on its own.
|
||||||
|
rm -rf "$S6_CONTAINER_ENV" "$S6_CONTAINER_ENV.tmp" 2>/dev/null || true
|
||||||
|
echo -e "\e[38;5;214m$(date) WARNING: could not populate $S6_CONTAINER_ENV; scripts with a with-contenv shebang will fail at their shebang, as they did before this was attempted\e[0m"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
####################################
|
####################################
|
||||||
# Bashio library for source fallback
|
# Bashio library for source fallback
|
||||||
####################################
|
####################################
|
||||||
|
|||||||
@@ -427,7 +427,7 @@ If you want to do add the repository manually, please follow the procedure highl
|
|||||||
![aarch64][aarch64-badge]
|
![aarch64][aarch64-badge]
|
||||||
![amd64][amd64-badge]
|
![amd64][amd64-badge]
|
||||||
|
|
||||||
✓ [Free Games Claimer](free_games_claimer/) : Automatically claims free games from Epic Games Store, Amazon Prime Gaming, GOG, Steam, and optional GamerPower-supported stores
|
✓ [Free Games Claimer](free_games_claimer/) : Claims free games from Epic, Prime, GOG, Steam, Ubisoft and more
|
||||||
|
|
||||||
  
|
  
|
||||||

|

|
||||||
|
|||||||
@@ -1,3 +1,28 @@
|
|||||||
|
## 2.1.0 (2026-08-24)
|
||||||
|
|
||||||
|
- Updated the pinned upstream from Free Games Claimer Remaster 1.1 to 1.6,
|
||||||
|
which adds the Ubisoft giveaway, Fab, AliExpress and Epic mobile stores,
|
||||||
|
fixed daily scheduler times, the `VNC_URL` notification link override, and a
|
||||||
|
fix for the `--accept-lang` flag that made every store's browser detectable
|
||||||
|
as automated.
|
||||||
|
- Mirrored upstream's Chromium hardening: `xdg-open` is neutralised and an
|
||||||
|
`AutoLaunchProtocolsFromOrigins` policy is installed, so app-scheme links
|
||||||
|
cannot open a blocking dialog in the VNC session.
|
||||||
|
- Disabled upstream's release-update notification by default. It tells the user
|
||||||
|
to run `docker compose pull`, while the add-on is updated through the Home
|
||||||
|
Assistant add-on store. Set `NOTIFY_UPDATES=true` in `config.env` to receive
|
||||||
|
it anyway.
|
||||||
|
- Reworded the add-on description so the store list reads as partial rather
|
||||||
|
than exhaustive; the full list is in the README.
|
||||||
|
- The default store selection is unchanged: existing installations keep
|
||||||
|
claiming from Epic, Prime Gaming and GOG until `STORES` is edited.
|
||||||
|
- Replaced the pinned upstream commit with `ARG BUILD_UPSTREAM`, which names an
|
||||||
|
upstream release tag, and re-enabled the repository updater for this add-on.
|
||||||
|
Upstream releases are now picked up automatically instead of requiring a
|
||||||
|
manual commit pin. Upstream's development tags are excluded: `lastversion`
|
||||||
|
reports the `v1.7d` development tag as release `1.7`, for which no source
|
||||||
|
archive exists, so an unfiltered update would have broken the build.
|
||||||
|
|
||||||
## 2.0.1 (2026-07-17)
|
## 2.0.1 (2026-07-17)
|
||||||
|
|
||||||
- Aligned the pull-request build context with the production builder by copying
|
- Aligned the pull-request build context with the production builder by copying
|
||||||
|
|||||||
@@ -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 UPSTREAM_REF="cca4992354215cef8807b748575af797606627f4"
|
ARG BUILD_UPSTREAM="1.6"
|
||||||
|
|
||||||
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" \
|
||||||
@@ -27,10 +27,7 @@ ENV S6_CMD_WAIT_FOR_SERVICES="1" \
|
|||||||
WIDTH="1280" \
|
WIDTH="1280" \
|
||||||
HEIGHT="720" \
|
HEIGHT="720" \
|
||||||
DEPTH="24" \
|
DEPTH="24" \
|
||||||
SHOW="1" \
|
SHOW="1"
|
||||||
COMMIT="${UPSTREAM_REF}" \
|
|
||||||
BRANCH="main" \
|
|
||||||
NOW="${BUILD_DATE}"
|
|
||||||
|
|
||||||
# Install the dependencies used by Free Games Claimer Remaster. The upstream
|
# Install the dependencies used by Free Games Claimer Remaster. The upstream
|
||||||
# Dockerfile supports both amd64 (Google Chrome) and arm64 (Chromium), so the
|
# Dockerfile supports both amd64 (Google Chrome) and arm64 (Chromium), so the
|
||||||
@@ -87,6 +84,11 @@ RUN apt-get update \
|
|||||||
else \
|
else \
|
||||||
apt-get install -y --no-install-recommends chromium; \
|
apt-get install -y --no-install-recommends chromium; \
|
||||||
fi \
|
fi \
|
||||||
|
&& printf '#!/bin/sh\nexit 0\n' > /usr/bin/xdg-open \
|
||||||
|
&& chmod +x /usr/bin/xdg-open \
|
||||||
|
&& mkdir -p /etc/opt/chrome/policies/managed /etc/chromium/policies/managed \
|
||||||
|
&& printf '%s\n' '{"AutoLaunchProtocolsFromOrigins":[{"protocol":"aliexpress","allowed_origins":["*"]},{"protocol":"aliexpresshd","allowed_origins":["*"]},{"protocol":"aecmd","allowed_origins":["*"]},{"protocol":"alibaba","allowed_origins":["*"]},{"protocol":"alipay","allowed_origins":["*"]},{"protocol":"alipays","allowed_origins":["*"]},{"protocol":"tmall","allowed_origins":["*"]},{"protocol":"taobao","allowed_origins":["*"]},{"protocol":"market","allowed_origins":["*"]},{"protocol":"intent","allowed_origins":["*"]}]}' \
|
||||||
|
| tee /etc/opt/chrome/policies/managed/fgc-autolaunch.json /etc/chromium/policies/managed/fgc-autolaunch.json > /dev/null \
|
||||||
&& ln -sf /usr/share/novnc/vnc_auto.html /usr/share/novnc/index.html \
|
&& ln -sf /usr/share/novnc/vnc_auto.html /usr/share/novnc/index.html \
|
||||||
&& ln -sf /usr/bin/python3 /usr/bin/python \
|
&& ln -sf /usr/bin/python3 /usr/bin/python \
|
||||||
&& apt-get purge -y gnupg \
|
&& apt-get purge -y gnupg \
|
||||||
@@ -94,13 +96,20 @@ RUN apt-get update \
|
|||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/* /var/cache/* /var/tmp/* /tmp/* /usr/share/doc/*
|
&& rm -rf /var/lib/apt/lists/* /var/cache/* /var/tmp/* /tmp/* /usr/share/doc/*
|
||||||
|
|
||||||
# Install a deterministic snapshot of the replacement upstream. Updating the
|
# Install the upstream release named by BUILD_UPSTREAM. The repository updater
|
||||||
# upstream reference is intentionally explicit so image contents cannot change
|
# bumps that value together with the add-on version. A release tag is a mutable
|
||||||
# without an add-on version bump.
|
# reference: rebuilding the same BUILD_UPSTREAM installs whatever the tag points
|
||||||
|
# at today, which is the accepted cost of tracking upstream automatically.
|
||||||
|
# Upstream tags its releases "v1.6", but the tag name is downloaded without the
|
||||||
|
# prefix as well so that an unattended update does not fail the build if
|
||||||
|
# upstream ever drops it.
|
||||||
WORKDIR /fgc
|
WORKDIR /fgc
|
||||||
RUN curl --proto "=https" --tlsv1.2 -fsSL \
|
RUN (curl --proto "=https" --tlsv1.2 -fsSL \
|
||||||
"https://github.com/${UPSTREAM_REPOSITORY}/archive/${UPSTREAM_REF}.tar.gz" \
|
"https://github.com/${UPSTREAM_REPOSITORY}/archive/v${BUILD_UPSTREAM}.tar.gz" \
|
||||||
-o /tmp/free-games-claimer-remaster.tar.gz \
|
-o /tmp/free-games-claimer-remaster.tar.gz \
|
||||||
|
|| curl --proto "=https" --tlsv1.2 -fsSL \
|
||||||
|
"https://github.com/${UPSTREAM_REPOSITORY}/archive/${BUILD_UPSTREAM}.tar.gz" \
|
||||||
|
-o /tmp/free-games-claimer-remaster.tar.gz) \
|
||||||
&& tar -xzf /tmp/free-games-claimer-remaster.tar.gz --strip-components=1 -C /fgc \
|
&& tar -xzf /tmp/free-games-claimer-remaster.tar.gz --strip-components=1 -C /fgc \
|
||||||
&& python3 -m pip install --no-cache-dir --break-system-packages -r requirements.txt \
|
&& python3 -m pip install --no-cache-dir --break-system-packages -r requirements.txt \
|
||||||
&& dos2unix ./*.sh \
|
&& dos2unix ./*.sh \
|
||||||
@@ -155,4 +164,4 @@ LABEL \
|
|||||||
org.opencontainers.image.created="${BUILD_DATE}" \
|
org.opencontainers.image.created="${BUILD_DATE}" \
|
||||||
org.opencontainers.image.revision="${BUILD_REF}" \
|
org.opencontainers.image.revision="${BUILD_REF}" \
|
||||||
org.opencontainers.image.version="${BUILD_VERSION}" \
|
org.opencontainers.image.version="${BUILD_VERSION}" \
|
||||||
io.hass.upstream="https://github.com/${UPSTREAM_REPOSITORY}/tree/${UPSTREAM_REF}"
|
io.hass.upstream="https://github.com/${UPSTREAM_REPOSITORY}/releases"
|
||||||
|
|||||||
@@ -24,14 +24,19 @@ This add-on is based on
|
|||||||
[Free Games Claimer Remaster](https://github.com/P-Adamiec/Free-Games-Claimer-Remaster).
|
[Free Games Claimer Remaster](https://github.com/P-Adamiec/Free-Games-Claimer-Remaster).
|
||||||
It can claim free games from:
|
It can claim free games from:
|
||||||
|
|
||||||
- Epic Games Store
|
- Epic Games Store, including its weekly free mobile game
|
||||||
|
- Fab, Epic's asset marketplace (`fab`)
|
||||||
- Amazon Prime Gaming
|
- Amazon Prime Gaming
|
||||||
- GOG
|
- GOG
|
||||||
- Steam
|
- Steam
|
||||||
|
- Ubisoft giveaways (`ubisoft`)
|
||||||
|
- AliExpress daily coin check-in (`aliexpress`)
|
||||||
- GamerPower-supported stores, when explicitly enabled
|
- GamerPower-supported stores, when explicitly enabled
|
||||||
|
|
||||||
For compatibility with previous add-on releases, the default store selection
|
For compatibility with previous add-on releases, the default store selection
|
||||||
remains Epic Games, Prime Gaming, and GOG.
|
remains Epic Games, Prime Gaming, and GOG. The other stores are enabled by
|
||||||
|
adding them to `STORES`, for example `epic,prime,gog,fab,ubisoft`, and each
|
||||||
|
needs its own credentials in `config.env`.
|
||||||
|
|
||||||
## Web interface
|
## Web interface
|
||||||
|
|
||||||
@@ -98,6 +103,11 @@ NOTIFY=tgram://bot-token/chat-id
|
|||||||
# DISCORD_WEBHOOK=https://discord.com/api/webhooks/...
|
# DISCORD_WEBHOOK=https://discord.com/api/webhooks/...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Upstream's release-update notification (`NOTIFY_UPDATES`) is disabled by the
|
||||||
|
add-on, because it advises running `docker compose pull` while the add-on is
|
||||||
|
actually updated through the Home Assistant add-on store. Setting
|
||||||
|
`NOTIFY_UPDATES=true` in `config.env` re-enables it.
|
||||||
|
|
||||||
Existing variables such as `EG_EMAIL`, `EG_PASSWORD`, `PG_EMAIL`,
|
Existing variables such as `EG_EMAIL`, `EG_PASSWORD`, `PG_EMAIL`,
|
||||||
`PG_PASSWORD`, `PG_OTPKEY`, `GOG_EMAIL`, `GOG_PASSWORD`, `SHOW`, `WIDTH`,
|
`PG_PASSWORD`, `PG_OTPKEY`, `GOG_EMAIL`, `GOG_PASSWORD`, `SHOW`, `WIDTH`,
|
||||||
`HEIGHT`, `TIMEOUT`, `LOGIN_TIMEOUT`, `DRYRUN`, and `NOTIFY` remain compatible.
|
`HEIGHT`, `TIMEOUT`, `LOGIN_TIMEOUT`, `DRYRUN`, and `NOTIFY` remain compatible.
|
||||||
@@ -132,16 +142,31 @@ normally uses port `7080`.
|
|||||||
|
|
||||||
## Upstream update policy
|
## Upstream update policy
|
||||||
|
|
||||||
The image is built from an explicit upstream commit in the Dockerfile. This
|
The image is built from the upstream release named by `ARG BUILD_UPSTREAM` in
|
||||||
keeps amd64 and aarch64 images reproducible and prevents an upstream branch or
|
the Dockerfile, downloaded as the matching `v<version>` source tarball. The
|
||||||
container tag from changing without an add-on review and version bump.
|
repository updater tracks upstream releases and bumps that value, the add-on
|
||||||
|
version and `CHANGELOG.md` together, so a new upstream release reaches the
|
||||||
|
add-on without a manual edit.
|
||||||
|
|
||||||
The repository updater is intentionally paused for this add-on because the
|
A release tag is a mutable reference. Rebuilding the same `BUILD_UPSTREAM`
|
||||||
add-on uses its own `2.x` version series while the replacement upstream uses a
|
installs whatever that tag points at, so an upstream tag that is force-moved or
|
||||||
`1.x` version series. An automatic replacement would risk a Home Assistant
|
deleted would change or fail the build without an add-on change. That is the
|
||||||
version regression and would not safely update the pinned commit. A maintainer
|
accepted cost of automatic tracking, and it is the same trade-off every other
|
||||||
upstream update must therefore update `UPSTREAM_REF`, `upstream_version`, the
|
automatically updated add-on in this repository makes; the previous commit pin
|
||||||
add-on version, and `CHANGELOG.md` together.
|
was immutable but could only be advanced by hand.
|
||||||
|
|
||||||
|
Upstream's development tags (`v1.7d` and similar) are filtered out through
|
||||||
|
`"github_exclude": "d"` in `updater.json`. Without it the updater reports the
|
||||||
|
`v1.7d` tag as release `1.7`, for which GitHub serves no source archive, and
|
||||||
|
the build would fail.
|
||||||
|
|
||||||
|
The add-on version does not track the upstream version. The add-on uses a `2.x`
|
||||||
|
series while upstream is on `1.x`, and Home Assistant only offers an update
|
||||||
|
when the new version sorts strictly higher, so the updater increments the
|
||||||
|
add-on version (`2.1.0` to `2.1.1`) instead of publishing a lower-sorting
|
||||||
|
upstream number. The upstream release actually installed is recorded in
|
||||||
|
`upstream_version` in `updater.json`, in `CHANGELOG.md`, and in the add-on's
|
||||||
|
startup banner.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
arch:
|
arch:
|
||||||
- aarch64
|
- aarch64
|
||||||
- amd64
|
- amd64
|
||||||
description: "Automatically claims free games from Epic Games Store, Amazon Prime Gaming, GOG, Steam, and optional GamerPower-supported stores"
|
description: "Claims free games from Epic, Prime, GOG, Steam, Ubisoft and more"
|
||||||
devices:
|
devices:
|
||||||
- /dev/dri
|
- /dev/dri
|
||||||
- /dev/dri/card0
|
- /dev/dri/card0
|
||||||
@@ -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.0.1"
|
version: "2.1.0"
|
||||||
webui: "[PROTO:ssl]://[HOST]:[PORT:6080]"
|
webui: "[PROTO:ssl]://[HOST]:[PORT:6080]"
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ set -a
|
|||||||
source "${RUNTIME_CONFIG}"
|
source "${RUNTIME_CONFIG}"
|
||||||
set +a
|
set +a
|
||||||
|
|
||||||
|
# Upstream checks GitHub for newer releases and then tells the user to run
|
||||||
|
# "docker compose pull", which is wrong here: the add-on is updated through the
|
||||||
|
# Home Assistant add-on store. Default the check off, but honour an explicit
|
||||||
|
# NOTIFY_UPDATES from config.env.
|
||||||
|
export NOTIFY_UPDATES="${NOTIFY_UPDATES:-false}"
|
||||||
|
|
||||||
# The Home Assistant port mapping is intentionally kept at 6080 for a seamless
|
# The Home Assistant port mapping is intentionally kept at 6080 for a seamless
|
||||||
# upgrade from the previous add-on, even though the new upstream defaults to 7080.
|
# upgrade from the previous add-on, even though the new upstream defaults to 7080.
|
||||||
if [ -n "${NOVNC_PORT:-}" ] && [ "${NOVNC_PORT}" != "6080" ]; then
|
if [ -n "${NOVNC_PORT:-}" ] && [ "${NOVNC_PORT}" != "6080" ]; then
|
||||||
|
|||||||
@@ -10,12 +10,17 @@ TIMEOUT=60
|
|||||||
LOGIN_TIMEOUT=180
|
LOGIN_TIMEOUT=180
|
||||||
VNC_LOGIN_TIMEOUT=180
|
VNC_LOGIN_TIMEOUT=180
|
||||||
NOVNC_PORT=6080
|
NOVNC_PORT=6080
|
||||||
|
# Public address used in notification links when behind a reverse proxy.
|
||||||
|
# VNC_URL=https://fgc.example.tld
|
||||||
|
|
||||||
# Keep the previous add-on's default stores. Add steam or gamerpower if wanted.
|
# Keep the previous add-on's default stores. The upstream also supports
|
||||||
|
# steam, fab, ubisoft, aliexpress and gamerpower; add them here if wanted.
|
||||||
STORES=epic,prime,gog
|
STORES=epic,prime,gog
|
||||||
|
|
||||||
# Used only when RUN_ONCE is disabled in the add-on options.
|
# Used only when RUN_ONCE is disabled in the add-on options.
|
||||||
SCHEDULER_HOURS=12
|
SCHEDULER_HOURS=12
|
||||||
|
# SCHEDULER_TIMEZONE=UTC
|
||||||
|
# SCHEDULER_FIXED_TIMES=17:00,21:30
|
||||||
|
|
||||||
# Common credentials can be used as fallbacks for all stores.
|
# Common credentials can be used as fallbacks for all stores.
|
||||||
# EMAIL=
|
# EMAIL=
|
||||||
@@ -33,7 +38,27 @@ SCHEDULER_HOURS=12
|
|||||||
# GOG_PASSWORD=
|
# GOG_PASSWORD=
|
||||||
# STEAM_USERNAME=
|
# STEAM_USERNAME=
|
||||||
# STEAM_PASSWORD=
|
# STEAM_PASSWORD=
|
||||||
|
# UBI_EMAIL=
|
||||||
|
# UBI_PASSWORD=
|
||||||
|
# UBI_OTPKEY=
|
||||||
|
# AE_EMAIL=
|
||||||
|
# AE_PASSWORD=
|
||||||
|
|
||||||
|
# Epic's weekly free mobile game, claimed with the epic store.
|
||||||
|
# EG_MOBILE=true
|
||||||
|
# EG_MOBILE_PLATFORMS=android,ios
|
||||||
|
|
||||||
|
# Fab assets sign in with the Epic account. Accepting the licence agreement is
|
||||||
|
# required to claim; set to false to be notified instead of accepting.
|
||||||
|
# FAB_ACCEPT_EULA=true
|
||||||
|
|
||||||
# Notifications (Apprise or Discord)
|
# Notifications (Apprise or Discord)
|
||||||
# NOTIFY=
|
# NOTIFY=
|
||||||
# DISCORD_WEBHOOK=
|
# DISCORD_WEBHOOK=
|
||||||
|
# NOTIFY_ALREADY_CLAIMED=false
|
||||||
|
# NOTIFY_SKIP_STORES=
|
||||||
|
|
||||||
|
# Upstream release notifications are disabled by the add-on: updates are
|
||||||
|
# delivered through the Home Assistant add-on store, not by docker compose.
|
||||||
|
# Set to true to receive them anyway.
|
||||||
|
# NOTIFY_UPDATES=false
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"dockerhub_by_date": false,
|
"dockerhub_by_date": false,
|
||||||
"dockerhub_list_size": 2,
|
"dockerhub_list_size": 2,
|
||||||
"last_update": "17-07-2026",
|
"github_exclude": "d",
|
||||||
"paused": true,
|
"github_fulltag": false,
|
||||||
|
"last_update": "24-08-2026",
|
||||||
|
"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.1"
|
"upstream_version": "1.6"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 2.44.0.1 (2026-08-24)
|
||||||
|
|
||||||
|
- Rebuild to pick up a fix in the shared entrypoint: when `ha_entrypoint.sh` runs as PID 1, as it does in this add-on, it now populates `/run/s6/container_environment`, so a script carrying a `#!/usr/bin/with-contenv` shebang gets a populated environment including `SUPERVISOR_TOKEN` instead of failing at its shebang. Nothing shipped in this add-on still uses that shebang (the healthcheck moved to plain bash in 2.44.0), so this changes nothing about the agent itself; it matters for a `script.sh` added by the user, and it is the add-on that build-tests the shared change
|
||||||
|
|
||||||
## 2.44.0 (2026-08-23)
|
## 2.44.0 (2026-08-23)
|
||||||
|
|
||||||
- Fix: Use `portainer/agent:alpine-sts` to match the STS release channel configured in `updater.json`
|
- Fix: Use `portainer/agent:alpine-sts` to match the STS release channel configured in `updater.json`
|
||||||
|
|||||||
@@ -41,4 +41,4 @@ schema:
|
|||||||
slug: portainer_agent
|
slug: portainer_agent
|
||||||
udev: true
|
udev: true
|
||||||
url: https://github.com/alexbelgium/hassio-addons
|
url: https://github.com/alexbelgium/hassio-addons
|
||||||
version: "2.44.0"
|
version: "2.44.0.1"
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
|
|
||||||
|
## 5.1.1.2 (2026-08-25)
|
||||||
|
- Ingress is now enabled: the WebUI opens directly in the Home Assistant sidebar, and the "Open Web UI" button now goes there. Access by ip:port is unchanged, but has to be typed rather than clicked, as Home Assistant does not allow an add-on to offer both.
|
||||||
|
- Note for users who set a "Host verification" whitelist in SABnzbd: ingress sends `Host: 127.0.0.1:8080` upstream, because SABnzbd rejects any Host that is not an IP literal. That whitelist therefore no longer filters the ingress route, which is gated by Home Assistant authentication instead. Direct ip:port access is unchanged and still filtered.
|
||||||
|
|
||||||
## 5.1.1 (2026-08-22)
|
## 5.1.1 (2026-08-22)
|
||||||
- Update to latest version from linuxserver/docker-sabnzbd (changelog : https://github.com/linuxserver/docker-sabnzbd/releases)
|
- Update to latest version from linuxserver/docker-sabnzbd (changelog : https://github.com/linuxserver/docker-sabnzbd/releases)
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ environment:
|
|||||||
PGID: "0"
|
PGID: "0"
|
||||||
PUID: "0"
|
PUID: "0"
|
||||||
image: ghcr.io/alexbelgium/sabnzbd-{arch}
|
image: ghcr.io/alexbelgium/sabnzbd-{arch}
|
||||||
ingress_entry: sabnzbd
|
ingress: true
|
||||||
init: false
|
init: false
|
||||||
map:
|
map:
|
||||||
- addon_config:rw
|
- addon_config:rw
|
||||||
@@ -106,5 +106,4 @@ schema:
|
|||||||
slug: sabnzbd
|
slug: sabnzbd
|
||||||
udev: true
|
udev: true
|
||||||
url: https://github.com/alexbelgium/hassio-addons
|
url: https://github.com/alexbelgium/hassio-addons
|
||||||
version: "5.1.1"
|
version: "5.1.1.2"
|
||||||
webui: http://[HOST]:[PORT:8080]
|
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
#!/usr/bin/with-contenv bashio
|
#!/usr/bin/with-contenv bashio
|
||||||
# shellcheck shell=bash
|
# shellcheck shell=bash
|
||||||
# shellcheck disable=SC2317
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# NGINX SETTING #
|
# NGINX SETTING #
|
||||||
#################
|
#################
|
||||||
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
ingress_port=$(bashio::addon.ingress_port)
|
ingress_port=$(bashio::addon.ingress_port)
|
||||||
ingress_interface=$(bashio::addon.ip_address)
|
ingress_interface=$(bashio::addon.ip_address)
|
||||||
|
ingress_entry=$(bashio::addon.ingress_entry)
|
||||||
sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf
|
sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf
|
||||||
sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf
|
sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf
|
||||||
|
sed -i "s|%%ingress_entry%%|${ingress_entry}|g" /etc/nginx/servers/ingress.conf
|
||||||
# Allows serving js
|
|
||||||
sed -i 's/<!-- %if-not-debug% -->/<!-- %if-not-debug% /g' /app/sabnzbd/webui/index.html
|
|
||||||
sed -i 's/<!-- %end% -->/ %end% -->/g' /app/sabnzbd/webui/index.html
|
|
||||||
sed -i 's/<!-- %if-debug%/<!-- %if-debug% -->/g' /app/sabnzbd/webui/index.html
|
|
||||||
sed -i 's/ %end% -->/<!-- %end% -->/g' /app/sabnzbd/webui/index.html
|
|
||||||
|
|||||||
@@ -2,22 +2,34 @@ server {
|
|||||||
listen %%interface%%:%%port%% default_server;
|
listen %%interface%%:%%port%% default_server;
|
||||||
|
|
||||||
include /etc/nginx/includes/server_params.conf;
|
include /etc/nginx/includes/server_params.conf;
|
||||||
include /etc/nginx/includes/proxy_params.conf;
|
|
||||||
|
|
||||||
client_max_body_size 0;
|
client_max_body_size 0;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
add_header Access-Control-Allow-Origin *;
|
proxy_pass http://127.0.0.1:8080;
|
||||||
proxy_connect_timeout 30m;
|
|
||||||
proxy_send_timeout 30m;
|
|
||||||
proxy_read_timeout 30m;
|
|
||||||
proxy_pass http://127.0.0.1:8080;
|
|
||||||
|
|
||||||
proxy_set_header Accept-Encoding "";
|
# SABnzbd refuses any request whose Host is not an IP literal
|
||||||
# Correct url without port when using https
|
# ("Access denied - Hostname verification failed"), so send the
|
||||||
sub_filter_once off;
|
# upstream socket rather than the browser's host. X-Forwarded-For is
|
||||||
sub_filter_types *;
|
# the only other header it reads (for its verify_xff_header option).
|
||||||
sub_filter /sabnzbd %%ingress_entry%%/sabnzbd;
|
proxy_set_header Host $proxy_host;
|
||||||
}
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
# The interface itself only emits relative links, so no body
|
||||||
|
# rewriting is needed. Redirects are the exception: Raiser() emits
|
||||||
|
# a path under url_base, so prefix them with the ingress entry.
|
||||||
|
# absolute_redirect must stay off, or nginx expands the rewritten
|
||||||
|
# Location into http://<browser host>:<ingress port>/..., a port the
|
||||||
|
# browser cannot reach.
|
||||||
|
proxy_redirect / %%ingress_entry%%/;
|
||||||
|
absolute_redirect off;
|
||||||
|
|
||||||
|
# The login cookie is hardcoded to Path=/, which on the ingress
|
||||||
|
# origin would send it to every other add-on's ingress path too.
|
||||||
|
proxy_cookie_path / %%ingress_entry%%/;
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_read_timeout 86400s;
|
||||||
|
proxy_send_timeout 86400s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
sabnzbd/rootfs/etc/services.d/nginx/finish
Executable file
9
sabnzbd/rootfs/etc/services.d/nginx/finish
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/with-contenv bashio
|
||||||
|
# shellcheck shell=bash
|
||||||
|
# ==============================================================================
|
||||||
|
# Stop the container when Nginx fails, so ingress does not silently go dead
|
||||||
|
# ==============================================================================
|
||||||
|
if [[ "$1" -ne 0 && "$1" -ne 256 ]]; then
|
||||||
|
bashio::log.error "Nginx exited with code $1"
|
||||||
|
kill -15 1
|
||||||
|
fi
|
||||||
11
sabnzbd/rootfs/etc/services.d/nginx/run
Executable file
11
sabnzbd/rootfs/etc/services.d/nginx/run
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/with-contenv bashio
|
||||||
|
# shellcheck shell=bash
|
||||||
|
set -e
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# Wait for sabnzbd to become available
|
||||||
|
bashio::net.wait_for 8080 localhost 900
|
||||||
|
|
||||||
|
bashio::log.info "Starting NGinx..."
|
||||||
|
|
||||||
|
exec nginx
|
||||||
Reference in New Issue
Block a user