Compare commits
2 Commits
create-pul
...
ai-fix/wge
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acfa7ec0fa | ||
|
|
c30629aa8b |
@@ -254,38 +254,13 @@ 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 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.
|
||||
**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.
|
||||
|
||||
**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
|
||||
|
||||
@@ -30,14 +30,8 @@ VAR="${1:?usage: env_trace.sh <VAR> [process-name-or-pid]}"
|
||||
TARGET="${2:-}"
|
||||
# VAR is interpolated into grep/sed patterns below — restrict it to a valid env var name
|
||||
case "$VAR" in
|
||||
[A-Za-z_]*) [ -z "${VAR//[A-Za-z0-9_]/}" ] || {
|
||||
echo "invalid env var name: $VAR" >&2
|
||||
exit 1
|
||||
} ;;
|
||||
*)
|
||||
echo "invalid env var name: $VAR" >&2
|
||||
exit 1
|
||||
;;
|
||||
[A-Za-z_]*) [ -z "${VAR//[A-Za-z0-9_]/}" ] || { echo "invalid env var name: $VAR" >&2; exit 1; } ;;
|
||||
*) echo "invalid env var name: $VAR" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "== tracing ${VAR} =="
|
||||
@@ -86,8 +80,7 @@ echo "3. s6 container_environment (only read by services using #!/usr/bin/with-c
|
||||
seen3=0
|
||||
for d in /var/run/s6/container_environment /run/s6/container_environment; do
|
||||
if [ -f "$d/$VAR" ]; then
|
||||
echo " $d/$VAR = [$(cat "$d/$VAR")]"
|
||||
seen3=1
|
||||
echo " $d/$VAR = [$(cat "$d/$VAR")]"; seen3=1
|
||||
fi
|
||||
done
|
||||
[ "$seen3" -eq 0 ] && echo " not present in either envdir"
|
||||
@@ -129,8 +122,8 @@ else
|
||||
done
|
||||
fi
|
||||
# What it was actually launched with beats any theory about its environment.
|
||||
tr '\0' '\n' < "/proc/$pid/cmdline" 2> /dev/null | tail -n +2 \
|
||||
| grep -iE "res|screen|${VAR}" | head -3 | sed 's/^/ argv: /'
|
||||
tr '\0' '\n' < "/proc/$pid/cmdline" 2> /dev/null | tail -n +2 |
|
||||
grep -iE "res|screen|${VAR}" | head -3 | sed 's/^/ argv: /'
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
# 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.
|
||||
#
|
||||
@@ -25,7 +21,7 @@ REPO="${HASSIO_REPO:-}"
|
||||
[ -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,20p' "$0" | sed 's/^# \?//'; exit 1; }
|
||||
[ -z "$CMD" ] || [ -z "$PR" ] && { sed -n '2,16p' "$0" | sed 's/^# \?//'; exit 1; }
|
||||
|
||||
case "$CMD" in
|
||||
list)
|
||||
@@ -94,72 +90,25 @@ resolve)
|
||||
;;
|
||||
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
|
||||
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
|
||||
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
|
||||
# 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)) ;;
|
||||
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 ;;
|
||||
esac
|
||||
done
|
||||
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
|
||||
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"
|
||||
;;
|
||||
|
||||
@@ -47,10 +47,7 @@ if [ -n "${BUILD_VERSION:-}" ]; then
|
||||
if [ -z "$SLUG" ] && [ -n "${HOSTNAME:-}" ]; then
|
||||
base=$(printf '%s' "$HOSTNAME" | sed 's/^[0-9a-f]\{8\}-//')
|
||||
for cand in "$(printf '%s' "$base" | tr '-' '_')" "$base"; do
|
||||
[ -f "$REPO/$cand/config.yaml" ] && {
|
||||
SLUG="$cand"
|
||||
break
|
||||
}
|
||||
[ -f "$REPO/$cand/config.yaml" ] && { SLUG="$cand"; break; }
|
||||
done
|
||||
[ -z "$SLUG" ] && SLUG="$base"
|
||||
fi
|
||||
@@ -72,8 +69,8 @@ if [ -n "${BUILD_VERSION:-}" ]; then
|
||||
else
|
||||
echo " MISMATCH — this branch is NOT what is running."
|
||||
git fetch origin master --quiet 2> /dev/null
|
||||
master=$(git show origin/master:"$SLUG/config.yaml" 2> /dev/null \
|
||||
| grep -E '^version:' | head -1 | tr -d "\"'" | awk '{print $2}')
|
||||
master=$(git show origin/master:"$SLUG/config.yaml" 2> /dev/null |
|
||||
grep -E '^version:' | head -1 | tr -d "\"'" | awk '{print $2}')
|
||||
echo " origin/master version = ${master:-unknown}"
|
||||
echo " -> work from origin/master; analysing this branch will mislead you."
|
||||
echo
|
||||
|
||||
@@ -13,31 +13,22 @@ set -uo pipefail
|
||||
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
|
||||
}
|
||||
[ "${ADDON:-}" = "--vs-master" ] && { ADDON=""; set -- --vs-master; }
|
||||
VS_MASTER=false
|
||||
for a in "$@"; do [ "$a" = "--vs-master" ] && VS_MASTER=true; done
|
||||
|
||||
if [ -z "$ADDON" ]; then
|
||||
mapfile -t _dirs < <(git diff --name-only origin/master...HEAD 2> /dev/null \
|
||||
| cut -d/ -f1 | sort -u | grep -vE '^\.')
|
||||
mapfile -t _dirs < <(git diff --name-only origin/master...HEAD 2> /dev/null |
|
||||
cut -d/ -f1 | sort -u | grep -vE '^\.' )
|
||||
if [ "${#_dirs[@]}" -gt 1 ]; then
|
||||
echo "several changed dirs: ${_dirs[*]}"
|
||||
echo "pass one explicitly: validate.sh <addon-dir>"
|
||||
exit 1
|
||||
echo "pass one explicitly: validate.sh <addon-dir>"; exit 1
|
||||
fi
|
||||
ADDON="${_dirs[0]:-}"
|
||||
fi
|
||||
[ -z "$ADDON" ] && {
|
||||
echo "usage: validate.sh <addon-dir> [--vs-master]"
|
||||
exit 1
|
||||
}
|
||||
[ -z "$ADDON" ] && { echo "usage: validate.sh <addon-dir> [--vs-master]"; exit 1; }
|
||||
git rev-parse --verify origin/master > /dev/null 2>&1 || {
|
||||
echo "origin/master missing — run: git fetch origin master"
|
||||
exit 1
|
||||
}
|
||||
echo "origin/master missing — run: git fetch origin master"; exit 1; }
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
echo "== validating $ADDON =="
|
||||
|
||||
@@ -45,22 +36,14 @@ 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* ]]
|
||||
}
|
||||
is_execline() { local l; IFS= read -r l < "$1" 2> /dev/null; [[ $l == '#!'*execlineb* ]]; }
|
||||
|
||||
# Shell: bash -n then shellcheck -x (follows sourced files, as CI does). One list for both.
|
||||
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
|
||||
if ! out=$(bash -n "$f" 2>&1); then note "bash -n" "FAIL $f"; echo "$out" | sed 's/^/ /'; fail=1; fi
|
||||
done
|
||||
[ "$fail" -eq 0 ] && note "bash -n" "${#files[@]} file(s) checked"
|
||||
|
||||
@@ -82,16 +65,13 @@ command -v hadolint > /dev/null 2>&1 && [ -f "$ADDON/Dockerfile" ] && {
|
||||
|
||||
if [ -f "$ADDON/config.yaml" ]; then
|
||||
# path passed as argv, never interpolated into Python source
|
||||
python3 - "$ADDON/config.yaml" << 'PY' || {
|
||||
python3 - "$ADDON/config.yaml" <<'PY' || { note "config.yaml" "FAIL parse"; fail=1; }
|
||||
import yaml,sys
|
||||
d=yaml.safe_load(open(sys.argv[1]))
|
||||
print(' %-13s ok (version=%s, %d options)' % ('config.yaml', d.get('version'), len(d.get('options') or {})))
|
||||
missing=[k for k in (d.get('options') or {}) if k not in (d.get('schema') or {})]
|
||||
if missing: print(' %-13s options with no schema entry: %s' % ('WARN', missing)); sys.exit(0)
|
||||
PY
|
||||
note "config.yaml" "FAIL parse"
|
||||
fail=1
|
||||
}
|
||||
command -v yamllint > /dev/null 2>&1 && {
|
||||
yl=$(yamllint -f parsable "$ADDON/config.yaml" 2>&1 | grep -c .)
|
||||
note "yamllint" "$yl finding(s) (compare with --vs-master)"
|
||||
@@ -99,10 +79,7 @@ PY
|
||||
fi
|
||||
|
||||
while IFS= read -r f; do
|
||||
python3 -m py_compile "$f" 2> /dev/null || {
|
||||
note "py_compile" "FAIL $f"
|
||||
fail=1
|
||||
}
|
||||
python3 -m py_compile "$f" 2> /dev/null || { note "py_compile" "FAIL $f"; fail=1; }
|
||||
done < <(find "$ADDON" -type f -name '*.py' 2> /dev/null)
|
||||
|
||||
$VS_MASTER && command -v npx > /dev/null 2>&1 && [ -f "$ADDON/CHANGELOG.md" ] && {
|
||||
@@ -119,8 +96,7 @@ if git diff --name-only origin/master...HEAD 2> /dev/null | grep -Fxq "$ADDON/CH
|
||||
note "CHANGELOG" "updated"
|
||||
else
|
||||
# This one IS gated: onpr_check-pr.yaml exits 1 without it.
|
||||
note "CHANGELOG" "NOT UPDATED for $ADDON — CI hard-gates this"
|
||||
fail=1
|
||||
note "CHANGELOG" "NOT UPDATED for $ADDON — CI hard-gates this"; fail=1
|
||||
fi
|
||||
if git diff origin/master...HEAD -- "$ADDON/config.yaml" 2> /dev/null | grep -q '^+version:'; then
|
||||
note "version" "bumped"
|
||||
@@ -134,8 +110,7 @@ note "docker build" "NOT tested locally (dockerd unavailable) — CI is the only
|
||||
if $VS_MASTER; then
|
||||
echo
|
||||
echo "== findings ADDED by this diff (pre-existing ones filtered out) =="
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
|
||||
added=0
|
||||
# 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.
|
||||
@@ -150,26 +125,14 @@ if $VS_MASTER; then
|
||||
# cancel it out and report a false clean.
|
||||
case "$f" in
|
||||
*.sh | *autostart | */run | */finish)
|
||||
command -v shellcheck > /dev/null 2>&1 || {
|
||||
echo " $f: SKIPPED (shellcheck not installed)"
|
||||
continue
|
||||
}
|
||||
cmd() { shellcheck -x -f gcc "$1" 2>&1 | sed 's/^[^:]*:[0-9]*:[0-9]*://'; }
|
||||
;;
|
||||
command -v shellcheck > /dev/null 2>&1 || { echo " $f: SKIPPED (shellcheck not installed)"; continue; }
|
||||
cmd() { shellcheck -x -f gcc "$1" 2>&1 | sed 's/^[^:]*:[0-9]*:[0-9]*://'; } ;;
|
||||
*.yaml | *.yml)
|
||||
command -v yamllint > /dev/null 2>&1 || {
|
||||
echo " $f: SKIPPED (yamllint not installed)"
|
||||
continue
|
||||
}
|
||||
cmd() { yamllint -f parsable "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*:[0-9]*//'; }
|
||||
;;
|
||||
command -v yamllint > /dev/null 2>&1 || { echo " $f: SKIPPED (yamllint not installed)"; continue; }
|
||||
cmd() { yamllint -f parsable "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*:[0-9]*//'; } ;;
|
||||
*Dockerfile)
|
||||
command -v hadolint > /dev/null 2>&1 || {
|
||||
echo " $f: SKIPPED (hadolint not installed)"
|
||||
continue
|
||||
}
|
||||
cmd() { hadolint "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*//'; }
|
||||
;;
|
||||
command -v hadolint > /dev/null 2>&1 || { echo " $f: SKIPPED (hadolint not installed)"; continue; }
|
||||
cmd() { hadolint "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*//'; } ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
b=$(cmd "$tmp/base" | sort)
|
||||
@@ -178,8 +141,7 @@ if $VS_MASTER; then
|
||||
[ -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
|
||||
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)"
|
||||
|
||||
10
.github/scripts/ai_triage_context.sh
vendored
@@ -28,10 +28,7 @@ if [ -n "$RAW" ]; then
|
||||
# Directory list without checking out any of them.
|
||||
git ls-tree -d --name-only HEAD > "$OUT/dirs.txt"
|
||||
for guess in "$CAND" "${CAND//_/-}" "${CAND//_/.}"; do
|
||||
if grep -qxF "$guess" "$OUT/dirs.txt"; then
|
||||
ADDON="$guess"
|
||||
break
|
||||
fi
|
||||
if grep -qxF "$guess" "$OUT/dirs.txt"; then ADDON="$guess"; break; fi
|
||||
done
|
||||
# Separator-insensitive exact match: a title like "[Calibre-web]" (hyphen)
|
||||
# against a directory named calibre_web (underscore) matches neither exact
|
||||
@@ -41,10 +38,7 @@ if [ -n "$RAW" ]; then
|
||||
if [ -z "$ADDON" ]; then
|
||||
CAND_STRIPPED=$(tr -d '_.-' <<<"$CAND")
|
||||
while IFS= read -r dir; do
|
||||
if [ "$(tr -d '_.-' <<< "$dir")" = "$CAND_STRIPPED" ]; then
|
||||
ADDON="$dir"
|
||||
break
|
||||
fi
|
||||
if [ "$(tr -d '_.-' <<<"$dir")" = "$CAND_STRIPPED" ]; then ADDON="$dir"; break; fi
|
||||
done < "$OUT/dirs.txt"
|
||||
fi
|
||||
# Last resort: longest directory name contained in the candidate.
|
||||
|
||||
6
.github/stargazer_countries.csv
vendored
@@ -418,7 +418,6 @@ 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
|
||||
@@ -445,7 +444,6 @@ 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
|
||||
@@ -1392,7 +1390,6 @@ 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,
|
||||
@@ -1837,7 +1834,6 @@ 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
|
||||
@@ -2089,7 +2085,6 @@ 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
|
||||
@@ -2669,7 +2664,6 @@ 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: 66 KiB After Width: | Height: | Size: 64 KiB |
BIN
.github/stats.png
vendored
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.9 KiB |
BIN
.github/stats_addons.png
vendored
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 10 KiB |
@@ -19,8 +19,8 @@ if ! bashio::supervisor.ping 2> /dev/null; then
|
||||
if [ ! -f /usr/bin/bashio ]; then
|
||||
cp -rf /usr/local/lib/bashio-standalone.sh /usr/bin/bashio
|
||||
fi
|
||||
grep -rl "^#!.*bashio" /etc \
|
||||
| while IFS= read -r f; do
|
||||
grep -rl "^#!.*bashio" /etc |
|
||||
while IFS= read -r f; do
|
||||
grep -qF "source /usr/local/lib/bashio-standalone.sh" "$f" && continue
|
||||
sed -i '1a source /usr/local/lib/bashio-standalone.sh' "$f"
|
||||
done
|
||||
|
||||
@@ -152,8 +152,8 @@ if [[ "${1:-}" == "--self-test" ]]; then
|
||||
done
|
||||
fi
|
||||
|
||||
[[ "$self_test_rc" -eq 0 ]] \
|
||||
&& echo "${#self_test_values[@]} values round-tripped unchanged (export block + dotenv)"
|
||||
[[ "$self_test_rc" -eq 0 ]] &&
|
||||
echo "${#self_test_values[@]} values round-tripped unchanged (export block + dotenv)"
|
||||
exit "$self_test_rc"
|
||||
fi
|
||||
|
||||
@@ -193,10 +193,7 @@ SECRETSOURCE=""
|
||||
|
||||
resolve_secret() {
|
||||
local v="$1" name line
|
||||
[[ "$v" =~ ^[[:space:]]*\!secret[[:space:]]+(.+)$ ]] || {
|
||||
printf '%s' "$v"
|
||||
return
|
||||
}
|
||||
[[ "$v" =~ ^[[:space:]]*\!secret[[:space:]]+(.+)$ ]] || { printf '%s' "$v"; return; }
|
||||
name="${BASH_REMATCH[1]}"
|
||||
[[ -n "$SECRETSOURCE" ]] || bashio::exit.nok "Secrets not mounted"
|
||||
|
||||
|
||||
@@ -11,14 +11,8 @@ pick_exec_dir() {
|
||||
for d in /dev/shm /run /var/run /mnt /root /; do
|
||||
if [ -d "$d" ] && [ -w "$d" ]; then
|
||||
local t="${d%/}/.exec_test_$$"
|
||||
printf '#!/bin/sh\necho ok\n' > "$t" 2> /dev/null || {
|
||||
rm -f "$t" 2> /dev/null || true
|
||||
continue
|
||||
}
|
||||
chmod 700 "$t" 2> /dev/null || {
|
||||
rm -f "$t" 2> /dev/null || true
|
||||
continue
|
||||
}
|
||||
printf '#!/bin/sh\necho ok\n' >"$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
|
||||
chmod 700 "$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
|
||||
if "$t" >/dev/null 2>&1; then
|
||||
rm -f "$t" 2>/dev/null || true
|
||||
echo "$d"
|
||||
|
||||
@@ -46,8 +46,7 @@ _bashio_color() {
|
||||
}
|
||||
|
||||
_bashio_log() {
|
||||
local c="${1:-}"
|
||||
shift || true
|
||||
local c="${1:-}"; shift || true
|
||||
printf '%s%s%s\n' "$(_bashio_color "$c")" "$*" "$(_bashio_color reset)"
|
||||
}
|
||||
|
||||
@@ -247,10 +246,7 @@ bashio::info.supervisor() { printf '%s' "standalone"; }
|
||||
# -----------------------------------------------------------------------------
|
||||
bashio::config() {
|
||||
local key="${1:-}"
|
||||
[ -n "$key" ] || {
|
||||
printf '%s' ""
|
||||
return 0
|
||||
}
|
||||
[ -n "$key" ] || { printf '%s' ""; return 0; }
|
||||
|
||||
local v=""
|
||||
if _bashio_env_get "$key" >/dev/null 2>&1; then
|
||||
@@ -360,10 +356,7 @@ bashio::host.hostname() {
|
||||
# bashio::services.available "mqtt"
|
||||
bashio::services() {
|
||||
local svc="${1:-}" key="${2:-}"
|
||||
[ -n "$svc" ] && [ -n "$key" ] || {
|
||||
printf '%s' ""
|
||||
return 0
|
||||
}
|
||||
[ -n "$svc" ] && [ -n "$key" ] || { printf '%s' ""; return 0; }
|
||||
|
||||
local upper svc_upper var v=""
|
||||
upper="$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')"
|
||||
@@ -426,11 +419,7 @@ bashio::homeassistant.token() {
|
||||
# Exit helpers
|
||||
# -----------------------------------------------------------------------------
|
||||
bashio::exit.ok() { exit 0; }
|
||||
bashio::exit.nok() {
|
||||
local m="${1:-}"
|
||||
[ -n "$m" ] && bashio::log.red "$m"
|
||||
exit 1
|
||||
}
|
||||
bashio::exit.nok() { local m="${1:-}"; [ -n "$m" ] && bashio::log.red "$m"; exit 1; }
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Core config check shim
|
||||
|
||||
@@ -28,14 +28,8 @@ pick_exec_dir() {
|
||||
if [ -d "$d" ] && [ -w "$d" ]; then
|
||||
# Create a tiny test executable to confirm "exec" works
|
||||
local t="${d%/}/.exec_test_$$"
|
||||
printf '#!/bin/sh\necho ok\n' > "$t" 2> /dev/null || {
|
||||
rm -f "$t" 2> /dev/null || true
|
||||
continue
|
||||
}
|
||||
chmod 700 "$t" 2> /dev/null || {
|
||||
rm -f "$t" 2> /dev/null || true
|
||||
continue
|
||||
}
|
||||
printf '#!/bin/sh\necho ok\n' >"$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
|
||||
chmod 700 "$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
|
||||
if "$t" >/dev/null 2>&1; then
|
||||
rm -f "$t" 2>/dev/null || true
|
||||
echo "$d"
|
||||
@@ -215,8 +209,8 @@ if $PID1 && { [ -x /command/with-contenv ] || [ -x /usr/bin/with-contenv ]; }; t
|
||||
# accepts a surviving symlink-to-directory and would let the dump follow it. rm does not traverse
|
||||
# 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
|
||||
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
|
||||
|
||||
@@ -75,7 +75,7 @@ You can add the following tags in the file :
|
||||
|
||||
### Addon version numbering
|
||||
|
||||
The `version` written in the addon `config.yaml` is the one Home Assistant compares to decide whether an update is available. Home Assistant hides the update when it can order both versions and the new one is not strictly newer (`1.2.3` -> `1.2.3-2` is a semver pre-release, so it is _older_), and it cannot order tags such as `version-bf9e0b4f` or `ubuntu-2026-06-01` at all.
|
||||
The `version` written in the addon `config.yaml` is the one Home Assistant compares to decide whether an update is available. Home Assistant hides the update when it can order both versions and the new one is not strictly newer (`1.2.3` -> `1.2.3-2` is a semver pre-release, so it is *older*), and it cannot order tags such as `version-bf9e0b4f` or `ubuntu-2026-06-01` at all.
|
||||
|
||||
The addon version is therefore derived from the upstream tag:
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.6 KiB |
BIN
aurral/stats.png
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
baikal/stats.png
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.1 KiB |
0
bazarr/rootfs/etc/services.d/nginx/run
Executable file → Normal file
BIN
bazarr/stats.png
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.1 KiB |
0
bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/run
Executable file → Normal file
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -9,9 +9,9 @@ Here are some example tests I did (the whole threads are really interesting also
|
||||
|
||||
**My recommendation:**
|
||||
|
||||
- **Best entry system (< 50 €):** Boya By-lm40 (30 €) + deadcat (10 €)
|
||||
- **Best middle end system (< 150 €):** Clippy EM272 TRS/TRRS (55 €) + Rode AI micro trs/trrs to usb (70 €) + Rycote deadcat (27 €)
|
||||
- **Best high end system (<400 €):** Clippy EM272 XLR (85 €) or LOM Ucho Pro (75 €) + Focusrite Scarlet 2i2 4th Gen (200 €) + Bubblebee Pro Extreme deadcat (45 €)
|
||||
- **Best entry system (< 50 €):** Boya By-lm40 (30 €) + deadcat (10 €)
|
||||
- **Best middle end system (< 150 €):** Clippy EM272 TRS/TRRS (55 €) + Rode AI micro trs/trrs to usb (70 €) + Rycote deadcat (27 €)
|
||||
- **Best high end system (<400 €):** Clippy EM272 XLR (85 €) or LOM Ucho Pro (75 €) + Focusrite Scarlet 2i2 4th Gen (200 €) + Bubblebee Pro Extreme deadcat (45 €)
|
||||
|
||||
**Sources for high end microphones in Europe:**
|
||||
|
||||
|
||||
@@ -112,7 +112,6 @@ def automatic_mqtt_publish(file, detection, path):
|
||||
mqttc.publish(mqtt_topic, json_bird, 1)
|
||||
log.info("Posted to MQTT: ok")
|
||||
|
||||
|
||||
# Create MQTT client using legacy callback API when available for
|
||||
# compatibility with paho-mqtt >= 2.0
|
||||
callback_api = getattr(mqtt, "CallbackAPIVersion", None)
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -9,9 +9,9 @@ Here are some example tests I did (the whole threads are really interesting also
|
||||
|
||||
**My recommendation:**
|
||||
|
||||
- **Best entry system (< 50 €):** Boya By-lm40 (30 €) + deadcat (10 €)
|
||||
- **Best middle end system (< 150 €):** Clippy EM272 TRS/TRRS (55 €) + Rode AI micro trs/trrs to usb (70 €) + Rycote deadcat (27 €)
|
||||
- **Best high end system (<400 €):** Clippy EM272 XLR (85 €) or LOM Ucho Pro (75 €) + Focusrite Scarlet 2i2 4th Gen (200 €) + Bubblebee Pro Extreme deadcat (45 €)
|
||||
- **Best entry system (< 50 €):** Boya By-lm40 (30 €) + deadcat (10 €)
|
||||
- **Best middle end system (< 150 €):** Clippy EM272 TRS/TRRS (55 €) + Rode AI micro trs/trrs to usb (70 €) + Rycote deadcat (27 €)
|
||||
- **Best high end system (<400 €):** Clippy EM272 XLR (85 €) or LOM Ucho Pro (75 €) + Focusrite Scarlet 2i2 4th Gen (200 €) + Bubblebee Pro Extreme deadcat (45 €)
|
||||
|
||||
**Sources for high end microphones in Europe:**
|
||||
|
||||
|
||||
@@ -112,7 +112,6 @@ def automatic_mqtt_publish(file, detection, path):
|
||||
mqttc.publish(mqtt_topic, json_bird, 1)
|
||||
log.info("Posted to MQTT: ok")
|
||||
|
||||
|
||||
# Create MQTT client using legacy callback API when available for
|
||||
# compatibility with paho-mqtt >= 2.0
|
||||
callback_api = getattr(mqtt, "CallbackAPIVersion", None)
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 4.7 KiB |
@@ -73,7 +73,7 @@ map:
|
||||
name: BirdNET-PiPy
|
||||
options:
|
||||
env_vars: []
|
||||
ICECAST_PASSWORD: ""
|
||||
ICECAST_PASSWORD: ''
|
||||
data_location: /config/data
|
||||
panel_icon: mdi:bird
|
||||
ports:
|
||||
|
||||
0
birdnet-pipy/rootfs/etc/services.d/api/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/icecast/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/main/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/model/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/nginx/run
Executable file → Normal file
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.7 KiB |
0
bitwarden/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run
Executable file → Normal file
0
bitwarden/rootfs/etc/s6-overlay/s6-rc.d/nginx/run
Executable file → Normal file
0
bitwarden/rootfs/etc/s6-overlay/s6-rc.d/vaultwarden/run
Executable file → Normal file
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.6 KiB |
1
browser_chromium/rootfs/etc/cont-init.d/20-folders.sh
Normal file → Executable file
@@ -7,6 +7,7 @@ set -e
|
||||
PUID=$(bashio::config "PUID")
|
||||
PGID=$(bashio::config "PGID")
|
||||
|
||||
|
||||
# Create cache
|
||||
#mkdir -p /.cache
|
||||
#chmod 755 /.cache
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -99,7 +99,7 @@ This addon supports mounting both local drives and remote SMB shares:
|
||||
|
||||
### Optional Calibre-Web features
|
||||
|
||||
Calibre-Web documents optional extras that a manual installation adds with `pip install calibreweb[metadata]` and similar. **You do not need to install anything here**: the LinuxServer base image this add-on builds on installs Calibre-Web's `requirements.txt` _and_ its full `optional-requirements.txt` into the application's virtualenv, so the gdrive, gmail, goodreads, ldap, oauth, metadata, comics and kobo dependencies are all present already. Running `pip install calibreweb[...]` inside the container is not a supported way to enable them: it installs the PyPI distribution of Calibre-Web over an installation that already has those dependencies, and it can disturb the versions the base image pinned. It is also thrown away, because the Supervisor recreates the add-on container on restart.
|
||||
Calibre-Web documents optional extras that a manual installation adds with `pip install calibreweb[metadata]` and similar. **You do not need to install anything here**: the LinuxServer base image this add-on builds on installs Calibre-Web's `requirements.txt` *and* its full `optional-requirements.txt` into the application's virtualenv, so the gdrive, gmail, goodreads, ldap, oauth, metadata, comics and kobo dependencies are all present already. Running `pip install calibreweb[...]` inside the container is not a supported way to enable them: it installs the PyPI distribution of Calibre-Web over an installation that already has those dependencies, and it can disturb the versions the base image pinned. It is also thrown away, because the Supervisor recreates the add-on container on restart.
|
||||
|
||||
Optional features are switched on in the Calibre-Web web interface, not in the add-on options, under `Admin` -> `Basic Configuration` -> `Feature Configuration` (for example `Enable Uploads`, `Enable Kobo sync`, `Use Goodreads`).
|
||||
|
||||
|
||||
@@ -30,16 +30,16 @@ else
|
||||
# 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})"
|
||||
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})"
|
||||
|
||||
# Calibre-web autodetects kepubify only while this setting is still NULL. On every install
|
||||
# that predates the Dockerfile fix above, that detection already ran, found nothing usable and
|
||||
# stored an empty string, so it is never retried. Put an empty value back to NULL and
|
||||
# calibre-web detects /opt/kepubify itself when it starts, a few seconds after this runs.
|
||||
# A path the user set by hand is not empty and is left alone.
|
||||
kepubify_error=$(sqlite3 /config/app.db "update settings set config_kepubifypath = NULL where config_kepubifypath = ''" 2>&1) \
|
||||
|| bashio::log.warning "Could not reset the kepubify path, it will be applied at next start (${kepubify_error})"
|
||||
kepubify_error=$(sqlite3 /config/app.db "update settings set config_kepubifypath = NULL where config_kepubifypath = ''" 2>&1) ||
|
||||
bashio::log.warning "Could not reset the kepubify path, it will be applied at next start (${kepubify_error})"
|
||||
fi
|
||||
|
||||
bashio::log.info "Default username:password is admin:admin123"
|
||||
|
||||
0
changedetection.io/rootfs/etc/services.d/nginx/run
Executable file → Normal file
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -338,7 +338,8 @@ ln -sfn "$CODEX_BIN" "$CODEX_LINK"
|
||||
CODEX_SANDBOX_MODE="$(bashio::config 'codex_sandbox_mode' 'danger-full-access')"
|
||||
run_as_runtime_user mkdir -p "$RUNTIME_HOME/.codex"
|
||||
CODEX_SANDBOX_MODE="$CODEX_SANDBOX_MODE" RUNTIME_HOME="$RUNTIME_HOME" \
|
||||
run_as_runtime_user python3 - << 'PY' || bashio::log.warning "Unable to update the managed Codex configuration block"
|
||||
run_as_runtime_user python3 - <<'PY' \
|
||||
|| bashio::log.warning "Unable to update the managed Codex configuration block"
|
||||
import os
|
||||
import re
|
||||
import tomllib
|
||||
|
||||
0
claude_desktop/rootfs/usr/local/bin/codex-login
Normal file → Executable file
12
claude_desktop/rootfs/usr/local/bin/ha-cli
Normal file → Executable file
@@ -133,9 +133,7 @@ def rest(method, base, token, path, body=None):
|
||||
if body is not None:
|
||||
data = json.dumps(body).encode("utf-8")
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(
|
||||
_url(base, path), data=data, method=method, headers=headers
|
||||
)
|
||||
req = urllib.request.Request(_url(base, path), data=data, method=method, headers=headers)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
text = resp.read().decode("utf-8")
|
||||
@@ -143,9 +141,7 @@ def rest(method, base, token, path, body=None):
|
||||
detail = exc.read().decode("utf-8", "replace").strip()
|
||||
sys.exit(f"ha-cli: HTTP {exc.code} {exc.reason} on {method} {path}\n{detail}")
|
||||
except urllib.error.URLError as exc:
|
||||
sys.exit(
|
||||
f"ha-cli: cannot reach Home Assistant ({exc.reason}) on {method} {path}"
|
||||
)
|
||||
sys.exit(f"ha-cli: cannot reach Home Assistant ({exc.reason}) on {method} {path}")
|
||||
try:
|
||||
return json.loads(text) if text.strip() else None
|
||||
except ValueError:
|
||||
@@ -218,9 +214,7 @@ def main(argv):
|
||||
sys.exit("usage: ha-cli call <domain.service> [BODY]")
|
||||
domain, service = args[0].split(".", 1)
|
||||
body = _read_body(args[1]) if len(args) > 1 else None
|
||||
_print(
|
||||
rest("POST", rest_base, token, f"services/{domain}/{service}", body or {})
|
||||
)
|
||||
_print(rest("POST", rest_base, token, f"services/{domain}/{service}", body or {}))
|
||||
elif cmd == "states":
|
||||
path = f"states/{args[0]}" if args else "states"
|
||||
_print(rest("GET", rest_base, token, path))
|
||||
|
||||
14
claude_desktop/rootfs/usr/local/bin/headroom-posttooluse-compress.py
Normal file → Executable file
@@ -76,11 +76,7 @@ def main() -> int:
|
||||
|
||||
# Find big string/array fields before paying the headroom import cost.
|
||||
def is_string_array(value):
|
||||
return (
|
||||
isinstance(value, list)
|
||||
and len(value) > ARRAY_KEEP
|
||||
and all(isinstance(v, str) for v in value)
|
||||
)
|
||||
return isinstance(value, list) and len(value) > ARRAY_KEEP and all(isinstance(v, str) for v in value)
|
||||
|
||||
if isinstance(response, str):
|
||||
string_candidates = ["__whole__"] if len(response) >= MIN_CHARS else []
|
||||
@@ -89,14 +85,10 @@ def main() -> int:
|
||||
string_candidates = [
|
||||
key
|
||||
for key, value in response.items()
|
||||
if key not in SKIP_KEYS
|
||||
and isinstance(value, str)
|
||||
and len(value) >= MIN_CHARS
|
||||
if key not in SKIP_KEYS and isinstance(value, str) and len(value) >= MIN_CHARS
|
||||
]
|
||||
array_candidates = [
|
||||
key
|
||||
for key, value in response.items()
|
||||
if key not in SKIP_KEYS and is_string_array(value)
|
||||
key for key, value in response.items() if key not in SKIP_KEYS and is_string_array(value)
|
||||
]
|
||||
else:
|
||||
string_candidates = []
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,7 +1,8 @@
|
||||
arch:
|
||||
- aarch64
|
||||
- amd64
|
||||
description: Automatically removes stuck and unwanted downloads from your *arr and download clients
|
||||
description:
|
||||
Automatically removes stuck and unwanted downloads from your *arr and download clients
|
||||
devices:
|
||||
- /dev/dri
|
||||
- /dev/dri/card0
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.9 KiB |
BIN
codex/stats.png
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.8 KiB |
0
comicarr/rootfs/etc/services.d/comicarr/run
Executable file → Normal file
0
comicarr/rootfs/etc/services.d/nginx/run
Executable file → Normal file
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.5 KiB |
0
emby/rootfs/etc/services.d/nginx/run
Executable file → Normal file
BIN
emby/stats.png
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
ente/stats.png
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -4,17 +4,9 @@ set -e
|
||||
|
||||
# Source environment variables for cron context
|
||||
# shellcheck disable=SC1091
|
||||
if [ -f /etc/environment ]; then
|
||||
set -a
|
||||
. /etc/environment
|
||||
set +a
|
||||
fi
|
||||
if [ -f /etc/environment ]; then set -a; . /etc/environment; set +a; fi
|
||||
# shellcheck disable=SC1091
|
||||
if [ -f /.env ]; then
|
||||
set -a
|
||||
. /.env
|
||||
set +a
|
||||
fi
|
||||
if [ -f /.env ]; then set -a; . /.env; set +a; fi
|
||||
|
||||
# Use persisted value instead of calling bashio::config (www-data may lack API access)
|
||||
CONFIGSOURCE="${CONFIG_LOCATION}"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -2,14 +2,14 @@
|
||||
# Complete option reference:
|
||||
# https://github.com/P-Adamiec/Free-Games-Claimer-Remaster#configuration
|
||||
|
||||
HEIGHT=720
|
||||
LOGIN_TIMEOUT=180
|
||||
NOVNC_PORT=6080
|
||||
# Browser and noVNC
|
||||
SHOW=1
|
||||
TIMEOUT=60
|
||||
VNC_LOGIN_TIMEOUT=180
|
||||
WIDTH=1280
|
||||
HEIGHT=720
|
||||
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
|
||||
|
||||
|
||||
@@ -47,10 +47,8 @@ def normalize_timestamp(value: Any) -> str:
|
||||
return parsed.isoformat(sep=" ", timespec="seconds")
|
||||
except ValueError:
|
||||
pass
|
||||
return (
|
||||
datetime.now(timezone.utc)
|
||||
.replace(tzinfo=None)
|
||||
.isoformat(sep=" ", timespec="seconds")
|
||||
return datetime.now(timezone.utc).replace(tzinfo=None).isoformat(
|
||||
sep=" ", timespec="seconds"
|
||||
)
|
||||
|
||||
|
||||
@@ -101,7 +99,8 @@ def iter_records(store: str, payload: Any, source: Path) -> Iterator[dict[str, A
|
||||
|
||||
|
||||
def ensure_schema(connection: sqlite3.Connection) -> None:
|
||||
connection.executescript("""
|
||||
connection.executescript(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS claimed_games (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
store VARCHAR(32) NOT NULL,
|
||||
@@ -118,7 +117,8 @@ def ensure_schema(connection: sqlite3.Connection) -> None:
|
||||
CREATE INDEX IF NOT EXISTS ix_claimed_games_store ON claimed_games (store);
|
||||
CREATE INDEX IF NOT EXISTS ix_claimed_games_user ON claimed_games (user);
|
||||
CREATE INDEX IF NOT EXISTS ix_claimed_games_game_id ON claimed_games (game_id);
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def migrate() -> int:
|
||||
@@ -209,12 +209,7 @@ def migrate() -> int:
|
||||
|
||||
connection.commit()
|
||||
log(f"Imported {source_imported} record(s) from {source}")
|
||||
except (
|
||||
OSError,
|
||||
ValueError,
|
||||
json.JSONDecodeError,
|
||||
sqlite3.Error,
|
||||
) as err:
|
||||
except (OSError, ValueError, json.JSONDecodeError, sqlite3.Error) as err:
|
||||
connection.rollback()
|
||||
message = f"Failed to import {source}: {err}"
|
||||
errors.append(message)
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
gitea/stats.png
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.8 KiB |
BIN
grav/stats.png
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
immich/stats.png
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -2,7 +2,8 @@ arch:
|
||||
- amd64
|
||||
backup_exclude:
|
||||
- "**/machine-learning/*"
|
||||
description: Self-hosted photo and video backup solution directly from your mobile phone
|
||||
description:
|
||||
Self-hosted photo and video backup solution directly from your mobile phone
|
||||
devices:
|
||||
- /dev/dri
|
||||
- /dev/dxg
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -121,10 +121,7 @@ ACCOUNT_SCHEMA_OPTS="Albums ExcludedAlbums People Tags ShowFavorites ShowMemorie
|
||||
|
||||
for opt in $GENERAL_SCHEMA_OPTS; do
|
||||
if config_has ".$opt"; then
|
||||
$GENERAL_STARTED || {
|
||||
echo "General:"
|
||||
GENERAL_STARTED=true
|
||||
}
|
||||
$GENERAL_STARTED || { echo "General:"; GENERAL_STARTED=true; }
|
||||
yaml_kv " " "$opt" "$(config_val ".$opt")"
|
||||
fi
|
||||
done
|
||||
@@ -132,10 +129,7 @@ ACCOUNT_SCHEMA_OPTS="Albums ExcludedAlbums People Tags ShowFavorites ShowMemorie
|
||||
# Add general env_vars (skip if already set via schema option)
|
||||
for key in "${!GENERAL_ENVS[@]}"; do
|
||||
if ! config_has ".$key"; then
|
||||
$GENERAL_STARTED || {
|
||||
echo "General:"
|
||||
GENERAL_STARTED=true
|
||||
}
|
||||
$GENERAL_STARTED || { echo "General:"; GENERAL_STARTED=true; }
|
||||
yaml_kv " " "$key" "${GENERAL_ENVS[$key]}"
|
||||
fi
|
||||
done
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -3,7 +3,8 @@ arch:
|
||||
- amd64
|
||||
backup_exclude:
|
||||
- "**/machine-learning/*"
|
||||
description: Self-hosted photo and video backup solution directly from your mobile phone
|
||||
description:
|
||||
Self-hosted photo and video backup solution directly from your mobile phone
|
||||
devices:
|
||||
- /dev/dri
|
||||
- /dev/dxg
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.1 KiB |