Merge remote-tracking branch 'origin/master' into create-pull-request/patch-1788655917
# Conflicts: # .claude/skills/hassio-addon-workflow/scripts/pr_review.sh Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
@@ -254,13 +254,38 @@ without your involvement — another reason a shared checkout goes stale mid-tas
|
||||
**Reviewers**: CodeRabbit (deepest — often runs scripts to prove a claim; reviews ~9 minutes
|
||||
after the PR opens, or on `@coderabbitai review`), chatgpt-codex-connector, Copilot, Codacy.
|
||||
|
||||
**Codacy `action_required` is this repo's normal state.** Other open PRs show the same. It
|
||||
exposes no annotations via the API, so its findings are only visible in the maintainer's Codacy
|
||||
account. Note it and move on rather than guessing.
|
||||
**Codacy is red on essentially every add-on PR and gates nothing.** `gh pr checks` reports it as
|
||||
`fail` (older runs showed `action_required`); #3019, #3044 and #3050 all merged with it failing,
|
||||
and `master` carries no branch protection, so no check is required in the GitHub sense. It exposes
|
||||
no annotations via the API, so its findings are only visible in the maintainer's Codacy account.
|
||||
Note it and move on rather than guessing. `pr_review.sh watch` therefore prints it every poll but
|
||||
keeps it out of the verdict — the one check on that list, which is a denylist of known noise, not
|
||||
an allowlist of gates, so a job added to CI later counts as blocking until someone exempts it.
|
||||
|
||||
**Resolving a review thread requires GraphQL** (`resolveReviewThread`); the REST API cannot do it.
|
||||
`scripts/pr_review.sh` wraps fetch / reply / resolve.
|
||||
|
||||
**`gh pr checks` output is TAB-separated, and every blocking gate here has spaces in its name.**
|
||||
Parsing it with awk's default field splitting truncates each check to its first word and reads the
|
||||
wrong column as the state: `Codacy Static Code Analysis<TAB>fail` becomes `Codacy=Static`, and
|
||||
`Test addon build (wger)<TAB>pending` becomes `Test=addon`. A `case` over that string then matches
|
||||
neither `*fail*` nor `*pending*` and falls through to the "all passing" branch — the failure mode
|
||||
that makes a CI-reporting command lie. `pr_review.sh watch` called #3044 green while Codacy was
|
||||
red, and on #3042 printed "settled — all passing" while the HA add-on linter was failing; it would
|
||||
also have called a build that had not started a pass. Use `awk -F'\t'`, judge the state column
|
||||
alone (never the joined `name=state` text, or a check named `flaky-fail-detector` reads as a
|
||||
failure), and treat an unrecognised state as a failure instead of letting it reach the passing
|
||||
branch. Fixed in #3052.
|
||||
|
||||
The TSV is gh's *non-TTY* renderer, which is what `$(gh pr checks ... | awk)` always gets; attached
|
||||
to a terminal the same command prints a coloured, aligned table with a summary line, so never
|
||||
sanity-check the format by eye in a shell and assume the script sees that. `gh pr checks --json`
|
||||
would be sturdier, and Copilot recommends it (#3052), but it does not exist before gh 2.36 and the
|
||||
add-on ships 2.23 — it fails with `unknown flag: --json`. The parse is therefore built to fail
|
||||
safe instead: states are allowlisted, so a header row would land in the failure branch and a
|
||||
space-aligned table would parse to zero rows and keep `watch` waiting. Either way it cannot
|
||||
return a false pass.
|
||||
|
||||
**CHANGELOG heading dates are ISO, whatever the bots' defaults say.** Match the format already in
|
||||
the add-on's file. Repo-wide that is `## <version> (YYYY-MM-DD)`: 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
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
# pr_review.sh resolve <PR> <THREAD_ID...|--all> --all = every unresolved, asks first
|
||||
# pr_review.sh watch <PR> [minutes] poll checks (run this backgrounded)
|
||||
#
|
||||
# watch exits 0 when every blocking check passed *or was skipped* — a PR touching no add-on
|
||||
# skips all three gates, and it says so — 1 on failure, 2 if it ran out of minutes. Codacy is
|
||||
# advisory here: printed every poll, excluded from the verdict.
|
||||
#
|
||||
# Reviewers seen here: coderabbitai (deepest; reviews ~9 min after open, or on
|
||||
# "@coderabbitai review"), chatgpt-codex-connector, Copilot, Codacy.
|
||||
#
|
||||
@@ -18,126 +22,146 @@ set -uo pipefail
|
||||
|
||||
REPO="${HASSIO_REPO:-}"
|
||||
[ -z "$REPO" ] && REPO=$(gh repo view --json nameWithOwner --jq .nameWithOwner 2> /dev/null)
|
||||
[ -z "$REPO" ] && {
|
||||
echo "cannot determine repo; set HASSIO_REPO=owner/name" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -z "$REPO" ] && { echo "cannot determine repo; set HASSIO_REPO=owner/name" >&2; exit 1; }
|
||||
echo "repo: $REPO" >&2
|
||||
CMD="${1:-}"
|
||||
PR="${2:-}"
|
||||
[ -z "$CMD" ] || [ -z "$PR" ] && {
|
||||
sed -n '2,16p' "$0" | sed 's/^# \?//'
|
||||
exit 1
|
||||
}
|
||||
CMD="${1:-}"; PR="${2:-}"
|
||||
[ -z "$CMD" ] || [ -z "$PR" ] && { sed -n '2,20p' "$0" | sed 's/^# \?//'; exit 1; }
|
||||
|
||||
case "$CMD" in
|
||||
list)
|
||||
echo "== inline comments on #$PR =="
|
||||
gh api "repos/$REPO/pulls/$PR/comments" --paginate \
|
||||
--jq 'sort_by(.created_at)[] | "=== [\(.id)] \(.user.login) | \(.path):\(.line // .original_line) ===\n\(.body)\n"'
|
||||
echo "== review bodies =="
|
||||
gh api "repos/$REPO/pulls/$PR/reviews" \
|
||||
--jq '.[] | select(.body != "") | "--- \(.user.login) (\(.state)) ---\n\(.body[0:4000])\n"'
|
||||
;;
|
||||
status)
|
||||
gh pr checks "$PR" 2>&1 | head -15
|
||||
echo
|
||||
gh api graphql -f query="{repository(owner:\"${REPO%%/*}\",name:\"${REPO##*/}\"){pullRequest(number:$PR){reviewThreads(first:50){nodes{id isResolved path comments(first:1){nodes{author{login}}}}}}}}" \
|
||||
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | "\(if .isResolved then "resolved" else "OPEN " end) \(.id) \(.comments.nodes[0].author.login) \(.path)"'
|
||||
;;
|
||||
reply)
|
||||
ID="${3:?comment id}"
|
||||
BODY="${4:?text or @file}"
|
||||
if [ "${BODY#@}" != "$BODY" ]; then
|
||||
out=$(gh api "repos/$REPO/pulls/$PR/comments/$ID/replies" -F body=@"${BODY#@}" --jq '.id' 2>&1)
|
||||
rc=$?
|
||||
else
|
||||
out=$(gh api "repos/$REPO/pulls/$PR/comments/$ID/replies" -f body="$BODY" --jq '.id' 2>&1)
|
||||
rc=$?
|
||||
list)
|
||||
echo "== inline comments on #$PR =="
|
||||
gh api "repos/$REPO/pulls/$PR/comments" --paginate \
|
||||
--jq 'sort_by(.created_at)[] | "=== [\(.id)] \(.user.login) | \(.path):\(.line // .original_line) ===\n\(.body)\n"'
|
||||
echo "== review bodies =="
|
||||
gh api "repos/$REPO/pulls/$PR/reviews" \
|
||||
--jq '.[] | select(.body != "") | "--- \(.user.login) (\(.state)) ---\n\(.body[0:4000])\n"'
|
||||
;;
|
||||
status)
|
||||
gh pr checks "$PR" 2>&1 | head -15
|
||||
echo
|
||||
gh api graphql -f query="{repository(owner:\"${REPO%%/*}\",name:\"${REPO##*/}\"){pullRequest(number:$PR){reviewThreads(first:50){nodes{id isResolved path comments(first:1){nodes{author{login}}}}}}}}" \
|
||||
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | "\(if .isResolved then "resolved" else "OPEN " end) \(.id) \(.comments.nodes[0].author.login) \(.path)"'
|
||||
;;
|
||||
reply)
|
||||
ID="${3:?comment id}"; BODY="${4:?text or @file}"
|
||||
if [ "${BODY#@}" != "$BODY" ]; then
|
||||
out=$(gh api "repos/$REPO/pulls/$PR/comments/$ID/replies" -F body=@"${BODY#@}" --jq '.id' 2>&1)
|
||||
rc=$?
|
||||
else
|
||||
out=$(gh api "repos/$REPO/pulls/$PR/comments/$ID/replies" -f body="$BODY" --jq '.id' 2>&1)
|
||||
rc=$?
|
||||
fi
|
||||
# Silently "succeeding" here is worse than failing: a later session reads the transcript and
|
||||
# believes a reviewer was answered when they were not.
|
||||
if [ "$rc" -eq 0 ] && [ -n "$out" ]; then
|
||||
echo "replied to $ID (comment $out)"
|
||||
else
|
||||
echo "FAILED to reply to $ID: $out" >&2
|
||||
echo " (top-level review bodies have different ids and cannot take replies here)" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
resolve)
|
||||
shift 2
|
||||
ids="$*"
|
||||
if [ "${ids:-}" = "--all" ]; then
|
||||
echo "About to resolve EVERY unresolved thread. Only do this if you have read and"
|
||||
echo "answered each one — a resolved-but-wrong thread is worse than an open one."
|
||||
gh api graphql -f query="{repository(owner:\"${REPO%%/*}\",name:\"${REPO##*/}\"){pullRequest(number:$PR){reviewThreads(first:100){nodes{isResolved path comments(first:1){nodes{author{login} body}}}}}}}" \
|
||||
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | " - \(.comments.nodes[0].author.login) \(.path): \(.comments.nodes[0].body[0:90])"'
|
||||
printf 'Type "yes" to resolve all: '; read -r ok
|
||||
[ "$ok" = "yes" ] || { echo "aborted"; exit 1; }
|
||||
ids=""
|
||||
elif [ -z "$ids" ]; then
|
||||
echo "usage: pr_review.sh resolve <PR> <THREAD_ID...> (or --all, with confirmation)" >&2
|
||||
echo "resolve each thread as you answer it; get ids from: pr_review.sh status $PR" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$ids" ]; then
|
||||
ids=$(gh api graphql -f query="{repository(owner:\"${REPO%%/*}\",name:\"${REPO##*/}\"){pullRequest(number:$PR){reviewThreads(first:50){nodes{id isResolved}}}}}" \
|
||||
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .id')
|
||||
fi
|
||||
[ -z "$ids" ] && { echo "nothing unresolved"; exit 0; }
|
||||
rfail=0
|
||||
for id in $ids; do
|
||||
r=$(gh api graphql -f query="mutation{resolveReviewThread(input:{threadId:\"$id\"}){thread{isResolved}}}" \
|
||||
--jq '.data.resolveReviewThread.thread.isResolved' 2>&1)
|
||||
echo " $id -> $r"
|
||||
[ "$r" = "true" ] || rfail=1
|
||||
done
|
||||
# exiting 0 on a failed mutation would let a session believe threads were resolved
|
||||
exit "$rfail"
|
||||
;;
|
||||
watch)
|
||||
MINS="${3:-180}" # the addon build alone has taken ~3h; 20 was far too short
|
||||
# Checks that are red on essentially every add-on PR here and gate nothing: master carries no
|
||||
# branch protection, and #3019, #3044 and #3050 all merged with Codacy failing. They are kept
|
||||
# out of the verdict but always printed, so the reader still sees them and can judge. This is
|
||||
# deliberately a denylist of known noise, not an allowlist of blocking checks — a job added to
|
||||
# CI later counts as blocking until someone puts it here on purpose.
|
||||
ADVISORY_CHECKS="Codacy Static Code Analysis" # one per line if more are ever added
|
||||
wfail=2 # not 0: running out of minutes with checks still pending is not a pass
|
||||
c=""; bstates=""; adv=""
|
||||
for i in $(seq 1 "$MINS"); do
|
||||
# gh pr checks emits TAB-separated columns with no header when its output is not a TTY,
|
||||
# which inside this $(... | awk) it never is. (Attached to a terminal it prints a wholly
|
||||
# different ANSI table; --json would be sturdier still but does not exist before gh 2.36,
|
||||
# and 2.23 ships here.) Every blocking gate has spaces in its name — "Addon linting
|
||||
# (wger)", "Test addon build (wger)" — so awk's default separator split them on
|
||||
# whitespace: "Codacy Static Code Analysis<TAB>fail" became "Codacy=Static" and the state
|
||||
# column was never read at all. watch printed "all passing" on a red #3044 and on #3042
|
||||
# with the linter failing, and could not see a pending build either.
|
||||
# If that format ever does change, the allowlist below fails safe rather than passing: a
|
||||
# header row lands in the failure branch, and a space-aligned table parses to no rows,
|
||||
# which keeps watch waiting instead of returning 0.
|
||||
rows=$(gh pr checks "$PR" 2> /dev/null | awk -F'\t' -v ADV="$ADVISORY_CHECKS" '
|
||||
BEGIN { n = split(ADV, a, "\n"); for (j = 1; j <= n; j++) adv[a[j]] = 1 }
|
||||
NF >= 2 { print (($1 in adv) ? "A" : "B") "\t" $1 "=" $2 "\t" $2 }')
|
||||
if [ -z "$rows" ]; then
|
||||
# Normal in the first minutes after `gh pr create`, and also whenever gh errors.
|
||||
# Calling that "settled" would report success for checks that never ran.
|
||||
echo "[$i] no checks reported yet (gh returned nothing) — still waiting"
|
||||
sleep 60; continue
|
||||
fi
|
||||
# Silently "succeeding" here is worse than failing: a later session reads the transcript and
|
||||
# believes a reviewer was answered when they were not.
|
||||
if [ "$rc" -eq 0 ] && [ -n "$out" ]; then
|
||||
echo "replied to $ID (comment $out)"
|
||||
else
|
||||
echo "FAILED to reply to $ID: $out" >&2
|
||||
echo " (top-level review bodies have different ids and cannot take replies here)" >&2
|
||||
exit 1
|
||||
c=$(printf '%s\n' "$rows" | cut -f2 | tr '\n' ' ')
|
||||
echo "[$i] $c"
|
||||
# Judge the state column only, never the joined name=state line: a check whose NAME
|
||||
# contains "fail" must not read as a failure.
|
||||
bstates=$(printf '%s\n' "$rows" | awk -F'\t' '$1 == "B" { print $3 }' | tr '\n' ' ')
|
||||
adv=$(printf '%s\n' "$rows" | awk -F'\t' '$1 == "A" { print $2 }' | tr '\n' ' ')
|
||||
if [ -z "$bstates" ]; then
|
||||
echo " only advisory checks have reported — no blocking check has run yet"
|
||||
sleep 60; continue
|
||||
fi
|
||||
;;
|
||||
resolve)
|
||||
shift 2
|
||||
ids="$*"
|
||||
if [ "${ids:-}" = "--all" ]; then
|
||||
echo "About to resolve EVERY unresolved thread. Only do this if you have read and"
|
||||
echo "answered each one — a resolved-but-wrong thread is worse than an open one."
|
||||
gh api graphql -f query="{repository(owner:\"${REPO%%/*}\",name:\"${REPO##*/}\"){pullRequest(number:$PR){reviewThreads(first:100){nodes{isResolved path comments(first:1){nodes{author{login} body}}}}}}}" \
|
||||
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | " - \(.comments.nodes[0].author.login) \(.path): \(.comments.nodes[0].body[0:90])"'
|
||||
printf 'Type "yes" to resolve all: '
|
||||
read -r ok
|
||||
[ "$ok" = "yes" ] || {
|
||||
echo "aborted"
|
||||
exit 1
|
||||
}
|
||||
ids=""
|
||||
elif [ -z "$ids" ]; then
|
||||
echo "usage: pr_review.sh resolve <PR> <THREAD_ID...> (or --all, with confirmation)" >&2
|
||||
echo "resolve each thread as you answer it; get ids from: pr_review.sh status $PR" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$ids" ]; then
|
||||
ids=$(gh api graphql -f query="{repository(owner:\"${REPO%%/*}\",name:\"${REPO##*/}\"){pullRequest(number:$PR){reviewThreads(first:50){nodes{id isResolved}}}}}" \
|
||||
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .id')
|
||||
fi
|
||||
[ -z "$ids" ] && {
|
||||
echo "nothing unresolved"
|
||||
exit 0
|
||||
}
|
||||
rfail=0
|
||||
for id in $ids; do
|
||||
r=$(gh api graphql -f query="mutation{resolveReviewThread(input:{threadId:\"$id\"}){thread{isResolved}}}" \
|
||||
--jq '.data.resolveReviewThread.thread.isResolved' 2>&1)
|
||||
echo " $id -> $r"
|
||||
[ "$r" = "true" ] || rfail=1
|
||||
done
|
||||
# exiting 0 on a failed mutation would let a session believe threads were resolved
|
||||
exit "$rfail"
|
||||
;;
|
||||
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
|
||||
# Normal in the first minutes after `gh pr create`, and also whenever gh errors.
|
||||
# Calling that "settled" would report success for checks that never ran.
|
||||
echo "[$i] no checks reported yet (gh returned nothing) — still waiting"
|
||||
sleep 60
|
||||
continue
|
||||
fi
|
||||
echo "[$i] $c"
|
||||
case "$c" in
|
||||
*pending*) sleep 60 ;;
|
||||
*fail* | *error* | *cancel*)
|
||||
echo "settled — with FAILURES (see above)"
|
||||
wfail=1
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "settled — all passing"
|
||||
wfail=0
|
||||
break
|
||||
;;
|
||||
# Allowlist the good states rather than denylisting the bad ones: an unrecognised state
|
||||
# must land in the failure branch, because falling through to "passing" is this command's
|
||||
# worst outcome.
|
||||
nbad=0; npend=0
|
||||
for s in $bstates; do
|
||||
case "$s" in
|
||||
pass | skipping) ;;
|
||||
pending) npend=$((npend + 1)) ;;
|
||||
*) nbad=$((nbad + 1)) ;;
|
||||
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"
|
||||
;;
|
||||
*)
|
||||
echo "unknown: $CMD"
|
||||
exit 1
|
||||
;;
|
||||
if [ "$nbad" -gt 0 ]; then
|
||||
echo "settled — blocking checks FAILED:"
|
||||
printf '%s\n' "$rows" |
|
||||
awk -F'\t' '$1 == "B" && $3 != "pass" && $3 != "skipping" && $3 != "pending" { print " " $2 }'
|
||||
wfail=1; break
|
||||
elif [ "$npend" -gt 0 ]; then
|
||||
sleep 60; continue
|
||||
else
|
||||
echo "settled — blocking checks passing"; wfail=0; break
|
||||
fi
|
||||
done
|
||||
[ "$wfail" -eq 2 ] && echo "gave up after ${MINS}m, checks still unsettled — NOT a pass"
|
||||
# Printed on pass and on failure alike: it is excluded from the verdict, not hidden.
|
||||
[ -n "$adv" ] && echo " advisory (non-blocking, not counted in the verdict): $adv"
|
||||
# A PR touching no */config.* skips the CHANGELOG, linter and build jobs outright (#3018).
|
||||
case " $bstates " 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"
|
||||
;;
|
||||
*) echo "unknown: $CMD"; exit 1 ;;
|
||||
esac
|
||||
|
||||
6
.github/stargazer_countries.csv
vendored
@@ -418,6 +418,7 @@ KuchenKavalier,,2026-08-10
|
||||
KuerbisK,,2026-08-10
|
||||
Kvasenok,,2026-08-10
|
||||
KyleGolfer,,2026-08-10
|
||||
L00PERY,,2026-09-06
|
||||
L0rdShrek,Germany,
|
||||
L1nKinc,Germany,
|
||||
LaLaBer,,2026-08-10
|
||||
@@ -444,6 +445,7 @@ LoginByCall,,2026-08-10
|
||||
Lolekpolek,,2026-08-10
|
||||
LonelySoul7X,,2026-08-10
|
||||
Loong-He,China,2026-08-30
|
||||
Loong-OvO,China,2026-09-06
|
||||
Lorsel,Italy,
|
||||
Luca2165801154,,2026-08-10
|
||||
Lucius-Waverly,,2026-08-16
|
||||
@@ -1390,6 +1392,7 @@ flostingapplesauce,,2026-08-10
|
||||
flozi00,Germany,
|
||||
flue17,,2026-08-10
|
||||
fmcglinn,Australia,
|
||||
foodstampou812,United States,2026-09-06
|
||||
forming,,2026-08-10
|
||||
forreggbor,Hungary,
|
||||
foundbobby,United States,
|
||||
@@ -1834,6 +1837,7 @@ m1kethai,Canada,
|
||||
m23l,,2026-08-10
|
||||
m2sh,"Iran, Islamic Republic of",
|
||||
m4rcSA,,2026-08-10
|
||||
m4rkolson,United States,2026-09-06
|
||||
mProwler,United States,
|
||||
mStrangers,,2026-08-10
|
||||
mabt,,2026-08-10
|
||||
@@ -2085,6 +2089,7 @@ p0wertiger,Poland,
|
||||
paalwilliams,Germany,
|
||||
pace6666,,2026-08-10
|
||||
pafnow,France,
|
||||
pahanitsch,Germany,2026-09-06
|
||||
pandabreads,,2026-08-10
|
||||
pankaj151,India,
|
||||
papafrank66,,2026-08-10
|
||||
@@ -2664,6 +2669,7 @@ yycsdm,,2026-08-10
|
||||
yyll2233,,2026-08-10
|
||||
z2833,,2026-08-10
|
||||
z307917424,,2026-08-10
|
||||
zacharee,Panama,2026-09-06
|
||||
zachgilliam,United States,
|
||||
zanyraspi,,2026-08-10
|
||||
zdaar,,2026-08-10
|
||||
|
||||
|
BIN
.github/stargazer_map.png
vendored
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 66 KiB |
BIN
.github/stats.png
vendored
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
.github/stats_addons.png
vendored
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.6 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.2 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
baikal/stats.png
|
Before Width: | Height: | Size: 3.8 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.1 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.5 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: 2.9 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.7 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.7 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: 2.6 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.5 KiB |
|
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.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.3 KiB |
BIN
codex/stats.png
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
emby/stats.png
|
Before Width: | Height: | Size: 3.0 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.5 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
ente/stats.png
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.8 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: 3.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.1 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: 3.4 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
gitea/stats.png
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
grav/stats.png
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
immich/stats.png
|
Before Width: | Height: | Size: 2.9 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.0 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: 3.1 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
inadyn/stats.png
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.5 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.4 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
joplin/stats.png
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
kometa/stats.png
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
komga/stats.png
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
lidarr/stats.png
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
mealie/stats.png
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
monica/stats.png
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
mylar3/stats.png
|
Before Width: | Height: | Size: 2.8 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: 2.9 KiB After Width: | Height: | Size: 1.5 KiB |
|
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.5 KiB |
BIN
nzbget/stats.png
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,3 +1,6 @@
|
||||
## 0.6.1.1 (2026-09-07)
|
||||
- Fix the add-on refusing to stop: Home Assistant reported an Error status after a few seconds and the container kept running and serving the web UI. `cont-init.d/99-run.sh` started nginx in the foreground, and `ha_entrypoint.sh` runs every cont-init script in the foreground, so the entrypoint never reached the point where it installs the `terminate()` handler that forwards SIGTERM to the application on shutdown. The application is now started in the background, and `init: false` makes the entrypoint run as PID 1 so the orphaned process is reparented to it and receives that signal. Closes #3049.
|
||||
|
||||
|
||||
## 0.6.1 (2025-11-18)
|
||||
- Added `env_vars` option to allow passing custom environment variables from the add-on configuration.
|
||||
|
||||
@@ -5,6 +5,7 @@ description:
|
||||
Self-hosted collection of powerful web-based tools for everyday tasks.
|
||||
No ads, no tracking, just fast, accessible utilities right from your browser
|
||||
image: ghcr.io/alexbelgium/omni-tools-{arch}
|
||||
init: false
|
||||
map:
|
||||
- addon_config:rw
|
||||
name: Omni Tools
|
||||
@@ -20,5 +21,5 @@ schema:
|
||||
value: str?
|
||||
slug: omni-tools
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: 0.6.1
|
||||
version: 0.6.1.1
|
||||
webui: "[PROTO:ssl]://[HOST]:[PORT:80]"
|
||||
|
||||
@@ -7,4 +7,10 @@
|
||||
|
||||
# Start omni-tools container content
|
||||
bashio::log.info "Starting application"
|
||||
/./docker-entrypoint.sh nginx -g "daemon off;" &> /proc/1/fd/1
|
||||
# Backgrounded on purpose. ha_entrypoint.sh runs every cont-init.d script in the
|
||||
# foreground, so launching nginx here in the foreground never lets it reach the
|
||||
# terminate() handler that forwards SIGTERM on shutdown -- the add-on then could
|
||||
# not be stopped at all (#3049). Backgrounded, this script returns, nginx is
|
||||
# reparented to the entrypoint as PID 1, and terminate() signals it directly.
|
||||
# Requires init: false in config.yaml, which is what makes the entrypoint PID 1.
|
||||
/./docker-entrypoint.sh nginx -g "daemon off;" &> /proc/1/fd/1 &
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
piwigo/stats.png
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
plex/stats.png
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
radarr/stats.png
|
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.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.5 KiB |