Compare commits
13 Commits
ci/version
...
docs/skill
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
833d0d3758 | ||
|
|
138e7d7c33 | ||
|
|
8341e542fb | ||
|
|
a68ef0dde2 | ||
|
|
23fad482b8 | ||
|
|
5a87daf3e4 | ||
|
|
f12cfa9b78 | ||
|
|
afbab942a6 | ||
|
|
0cb360d04a | ||
|
|
7cd844ac9f | ||
|
|
9e5405fadc | ||
|
|
f70fbe9705 | ||
|
|
9204b31af8 |
@@ -29,8 +29,8 @@ Triage first, then one of two paths:
|
||||
resolve bot comments.
|
||||
- **Full loop** — performance/RAM/CPU work, diagnosis, anything changing a shipped default,
|
||||
ladder levels 4-6, or an explicit Codex-check request: scope → measure → plan → Codex reviews
|
||||
the plan → implement → simplify → Codex reviews the code → PR → resolve comments → verify in
|
||||
production → report.
|
||||
the plan → implement → simplify → Codex reviews the code → **simplify again** → PR → resolve
|
||||
comments → verify in production → report.
|
||||
|
||||
Escalate mid-flight if a light task grows — touches a default, needs a new script or service, or
|
||||
reveals a deeper problem.
|
||||
@@ -42,7 +42,10 @@ where every add-on solves a problem the same way is worth more than a locally ni
|
||||
design. Prefer reusing or extending over adding a parallel implementation, and when you must add
|
||||
something new, spell it the way the rest of the repo spells it (naming, option names, script
|
||||
numbering, file layout). Complexity is bought only by a **measurement** showing a concrete,
|
||||
user-visible cost on a real host — never by reasoning about hypothetical performance.
|
||||
user-visible cost on a real host — never by reasoning about hypothetical performance, and never by
|
||||
reasoning about a hypothetical *host* either. A defensive branch is complexity like any other: name
|
||||
the input that reaches it and the image or host where that happens, or delete it and let the case
|
||||
fail visibly instead.
|
||||
|
||||
**Repo layout.** `alexbelgium/hassio-addons`; each add-on is a top-level directory. This skill is
|
||||
checked in at `.claude/skills/hassio-addon-workflow/` (canonical copy). Set the skill root once,
|
||||
@@ -113,6 +116,10 @@ Attack your own plan before implementing:
|
||||
- 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**?
|
||||
Highest-yield question here — see `references/evidence.md`'s failure-mode section.
|
||||
- For every branch that exists **only to survive something going wrong**: name the image or host
|
||||
where that input actually arrives, and go and look. Naming is the bar, not reproducing it here —
|
||||
`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
|
||||
whose prompt includes the path `references/codex-review.md` and tells it to follow that file's
|
||||
@@ -140,18 +147,31 @@ justify the divergence in the PR body — but never at the cost of an isolation
|
||||
numbered script, not an edit. Case studies of what happens when this check is skipped:
|
||||
`references/simplify.md`.
|
||||
|
||||
## 6. Codex attacks the code (full loop only)
|
||||
## 6. Codex attacks the code, then simplify what the review added (full loop only)
|
||||
|
||||
Same delegated invocation, pointed at `git diff origin/master...HEAD` plus your reasoning per
|
||||
hunk. Details in `references/codex-review.md`.
|
||||
|
||||
Then run step 5's checks again over the hunks the review changed. Adversarial review only ever
|
||||
argues *for* another branch — that is its job — so accepting objections ratchets the diff upward,
|
||||
and nothing else in the loop walks it back down. For each accepted objection: is the case it
|
||||
defends one 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
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
`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,
|
||||
**what is not verified**, and how to roll back the riskiest hunk alone.
|
||||
@@ -172,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
|
||||
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`.
|
||||
|
||||
## 10. Calibrate and report
|
||||
@@ -191,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
|
||||
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
|
||||
only if its output surprises you.
|
||||
|
||||
@@ -39,3 +39,10 @@ codex exec --model gpt-5.6-sol --sandbox read-only --skip-git-repo-check \
|
||||
**Codex agrees with confident premises.** It has confirmed a wrong conclusion stated too
|
||||
confidently, and separately caught a genuine methodology error in the same review. Treat its
|
||||
confirmations with the same scepticism as its objections — especially about the build.
|
||||
|
||||
**Its objections ratchet complexity upward.** An adversarial reviewer is asked to find what could
|
||||
go wrong, so its output is a list of arguments for more code; it is never asked whether the branch
|
||||
it wants is reachable. Separate "this is wrong" from "this is undefended" before you write
|
||||
anything: the first is a bug and you fix it, the second is a claim about some host, and it needs
|
||||
the same demonstration you would demand of a measurement. That is what step 6's second simplify
|
||||
pass is for.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Simplify — case studies
|
||||
|
||||
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.
|
||||
|
||||
- A rejected PR spent a **388-line TCP proxy plus a 142-line monkeypatch of a private upstream
|
||||
@@ -13,6 +13,22 @@ 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
|
||||
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.
|
||||
- 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
|
||||
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
|
||||
binaries living in different s6 packages, never demonstrated on any real image, and the case it
|
||||
defended would have degraded to the pre-fix behaviour anyway — and a **helper function plus a
|
||||
second reset** that existed only to serve that fallback. Deleting the fallback deleted all of
|
||||
it. The rest of the review's objections were correct and cost two tokens on an existing line.
|
||||
It took the maintainer asking "is this the simplest way possible?" to run the pass that step 6
|
||||
now requires.
|
||||
|
||||
## Checks worth running against your own diff
|
||||
|
||||
@@ -25,6 +41,17 @@ Being able to build the complicated thing is not a reason to.
|
||||
A removal that depends on a host default still needs the same verification as an addition.
|
||||
- **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.
|
||||
- **For each defensive branch: what input reaches it, on which image or host?** Go and check,
|
||||
the way you would check a measurement. The bar is being able to **name** the case, not to
|
||||
reproduce it here: Docker's 64 MB `/dev/shm` default is documented behaviour that HA does not
|
||||
override, so the bullet above keeps that guard even though this host measured 7.7 GB. Nobody
|
||||
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?
|
||||
Code that reads a documented knob keeps working. Code that reaches into private internals
|
||||
does not.
|
||||
|
||||
@@ -11,6 +11,7 @@ workflows and lint rules — that is not repeated here.
|
||||
- [Environment and workspace](#environment-and-workspace)
|
||||
- [Measurement](#measurement)
|
||||
- [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)
|
||||
- [Dockerfile and architecture](#dockerfile-and-architecture)
|
||||
- [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
|
||||
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
|
||||
|
||||
**`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
|
||||
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`
|
||||
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.
|
||||
@@ -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.
|
||||
`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
|
||||
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.
|
||||
|
||||
@@ -90,6 +90,7 @@ resolve)
|
||||
;;
|
||||
watch)
|
||||
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
|
||||
c=$(gh pr checks "$PR" 2> /dev/null | awk '{print $1"="$2}' | tr '\n' ' ')
|
||||
if [ -z "$c" ]; then
|
||||
@@ -102,11 +103,14 @@ watch)
|
||||
case "$c" in
|
||||
*pending*) sleep 60 ;;
|
||||
*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
|
||||
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."
|
||||
exit "${wfail:-0}"
|
||||
exit "$wfail"
|
||||
;;
|
||||
*) echo "unknown: $CMD"; exit 1 ;;
|
||||
esac
|
||||
|
||||
@@ -64,7 +64,8 @@ if [ -n "${BUILD_VERSION:-}" ]; then
|
||||
echo " $SLUG/config.yaml version = $here"
|
||||
echo " running image BUILD_VERSION = $BUILD_VERSION"
|
||||
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
|
||||
echo " MISMATCH — this branch is NOT what is running."
|
||||
git fetch origin master --quiet 2> /dev/null
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
# rather than implying the build was checked.
|
||||
#
|
||||
# --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]
|
||||
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:-}" = "--vs-master" ] && { ADDON=""; set -- --vs-master; }
|
||||
@@ -31,17 +34,21 @@ echo "== validating $ADDON =="
|
||||
|
||||
fail=0
|
||||
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).
|
||||
while IFS= read -r f; do
|
||||
[ -f "$f" ] || continue
|
||||
# Shell: bash -n then shellcheck -x (follows sourced files, as CI does). One list for both.
|
||||
files=()
|
||||
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
|
||||
done < <(find "$ADDON" -type f \( -name '*.sh' -o -name 'run' -o -name 'finish' -o -name 'autostart' \) 2> /dev/null)
|
||||
[ "$fail" -eq 0 ] && note "bash -n" "ok"
|
||||
done
|
||||
[ "$fail" -eq 0 ] && note "bash -n" "${#files[@]} file(s) checked"
|
||||
|
||||
if 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 |
|
||||
xargs -0 -r shellcheck -x -f gcc 2>&1)
|
||||
if [ "${#files[@]}" -gt 0 ] && command -v shellcheck > /dev/null 2>&1; then
|
||||
sc=$(shellcheck -x -f gcc "${files[@]}" 2>&1)
|
||||
if [ -n "$sc" ]; then
|
||||
note "shellcheck" "$(printf '%s\n' "$sc" | grep -c .) finding(s)"
|
||||
printf '%s\n' "$sc" | sed 's/^/ /' | head -20
|
||||
@@ -50,7 +57,10 @@ fi
|
||||
|
||||
command -v hadolint > /dev/null 2>&1 && [ -f "$ADDON/Dockerfile" ] && {
|
||||
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
|
||||
@@ -79,11 +89,14 @@ $VS_MASTER && command -v npx > /dev/null 2>&1 && [ -f "$ADDON/CHANGELOG.md" ] &&
|
||||
|
||||
echo
|
||||
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"
|
||||
else
|
||||
# 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
|
||||
if git diff origin/master...HEAD -- "$ADDON/config.yaml" 2> /dev/null | grep -q '^+version:'; then
|
||||
note "version" "bumped"
|
||||
@@ -98,8 +111,15 @@ if $VS_MASTER; then
|
||||
echo
|
||||
echo "== findings ADDED by this diff (pre-existing ones filtered out) =="
|
||||
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
|
||||
git show "origin/master:$f" > "$tmp/base" 2> /dev/null || continue
|
||||
added=0
|
||||
# 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":
|
||||
# its "command not found" error is identical for base and head, so comm would
|
||||
# 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]*//'; } ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
cp "$tmp/base" "$tmp/base_f"; b=$(cmd "$tmp/base_f" | sort)
|
||||
b=$(cmd "$tmp/base" | sort)
|
||||
a=$(cmd "$f" | sort)
|
||||
new=$(comm -13 <(printf '%s\n' "$b") <(printf '%s\n' "$a") | grep -c .)
|
||||
[ "$new" -gt 0 ] && { echo " $f: $new NEW finding(s)"; comm -13 <(printf '%s\n' "$b") <(printf '%s\n' "$a") | sed 's/^/ /' | head -5; }
|
||||
done
|
||||
echo " (nothing listed above = your diff introduced no new lint findings)"
|
||||
new=$(comm -13 <(printf '%s\n' "$b") <(printf '%s\n' "$a"))
|
||||
[ -n "$new" ] && {
|
||||
echo " $f: $(printf '%s\n' "$new" | grep -c .) NEW finding(s)"
|
||||
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
|
||||
|
||||
echo
|
||||
|
||||
143
.github/scripts/check_version_bump.sh
vendored
@@ -1,143 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Destination: .github/scripts/check_version_bump.sh
|
||||
#
|
||||
# Fails a pull request that changes what an add-on ships without bumping that
|
||||
# add-on's `version`. Supervisor only offers a rebuild when `version` changes,
|
||||
# so a fix merged without one leaves every user on the old image: merged, inert,
|
||||
# and the issue looks closed. Nothing else in CI checks this.
|
||||
#
|
||||
# It deliberately does NOT reuse check-addon-changes' `changedAddons`. That
|
||||
# output is built from `^<addon>/config\.(json|ya?ml)$` alone, so it is empty
|
||||
# for exactly the pull requests this check exists to catch — an add-on whose
|
||||
# scripts changed while config.yaml did not. Reusing it would make this a no-op.
|
||||
# This scan is local to this check and does not affect which add-ons get linted
|
||||
# or built.
|
||||
#
|
||||
# Env:
|
||||
# BASE_SHA (required) — commit this PR is diffed against
|
||||
# HEAD_SHA (required) — the PR's merge commit
|
||||
#
|
||||
# Exit 0 = every add-on that needs a bump got one (or nothing relevant changed).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${BASE_SHA:?BASE_SHA must be set}"
|
||||
: "${HEAD_SHA:?HEAD_SHA must be set}"
|
||||
|
||||
# Files that ship to users only as repo metadata, or that bots rewrite on their
|
||||
# own schedule. Changing one of these alone does not require anybody to receive
|
||||
# a new image, so it must not demand a version bump — otherwise every changelog
|
||||
# or stats-graph commit would fail CI.
|
||||
#
|
||||
# Everything else under an add-on directory counts: Dockerfile, rootfs/,
|
||||
# build.json|yaml (base images), apparmor.txt and translations/ (Supervisor
|
||||
# re-reads them on update), root-level *.sh that Dockerfiles COPY, and
|
||||
# config.yaml itself — an added option or changed port needs the update offered
|
||||
# just as much as a code change does. Allowlisting the ignorable and treating
|
||||
# the remainder as significant fails closed: a new kind of file defaults to
|
||||
# "needs a bump" rather than silently escaping the check.
|
||||
is_ignorable() {
|
||||
local rel="$1" # path relative to the add-on directory
|
||||
# addon/images/** is artwork; addon/rootfs/**/images/** is shipped content,
|
||||
# so this must be anchored at the add-on root rather than matching any
|
||||
# path that happens to contain an "images" segment.
|
||||
case "$rel" in
|
||||
images/*) return 0 ;;
|
||||
esac
|
||||
# Anything else nested ships inside the image (rootfs/, translations/, ...).
|
||||
case "$rel" in
|
||||
*/*) return 1 ;;
|
||||
esac
|
||||
case "$rel" in
|
||||
CHANGELOG.md | updater.json | stats.png | icon.png | logo.png | *.md) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# An add-on is a top-level directory with a config file. Tested against the
|
||||
# BASE tree so a directory deleted by this PR is still recognised (and then
|
||||
# skipped below), and .github/, .templates/ and .claude/ are excluded for free
|
||||
# by simply not having one.
|
||||
addon_config_at() {
|
||||
local ref="$1" addon="$2" f
|
||||
for f in config.yaml config.yml config.json; do
|
||||
if git cat-file -e "${ref}:${addon}/${f}" 2> /dev/null; then
|
||||
printf '%s' "$f"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Reads the add-on's declared version. JSON goes through jq so a minified or
|
||||
# reordered config.json is read correctly rather than silently returning empty.
|
||||
# YAML is matched at column 0 on purpose: an indented `version:` belongs to a
|
||||
# nested mapping (a schema entry, an option literally named version) and
|
||||
# comparing it would compare the wrong value. A trailing YAML comment is
|
||||
# stripped before quotes so `version: "1.2.3" # note` does not read as a bump.
|
||||
version_at() {
|
||||
local ref="$1" path="$2" content
|
||||
content=$(git show "${ref}:${path}" 2> /dev/null) || return 1
|
||||
case "$path" in
|
||||
*.json)
|
||||
printf '%s' "$content" | jq -r '.version // empty' 2> /dev/null
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "$content" |
|
||||
grep -m1 -E '^version[[:space:]]*:' |
|
||||
sed -E 's/^version[[:space:]]*:[[:space:]]*//; s/[[:space:]]+#.*$//; s/^["'\'']//; s/["'\'']$//; s/[[:space:]]*$//'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
mapfile -t CHANGED < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
|
||||
|
||||
declare -A NEEDS_BUMP=()
|
||||
for file in "${CHANGED[@]}"; do
|
||||
[ -n "$file" ] || continue
|
||||
case "$file" in */*) ;; *) continue ;; esac # top-level files are not add-ons
|
||||
addon="${file%%/*}"
|
||||
is_ignorable "${file#*/}" && continue
|
||||
addon_config_at "$BASE_SHA" "$addon" > /dev/null 2>&1 || continue
|
||||
NEEDS_BUMP["$addon"]=1
|
||||
done
|
||||
|
||||
if [ "${#NEEDS_BUMP[@]}" -eq 0 ]; then
|
||||
echo "No add-on changes that require a version bump."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
FAILED=0
|
||||
for addon in $(printf '%s\n' "${!NEEDS_BUMP[@]}" | sort); do
|
||||
base_cfg=$(addon_config_at "$BASE_SHA" "$addon")
|
||||
# Resolved separately at HEAD: an add-on that renames its manifest between
|
||||
# supported names (config.yaml -> config.yml) while changing shipped files
|
||||
# would otherwise be read at the old path, come back empty, and slip through.
|
||||
if ! head_cfg=$(addon_config_at "$HEAD_SHA" "$addon"); then
|
||||
echo " $addon: removed by this PR, skipping"
|
||||
continue
|
||||
fi
|
||||
old=$(version_at "$BASE_SHA" "$addon/$base_cfg" || true)
|
||||
new=$(version_at "$HEAD_SHA" "$addon/$head_cfg" || true)
|
||||
# Fail closed. An unreadable version used to warn and skip, which let the
|
||||
# job go green on exactly the add-ons whose manifest this check could not
|
||||
# understand — the opposite of what it is for.
|
||||
if [ -z "$old" ] || [ -z "$new" ]; then
|
||||
echo "::error file=$addon/$head_cfg::$addon: could not read a version from $base_cfg (base) or $head_cfg (head). Refusing to pass a check that could not be performed."
|
||||
FAILED=1
|
||||
continue
|
||||
fi
|
||||
if [ "$old" = "$new" ]; then
|
||||
echo "::error file=$addon/$head_cfg::$addon ships changed files but version is still $old. Supervisor only offers a rebuild when version changes, so this would merge without reaching anyone. Bump it following this add-on's own convention: for a local patch counter take the boundary from updater.json's upstream_version (append .1 when version equals it, otherwise increment the digits after it); date-based and LSIO-style versions have no counter and follow their own scheme."
|
||||
FAILED=1
|
||||
else
|
||||
echo " $addon: $old -> $new"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$FAILED" -ne 0 ]; then
|
||||
echo "::error::One or more add-ons changed without a version bump. Add the 'skip-version-check' label and re-run this job if that is deliberate."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All changed add-ons have a version bump."
|
||||
BIN
.github/stargazer_map.png
vendored
|
Before Width: | Height: | Size: 404 KiB After Width: | Height: | Size: 68 KiB |
BIN
.github/stats.png
vendored
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
.github/stats_addons.png
vendored
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 4.2 KiB |
2
.github/workflows/daily_README.yaml
vendored
@@ -228,7 +228,7 @@ jobs:
|
||||
echo "... done"
|
||||
|
||||
- name: Commit if needed
|
||||
uses: EndBug/add-and-commit@v11.0.0
|
||||
uses: EndBug/add-and-commit@v11.1.1
|
||||
with:
|
||||
message: "GitHub bot : README updated"
|
||||
default_author: github_actions
|
||||
|
||||
2
.github/workflows/daily_ai_fix.yaml
vendored
@@ -125,7 +125,7 @@ jobs:
|
||||
|
||||
- name: Analyse and fix
|
||||
if: steps.batch.outputs.count != '0'
|
||||
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
|
||||
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Skip the OIDC -> Claude App token exchange. The scheduled path
|
||||
|
||||
2
.github/workflows/helper_stats_graphs.yaml
vendored
@@ -237,7 +237,7 @@ jobs:
|
||||
# Get stars evolution
|
||||
wget -S -O .github/starsevol.svg "https://api.star-history.com/svg?repos=alexbelgium/hassio-addons&type=Date" || true
|
||||
- name: Commit if needed
|
||||
uses: EndBug/add-and-commit@v11.0.0
|
||||
uses: EndBug/add-and-commit@v11.1.1
|
||||
with:
|
||||
message: "GitHub bot : graphs updated"
|
||||
default_author: github_actions
|
||||
|
||||
2
.github/workflows/on_claude_mention.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code
|
||||
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
|
||||
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.
|
||||
|
||||
2
.github/workflows/on_issue_approved.yaml
vendored
@@ -135,7 +135,7 @@ jobs:
|
||||
|
||||
- name: Execute the plan
|
||||
if: steps.bundle.outputs.has_plan == 'true'
|
||||
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
|
||||
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||
|
||||
2
.github/workflows/on_issues.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
# Remove issues list
|
||||
rm issueslist
|
||||
- name: Commit if needed
|
||||
uses: EndBug/add-and-commit@v11.0.0
|
||||
uses: EndBug/add-and-commit@v11.1.1
|
||||
with:
|
||||
message: "Github bot : issues linked to readme"
|
||||
default_author: github_actions
|
||||
|
||||
2
.github/workflows/on_issues_ai_triage.yaml
vendored
@@ -166,7 +166,7 @@ jobs:
|
||||
id: classify
|
||||
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
|
||||
continue-on-error: true
|
||||
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
|
||||
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Without this the action falls back to the OIDC -> Claude App token
|
||||
|
||||
2
.github/workflows/on_pr_coderabbit.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
||||
|
||||
- name: Address CodeRabbit comments
|
||||
if: steps.claim.outputs.go == 'true'
|
||||
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
|
||||
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||
|
||||
63
.github/workflows/onpr_check-pr.yaml
vendored
@@ -58,69 +58,6 @@ jobs:
|
||||
echo "Changed addons: $changed_addons"
|
||||
echo "changed_addons=$changed_addons" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# 1b. A pull request that changes what an add-on ships must bump that add-on's
|
||||
# version. Supervisor only offers a rebuild when `version` changes, so without
|
||||
# one the fix merges and no user ever receives it - inert, while the issue
|
||||
# looks closed. Nothing else in CI checks this (the add-on linter validates
|
||||
# the schema, not that the value moved).
|
||||
#
|
||||
# Deliberately independent of check-addon-changes above: that job derives
|
||||
# `changedAddons` from `^<addon>/config.(json|ya?ml)$` alone, so it is empty
|
||||
# for precisely the pull requests this catches - an add-on whose scripts
|
||||
# changed while config.yaml did not. Gating this on its output would make it
|
||||
# a no-op. It does its own scan and changes nothing about which add-ons are
|
||||
# linted or built.
|
||||
check-version-bump:
|
||||
name: Check add-on version bumped
|
||||
if: ${{ github.repository_owner == 'alexbelgium' }}
|
||||
runs-on: ubuntu-latest
|
||||
# Only reads git history and the PR's labels.
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
steps:
|
||||
# The bypass label is read LIVE here rather than from the job's `if:`.
|
||||
# The event payload is a snapshot from trigger time, and this workflow
|
||||
# deliberately does not listen for `labeled` — adding that activity type
|
||||
# would re-run the ~3 h add-on builds on every label change. Reading it
|
||||
# at run time instead means "label the PR, then re-run this one job"
|
||||
# works, which is the sequence the failure message asks for.
|
||||
- name: Check for the bypass label
|
||||
id: bypass
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR: ${{ github.event.pull_request.number }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
skip=$(gh pr view "$PR" --repo "$REPO" --json labels \
|
||||
--jq '[.labels[].name] | index("skip-version-check") != null')
|
||||
echo "skip=$skip" >> "$GITHUB_OUTPUT"
|
||||
[ "$skip" = "true" ] && echo "skip-version-check label present; skipping." || true
|
||||
|
||||
- name: Checkout repo
|
||||
if: steps.bypass.outputs.skip != 'true'
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
# Same reason as check-addon-changes: HEAD^1 must be resolvable.
|
||||
fetch-depth: 2
|
||||
# Nothing here pushes, and the repo is public, so an anonymous fetch
|
||||
# of the base commit is enough - do not leave a token in .git/config.
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check every changed add-on bumped its version
|
||||
if: steps.bypass.outputs.skip != 'true'
|
||||
env:
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# HEAD^1, not pull_request.base.sha, for the same reason as above: the
|
||||
# event payload's base can be stale if master advanced since trigger.
|
||||
BASE_SHA=$(git rev-parse HEAD^1)
|
||||
git fetch origin "$BASE_SHA"
|
||||
export BASE_SHA
|
||||
bash .github/scripts/check_version_bump.sh
|
||||
|
||||
check-changed-changelog:
|
||||
name: Check if CHANGELOG.md changed
|
||||
needs: check-addon-changes
|
||||
|
||||
4
.github/workflows/onpush_builder.yaml
vendored
@@ -95,7 +95,7 @@ jobs:
|
||||
- name: Commit sanitize changes
|
||||
id: sanitize_commit
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: EndBug/add-and-commit@v11.0.0
|
||||
uses: EndBug/add-and-commit@v11.1.1
|
||||
with:
|
||||
commit: -u
|
||||
message: "GitHub bot: sanitize (spaces + LF endings) & chmod [nobuild]"
|
||||
@@ -410,7 +410,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Commit changelog changes
|
||||
uses: EndBug/add-and-commit@v11.0.0
|
||||
uses: EndBug/add-and-commit@v11.1.1
|
||||
with:
|
||||
commit: -u
|
||||
message: "GitHub bot: changelog [nobuild]"
|
||||
|
||||
4
.github/workflows/weekly_crlftolf.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: erclu/check-crlf@v1
|
||||
|
||||
- name: Commit if needed
|
||||
uses: EndBug/add-and-commit@v11.0.0
|
||||
uses: EndBug/add-and-commit@v11.1.1
|
||||
with:
|
||||
message: "Github bot : CRLF corrected"
|
||||
default_author: github_actions
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
dos2unix -k "$f"
|
||||
done
|
||||
- name: Commit if needed
|
||||
uses: EndBug/add-and-commit@v11.0.0
|
||||
uses: EndBug/add-and-commit@v11.1.1
|
||||
with:
|
||||
message: "Github bot : CRLF corrected"
|
||||
default_author: github_actions
|
||||
|
||||
2
.github/workflows/weekly_reduceimagesize.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
- name: Commit if needed
|
||||
if: steps.calibre.outputs.markdown != ''
|
||||
uses: EndBug/add-and-commit@v11.0.0
|
||||
uses: EndBug/add-and-commit@v11.1.1
|
||||
with:
|
||||
message: "Github bot : image compressed"
|
||||
default_author: github_actions
|
||||
|
||||
2
.github/workflows/weekly_stats.yaml
vendored
@@ -109,7 +109,7 @@ jobs:
|
||||
#TOTAL3="$(awk '{SUM+=$2}END{print SUM}' Stats)"
|
||||
|
||||
- name: Commit if needed
|
||||
uses: EndBug/add-and-commit@v11.0.0
|
||||
uses: EndBug/add-and-commit@v11.1.1
|
||||
with:
|
||||
default_author: github_actions
|
||||
message : "Github bot : stats updated"
|
||||
|
||||
@@ -168,6 +168,59 @@ if [ -z "$shebang" ]; then
|
||||
exit 1
|
||||
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
|
||||
####################################
|
||||
|
||||
@@ -427,7 +427,7 @@ If you want to do add the repository manually, please follow the procedure highl
|
||||
![aarch64][aarch64-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
|
||||
|
||||
  
|
||||

|
||||
|
||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
aurral/stats.png
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
baikal/stats.png
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
bazarr/stats.png
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,4 +1,7 @@
|
||||
|
||||
## 0.6.27.2 (2026-08-23)
|
||||
- Fix: trust the whole supervisor network range for the ingress auth header instead of the addon's own address, which changes across restarts. The list is only written when that range is missing, so an entry added in the calibre-web admin page is no longer erased on every start (https://github.com/alexbelgium/hassio-addons/pull/3010)
|
||||
|
||||
## 0.6.27.1 (2026-08-23)
|
||||
- Fix: Ingress login was rejected since 0.6.27, which only accepts the reverse proxy auth header from trusted source addresses. The addon now adds its own ip to that list (https://github.com/alexbelgium/hassio-addons/issues/3003)
|
||||
|
||||
|
||||
@@ -116,5 +116,5 @@ schema:
|
||||
slug: calibre-web
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons/tree/master/calibre_web
|
||||
version: "0.6.27.1"
|
||||
version: "0.6.27.2"
|
||||
video: true
|
||||
|
||||
@@ -19,25 +19,19 @@ if [ ! -f /config/app.db ]; then
|
||||
else
|
||||
sqlite3 /config/app.db 'update settings set config_reverse_proxy_login_header_name="X-WebAuth-User",config_allow_reverse_proxy_header_login=1'
|
||||
|
||||
# Calibre-web 0.6.27 only accepts the ingress auth header from a trusted source address, and
|
||||
# defaults that list to "127.0.0.1,::1". Nginx binds its upstream socket to the addon ip
|
||||
# (proxy_bind $server_addr in ingress.conf) and calibre-web listens dual-stack, so it sees
|
||||
# ::ffff:<addon ip> and drops the header. Both the plain and the ipv4-mapped forms are listed
|
||||
# because an ipv4 entry never matches an ipv6-mapped address on the calibre-web side.
|
||||
# The column only exists once calibre-web 0.6.27+ has migrated app.db, so a failure here is
|
||||
# not fatal : the next start applies it.
|
||||
addon_ip=$(bashio::addon.ip_address)
|
||||
trusted_ips="127.0.0.1,::1,::ffff:127.0.0.1"
|
||||
if bashio::var.has_value "${addon_ip}"; then
|
||||
trusted_ips="${trusted_ips},${addon_ip},::ffff:${addon_ip}"
|
||||
fi
|
||||
trusted_ips_error=$(sqlite3 /config/app.db "update settings set config_reverse_proxy_trusted_ips='${trusted_ips}'" 2>&1) || {
|
||||
if echo "${trusted_ips_error}" | grep -q "no such column"; then
|
||||
bashio::log.warning "Could not set the ingress trusted ip list, it will be applied at next start"
|
||||
else
|
||||
bashio::log.warning "Could not set the ingress trusted ip list: ${trusted_ips_error}"
|
||||
fi
|
||||
}
|
||||
# Calibre-web 0.6.27 only accepts that header from a trusted source address, and defaults the
|
||||
# list to "127.0.0.1,::1". Ingress reaches calibre-web from the addon's own address on the
|
||||
# supervisor network (proxy_bind $server_addr in ingress.conf) and calibre-web listens
|
||||
# dual-stack, so it sees ::ffff:<addon ip> and drops the header. The supervisor range is
|
||||
# listed in both forms because an ipv4 entry never matches an ipv4-mapped address.
|
||||
# Prepended to whatever is already there, and only when the mapped form is missing : that form
|
||||
# is the one ingress needs and the one nobody types by hand, so it doubles as the marker that
|
||||
# this already ran. Anything the user added is kept, the statement runs at most once, and the
|
||||
# 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
|
||||
# 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) ||
|
||||
bashio::log.warning "Could not set the ingress trusted ip list, it will be applied at next start (${trusted_ips_error})"
|
||||
fi
|
||||
|
||||
bashio::log.info "Default username:password is admin:admin123"
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
codex/stats.png
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
emby/stats.png
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
ente/stats.png
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -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)
|
||||
|
||||
- Aligned the pull-request build context with the production builder by copying
|
||||
|
||||
@@ -17,7 +17,7 @@ ARG BUILD_REF
|
||||
ARG BUILD_REPOSITORY
|
||||
ARG BUILD_VERSION
|
||||
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" \
|
||||
S6_CMD_WAIT_FOR_SERVICES_MAXTIME="0" \
|
||||
@@ -27,10 +27,7 @@ ENV S6_CMD_WAIT_FOR_SERVICES="1" \
|
||||
WIDTH="1280" \
|
||||
HEIGHT="720" \
|
||||
DEPTH="24" \
|
||||
SHOW="1" \
|
||||
COMMIT="${UPSTREAM_REF}" \
|
||||
BRANCH="main" \
|
||||
NOW="${BUILD_DATE}"
|
||||
SHOW="1"
|
||||
|
||||
# Install the dependencies used by Free Games Claimer Remaster. The upstream
|
||||
# Dockerfile supports both amd64 (Google Chrome) and arm64 (Chromium), so the
|
||||
@@ -87,6 +84,11 @@ RUN apt-get update \
|
||||
else \
|
||||
apt-get install -y --no-install-recommends chromium; \
|
||||
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/bin/python3 /usr/bin/python \
|
||||
&& apt-get purge -y gnupg \
|
||||
@@ -94,13 +96,20 @@ RUN apt-get update \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /var/cache/* /var/tmp/* /tmp/* /usr/share/doc/*
|
||||
|
||||
# Install a deterministic snapshot of the replacement upstream. Updating the
|
||||
# upstream reference is intentionally explicit so image contents cannot change
|
||||
# without an add-on version bump.
|
||||
# Install the upstream release named by BUILD_UPSTREAM. The repository updater
|
||||
# bumps that value together with the add-on version. A release tag is a mutable
|
||||
# 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
|
||||
RUN curl --proto "=https" --tlsv1.2 -fsSL \
|
||||
"https://github.com/${UPSTREAM_REPOSITORY}/archive/${UPSTREAM_REF}.tar.gz" \
|
||||
RUN (curl --proto "=https" --tlsv1.2 -fsSL \
|
||||
"https://github.com/${UPSTREAM_REPOSITORY}/archive/v${BUILD_UPSTREAM}.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 \
|
||||
&& python3 -m pip install --no-cache-dir --break-system-packages -r requirements.txt \
|
||||
&& dos2unix ./*.sh \
|
||||
@@ -155,4 +164,4 @@ LABEL \
|
||||
org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
org.opencontainers.image.revision="${BUILD_REF}" \
|
||||
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).
|
||||
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
|
||||
- GOG
|
||||
- Steam
|
||||
- Ubisoft giveaways (`ubisoft`)
|
||||
- AliExpress daily coin check-in (`aliexpress`)
|
||||
- GamerPower-supported stores, when explicitly enabled
|
||||
|
||||
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
|
||||
|
||||
@@ -98,6 +103,11 @@ NOTIFY=tgram://bot-token/chat-id
|
||||
# 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`,
|
||||
`PG_PASSWORD`, `PG_OTPKEY`, `GOG_EMAIL`, `GOG_PASSWORD`, `SHOW`, `WIDTH`,
|
||||
`HEIGHT`, `TIMEOUT`, `LOGIN_TIMEOUT`, `DRYRUN`, and `NOTIFY` remain compatible.
|
||||
@@ -132,16 +142,31 @@ normally uses port `7080`.
|
||||
|
||||
## Upstream update policy
|
||||
|
||||
The image is built from an explicit upstream commit in the Dockerfile. This
|
||||
keeps amd64 and aarch64 images reproducible and prevents an upstream branch or
|
||||
container tag from changing without an add-on review and version bump.
|
||||
The image is built from the upstream release named by `ARG BUILD_UPSTREAM` in
|
||||
the Dockerfile, downloaded as the matching `v<version>` source tarball. The
|
||||
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
|
||||
add-on uses its own `2.x` version series while the replacement upstream uses a
|
||||
`1.x` version series. An automatic replacement would risk a Home Assistant
|
||||
version regression and would not safely update the pinned commit. A maintainer
|
||||
upstream update must therefore update `UPSTREAM_REF`, `upstream_version`, the
|
||||
add-on version, and `CHANGELOG.md` together.
|
||||
A release tag is a mutable reference. Rebuilding the same `BUILD_UPSTREAM`
|
||||
installs whatever that tag points at, so an upstream tag that is force-moved or
|
||||
deleted would change or fail the build without an add-on change. That is the
|
||||
accepted cost of automatic tracking, and it is the same trade-off every other
|
||||
automatically updated add-on in this repository makes; the previous commit pin
|
||||
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
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
arch:
|
||||
- aarch64
|
||||
- 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:
|
||||
- /dev/dri
|
||||
- /dev/dri/card0
|
||||
@@ -96,5 +96,5 @@ schema:
|
||||
slug: free_games_claimer
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "2.0.1"
|
||||
version: "2.1.0"
|
||||
webui: "[PROTO:ssl]://[HOST]:[PORT:6080]"
|
||||
|
||||
@@ -36,6 +36,12 @@ set -a
|
||||
source "${RUNTIME_CONFIG}"
|
||||
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
|
||||
# upgrade from the previous add-on, even though the new upstream defaults to 7080.
|
||||
if [ -n "${NOVNC_PORT:-}" ] && [ "${NOVNC_PORT}" != "6080" ]; then
|
||||
|
||||
@@ -10,12 +10,17 @@ TIMEOUT=60
|
||||
LOGIN_TIMEOUT=180
|
||||
VNC_LOGIN_TIMEOUT=180
|
||||
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
|
||||
|
||||
# Used only when RUN_ONCE is disabled in the add-on options.
|
||||
SCHEDULER_HOURS=12
|
||||
# SCHEDULER_TIMEZONE=UTC
|
||||
# SCHEDULER_FIXED_TIMES=17:00,21:30
|
||||
|
||||
# Common credentials can be used as fallbacks for all stores.
|
||||
# EMAIL=
|
||||
@@ -33,7 +38,27 @@ SCHEDULER_HOURS=12
|
||||
# GOG_PASSWORD=
|
||||
# STEAM_USERNAME=
|
||||
# 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)
|
||||
# NOTIFY=
|
||||
# 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
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -1,11 +1,13 @@
|
||||
{
|
||||
"dockerhub_by_date": false,
|
||||
"dockerhub_list_size": 2,
|
||||
"last_update": "17-07-2026",
|
||||
"paused": true,
|
||||
"github_exclude": "d",
|
||||
"github_fulltag": false,
|
||||
"last_update": "24-08-2026",
|
||||
"paused": false,
|
||||
"repository": "alexbelgium/hassio-addons",
|
||||
"slug": "free_games_claimer",
|
||||
"source": "github",
|
||||
"upstream_repo": "P-Adamiec/Free-Games-Claimer-Remaster",
|
||||
"upstream_version": "1.1"
|
||||
"upstream_version": "1.6"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
gitea/stats.png
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
grav/stats.png
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
immich/stats.png
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
inadyn/stats.png
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
joal/stats.png
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
joplin/stats.png
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
kometa/stats.png
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
komga/stats.png
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
lidarr/stats.png
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.4 KiB |