Compare commits

...

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
fc33543d4b Merge remote-tracking branch 'origin/master' into create-pull-request/patch-1788655917
# Conflicts:
#	.claude/skills/hassio-addon-workflow/scripts/pr_review.sh

Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
2026-09-07 11:26:55 +00:00
alexbelgium
df62d357d8 fix: auto-fix linting issues 2026-09-06 00:51:57 +00:00
295 changed files with 2665 additions and 2543 deletions

View File

@@ -30,8 +30,14 @@ 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} =="
@@ -39,7 +45,7 @@ echo
echo "1. /data/options.json (the user's saved options)"
if [ -f /data/options.json ]; then
python3 - "$VAR" <<'PY'
python3 - "$VAR" << 'PY'
import json, sys
var = sys.argv[1]
try:
@@ -80,7 +86,8 @@ 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"
@@ -122,8 +129,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

@@ -31,7 +31,7 @@ awk '{t+=$2} END{printf " threads=%d\n", t}' <(ps -eo pid,nlwp --no-headers 2>
echo
echo "== per-process memory (top 20 by PSS) =="
printf ' %-28s %8s %8s %8s\n' COMMAND RSS PSS PRIVATE
python3 - "$OUT" <<'PY'
python3 - "$OUT" << 'PY'
import os, sys
rows = []
for pid in filter(str.isdigit, os.listdir('/proc')):
@@ -62,17 +62,17 @@ PY
echo
echo "== reserved-but-not-resident (lazy allocations, NOT leaks) =="
ipcs -m 2>/dev/null | awk 'NR>3 && $5 ~ /^[0-9]+$/ && $5 > 50000000 {printf " SysV shm %.0f MB (owner %s) — check Rss in /proc/<pid>/smaps before calling it used\n", $5/1048576, $3}'
ipcs -m 2> /dev/null | awk 'NR>3 && $5 ~ /^[0-9]+$/ && $5 > 50000000 {printf " SysV shm %.0f MB (owner %s) — check Rss in /proc/<pid>/smaps before calling it used\n", $5/1048576, $3}'
echo
echo "== CPU over ${SAMPLE}s (idle unless you are driving the UI) =="
# utime+stime. Parsed after the LAST ')' because field 2 is (comm) and may contain spaces —
# a plain $14+$15 is wrong for anything like 'npm exec @foo' and silently reports a fabricated
# number rather than failing.
jiffies() { awk -F') ' '{n=split($NF,a," "); print a[12]+a[13]}' "/proc/$1/stat" 2>/dev/null; }
jiffies() { awk -F') ' '{n=split($NF,a," "); print a[12]+a[13]}' "/proc/$1/stat" 2> /dev/null; }
# jiffies are USER_HZ units — almost always 100, but read it rather than assume it
HZ=$(getconf CLK_TCK 2>/dev/null) && [ "$HZ" -gt 0 ] 2>/dev/null || HZ=100
HZ=$(getconf CLK_TCK 2> /dev/null) && [ "$HZ" -gt 0 ] 2> /dev/null || HZ=100
# Sample EVERY readable process, not the top-N of ps.txt: that list is sorted by RSS,
# and the busiest process is not necessarily a big one.
@@ -86,14 +86,14 @@ for pid in "${!t0[@]}"; do
[ -r "/proc/$pid/stat" ] || continue
t1=$(jiffies "$pid") || continue
[ -n "$t1" ] && [ -n "${t0[$pid]}" ] || continue
delta=$(( t1 - ${t0[$pid]} ))
delta=$((t1 - ${t0[$pid]}))
[ "$delta" -gt 0 ] || continue
pct=$(awk -v d="$delta" -v s="$SAMPLE" -v hz="$HZ" 'BEGIN{printf "%.2f", d*100/(hz*s)}')
comm=$(tr -d '\0' < "/proc/$pid/comm" 2>/dev/null)
comm=$(tr -d '\0' < "/proc/$pid/comm" 2> /dev/null)
echo "$pct $pid $comm"
done | sort -rn | head -12 | awk '{printf " %6s%% %-8s %s\n", $1, $2, $3}'
echo
echo " established conns on :8082/:3000/:3001 = $(ss -tn 2>/dev/null | grep -cE 'ESTAB.*:(8082|3000|3001)')"
echo " established conns on :8082/:3000/:3001 = $(ss -tn 2> /dev/null | grep -cE 'ESTAB.*:(8082|3000|3001)')"
echo " (those are claude_desktop/webtop viewer ports; 0 here means CPU above is idle burn)"
echo " raw ps: $OUT/ps.txt"

View File

@@ -47,7 +47,10 @@ 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
@@ -69,8 +72,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,22 +13,31 @@ 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 =="
@@ -36,14 +45,22 @@ 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"
@@ -65,13 +82,16 @@ 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' || { note "config.yaml" "FAIL parse"; fail=1; }
python3 - "$ADDON/config.yaml" << 'PY' || {
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)"
@@ -79,7 +99,10 @@ 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" ] && {
@@ -96,7 +119,8 @@ 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"
@@ -110,7 +134,8 @@ 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.
@@ -125,14 +150,26 @@ 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)
@@ -141,7 +178,8 @@ 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

@@ -40,7 +40,7 @@ the check is a guard, not a common case.)
## Rule 1 — pick exactly one verdict
| verdict | when |
|---|---|
| --- | --- |
| `duplicate` | An existing open or closed issue reports the same thing. Set `duplicate_of`. |
| `needs-info` | You cannot tell what is wrong without the add-on version, HA version, architecture, config, or the actual log output. |
| `question` | A usage question answerable from `DOCS.md`, the wiki, or the add-on config. Not a defect. |

View File

@@ -39,7 +39,7 @@ rest are on you: nothing checks them, so breaking one ships silently.
that value, and compare:
| `version` vs `U` | what to do | example |
|---|---|---|
| --- | --- | --- |
| identical | **append** `.1` | sonarr `4.0.19.3001` -> `4.0.19.3001.1` |
| `U` + `.` + digits | **increment** those digits | radarr `6.3.0.10514.1` -> `6.3.0.10514.2` |
| anything else | **leave it alone** | plex, readarr, joal |

View File

@@ -44,7 +44,7 @@ for pr in $PRS; do
echo "$BAD"
# Ensure the label exists before adding it — with set -e a missing
# label would abort the whole loop and skip any PRs behind this one.
gh label create "ai:blocked" --repo "$REPO" --color ededed >/dev/null 2>&1 || true
gh label create "ai:blocked" --repo "$REPO" --color ededed > /dev/null 2>&1 || true
gh pr ready "$pr" --repo "$REPO" --undo || true
gh pr edit "$pr" --repo "$REPO" --add-label "ai:blocked"
gh pr comment "$pr" --repo "$REPO" --body \

View File

@@ -21,14 +21,17 @@ TITLE=$(jq -r '.title' "$OUT/issue.json")
# ---------------------------------------------------------------- addon slug
# Titles follow "🐛 [Immich Frame] ENV_VARS arent being picked up".
RAW=$(sed -n 's/.*\[\([^]]*\)\].*/\1/p' <<<"$TITLE" | head -n1)
RAW=$(sed -n 's/.*\[\([^]]*\)\].*/\1/p' <<< "$TITLE" | head -n1)
ADDON=""
if [ -n "$RAW" ]; then
CAND=$(tr '[:upper:] ' '[:lower:]_' <<<"$RAW")
CAND=$(tr '[:upper:] ' '[:lower:]_' <<< "$RAW")
# 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
@@ -36,9 +39,12 @@ if [ -n "$RAW" ]; then
# below, which picks the shorter "calibre" instead — the wrong add-on.
# Stripping -, _, . from both sides before comparing catches this case.
if [ -z "$ADDON" ]; then
CAND_STRIPPED=$(tr -d '_.-' <<<"$CAND")
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.
@@ -99,7 +105,7 @@ if [ -n "$ADDON" ]; then
echo
echo "## Recent commits touching ${ADDON}/"
git log -n 15 --date=short --pretty='- %ad %h %s' -- "$ADDON" 2>/dev/null || true
git log -n 15 --date=short --pretty='- %ad %h %s' -- "$ADDON" 2> /dev/null || true
fi
} >> "$CTX"
fi
@@ -108,14 +114,14 @@ fi
{
echo
echo "## Similar existing issues (candidate duplicates)"
KEYWORDS=$(tr -cs '[:alnum:]' ' ' <<<"$TITLE" \
KEYWORDS=$(tr -cs '[:alnum:]' ' ' <<< "$TITLE" \
| tr '[:upper:]' '[:lower:]' \
| tr ' ' '\n' | awk 'length($0)>3' | head -n6 | paste -sd' ')
# Excludes the issue being triaged: if it's already indexed by GitHub search
# by the time this runs, keyword overlap with its own title would otherwise
# list it as a "candidate duplicate" of itself.
gh search issues --repo "$REPO" --limit 15 \
--json number,title,state,url -- "$KEYWORDS" 2>/dev/null \
--json number,title,state,url -- "$KEYWORDS" 2> /dev/null \
| jq -r --argjson self "$ISSUE_NUMBER" \
'.[] | select(.number != $self) | "- #\(.number) [\(.state)] \(.title)"' \
|| echo "(search unavailable)"

View File

@@ -6,7 +6,7 @@ set -e
# Banner
# ======================================================================
if ! bashio::supervisor.ping 2>/dev/null; then
if ! bashio::supervisor.ping 2> /dev/null; then
# Source standalone bashio first to provide function definitions
if [ -f /usr/local/lib/bashio-standalone.sh ]; then
source /usr/local/lib/bashio-standalone.sh
@@ -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
@@ -49,7 +49,7 @@ bashio::log.green ' Provided by: https://github.com/alexbelgium/hassio-addons '
bashio::log.blue '-----------------------------------------------------------'
# Adapt user abc
if command -v id &>/dev/null && id abc &>/dev/null; then
if command -v id &> /dev/null && id abc &> /dev/null; then
if bashio::config.has_value "PUID" && bashio::config.has_value "PGID"; then
PUID="$(bashio::config "PUID")"
PGID="$(bashio::config "PGID")"

View File

@@ -152,15 +152,15 @@ 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
################################################################################
# Guard: only run inside Supervisor-managed add-ons
################################################################################
if ! bashio::supervisor.ping 2>/dev/null; then
if ! bashio::supervisor.ping 2> /dev/null; then
echo "..."
exit 0
fi
@@ -179,7 +179,7 @@ ENV_FILE="/.env"
ETC_ENV_FILE="/etc/environment"
[[ -f "$JSONSOURCE" ]] || bashio::exit.nok "Missing $JSONSOURCE"
command -v jq >/dev/null || bashio::exit.nok "jq is required"
command -v jq > /dev/null || bashio::exit.nok "jq is required"
mkdir -p /etc
touch "$ETC_ENV_FILE"
@@ -193,7 +193,10 @@ 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

@@ -4,7 +4,7 @@
set -e
if ! bashio::supervisor.ping 2>/dev/null; then
if ! bashio::supervisor.ping 2> /dev/null; then
bashio::log.blue "Disabled : please use another method"
exit 0
fi
@@ -33,16 +33,16 @@ test_mount() {
_test_write() {
local testfile="$mountpoint/.writetest_$$"
if : >"$testfile" 2>/dev/null; then
rm -f "$testfile" 2>/dev/null || true
if : > "$testfile" 2> /dev/null; then
rm -f "$testfile" 2> /dev/null || true
return 0
else
rm -f "$testfile" 2>/dev/null || true
rm -f "$testfile" 2> /dev/null || true
return 1
fi
}
if _test_write 2>/dev/null; then
if _test_write 2> /dev/null; then
MOUNTED=true
return 0
fi
@@ -53,9 +53,9 @@ test_mount() {
echo "... retrying mount with noserverino"
MOUNTOPTIONS="${orig_mountoptions},noserverino"
umount "$mountpoint" 2>/dev/null || true
umount "$mountpoint" 2> /dev/null || true
if mount_drive "$MOUNTOPTIONS"; then
if _test_write 2>/dev/null; then
if _test_write 2> /dev/null; then
MOUNTED=true
return 0
fi
@@ -66,9 +66,9 @@ test_mount() {
echo "... retrying mount with noperm"
MOUNTOPTIONS="${orig_mountoptions},noperm"
umount "$mountpoint" 2>/dev/null || true
umount "$mountpoint" 2> /dev/null || true
if mount_drive "$MOUNTOPTIONS"; then
if _test_write 2>/dev/null; then
if _test_write 2> /dev/null; then
MOUNTED=true
return 0
fi
@@ -88,9 +88,9 @@ mount_drive() {
MOUNTOPTIONS="$1"
if [[ "$FSTYPE" == "cifs" ]]; then
mount -t cifs -o "$MOUNTOPTIONS" "$disk" "/mnt/$diskname" 2>"$ERRORCODE_FILE" || MOUNTED=false
mount -t cifs -o "$MOUNTOPTIONS" "$disk" "/mnt/$diskname" 2> "$ERRORCODE_FILE" || MOUNTED=false
elif [[ "$FSTYPE" == "nfs" ]]; then
mount -t nfs -o "$MOUNTOPTIONS" "$disk" "/mnt/$diskname" 2>"$ERRORCODE_FILE" || MOUNTED=false
mount -t nfs -o "$MOUNTOPTIONS" "$disk" "/mnt/$diskname" 2> "$ERRORCODE_FILE" || MOUNTED=false
fi
if [[ "$MOUNTED" == "true" ]]; then
@@ -106,7 +106,7 @@ retry_cifs_with_vers_ladder_on_dialect_failure() {
local err mountpoint
mountpoint="/mnt/$diskname"
err="$(cat "$ERRORCODE_FILE" 2>/dev/null || true)"
err="$(cat "$ERRORCODE_FILE" 2> /dev/null || true)"
if echo "$err" | grep -Eq 'mount error\(13\)|Permission denied|NT_STATUS_(LOGON_FAILURE|ACCESS_DENIED)|STATUS_(LOGON_FAILURE|ACCESS_DENIED)'; then
return 0
@@ -132,7 +132,7 @@ retry_cifs_with_vers_ladder_on_dialect_failure() {
for vers in "${vers_variants[@]}"; do
for sectry in "${sec_variants[@]}"; do
[[ "$MOUNTED" == "false" ]] || break
umount "$mountpoint" 2>/dev/null || true
umount "$mountpoint" 2> /dev/null || true
try_opts="${base_opts}${vopt},vers=${vers}${sectry}"
mount_drive "$try_opts"
done
@@ -154,7 +154,7 @@ retry_cifs_with_vers_ladder_on_dialect_failure() {
for vers in "${vers_variants2[@]}"; do
for sectry in "${sec_variants[@]}"; do
[[ "$MOUNTED" == "false" ]] || break
umount "$mountpoint" 2>/dev/null || true
umount "$mountpoint" 2> /dev/null || true
try_opts="${base_opts}${vopt},vers=${vers}${sectry}"
mount_drive "$try_opts"
done
@@ -167,7 +167,7 @@ retry_cifs_with_vers_ladder_on_dialect_failure() {
for vopt in "${opt_variants[@]}"; do
for vers in "${vers_variants2[@]}"; do
[[ "$MOUNTED" == "false" ]] || break
umount "$mountpoint" 2>/dev/null || true
umount "$mountpoint" 2> /dev/null || true
try_opts="${base_opts}${vopt},vers=${vers},sec=ntlmssp"
mount_drive "$try_opts"
done
@@ -246,13 +246,13 @@ if bashio::config.has_value 'networkdisks'; then
diskname="${diskname//\\//}"
diskname="${diskname##*/}"
if mountpoint -q "/mnt/$diskname" 2>/dev/null; then
if mountpoint -q "/mnt/$diskname" 2> /dev/null; then
bashio::log.warning "...... mount point /mnt/$diskname already in use (name collision for $disk). Skipping this share."
continue
fi
ERRORCODE_FILE="/tmp/mount_error_${diskname//[^a-zA-Z0-9._-]/_}.log"
: >"$ERRORCODE_FILE" || true
: > "$ERRORCODE_FILE" || true
MOUNTED=false
CIFS_LADDER_ATTEMPTED=false
@@ -266,13 +266,13 @@ if bashio::config.has_value 'networkdisks'; then
if [[ "$FSTYPE" == "cifs" ]]; then
if [[ ! "$disk" =~ ^//[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/.+ ]]; then
bashio::log.fatal "...... invalid CIFS path \"$disk\". Use //123.12.12.12/sharedfolder,//123.12.12.12/sharedfolder2"
echo "Invalid CIFS path structure: $disk" >"$ERRORCODE_FILE" || true
echo "Invalid CIFS path structure: $disk" > "$ERRORCODE_FILE" || true
continue
fi
else
if [[ ! "$disk" =~ ^[^/:]+:/.+ ]]; then
bashio::log.fatal "...... invalid NFS path \"$disk\". Use server:/export/path or 123.12.12.12:/export/path"
echo "Invalid NFS path structure: $disk" >"$ERRORCODE_FILE" || true
echo "Invalid NFS path structure: $disk" > "$ERRORCODE_FILE" || true
continue
fi
fi
@@ -289,7 +289,7 @@ if bashio::config.has_value 'networkdisks'; then
if [[ -n "${CIFSDOMAIN:-}" ]]; then
printf 'domain=%s\n' "$CIFSDOMAIN"
fi
} >"$CRED_FILE"
} > "$CRED_FILE"
fi
if [[ "$FSTYPE" == "cifs" ]]; then
@@ -303,8 +303,8 @@ if bashio::config.has_value 'networkdisks'; then
if [[ "$MOUNTED" == "false" ]]; then
if [[ "$FSTYPE" == "cifs" ]]; then
if command -v nmap >/dev/null 2>&1; then
output="$(nmap -F "$server" -T5 -oG - 2>/dev/null || true)"
if command -v nmap > /dev/null 2>&1; then
output="$(nmap -F "$server" -T5 -oG - 2> /dev/null || true)"
if ! echo "$output" | grep -q "445/open"; then
if echo "$output" | grep -q "/open"; then
bashio::log.fatal "...... $server is reachable but SMB port not opened, stopping script"
@@ -312,7 +312,7 @@ if bashio::config.has_value 'networkdisks'; then
bashio::log.fatal "...... fatal : $server not reachable, is it correct"
fi
cleanup_cred
rm -f "$ERRORCODE_FILE" 2>/dev/null || true
rm -f "$ERRORCODE_FILE" 2> /dev/null || true
continue
fi
echo "...... $server is confirmed reachable"
@@ -320,13 +320,13 @@ if bashio::config.has_value 'networkdisks'; then
bashio::log.warning "...... nmap not available; skipping SMB port reachability test"
fi
if command -v smbclient >/dev/null 2>&1; then
if command -v smbclient > /dev/null 2>&1; then
OUTPUT="$(smbclient -t 2 -L "$server" -U "$USERNAME%$PASSWORD" -c "exit" $DOMAINCLIENT 2>&1 || true)"
if echo "$OUTPUT" | grep -q "LOGON_FAILURE"; then
bashio::log.fatal "...... incorrect Username, Password, or Domain! Script will stop."
cleanup_cred
rm -f "$ERRORCODE_FILE" 2>/dev/null || true
rm -f "$ERRORCODE_FILE" 2> /dev/null || true
bashio::addon.stop
elif echo "$OUTPUT" | grep -q "tree connect failed" || echo "$OUTPUT" | grep -q "NT_STATUS_CONNECTION_DISCONNECTED"; then
echo "... using SMBv1"
@@ -344,8 +344,8 @@ if bashio::config.has_value 'networkdisks'; then
SMBRAW=""
SMB1_DETECTED=false
if command -v nmap >/dev/null 2>&1; then
NMAP_OUTPUT="$(nmap --script smb-protocols -p 445 "$server" 2>/dev/null || true)"
if command -v nmap > /dev/null 2>&1; then
NMAP_OUTPUT="$(nmap --script smb-protocols -p 445 "$server" 2> /dev/null || true)"
SMBRAW="$(
echo "$NMAP_OUTPUT" \
| awk '/SMB2_DIALECT_/ {print $NF}' \
@@ -364,7 +364,7 @@ if bashio::config.has_value 'networkdisks'; then
302) SMBVERS=",vers=3.02" ;;
300) SMBVERS=",vers=3.0" ;;
210) SMBVERS=",vers=2.1" ;;
202|200) SMBVERS=",vers=2.0" ;;
202 | 200) SMBVERS=",vers=2.0" ;;
*) SMBVERS="" ;;
esac
@@ -376,7 +376,7 @@ if bashio::config.has_value 'networkdisks'; then
if [[ -n "$SMBVERS" ]]; then
echo "...... SMB version detected : ${SMBVERS#,vers=}"
elif command -v smbclient >/dev/null 2>&1 && smbclient -t 2 -L "$server" -m NT1 -U "$USERNAME%$PASSWORD" $DOMAINCLIENT -c "exit" &>/dev/null; then
elif command -v smbclient > /dev/null 2>&1 && smbclient -t 2 -L "$server" -m NT1 -U "$USERNAME%$PASSWORD" $DOMAINCLIENT -c "exit" &> /dev/null; then
echo "...... SMB version : only SMBv1 is supported, this can lead to issues"
SECVERS=",sec=ntlm"
SMBVERS=",vers=1.0"
@@ -405,8 +405,8 @@ if bashio::config.has_value 'networkdisks'; then
fi
else
if command -v nmap >/dev/null 2>&1; then
output="$(nmap -F "$server" -T5 -oG - 2>/dev/null || true)"
if command -v nmap > /dev/null 2>&1; then
output="$(nmap -F "$server" -T5 -oG - 2> /dev/null || true)"
if ! echo "$output" | grep -Eq '(2049|111)/open'; then
bashio::log.fatal "...... $server is reachable but NFS ports not open"
fi
@@ -424,7 +424,7 @@ if bashio::config.has_value 'networkdisks'; then
if [[ "$MOUNTED" == "true" ]]; then
bashio::log.info "...... $disk successfully mounted to /mnt/$diskname with options ${MOUNTOPTIONS/$PASSWORD/XXXXXXXXXX}"
rm -f "$ERRORCODE_FILE" 2>/dev/null || true
rm -f "$ERRORCODE_FILE" 2> /dev/null || true
if [[ "$FSTYPE" == "cifs" && "$MOUNTOPTIONS" == *"vers=1.0"* ]]; then
bashio::log.warning ""
@@ -437,14 +437,14 @@ if bashio::config.has_value 'networkdisks'; then
elif [[ "$MOUNTED" == "readonly" ]]; then
bashio::log.warning "...... $disk mounted to /mnt/$diskname but is READ-ONLY or not writable by UID/GID ${PUID#,uid=}:${PGID#,gid=}."
bashio::log.warning "...... Check Samba share permissions, or try setting PUID/PGID to 0/0 (root), or adjust server ACLs."
rm -f "$ERRORCODE_FILE" 2>/dev/null || true
rm -f "$ERRORCODE_FILE" 2> /dev/null || true
cleanup_cred
else
if [[ "$FSTYPE" == "cifs" ]]; then
bashio::log.fatal "Error, unable to mount $disk to /mnt/$diskname with username $USERNAME. Please check share path, username/password/domain; try UID/GID 0."
bashio::log.fatal "Here is some debugging info :"
if command -v smbclient >/dev/null 2>&1; then
if command -v smbclient > /dev/null 2>&1; then
smbclient -t 2 -L "$server" -U "$USERNAME%$PASSWORD" -c "exit" $DOMAINCLIENT || true
else
bashio::log.warning "smbclient not available; cannot print SMB debugging info"
@@ -459,13 +459,13 @@ if bashio::config.has_value 'networkdisks'; then
mount_drive "rw"
fi
ERR_READ="$(cat "$ERRORCODE_FILE" 2>/dev/null || true)"
ERR_READ="$(cat "$ERRORCODE_FILE" 2> /dev/null || true)"
bashio::log.fatal "Error read : ${ERR_READ:-unknown error}, addon will stop in 1 min"
umount "/mnt/$diskname" 2>/dev/null || true
rmdir "/mnt/$diskname" 2>/dev/null || true
umount "/mnt/$diskname" 2> /dev/null || true
rmdir "/mnt/$diskname" 2> /dev/null || true
cleanup_cred
rm -f "$ERRORCODE_FILE" 2>/dev/null || true
rm -f "$ERRORCODE_FILE" 2> /dev/null || true
bashio::addon.stop
fi

View File

@@ -138,9 +138,9 @@ append_unique_line() {
# $1=file, $2=line
local _file="$1"
local _line="$2"
mkdir -p "$(dirname "$_file")" 2>/dev/null || true
touch "$_file" 2>/dev/null || true
grep -qxF -- "$_line" "$_file" 2>/dev/null || echo "$_line" >> "$_file"
mkdir -p "$(dirname "$_file")" 2> /dev/null || true
touch "$_file" 2> /dev/null || true
grep -qxF -- "$_line" "$_file" 2> /dev/null || echo "$_line" >> "$_file"
}
while IFS= read -r line; do

View File

@@ -11,14 +11,20 @@ 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; }
if "$t" >/dev/null 2>&1; then
rm -f "$t" 2>/dev/null || true
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"
return 0
fi
rm -f "$t" 2>/dev/null || true
rm -f "$t" 2> /dev/null || true
fi
done
return 1
@@ -90,7 +96,7 @@ validate_shebang() {
local errfile msg
# shellcheck disable=SC2206
local cmd=( $candidate )
local cmd=($candidate)
local exe="${cmd[0]}"
if [ ! -x "$exe" ]; then
@@ -103,18 +109,18 @@ validate_shebang() {
{
printf '#!%s\n' "$candidate"
printf '%s\n' "$probe_script_content"
} >"$tmp"
chmod 700 "$tmp" 2>/dev/null || true
} > "$tmp"
chmod 700 "$tmp" 2> /dev/null || true
set +e
out="$("$tmp" 2>"$errfile")"
out="$("$tmp" 2> "$errfile")"
rc=$?
set -e
rm -f "$tmp" 2>/dev/null || true
rm -f "$tmp" 2> /dev/null || true
if [ "$rc" -eq 0 ] && [ -n "${out:-}" ] && [ "$out" != "null" ]; then
rm -f "$errfile" 2>/dev/null || true
rm -f "$errfile" 2> /dev/null || true
return 0
fi
@@ -126,7 +132,7 @@ validate_shebang() {
msg+=$' stderr: <empty>\n'
fi
SHEBANG_ERRORS+=("$msg")
rm -f "$errfile" 2>/dev/null || true
rm -f "$errfile" 2> /dev/null || true
return 1
}
@@ -147,7 +153,7 @@ if [ -z "$shebang" ]; then
exit 1
fi
if ! command -v bashio::addon.version >/dev/null 2>&1; then
if ! command -v bashio::addon.version > /dev/null 2>&1; then
for f in /usr/lib/bashio/bashio.sh /usr/lib/bashio/lib.sh /usr/src/bashio/bashio.sh /usr/local/lib/bashio/bashio.sh /usr/local/lib/bashio-standalone.sh; do
if [ -f "$f" ]; then
# shellcheck disable=SC1090
@@ -162,7 +168,7 @@ fi
##################
# Exit if /config is not mounted or HA not used
if [ ! -d /config ] || ! bashio::supervisor.ping 2>/dev/null; then
if [ ! -d /config ] || ! bashio::supervisor.ping 2> /dev/null; then
echo "..."
exit 0
fi
@@ -195,7 +201,7 @@ if [ ! -f "$CONFIGSOURCE" ]; then
fi
# Convert scripts to linux
dos2unix "$CONFIGSOURCE" >/dev/null 2>&1 || true
dos2unix "$CONFIGSOURCE" > /dev/null 2>&1 || true
chmod +x "$CONFIGSOURCE"
if grep -q 'bashio' "$CONFIGSOURCE"; then

View File

@@ -46,7 +46,8 @@ _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)"
}
@@ -55,7 +56,7 @@ _bashio_log() {
# -----------------------------------------------------------------------------
_bashio_is_true() {
case "${1:-}" in
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
1 | true | TRUE | True | yes | YES | Yes | on | ON | On) return 0 ;;
*) return 1 ;;
esac
}
@@ -99,7 +100,7 @@ _bashio_env_get() {
_bashio_env_has() {
local key="${1:-}"
[ -n "$key" ] || return 1
_bashio_env_get "$key" >/dev/null 2>&1
_bashio_env_get "$key" > /dev/null 2>&1
}
# JSON options source (jq required). Prints value or empty; returns 0 always.
@@ -110,10 +111,10 @@ _bashio_json_get() {
[ -n "$key" ] || return 0
[ -n "$file" ] || return 0
[ -f "$file" ] || return 0
command -v jq >/dev/null 2>&1 || return 0
command -v jq > /dev/null 2>&1 || return 0
# getpath(split(".")) supports nested access; missing => empty
jq -er --arg k "$key" 'getpath(($k|split("."))) // empty' "$file" 2>/dev/null || true
jq -er --arg k "$key" 'getpath(($k|split("."))) // empty' "$file" 2> /dev/null || true
}
# Net wait using /dev/tcp with a timeout
@@ -124,7 +125,7 @@ _bashio_tcp_wait() {
local start now
start="$(date +%s)"
while :; do
if exec 3<>"/dev/tcp/${host}/${port}" 2>/dev/null; then
if exec 3<> "/dev/tcp/${host}/${port}" 2> /dev/null; then
exec 3>&- 3<&-
return 0
fi
@@ -138,10 +139,10 @@ _bashio_tcp_wait() {
# Prefer nc if present, fallback to /dev/tcp
_bashio_tcp_wait_nc() {
command -v nc >/dev/null 2>&1 || return 1
command -v nc > /dev/null 2>&1 || return 1
local host="${1:-}" port="${2:-}" to="${3:-30}"
# BusyBox and OpenBSD nc differ; cover both styles
nc -z -w "$to" "$host" "$port" 2>/dev/null || nc -z "$host" "$port" 2>/dev/null
nc -z -w "$to" "$host" "$port" 2> /dev/null || nc -z "$host" "$port" 2> /dev/null
}
# -----------------------------------------------------------------------------
@@ -173,7 +174,7 @@ bashio::supervisor.ping() {
bashio::addon.name() { printf '%s' "${ADDON_NAME:-Standalone container}"; }
bashio::addon.description() { printf '%s' "${ADDON_DESCRIPTION:-Running without Home Assistant Supervisor}"; }
bashio::addon.version() { printf '%s' "${BUILD_VERSION:-1.0}"; }
bashio::addon.version_latest(){ printf '%s' "${ADDON_VERSION_LATEST:-${BUILD_VERSION:-1.0}}"; }
bashio::addon.version_latest() { printf '%s' "${ADDON_VERSION_LATEST:-${BUILD_VERSION:-1.0}}"; }
bashio::addon.update_available() {
if [ -n "${ADDON_VERSION_LATEST:-}" ] && [ "${ADDON_VERSION_LATEST:-}" != "${BUILD_VERSION:-}" ]; then
@@ -194,11 +195,11 @@ bashio::addon.port() {
local arg="${1:-}"
if [[ "$arg" =~ ^[0-9]+$ ]]; then
local v=""
v="$(_bashio_env_get "PORT_${arg}" 2>/dev/null || true)"
[ -z "$v" ] && v="$(_bashio_env_get "ADDON_PORT_${arg}" 2>/dev/null || true)"
v="$(_bashio_env_get "PORT_${arg}" 2> /dev/null || true)"
[ -z "$v" ] && v="$(_bashio_env_get "ADDON_PORT_${arg}" 2> /dev/null || true)"
printf '%s' "${v:-$arg}"
else
printf '%s' "$(_bashio_env_get "$arg" 2>/dev/null || true)"
printf '%s' "$(_bashio_env_get "$arg" 2> /dev/null || true)"
fi
}
@@ -207,13 +208,13 @@ bashio::addon.option() {
local key="${1:-}" value="${2-__BASHIO_UNSET__}" file="${STANDALONE_OPTIONS_JSON:-}"
[ -n "$key" ] || return 0
if [ -n "$file" ] && [ -f "$file" ] && command -v jq >/dev/null 2>&1; then
if [ -n "$file" ] && [ -f "$file" ] && command -v jq > /dev/null 2>&1; then
local tmp
tmp="$(mktemp)"
if [ "$value" = "__BASHIO_UNSET__" ]; then
jq --arg k "$key" 'delpath(($k|split(".")))' "$file" >"$tmp" && mv "$tmp" "$file"
jq --arg k "$key" 'delpath(($k|split(".")))' "$file" > "$tmp" && mv "$tmp" "$file"
else
jq --arg k "$key" --arg v "$value" 'setpath(($k|split(".")); $v)' "$file" >"$tmp" && mv "$tmp" "$file"
jq --arg k "$key" --arg v "$value" 'setpath(($k|split(".")); $v)' "$file" > "$tmp" && mv "$tmp" "$file"
fi
return 0
fi
@@ -238,7 +239,7 @@ bashio::info.operating_system() {
}
bashio::info.arch() { uname -m; }
bashio::info.machine() { uname -m; }
bashio::info.homeassistant(){ printf '%s' "standalone"; }
bashio::info.homeassistant() { printf '%s' "standalone"; }
bashio::info.supervisor() { printf '%s' "standalone"; }
# -----------------------------------------------------------------------------
@@ -246,11 +247,14 @@ 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
v="$(_bashio_env_get "$key" 2>/dev/null || true)"
if _bashio_env_get "$key" > /dev/null 2>&1; then
v="$(_bashio_env_get "$key" 2> /dev/null || true)"
fi
[ -z "$v" ] && v="$(_bashio_json_get "$key")"
printf '%s' "${v:-}"
@@ -271,8 +275,8 @@ bashio::config.exists() {
return 0
fi
if [ -n "$file" ] && [ -f "$file" ] && command -v jq >/dev/null 2>&1; then
jq -e --arg k "$key" 'haspath(($k|split(".")))' "$file" >/dev/null 2>&1
if [ -n "$file" ] && [ -f "$file" ] && command -v jq > /dev/null 2>&1; then
jq -e --arg k "$key" 'haspath(($k|split(".")))' "$file" > /dev/null 2>&1
return $?
fi
@@ -293,8 +297,8 @@ bashio::config.array() {
raw="$(bashio::config "$key")"
[ -n "$raw" ] || return 0
if command -v jq >/dev/null 2>&1 && printf '%s' "$raw" | jq -e . >/dev/null 2>&1; then
printf '%s' "$raw" | jq -r '.[]' 2>/dev/null && return 0
if command -v jq > /dev/null 2>&1 && printf '%s' "$raw" | jq -e . > /dev/null 2>&1; then
printf '%s' "$raw" | jq -r '.[]' 2> /dev/null && return 0
fi
if printf '%s' "$raw" | grep -q ','; then
@@ -319,7 +323,7 @@ bashio::fs.file_exists() { [ -f "${1:-}" ]; }
bashio::fs.directory_exists() { [ -d "${1:-}" ]; }
bashio::fs.file_contains() {
local f="${1:-}" p="${2:-}"
[ -f "$f" ] && grep -q -- "$p" "$f" 2>/dev/null
[ -f "$f" ] && grep -q -- "$p" "$f" 2> /dev/null
}
# -----------------------------------------------------------------------------
@@ -336,16 +340,16 @@ bashio::net.wait_for() {
bashio::dns.host() {
local h="${1:-}"
[ -n "$h" ] || return 1
if command -v getent >/dev/null 2>&1; then
if command -v getent > /dev/null 2>&1; then
getent ahostsv4 "$h" | awk '{print $1; exit}'
else
nslookup "$h" 2>/dev/null | awk '/^Address: /{print $2; exit}'
nslookup "$h" 2> /dev/null | awk '/^Address: /{print $2; exit}'
fi
}
# Hostname
bashio::host.hostname() {
command -v hostname >/dev/null 2>&1 && hostname || printf '%s' "${HOSTNAME:-unknown}"
command -v hostname > /dev/null 2>&1 && hostname || printf '%s' "${HOSTNAME:-unknown}"
}
# -----------------------------------------------------------------------------
@@ -356,7 +360,10 @@ 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:]')"
@@ -369,15 +376,15 @@ bashio::services() {
MQTT:USERNAME) var="MQTT_USER" ;;
MQTT:PASSWORD) var="MQTT_PASSWORD" ;;
MQTT:TLS) var="MQTT_TLS" ;;
MYSQL:HOST|MARIADB:HOST) var="DB_HOST" ;;
MYSQL:PORT|MARIADB:PORT) var="DB_PORT" ;;
MYSQL:USERNAME|MARIADB:USERNAME) var="DB_USER" ;;
MYSQL:PASSWORD|MARIADB:PASSWORD) var="DB_PASSWORD" ;;
MYSQL:DATABASE|MARIADB:DATABASE) var="DB_NAME" ;;
MYSQL:HOST | MARIADB:HOST) var="DB_HOST" ;;
MYSQL:PORT | MARIADB:PORT) var="DB_PORT" ;;
MYSQL:USERNAME | MARIADB:USERNAME) var="DB_USER" ;;
MYSQL:PASSWORD | MARIADB:PASSWORD) var="DB_PASSWORD" ;;
MYSQL:DATABASE | MARIADB:DATABASE) var="DB_NAME" ;;
*) var="${svc_upper}_${upper}" ;;
esac
v="$(_bashio_env_get "$var" 2>/dev/null || true)"
v="$(_bashio_env_get "$var" 2> /dev/null || true)"
if [ -z "$v" ]; then
v="$(_bashio_json_get "services.${svc}.${key}")"
[ -z "$v" ] && v="$(_bashio_json_get "${svc}.${key}")"
@@ -402,15 +409,15 @@ bashio::cache.set() { printf '%s' "${2:-}" > "$BASHIO_CACHE_DIR/${1}.cache";
# -----------------------------------------------------------------------------
# jq wrapper (some add-ons call bashio::jq)
# -----------------------------------------------------------------------------
bashio::jq() { command -v jq >/dev/null 2>&1 && jq "$@"; }
bashio::jq() { command -v jq > /dev/null 2>&1 && jq "$@"; }
# -----------------------------------------------------------------------------
# Home Assistant token
# -----------------------------------------------------------------------------
bashio::homeassistant.token() {
local t="${HOMEASSISTANT_TOKEN:-${HASS_TOKEN:-}}"
if [ -z "$t" ] && [ -n "${STANDALONE_OPTIONS_JSON:-}" ] && [ -f "${STANDALONE_OPTIONS_JSON:-}" ] && command -v jq >/dev/null 2>&1; then
t="$(jq -er '.homeassistant.token // empty' "$STANDALONE_OPTIONS_JSON" 2>/dev/null || true)"
if [ -z "$t" ] && [ -n "${STANDALONE_OPTIONS_JSON:-}" ] && [ -f "${STANDALONE_OPTIONS_JSON:-}" ] && command -v jq > /dev/null 2>&1; then
t="$(jq -er '.homeassistant.token // empty' "$STANDALONE_OPTIONS_JSON" 2> /dev/null || true)"
fi
printf '%s' "${t:-}"
}
@@ -419,7 +426,11 @@ 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

@@ -41,14 +41,14 @@ restore_apt_sources_if_missing() {
mirror="http://ports.ubuntu.com/ubuntu-ports"
security_mirror="$mirror"
fi
cat > /etc/apt/sources.list <<EOF2
cat > /etc/apt/sources.list << EOF2
deb ${mirror} ${codename} main restricted universe multiverse
deb ${mirror} ${codename}-updates main restricted universe multiverse
deb ${security_mirror} ${codename}-security main restricted universe multiverse
EOF2
;;
debian)
cat > /etc/apt/sources.list <<EOF2
cat > /etc/apt/sources.list << EOF2
deb http://deb.debian.org/debian ${codename} main contrib non-free non-free-firmware
deb http://deb.debian.org/debian ${codename}-updates main contrib non-free non-free-firmware
deb http://deb.debian.org/debian-security ${codename}-security main contrib non-free non-free-firmware

View File

@@ -184,7 +184,7 @@ done
if [ "$PACKMANAGER" = "apt" ]; then apt-get update > /dev/null; fi
if [ "$PACKMANAGER" = "pacman" ]; then pacman -Sy > /dev/null; fi
if [ "$PACKMANAGER" = "apk" ] && [ -f /etc/apk/repositories ] && ! grep -q "community" /etc/apk/repositories; then
ALPINE_VER=$(cat /etc/alpine-release 2>/dev/null | cut -d. -f1,2)
ALPINE_VER=$(cat /etc/alpine-release 2> /dev/null | cut -d. -f1,2)
if [ -n "$ALPINE_VER" ]; then
echo "https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VER}/community" >> /etc/apk/repositories
apk update > /dev/null
@@ -228,7 +228,7 @@ for files in "/etc/services.d" "/etc/cont-init.d"; do
if ! ls $files 1> /dev/null 2>&1; then continue; fi
# Bashio
if grep -q -rnw "${files}" -e 'bashio' 2>/dev/null && [ ! -f "/usr/bin/bashio" ]; then
if grep -q -rnw "${files}" -e 'bashio' 2> /dev/null && [ ! -f "/usr/bin/bashio" ]; then
[ "$VERBOSE" = true ] && echo "install bashio"
BASHIO_VERSION="v0.17.5"
mkdir -p /tmp/bashio

View File

@@ -10,7 +10,7 @@ if [ "$$" -eq 1 ]; then
PID1=true
echo "Starting as entrypoint"
if [ -d /command ]; then
ln -sf /command/* /usr/bin/ 2>/dev/null || true
ln -sf /command/* /usr/bin/ 2> /dev/null || true
fi
else
echo "Starting custom scripts"
@@ -28,14 +28,20 @@ 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; }
if "$t" >/dev/null 2>&1; then
rm -f "$t" 2>/dev/null || true
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"
return 0
fi
rm -f "$t" 2>/dev/null || true
rm -f "$t" 2> /dev/null || true
fi
done
return 1
@@ -109,7 +115,7 @@ validate_shebang() {
local errfile msg
# shellcheck disable=SC2206
local cmd=( $candidate )
local cmd=($candidate)
local exe="${cmd[0]}"
if [ ! -x "$exe" ]; then
@@ -122,18 +128,18 @@ validate_shebang() {
{
printf '#!%s\n' "$candidate"
printf '%s\n' "$probe_script_content"
} >"$tmp"
chmod 700 "$tmp" 2>/dev/null || true
} > "$tmp"
chmod 700 "$tmp" 2> /dev/null || true
set +e
out="$("$tmp" 2>"$errfile")"
out="$("$tmp" 2> "$errfile")"
rc=$?
set -e
rm -f "$tmp" 2>/dev/null || true
rm -f "$tmp" 2> /dev/null || true
if [ "$rc" -eq 0 ] && [ -n "${out:-}" ] && [ "$out" != "null" ]; then
rm -f "$errfile" 2>/dev/null || true
rm -f "$errfile" 2> /dev/null || true
return 0
fi
@@ -145,7 +151,7 @@ validate_shebang() {
msg+=$' stderr: <empty>\n'
fi
SHEBANG_ERRORS+=("$msg")
rm -f "$errfile" 2>/dev/null || true
rm -f "$errfile" 2> /dev/null || true
return 1
}
@@ -209,14 +215,14 @@ 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
# unchanged, not newly degraded. Never fatal, a read-only /run must still let the add-on boot,
# but never silent either, since the shebang failure it leaves behind says nothing on its own.
rm -rf "$S6_CONTAINER_ENV" "$S6_CONTAINER_ENV.tmp" 2>/dev/null || true
rm -rf "$S6_CONTAINER_ENV" "$S6_CONTAINER_ENV.tmp" 2> /dev/null || true
echo -e "\e[38;5;214m$(date) WARNING: could not populate $S6_CONTAINER_ENV; scripts with a with-contenv shebang will fail at their shebang, as they did before this was attempted\e[0m"
fi
fi
@@ -277,7 +283,7 @@ wait_for_supervisor() {
[ -n "${SUPERVISOR_TOKEN:-}" ] || return 0
[ "${BASHIO_LIB_FULL:-false}" = "true" ] || return 0
# bashio's own curl carries no --max-time, so each attempt is bounded from the outside.
command -v timeout >/dev/null 2>&1 || return 0
command -v timeout > /dev/null 2>&1 || return 0
# Digits only, then forced to base 10: `test -gt` accepts a zero-padded override like 08, but
# arithmetic expansion reads it as octal and fails, which would leave the deadline empty and
# spin the loop below forever.
@@ -305,7 +311,7 @@ wait_for_supervisor() {
# logging is dropped because a failed attempt here is expected, not news.
# shellcheck disable=SC2016
if timeout "$attempt" bash -c '. "$1" && [ -n "$(bashio::addon.ip_address)" ]' \
_ "$BASHIO_LIB" >/dev/null 2>&1; then
_ "$BASHIO_LIB" > /dev/null 2>&1; then
[ "$announced" -eq 0 ] || echo "Supervisor API ready after $((SECONDS - started))s"
return 0
fi
@@ -357,7 +363,7 @@ run_one_script() {
_run_rc=0
(
# shellcheck disable=SC1090
. "$BASHIO_LIB" 2>/dev/null || true
. "$BASHIO_LIB" 2> /dev/null || true
# shellcheck disable=SC1090
. "$script"
) || _run_rc=$?
@@ -397,7 +403,7 @@ if $PID1; then
_svc_rc=0
(
# shellcheck disable=SC1090
. "$BASHIO_LIB" 2>/dev/null || true
. "$BASHIO_LIB" 2> /dev/null || true
# shellcheck disable=SC1090
. "$runfile"
) || _svc_rc=$?
@@ -431,19 +437,19 @@ if $PID1; then
terminate() {
local local_pid
echo "Termination signal received, forwarding to subprocesses..."
if command -v pgrep >/dev/null 2>&1; then
if command -v pgrep > /dev/null 2>&1; then
while read -r pid; do
[ -n "$pid" ] || continue
echo "Terminating child PID $pid"
kill -TERM "$pid" 2>/dev/null || echo "Failed to terminate PID $pid"
kill -TERM "$pid" 2> /dev/null || echo "Failed to terminate PID $pid"
done < <(pgrep -P "$$" || true)
else
for p in /proc/[0-9]*/; do
local_pid="${p#/proc/}"
local_pid="${local_pid%/}"
if [ "$local_pid" -ne 1 ] && grep -q "^PPid:[[:space:]]*$$" "/proc/$local_pid/status" 2>/dev/null; then
if [ "$local_pid" -ne 1 ] && grep -q "^PPid:[[:space:]]*$$" "/proc/$local_pid/status" 2> /dev/null; then
echo "Terminating child PID $local_pid"
kill -TERM "$local_pid" 2>/dev/null || echo "Failed to terminate PID $local_pid"
kill -TERM "$local_pid" 2> /dev/null || echo "Failed to terminate PID $local_pid"
fi
done
fi

View File

@@ -147,7 +147,7 @@ manual actions with precedence. Prompts live in `.github/prompts/`, shared shell
in `.github/scripts/`.
| Workflow | Model | Trigger | Role |
|---|---|---|---|
| --- | --- | --- | --- |
| `on_issues_ai_triage.yaml` | Sonnet-low | issue opened (+ author reply, daily catch-up) | Tier 1: classify, dedupe, answer, ask for info; label `ai-triage` for real add-on bugs |
| `daily_ai_fix.yaml` | Opus 5-xhigh | daily 03:00 | Tier 2: diagnose the `ai-triage` batch; small+confident → ready PR (`ai:fixed`); else write a plan (`ai:plan-pending`) |
| `on_issue_approved.yaml` | Opus 5-high | maintainer adds `ai:approved` | Tier 3: execute the approved plan → ready PR |
@@ -176,7 +176,7 @@ prompt-only, not machine-enforced.
## Linting Rules
| Tool | Config | Key ignores |
|------|--------|------------|
| ------ | -------- | ------------ |
| Hadolint | `.hadolint.yaml` | DL3002, DL3006-9, DL3018 (no pinning required) |
| ShellCheck | `.shellcheckrc` | SC2002 |
| Markdownlint | `.markdownlint.yaml` | MD013 (line length), MD025, MD033, MD041 |

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:

View File

@@ -6,7 +6,7 @@ This addon is based on the docker image <https://github.com/lklynet/aurral>
## Configuration
| Option | Default | Description |
|---|---|---|
| --- | --- | --- |
| `download_folder` | `/share/aurral/downloads` | Path where Aurral writes flow downloads. Must be under `/share`. |
| `weekly_flow_folder` | `weekly-flow` | Subfolder name appended to `download_folder` for weekly flow files. The full path will be `download_folder/weekly_flow_folder`. |

View File

@@ -67,7 +67,7 @@ Default credentials: `admin` / `password` (change after first login).
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `PGID` | int | `0` | Group ID for file permissions |
| `PUID` | int | `0` | User ID for file permissions |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |

View File

@@ -44,7 +44,7 @@ Configurations can be done through the app webUI, except for the following optio
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `PGID` | int | `0` | Group ID for file permissions |
| `PUID` | int | `0` | User ID for file permissions |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |

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

View File

@@ -17,7 +17,7 @@ A privacy-first PDF toolkit running entirely in your browser — no uploads, no
### Organize & Edit
| Tool | Tool | Tool |
|------|------|------|
| ------ | ------ | ------ |
| Merge PDF | Split PDF | Organize PDF |
| Delete Pages | Extract Pages | Reverse Pages |
| Rotate PDF | Rotate Custom | Crop PDF |
@@ -28,7 +28,7 @@ A privacy-first PDF toolkit running entirely in your browser — no uploads, no
### Convert TO PDF
| Tool | Tool | Tool |
|------|------|------|
| ------ | ------ | ------ |
| Word to PDF | Excel to PDF | PowerPoint to PDF |
| Image to PDF | JPG to PDF | PNG to PDF |
| BMP to PDF | TIFF to PDF | WEBP to PDF |
@@ -44,7 +44,7 @@ A privacy-first PDF toolkit running entirely in your browser — no uploads, no
### Convert FROM PDF
| Tool | Tool | Tool |
|------|------|------|
| ------ | ------ | ------ |
| PDF to DOCX | PDF to Excel | PDF to JPG |
| PDF to PNG | PDF to BMP | PDF to TIFF |
| PDF to WEBP | PDF to SVG | PDF to Text |
@@ -54,7 +54,7 @@ A privacy-first PDF toolkit running entirely in your browser — no uploads, no
### Security & Metadata
| Tool | Tool | Tool |
|------|------|------|
| ------ | ------ | ------ |
| Encrypt PDF | Decrypt PDF | Change Permissions |
| Remove Restrictions | Sign PDF | Digital Sign PDF |
| Validate Signature | Edit Metadata | View Metadata |
@@ -64,7 +64,7 @@ A privacy-first PDF toolkit running entirely in your browser — no uploads, no
### Enhance & Process
| Tool | Tool | Tool |
|------|------|------|
| ------ | ------ | ------ |
| Compress PDF | OCR PDF | Deskew PDF |
| Rasterize PDF | Linearize PDF | PDF to PDF/A |
| Adjust Colors | Invert Colors | Text Color |
@@ -76,7 +76,7 @@ A privacy-first PDF toolkit running entirely in your browser — no uploads, no
### Forms & More
| Tool | Tool | Tool |
|------|------|------|
| ------ | ------ | ------ |
| Form Creator | Form Filler | Table of Contents |
| Bookmark | PDF Editor | Extract Images |
| Extract Tables | Extract Attachments | Edit Attachments |

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

View File

@@ -190,5 +190,5 @@ LOG_DIR="/config/logs"
if [ -d "$LOG_DIR" ]; then
bashio::log.info "Trimming log files older than ${LOG_MAX_AGE_DAYS} days in ${LOG_DIR}"
ln -sf "$LOG_DIR" /logs
find "$LOG_DIR" -type f -name "*.log*" -mtime +"$LOG_MAX_AGE_DAYS" -delete 2>/dev/null || true
find "$LOG_DIR" -type f -name "*.log*" -mtime +"$LOG_MAX_AGE_DAYS" -delete 2> /dev/null || true
fi

View File

@@ -44,8 +44,8 @@ if ! bashio::config.true 'mariadb_auto_config'; then
# Only revert if config.yaml points at the HA MariaDB host we would have
# written — a mysql block pointing at a different host was set manually.
# shellcheck disable=SC2016
CURRENT_MYSQL_HOST="$(yq -r '.output.mysql.host // empty' "$CONFIG_LOCATION" 2>/dev/null || true)"
if yq -e '.output.mysql.enabled == true' "$CONFIG_LOCATION" >/dev/null 2>&1 \
CURRENT_MYSQL_HOST="$(yq -r '.output.mysql.host // empty' "$CONFIG_LOCATION" 2> /dev/null || true)"
if yq -e '.output.mysql.enabled == true' "$CONFIG_LOCATION" > /dev/null 2>&1 \
&& [ "${CURRENT_MYSQL_HOST}" = "${MYSQL_HOST}" ]; then
yq -i -y \
'.output.mysql.enabled = false
@@ -71,7 +71,7 @@ bashio::log.green "---"
# Resolve MariaDB hostname to IPv4: on HAOS >=17.3 the Supervisor network
# gained IPv6, but the MariaDB addon only grants its user from the IPv4
# subnet. Fall back to the raw hostname if resolution fails.
MYSQL_HOST_RESOLVED="$(getent ahostsv4 "${MYSQL_HOST}" 2>/dev/null | awk '{print $1; exit}')"
MYSQL_HOST_RESOLVED="$(getent ahostsv4 "${MYSQL_HOST}" 2> /dev/null | awk '{print $1; exit}')"
MYSQL_HOST_RESOLVED="${MYSQL_HOST_RESOLVED:-${MYSQL_HOST}}"
if [ "${MYSQL_HOST_RESOLVED}" != "${MYSQL_HOST}" ]; then
bashio::log.blue "Resolved ${MYSQL_HOST} -> ${MYSQL_HOST_RESOLVED} (forcing IPv4)"

View File

@@ -22,8 +22,8 @@
## nightly-20260615-2 (05-07-2026)
- Minor bugs fixed
- Fix detections/database not persisting across restarts on a fresh install: upstream's default `config.yaml` ships `output.sqlite.path: birdnet.db` (relative) explicitly, so the missing-only (`//=`) seeding introduced previously never rewrote it to an absolute path. A relative path resolves against the app's ephemeral working directory, so the database was silently recreated empty on every restart. Any relative `output.sqlite.path` is now rewritten to live under the persistent `/config` on startup; values already set to an absolute path are left untouched. (https://github.com/tphakala/birdnet-go/discussions/3774)
- MQTT auto-config now also enables BirdNET-Go's native Home Assistant MQTT auto-discovery: detection sensors appear in Home Assistant automatically with no manual YAML (existing UI/config.yaml edits are preserved)
- Fix detections/database not persisting across restarts on a fresh install: upstream's default `config.yaml` ships `output.sqlite.path: birdnet.db` (relative) explicitly, so the missing-only (`//=`) seeding introduced previously never rewrote it to an absolute path. A relative path resolves against the app's ephemeral working directory, so the database was silently recreated empty on every restart. Any relative `output.sqlite.path` is now rewritten to live under the persistent `/config` on startup; values already set to an absolute path are left untouched. (https://github.com/tphakala/birdnet-go/discussions/3774)
- MQTT auto-config now also enables BirdNET-Go's native Home Assistant MQTT auto-discovery: detection sensors appear in Home Assistant automatically with no manual YAML (existing UI/config.yaml edits are preserved)
- MQTT auto-config seeds `realtime.mqtt.retain: true` (only when unset) so sensor states survive Home Assistant restarts
- Added supervisor watchdog (tcp://[HOST]:[PORT:8080]) so the add-on is automatically restarted if BirdNET-Go stops responding
- Added backup_exclude for rotated logs, making Home Assistant backups smaller (SQLite journals are kept so hot backups stay consistent)

View File

@@ -186,5 +186,5 @@ LOG_DIR="/config/logs"
if [ -d "$LOG_DIR" ]; then
bashio::log.info "Trimming log files older than ${LOG_MAX_AGE_DAYS} days in ${LOG_DIR}"
ln -sf "$LOG_DIR" /logs
find "$LOG_DIR" -type f -name "*.log*" -mtime +"$LOG_MAX_AGE_DAYS" -delete 2>/dev/null || true
find "$LOG_DIR" -type f -name "*.log*" -mtime +"$LOG_MAX_AGE_DAYS" -delete 2> /dev/null || true
fi

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,6 +112,7 @@ 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,6 +112,7 @@ 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:

View File

@@ -7,7 +7,7 @@ DATA_LOCATION="$(bashio::config 'data_location' || true)"
DATA_LOCATION="${DATA_LOCATION:-$DEFAULT_LOCATION}"
case "${DATA_LOCATION}" in
/config/*|/share/*|/data/*) ;;
/config/* | /share/* | /data/*) ;;
*)
bashio::log.warning "Invalid data_location '${DATA_LOCATION}', falling back to ${DEFAULT_LOCATION}"
DATA_LOCATION="${DEFAULT_LOCATION}"
@@ -19,11 +19,11 @@ LEGACY2="/data"
mkdir -p "${DATA_LOCATION}"
if [ -z "$(ls -A "${DATA_LOCATION}" 2>/dev/null || true)" ]; then
if [ -d "${LEGACY1}" ] && [ -n "$(ls -A "${LEGACY1}" 2>/dev/null || true)" ]; then
if [ -z "$(ls -A "${DATA_LOCATION}" 2> /dev/null || true)" ]; then
if [ -d "${LEGACY1}" ] && [ -n "$(ls -A "${LEGACY1}" 2> /dev/null || true)" ]; then
bashio::log.notice "Migrating legacy data from ${LEGACY1} to ${DATA_LOCATION}"
cp -a "${LEGACY1}/." "${DATA_LOCATION}/" || true
elif [ -d "${LEGACY2}" ] && [ "${LEGACY2}" != "${DATA_LOCATION}" ] && [ -n "$(ls -A "${LEGACY2}" 2>/dev/null || true)" ]; then
elif [ -d "${LEGACY2}" ] && [ "${LEGACY2}" != "${DATA_LOCATION}" ] && [ -n "$(ls -A "${LEGACY2}" 2> /dev/null || true)" ]; then
bashio::log.notice "Migrating legacy data from ${LEGACY2} to ${DATA_LOCATION}"
cp -a "${LEGACY2}/." "${DATA_LOCATION}/" || true
fi

View File

@@ -3,20 +3,20 @@
set -euo pipefail
SND_GID=""
if [ -e /dev/snd ] && command -v stat >/dev/null 2>&1; then
SND_GID="$(stat -c '%g' /dev/snd 2>/dev/null || true)"
if [ -e /dev/snd ] && command -v stat > /dev/null 2>&1; then
SND_GID="$(stat -c '%g' /dev/snd 2> /dev/null || true)"
fi
if [ -n "${SND_GID}" ] && getent group audio >/dev/null 2>&1; then
if [ -n "${SND_GID}" ] && getent group audio > /dev/null 2>&1; then
current_gid="$(getent group audio | cut -d: -f3 || true)"
if [ -n "${current_gid}" ] && [ "${current_gid}" != "${SND_GID}" ]; then
groupmod -g "${SND_GID}" audio 2>/dev/null || true
groupmod -g "${SND_GID}" audio 2> /dev/null || true
fi
fi
for u in root nginx www-data icecast2; do
if id "${u}" >/dev/null 2>&1; then
usermod -aG audio "${u}" 2>/dev/null || true
if id "${u}" > /dev/null 2>&1; then
usermod -aG audio "${u}" 2> /dev/null || true
fi
done
@@ -30,7 +30,7 @@ if [ -n "${PULSE_SOCK}" ] && [ -S "${PULSE_SOCK}" ] && [ ! -S /run/pulse/native
fi
# Copy PulseAudio cookie for the icecast2 user so it can authenticate
if id icecast2 >/dev/null 2>&1; then
if id icecast2 > /dev/null 2>&1; then
ICECAST_HOME="$(getent passwd icecast2 | cut -d: -f6)"
if [ -n "${ICECAST_HOME}" ]; then
for cookie in /config/.config/pulse/cookie /root/.config/pulse/cookie; do

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

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

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

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

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

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

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

View File

@@ -21,19 +21,19 @@ export ROCKET_SECRET_KEY="${secret_key}"
# Find the matching log level
if bashio::config.has_value 'log_level'; then
case "$(bashio::string.lower "$(bashio::config 'log_level')")" in
all|trace)
all | trace)
log_level="trace"
;;
debug)
log_level="debug"
;;
info|notice)
info | notice)
log_level="info"
;;
warning)
log_level="warn"
;;
error|fatal)
error | fatal)
log_level="error"
;;
off)

View File

@@ -64,7 +64,7 @@ Default credentials are shown in the startup logs.
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `PGID` | int | `0` | Group ID for file permissions |
| `PUID` | int | `0` | User ID for file permissions |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |

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

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

View File

@@ -42,7 +42,7 @@ args=(
# Container reality: sandbox usually breaks unless seccomp is fully disabled
if [[ "${seccomp_mode}" != "0" ]]; then
args+=( --no-sandbox --test-type )
args+=(--no-sandbox --test-type)
fi
# IMPORTANT:

View File

@@ -62,7 +62,7 @@ Please read the upstream container documentation for further info: https://githu
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `PGID` | int | `0` | Group ID for file permissions |
| `PUID` | int | `0` | User ID for file permissions |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |

View File

@@ -62,7 +62,7 @@ Default password: admin123
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `PGID` | int | `0` | Group ID for file permissions |
| `PUID` | int | `0` | User ID for file permissions |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
@@ -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"

View File

@@ -52,7 +52,7 @@ You can add a shortcut pointing to your Changedetection.io instance with the fol
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `PGID` | int | `0` | Group ID for file permissions |
| `PUID` | int | `0` | User ID for file permissions |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |

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

View File

@@ -55,7 +55,7 @@
# shared memory into ordinary files for no benefit. If the size cannot be determined, keep the
# flag — the crash it prevents is worse than the overhead it costs.
SHM_FLAGS="--disable-dev-shm-usage"
SHM_KB="$(df -k /dev/shm 2> /dev/null | awk 'NR==2 {print $2}')"
SHM_KB="$(df -k /dev/shm 2>/dev/null | awk 'NR==2 {print $2}')"
if [ -n "$SHM_KB" ] && [ "$SHM_KB" -ge 262144 ]; then
SHM_FLAGS=""
fi

View File

@@ -141,7 +141,7 @@ release_info=""
if curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 10 --max-time 30 \
-o "$release_metadata" "$CODEX_RELEASE_API"; then
release_info="$(
CODEX_ASSET="$CODEX_ASSET" python3 - "$release_metadata" <<'PY' 2> /dev/null || true
CODEX_ASSET="$CODEX_ASSET" python3 - "$release_metadata" << 'PY' 2> /dev/null || true
import json
import os
import re
@@ -249,7 +249,7 @@ fi
{
printf '#!/usr/bin/env bash\n'
printf 'CODEX_REAL=%q\n' "$CODEX_REAL"
cat <<'SH'
cat << 'SH'
RUNTIME_HOME="$(getent passwd abc | cut -d: -f6)"
if [ -z "$RUNTIME_HOME" ]; then
echo "codex: unable to resolve the abc runtime home" >&2
@@ -338,8 +338,7 @@ 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

View File

@@ -30,7 +30,7 @@ manage_claude_md_block() {
fi
elif [ -f "$CLAUDE_MD" ] && grep -qF "$begin" "$CLAUDE_MD"; then
bashio::log.info "Removing ${name} guidance from CLAUDE.md"
CLAUDE_MD="$CLAUDE_MD" BLOCK_NAME="$name" python3 - <<'PY' || bashio::log.warning "Unable to remove the ${name} guidance automatically"
CLAUDE_MD="$CLAUDE_MD" BLOCK_NAME="$name" python3 - << 'PY' || bashio::log.warning "Unable to remove the ${name} guidance automatically"
import os
import re
from pathlib import Path
@@ -58,7 +58,7 @@ PY
manage_settings_hook() {
# manage_settings_hook <event> <matcher> <command> <add|remove>
HOOK_EVENT="$1" HOOK_MATCHER="$2" HOOK_COMMAND="$3" HOOK_ACTION="$4" \
python3 - <<'PY' || bashio::log.warning "Unable to update the $1 hook for '$3'"
python3 - << 'PY' || bashio::log.warning "Unable to update the $1 hook for '$3'"
import json
import os
from pathlib import Path
@@ -389,14 +389,14 @@ bashio::log.info "MCP servers for Claude Desktop: ${MCP_SERVERS_DESKTOP}"
bashio::log.info "MCP servers for Claude Code: ${MCP_SERVERS_CODE}"
HEADROOM_ENABLED="$HEADROOM_ENABLED" HEADROOM_BIN="$(command -v headroom || echo headroom)" \
HEADROOM_HF_HOME="${HOME}/.headroom/hf" \
TOKENSAVE_ENABLED="$TOKENSAVE_ENABLED" TOKENSAVE_BIN="$(command -v tokensave || echo tokensave)" \
CODEX_ENABLED="$CODEX_ENABLED" CODEX_BIN="$CODEX_BIN" CODEX_SANDBOX_MODE="$CODEX_SANDBOX_MODE" \
HA_MCP_ENABLED="$HA_MCP_ENABLED" HA_MCP_URL="$HA_MCP_URL" HA_MCP_TOKEN="$HA_MCP_TOKEN" \
MCP_PROXY_BIN="$(command -v mcp-proxy || echo mcp-proxy)" \
MCP_SERVERS_DESKTOP="$MCP_SERVERS_DESKTOP" MCP_SERVERS_CODE="$MCP_SERVERS_CODE" \
CLAUDE_DESKTOP_CONFIG="$CLAUDE_DESKTOP_CONFIG" CLAUDE_CODE_CONFIG="$CLAUDE_CODE_CONFIG" \
python3 - <<'PY' || bashio::log.warning "Unable to update the MCP server registrations automatically"
HEADROOM_HF_HOME="${HOME}/.headroom/hf" \
TOKENSAVE_ENABLED="$TOKENSAVE_ENABLED" TOKENSAVE_BIN="$(command -v tokensave || echo tokensave)" \
CODEX_ENABLED="$CODEX_ENABLED" CODEX_BIN="$CODEX_BIN" CODEX_SANDBOX_MODE="$CODEX_SANDBOX_MODE" \
HA_MCP_ENABLED="$HA_MCP_ENABLED" HA_MCP_URL="$HA_MCP_URL" HA_MCP_TOKEN="$HA_MCP_TOKEN" \
MCP_PROXY_BIN="$(command -v mcp-proxy || echo mcp-proxy)" \
MCP_SERVERS_DESKTOP="$MCP_SERVERS_DESKTOP" MCP_SERVERS_CODE="$MCP_SERVERS_CODE" \
CLAUDE_DESKTOP_CONFIG="$CLAUDE_DESKTOP_CONFIG" CLAUDE_CODE_CONFIG="$CLAUDE_CODE_CONFIG" \
python3 - << 'PY' || bashio::log.warning "Unable to update the MCP server registrations automatically"
import json
import os
from pathlib import Path
@@ -607,7 +607,7 @@ PY
# Guide Claude to actually use the Headroom compression tools so the MCP integration produces
# real savings when transparent proxying is unavailable.
if $HEADROOM_ENABLED; then
manage_claude_md_block headroom add <<'MD'
manage_claude_md_block headroom add << 'MD'
## Headroom context compression
A local Headroom proxy (127.0.0.1:8787) backs the `headroom` MCP tools. To save context tokens:
@@ -636,7 +636,7 @@ if $HEADROOM_ENABLED && bashio::config.true 'headroom_wrap_claude_code'; then
else
HEADROOM_ROUTE_ACTION="remove"
fi
HEADROOM_ROUTE_ACTION="$HEADROOM_ROUTE_ACTION" python3 - <<'PY' || bashio::log.warning "Unable to manage the Claude Code proxy routing env"
HEADROOM_ROUTE_ACTION="$HEADROOM_ROUTE_ACTION" python3 - << 'PY' || bashio::log.warning "Unable to manage the Claude Code proxy routing env"
import json
import os
from pathlib import Path
@@ -703,7 +703,7 @@ manage_settings_hook PostToolUse "Bash|Grep|Glob|WebFetch" "$HEADROOM_HOOK_CMD"
# Tell Claude Code that it can configure Home Assistant over the Core API via the shipped
# `ha-cli` helper (no /config filesystem mount needed).
if bashio::config.true 'enable_ha_api_helper'; then
manage_claude_md_block ha-api-helper add <<'MD'
manage_claude_md_block ha-api-helper add << 'MD'
## Configuring Home Assistant
You can configure this Home Assistant instance through its Core API using the `ha-cli`
@@ -731,7 +731,7 @@ fi
# Registering the MCP server is not enough on its own: without guidance the model rarely reaches
# for a second agent, the same gap the Headroom block above exists to close.
if $CODEX_ENABLED; then
manage_claude_md_block codex add <<'MD'
manage_claude_md_block codex add << 'MD'
## Delegating to ChatGPT Codex
The `codex` MCP server runs OpenAI's Codex agent locally, signed in with the user's ChatGPT

View File

@@ -19,7 +19,7 @@ esac
# only while it still holds one of those managed values — a defaultMode the user set by hand
# is never deleted. Ownership of the written file is reconciled by 84-claude_runtime_ownership.sh.
mkdir -p "$(dirname "$SETTINGS_PATH")"
PERMISSION_MODE="$PERMISSION_MODE" SETTINGS_PATH="$SETTINGS_PATH" python3 - <<'PY'
PERMISSION_MODE="$PERMISSION_MODE" SETTINGS_PATH="$SETTINGS_PATH" python3 - << 'PY'
import json
import os
from pathlib import Path

View File

@@ -36,7 +36,7 @@ fi
# archive stays unpatched, so every later boot would retry under a new pid and strand another
# copy until the container runs out of space.
cleanup_tmp() {
find "$(dirname "$ASAR")" -maxdepth 1 -name ".$(basename "$ASAR").addon-tmp.*" -delete 2>/dev/null || true
find "$(dirname "$ASAR")" -maxdepth 1 -name ".$(basename "$ASAR").addon-tmp.*" -delete 2> /dev/null || true
}
if output=$(timeout 120 node "$PATCHER" "$ASAR" 2>&1); then

View File

@@ -45,7 +45,7 @@ if [ "$(bashio::config 'permission_mode')" = "bypass" ]; then
fi
section "Claude Code permission state"
RUNTIME_HOME="$RUNTIME_HOME" python3 - <<'PY'
RUNTIME_HOME="$RUNTIME_HOME" python3 - << 'PY'
import json
import os
from pathlib import Path
@@ -66,7 +66,7 @@ else:
PY
section "MCP registrations (environment values redacted)"
RUNTIME_HOME="$RUNTIME_HOME" python3 - <<'PY'
RUNTIME_HOME="$RUNTIME_HOME" python3 - << 'PY'
import json
import os
from pathlib import Path
@@ -104,7 +104,7 @@ for path in paths:
PY
section "Claude Code hooks"
RUNTIME_HOME="$RUNTIME_HOME" python3 - <<'PY'
RUNTIME_HOME="$RUNTIME_HOME" python3 - << 'PY'
import json
import os
from pathlib import Path

0
claude_desktop/rootfs/usr/local/bin/codex-login Executable file → Normal file
View File

14
claude_desktop/rootfs/usr/local/bin/ha-cli Executable file → Normal file
View File

@@ -96,7 +96,7 @@ def resolve_endpoint():
def _url(base, path):
path = path.lstrip("/")
if path.startswith("api/"):
path = path[len("api/"):]
path = path[len("api/") :]
return base.rstrip("/") + "/" + path
@@ -133,7 +133,9 @@ 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")
@@ -141,7 +143,9 @@ 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:
@@ -214,7 +218,9 @@ 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,7 +76,11 @@ 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 []
@@ -85,10 +89,14 @@ 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

@@ -23,7 +23,7 @@ Integrations supported:
## Configuration
| Option | Description |
|--------|-------------|
| -------- | ------------- |
| `TZ` | Timezone (e.g. `Europe/Paris`). Defaults to `Europe/London`. |
| `PUID` | User ID to run the process as. Defaults to `0` (root). |
| `PGID` | Group ID to run the process as. Defaults to `0` (root). |

View File

@@ -1,8 +1,7 @@
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

View File

@@ -33,7 +33,7 @@ mkdir -p "$HA_DATA_DIR"
# Symlink /app/config → HA persistent storage
# The .NET app uses /app/config, NOT /config at the filesystem root
if [ -d /app/config ] && [ ! -L /app/config ]; then
cp -rn /app/config/. "$HA_DATA_DIR/" 2>/dev/null || true
cp -rn /app/config/. "$HA_DATA_DIR/" 2> /dev/null || true
rm -rf /app/config
fi
ln -sfn "$HA_DATA_DIR" /app/config

View File

@@ -40,7 +40,7 @@ Configurations can be done through the app webUI, except for the following optio
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `CUSTOM_OPTIONS` | str | | Custom CLI options (e.g., `--name Homeassistant`) |
| `DROPBOX_TOKEN` | str | | Dropbox integration token (see https://cloudcmd.io/) |
| `localdisks` | str | | Local drives to mount (e.g., `sda1,sdb1,MYNAS`) |

View File

@@ -103,7 +103,7 @@ for candidate in "${cloudcmd_candidates[@]}"; do
fi
done
if [ -z "$CLOUDCMD_BIN" ] && command -v cloudcmd >/dev/null 2>&1; then
if [ -z "$CLOUDCMD_BIN" ] && command -v cloudcmd > /dev/null 2>&1; then
CLOUDCMD_BIN=$(command -v cloudcmd)
fi

View File

@@ -62,7 +62,7 @@ You can place the user folder from the theme/skeleton in /share/codex/www/user,
## Options
| Option | Description | Default | Example |
|--------|-------------|---------|---------|
| -------- | ------------- | --------- | --------- |
| `PGID` | Group ID for file permissions | `0` | `1000` |
| `PUID` | User ID for file permissions | `0` | `1000` |
| `TZ` | Timezone in long format | - | `America/Los_Angeles` |

View File

@@ -51,7 +51,7 @@ Webui can be found at `https://homeassistant:9980/browser/dist/admin/admin.html`
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `aliasgroup1` | str | | External address of the **Nextcloud** server allowed to use this Collabora (e.g. `https://nextcloud_domain\.com:443`) |
| `aliasgroup2` | str | | A second Nextcloud server, same format as `aliasgroup1` |
| `aliasgroup3` | str | | A third Nextcloud server, same format as `aliasgroup1` |

View File

@@ -30,7 +30,7 @@ database.
## Configuration
| Option | Description |
|--------|-------------|
| -------- | ------------- |
| `PUID` / `PGID` | Ownership applied to the add-on configuration directory. Defaults to `0` (root). See the note below before changing it. |
| `TZ` | Timezone, e.g. `Europe/Paris`. |
| `localdisks` | Local disks to mount, e.g. `sda1` or a disk label. |

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

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

View File

@@ -60,7 +60,7 @@ Initial setup requires creating an admin account through the web interface.
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
| `localdisks` | str | | Local drives to mount (e.g., `sda1,sdb1,MYNAS`) |
| `networkdisks` | str | | SMB shares to mount (e.g., `//SERVER/SHARE`) |

View File

@@ -46,17 +46,17 @@ fi
# 1 Export user env_vars #
############################
if [ -f "$OPTIONS_JSON" ] && command -v jq >/dev/null 2>&1; then
if [ -f "$OPTIONS_JSON" ] && command -v jq > /dev/null 2>&1; then
while IFS= read -r pair; do
name=$(jq -r '.name // empty' <<<"$pair")
value=$(jq -r '.value // empty' <<<"$pair")
name=$(jq -r '.name // empty' <<< "$pair")
value=$(jq -r '.value // empty' <<< "$pair")
if [[ $name =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
echo "Setting env variable from options: $name"
export "$name"="$value"
elif [ -n "$name" ]; then
echo "WARNING: ignoring invalid env_vars name: $name"
fi
done < <(jq -c '.env_vars[]?' "$OPTIONS_JSON" 2>/dev/null || true)
done < <(jq -c '.env_vars[]?' "$OPTIONS_JSON" 2> /dev/null || true)
fi
##################################
@@ -82,7 +82,7 @@ data_version=""
if [ -f "$VERSION_MARKER" ]; then
data_version="$(head -n 1 "$VERSION_MARKER" | tr -cd '0-9.')"
elif [ -d "$PERSISTENT_HOME/data" ] && [ -n "$(ls -A "$PERSISTENT_HOME/data" 2>/dev/null)" ]; then
elif [ -d "$PERSISTENT_HOME/data" ] && [ -n "$(ls -A "$PERSISTENT_HOME/data" 2> /dev/null)" ]; then
# Existing data without a marker: only 7.17.9 was ever shipped before markers
data_version="7.17.9"
fi
@@ -128,7 +128,7 @@ mkdir -p "$PERSISTENT_HOME"
for dir in "data" "config"; do
if [ ! -L "$ES_HOME/$dir" ]; then
if [ -d "$ES_HOME/$dir" ]; then
cp -rn "$ES_HOME/$dir" "$PERSISTENT_HOME" 2>/dev/null || true
cp -rn "$ES_HOME/$dir" "$PERSISTENT_HOME" 2> /dev/null || true
rm -rf "${ES_HOME:?}/$dir"
fi
mkdir -p "$PERSISTENT_HOME/$dir"
@@ -139,7 +139,7 @@ done
# Make the persisted files usable by the elasticsearch user (uid 1000),
# which the official entrypoint drops to when started as root
if [ "$(id -u)" -eq 0 ]; then
chown -R 1000:0 "$PERSISTENT_HOME/data" "$PERSISTENT_HOME/config" 2>/dev/null || true
chown -R 1000:0 "$PERSISTENT_HOME/data" "$PERSISTENT_HOME/config" 2> /dev/null || true
fi
echo "Data location: $PERSISTENT_HOME (persistent). Please wait while elasticsearch starts..."
@@ -156,9 +156,9 @@ if [ "$data_version" != "$current_version" ]; then
# Check the HTTP status directly instead of curl -f: a 401 means
# Elasticsearch is up and answering (security just requires
# auth), so it must count as healthy too, not as a failure.
status=$(curl -A "HealthCheck: Docker/1.0" -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:9200" 2>/dev/null || true)
status=$(curl -A "HealthCheck: Docker/1.0" -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:9200" 2> /dev/null || true)
if [ "$status" = "200" ] || [ "$status" = "401" ]; then
echo "$current_version" >"$VERSION_MARKER"
echo "$current_version" > "$VERSION_MARKER"
echo "Elasticsearch $current_version started successfully; data version recorded."
exit 0
fi

View File

@@ -43,7 +43,7 @@ Webui can be found at `<your-ip>:8096`, or within Home Assistant through Ingress
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `PGID` | int | `0` | Group ID for file permissions |
| `PUID` | int | `0` | User ID for file permissions |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |

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

View File

@@ -48,7 +48,7 @@ Initial setup requires starting the addon once to initialize configuration templ
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `CONFIG_LOCATION` | str | `/config/myelectricaldata/config.yaml` | Path to configuration file |
| `TZ` | str | `Europe/Paris` | Timezone (e.g., `Europe/London`) |
| `mqtt_autodiscover` | bool | `true` | Enable MQTT autodiscovery |

View File

@@ -48,7 +48,7 @@ Initial setup requires starting the addon once to initialize configuration templ
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `CONFIG_LOCATION` | str | `/config/myelectricaldata/config.yaml` | Path to configuration file |
| `TZ` | str | `Europe/Paris` | Timezone (e.g., `Europe/London`) |
| `mqtt_autodiscover` | bool | `true` | Enable MQTT autodiscovery |

View File

@@ -52,7 +52,7 @@ Webui can be found at <http://homeassistant:PORT>.
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `ENTE_ENDPOINT_URL` | str | `http://homeassistant.local:8280` | The URL where Ente API will be accessible (used by web UI) |
| `DB_PASSWORD` | str | `ente` | Database password for internal PostgreSQL |
| `USE_EXTERNAL_DB` | bool | `false` | Use external PostgreSQL database |
@@ -63,7 +63,7 @@ Webui can be found at <http://homeassistant:PORT>.
If you want to use an external PostgreSQL database, set `USE_EXTERNAL_DB: true` and configure:
| Option | Type | Description |
|--------|------|-------------|
| -------- | ------ | ------------- |
| `DB_HOSTNAME` | str | PostgreSQL server hostname |
| `DB_PORT` | int | PostgreSQL server port (default: 5432) |
| `DB_USERNAME` | str | PostgreSQL username |

View File

@@ -88,7 +88,7 @@ Create `/config/addons_config/epicgamesfree/config.json`:
### Configuration Options
| Option | Type | Description |
|--------|------|-------------|
| -------- | ------ | ------------- |
| `accounts` | array | List of Epic Games accounts |
| `cronSchedule` | string | Cron schedule to claim games (default: `0 */6 * * *`) |
| `runOnStartup` | boolean | Run a claim cycle when the add-on starts |

View File

@@ -22,7 +22,7 @@ if bashio::config.true 'disable_cron'; then
fi
if bashio::command.exists service; then
service cron stop >/dev/null 2>&1 || true
service cron stop > /dev/null 2>&1 || true
fi
fi

View File

@@ -59,7 +59,7 @@ The web UI can be found at `<your-ip>:8071` or through the Home Assistant sideba
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `ssl` | bool | `false` | Enable HTTPS for web interface |
| `certfile` | str | `fullchain.pem` | SSL certificate file (in `/ssl/`) |
| `keyfile` | str | `privkey.pem` | SSL private key file (in `/ssl/`) |

View File

@@ -57,7 +57,7 @@ The web UI can be found at `<your-ip>:8071` or through the Home Assistant sideba
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `auth_method` | list | `password` | Authentication method (`password`, `noauth`, `proxy`, `oidc`) |
| `default_user_scope` | str | `/` | The root filesystem path used as the FileBrowser source and as the default scope for all users. Must be an existing absolute directory path (e.g. `/share`, `/media`). |
| `localdisks` | str | _(optional)_ | Local drives to mount (e.g., `sda1,sdb1,MYNAS`) |

View File

@@ -45,7 +45,7 @@ Configurations can be done through the app webUI, except for the following optio
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `APP_KEY` | str | `CHANGEME_32_CHARS_EuC5dfn3LAPzeO` | **CRITICAL**: 32-character encryption key - change before first run! |
| `CONFIG_LOCATION` | str | `/config/addons_config/fireflyiii/config.yaml` | Location of additional config file |
| `DB_CONNECTION` | list | `sqlite_internal` | Database type (sqlite_internal/mariadb_addon/mysql/pgsql) |

View File

@@ -49,7 +49,7 @@ For complete setup documentation, see: https://docs.firefly-iii.org/data-importe
### Options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| -------- | ------ | ---------- | ------------- |
| `FIREFLY_III_URL` | str | Yes | URL to your Firefly III instance |
| `FIREFLY_III_ACCESS_TOKEN` | str | Yes | Personal Access Token from Firefly III |
| `CONFIG_LOCATION` | str | Yes | Location for configuration files |

View File

@@ -4,19 +4,27 @@ 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}"
(
echo "[INFO] Running update according to defined schedule. Files located in $CONFIGSOURCE/import_files will be imported."
if [ "${SILENT_MODE}" = "true" ]; then
echo "[INFO] Running update according to defined schedule. Files located in $CONFIGSOURCE/import_files will be imported."
if [ "${SILENT_MODE}" = "true" ]; then
echo "[WARNING] Silent mode activated. Only errors will be shown. Please disable in addon options if you need to debug"
sudo -Eu www-data /usr/local/bin/php /var/www/html/artisan importer:auto-import "$CONFIGSOURCE/import_files" >/dev/null
else
else
sudo -Eu www-data /usr/local/bin/php /var/www/html/artisan importer:auto-import "$CONFIGSOURCE/import_files"
fi
fi
) &>/proc/1/fd/1

View File

@@ -69,7 +69,7 @@ Default password: `homeassistant123` (change via addon options).
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `PGID` | int | `0` | Group ID for file permissions |
| `PUID` | int | `0` | User ID for file permissions |
| `WebuiPass` | str | `homeassistant123` | Web interface password |

View File

@@ -52,7 +52,7 @@ interaction. Set `VNC_PASSWORD` in `config.env` to protect the VNC session.
## Add-on options
| Option | Default | Description |
|--------|---------|-------------|
| -------- | --------- | ------------- |
| `CONFIG_LOCATION` | `/config/config.env` | Persistent environment configuration file |
| `RUN_ONCE` | `true` | Run all selected claimers once, then stop the add-on as previous releases did |
| `STORES` | empty | Optional comma-separated override, such as `epic,prime,gog,steam` |

View File

@@ -84,19 +84,19 @@ legacy_commands_to_stores() {
for command in "${command_list[@]}"; do
normalized="${command,,}"
case "${normalized}" in
*epic-games*|*epicgames*|*" epic"*|epic*)
*epic-games* | *epicgames* | *" epic"* | epic*)
selected="$(append_store "epic" "${selected}")"
;;
*prime-gaming*|*primegaming*|*" prime"*|prime*|*" amazon"*|amazon*)
*prime-gaming* | *primegaming* | *" prime"* | prime* | *" amazon"* | amazon*)
selected="$(append_store "prime" "${selected}")"
;;
*steam-games*|*" steam"*|steam*)
*steam-games* | *" steam"* | steam*)
selected="$(append_store "steam" "${selected}")"
;;
*gamerpower*)
selected="$(append_store "gamerpower" "${selected}")"
;;
*" gog"*|gog*)
*" gog"* | gog*)
selected="$(append_store "gog" "${selected}")"
;;
esac

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
WIDTH=1280
HEIGHT=720
TIMEOUT=60
LOGIN_TIMEOUT=180
VNC_LOGIN_TIMEOUT=180
NOVNC_PORT=6080
WIDTH=1280
# Public address used in notification links when behind a reverse proxy.
# VNC_URL=https://fgc.example.tld

View File

@@ -47,8 +47,10 @@ 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")
)
@@ -99,8 +101,7 @@ 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,
@@ -117,8 +118,7 @@ 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,7 +209,12 @@ 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

@@ -48,7 +48,7 @@ This addon fetches gas consumption data from GRDF (French gas utility) and publi
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `CONFIG_LOCATION` | str | `/config/gazpar2mqtt/config.yaml` | Path to configuration file |
| `TZ` | str | `Europe/Paris` | Timezone (e.g., `Europe/London`) |
| `mqtt_autodiscover` | bool | `true` | Enable MQTT autodiscovery |

View File

@@ -42,7 +42,7 @@ Configurations can be done through the app webUI, except for the following optio
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `ssl` | bool | `false` | Enable HTTPS for the web interface |
| `certfile` | str | `fullchain.pem` | SSL certificate file (must be located in /ssl) |
| `keyfile` | str | `privkey.pem` | SSL key file (must be located in /ssl) |

View File

@@ -53,7 +53,7 @@ Webui can be found at <http://homeassistant:5000>.
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `CELERY_NUM_WORKERS` | int | `2` | Number of Celery workers for background tasks |
| `GUNICORN_NUM_WORKERS` | int | `8` | Number of Gunicorn workers for web requests |
| `GRAMPSWEB_SECRET_KEY` | str | - | Secret key for session security (auto-generated if not set) |
@@ -65,7 +65,7 @@ Webui can be found at <http://homeassistant:5000>.
### Email Configuration (Optional)
| Option | Type | Description |
|--------|------|-------------|
| -------- | ------ | ------------- |
| `GRAMPSWEB_EMAIL_HOST` | str | SMTP server hostname |
| `GRAMPSWEB_EMAIL_PORT` | int | SMTP server port |
| `GRAMPSWEB_EMAIL_USE_SSL` | bool | Use SSL encryption (for port 465) |

View File

@@ -66,7 +66,7 @@ Configurations can be done through the app webUI, except for the following optio
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `PGID` | int | `1000` | Group ID for file permissions |
| `PUID` | int | `1000` | User ID for file permissions |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |

View File

@@ -44,7 +44,7 @@ The default username is `guacadmin` with password `guacadmin`. It is strongly re
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `EXTENSIONS` | str | `auth-totp` | Guacamole extensions to enable (e.g., `auth-totp`, `history-recording-storage`) |
| `recording_search_path` | str | `/config/recordings` | Directory added to `guacamole.properties` as the `recording-search-path` used by the history recording storage extension |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |

View File

@@ -51,7 +51,7 @@ Webui can be found at `<your-ip>:8080`. PostgreSQL can be either internal or ext
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `data_location` | str | `/share/immich` | Path where Immich data is stored |
| `library_location` | str | | Path to photo/video library |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |

View File

@@ -58,7 +58,7 @@ Webui can be found at `<your-ip>:8080`. PostgreSQL can be either internal or ext
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `data_location` | str | `/share/immich` | Path where Immich data is stored |
| `library_location` | str | | Path to photo/video library |
| `TZ` | str | | Timezone (e.g., `Europe/London`) |

View File

@@ -2,8 +2,7 @@ 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

@@ -42,7 +42,7 @@ Webui can be found at `<your-ip>:8171`.
#### Connection
| Option | Type | Description |
|--------|------|-------------|
| -------- | ------ | ------------- |
| `ImmichServerUrl` | str | URL of your Immich server (e.g., `http://homeassistant:3001`). Used for single-account setup. |
| `ApiKey` | str | Immich API key for authentication. Used for single-account setup. |
| `Accounts` | list | List of Immich accounts for multi-account support. Each entry requires `ImmichServerUrl` and `ApiKey`, plus optional per-account filters (see below). |
@@ -53,7 +53,7 @@ Webui can be found at `<your-ip>:8171`.
These top-level options map to ImmichFrame's `General` settings and control the display behavior:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| -------- | ------ | --------- | ------------- |
| `Interval` | int | 45 | Image display interval in seconds |
| `TransitionDuration` | float | 2 | Transition duration in seconds |
| `ShowClock` | bool | true | Display the current time |
@@ -91,7 +91,7 @@ These top-level options map to ImmichFrame's `General` settings and control the
These options can be set within each `Accounts` entry to control which images are shown:
| Option | Type | Description |
|--------|------|-------------|
| -------- | ------ | ------------- |
| `Albums` | str | Comma-separated album UUIDs |
| `ExcludedAlbums` | str | Comma-separated excluded album UUIDs |
| `People` | str | Comma-separated people UUIDs |

View File

@@ -9,13 +9,13 @@ if [ -L /config/Config ]; then
bashio::log.info "Migrating legacy /config/Config symlink to real directory"
mkdir -p /config/Config.migrate
# Copy contents from the symlink target into the new real directory
cp -a /config/Config/. /config/Config.migrate/ 2>/dev/null || true
cp -a /config/Config/. /config/Config.migrate/ 2> /dev/null || true
rm -f /config/Config
mv /config/Config.migrate /config/Config
fi
if [ -d /app/Config ] && [ ! -L /app/Config ]; then
cp -n /app/Config/* /config/Config/ 2>/dev/null || true
cp -n /app/Config/* /config/Config/ 2> /dev/null || true
rm -rf /app/Config
fi
if [ ! -e /app/Config ]; then
@@ -35,10 +35,10 @@ in_list() { [[ "$2" == *" $1 "* ]]; }
# Helper: read a value from options.json handling booleans and nulls correctly
config_val() {
jq -r "($1) as \$v | if \$v == null then \"\" else (\$v | tostring) end" /data/options.json 2>/dev/null
jq -r "($1) as \$v | if \$v == null then \"\" else (\$v | tostring) end" /data/options.json 2> /dev/null
}
config_has() {
jq -e "($1) != null" /data/options.json >/dev/null 2>&1
jq -e "($1) != null" /data/options.json > /dev/null 2>&1
}
# Helper: write a YAML key-value pair with proper formatting
@@ -82,7 +82,7 @@ yaml_kv() {
declare -A GENERAL_ENVS
declare -A ACCOUNT_ENVS
ENV_COUNT=$(jq '.env_vars // [] | length' /data/options.json 2>/dev/null || echo 0)
ENV_COUNT=$(jq '.env_vars // [] | length' /data/options.json 2> /dev/null || echo 0)
if [ "$ENV_COUNT" -gt 0 ]; then
bashio::log.info "Processing ${ENV_COUNT} env_var(s) for Settings.yaml"
fi
@@ -121,7 +121,10 @@ 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
@@ -129,13 +132,16 @@ 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
# -- Accounts section --
ACCOUNT_COUNT=$(jq '.Accounts // [] | length' /data/options.json 2>/dev/null || echo 0)
ACCOUNT_COUNT=$(jq '.Accounts // [] | length' /data/options.json 2> /dev/null || echo 0)
if [ "$ACCOUNT_COUNT" -gt 0 ]; then
bashio::log.info "Configuring ${ACCOUNT_COUNT} account(s) from Accounts list"

Some files were not shown because too many files have changed in this diff Show More