Compare commits

..

18 Commits

Author SHA1 Message Date
github-actions
d31ccfa21c GitHub bot: changelog [nobuild] 2026-09-09 19:54:08 +00:00
Alexandre
9c277876ff Update config.yaml 2026-09-09 21:47:42 +02:00
github-actions
7e59aa8803 GitHub bot: changelog [nobuild] 2026-09-09 12:53:50 +00:00
Alexandre
360fd669fa Update config.yaml 2026-09-09 14:47:48 +02:00
github-actions
7bf77c8558 GitHub bot: changelog [nobuild] 2026-09-09 11:28:42 +00:00
Alexandre
a195d3ac39 Update version to 20260909.3 in config.yaml 2026-09-09 13:22:38 +02:00
github-actions
14834d4063 GitHub bot: changelog [nobuild] 2026-09-09 09:18:02 +00:00
Alexandre
f5caafd62c Update config.yaml 2026-09-09 11:12:33 +02:00
github-actions
fd61631ccd GitHub bot: changelog [nobuild] 2026-09-09 08:12:04 +00:00
Alexandre
578a7f8368 Update config.yaml 2026-09-09 10:06:30 +02:00
Alexandre
5115dcb93a Update config.yaml 2026-09-09 10:06:18 +02:00
github-actions
fa5e890c4c GitHub bot: changelog [nobuild] 2026-09-08 13:44:27 +00:00
Alexandre
addb0ca66f Update config.yaml 2026-09-08 15:38:17 +02:00
dependabot[bot]
16e112b21f build(deps): bump anthropics/claude-code-action from 1.0.210 to 1.0.216 (#3055)
Bumps [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) from 1.0.210 to 1.0.216.
- [Release notes](https://github.com/anthropics/claude-code-action/releases)
- [Commits](a874e9ecd7...d75b94d5ad)

---
updated-dependencies:
- dependency-name: anthropics/claude-code-action
  dependency-version: 1.0.216
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-08 15:35:47 +02:00
BirdNET-Go Addon Builder
040a4e0f2f birdnet-go-dev: rebuild 20260908.1 (upstream sync, 4 commits; fork PR #62) 2026-09-08 15:00:11 +02:00
alexbelgium
f72f7bef0f birdnet-go-dev: rebuild 20260908 (upstream sync, 7 commits) 2026-09-08 09:34:22 +02:00
BirdNET-Go Addon Builder
c18f45d822 birdnet-go-dev: rebuild 20260907 (fork PR #57 updated) 2026-09-07 14:39:59 +02:00
Alexandre
abf3d76873 birdnet-go-dev: add merge-prs.sh --check to catch build-breaking PR conflicts (#3053)
* birdnet-go-dev: add merge-prs.sh --check to catch build-breaking PR conflicts

The add-on build merges every open non-draft fork PR onto an upstream-synced
main. GitHub's `mergeable` field answers a different question: it compares a PR
against its *own* base ref, which for the stacked dashboard PRs is another
feature branch - sometimes one belonging to an already-closed PR. So a PR can
read MERGEABLE/CLEAN and still fail the build.

That is how run 34101694542 broke: PR #57 is MERGEABLE against the frozen branch
of closed PR #56, but conflicts with main on DetectionCard.svelte.

--check replays the exact same merge sequence, skips past conflicts instead of
stopping at the first one, and reports every offender as

    !!! CONFLICT pr=#N conflicts-with=<main|accumulated> files=... title=...

conflicts-with is probed in a throwaway worktree against the pristine synced
main, so it separates a PR that is merely stale (fixable in its own branch) from
one that only clashes with another open PR (needs a cross-PR decision).

Build behaviour is unchanged; --check is a no-op unless asked for, so no version
bump.

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

* birdnet-go-dev: fix --check misclassification, arg handling and temp leak

Review findings on #3053, all reproduced against git fixtures before fixing.

1. The probe did not apply the build's own merge policy (Codex).
   The real merge treats a *sole* frontend/package-lock.json conflict as
   non-fatal, but merges_onto_main() did a raw merge. A PR whose only clash
   with main is the generated lockfile was therefore reported as
   conflicts-with=main, which prints "the PR is stale against main; merge main
   into its branch" when in truth it merges onto main fine and only clashes
   with another open PR — the exact opposite remediation, from the feature
   whose entire job is to say which one it is.

   Extracted the policy into resolve_sole_lockfile() and routed both the real
   merge and the probe through it, so the two cannot drift apart again.

2. A second positional operand silently won (CodeRabbit).
   `merge-prs.sh a b` ran against b, where the pre-flag script used "${1}".
   A stray argument would have cloned into the wrong directory. Now exits 64.

3. mktemp parent directory leaked (CodeRabbit, Copilot).
   probe="$(mktemp -d)/probe" and only the child was removed, leaking one
   empty dir per checked conflict. Measured 3 leaked dirs over 3 calls; now 0.

Verified with throwaway repos: a lockfile-only clash now classifies as
"accumulated" (was "main"), a real source conflict with main still classifies
as "main", a clean PR still classifies as "accumulated", 0 leaked temp dirs,
0 stray worktrees, and the argument matrix behaves. shellcheck clean.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: BirdNET-Go Addon Builder <addon-builder@users.noreply.github.com>
2026-09-07 14:05:42 +02:00
303 changed files with 2658 additions and 2671 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)"

View File

@@ -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.

View File

@@ -125,7 +125,7 @@ jobs:
- name: Analyse and fix
if: steps.batch.outputs.count != '0'
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange. The scheduled path

View File

@@ -64,7 +64,7 @@ jobs:
fetch-depth: 1
- name: Run Claude Code
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.

View File

@@ -135,7 +135,7 @@ jobs:
- name: Execute the plan
if: steps.bundle.outputs.has_plan == 'true'
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange, which 401s whenever

View File

@@ -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@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Without this the action falls back to the OIDC -> Claude App token

View File

@@ -79,7 +79,7 @@ jobs:
- name: Address CodeRabbit comments
if: steps.claim.outputs.go == 'true'
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange, which 401s whenever

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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:

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

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

View File

@@ -1,3 +1,21 @@
## 20260909.5 (09-09-2026)
- Minor bugs fixed
## 20260909.4 (09-09-2026)
- Minor bugs fixed
## 20260909.3 (09-09-2026)
- Minor bugs fixed
## 20260909.2 (09-09-2026)
- Minor bugs fixed
## 20260909 (09-09-2026)
- Minor bugs fixed
## 20260908.2 (08-09-2026)
- Minor bugs fixed
## 20260908.1 (08-09-2026)
- Synced with upstream birdnet-go (4 commits); re-merges the open fork PRs, adding fork PR #62 (reanalyze a clip with every loaded model + one-click correction)
## 20260908 (08-09-2026)
- Synced with upstream birdnet-go (7 commits); re-merges the open fork PRs
## 20260907 (07-09-2026)
- Rebuild: re-merges the open fork PRs, picking up the updated fork PR #57
## 20260901.4 (01-09-2026)
- Minor bugs fixed
## 20260901.3 (01-09-2026)

View File

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

View File

@@ -11,6 +11,15 @@
# stamping) is written to the directory given as $1 so the Docker build can
# compile it.
#
# With --check the script does not build anything: it performs the exact same
# merge sequence, skips (instead of failing on) every conflicting PR, and prints
# one "!!! CONFLICT pr=#N conflicts-with=... files=... " line per offender before
# exiting 2. Use it to find conflicts *before* a build burns on them. This is a
# different question from GitHub's `mergeable` field, which compares a PR against
# its own base ref - for a stacked PR that base is another feature branch (often
# stale, sometimes belonging to a closed PR), so GitHub can report CLEAN for a PR
# that does not merge onto main at all.
#
# Environment:
# BIRDNET_FORK owner/repo of the fork (default alexbelgium/birdnet-go)
# BIRDNET_UPSTREAM owner/repo of the upstream (default tphakala/birdnet-go)
@@ -19,7 +28,26 @@
#
set -euo pipefail
TARGET_DIR="${1:?usage: merge-prs.sh <target-dir>}"
CHECK_ONLY="${MERGE_PRS_CHECK:-0}"
TARGET_DIR=""
while [ "$#" -gt 0 ]; do
case "$1" in
--check) CHECK_ONLY=1 ;;
-*) echo "unknown option: $1" >&2; exit 64 ;;
*)
# Last-one-wins would silently clone into the wrong directory if a caller ever
# appended an argument; the pre-flag script used "${1}", so refuse rather than
# quietly change which operand counts.
if [ -n "${TARGET_DIR}" ]; then
echo "usage: merge-prs.sh [--check] <target-dir>" >&2
exit 64
fi
TARGET_DIR="$1"
;;
esac
shift
done
: "${TARGET_DIR:?usage: merge-prs.sh [--check] <target-dir>}"
FORK="${BIRDNET_FORK:-alexbelgium/birdnet-go}"
UPSTREAM="${BIRDNET_UPSTREAM:-tphakala/birdnet-go}"
@@ -33,6 +61,52 @@ GH_TOKEN="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
log() { echo ">>> $*"; }
# Conflicting PRs collected in --check mode: "number|scope|files|title".
conflicting=()
LOCKFILE="frontend/package-lock.json"
# The single place that decides whether a conflicted merge is still acceptable.
# package-lock.json is generated content and stacked PRs can carry an older copy even when
# their source changes merge cleanly, so keep the lockfile already assembled on the base side
# — but only when it is the sole conflict. Any source conflict stays fatal.
# Returns 0 when it resolved and committed such a merge, 1 when the conflict is real.
# BOTH the real merge and the --check probe must go through here: when only the real merge
# applied the policy, the probe called a PR "conflicts-with=main" that the build would have
# merged fine, and printed the opposite remediation to the true one.
resolve_sole_lockfile() {
local dir="$1"
local -a conflicted
mapfile -t conflicted < <(git -C "${dir}" diff --name-only --diff-filter=U)
if [ "${#conflicted[@]}" -ne 1 ] || [ "${conflicted[0]}" != "${LOCKFILE}" ]; then
return 1
fi
log "Resolving generated ${LOCKFILE} conflict using the base tree"
git -C "${dir}" checkout --ours -- "${LOCKFILE}" || return 1
git -C "${dir}" add "${LOCKFILE}" || return 1
git -C "${dir}" commit --no-edit > /dev/null || return 1
}
# Does ${1} merge cleanly onto the pristine upstream-synced main? Probed in a
# throwaway worktree so the accumulated tree is left untouched. Tells apart a PR
# that is simply stale against main (fixable inside that PR's own branch) from
# one that only clashes with another open PR (needs a cross-PR decision).
merges_onto_main() {
local sha="$1" tmpdir probe rc=0
tmpdir="$(mktemp -d)"
probe="${tmpdir}/probe"
git worktree add --quiet --detach "${probe}" "${MAIN_SYNCED}"
if ! git -C "${probe}" merge --no-edit --no-ff -m probe "${sha}" > /dev/null 2>&1; then
# Same policy as the real merge, or this misclassifies a lockfile-only clash.
resolve_sole_lockfile "${probe}" > /dev/null 2>&1 || rc=1
fi
git worktree remove --force "${probe}" > /dev/null 2>&1 || true
# worktree remove only takes the child back; without this the mktemp parent is left behind
# on every checked conflict.
rmdir "${tmpdir}" > /dev/null 2>&1 || true
return "${rc}"
}
git config --global user.email "addon-builder@users.noreply.github.com"
git config --global user.name "BirdNET-Go Addon Builder"
git config --global advice.detachedHead false
@@ -47,6 +121,7 @@ git remote add upstream "${UPSTREAM_URL}"
git fetch --no-tags upstream main
# --no-ff keeps an explicit sync commit; a no-op when main is already current.
git merge --no-edit --no-ff upstream/main
MAIN_SYNCED="$(git rev-parse HEAD)"
log "Querying open non-draft PRs from ${FORK}"
auth_header=()
@@ -79,27 +154,43 @@ for entry in "${prs[@]}"; do
if ! git merge --no-edit --no-ff -m "Merge PR #${number}: ${title}" "${sha}"; then
mapfile -t conflicted_files < <(git diff --name-only --diff-filter=U)
# package-lock.json is generated content and stacked PRs can carry an
# older copy even when their source changes merge cleanly. Keep the
# lockfile already assembled from upstream and earlier PRs, but only
# when it is the sole conflict. Any source conflict remains fatal.
if [ "${#conflicted_files[@]}" -eq 1 ] \
&& [ "${conflicted_files[0]}" = "frontend/package-lock.json" ]; then
log "Resolving generated frontend/package-lock.json conflict using the accumulated tree"
git checkout --ours -- frontend/package-lock.json
git add frontend/package-lock.json
git commit --no-edit
if resolve_sole_lockfile .; then
: # generated lockfile only - the merge is committed and the build continues
else
echo "!!! Merge conflict while merging PR #${number} (${title})." >&2
if [ "${#conflicted_files[@]}" -gt 0 ]; then
printf '!!! Conflicting file: %s\n' "${conflicted_files[@]}" >&2
fi
echo "!!! Resolve the conflict in the fork or pause this PR, then rebuild." >&2
git merge --abort || true
if [ "${CHECK_ONLY}" = "1" ]; then
scope="accumulated"
merges_onto_main "${sha}" || scope="main"
conflicting+=("${number}|${scope}|${conflicted_files[*]:-}|${title}")
log "check mode: skipping PR #${number}, continuing with the rest"
continue
fi
echo "!!! Resolve the conflict in the fork or pause this PR, then rebuild." >&2
exit 1
fi
fi
done
if [ "${CHECK_ONLY}" = "1" ]; then
if [ "${#conflicting[@]}" -eq 0 ]; then
log "CHECK OK: every open non-draft PR merges into the combined build tree"
exit 0
fi
echo "!!! CHECK FAILED: ${#conflicting[@]} PR(s) would break the add-on build" >&2
for entry in "${conflicting[@]}"; do
IFS='|' read -r number scope files title <<<"${entry}"
echo "!!! CONFLICT pr=#${number} conflicts-with=${scope} files=${files} title=${title}" >&2
done
echo "!!! conflicts-with=main -> the PR is stale against main; merge main into its branch and resolve there." >&2
echo "!!! conflicts-with=accumulated -> the PR only clashes with another open PR; decide which one owns the hunk." >&2
exit 2
fi
log "Merged HEAD: $(git rev-parse --short HEAD)"
log "Source tree ready at ${TARGET_DIR}"

View File

@@ -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:**

View File

@@ -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)

View File

@@ -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:**

View File

@@ -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)

View File

@@ -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
View File

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

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

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

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

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

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

View File

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

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

View File

@@ -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`).

View File

@@ -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
View File

View File

@@ -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
View File

12
claude_desktop/rootfs/usr/local/bin/ha-cli Normal file → Executable file
View 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))

View 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 = []

View File

@@ -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

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

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

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

View File

@@ -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}"

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -71,7 +71,7 @@ devices:
- /dev/nvme1
- /dev/nvme2
environment:
ATTACHED_DEVICES_PERMS: "/dev/dri /dev/dvb /dev/vchiq /dev/vc-mem /dev/video1? -type c -o -type f"
ATTACHED_DEVICES_PERMS: '/dev/dri /dev/dvb /dev/vchiq /dev/vc-mem /dev/video1? -type c -o -type f'
host_dbus: true
host_network: true
image: ghcr.io/alexbelgium/jellyfin-{arch}

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

View File

@@ -60,10 +60,7 @@ unlock_postgres_migrations() {
local has_table
has_table="$(psql -h "$POSTGRES_HOST" -p "$pg_port" -U "$POSTGRES_USER" -d "$POSTGRES_DATABASE" -Atqc \
"SELECT 1 FROM information_schema.tables WHERE table_name='knex_migrations_lock' LIMIT 1;" 2>/dev/null || true)"
[[ "$has_table" == "1" ]] || {
unset PGPASSWORD
return 0
}
[[ "$has_table" == "1" ]] || { unset PGPASSWORD; return 0; }
# Ensure row exists
psql -h "$POSTGRES_HOST" -p "$pg_port" -U "$POSTGRES_USER" -d "$POSTGRES_DATABASE" -Atqc \

View File

@@ -1,7 +1,8 @@
arch:
- aarch64
- amd64
description: Rule-based media cleanup tool for Plex, Jellyfin and Emby. Creates collections and optionally deletes unwatched content.
description:
Rule-based media cleanup tool for Plex, Jellyfin and Emby. Creates collections and optionally deletes unwatched content.
devices:
- /dev/dri
- /dev/dri/card0

View File

@@ -172,7 +172,7 @@ chmod 600 /addon_configs/<new_slug>/secret_key_base
Replace `<old_slug>` and `<new_slug>` with the actual directory names (e.g. `db21ed7f_manyfold` and `088d77ac_manyfold_solo`). List them with `ls /addon_configs/`.
1. Start the new addon — it will pick up the existing database and secret automatically.
3. Start the new addon — it will pick up the existing database and secret automatically.
## Notes

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

View File

@@ -194,7 +194,8 @@ start_manyfold() {
/usr/local/bin/docker-entrypoint.sh \
/usr/local/bin/docker-entrypoint \
/docker-entrypoint.sh \
/entrypoint.sh; do
/entrypoint.sh
do
if [[ -x "$candidate" ]]; then
log "Starting Manyfold via ${candidate}"
if [[ "$candidate" == *docker-entrypoint* ]]; then
@@ -227,34 +228,20 @@ start_manyfold() {
[[ -f "$OPTIONS_JSON" ]] || die "Missing options file at ${OPTIONS_JSON}"
PUID="$(read_opt puid)"
PUID="${PUID:-1000}"
PGID="$(read_opt pgid)"
PGID="${PGID:-1000}"
MULTIUSER="$(read_opt multiuser)"
MULTIUSER="${MULTIUSER:-true}"
LIBRARY_PATH_RAW="$(read_opt library_path)"
LIBRARY_PATH_RAW="${LIBRARY_PATH_RAW:-$DEFAULT_LIBRARY_PATH}"
THUMBNAILS_PATH_RAW="$(read_opt thumbnails_path)"
THUMBNAILS_PATH_RAW="${THUMBNAILS_PATH_RAW:-$DEFAULT_THUMBNAILS_PATH}"
LOG_LEVEL="$(read_opt log_level)"
LOG_LEVEL="${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}"
WEB_CONCURRENCY="$(read_opt web_concurrency)"
WEB_CONCURRENCY="${WEB_CONCURRENCY:-$DEFAULT_WEB_CONCURRENCY}"
RAILS_MAX_THREADS="$(read_opt rails_max_threads)"
RAILS_MAX_THREADS="${RAILS_MAX_THREADS:-$DEFAULT_RAILS_MAX_THREADS}"
DEFAULT_WORKER_CONCURRENCY="$(read_opt default_worker_concurrency)"
DEFAULT_WORKER_CONCURRENCY="${DEFAULT_WORKER_CONCURRENCY:-$DEFAULT_DEFAULT_WORKER_CONCURRENCY}"
PERFORMANCE_WORKER_CONCURRENCY="$(read_opt performance_worker_concurrency)"
PERFORMANCE_WORKER_CONCURRENCY="${PERFORMANCE_WORKER_CONCURRENCY:-$DEFAULT_PERFORMANCE_WORKER_CONCURRENCY}"
MAX_FILE_UPLOAD_SIZE="$(read_opt max_file_upload_size)"
MAX_FILE_UPLOAD_SIZE="${MAX_FILE_UPLOAD_SIZE:-$DEFAULT_MAX_FILE_UPLOAD_SIZE}"
MAX_FILE_EXTRACT_SIZE="$(read_opt max_file_extract_size)"
MAX_FILE_EXTRACT_SIZE="${MAX_FILE_EXTRACT_SIZE:-$DEFAULT_MAX_FILE_EXTRACT_SIZE}"
SECRET_KEY_BASE="$(read_opt secret_key_base)"
SECRET_KEY_BASE="${SECRET_KEY_BASE:-}"
PUBLIC_HOSTNAME_RAW="$(read_opt public_hostname)"
PUBLIC_HOSTNAME_RAW="${PUBLIC_HOSTNAME_RAW:-}"
PUID="$(read_opt puid)"; PUID="${PUID:-1000}"
PGID="$(read_opt pgid)"; PGID="${PGID:-1000}"
MULTIUSER="$(read_opt multiuser)"; MULTIUSER="${MULTIUSER:-true}"
LIBRARY_PATH_RAW="$(read_opt library_path)"; LIBRARY_PATH_RAW="${LIBRARY_PATH_RAW:-$DEFAULT_LIBRARY_PATH}"
THUMBNAILS_PATH_RAW="$(read_opt thumbnails_path)"; THUMBNAILS_PATH_RAW="${THUMBNAILS_PATH_RAW:-$DEFAULT_THUMBNAILS_PATH}"
LOG_LEVEL="$(read_opt log_level)"; LOG_LEVEL="${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}"
WEB_CONCURRENCY="$(read_opt web_concurrency)"; WEB_CONCURRENCY="${WEB_CONCURRENCY:-$DEFAULT_WEB_CONCURRENCY}"
RAILS_MAX_THREADS="$(read_opt rails_max_threads)"; RAILS_MAX_THREADS="${RAILS_MAX_THREADS:-$DEFAULT_RAILS_MAX_THREADS}"
DEFAULT_WORKER_CONCURRENCY="$(read_opt default_worker_concurrency)"; DEFAULT_WORKER_CONCURRENCY="${DEFAULT_WORKER_CONCURRENCY:-$DEFAULT_DEFAULT_WORKER_CONCURRENCY}"
PERFORMANCE_WORKER_CONCURRENCY="$(read_opt performance_worker_concurrency)"; PERFORMANCE_WORKER_CONCURRENCY="${PERFORMANCE_WORKER_CONCURRENCY:-$DEFAULT_PERFORMANCE_WORKER_CONCURRENCY}"
MAX_FILE_UPLOAD_SIZE="$(read_opt max_file_upload_size)"; MAX_FILE_UPLOAD_SIZE="${MAX_FILE_UPLOAD_SIZE:-$DEFAULT_MAX_FILE_UPLOAD_SIZE}"
MAX_FILE_EXTRACT_SIZE="$(read_opt max_file_extract_size)"; MAX_FILE_EXTRACT_SIZE="${MAX_FILE_EXTRACT_SIZE:-$DEFAULT_MAX_FILE_EXTRACT_SIZE}"
SECRET_KEY_BASE="$(read_opt secret_key_base)"; SECRET_KEY_BASE="${SECRET_KEY_BASE:-}"
PUBLIC_HOSTNAME_RAW="$(read_opt public_hostname)"; PUBLIC_HOSTNAME_RAW="${PUBLIC_HOSTNAME_RAW:-}"
[[ "$PUID" =~ ^[0-9]+$ ]] || die "puid must be a non-negative integer"
[[ "$PGID" =~ ^[0-9]+$ ]] || die "pgid must be a non-negative integer"

View File

@@ -245,11 +245,11 @@ if [[ "${MEILISEARCH_LOCAL}" == true ]]; then
}
MEILISEARCH_CMD=(
env
MEILI_ENV="${MEILISEARCH_ENVIRONMENT}"
MEILI_NO_ANALYTICS="${MEILISEARCH_NO_ANALYTICS}"
meilisearch
--http-addr "${MEILISEARCH_ADDR}"
env \
MEILI_ENV="${MEILISEARCH_ENVIRONMENT}" \
MEILI_NO_ANALYTICS="${MEILISEARCH_NO_ANALYTICS}" \
meilisearch \
--http-addr "${MEILISEARCH_ADDR}" \
--db-path "${MEILISEARCH_DB_PATH}"
)

0
netbird-server/rootfs/etc/services.d/caddy/run Executable file → Normal file
View File

0
netbird-server/rootfs/etc/services.d/dashboard/run Executable file → Normal file
View File

0
netbird-server/rootfs/etc/services.d/management/run Executable file → Normal file
View File

0
netbird-server/rootfs/etc/services.d/relay/run Executable file → Normal file
View File

0
netbird-server/rootfs/etc/services.d/signal/run Executable file → Normal file
View File

View File

@@ -143,7 +143,7 @@ If you use clients for file syncing, the use of SQLite is highly discouraged.
and you want to overcome this, follow the below steps:
- 1. Install `mariadb` add-on, configure it with some random infos and start it. It is important to start it successfully in order to be seen by `nextcloud` in the network.
- 1. Install `nextcloud` add-on (or restart it if you have already installed), watch the logs until you will notice the following `warning`:
- 2. Install `nextcloud` add-on (or restart it if you have already installed), watch the logs until you will notice the following `warning`:
```bash
WARNING: MariaDB addon was found! It can't be configured automatically due to the way Nextcloud works, but you can configure it manually when running the web UI for the first time using those values :
@@ -153,8 +153,8 @@ and you want to overcome this, follow the below steps:
Host-name : core-mariadb:3306
```
- 1. Go back at `mariadb` add-on, configure it with above credentials and restart it. Make sure the add-on is creating the `nextcloud` database.
- 1. Go in the webui and fill all required info. Here you can view an example:
- 3. Go back at `mariadb` add-on, configure it with above credentials and restart it. Make sure the add-on is creating the `nextcloud` database.
- 4. Go in the webui and fill all required info. Here you can view an example:
![image](https://user-images.githubusercontent.com/19391765/207888717-50b43002-a5e2-4782-b5c9-1f582309df2b.png)

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

View File

View File

View File

@@ -108,7 +108,7 @@ wait_for_postgres() {
while ! pg_isready -h "$DB_HOSTNAME" -p "$DB_PORT" -U "$DB_USERNAME" > /dev/null 2>&1; do
tries=$((tries + 1))
[ "$tries" -ge 60 ] && {
bashio::log.error "Postgres did not start after 2 minutes, aborting."
bashio::log.error "Postgres did not start after 2minutes, aborting."
exit 1
}
echo "PostgreSQL is starting up... ($tries/60)"

View File

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

View File

@@ -178,8 +178,7 @@ read -r VPN_IP VPN_COUNTRY <<< "${VPN_INFO_OUT}"
bashio::log.info "VPN external IP: ${VPN_IP} (${VPN_COUNTRY})"
while true; do
sleep "${VPN_CHECK_INTERVAL}" &
wait $!
sleep "${VPN_CHECK_INTERVAL}" & wait $!
if ! current_out="$(get_ip_info)"; then
bashio::log.warning "Failed to refresh external IP; keeping previous assumptions."

6
qbittorrent/rootfs/etc/services.d/vpn-upnp/run Executable file → Normal file
View File

@@ -253,9 +253,9 @@ config["Interval"]=$((${VPN_UPNP_INTERVAL} + (${VPN_UPNP_INTERVAL} / 2)))
bashio::log.info "VPN UPnP enabled, starting NAT-PMP/UPnP monitor with an interval of ${VPN_UPNP_INTERVAL} seconds (UPnP lease interval: ${config["Interval"]} seconds)."
while true; do
sleep ${VPN_UPNP_INTERVAL} &
wait $!
while true;
do
sleep ${VPN_UPNP_INTERVAL} & wait $!
if pnat_set; then
bashio::log.info "NAT-PMP/UPnP Ok!"

0
qbittorrent/rootfs/usr/local/sbin/vpn Normal file → Executable file
View File

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

0
sabnzbd/rootfs/etc/services.d/nginx/finish Normal file → Executable file
View File

View File

@@ -73,8 +73,8 @@ validate_backup_against_source() {
find . -mindepth 1 -print | sed -e 's#^\./##' -e 's#/$##' | LC_ALL=C sort
) > "$source_manifest"
tar -tzf "$archive" |
sed -e "s#^${archive_root}/##" -e '/^$/d' -e 's#/$##' |
tar -tzf "$archive" | \
sed -e "s#^${archive_root}/##" -e '/^$/d' -e 's#/$##' | \
LC_ALL=C sort > "$archive_manifest"
if ! cmp -s "$source_manifest" "$archive_manifest"; then

0
scrutiny_fa/rootfs_overlay/etc/services.d/nginx/finish Normal file → Executable file
View File

View File

@@ -73,8 +73,8 @@ validate_backup_against_source() {
find . -mindepth 1 -print | sed -e 's#^\./##' -e 's#/$##' | LC_ALL=C sort
) > "$source_manifest"
tar -tzf "$archive" |
sed -e "s#^${archive_root}/##" -e '/^$/d' -e 's#/$##' |
tar -tzf "$archive" | \
sed -e "s#^${archive_root}/##" -e '/^$/d' -e 's#/$##' | \
LC_ALL=C sort > "$archive_manifest"
if ! cmp -s "$source_manifest" "$archive_manifest"; then

View File

@@ -108,8 +108,8 @@ sed -i "s|/shared|$DATA_LOCATION|g" /home/seafile/*.sh
ADMIN_EMAIL_VAL="$(bashio::config 'SEAFILE_ADMIN_EMAIL')"
ADMIN_PASSWORD_VAL="$(bashio::config 'SEAFILE_ADMIN_PASSWORD')"
if [[ -n "${ADMIN_EMAIL_VAL}" && "${ADMIN_EMAIL_VAL}" != "null" &&
-n "${ADMIN_PASSWORD_VAL}" && "${ADMIN_PASSWORD_VAL}" != "null" ]]; then
if [[ -n "${ADMIN_EMAIL_VAL}" && "${ADMIN_EMAIL_VAL}" != "null" \
&& -n "${ADMIN_PASSWORD_VAL}" && "${ADMIN_PASSWORD_VAL}" != "null" ]]; then
bashio::log.info "Seeding admin credentials"
mkdir -p "${DATA_LOCATION}/conf"

View File

@@ -1,7 +1,8 @@
arch:
- aarch64
- amd64
description: Open-source media request and discovery manager for Jellyfin, Plex, and Emby
description:
Open-source media request and discovery manager for Jellyfin, Plex, and Emby
devices:
- /dev/dri
- /dev/dri/card0

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

0
seerr/rootfs/etc/services.d/seerr/finish Normal file → Executable file
View File

View File

@@ -42,5 +42,6 @@ if [[ ! -f /data/version || "$current_version" != "$(cat /data/version)" ]]; the
fi
fi
bashio::log.info "Starting application"
sudo -u "$USER" -s /bin/sh -c "/home/node/signalk/startup.sh"

View File

View File

@@ -167,8 +167,8 @@ case "$(bashio::config "DB_CONNECTION")" in
-u "${ZM_DB_USER}" -p"${ZM_DB_PASS}" \
-h "${ZM_DB_HOST}" -P "${ZM_DB_PORT}" \
--routines --events --triggers \
"${LEGACY_DB_NAME}" \
| mysql \
"${LEGACY_DB_NAME}" | \
mysql \
-u "${ZM_DB_USER}" -p"${ZM_DB_PASS}" \
-h "${ZM_DB_HOST}" -P "${ZM_DB_PORT}" \
"${ZM_DB_NAME}"; then

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

View File

@@ -1,7 +1,8 @@
arch:
- aarch64
- amd64
description: Fork of overseerr for jellyfin support (deprecated, migrate to Seerr add-on)
description:
Fork of overseerr for jellyfin support (deprecated, migrate to Seerr add-on)
devices:
- /dev/dri
- /dev/dri/card0

View File

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

0
zzz_archived_omada/rootfs/etc/cont-init.d/99-run.sh Executable file → Normal file
View File

View File

View File

View File

@@ -90,10 +90,7 @@ set +u
# container_environment, which is read by `with-contenv` when each service starts.
S6_ENV_DIR=""
for candidate in /var/run/s6/container_environment /run/s6/container_environment; do
if [ -d "$candidate" ]; then
S6_ENV_DIR="$candidate"
break
fi
if [ -d "$candidate" ]; then S6_ENV_DIR="$candidate"; break; fi
done
# For all relevant variables

View File

@@ -38,7 +38,8 @@ fi
# Checks if client names where configured when using stealth mode
if bashio::config.true 'hidden_services' \
&& bashio::config.true 'stealth' \
&& ! bashio::config.has_value 'client_names'; then
&& ! bashio::config.has_value 'client_names';
then
bashio::log.fatal
bashio::log.fatal 'Add-on configuration is incomplete.'
bashio::log.fatal
@@ -131,7 +132,8 @@ fi
# Configure bridges
if bashio::config.exists 'bridges' \
&& ! bashio::config.is_empty 'bridges'; then
&& ! bashio::config.is_empty 'bridges';
then
bashio::log.info 'Use bridges:'
echo "UseBridges 1" >> "${torrc}"
@@ -174,12 +176,14 @@ if bashio::config.true 'hidden_services'; then
fi
# Configure stealth mode
if bashio::config.true 'hidden_services' && bashio::config.true 'stealth'; then
if bashio::config.true 'hidden_services' && bashio::config.true 'stealth';
then
# Following the documentation at:
# https://community.torproject.org/onion-services/advanced/client-auth/
while read -r clientname; do
# Generate key is they do not exist yet
if ! bashio::fs.file_exists "${authorized_clients_dir}/${clientname}.auth"; then
if ! bashio::fs.file_exists "${authorized_clients_dir}/${clientname}.auth"
then
key=$(openssl genpkey -algorithm x25519)
private_key=$(

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