mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-09-17 15:19:08 +02:00
Compare commits
18 Commits
create-pul
...
d31ccfa21c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d31ccfa21c | ||
|
|
9c277876ff | ||
|
|
7e59aa8803 | ||
|
|
360fd669fa | ||
|
|
7bf77c8558 | ||
|
|
a195d3ac39 | ||
|
|
14834d4063 | ||
|
|
f5caafd62c | ||
|
|
fd61631ccd | ||
|
|
578a7f8368 | ||
|
|
5115dcb93a | ||
|
|
fa5e890c4c | ||
|
|
addb0ca66f | ||
|
|
16e112b21f | ||
|
|
040a4e0f2f | ||
|
|
f72f7bef0f | ||
|
|
c18f45d822 | ||
|
|
abf3d76873 |
@@ -30,14 +30,8 @@ VAR="${1:?usage: env_trace.sh <VAR> [process-name-or-pid]}"
|
|||||||
TARGET="${2:-}"
|
TARGET="${2:-}"
|
||||||
# VAR is interpolated into grep/sed patterns below — restrict it to a valid env var name
|
# VAR is interpolated into grep/sed patterns below — restrict it to a valid env var name
|
||||||
case "$VAR" in
|
case "$VAR" in
|
||||||
[A-Za-z_]*) [ -z "${VAR//[A-Za-z0-9_]/}" ] || {
|
[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
|
*) echo "invalid env var name: $VAR" >&2; exit 1 ;;
|
||||||
exit 1
|
|
||||||
} ;;
|
|
||||||
*)
|
|
||||||
echo "invalid env var name: $VAR" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo "== tracing ${VAR} =="
|
echo "== tracing ${VAR} =="
|
||||||
@@ -45,7 +39,7 @@ echo
|
|||||||
|
|
||||||
echo "1. /data/options.json (the user's saved options)"
|
echo "1. /data/options.json (the user's saved options)"
|
||||||
if [ -f /data/options.json ]; then
|
if [ -f /data/options.json ]; then
|
||||||
python3 - "$VAR" << 'PY'
|
python3 - "$VAR" <<'PY'
|
||||||
import json, sys
|
import json, sys
|
||||||
var = sys.argv[1]
|
var = sys.argv[1]
|
||||||
try:
|
try:
|
||||||
@@ -86,8 +80,7 @@ echo "3. s6 container_environment (only read by services using #!/usr/bin/with-c
|
|||||||
seen3=0
|
seen3=0
|
||||||
for d in /var/run/s6/container_environment /run/s6/container_environment; do
|
for d in /var/run/s6/container_environment /run/s6/container_environment; do
|
||||||
if [ -f "$d/$VAR" ]; then
|
if [ -f "$d/$VAR" ]; then
|
||||||
echo " $d/$VAR = [$(cat "$d/$VAR")]"
|
echo " $d/$VAR = [$(cat "$d/$VAR")]"; seen3=1
|
||||||
seen3=1
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
[ "$seen3" -eq 0 ] && echo " not present in either envdir"
|
[ "$seen3" -eq 0 ] && echo " not present in either envdir"
|
||||||
@@ -129,8 +122,8 @@ else
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
# What it was actually launched with beats any theory about its environment.
|
# What it was actually launched with beats any theory about its environment.
|
||||||
tr '\0' '\n' < "/proc/$pid/cmdline" 2> /dev/null | tail -n +2 \
|
tr '\0' '\n' < "/proc/$pid/cmdline" 2> /dev/null | tail -n +2 |
|
||||||
| grep -iE "res|screen|${VAR}" | head -3 | sed 's/^/ argv: /'
|
grep -iE "res|screen|${VAR}" | head -3 | sed 's/^/ argv: /'
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ awk '{t+=$2} END{printf " threads=%d\n", t}' <(ps -eo pid,nlwp --no-headers 2>
|
|||||||
echo
|
echo
|
||||||
echo "== per-process memory (top 20 by PSS) =="
|
echo "== per-process memory (top 20 by PSS) =="
|
||||||
printf ' %-28s %8s %8s %8s\n' COMMAND RSS PSS PRIVATE
|
printf ' %-28s %8s %8s %8s\n' COMMAND RSS PSS PRIVATE
|
||||||
python3 - "$OUT" << 'PY'
|
python3 - "$OUT" <<'PY'
|
||||||
import os, sys
|
import os, sys
|
||||||
rows = []
|
rows = []
|
||||||
for pid in filter(str.isdigit, os.listdir('/proc')):
|
for pid in filter(str.isdigit, os.listdir('/proc')):
|
||||||
@@ -62,17 +62,17 @@ PY
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== reserved-but-not-resident (lazy allocations, NOT leaks) =="
|
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
|
||||||
echo "== CPU over ${SAMPLE}s (idle unless you are driving the UI) =="
|
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 —
|
# 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
|
# a plain $14+$15 is wrong for anything like 'npm exec @foo' and silently reports a fabricated
|
||||||
# number rather than failing.
|
# 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
|
# 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,
|
# 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.
|
# and the busiest process is not necessarily a big one.
|
||||||
@@ -86,14 +86,14 @@ for pid in "${!t0[@]}"; do
|
|||||||
[ -r "/proc/$pid/stat" ] || continue
|
[ -r "/proc/$pid/stat" ] || continue
|
||||||
t1=$(jiffies "$pid") || continue
|
t1=$(jiffies "$pid") || continue
|
||||||
[ -n "$t1" ] && [ -n "${t0[$pid]}" ] || continue
|
[ -n "$t1" ] && [ -n "${t0[$pid]}" ] || continue
|
||||||
delta=$((t1 - ${t0[$pid]}))
|
delta=$(( t1 - ${t0[$pid]} ))
|
||||||
[ "$delta" -gt 0 ] || continue
|
[ "$delta" -gt 0 ] || continue
|
||||||
pct=$(awk -v d="$delta" -v s="$SAMPLE" -v hz="$HZ" 'BEGIN{printf "%.2f", d*100/(hz*s)}')
|
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"
|
echo "$pct $pid $comm"
|
||||||
done | sort -rn | head -12 | awk '{printf " %6s%% %-8s %s\n", $1, $2, $3}'
|
done | sort -rn | head -12 | awk '{printf " %6s%% %-8s %s\n", $1, $2, $3}'
|
||||||
|
|
||||||
echo
|
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 " (those are claude_desktop/webtop viewer ports; 0 here means CPU above is idle burn)"
|
||||||
echo " raw ps: $OUT/ps.txt"
|
echo " raw ps: $OUT/ps.txt"
|
||||||
|
|||||||
@@ -47,10 +47,7 @@ if [ -n "${BUILD_VERSION:-}" ]; then
|
|||||||
if [ -z "$SLUG" ] && [ -n "${HOSTNAME:-}" ]; then
|
if [ -z "$SLUG" ] && [ -n "${HOSTNAME:-}" ]; then
|
||||||
base=$(printf '%s' "$HOSTNAME" | sed 's/^[0-9a-f]\{8\}-//')
|
base=$(printf '%s' "$HOSTNAME" | sed 's/^[0-9a-f]\{8\}-//')
|
||||||
for cand in "$(printf '%s' "$base" | tr '-' '_')" "$base"; do
|
for cand in "$(printf '%s' "$base" | tr '-' '_')" "$base"; do
|
||||||
[ -f "$REPO/$cand/config.yaml" ] && {
|
[ -f "$REPO/$cand/config.yaml" ] && { SLUG="$cand"; break; }
|
||||||
SLUG="$cand"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
done
|
done
|
||||||
[ -z "$SLUG" ] && SLUG="$base"
|
[ -z "$SLUG" ] && SLUG="$base"
|
||||||
fi
|
fi
|
||||||
@@ -72,8 +69,8 @@ if [ -n "${BUILD_VERSION:-}" ]; then
|
|||||||
else
|
else
|
||||||
echo " MISMATCH — this branch is NOT what is running."
|
echo " MISMATCH — this branch is NOT what is running."
|
||||||
git fetch origin master --quiet 2> /dev/null
|
git fetch origin master --quiet 2> /dev/null
|
||||||
master=$(git show origin/master:"$SLUG/config.yaml" 2> /dev/null \
|
master=$(git show origin/master:"$SLUG/config.yaml" 2> /dev/null |
|
||||||
| grep -E '^version:' | head -1 | tr -d "\"'" | awk '{print $2}')
|
grep -E '^version:' | head -1 | tr -d "\"'" | awk '{print $2}')
|
||||||
echo " origin/master version = ${master:-unknown}"
|
echo " origin/master version = ${master:-unknown}"
|
||||||
echo " -> work from origin/master; analysing this branch will mislead you."
|
echo " -> work from origin/master; analysing this branch will mislead you."
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -13,31 +13,22 @@ set -uo pipefail
|
|||||||
if root=$(git rev-parse --show-toplevel 2> /dev/null); then cd "$root" || exit 1; fi
|
if root=$(git rev-parse --show-toplevel 2> /dev/null); then cd "$root" || exit 1; fi
|
||||||
|
|
||||||
ADDON="${1:-}"
|
ADDON="${1:-}"
|
||||||
[ "${ADDON:-}" = "--vs-master" ] && {
|
[ "${ADDON:-}" = "--vs-master" ] && { ADDON=""; set -- --vs-master; }
|
||||||
ADDON=""
|
|
||||||
set -- --vs-master
|
|
||||||
}
|
|
||||||
VS_MASTER=false
|
VS_MASTER=false
|
||||||
for a in "$@"; do [ "$a" = "--vs-master" ] && VS_MASTER=true; done
|
for a in "$@"; do [ "$a" = "--vs-master" ] && VS_MASTER=true; done
|
||||||
|
|
||||||
if [ -z "$ADDON" ]; then
|
if [ -z "$ADDON" ]; then
|
||||||
mapfile -t _dirs < <(git diff --name-only origin/master...HEAD 2> /dev/null \
|
mapfile -t _dirs < <(git diff --name-only origin/master...HEAD 2> /dev/null |
|
||||||
| cut -d/ -f1 | sort -u | grep -vE '^\.')
|
cut -d/ -f1 | sort -u | grep -vE '^\.' )
|
||||||
if [ "${#_dirs[@]}" -gt 1 ]; then
|
if [ "${#_dirs[@]}" -gt 1 ]; then
|
||||||
echo "several changed dirs: ${_dirs[*]}"
|
echo "several changed dirs: ${_dirs[*]}"
|
||||||
echo "pass one explicitly: validate.sh <addon-dir>"
|
echo "pass one explicitly: validate.sh <addon-dir>"; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
ADDON="${_dirs[0]:-}"
|
ADDON="${_dirs[0]:-}"
|
||||||
fi
|
fi
|
||||||
[ -z "$ADDON" ] && {
|
[ -z "$ADDON" ] && { echo "usage: validate.sh <addon-dir> [--vs-master]"; exit 1; }
|
||||||
echo "usage: validate.sh <addon-dir> [--vs-master]"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
git rev-parse --verify origin/master > /dev/null 2>&1 || {
|
git rev-parse --verify origin/master > /dev/null 2>&1 || {
|
||||||
echo "origin/master missing — run: git fetch origin master"
|
echo "origin/master missing — run: git fetch origin master"; exit 1; }
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
export PYTHONDONTWRITEBYTECODE=1
|
export PYTHONDONTWRITEBYTECODE=1
|
||||||
echo "== validating $ADDON =="
|
echo "== validating $ADDON =="
|
||||||
|
|
||||||
@@ -45,22 +36,14 @@ fail=0
|
|||||||
note() { printf ' %-13s %s\n' "$1" "$2"; }
|
note() { printf ' %-13s %s\n' "$1" "$2"; }
|
||||||
# execline `run`/`finish` files are not shell (25 of them here, across 21 add-ons). Neither
|
# 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.
|
# linter below can read one, so anything either says about it is noise.
|
||||||
is_execline() {
|
is_execline() { local l; IFS= read -r l < "$1" 2> /dev/null; [[ $l == '#!'*execlineb* ]]; }
|
||||||
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.
|
# Shell: bash -n then shellcheck -x (follows sourced files, as CI does). One list for both.
|
||||||
files=()
|
files=()
|
||||||
while IFS= read -r f; do is_execline "$f" || files+=("$f"); done \
|
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)
|
< <(find "$ADDON" -type f \( -name '*.sh' -o -name 'run' -o -name 'finish' -o -name 'autostart' \) 2> /dev/null)
|
||||||
for f in "${files[@]}"; do
|
for f in "${files[@]}"; do
|
||||||
if ! out=$(bash -n "$f" 2>&1); then
|
if ! out=$(bash -n "$f" 2>&1); then note "bash -n" "FAIL $f"; echo "$out" | sed 's/^/ /'; fail=1; fi
|
||||||
note "bash -n" "FAIL $f"
|
|
||||||
echo "$out" | sed 's/^/ /'
|
|
||||||
fail=1
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
[ "$fail" -eq 0 ] && note "bash -n" "${#files[@]} file(s) checked"
|
[ "$fail" -eq 0 ] && note "bash -n" "${#files[@]} file(s) checked"
|
||||||
|
|
||||||
@@ -82,16 +65,13 @@ command -v hadolint > /dev/null 2>&1 && [ -f "$ADDON/Dockerfile" ] && {
|
|||||||
|
|
||||||
if [ -f "$ADDON/config.yaml" ]; then
|
if [ -f "$ADDON/config.yaml" ]; then
|
||||||
# path passed as argv, never interpolated into Python source
|
# path passed as argv, never interpolated into Python source
|
||||||
python3 - "$ADDON/config.yaml" << 'PY' || {
|
python3 - "$ADDON/config.yaml" <<'PY' || { note "config.yaml" "FAIL parse"; fail=1; }
|
||||||
import yaml,sys
|
import yaml,sys
|
||||||
d=yaml.safe_load(open(sys.argv[1]))
|
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 {})))
|
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 {})]
|
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)
|
if missing: print(' %-13s options with no schema entry: %s' % ('WARN', missing)); sys.exit(0)
|
||||||
PY
|
PY
|
||||||
note "config.yaml" "FAIL parse"
|
|
||||||
fail=1
|
|
||||||
}
|
|
||||||
command -v yamllint > /dev/null 2>&1 && {
|
command -v yamllint > /dev/null 2>&1 && {
|
||||||
yl=$(yamllint -f parsable "$ADDON/config.yaml" 2>&1 | grep -c .)
|
yl=$(yamllint -f parsable "$ADDON/config.yaml" 2>&1 | grep -c .)
|
||||||
note "yamllint" "$yl finding(s) (compare with --vs-master)"
|
note "yamllint" "$yl finding(s) (compare with --vs-master)"
|
||||||
@@ -99,10 +79,7 @@ PY
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
while IFS= read -r f; do
|
while IFS= read -r f; do
|
||||||
python3 -m py_compile "$f" 2> /dev/null || {
|
python3 -m py_compile "$f" 2> /dev/null || { note "py_compile" "FAIL $f"; fail=1; }
|
||||||
note "py_compile" "FAIL $f"
|
|
||||||
fail=1
|
|
||||||
}
|
|
||||||
done < <(find "$ADDON" -type f -name '*.py' 2> /dev/null)
|
done < <(find "$ADDON" -type f -name '*.py' 2> /dev/null)
|
||||||
|
|
||||||
$VS_MASTER && command -v npx > /dev/null 2>&1 && [ -f "$ADDON/CHANGELOG.md" ] && {
|
$VS_MASTER && command -v npx > /dev/null 2>&1 && [ -f "$ADDON/CHANGELOG.md" ] && {
|
||||||
@@ -119,8 +96,7 @@ if git diff --name-only origin/master...HEAD 2> /dev/null | grep -Fxq "$ADDON/CH
|
|||||||
note "CHANGELOG" "updated"
|
note "CHANGELOG" "updated"
|
||||||
else
|
else
|
||||||
# This one IS gated: onpr_check-pr.yaml exits 1 without it.
|
# This one IS gated: onpr_check-pr.yaml exits 1 without it.
|
||||||
note "CHANGELOG" "NOT UPDATED for $ADDON — CI hard-gates this"
|
note "CHANGELOG" "NOT UPDATED for $ADDON — CI hard-gates this"; fail=1
|
||||||
fail=1
|
|
||||||
fi
|
fi
|
||||||
if git diff origin/master...HEAD -- "$ADDON/config.yaml" 2> /dev/null | grep -q '^+version:'; then
|
if git diff origin/master...HEAD -- "$ADDON/config.yaml" 2> /dev/null | grep -q '^+version:'; then
|
||||||
note "version" "bumped"
|
note "version" "bumped"
|
||||||
@@ -134,8 +110,7 @@ note "docker build" "NOT tested locally (dockerd unavailable) — CI is the only
|
|||||||
if $VS_MASTER; then
|
if $VS_MASTER; then
|
||||||
echo
|
echo
|
||||||
echo "== findings ADDED by this diff (pre-existing ones filtered out) =="
|
echo "== findings ADDED by this diff (pre-existing ones filtered out) =="
|
||||||
tmp=$(mktemp -d)
|
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
|
||||||
trap 'rm -rf "$tmp"' EXIT
|
|
||||||
added=0
|
added=0
|
||||||
# Fed by process substitution, not a pipe: a pipeline runs this in a subshell, where the
|
# 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.
|
# findings below could never reach $fail and the verdict would contradict the list.
|
||||||
@@ -150,26 +125,14 @@ if $VS_MASTER; then
|
|||||||
# cancel it out and report a false clean.
|
# cancel it out and report a false clean.
|
||||||
case "$f" in
|
case "$f" in
|
||||||
*.sh | *autostart | */run | */finish)
|
*.sh | *autostart | */run | */finish)
|
||||||
command -v shellcheck > /dev/null 2>&1 || {
|
command -v shellcheck > /dev/null 2>&1 || { echo " $f: SKIPPED (shellcheck not installed)"; continue; }
|
||||||
echo " $f: SKIPPED (shellcheck not installed)"
|
cmd() { shellcheck -x -f gcc "$1" 2>&1 | sed 's/^[^:]*:[0-9]*:[0-9]*://'; } ;;
|
||||||
continue
|
|
||||||
}
|
|
||||||
cmd() { shellcheck -x -f gcc "$1" 2>&1 | sed 's/^[^:]*:[0-9]*:[0-9]*://'; }
|
|
||||||
;;
|
|
||||||
*.yaml | *.yml)
|
*.yaml | *.yml)
|
||||||
command -v yamllint > /dev/null 2>&1 || {
|
command -v yamllint > /dev/null 2>&1 || { echo " $f: SKIPPED (yamllint not installed)"; continue; }
|
||||||
echo " $f: SKIPPED (yamllint not installed)"
|
cmd() { yamllint -f parsable "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*:[0-9]*//'; } ;;
|
||||||
continue
|
|
||||||
}
|
|
||||||
cmd() { yamllint -f parsable "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*:[0-9]*//'; }
|
|
||||||
;;
|
|
||||||
*Dockerfile)
|
*Dockerfile)
|
||||||
command -v hadolint > /dev/null 2>&1 || {
|
command -v hadolint > /dev/null 2>&1 || { echo " $f: SKIPPED (hadolint not installed)"; continue; }
|
||||||
echo " $f: SKIPPED (hadolint not installed)"
|
cmd() { hadolint "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*//'; } ;;
|
||||||
continue
|
|
||||||
}
|
|
||||||
cmd() { hadolint "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*//'; }
|
|
||||||
;;
|
|
||||||
*) continue ;;
|
*) continue ;;
|
||||||
esac
|
esac
|
||||||
b=$(cmd "$tmp/base" | sort)
|
b=$(cmd "$tmp/base" | sort)
|
||||||
@@ -178,8 +141,7 @@ if $VS_MASTER; then
|
|||||||
[ -n "$new" ] && {
|
[ -n "$new" ] && {
|
||||||
echo " $f: $(printf '%s\n' "$new" | grep -c .) NEW finding(s)"
|
echo " $f: $(printf '%s\n' "$new" | grep -c .) NEW finding(s)"
|
||||||
printf '%s\n' "$new" | sed 's/^/ /' | head -5
|
printf '%s\n' "$new" | sed 's/^/ /' | head -5
|
||||||
added=1
|
added=1; fail=1
|
||||||
fail=1
|
|
||||||
}
|
}
|
||||||
done < <(git diff --name-only origin/master...HEAD -- "$ADDON" 2> /dev/null)
|
done < <(git diff --name-only origin/master...HEAD -- "$ADDON" 2> /dev/null)
|
||||||
[ "$added" -eq 0 ] && echo " (none — this diff introduced no new lint findings)"
|
[ "$added" -eq 0 ] && echo " (none — this diff introduced no new lint findings)"
|
||||||
|
|||||||
2
.github/prompts/issue-classify.md
vendored
2
.github/prompts/issue-classify.md
vendored
@@ -40,7 +40,7 @@ the check is a guard, not a common case.)
|
|||||||
## Rule 1 — pick exactly one verdict
|
## Rule 1 — pick exactly one verdict
|
||||||
|
|
||||||
| verdict | when |
|
| verdict | when |
|
||||||
| --- | --- |
|
|---|---|
|
||||||
| `duplicate` | An existing open or closed issue reports the same thing. Set `duplicate_of`. |
|
| `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. |
|
| `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. |
|
| `question` | A usage question answerable from `DOCS.md`, the wiki, or the add-on config. Not a defect. |
|
||||||
|
|||||||
2
.github/prompts/issue-fix.md
vendored
2
.github/prompts/issue-fix.md
vendored
@@ -39,7 +39,7 @@ rest are on you: nothing checks them, so breaking one ships silently.
|
|||||||
that value, and compare:
|
that value, and compare:
|
||||||
|
|
||||||
| `version` vs `U` | what to do | example |
|
| `version` vs `U` | what to do | example |
|
||||||
| --- | --- | --- |
|
|---|---|---|
|
||||||
| identical | **append** `.1` | sonarr `4.0.19.3001` -> `4.0.19.3001.1` |
|
| 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` |
|
| `U` + `.` + digits | **increment** those digits | radarr `6.3.0.10514.1` -> `6.3.0.10514.2` |
|
||||||
| anything else | **leave it alone** | plex, readarr, joal |
|
| anything else | **leave it alone** | plex, readarr, joal |
|
||||||
|
|||||||
2
.github/scripts/ai_guard_paths.sh
vendored
2
.github/scripts/ai_guard_paths.sh
vendored
@@ -44,7 +44,7 @@ for pr in $PRS; do
|
|||||||
echo "$BAD"
|
echo "$BAD"
|
||||||
# Ensure the label exists before adding it — with set -e a missing
|
# 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.
|
# 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 ready "$pr" --repo "$REPO" --undo || true
|
||||||
gh pr edit "$pr" --repo "$REPO" --add-label "ai:blocked"
|
gh pr edit "$pr" --repo "$REPO" --add-label "ai:blocked"
|
||||||
gh pr comment "$pr" --repo "$REPO" --body \
|
gh pr comment "$pr" --repo "$REPO" --body \
|
||||||
|
|||||||
22
.github/scripts/ai_triage_context.sh
vendored
22
.github/scripts/ai_triage_context.sh
vendored
@@ -21,17 +21,14 @@ TITLE=$(jq -r '.title' "$OUT/issue.json")
|
|||||||
|
|
||||||
# ---------------------------------------------------------------- addon slug
|
# ---------------------------------------------------------------- addon slug
|
||||||
# Titles follow "🐛 [Immich Frame] ENV_VARS arent being picked up".
|
# 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=""
|
ADDON=""
|
||||||
if [ -n "$RAW" ]; then
|
if [ -n "$RAW" ]; then
|
||||||
CAND=$(tr '[:upper:] ' '[:lower:]_' <<< "$RAW")
|
CAND=$(tr '[:upper:] ' '[:lower:]_' <<<"$RAW")
|
||||||
# Directory list without checking out any of them.
|
# Directory list without checking out any of them.
|
||||||
git ls-tree -d --name-only HEAD > "$OUT/dirs.txt"
|
git ls-tree -d --name-only HEAD > "$OUT/dirs.txt"
|
||||||
for guess in "$CAND" "${CAND//_/-}" "${CAND//_/.}"; do
|
for guess in "$CAND" "${CAND//_/-}" "${CAND//_/.}"; do
|
||||||
if grep -qxF "$guess" "$OUT/dirs.txt"; then
|
if grep -qxF "$guess" "$OUT/dirs.txt"; then ADDON="$guess"; break; fi
|
||||||
ADDON="$guess"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
# Separator-insensitive exact match: a title like "[Calibre-web]" (hyphen)
|
# Separator-insensitive exact match: a title like "[Calibre-web]" (hyphen)
|
||||||
# against a directory named calibre_web (underscore) matches neither exact
|
# against a directory named calibre_web (underscore) matches neither exact
|
||||||
@@ -39,12 +36,9 @@ if [ -n "$RAW" ]; then
|
|||||||
# below, which picks the shorter "calibre" instead — the wrong add-on.
|
# below, which picks the shorter "calibre" instead — the wrong add-on.
|
||||||
# Stripping -, _, . from both sides before comparing catches this case.
|
# Stripping -, _, . from both sides before comparing catches this case.
|
||||||
if [ -z "$ADDON" ]; then
|
if [ -z "$ADDON" ]; then
|
||||||
CAND_STRIPPED=$(tr -d '_.-' <<< "$CAND")
|
CAND_STRIPPED=$(tr -d '_.-' <<<"$CAND")
|
||||||
while IFS= read -r dir; do
|
while IFS= read -r dir; do
|
||||||
if [ "$(tr -d '_.-' <<< "$dir")" = "$CAND_STRIPPED" ]; then
|
if [ "$(tr -d '_.-' <<<"$dir")" = "$CAND_STRIPPED" ]; then ADDON="$dir"; break; fi
|
||||||
ADDON="$dir"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done < "$OUT/dirs.txt"
|
done < "$OUT/dirs.txt"
|
||||||
fi
|
fi
|
||||||
# Last resort: longest directory name contained in the candidate.
|
# Last resort: longest directory name contained in the candidate.
|
||||||
@@ -105,7 +99,7 @@ if [ -n "$ADDON" ]; then
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "## Recent commits touching ${ADDON}/"
|
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
|
fi
|
||||||
} >> "$CTX"
|
} >> "$CTX"
|
||||||
fi
|
fi
|
||||||
@@ -114,14 +108,14 @@ fi
|
|||||||
{
|
{
|
||||||
echo
|
echo
|
||||||
echo "## Similar existing issues (candidate duplicates)"
|
echo "## Similar existing issues (candidate duplicates)"
|
||||||
KEYWORDS=$(tr -cs '[:alnum:]' ' ' <<< "$TITLE" \
|
KEYWORDS=$(tr -cs '[:alnum:]' ' ' <<<"$TITLE" \
|
||||||
| tr '[:upper:]' '[:lower:]' \
|
| tr '[:upper:]' '[:lower:]' \
|
||||||
| tr ' ' '\n' | awk 'length($0)>3' | head -n6 | paste -sd' ')
|
| tr ' ' '\n' | awk 'length($0)>3' | head -n6 | paste -sd' ')
|
||||||
# Excludes the issue being triaged: if it's already indexed by GitHub search
|
# 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
|
# by the time this runs, keyword overlap with its own title would otherwise
|
||||||
# list it as a "candidate duplicate" of itself.
|
# list it as a "candidate duplicate" of itself.
|
||||||
gh search issues --repo "$REPO" --limit 15 \
|
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" \
|
| jq -r --argjson self "$ISSUE_NUMBER" \
|
||||||
'.[] | select(.number != $self) | "- #\(.number) [\(.state)] \(.title)"' \
|
'.[] | select(.number != $self) | "- #\(.number) [\(.state)] \(.title)"' \
|
||||||
|| echo "(search unavailable)"
|
|| echo "(search unavailable)"
|
||||||
|
|||||||
2
.github/workflows/daily_ai_fix.yaml
vendored
2
.github/workflows/daily_ai_fix.yaml
vendored
@@ -125,7 +125,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Analyse and fix
|
- name: Analyse and fix
|
||||||
if: steps.batch.outputs.count != '0'
|
if: steps.batch.outputs.count != '0'
|
||||||
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
|
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Skip the OIDC -> Claude App token exchange. The scheduled path
|
# Skip the OIDC -> Claude App token exchange. The scheduled path
|
||||||
|
|||||||
2
.github/workflows/on_claude_mention.yml
vendored
2
.github/workflows/on_claude_mention.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
|||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Run Claude Code
|
- name: Run Claude Code
|
||||||
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
|
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.
|
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.
|
||||||
|
|||||||
2
.github/workflows/on_issue_approved.yaml
vendored
2
.github/workflows/on_issue_approved.yaml
vendored
@@ -135,7 +135,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Execute the plan
|
- name: Execute the plan
|
||||||
if: steps.bundle.outputs.has_plan == 'true'
|
if: steps.bundle.outputs.has_plan == 'true'
|
||||||
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
|
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||||
|
|||||||
2
.github/workflows/on_issues_ai_triage.yaml
vendored
2
.github/workflows/on_issues_ai_triage.yaml
vendored
@@ -166,7 +166,7 @@ jobs:
|
|||||||
id: classify
|
id: classify
|
||||||
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
|
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
|
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Without this the action falls back to the OIDC -> Claude App token
|
# Without this the action falls back to the OIDC -> Claude App token
|
||||||
|
|||||||
2
.github/workflows/on_pr_coderabbit.yml
vendored
2
.github/workflows/on_pr_coderabbit.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Address CodeRabbit comments
|
- name: Address CodeRabbit comments
|
||||||
if: steps.claim.outputs.go == 'true'
|
if: steps.claim.outputs.go == 'true'
|
||||||
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
|
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ set -e
|
|||||||
# Banner
|
# 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
|
# Source standalone bashio first to provide function definitions
|
||||||
if [ -f /usr/local/lib/bashio-standalone.sh ]; then
|
if [ -f /usr/local/lib/bashio-standalone.sh ]; then
|
||||||
source /usr/local/lib/bashio-standalone.sh
|
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
|
if [ ! -f /usr/bin/bashio ]; then
|
||||||
cp -rf /usr/local/lib/bashio-standalone.sh /usr/bin/bashio
|
cp -rf /usr/local/lib/bashio-standalone.sh /usr/bin/bashio
|
||||||
fi
|
fi
|
||||||
grep -rl "^#!.*bashio" /etc \
|
grep -rl "^#!.*bashio" /etc |
|
||||||
| while IFS= read -r f; do
|
while IFS= read -r f; do
|
||||||
grep -qF "source /usr/local/lib/bashio-standalone.sh" "$f" && continue
|
grep -qF "source /usr/local/lib/bashio-standalone.sh" "$f" && continue
|
||||||
sed -i '1a source /usr/local/lib/bashio-standalone.sh' "$f"
|
sed -i '1a source /usr/local/lib/bashio-standalone.sh' "$f"
|
||||||
done
|
done
|
||||||
@@ -49,7 +49,7 @@ bashio::log.green ' Provided by: https://github.com/alexbelgium/hassio-addons '
|
|||||||
bashio::log.blue '-----------------------------------------------------------'
|
bashio::log.blue '-----------------------------------------------------------'
|
||||||
|
|
||||||
# Adapt user abc
|
# 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
|
if bashio::config.has_value "PUID" && bashio::config.has_value "PGID"; then
|
||||||
PUID="$(bashio::config "PUID")"
|
PUID="$(bashio::config "PUID")"
|
||||||
PGID="$(bashio::config "PGID")"
|
PGID="$(bashio::config "PGID")"
|
||||||
|
|||||||
@@ -152,15 +152,15 @@ if [[ "${1:-}" == "--self-test" ]]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ "$self_test_rc" -eq 0 ]] \
|
[[ "$self_test_rc" -eq 0 ]] &&
|
||||||
&& echo "${#self_test_values[@]} values round-tripped unchanged (export block + dotenv)"
|
echo "${#self_test_values[@]} values round-tripped unchanged (export block + dotenv)"
|
||||||
exit "$self_test_rc"
|
exit "$self_test_rc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Guard: only run inside Supervisor-managed add-ons
|
# 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 "..."
|
echo "..."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -179,7 +179,7 @@ ENV_FILE="/.env"
|
|||||||
ETC_ENV_FILE="/etc/environment"
|
ETC_ENV_FILE="/etc/environment"
|
||||||
|
|
||||||
[[ -f "$JSONSOURCE" ]] || bashio::exit.nok "Missing $JSONSOURCE"
|
[[ -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
|
mkdir -p /etc
|
||||||
touch "$ETC_ENV_FILE"
|
touch "$ETC_ENV_FILE"
|
||||||
@@ -193,10 +193,7 @@ SECRETSOURCE=""
|
|||||||
|
|
||||||
resolve_secret() {
|
resolve_secret() {
|
||||||
local v="$1" name line
|
local v="$1" name line
|
||||||
[[ "$v" =~ ^[[:space:]]*\!secret[[:space:]]+(.+)$ ]] || {
|
[[ "$v" =~ ^[[:space:]]*\!secret[[:space:]]+(.+)$ ]] || { printf '%s' "$v"; return; }
|
||||||
printf '%s' "$v"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
name="${BASH_REMATCH[1]}"
|
name="${BASH_REMATCH[1]}"
|
||||||
[[ -n "$SECRETSOURCE" ]] || bashio::exit.nok "Secrets not mounted"
|
[[ -n "$SECRETSOURCE" ]] || bashio::exit.nok "Secrets not mounted"
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
set -e
|
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"
|
bashio::log.blue "Disabled : please use another method"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -33,16 +33,16 @@ test_mount() {
|
|||||||
|
|
||||||
_test_write() {
|
_test_write() {
|
||||||
local testfile="$mountpoint/.writetest_$$"
|
local testfile="$mountpoint/.writetest_$$"
|
||||||
if : > "$testfile" 2> /dev/null; then
|
if : >"$testfile" 2>/dev/null; then
|
||||||
rm -f "$testfile" 2> /dev/null || true
|
rm -f "$testfile" 2>/dev/null || true
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
rm -f "$testfile" 2> /dev/null || true
|
rm -f "$testfile" 2>/dev/null || true
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
if _test_write 2> /dev/null; then
|
if _test_write 2>/dev/null; then
|
||||||
MOUNTED=true
|
MOUNTED=true
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -53,9 +53,9 @@ test_mount() {
|
|||||||
echo "... retrying mount with noserverino"
|
echo "... retrying mount with noserverino"
|
||||||
MOUNTOPTIONS="${orig_mountoptions},noserverino"
|
MOUNTOPTIONS="${orig_mountoptions},noserverino"
|
||||||
|
|
||||||
umount "$mountpoint" 2> /dev/null || true
|
umount "$mountpoint" 2>/dev/null || true
|
||||||
if mount_drive "$MOUNTOPTIONS"; then
|
if mount_drive "$MOUNTOPTIONS"; then
|
||||||
if _test_write 2> /dev/null; then
|
if _test_write 2>/dev/null; then
|
||||||
MOUNTED=true
|
MOUNTED=true
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -66,9 +66,9 @@ test_mount() {
|
|||||||
echo "... retrying mount with noperm"
|
echo "... retrying mount with noperm"
|
||||||
MOUNTOPTIONS="${orig_mountoptions},noperm"
|
MOUNTOPTIONS="${orig_mountoptions},noperm"
|
||||||
|
|
||||||
umount "$mountpoint" 2> /dev/null || true
|
umount "$mountpoint" 2>/dev/null || true
|
||||||
if mount_drive "$MOUNTOPTIONS"; then
|
if mount_drive "$MOUNTOPTIONS"; then
|
||||||
if _test_write 2> /dev/null; then
|
if _test_write 2>/dev/null; then
|
||||||
MOUNTED=true
|
MOUNTED=true
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -88,9 +88,9 @@ mount_drive() {
|
|||||||
MOUNTOPTIONS="$1"
|
MOUNTOPTIONS="$1"
|
||||||
|
|
||||||
if [[ "$FSTYPE" == "cifs" ]]; then
|
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
|
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
|
fi
|
||||||
|
|
||||||
if [[ "$MOUNTED" == "true" ]]; then
|
if [[ "$MOUNTED" == "true" ]]; then
|
||||||
@@ -106,7 +106,7 @@ retry_cifs_with_vers_ladder_on_dialect_failure() {
|
|||||||
|
|
||||||
local err mountpoint
|
local err mountpoint
|
||||||
mountpoint="/mnt/$diskname"
|
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
|
if echo "$err" | grep -Eq 'mount error\(13\)|Permission denied|NT_STATUS_(LOGON_FAILURE|ACCESS_DENIED)|STATUS_(LOGON_FAILURE|ACCESS_DENIED)'; then
|
||||||
return 0
|
return 0
|
||||||
@@ -132,7 +132,7 @@ retry_cifs_with_vers_ladder_on_dialect_failure() {
|
|||||||
for vers in "${vers_variants[@]}"; do
|
for vers in "${vers_variants[@]}"; do
|
||||||
for sectry in "${sec_variants[@]}"; do
|
for sectry in "${sec_variants[@]}"; do
|
||||||
[[ "$MOUNTED" == "false" ]] || break
|
[[ "$MOUNTED" == "false" ]] || break
|
||||||
umount "$mountpoint" 2> /dev/null || true
|
umount "$mountpoint" 2>/dev/null || true
|
||||||
try_opts="${base_opts}${vopt},vers=${vers}${sectry}"
|
try_opts="${base_opts}${vopt},vers=${vers}${sectry}"
|
||||||
mount_drive "$try_opts"
|
mount_drive "$try_opts"
|
||||||
done
|
done
|
||||||
@@ -154,7 +154,7 @@ retry_cifs_with_vers_ladder_on_dialect_failure() {
|
|||||||
for vers in "${vers_variants2[@]}"; do
|
for vers in "${vers_variants2[@]}"; do
|
||||||
for sectry in "${sec_variants[@]}"; do
|
for sectry in "${sec_variants[@]}"; do
|
||||||
[[ "$MOUNTED" == "false" ]] || break
|
[[ "$MOUNTED" == "false" ]] || break
|
||||||
umount "$mountpoint" 2> /dev/null || true
|
umount "$mountpoint" 2>/dev/null || true
|
||||||
try_opts="${base_opts}${vopt},vers=${vers}${sectry}"
|
try_opts="${base_opts}${vopt},vers=${vers}${sectry}"
|
||||||
mount_drive "$try_opts"
|
mount_drive "$try_opts"
|
||||||
done
|
done
|
||||||
@@ -167,7 +167,7 @@ retry_cifs_with_vers_ladder_on_dialect_failure() {
|
|||||||
for vopt in "${opt_variants[@]}"; do
|
for vopt in "${opt_variants[@]}"; do
|
||||||
for vers in "${vers_variants2[@]}"; do
|
for vers in "${vers_variants2[@]}"; do
|
||||||
[[ "$MOUNTED" == "false" ]] || break
|
[[ "$MOUNTED" == "false" ]] || break
|
||||||
umount "$mountpoint" 2> /dev/null || true
|
umount "$mountpoint" 2>/dev/null || true
|
||||||
try_opts="${base_opts}${vopt},vers=${vers},sec=ntlmssp"
|
try_opts="${base_opts}${vopt},vers=${vers},sec=ntlmssp"
|
||||||
mount_drive "$try_opts"
|
mount_drive "$try_opts"
|
||||||
done
|
done
|
||||||
@@ -246,13 +246,13 @@ if bashio::config.has_value 'networkdisks'; then
|
|||||||
diskname="${diskname//\\//}"
|
diskname="${diskname//\\//}"
|
||||||
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."
|
bashio::log.warning "...... mount point /mnt/$diskname already in use (name collision for $disk). Skipping this share."
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ERRORCODE_FILE="/tmp/mount_error_${diskname//[^a-zA-Z0-9._-]/_}.log"
|
ERRORCODE_FILE="/tmp/mount_error_${diskname//[^a-zA-Z0-9._-]/_}.log"
|
||||||
: > "$ERRORCODE_FILE" || true
|
: >"$ERRORCODE_FILE" || true
|
||||||
|
|
||||||
MOUNTED=false
|
MOUNTED=false
|
||||||
CIFS_LADDER_ATTEMPTED=false
|
CIFS_LADDER_ATTEMPTED=false
|
||||||
@@ -266,13 +266,13 @@ if bashio::config.has_value 'networkdisks'; then
|
|||||||
if [[ "$FSTYPE" == "cifs" ]]; then
|
if [[ "$FSTYPE" == "cifs" ]]; then
|
||||||
if [[ ! "$disk" =~ ^//[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/.+ ]]; 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"
|
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
|
continue
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ ! "$disk" =~ ^[^/:]+:/.+ ]]; then
|
if [[ ! "$disk" =~ ^[^/:]+:/.+ ]]; then
|
||||||
bashio::log.fatal "...... invalid NFS path \"$disk\". Use server:/export/path or 123.12.12.12:/export/path"
|
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
|
continue
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -289,7 +289,7 @@ if bashio::config.has_value 'networkdisks'; then
|
|||||||
if [[ -n "${CIFSDOMAIN:-}" ]]; then
|
if [[ -n "${CIFSDOMAIN:-}" ]]; then
|
||||||
printf 'domain=%s\n' "$CIFSDOMAIN"
|
printf 'domain=%s\n' "$CIFSDOMAIN"
|
||||||
fi
|
fi
|
||||||
} > "$CRED_FILE"
|
} >"$CRED_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$FSTYPE" == "cifs" ]]; then
|
if [[ "$FSTYPE" == "cifs" ]]; then
|
||||||
@@ -303,8 +303,8 @@ if bashio::config.has_value 'networkdisks'; then
|
|||||||
|
|
||||||
if [[ "$MOUNTED" == "false" ]]; then
|
if [[ "$MOUNTED" == "false" ]]; then
|
||||||
if [[ "$FSTYPE" == "cifs" ]]; then
|
if [[ "$FSTYPE" == "cifs" ]]; then
|
||||||
if command -v nmap > /dev/null 2>&1; then
|
if command -v nmap >/dev/null 2>&1; then
|
||||||
output="$(nmap -F "$server" -T5 -oG - 2> /dev/null || true)"
|
output="$(nmap -F "$server" -T5 -oG - 2>/dev/null || true)"
|
||||||
if ! echo "$output" | grep -q "445/open"; then
|
if ! echo "$output" | grep -q "445/open"; then
|
||||||
if echo "$output" | grep -q "/open"; then
|
if echo "$output" | grep -q "/open"; then
|
||||||
bashio::log.fatal "...... $server is reachable but SMB port not opened, stopping script"
|
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"
|
bashio::log.fatal "...... fatal : $server not reachable, is it correct"
|
||||||
fi
|
fi
|
||||||
cleanup_cred
|
cleanup_cred
|
||||||
rm -f "$ERRORCODE_FILE" 2> /dev/null || true
|
rm -f "$ERRORCODE_FILE" 2>/dev/null || true
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
echo "...... $server is confirmed reachable"
|
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"
|
bashio::log.warning "...... nmap not available; skipping SMB port reachability test"
|
||||||
fi
|
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)"
|
OUTPUT="$(smbclient -t 2 -L "$server" -U "$USERNAME%$PASSWORD" -c "exit" $DOMAINCLIENT 2>&1 || true)"
|
||||||
|
|
||||||
if echo "$OUTPUT" | grep -q "LOGON_FAILURE"; then
|
if echo "$OUTPUT" | grep -q "LOGON_FAILURE"; then
|
||||||
bashio::log.fatal "...... incorrect Username, Password, or Domain! Script will stop."
|
bashio::log.fatal "...... incorrect Username, Password, or Domain! Script will stop."
|
||||||
cleanup_cred
|
cleanup_cred
|
||||||
rm -f "$ERRORCODE_FILE" 2> /dev/null || true
|
rm -f "$ERRORCODE_FILE" 2>/dev/null || true
|
||||||
bashio::addon.stop
|
bashio::addon.stop
|
||||||
elif echo "$OUTPUT" | grep -q "tree connect failed" || echo "$OUTPUT" | grep -q "NT_STATUS_CONNECTION_DISCONNECTED"; then
|
elif echo "$OUTPUT" | grep -q "tree connect failed" || echo "$OUTPUT" | grep -q "NT_STATUS_CONNECTION_DISCONNECTED"; then
|
||||||
echo "... using SMBv1"
|
echo "... using SMBv1"
|
||||||
@@ -344,8 +344,8 @@ if bashio::config.has_value 'networkdisks'; then
|
|||||||
|
|
||||||
SMBRAW=""
|
SMBRAW=""
|
||||||
SMB1_DETECTED=false
|
SMB1_DETECTED=false
|
||||||
if command -v nmap > /dev/null 2>&1; then
|
if command -v nmap >/dev/null 2>&1; then
|
||||||
NMAP_OUTPUT="$(nmap --script smb-protocols -p 445 "$server" 2> /dev/null || true)"
|
NMAP_OUTPUT="$(nmap --script smb-protocols -p 445 "$server" 2>/dev/null || true)"
|
||||||
SMBRAW="$(
|
SMBRAW="$(
|
||||||
echo "$NMAP_OUTPUT" \
|
echo "$NMAP_OUTPUT" \
|
||||||
| awk '/SMB2_DIALECT_/ {print $NF}' \
|
| awk '/SMB2_DIALECT_/ {print $NF}' \
|
||||||
@@ -364,7 +364,7 @@ if bashio::config.has_value 'networkdisks'; then
|
|||||||
302) SMBVERS=",vers=3.02" ;;
|
302) SMBVERS=",vers=3.02" ;;
|
||||||
300) SMBVERS=",vers=3.0" ;;
|
300) SMBVERS=",vers=3.0" ;;
|
||||||
210) SMBVERS=",vers=2.1" ;;
|
210) SMBVERS=",vers=2.1" ;;
|
||||||
202 | 200) SMBVERS=",vers=2.0" ;;
|
202|200) SMBVERS=",vers=2.0" ;;
|
||||||
*) SMBVERS="" ;;
|
*) SMBVERS="" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -376,7 +376,7 @@ if bashio::config.has_value 'networkdisks'; then
|
|||||||
|
|
||||||
if [[ -n "$SMBVERS" ]]; then
|
if [[ -n "$SMBVERS" ]]; then
|
||||||
echo "...... SMB version detected : ${SMBVERS#,vers=}"
|
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"
|
echo "...... SMB version : only SMBv1 is supported, this can lead to issues"
|
||||||
SECVERS=",sec=ntlm"
|
SECVERS=",sec=ntlm"
|
||||||
SMBVERS=",vers=1.0"
|
SMBVERS=",vers=1.0"
|
||||||
@@ -405,8 +405,8 @@ if bashio::config.has_value 'networkdisks'; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
if command -v nmap > /dev/null 2>&1; then
|
if command -v nmap >/dev/null 2>&1; then
|
||||||
output="$(nmap -F "$server" -T5 -oG - 2> /dev/null || true)"
|
output="$(nmap -F "$server" -T5 -oG - 2>/dev/null || true)"
|
||||||
if ! echo "$output" | grep -Eq '(2049|111)/open'; then
|
if ! echo "$output" | grep -Eq '(2049|111)/open'; then
|
||||||
bashio::log.fatal "...... $server is reachable but NFS ports not open"
|
bashio::log.fatal "...... $server is reachable but NFS ports not open"
|
||||||
fi
|
fi
|
||||||
@@ -424,7 +424,7 @@ if bashio::config.has_value 'networkdisks'; then
|
|||||||
|
|
||||||
if [[ "$MOUNTED" == "true" ]]; then
|
if [[ "$MOUNTED" == "true" ]]; then
|
||||||
bashio::log.info "...... $disk successfully mounted to /mnt/$diskname with options ${MOUNTOPTIONS/$PASSWORD/XXXXXXXXXX}"
|
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
|
if [[ "$FSTYPE" == "cifs" && "$MOUNTOPTIONS" == *"vers=1.0"* ]]; then
|
||||||
bashio::log.warning ""
|
bashio::log.warning ""
|
||||||
@@ -437,14 +437,14 @@ if bashio::config.has_value 'networkdisks'; then
|
|||||||
elif [[ "$MOUNTED" == "readonly" ]]; 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 "...... $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."
|
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
|
cleanup_cred
|
||||||
|
|
||||||
else
|
else
|
||||||
if [[ "$FSTYPE" == "cifs" ]]; then
|
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 "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 :"
|
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
|
smbclient -t 2 -L "$server" -U "$USERNAME%$PASSWORD" -c "exit" $DOMAINCLIENT || true
|
||||||
else
|
else
|
||||||
bashio::log.warning "smbclient not available; cannot print SMB debugging info"
|
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"
|
mount_drive "rw"
|
||||||
fi
|
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"
|
bashio::log.fatal "Error read : ${ERR_READ:-unknown error}, addon will stop in 1 min"
|
||||||
|
|
||||||
umount "/mnt/$diskname" 2> /dev/null || true
|
umount "/mnt/$diskname" 2>/dev/null || true
|
||||||
rmdir "/mnt/$diskname" 2> /dev/null || true
|
rmdir "/mnt/$diskname" 2>/dev/null || true
|
||||||
cleanup_cred
|
cleanup_cred
|
||||||
rm -f "$ERRORCODE_FILE" 2> /dev/null || true
|
rm -f "$ERRORCODE_FILE" 2>/dev/null || true
|
||||||
|
|
||||||
bashio::addon.stop
|
bashio::addon.stop
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -138,9 +138,9 @@ append_unique_line() {
|
|||||||
# $1=file, $2=line
|
# $1=file, $2=line
|
||||||
local _file="$1"
|
local _file="$1"
|
||||||
local _line="$2"
|
local _line="$2"
|
||||||
mkdir -p "$(dirname "$_file")" 2> /dev/null || true
|
mkdir -p "$(dirname "$_file")" 2>/dev/null || true
|
||||||
touch "$_file" 2> /dev/null || true
|
touch "$_file" 2>/dev/null || true
|
||||||
grep -qxF -- "$_line" "$_file" 2> /dev/null || echo "$_line" >> "$_file"
|
grep -qxF -- "$_line" "$_file" 2>/dev/null || echo "$_line" >> "$_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
|
|||||||
@@ -11,20 +11,14 @@ pick_exec_dir() {
|
|||||||
for d in /dev/shm /run /var/run /mnt /root /; do
|
for d in /dev/shm /run /var/run /mnt /root /; do
|
||||||
if [ -d "$d" ] && [ -w "$d" ]; then
|
if [ -d "$d" ] && [ -w "$d" ]; then
|
||||||
local t="${d%/}/.exec_test_$$"
|
local t="${d%/}/.exec_test_$$"
|
||||||
printf '#!/bin/sh\necho ok\n' > "$t" 2> /dev/null || {
|
printf '#!/bin/sh\necho ok\n' >"$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
|
||||||
rm -f "$t" 2> /dev/null || true
|
chmod 700 "$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
|
||||||
continue
|
if "$t" >/dev/null 2>&1; then
|
||||||
}
|
rm -f "$t" 2>/dev/null || true
|
||||||
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"
|
echo "$d"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
rm -f "$t" 2> /dev/null || true
|
rm -f "$t" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
return 1
|
return 1
|
||||||
@@ -96,7 +90,7 @@ validate_shebang() {
|
|||||||
local errfile msg
|
local errfile msg
|
||||||
|
|
||||||
# shellcheck disable=SC2206
|
# shellcheck disable=SC2206
|
||||||
local cmd=($candidate)
|
local cmd=( $candidate )
|
||||||
local exe="${cmd[0]}"
|
local exe="${cmd[0]}"
|
||||||
|
|
||||||
if [ ! -x "$exe" ]; then
|
if [ ! -x "$exe" ]; then
|
||||||
@@ -109,18 +103,18 @@ validate_shebang() {
|
|||||||
{
|
{
|
||||||
printf '#!%s\n' "$candidate"
|
printf '#!%s\n' "$candidate"
|
||||||
printf '%s\n' "$probe_script_content"
|
printf '%s\n' "$probe_script_content"
|
||||||
} > "$tmp"
|
} >"$tmp"
|
||||||
chmod 700 "$tmp" 2> /dev/null || true
|
chmod 700 "$tmp" 2>/dev/null || true
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
out="$("$tmp" 2> "$errfile")"
|
out="$("$tmp" 2>"$errfile")"
|
||||||
rc=$?
|
rc=$?
|
||||||
set -e
|
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
|
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
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -132,7 +126,7 @@ validate_shebang() {
|
|||||||
msg+=$' stderr: <empty>\n'
|
msg+=$' stderr: <empty>\n'
|
||||||
fi
|
fi
|
||||||
SHEBANG_ERRORS+=("$msg")
|
SHEBANG_ERRORS+=("$msg")
|
||||||
rm -f "$errfile" 2> /dev/null || true
|
rm -f "$errfile" 2>/dev/null || true
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +147,7 @@ if [ -z "$shebang" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
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
|
if [ -f "$f" ]; then
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
@@ -168,7 +162,7 @@ fi
|
|||||||
##################
|
##################
|
||||||
|
|
||||||
# Exit if /config is not mounted or HA not used
|
# 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 "..."
|
echo "..."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -201,7 +195,7 @@ if [ ! -f "$CONFIGSOURCE" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Convert scripts to linux
|
# Convert scripts to linux
|
||||||
dos2unix "$CONFIGSOURCE" > /dev/null 2>&1 || true
|
dos2unix "$CONFIGSOURCE" >/dev/null 2>&1 || true
|
||||||
chmod +x "$CONFIGSOURCE"
|
chmod +x "$CONFIGSOURCE"
|
||||||
|
|
||||||
if grep -q 'bashio' "$CONFIGSOURCE"; then
|
if grep -q 'bashio' "$CONFIGSOURCE"; then
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ _bashio_color() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_bashio_log() {
|
_bashio_log() {
|
||||||
local c="${1:-}"
|
local c="${1:-}"; shift || true
|
||||||
shift || true
|
|
||||||
printf '%s%s%s\n' "$(_bashio_color "$c")" "$*" "$(_bashio_color reset)"
|
printf '%s%s%s\n' "$(_bashio_color "$c")" "$*" "$(_bashio_color reset)"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +55,7 @@ _bashio_log() {
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
_bashio_is_true() {
|
_bashio_is_true() {
|
||||||
case "${1:-}" in
|
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 ;;
|
*) return 1 ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -100,7 +99,7 @@ _bashio_env_get() {
|
|||||||
_bashio_env_has() {
|
_bashio_env_has() {
|
||||||
local key="${1:-}"
|
local key="${1:-}"
|
||||||
[ -n "$key" ] || return 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.
|
# JSON options source (jq required). Prints value or empty; returns 0 always.
|
||||||
@@ -111,10 +110,10 @@ _bashio_json_get() {
|
|||||||
[ -n "$key" ] || return 0
|
[ -n "$key" ] || return 0
|
||||||
[ -n "$file" ] || return 0
|
[ -n "$file" ] || return 0
|
||||||
[ -f "$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
|
# 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
|
# Net wait using /dev/tcp with a timeout
|
||||||
@@ -125,7 +124,7 @@ _bashio_tcp_wait() {
|
|||||||
local start now
|
local start now
|
||||||
start="$(date +%s)"
|
start="$(date +%s)"
|
||||||
while :; do
|
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<&-
|
exec 3>&- 3<&-
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -139,10 +138,10 @@ _bashio_tcp_wait() {
|
|||||||
|
|
||||||
# Prefer nc if present, fallback to /dev/tcp
|
# Prefer nc if present, fallback to /dev/tcp
|
||||||
_bashio_tcp_wait_nc() {
|
_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}"
|
local host="${1:-}" port="${2:-}" to="${3:-30}"
|
||||||
# BusyBox and OpenBSD nc differ; cover both styles
|
# 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
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -174,7 +173,7 @@ bashio::supervisor.ping() {
|
|||||||
bashio::addon.name() { printf '%s' "${ADDON_NAME:-Standalone container}"; }
|
bashio::addon.name() { printf '%s' "${ADDON_NAME:-Standalone container}"; }
|
||||||
bashio::addon.description() { printf '%s' "${ADDON_DESCRIPTION:-Running without Home Assistant Supervisor}"; }
|
bashio::addon.description() { printf '%s' "${ADDON_DESCRIPTION:-Running without Home Assistant Supervisor}"; }
|
||||||
bashio::addon.version() { printf '%s' "${BUILD_VERSION:-1.0}"; }
|
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() {
|
bashio::addon.update_available() {
|
||||||
if [ -n "${ADDON_VERSION_LATEST:-}" ] && [ "${ADDON_VERSION_LATEST:-}" != "${BUILD_VERSION:-}" ]; then
|
if [ -n "${ADDON_VERSION_LATEST:-}" ] && [ "${ADDON_VERSION_LATEST:-}" != "${BUILD_VERSION:-}" ]; then
|
||||||
@@ -195,11 +194,11 @@ bashio::addon.port() {
|
|||||||
local arg="${1:-}"
|
local arg="${1:-}"
|
||||||
if [[ "$arg" =~ ^[0-9]+$ ]]; then
|
if [[ "$arg" =~ ^[0-9]+$ ]]; then
|
||||||
local v=""
|
local v=""
|
||||||
v="$(_bashio_env_get "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)"
|
[ -z "$v" ] && v="$(_bashio_env_get "ADDON_PORT_${arg}" 2>/dev/null || true)"
|
||||||
printf '%s' "${v:-$arg}"
|
printf '%s' "${v:-$arg}"
|
||||||
else
|
else
|
||||||
printf '%s' "$(_bashio_env_get "$arg" 2> /dev/null || true)"
|
printf '%s' "$(_bashio_env_get "$arg" 2>/dev/null || true)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,13 +207,13 @@ bashio::addon.option() {
|
|||||||
local key="${1:-}" value="${2-__BASHIO_UNSET__}" file="${STANDALONE_OPTIONS_JSON:-}"
|
local key="${1:-}" value="${2-__BASHIO_UNSET__}" file="${STANDALONE_OPTIONS_JSON:-}"
|
||||||
[ -n "$key" ] || return 0
|
[ -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
|
local tmp
|
||||||
tmp="$(mktemp)"
|
tmp="$(mktemp)"
|
||||||
if [ "$value" = "__BASHIO_UNSET__" ]; then
|
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
|
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
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -239,7 +238,7 @@ bashio::info.operating_system() {
|
|||||||
}
|
}
|
||||||
bashio::info.arch() { uname -m; }
|
bashio::info.arch() { uname -m; }
|
||||||
bashio::info.machine() { 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"; }
|
bashio::info.supervisor() { printf '%s' "standalone"; }
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -247,14 +246,11 @@ bashio::info.supervisor() { printf '%s' "standalone"; }
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
bashio::config() {
|
bashio::config() {
|
||||||
local key="${1:-}"
|
local key="${1:-}"
|
||||||
[ -n "$key" ] || {
|
[ -n "$key" ] || { printf '%s' ""; return 0; }
|
||||||
printf '%s' ""
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
local v=""
|
local v=""
|
||||||
if _bashio_env_get "$key" > /dev/null 2>&1; then
|
if _bashio_env_get "$key" >/dev/null 2>&1; then
|
||||||
v="$(_bashio_env_get "$key" 2> /dev/null || true)"
|
v="$(_bashio_env_get "$key" 2>/dev/null || true)"
|
||||||
fi
|
fi
|
||||||
[ -z "$v" ] && v="$(_bashio_json_get "$key")"
|
[ -z "$v" ] && v="$(_bashio_json_get "$key")"
|
||||||
printf '%s' "${v:-}"
|
printf '%s' "${v:-}"
|
||||||
@@ -275,8 +271,8 @@ bashio::config.exists() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
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
|
||||||
jq -e --arg k "$key" 'haspath(($k|split(".")))' "$file" > /dev/null 2>&1
|
jq -e --arg k "$key" 'haspath(($k|split(".")))' "$file" >/dev/null 2>&1
|
||||||
return $?
|
return $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -297,8 +293,8 @@ bashio::config.array() {
|
|||||||
raw="$(bashio::config "$key")"
|
raw="$(bashio::config "$key")"
|
||||||
[ -n "$raw" ] || return 0
|
[ -n "$raw" ] || return 0
|
||||||
|
|
||||||
if command -v jq > /dev/null 2>&1 && printf '%s' "$raw" | jq -e . > /dev/null 2>&1; then
|
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
|
printf '%s' "$raw" | jq -r '.[]' 2>/dev/null && return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if printf '%s' "$raw" | grep -q ','; then
|
if printf '%s' "$raw" | grep -q ','; then
|
||||||
@@ -323,7 +319,7 @@ bashio::fs.file_exists() { [ -f "${1:-}" ]; }
|
|||||||
bashio::fs.directory_exists() { [ -d "${1:-}" ]; }
|
bashio::fs.directory_exists() { [ -d "${1:-}" ]; }
|
||||||
bashio::fs.file_contains() {
|
bashio::fs.file_contains() {
|
||||||
local f="${1:-}" p="${2:-}"
|
local f="${1:-}" p="${2:-}"
|
||||||
[ -f "$f" ] && grep -q -- "$p" "$f" 2> /dev/null
|
[ -f "$f" ] && grep -q -- "$p" "$f" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -340,16 +336,16 @@ bashio::net.wait_for() {
|
|||||||
bashio::dns.host() {
|
bashio::dns.host() {
|
||||||
local h="${1:-}"
|
local h="${1:-}"
|
||||||
[ -n "$h" ] || return 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}'
|
getent ahostsv4 "$h" | awk '{print $1; exit}'
|
||||||
else
|
else
|
||||||
nslookup "$h" 2> /dev/null | awk '/^Address: /{print $2; exit}'
|
nslookup "$h" 2>/dev/null | awk '/^Address: /{print $2; exit}'
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
bashio::host.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}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -360,10 +356,7 @@ bashio::host.hostname() {
|
|||||||
# bashio::services.available "mqtt"
|
# bashio::services.available "mqtt"
|
||||||
bashio::services() {
|
bashio::services() {
|
||||||
local svc="${1:-}" key="${2:-}"
|
local svc="${1:-}" key="${2:-}"
|
||||||
[ -n "$svc" ] && [ -n "$key" ] || {
|
[ -n "$svc" ] && [ -n "$key" ] || { printf '%s' ""; return 0; }
|
||||||
printf '%s' ""
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
local upper svc_upper var v=""
|
local upper svc_upper var v=""
|
||||||
upper="$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')"
|
upper="$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')"
|
||||||
@@ -376,15 +369,15 @@ bashio::services() {
|
|||||||
MQTT:USERNAME) var="MQTT_USER" ;;
|
MQTT:USERNAME) var="MQTT_USER" ;;
|
||||||
MQTT:PASSWORD) var="MQTT_PASSWORD" ;;
|
MQTT:PASSWORD) var="MQTT_PASSWORD" ;;
|
||||||
MQTT:TLS) var="MQTT_TLS" ;;
|
MQTT:TLS) var="MQTT_TLS" ;;
|
||||||
MYSQL:HOST | MARIADB:HOST) var="DB_HOST" ;;
|
MYSQL:HOST|MARIADB:HOST) var="DB_HOST" ;;
|
||||||
MYSQL:PORT | MARIADB:PORT) var="DB_PORT" ;;
|
MYSQL:PORT|MARIADB:PORT) var="DB_PORT" ;;
|
||||||
MYSQL:USERNAME | MARIADB:USERNAME) var="DB_USER" ;;
|
MYSQL:USERNAME|MARIADB:USERNAME) var="DB_USER" ;;
|
||||||
MYSQL:PASSWORD | MARIADB:PASSWORD) var="DB_PASSWORD" ;;
|
MYSQL:PASSWORD|MARIADB:PASSWORD) var="DB_PASSWORD" ;;
|
||||||
MYSQL:DATABASE | MARIADB:DATABASE) var="DB_NAME" ;;
|
MYSQL:DATABASE|MARIADB:DATABASE) var="DB_NAME" ;;
|
||||||
*) var="${svc_upper}_${upper}" ;;
|
*) var="${svc_upper}_${upper}" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
v="$(_bashio_env_get "$var" 2> /dev/null || true)"
|
v="$(_bashio_env_get "$var" 2>/dev/null || true)"
|
||||||
if [ -z "$v" ]; then
|
if [ -z "$v" ]; then
|
||||||
v="$(_bashio_json_get "services.${svc}.${key}")"
|
v="$(_bashio_json_get "services.${svc}.${key}")"
|
||||||
[ -z "$v" ] && v="$(_bashio_json_get "${svc}.${key}")"
|
[ -z "$v" ] && v="$(_bashio_json_get "${svc}.${key}")"
|
||||||
@@ -409,15 +402,15 @@ bashio::cache.set() { printf '%s' "${2:-}" > "$BASHIO_CACHE_DIR/${1}.cache"; }
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# jq wrapper (some add-ons call bashio::jq)
|
# 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
|
# Home Assistant token
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
bashio::homeassistant.token() {
|
bashio::homeassistant.token() {
|
||||||
local t="${HOMEASSISTANT_TOKEN:-${HASS_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
|
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)"
|
t="$(jq -er '.homeassistant.token // empty' "$STANDALONE_OPTIONS_JSON" 2>/dev/null || true)"
|
||||||
fi
|
fi
|
||||||
printf '%s' "${t:-}"
|
printf '%s' "${t:-}"
|
||||||
}
|
}
|
||||||
@@ -426,11 +419,7 @@ bashio::homeassistant.token() {
|
|||||||
# Exit helpers
|
# Exit helpers
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
bashio::exit.ok() { exit 0; }
|
bashio::exit.ok() { exit 0; }
|
||||||
bashio::exit.nok() {
|
bashio::exit.nok() { local m="${1:-}"; [ -n "$m" ] && bashio::log.red "$m"; exit 1; }
|
||||||
local m="${1:-}"
|
|
||||||
[ -n "$m" ] && bashio::log.red "$m"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Core config check shim
|
# Core config check shim
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ restore_apt_sources_if_missing() {
|
|||||||
mirror="http://ports.ubuntu.com/ubuntu-ports"
|
mirror="http://ports.ubuntu.com/ubuntu-ports"
|
||||||
security_mirror="$mirror"
|
security_mirror="$mirror"
|
||||||
fi
|
fi
|
||||||
cat > /etc/apt/sources.list << EOF2
|
cat > /etc/apt/sources.list <<EOF2
|
||||||
deb ${mirror} ${codename} main restricted universe multiverse
|
deb ${mirror} ${codename} main restricted universe multiverse
|
||||||
deb ${mirror} ${codename}-updates main restricted universe multiverse
|
deb ${mirror} ${codename}-updates main restricted universe multiverse
|
||||||
deb ${security_mirror} ${codename}-security main restricted universe multiverse
|
deb ${security_mirror} ${codename}-security main restricted universe multiverse
|
||||||
EOF2
|
EOF2
|
||||||
;;
|
;;
|
||||||
debian)
|
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} 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 ${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
|
deb http://deb.debian.org/debian-security ${codename}-security main contrib non-free non-free-firmware
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ done
|
|||||||
if [ "$PACKMANAGER" = "apt" ]; then apt-get update > /dev/null; fi
|
if [ "$PACKMANAGER" = "apt" ]; then apt-get update > /dev/null; fi
|
||||||
if [ "$PACKMANAGER" = "pacman" ]; then pacman -Sy > /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
|
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
|
if [ -n "$ALPINE_VER" ]; then
|
||||||
echo "https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VER}/community" >> /etc/apk/repositories
|
echo "https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VER}/community" >> /etc/apk/repositories
|
||||||
apk update > /dev/null
|
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
|
if ! ls $files 1> /dev/null 2>&1; then continue; fi
|
||||||
|
|
||||||
# Bashio
|
# 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"
|
[ "$VERBOSE" = true ] && echo "install bashio"
|
||||||
BASHIO_VERSION="v0.17.5"
|
BASHIO_VERSION="v0.17.5"
|
||||||
mkdir -p /tmp/bashio
|
mkdir -p /tmp/bashio
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ if [ "$$" -eq 1 ]; then
|
|||||||
PID1=true
|
PID1=true
|
||||||
echo "Starting as entrypoint"
|
echo "Starting as entrypoint"
|
||||||
if [ -d /command ]; then
|
if [ -d /command ]; then
|
||||||
ln -sf /command/* /usr/bin/ 2> /dev/null || true
|
ln -sf /command/* /usr/bin/ 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Starting custom scripts"
|
echo "Starting custom scripts"
|
||||||
@@ -28,20 +28,14 @@ pick_exec_dir() {
|
|||||||
if [ -d "$d" ] && [ -w "$d" ]; then
|
if [ -d "$d" ] && [ -w "$d" ]; then
|
||||||
# Create a tiny test executable to confirm "exec" works
|
# Create a tiny test executable to confirm "exec" works
|
||||||
local t="${d%/}/.exec_test_$$"
|
local t="${d%/}/.exec_test_$$"
|
||||||
printf '#!/bin/sh\necho ok\n' > "$t" 2> /dev/null || {
|
printf '#!/bin/sh\necho ok\n' >"$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
|
||||||
rm -f "$t" 2> /dev/null || true
|
chmod 700 "$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
|
||||||
continue
|
if "$t" >/dev/null 2>&1; then
|
||||||
}
|
rm -f "$t" 2>/dev/null || true
|
||||||
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"
|
echo "$d"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
rm -f "$t" 2> /dev/null || true
|
rm -f "$t" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
return 1
|
return 1
|
||||||
@@ -115,7 +109,7 @@ validate_shebang() {
|
|||||||
local errfile msg
|
local errfile msg
|
||||||
|
|
||||||
# shellcheck disable=SC2206
|
# shellcheck disable=SC2206
|
||||||
local cmd=($candidate)
|
local cmd=( $candidate )
|
||||||
local exe="${cmd[0]}"
|
local exe="${cmd[0]}"
|
||||||
|
|
||||||
if [ ! -x "$exe" ]; then
|
if [ ! -x "$exe" ]; then
|
||||||
@@ -128,18 +122,18 @@ validate_shebang() {
|
|||||||
{
|
{
|
||||||
printf '#!%s\n' "$candidate"
|
printf '#!%s\n' "$candidate"
|
||||||
printf '%s\n' "$probe_script_content"
|
printf '%s\n' "$probe_script_content"
|
||||||
} > "$tmp"
|
} >"$tmp"
|
||||||
chmod 700 "$tmp" 2> /dev/null || true
|
chmod 700 "$tmp" 2>/dev/null || true
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
out="$("$tmp" 2> "$errfile")"
|
out="$("$tmp" 2>"$errfile")"
|
||||||
rc=$?
|
rc=$?
|
||||||
set -e
|
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
|
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
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -151,7 +145,7 @@ validate_shebang() {
|
|||||||
msg+=$' stderr: <empty>\n'
|
msg+=$' stderr: <empty>\n'
|
||||||
fi
|
fi
|
||||||
SHEBANG_ERRORS+=("$msg")
|
SHEBANG_ERRORS+=("$msg")
|
||||||
rm -f "$errfile" 2> /dev/null || true
|
rm -f "$errfile" 2>/dev/null || true
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,14 +209,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
|
# 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
|
# 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.
|
# 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" \
|
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
|
s6-dumpenv -- "$S6_CONTAINER_ENV.tmp" && mv "$S6_CONTAINER_ENV.tmp" "$S6_CONTAINER_ENV"; then
|
||||||
echo "Populated $S6_CONTAINER_ENV for with-contenv"
|
echo "Populated $S6_CONTAINER_ENV for with-contenv"
|
||||||
else
|
else
|
||||||
# Leaves the directory absent, which is exactly how this fails today -- so the failure mode is
|
# 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,
|
# 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.
|
# 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"
|
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
|
||||||
fi
|
fi
|
||||||
@@ -283,7 +277,7 @@ wait_for_supervisor() {
|
|||||||
[ -n "${SUPERVISOR_TOKEN:-}" ] || return 0
|
[ -n "${SUPERVISOR_TOKEN:-}" ] || return 0
|
||||||
[ "${BASHIO_LIB_FULL:-false}" = "true" ] || 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.
|
# 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
|
# 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
|
# arithmetic expansion reads it as octal and fails, which would leave the deadline empty and
|
||||||
# spin the loop below forever.
|
# spin the loop below forever.
|
||||||
@@ -311,7 +305,7 @@ wait_for_supervisor() {
|
|||||||
# logging is dropped because a failed attempt here is expected, not news.
|
# logging is dropped because a failed attempt here is expected, not news.
|
||||||
# shellcheck disable=SC2016
|
# shellcheck disable=SC2016
|
||||||
if timeout "$attempt" bash -c '. "$1" && [ -n "$(bashio::addon.ip_address)" ]' \
|
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"
|
[ "$announced" -eq 0 ] || echo "Supervisor API ready after $((SECONDS - started))s"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -363,7 +357,7 @@ run_one_script() {
|
|||||||
_run_rc=0
|
_run_rc=0
|
||||||
(
|
(
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
. "$BASHIO_LIB" 2> /dev/null || true
|
. "$BASHIO_LIB" 2>/dev/null || true
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
. "$script"
|
. "$script"
|
||||||
) || _run_rc=$?
|
) || _run_rc=$?
|
||||||
@@ -403,7 +397,7 @@ if $PID1; then
|
|||||||
_svc_rc=0
|
_svc_rc=0
|
||||||
(
|
(
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
. "$BASHIO_LIB" 2> /dev/null || true
|
. "$BASHIO_LIB" 2>/dev/null || true
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
. "$runfile"
|
. "$runfile"
|
||||||
) || _svc_rc=$?
|
) || _svc_rc=$?
|
||||||
@@ -437,19 +431,19 @@ if $PID1; then
|
|||||||
terminate() {
|
terminate() {
|
||||||
local local_pid
|
local local_pid
|
||||||
echo "Termination signal received, forwarding to subprocesses..."
|
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
|
while read -r pid; do
|
||||||
[ -n "$pid" ] || continue
|
[ -n "$pid" ] || continue
|
||||||
echo "Terminating child PID $pid"
|
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)
|
done < <(pgrep -P "$$" || true)
|
||||||
else
|
else
|
||||||
for p in /proc/[0-9]*/; do
|
for p in /proc/[0-9]*/; do
|
||||||
local_pid="${p#/proc/}"
|
local_pid="${p#/proc/}"
|
||||||
local_pid="${local_pid%/}"
|
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"
|
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
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ manual actions with precedence. Prompts live in `.github/prompts/`, shared shell
|
|||||||
in `.github/scripts/`.
|
in `.github/scripts/`.
|
||||||
|
|
||||||
| Workflow | Model | Trigger | Role |
|
| 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 |
|
| `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`) |
|
| `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 |
|
| `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
|
## Linting Rules
|
||||||
|
|
||||||
| Tool | Config | Key ignores |
|
| Tool | Config | Key ignores |
|
||||||
| ------ | -------- | ------------ |
|
|------|--------|------------|
|
||||||
| Hadolint | `.hadolint.yaml` | DL3002, DL3006-9, DL3018 (no pinning required) |
|
| Hadolint | `.hadolint.yaml` | DL3002, DL3006-9, DL3018 (no pinning required) |
|
||||||
| ShellCheck | `.shellcheckrc` | SC2002 |
|
| ShellCheck | `.shellcheckrc` | SC2002 |
|
||||||
| Markdownlint | `.markdownlint.yaml` | MD013 (line length), MD025, MD033, MD041 |
|
| Markdownlint | `.markdownlint.yaml` | MD013 (line length), MD025, MD033, MD041 |
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ You can add the following tags in the file :
|
|||||||
|
|
||||||
### Addon version numbering
|
### 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:
|
The addon version is therefore derived from the upstream tag:
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ This addon is based on the docker image <https://github.com/lklynet/aurral>
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
| --- | --- | --- |
|
|---|---|---|
|
||||||
| `download_folder` | `/share/aurral/downloads` | Path where Aurral writes flow downloads. Must be under `/share`. |
|
| `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`. |
|
| `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`. |
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ Default credentials: `admin` / `password` (change after first login).
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `PGID` | int | `0` | Group ID for file permissions |
|
| `PGID` | int | `0` | Group ID for file permissions |
|
||||||
| `PUID` | int | `0` | User ID for file permissions |
|
| `PUID` | int | `0` | User ID for file permissions |
|
||||||
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Configurations can be done through the app webUI, except for the following optio
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `PGID` | int | `0` | Group ID for file permissions |
|
| `PGID` | int | `0` | Group ID for file permissions |
|
||||||
| `PUID` | int | `0` | User ID for file permissions |
|
| `PUID` | int | `0` | User ID for file permissions |
|
||||||
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
||||||
|
|||||||
0
bazarr/rootfs/etc/services.d/nginx/run
Executable file → Normal file
0
bazarr/rootfs/etc/services.d/nginx/run
Executable file → Normal file
@@ -17,7 +17,7 @@ A privacy-first PDF toolkit running entirely in your browser — no uploads, no
|
|||||||
### Organize & Edit
|
### Organize & Edit
|
||||||
|
|
||||||
| Tool | Tool | Tool |
|
| Tool | Tool | Tool |
|
||||||
| ------ | ------ | ------ |
|
|------|------|------|
|
||||||
| Merge PDF | Split PDF | Organize PDF |
|
| Merge PDF | Split PDF | Organize PDF |
|
||||||
| Delete Pages | Extract Pages | Reverse Pages |
|
| Delete Pages | Extract Pages | Reverse Pages |
|
||||||
| Rotate PDF | Rotate Custom | Crop PDF |
|
| 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
|
### Convert TO PDF
|
||||||
|
|
||||||
| Tool | Tool | Tool |
|
| Tool | Tool | Tool |
|
||||||
| ------ | ------ | ------ |
|
|------|------|------|
|
||||||
| Word to PDF | Excel to PDF | PowerPoint to PDF |
|
| Word to PDF | Excel to PDF | PowerPoint to PDF |
|
||||||
| Image to PDF | JPG to PDF | PNG to PDF |
|
| Image to PDF | JPG to PDF | PNG to PDF |
|
||||||
| BMP to PDF | TIFF to PDF | WEBP 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
|
### Convert FROM PDF
|
||||||
|
|
||||||
| Tool | Tool | Tool |
|
| Tool | Tool | Tool |
|
||||||
| ------ | ------ | ------ |
|
|------|------|------|
|
||||||
| PDF to DOCX | PDF to Excel | PDF to JPG |
|
| PDF to DOCX | PDF to Excel | PDF to JPG |
|
||||||
| PDF to PNG | PDF to BMP | PDF to TIFF |
|
| PDF to PNG | PDF to BMP | PDF to TIFF |
|
||||||
| PDF to WEBP | PDF to SVG | PDF to Text |
|
| 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
|
### Security & Metadata
|
||||||
|
|
||||||
| Tool | Tool | Tool |
|
| Tool | Tool | Tool |
|
||||||
| ------ | ------ | ------ |
|
|------|------|------|
|
||||||
| Encrypt PDF | Decrypt PDF | Change Permissions |
|
| Encrypt PDF | Decrypt PDF | Change Permissions |
|
||||||
| Remove Restrictions | Sign PDF | Digital Sign PDF |
|
| Remove Restrictions | Sign PDF | Digital Sign PDF |
|
||||||
| Validate Signature | Edit Metadata | View Metadata |
|
| 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
|
### Enhance & Process
|
||||||
|
|
||||||
| Tool | Tool | Tool |
|
| Tool | Tool | Tool |
|
||||||
| ------ | ------ | ------ |
|
|------|------|------|
|
||||||
| Compress PDF | OCR PDF | Deskew PDF |
|
| Compress PDF | OCR PDF | Deskew PDF |
|
||||||
| Rasterize PDF | Linearize PDF | PDF to PDF/A |
|
| Rasterize PDF | Linearize PDF | PDF to PDF/A |
|
||||||
| Adjust Colors | Invert Colors | Text Color |
|
| 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
|
### Forms & More
|
||||||
|
|
||||||
| Tool | Tool | Tool |
|
| Tool | Tool | Tool |
|
||||||
| ------ | ------ | ------ |
|
|------|------|------|
|
||||||
| Form Creator | Form Filler | Table of Contents |
|
| Form Creator | Form Filler | Table of Contents |
|
||||||
| Bookmark | PDF Editor | Extract Images |
|
| Bookmark | PDF Editor | Extract Images |
|
||||||
| Extract Tables | Extract Attachments | Edit Attachments |
|
| Extract Tables | Extract Attachments | Edit Attachments |
|
||||||
|
|||||||
0
bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/run
Executable file → Normal file
0
bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/run
Executable file → Normal file
@@ -1,3 +1,21 @@
|
|||||||
|
## 20260909.5 (09-09-2026)
|
||||||
|
- Minor bugs fixed
|
||||||
|
## 20260909.4 (09-09-2026)
|
||||||
|
- Minor bugs fixed
|
||||||
|
## 20260909.3 (09-09-2026)
|
||||||
|
- Minor bugs fixed
|
||||||
|
## 20260909.2 (09-09-2026)
|
||||||
|
- Minor bugs fixed
|
||||||
|
## 20260909 (09-09-2026)
|
||||||
|
- Minor bugs fixed
|
||||||
|
## 20260908.2 (08-09-2026)
|
||||||
|
- Minor bugs fixed
|
||||||
|
## 20260908.1 (08-09-2026)
|
||||||
|
- Synced with upstream birdnet-go (4 commits); re-merges the open fork PRs, adding fork PR #62 (reanalyze a clip with every loaded model + one-click correction)
|
||||||
|
## 20260908 (08-09-2026)
|
||||||
|
- Synced with upstream birdnet-go (7 commits); re-merges the open fork PRs
|
||||||
|
## 20260907 (07-09-2026)
|
||||||
|
- Rebuild: re-merges the open fork PRs, picking up the updated fork PR #57
|
||||||
## 20260901.4 (01-09-2026)
|
## 20260901.4 (01-09-2026)
|
||||||
- Minor bugs fixed
|
- Minor bugs fixed
|
||||||
## 20260901.3 (01-09-2026)
|
## 20260901.3 (01-09-2026)
|
||||||
|
|||||||
@@ -127,5 +127,5 @@ slug: birdnet-go-dev
|
|||||||
udev: true
|
udev: true
|
||||||
url: https://github.com/alexbelgium/hassio-addons
|
url: https://github.com/alexbelgium/hassio-addons
|
||||||
usb: true
|
usb: true
|
||||||
version: "20260901.4"
|
version: "20260909.5"
|
||||||
video: true
|
video: true
|
||||||
|
|||||||
@@ -11,6 +11,15 @@
|
|||||||
# stamping) is written to the directory given as $1 so the Docker build can
|
# stamping) is written to the directory given as $1 so the Docker build can
|
||||||
# compile it.
|
# compile it.
|
||||||
#
|
#
|
||||||
|
# With --check the script does not build anything: it performs the exact same
|
||||||
|
# merge sequence, skips (instead of failing on) every conflicting PR, and prints
|
||||||
|
# one "!!! CONFLICT pr=#N conflicts-with=... files=... " line per offender before
|
||||||
|
# exiting 2. Use it to find conflicts *before* a build burns on them. This is a
|
||||||
|
# different question from GitHub's `mergeable` field, which compares a PR against
|
||||||
|
# its own base ref - for a stacked PR that base is another feature branch (often
|
||||||
|
# stale, sometimes belonging to a closed PR), so GitHub can report CLEAN for a PR
|
||||||
|
# that does not merge onto main at all.
|
||||||
|
#
|
||||||
# Environment:
|
# Environment:
|
||||||
# BIRDNET_FORK owner/repo of the fork (default alexbelgium/birdnet-go)
|
# BIRDNET_FORK owner/repo of the fork (default alexbelgium/birdnet-go)
|
||||||
# BIRDNET_UPSTREAM owner/repo of the upstream (default tphakala/birdnet-go)
|
# BIRDNET_UPSTREAM owner/repo of the upstream (default tphakala/birdnet-go)
|
||||||
@@ -19,7 +28,26 @@
|
|||||||
#
|
#
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
TARGET_DIR="${1:?usage: merge-prs.sh <target-dir>}"
|
CHECK_ONLY="${MERGE_PRS_CHECK:-0}"
|
||||||
|
TARGET_DIR=""
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--check) CHECK_ONLY=1 ;;
|
||||||
|
-*) echo "unknown option: $1" >&2; exit 64 ;;
|
||||||
|
*)
|
||||||
|
# Last-one-wins would silently clone into the wrong directory if a caller ever
|
||||||
|
# appended an argument; the pre-flag script used "${1}", so refuse rather than
|
||||||
|
# quietly change which operand counts.
|
||||||
|
if [ -n "${TARGET_DIR}" ]; then
|
||||||
|
echo "usage: merge-prs.sh [--check] <target-dir>" >&2
|
||||||
|
exit 64
|
||||||
|
fi
|
||||||
|
TARGET_DIR="$1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
: "${TARGET_DIR:?usage: merge-prs.sh [--check] <target-dir>}"
|
||||||
|
|
||||||
FORK="${BIRDNET_FORK:-alexbelgium/birdnet-go}"
|
FORK="${BIRDNET_FORK:-alexbelgium/birdnet-go}"
|
||||||
UPSTREAM="${BIRDNET_UPSTREAM:-tphakala/birdnet-go}"
|
UPSTREAM="${BIRDNET_UPSTREAM:-tphakala/birdnet-go}"
|
||||||
@@ -33,6 +61,52 @@ GH_TOKEN="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
|
|||||||
|
|
||||||
log() { echo ">>> $*"; }
|
log() { echo ">>> $*"; }
|
||||||
|
|
||||||
|
# Conflicting PRs collected in --check mode: "number|scope|files|title".
|
||||||
|
conflicting=()
|
||||||
|
|
||||||
|
LOCKFILE="frontend/package-lock.json"
|
||||||
|
|
||||||
|
# The single place that decides whether a conflicted merge is still acceptable.
|
||||||
|
# package-lock.json is generated content and stacked PRs can carry an older copy even when
|
||||||
|
# their source changes merge cleanly, so keep the lockfile already assembled on the base side
|
||||||
|
# — but only when it is the sole conflict. Any source conflict stays fatal.
|
||||||
|
# Returns 0 when it resolved and committed such a merge, 1 when the conflict is real.
|
||||||
|
# BOTH the real merge and the --check probe must go through here: when only the real merge
|
||||||
|
# applied the policy, the probe called a PR "conflicts-with=main" that the build would have
|
||||||
|
# merged fine, and printed the opposite remediation to the true one.
|
||||||
|
resolve_sole_lockfile() {
|
||||||
|
local dir="$1"
|
||||||
|
local -a conflicted
|
||||||
|
mapfile -t conflicted < <(git -C "${dir}" diff --name-only --diff-filter=U)
|
||||||
|
if [ "${#conflicted[@]}" -ne 1 ] || [ "${conflicted[0]}" != "${LOCKFILE}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
log "Resolving generated ${LOCKFILE} conflict using the base tree"
|
||||||
|
git -C "${dir}" checkout --ours -- "${LOCKFILE}" || return 1
|
||||||
|
git -C "${dir}" add "${LOCKFILE}" || return 1
|
||||||
|
git -C "${dir}" commit --no-edit > /dev/null || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Does ${1} merge cleanly onto the pristine upstream-synced main? Probed in a
|
||||||
|
# throwaway worktree so the accumulated tree is left untouched. Tells apart a PR
|
||||||
|
# that is simply stale against main (fixable inside that PR's own branch) from
|
||||||
|
# one that only clashes with another open PR (needs a cross-PR decision).
|
||||||
|
merges_onto_main() {
|
||||||
|
local sha="$1" tmpdir probe rc=0
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
probe="${tmpdir}/probe"
|
||||||
|
git worktree add --quiet --detach "${probe}" "${MAIN_SYNCED}"
|
||||||
|
if ! git -C "${probe}" merge --no-edit --no-ff -m probe "${sha}" > /dev/null 2>&1; then
|
||||||
|
# Same policy as the real merge, or this misclassifies a lockfile-only clash.
|
||||||
|
resolve_sole_lockfile "${probe}" > /dev/null 2>&1 || rc=1
|
||||||
|
fi
|
||||||
|
git worktree remove --force "${probe}" > /dev/null 2>&1 || true
|
||||||
|
# worktree remove only takes the child back; without this the mktemp parent is left behind
|
||||||
|
# on every checked conflict.
|
||||||
|
rmdir "${tmpdir}" > /dev/null 2>&1 || true
|
||||||
|
return "${rc}"
|
||||||
|
}
|
||||||
|
|
||||||
git config --global user.email "addon-builder@users.noreply.github.com"
|
git config --global user.email "addon-builder@users.noreply.github.com"
|
||||||
git config --global user.name "BirdNET-Go Addon Builder"
|
git config --global user.name "BirdNET-Go Addon Builder"
|
||||||
git config --global advice.detachedHead false
|
git config --global advice.detachedHead false
|
||||||
@@ -47,6 +121,7 @@ git remote add upstream "${UPSTREAM_URL}"
|
|||||||
git fetch --no-tags upstream main
|
git fetch --no-tags upstream main
|
||||||
# --no-ff keeps an explicit sync commit; a no-op when main is already current.
|
# --no-ff keeps an explicit sync commit; a no-op when main is already current.
|
||||||
git merge --no-edit --no-ff upstream/main
|
git merge --no-edit --no-ff upstream/main
|
||||||
|
MAIN_SYNCED="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
log "Querying open non-draft PRs from ${FORK}"
|
log "Querying open non-draft PRs from ${FORK}"
|
||||||
auth_header=()
|
auth_header=()
|
||||||
@@ -79,27 +154,43 @@ for entry in "${prs[@]}"; do
|
|||||||
if ! git merge --no-edit --no-ff -m "Merge PR #${number}: ${title}" "${sha}"; then
|
if ! git merge --no-edit --no-ff -m "Merge PR #${number}: ${title}" "${sha}"; then
|
||||||
mapfile -t conflicted_files < <(git diff --name-only --diff-filter=U)
|
mapfile -t conflicted_files < <(git diff --name-only --diff-filter=U)
|
||||||
|
|
||||||
# package-lock.json is generated content and stacked PRs can carry an
|
if resolve_sole_lockfile .; then
|
||||||
# older copy even when their source changes merge cleanly. Keep the
|
: # generated lockfile only - the merge is committed and the build continues
|
||||||
# lockfile already assembled from upstream and earlier PRs, but only
|
|
||||||
# when it is the sole conflict. Any source conflict remains fatal.
|
|
||||||
if [ "${#conflicted_files[@]}" -eq 1 ] \
|
|
||||||
&& [ "${conflicted_files[0]}" = "frontend/package-lock.json" ]; then
|
|
||||||
log "Resolving generated frontend/package-lock.json conflict using the accumulated tree"
|
|
||||||
git checkout --ours -- frontend/package-lock.json
|
|
||||||
git add frontend/package-lock.json
|
|
||||||
git commit --no-edit
|
|
||||||
else
|
else
|
||||||
echo "!!! Merge conflict while merging PR #${number} (${title})." >&2
|
echo "!!! Merge conflict while merging PR #${number} (${title})." >&2
|
||||||
if [ "${#conflicted_files[@]}" -gt 0 ]; then
|
if [ "${#conflicted_files[@]}" -gt 0 ]; then
|
||||||
printf '!!! Conflicting file: %s\n' "${conflicted_files[@]}" >&2
|
printf '!!! Conflicting file: %s\n' "${conflicted_files[@]}" >&2
|
||||||
fi
|
fi
|
||||||
echo "!!! Resolve the conflict in the fork or pause this PR, then rebuild." >&2
|
|
||||||
git merge --abort || true
|
git merge --abort || true
|
||||||
|
|
||||||
|
if [ "${CHECK_ONLY}" = "1" ]; then
|
||||||
|
scope="accumulated"
|
||||||
|
merges_onto_main "${sha}" || scope="main"
|
||||||
|
conflicting+=("${number}|${scope}|${conflicted_files[*]:-}|${title}")
|
||||||
|
log "check mode: skipping PR #${number}, continuing with the rest"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "!!! Resolve the conflict in the fork or pause this PR, then rebuild." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [ "${CHECK_ONLY}" = "1" ]; then
|
||||||
|
if [ "${#conflicting[@]}" -eq 0 ]; then
|
||||||
|
log "CHECK OK: every open non-draft PR merges into the combined build tree"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "!!! CHECK FAILED: ${#conflicting[@]} PR(s) would break the add-on build" >&2
|
||||||
|
for entry in "${conflicting[@]}"; do
|
||||||
|
IFS='|' read -r number scope files title <<<"${entry}"
|
||||||
|
echo "!!! CONFLICT pr=#${number} conflicts-with=${scope} files=${files} title=${title}" >&2
|
||||||
|
done
|
||||||
|
echo "!!! conflicts-with=main -> the PR is stale against main; merge main into its branch and resolve there." >&2
|
||||||
|
echo "!!! conflicts-with=accumulated -> the PR only clashes with another open PR; decide which one owns the hunk." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
log "Merged HEAD: $(git rev-parse --short HEAD)"
|
log "Merged HEAD: $(git rev-parse --short HEAD)"
|
||||||
log "Source tree ready at ${TARGET_DIR}"
|
log "Source tree ready at ${TARGET_DIR}"
|
||||||
|
|||||||
@@ -190,5 +190,5 @@ LOG_DIR="/config/logs"
|
|||||||
if [ -d "$LOG_DIR" ]; then
|
if [ -d "$LOG_DIR" ]; then
|
||||||
bashio::log.info "Trimming log files older than ${LOG_MAX_AGE_DAYS} days in ${LOG_DIR}"
|
bashio::log.info "Trimming log files older than ${LOG_MAX_AGE_DAYS} days in ${LOG_DIR}"
|
||||||
ln -sf "$LOG_DIR" /logs
|
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
|
fi
|
||||||
|
|||||||
@@ -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
|
# 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.
|
# written — a mysql block pointing at a different host was set manually.
|
||||||
# shellcheck disable=SC2016
|
# shellcheck disable=SC2016
|
||||||
CURRENT_MYSQL_HOST="$(yq -r '.output.mysql.host // empty' "$CONFIG_LOCATION" 2> /dev/null || true)"
|
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 \
|
if yq -e '.output.mysql.enabled == true' "$CONFIG_LOCATION" >/dev/null 2>&1 \
|
||||||
&& [ "${CURRENT_MYSQL_HOST}" = "${MYSQL_HOST}" ]; then
|
&& [ "${CURRENT_MYSQL_HOST}" = "${MYSQL_HOST}" ]; then
|
||||||
yq -i -y \
|
yq -i -y \
|
||||||
'.output.mysql.enabled = false
|
'.output.mysql.enabled = false
|
||||||
@@ -71,7 +71,7 @@ bashio::log.green "---"
|
|||||||
# Resolve MariaDB hostname to IPv4: on HAOS >=17.3 the Supervisor network
|
# 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
|
# gained IPv6, but the MariaDB addon only grants its user from the IPv4
|
||||||
# subnet. Fall back to the raw hostname if resolution fails.
|
# 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}}"
|
MYSQL_HOST_RESOLVED="${MYSQL_HOST_RESOLVED:-${MYSQL_HOST}}"
|
||||||
if [ "${MYSQL_HOST_RESOLVED}" != "${MYSQL_HOST}" ]; then
|
if [ "${MYSQL_HOST_RESOLVED}" != "${MYSQL_HOST}" ]; then
|
||||||
bashio::log.blue "Resolved ${MYSQL_HOST} -> ${MYSQL_HOST_RESOLVED} (forcing IPv4)"
|
bashio::log.blue "Resolved ${MYSQL_HOST} -> ${MYSQL_HOST_RESOLVED} (forcing IPv4)"
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
|
|
||||||
## nightly-20260615-2 (05-07-2026)
|
## nightly-20260615-2 (05-07-2026)
|
||||||
- Minor bugs fixed
|
- 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)
|
- 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 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
|
- 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 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)
|
- Added backup_exclude for rotated logs, making Home Assistant backups smaller (SQLite journals are kept so hot backups stay consistent)
|
||||||
|
|||||||
@@ -186,5 +186,5 @@ LOG_DIR="/config/logs"
|
|||||||
if [ -d "$LOG_DIR" ]; then
|
if [ -d "$LOG_DIR" ]; then
|
||||||
bashio::log.info "Trimming log files older than ${LOG_MAX_AGE_DAYS} days in ${LOG_DIR}"
|
bashio::log.info "Trimming log files older than ${LOG_MAX_AGE_DAYS} days in ${LOG_DIR}"
|
||||||
ln -sf "$LOG_DIR" /logs
|
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
|
fi
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ Here are some example tests I did (the whole threads are really interesting also
|
|||||||
|
|
||||||
**My recommendation:**
|
**My recommendation:**
|
||||||
|
|
||||||
- **Best entry system (< 50 €):** Boya By-lm40 (30 €) + deadcat (10 €)
|
- **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 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 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:**
|
**Sources for high end microphones in Europe:**
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ def automatic_mqtt_publish(file, detection, path):
|
|||||||
mqttc.publish(mqtt_topic, json_bird, 1)
|
mqttc.publish(mqtt_topic, json_bird, 1)
|
||||||
log.info("Posted to MQTT: ok")
|
log.info("Posted to MQTT: ok")
|
||||||
|
|
||||||
|
|
||||||
# Create MQTT client using legacy callback API when available for
|
# Create MQTT client using legacy callback API when available for
|
||||||
# compatibility with paho-mqtt >= 2.0
|
# compatibility with paho-mqtt >= 2.0
|
||||||
callback_api = getattr(mqtt, "CallbackAPIVersion", None)
|
callback_api = getattr(mqtt, "CallbackAPIVersion", None)
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ Here are some example tests I did (the whole threads are really interesting also
|
|||||||
|
|
||||||
**My recommendation:**
|
**My recommendation:**
|
||||||
|
|
||||||
- **Best entry system (< 50 €):** Boya By-lm40 (30 €) + deadcat (10 €)
|
- **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 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 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:**
|
**Sources for high end microphones in Europe:**
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ def automatic_mqtt_publish(file, detection, path):
|
|||||||
mqttc.publish(mqtt_topic, json_bird, 1)
|
mqttc.publish(mqtt_topic, json_bird, 1)
|
||||||
log.info("Posted to MQTT: ok")
|
log.info("Posted to MQTT: ok")
|
||||||
|
|
||||||
|
|
||||||
# Create MQTT client using legacy callback API when available for
|
# Create MQTT client using legacy callback API when available for
|
||||||
# compatibility with paho-mqtt >= 2.0
|
# compatibility with paho-mqtt >= 2.0
|
||||||
callback_api = getattr(mqtt, "CallbackAPIVersion", None)
|
callback_api = getattr(mqtt, "CallbackAPIVersion", None)
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ map:
|
|||||||
name: BirdNET-PiPy
|
name: BirdNET-PiPy
|
||||||
options:
|
options:
|
||||||
env_vars: []
|
env_vars: []
|
||||||
ICECAST_PASSWORD: ""
|
ICECAST_PASSWORD: ''
|
||||||
data_location: /config/data
|
data_location: /config/data
|
||||||
panel_icon: mdi:bird
|
panel_icon: mdi:bird
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ DATA_LOCATION="$(bashio::config 'data_location' || true)"
|
|||||||
DATA_LOCATION="${DATA_LOCATION:-$DEFAULT_LOCATION}"
|
DATA_LOCATION="${DATA_LOCATION:-$DEFAULT_LOCATION}"
|
||||||
|
|
||||||
case "${DATA_LOCATION}" in
|
case "${DATA_LOCATION}" in
|
||||||
/config/* | /share/* | /data/*) ;;
|
/config/*|/share/*|/data/*) ;;
|
||||||
*)
|
*)
|
||||||
bashio::log.warning "Invalid data_location '${DATA_LOCATION}', falling back to ${DEFAULT_LOCATION}"
|
bashio::log.warning "Invalid data_location '${DATA_LOCATION}', falling back to ${DEFAULT_LOCATION}"
|
||||||
DATA_LOCATION="${DEFAULT_LOCATION}"
|
DATA_LOCATION="${DEFAULT_LOCATION}"
|
||||||
@@ -19,11 +19,11 @@ LEGACY2="/data"
|
|||||||
|
|
||||||
mkdir -p "${DATA_LOCATION}"
|
mkdir -p "${DATA_LOCATION}"
|
||||||
|
|
||||||
if [ -z "$(ls -A "${DATA_LOCATION}" 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
|
if [ -d "${LEGACY1}" ] && [ -n "$(ls -A "${LEGACY1}" 2>/dev/null || true)" ]; then
|
||||||
bashio::log.notice "Migrating legacy data from ${LEGACY1} to ${DATA_LOCATION}"
|
bashio::log.notice "Migrating legacy data from ${LEGACY1} to ${DATA_LOCATION}"
|
||||||
cp -a "${LEGACY1}/." "${DATA_LOCATION}/" || true
|
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}"
|
bashio::log.notice "Migrating legacy data from ${LEGACY2} to ${DATA_LOCATION}"
|
||||||
cp -a "${LEGACY2}/." "${DATA_LOCATION}/" || true
|
cp -a "${LEGACY2}/." "${DATA_LOCATION}/" || true
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -3,20 +3,20 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SND_GID=""
|
SND_GID=""
|
||||||
if [ -e /dev/snd ] && command -v stat > /dev/null 2>&1; then
|
if [ -e /dev/snd ] && command -v stat >/dev/null 2>&1; then
|
||||||
SND_GID="$(stat -c '%g' /dev/snd 2> /dev/null || true)"
|
SND_GID="$(stat -c '%g' /dev/snd 2>/dev/null || true)"
|
||||||
fi
|
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)"
|
current_gid="$(getent group audio | cut -d: -f3 || true)"
|
||||||
if [ -n "${current_gid}" ] && [ "${current_gid}" != "${SND_GID}" ]; then
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for u in root nginx www-data icecast2; do
|
for u in root nginx www-data icecast2; do
|
||||||
if id "${u}" > /dev/null 2>&1; then
|
if id "${u}" >/dev/null 2>&1; then
|
||||||
usermod -aG audio "${u}" 2> /dev/null || true
|
usermod -aG audio "${u}" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ if [ -n "${PULSE_SOCK}" ] && [ -S "${PULSE_SOCK}" ] && [ ! -S /run/pulse/native
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Copy PulseAudio cookie for the icecast2 user so it can authenticate
|
# 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)"
|
ICECAST_HOME="$(getent passwd icecast2 | cut -d: -f6)"
|
||||||
if [ -n "${ICECAST_HOME}" ]; then
|
if [ -n "${ICECAST_HOME}" ]; then
|
||||||
for cookie in /config/.config/pulse/cookie /root/.config/pulse/cookie; do
|
for cookie in /config/.config/pulse/cookie /root/.config/pulse/cookie; do
|
||||||
|
|||||||
0
birdnet-pipy/rootfs/etc/services.d/api/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/api/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/icecast/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/icecast/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/main/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/main/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/model/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/model/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/nginx/run
Executable file → Normal file
0
birdnet-pipy/rootfs/etc/services.d/nginx/run
Executable file → Normal file
0
bitwarden/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run
Executable file → Normal file
0
bitwarden/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run
Executable file → Normal file
0
bitwarden/rootfs/etc/s6-overlay/s6-rc.d/nginx/run
Executable file → Normal file
0
bitwarden/rootfs/etc/s6-overlay/s6-rc.d/nginx/run
Executable file → Normal file
6
bitwarden/rootfs/etc/s6-overlay/s6-rc.d/vaultwarden/run
Executable file → Normal file
6
bitwarden/rootfs/etc/s6-overlay/s6-rc.d/vaultwarden/run
Executable file → Normal file
@@ -21,19 +21,19 @@ export ROCKET_SECRET_KEY="${secret_key}"
|
|||||||
# Find the matching log level
|
# Find the matching log level
|
||||||
if bashio::config.has_value 'log_level'; then
|
if bashio::config.has_value 'log_level'; then
|
||||||
case "$(bashio::string.lower "$(bashio::config 'log_level')")" in
|
case "$(bashio::string.lower "$(bashio::config 'log_level')")" in
|
||||||
all | trace)
|
all|trace)
|
||||||
log_level="trace"
|
log_level="trace"
|
||||||
;;
|
;;
|
||||||
debug)
|
debug)
|
||||||
log_level="debug"
|
log_level="debug"
|
||||||
;;
|
;;
|
||||||
info | notice)
|
info|notice)
|
||||||
log_level="info"
|
log_level="info"
|
||||||
;;
|
;;
|
||||||
warning)
|
warning)
|
||||||
log_level="warn"
|
log_level="warn"
|
||||||
;;
|
;;
|
||||||
error | fatal)
|
error|fatal)
|
||||||
log_level="error"
|
log_level="error"
|
||||||
;;
|
;;
|
||||||
off)
|
off)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ Default credentials are shown in the startup logs.
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `PGID` | int | `0` | Group ID for file permissions |
|
| `PGID` | int | `0` | Group ID for file permissions |
|
||||||
| `PUID` | int | `0` | User ID for file permissions |
|
| `PUID` | int | `0` | User ID for file permissions |
|
||||||
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
||||||
|
|||||||
1
browser_chromium/rootfs/etc/cont-init.d/20-folders.sh
Normal file → Executable file
1
browser_chromium/rootfs/etc/cont-init.d/20-folders.sh
Normal file → Executable file
@@ -7,6 +7,7 @@ set -e
|
|||||||
PUID=$(bashio::config "PUID")
|
PUID=$(bashio::config "PUID")
|
||||||
PGID=$(bashio::config "PGID")
|
PGID=$(bashio::config "PGID")
|
||||||
|
|
||||||
|
|
||||||
# Create cache
|
# Create cache
|
||||||
#mkdir -p /.cache
|
#mkdir -p /.cache
|
||||||
#chmod 755 /.cache
|
#chmod 755 /.cache
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ args=(
|
|||||||
|
|
||||||
# Container reality: sandbox usually breaks unless seccomp is fully disabled
|
# Container reality: sandbox usually breaks unless seccomp is fully disabled
|
||||||
if [[ "${seccomp_mode}" != "0" ]]; then
|
if [[ "${seccomp_mode}" != "0" ]]; then
|
||||||
args+=(--no-sandbox --test-type)
|
args+=( --no-sandbox --test-type )
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# IMPORTANT:
|
# IMPORTANT:
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ Please read the upstream container documentation for further info: https://githu
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `PGID` | int | `0` | Group ID for file permissions |
|
| `PGID` | int | `0` | Group ID for file permissions |
|
||||||
| `PUID` | int | `0` | User ID for file permissions |
|
| `PUID` | int | `0` | User ID for file permissions |
|
||||||
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ Default password: admin123
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `PGID` | int | `0` | Group ID for file permissions |
|
| `PGID` | int | `0` | Group ID for file permissions |
|
||||||
| `PUID` | int | `0` | User ID for file permissions |
|
| `PUID` | int | `0` | User ID for file permissions |
|
||||||
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
| `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
|
### 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`).
|
Optional features are switched on in the Calibre-Web web interface, not in the add-on options, under `Admin` -> `Basic Configuration` -> `Feature Configuration` (for example `Enable Uploads`, `Enable Kobo sync`, `Use Goodreads`).
|
||||||
|
|
||||||
|
|||||||
@@ -30,16 +30,16 @@ else
|
|||||||
# duplicates it can leave behind are entries calibre-web skips or already trusts.
|
# 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
|
# 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.
|
# 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) \
|
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})"
|
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
|
# 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
|
# 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
|
# 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.
|
# 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.
|
# 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) \
|
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})"
|
bashio::log.warning "Could not reset the kepubify path, it will be applied at next start (${kepubify_error})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
bashio::log.info "Default username:password is admin:admin123"
|
bashio::log.info "Default username:password is admin:admin123"
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ You can add a shortcut pointing to your Changedetection.io instance with the fol
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `PGID` | int | `0` | Group ID for file permissions |
|
| `PGID` | int | `0` | Group ID for file permissions |
|
||||||
| `PUID` | int | `0` | User ID for file permissions |
|
| `PUID` | int | `0` | User ID for file permissions |
|
||||||
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
||||||
|
|||||||
0
changedetection.io/rootfs/etc/services.d/nginx/run
Executable file → Normal file
0
changedetection.io/rootfs/etc/services.d/nginx/run
Executable file → Normal file
@@ -55,7 +55,7 @@
|
|||||||
# shared memory into ordinary files for no benefit. If the size cannot be determined, keep the
|
# 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.
|
# flag — the crash it prevents is worse than the overhead it costs.
|
||||||
SHM_FLAGS="--disable-dev-shm-usage"
|
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
|
if [ -n "$SHM_KB" ] && [ "$SHM_KB" -ge 262144 ]; then
|
||||||
SHM_FLAGS=""
|
SHM_FLAGS=""
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ release_info=""
|
|||||||
if curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 10 --max-time 30 \
|
if curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 10 --max-time 30 \
|
||||||
-o "$release_metadata" "$CODEX_RELEASE_API"; then
|
-o "$release_metadata" "$CODEX_RELEASE_API"; then
|
||||||
release_info="$(
|
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 json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -249,7 +249,7 @@ fi
|
|||||||
{
|
{
|
||||||
printf '#!/usr/bin/env bash\n'
|
printf '#!/usr/bin/env bash\n'
|
||||||
printf 'CODEX_REAL=%q\n' "$CODEX_REAL"
|
printf 'CODEX_REAL=%q\n' "$CODEX_REAL"
|
||||||
cat << 'SH'
|
cat <<'SH'
|
||||||
RUNTIME_HOME="$(getent passwd abc | cut -d: -f6)"
|
RUNTIME_HOME="$(getent passwd abc | cut -d: -f6)"
|
||||||
if [ -z "$RUNTIME_HOME" ]; then
|
if [ -z "$RUNTIME_HOME" ]; then
|
||||||
echo "codex: unable to resolve the abc runtime home" >&2
|
echo "codex: unable to resolve the abc runtime home" >&2
|
||||||
@@ -338,7 +338,8 @@ ln -sfn "$CODEX_BIN" "$CODEX_LINK"
|
|||||||
CODEX_SANDBOX_MODE="$(bashio::config 'codex_sandbox_mode' 'danger-full-access')"
|
CODEX_SANDBOX_MODE="$(bashio::config 'codex_sandbox_mode' 'danger-full-access')"
|
||||||
run_as_runtime_user mkdir -p "$RUNTIME_HOME/.codex"
|
run_as_runtime_user mkdir -p "$RUNTIME_HOME/.codex"
|
||||||
CODEX_SANDBOX_MODE="$CODEX_SANDBOX_MODE" RUNTIME_HOME="$RUNTIME_HOME" \
|
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 os
|
||||||
import re
|
import re
|
||||||
import tomllib
|
import tomllib
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ manage_claude_md_block() {
|
|||||||
fi
|
fi
|
||||||
elif [ -f "$CLAUDE_MD" ] && grep -qF "$begin" "$CLAUDE_MD"; then
|
elif [ -f "$CLAUDE_MD" ] && grep -qF "$begin" "$CLAUDE_MD"; then
|
||||||
bashio::log.info "Removing ${name} guidance from CLAUDE.md"
|
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 os
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -58,7 +58,7 @@ PY
|
|||||||
manage_settings_hook() {
|
manage_settings_hook() {
|
||||||
# manage_settings_hook <event> <matcher> <command> <add|remove>
|
# manage_settings_hook <event> <matcher> <command> <add|remove>
|
||||||
HOOK_EVENT="$1" HOOK_MATCHER="$2" HOOK_COMMAND="$3" HOOK_ACTION="$4" \
|
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 json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
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}"
|
bashio::log.info "MCP servers for Claude Code: ${MCP_SERVERS_CODE}"
|
||||||
|
|
||||||
HEADROOM_ENABLED="$HEADROOM_ENABLED" HEADROOM_BIN="$(command -v headroom || echo headroom)" \
|
HEADROOM_ENABLED="$HEADROOM_ENABLED" HEADROOM_BIN="$(command -v headroom || echo headroom)" \
|
||||||
HEADROOM_HF_HOME="${HOME}/.headroom/hf" \
|
HEADROOM_HF_HOME="${HOME}/.headroom/hf" \
|
||||||
TOKENSAVE_ENABLED="$TOKENSAVE_ENABLED" TOKENSAVE_BIN="$(command -v tokensave || echo tokensave)" \
|
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" \
|
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" \
|
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_PROXY_BIN="$(command -v mcp-proxy || echo mcp-proxy)" \
|
||||||
MCP_SERVERS_DESKTOP="$MCP_SERVERS_DESKTOP" MCP_SERVERS_CODE="$MCP_SERVERS_CODE" \
|
MCP_SERVERS_DESKTOP="$MCP_SERVERS_DESKTOP" MCP_SERVERS_CODE="$MCP_SERVERS_CODE" \
|
||||||
CLAUDE_DESKTOP_CONFIG="$CLAUDE_DESKTOP_CONFIG" CLAUDE_CODE_CONFIG="$CLAUDE_CODE_CONFIG" \
|
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"
|
python3 - <<'PY' || bashio::log.warning "Unable to update the MCP server registrations automatically"
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -607,7 +607,7 @@ PY
|
|||||||
# Guide Claude to actually use the Headroom compression tools so the MCP integration produces
|
# Guide Claude to actually use the Headroom compression tools so the MCP integration produces
|
||||||
# real savings when transparent proxying is unavailable.
|
# real savings when transparent proxying is unavailable.
|
||||||
if $HEADROOM_ENABLED; then
|
if $HEADROOM_ENABLED; then
|
||||||
manage_claude_md_block headroom add << 'MD'
|
manage_claude_md_block headroom add <<'MD'
|
||||||
## Headroom context compression
|
## Headroom context compression
|
||||||
|
|
||||||
A local Headroom proxy (127.0.0.1:8787) backs the `headroom` MCP tools. To save context tokens:
|
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
|
else
|
||||||
HEADROOM_ROUTE_ACTION="remove"
|
HEADROOM_ROUTE_ACTION="remove"
|
||||||
fi
|
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 json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
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
|
# Tell Claude Code that it can configure Home Assistant over the Core API via the shipped
|
||||||
# `ha-cli` helper (no /config filesystem mount needed).
|
# `ha-cli` helper (no /config filesystem mount needed).
|
||||||
if bashio::config.true 'enable_ha_api_helper'; then
|
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
|
## Configuring Home Assistant
|
||||||
|
|
||||||
You can configure this Home Assistant instance through its Core API using the `ha-cli`
|
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
|
# 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.
|
# for a second agent, the same gap the Headroom block above exists to close.
|
||||||
if $CODEX_ENABLED; then
|
if $CODEX_ENABLED; then
|
||||||
manage_claude_md_block codex add << 'MD'
|
manage_claude_md_block codex add <<'MD'
|
||||||
## Delegating to ChatGPT Codex
|
## Delegating to ChatGPT Codex
|
||||||
|
|
||||||
The `codex` MCP server runs OpenAI's Codex agent locally, signed in with the user's ChatGPT
|
The `codex` MCP server runs OpenAI's Codex agent locally, signed in with the user's ChatGPT
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ esac
|
|||||||
# only while it still holds one of those managed values — a defaultMode the user set by hand
|
# 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.
|
# is never deleted. Ownership of the written file is reconciled by 84-claude_runtime_ownership.sh.
|
||||||
mkdir -p "$(dirname "$SETTINGS_PATH")"
|
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 json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ fi
|
|||||||
# archive stays unpatched, so every later boot would retry under a new pid and strand another
|
# archive stays unpatched, so every later boot would retry under a new pid and strand another
|
||||||
# copy until the container runs out of space.
|
# copy until the container runs out of space.
|
||||||
cleanup_tmp() {
|
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
|
if output=$(timeout 120 node "$PATCHER" "$ASAR" 2>&1); then
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ if [ "$(bashio::config 'permission_mode')" = "bypass" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
section "Claude Code permission state"
|
section "Claude Code permission state"
|
||||||
RUNTIME_HOME="$RUNTIME_HOME" python3 - << 'PY'
|
RUNTIME_HOME="$RUNTIME_HOME" python3 - <<'PY'
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -66,7 +66,7 @@ else:
|
|||||||
PY
|
PY
|
||||||
|
|
||||||
section "MCP registrations (environment values redacted)"
|
section "MCP registrations (environment values redacted)"
|
||||||
RUNTIME_HOME="$RUNTIME_HOME" python3 - << 'PY'
|
RUNTIME_HOME="$RUNTIME_HOME" python3 - <<'PY'
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -104,7 +104,7 @@ for path in paths:
|
|||||||
PY
|
PY
|
||||||
|
|
||||||
section "Claude Code hooks"
|
section "Claude Code hooks"
|
||||||
RUNTIME_HOME="$RUNTIME_HOME" python3 - << 'PY'
|
RUNTIME_HOME="$RUNTIME_HOME" python3 - <<'PY'
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|||||||
0
claude_desktop/rootfs/usr/local/bin/codex-login
Normal file → Executable file
0
claude_desktop/rootfs/usr/local/bin/codex-login
Normal file → Executable file
14
claude_desktop/rootfs/usr/local/bin/ha-cli
Normal file → Executable file
14
claude_desktop/rootfs/usr/local/bin/ha-cli
Normal file → Executable file
@@ -96,7 +96,7 @@ def resolve_endpoint():
|
|||||||
def _url(base, path):
|
def _url(base, path):
|
||||||
path = path.lstrip("/")
|
path = path.lstrip("/")
|
||||||
if path.startswith("api/"):
|
if path.startswith("api/"):
|
||||||
path = path[len("api/") :]
|
path = path[len("api/"):]
|
||||||
return base.rstrip("/") + "/" + path
|
return base.rstrip("/") + "/" + path
|
||||||
|
|
||||||
|
|
||||||
@@ -133,9 +133,7 @@ def rest(method, base, token, path, body=None):
|
|||||||
if body is not None:
|
if body is not None:
|
||||||
data = json.dumps(body).encode("utf-8")
|
data = json.dumps(body).encode("utf-8")
|
||||||
headers["Content-Type"] = "application/json"
|
headers["Content-Type"] = "application/json"
|
||||||
req = urllib.request.Request(
|
req = urllib.request.Request(_url(base, path), data=data, method=method, headers=headers)
|
||||||
_url(base, path), data=data, method=method, headers=headers
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||||
text = resp.read().decode("utf-8")
|
text = resp.read().decode("utf-8")
|
||||||
@@ -143,9 +141,7 @@ def rest(method, base, token, path, body=None):
|
|||||||
detail = exc.read().decode("utf-8", "replace").strip()
|
detail = exc.read().decode("utf-8", "replace").strip()
|
||||||
sys.exit(f"ha-cli: HTTP {exc.code} {exc.reason} on {method} {path}\n{detail}")
|
sys.exit(f"ha-cli: HTTP {exc.code} {exc.reason} on {method} {path}\n{detail}")
|
||||||
except urllib.error.URLError as exc:
|
except urllib.error.URLError as exc:
|
||||||
sys.exit(
|
sys.exit(f"ha-cli: cannot reach Home Assistant ({exc.reason}) on {method} {path}")
|
||||||
f"ha-cli: cannot reach Home Assistant ({exc.reason}) on {method} {path}"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
return json.loads(text) if text.strip() else None
|
return json.loads(text) if text.strip() else None
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -218,9 +214,7 @@ def main(argv):
|
|||||||
sys.exit("usage: ha-cli call <domain.service> [BODY]")
|
sys.exit("usage: ha-cli call <domain.service> [BODY]")
|
||||||
domain, service = args[0].split(".", 1)
|
domain, service = args[0].split(".", 1)
|
||||||
body = _read_body(args[1]) if len(args) > 1 else None
|
body = _read_body(args[1]) if len(args) > 1 else None
|
||||||
_print(
|
_print(rest("POST", rest_base, token, f"services/{domain}/{service}", body or {}))
|
||||||
rest("POST", rest_base, token, f"services/{domain}/{service}", body or {})
|
|
||||||
)
|
|
||||||
elif cmd == "states":
|
elif cmd == "states":
|
||||||
path = f"states/{args[0]}" if args else "states"
|
path = f"states/{args[0]}" if args else "states"
|
||||||
_print(rest("GET", rest_base, token, path))
|
_print(rest("GET", rest_base, token, path))
|
||||||
|
|||||||
14
claude_desktop/rootfs/usr/local/bin/headroom-posttooluse-compress.py
Normal file → Executable file
14
claude_desktop/rootfs/usr/local/bin/headroom-posttooluse-compress.py
Normal file → Executable file
@@ -76,11 +76,7 @@ def main() -> int:
|
|||||||
|
|
||||||
# Find big string/array fields before paying the headroom import cost.
|
# Find big string/array fields before paying the headroom import cost.
|
||||||
def is_string_array(value):
|
def is_string_array(value):
|
||||||
return (
|
return isinstance(value, list) and len(value) > ARRAY_KEEP and all(isinstance(v, str) for v in value)
|
||||||
isinstance(value, list)
|
|
||||||
and len(value) > ARRAY_KEEP
|
|
||||||
and all(isinstance(v, str) for v in value)
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(response, str):
|
if isinstance(response, str):
|
||||||
string_candidates = ["__whole__"] if len(response) >= MIN_CHARS else []
|
string_candidates = ["__whole__"] if len(response) >= MIN_CHARS else []
|
||||||
@@ -89,14 +85,10 @@ def main() -> int:
|
|||||||
string_candidates = [
|
string_candidates = [
|
||||||
key
|
key
|
||||||
for key, value in response.items()
|
for key, value in response.items()
|
||||||
if key not in SKIP_KEYS
|
if key not in SKIP_KEYS and isinstance(value, str) and len(value) >= MIN_CHARS
|
||||||
and isinstance(value, str)
|
|
||||||
and len(value) >= MIN_CHARS
|
|
||||||
]
|
]
|
||||||
array_candidates = [
|
array_candidates = [
|
||||||
key
|
key for key, value in response.items() if key not in SKIP_KEYS and is_string_array(value)
|
||||||
for key, value in response.items()
|
|
||||||
if key not in SKIP_KEYS and is_string_array(value)
|
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
string_candidates = []
|
string_candidates = []
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Integrations supported:
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| -------- | ------------- |
|
|--------|-------------|
|
||||||
| `TZ` | Timezone (e.g. `Europe/Paris`). Defaults to `Europe/London`. |
|
| `TZ` | Timezone (e.g. `Europe/Paris`). Defaults to `Europe/London`. |
|
||||||
| `PUID` | User ID to run the process as. Defaults to `0` (root). |
|
| `PUID` | User ID to run the process as. Defaults to `0` (root). |
|
||||||
| `PGID` | Group ID to run the process as. Defaults to `0` (root). |
|
| `PGID` | Group ID to run the process as. Defaults to `0` (root). |
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
arch:
|
arch:
|
||||||
- aarch64
|
- aarch64
|
||||||
- amd64
|
- 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:
|
devices:
|
||||||
- /dev/dri
|
- /dev/dri
|
||||||
- /dev/dri/card0
|
- /dev/dri/card0
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ mkdir -p "$HA_DATA_DIR"
|
|||||||
# Symlink /app/config → HA persistent storage
|
# Symlink /app/config → HA persistent storage
|
||||||
# The .NET app uses /app/config, NOT /config at the filesystem root
|
# The .NET app uses /app/config, NOT /config at the filesystem root
|
||||||
if [ -d /app/config ] && [ ! -L /app/config ]; then
|
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
|
rm -rf /app/config
|
||||||
fi
|
fi
|
||||||
ln -sfn "$HA_DATA_DIR" /app/config
|
ln -sfn "$HA_DATA_DIR" /app/config
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Configurations can be done through the app webUI, except for the following optio
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `CUSTOM_OPTIONS` | str | | Custom CLI options (e.g., `--name Homeassistant`) |
|
| `CUSTOM_OPTIONS` | str | | Custom CLI options (e.g., `--name Homeassistant`) |
|
||||||
| `DROPBOX_TOKEN` | str | | Dropbox integration token (see https://cloudcmd.io/) |
|
| `DROPBOX_TOKEN` | str | | Dropbox integration token (see https://cloudcmd.io/) |
|
||||||
| `localdisks` | str | | Local drives to mount (e.g., `sda1,sdb1,MYNAS`) |
|
| `localdisks` | str | | Local drives to mount (e.g., `sda1,sdb1,MYNAS`) |
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ for candidate in "${cloudcmd_candidates[@]}"; do
|
|||||||
fi
|
fi
|
||||||
done
|
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)
|
CLOUDCMD_BIN=$(command -v cloudcmd)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ You can place the user folder from the theme/skeleton in /share/codex/www/user,
|
|||||||
## Options
|
## Options
|
||||||
|
|
||||||
| Option | Description | Default | Example |
|
| Option | Description | Default | Example |
|
||||||
| -------- | ------------- | --------- | --------- |
|
|--------|-------------|---------|---------|
|
||||||
| `PGID` | Group ID for file permissions | `0` | `1000` |
|
| `PGID` | Group ID for file permissions | `0` | `1000` |
|
||||||
| `PUID` | User ID for file permissions | `0` | `1000` |
|
| `PUID` | User ID for file permissions | `0` | `1000` |
|
||||||
| `TZ` | Timezone in long format | - | `America/Los_Angeles` |
|
| `TZ` | Timezone in long format | - | `America/Los_Angeles` |
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ Webui can be found at `https://homeassistant:9980/browser/dist/admin/admin.html`
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `aliasgroup1` | str | | External address of the **Nextcloud** server allowed to use this Collabora (e.g. `https://nextcloud_domain\.com:443`) |
|
| `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` |
|
| `aliasgroup2` | str | | A second Nextcloud server, same format as `aliasgroup1` |
|
||||||
| `aliasgroup3` | str | | A third Nextcloud server, same format as `aliasgroup1` |
|
| `aliasgroup3` | str | | A third Nextcloud server, same format as `aliasgroup1` |
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ database.
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| -------- | ------------- |
|
|--------|-------------|
|
||||||
| `PUID` / `PGID` | Ownership applied to the add-on configuration directory. Defaults to `0` (root). See the note below before changing it. |
|
| `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`. |
|
| `TZ` | Timezone, e.g. `Europe/Paris`. |
|
||||||
| `localdisks` | Local disks to mount, e.g. `sda1` or a disk label. |
|
| `localdisks` | Local disks to mount, e.g. `sda1` or a disk label. |
|
||||||
|
|||||||
0
comicarr/rootfs/etc/services.d/comicarr/run
Executable file → Normal file
0
comicarr/rootfs/etc/services.d/comicarr/run
Executable file → Normal file
0
comicarr/rootfs/etc/services.d/nginx/run
Executable file → Normal file
0
comicarr/rootfs/etc/services.d/nginx/run
Executable file → Normal file
@@ -60,7 +60,7 @@ Initial setup requires creating an admin account through the web interface.
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
||||||
| `localdisks` | str | | Local drives to mount (e.g., `sda1,sdb1,MYNAS`) |
|
| `localdisks` | str | | Local drives to mount (e.g., `sda1,sdb1,MYNAS`) |
|
||||||
| `networkdisks` | str | | SMB shares to mount (e.g., `//SERVER/SHARE`) |
|
| `networkdisks` | str | | SMB shares to mount (e.g., `//SERVER/SHARE`) |
|
||||||
|
|||||||
@@ -46,17 +46,17 @@ fi
|
|||||||
# 1 Export user env_vars #
|
# 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
|
while IFS= read -r pair; do
|
||||||
name=$(jq -r '.name // empty' <<< "$pair")
|
name=$(jq -r '.name // empty' <<<"$pair")
|
||||||
value=$(jq -r '.value // empty' <<< "$pair")
|
value=$(jq -r '.value // empty' <<<"$pair")
|
||||||
if [[ $name =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
|
if [[ $name =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
|
||||||
echo "Setting env variable from options: $name"
|
echo "Setting env variable from options: $name"
|
||||||
export "$name"="$value"
|
export "$name"="$value"
|
||||||
elif [ -n "$name" ]; then
|
elif [ -n "$name" ]; then
|
||||||
echo "WARNING: ignoring invalid env_vars name: $name"
|
echo "WARNING: ignoring invalid env_vars name: $name"
|
||||||
fi
|
fi
|
||||||
done < <(jq -c '.env_vars[]?' "$OPTIONS_JSON" 2> /dev/null || true)
|
done < <(jq -c '.env_vars[]?' "$OPTIONS_JSON" 2>/dev/null || true)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
@@ -82,7 +82,7 @@ data_version=""
|
|||||||
|
|
||||||
if [ -f "$VERSION_MARKER" ]; then
|
if [ -f "$VERSION_MARKER" ]; then
|
||||||
data_version="$(head -n 1 "$VERSION_MARKER" | tr -cd '0-9.')"
|
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
|
# Existing data without a marker: only 7.17.9 was ever shipped before markers
|
||||||
data_version="7.17.9"
|
data_version="7.17.9"
|
||||||
fi
|
fi
|
||||||
@@ -128,7 +128,7 @@ mkdir -p "$PERSISTENT_HOME"
|
|||||||
for dir in "data" "config"; do
|
for dir in "data" "config"; do
|
||||||
if [ ! -L "$ES_HOME/$dir" ]; then
|
if [ ! -L "$ES_HOME/$dir" ]; then
|
||||||
if [ -d "$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"
|
rm -rf "${ES_HOME:?}/$dir"
|
||||||
fi
|
fi
|
||||||
mkdir -p "$PERSISTENT_HOME/$dir"
|
mkdir -p "$PERSISTENT_HOME/$dir"
|
||||||
@@ -139,7 +139,7 @@ done
|
|||||||
# Make the persisted files usable by the elasticsearch user (uid 1000),
|
# Make the persisted files usable by the elasticsearch user (uid 1000),
|
||||||
# which the official entrypoint drops to when started as root
|
# which the official entrypoint drops to when started as root
|
||||||
if [ "$(id -u)" -eq 0 ]; then
|
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
|
fi
|
||||||
|
|
||||||
echo "Data location: $PERSISTENT_HOME (persistent). Please wait while elasticsearch starts..."
|
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
|
# Check the HTTP status directly instead of curl -f: a 401 means
|
||||||
# Elasticsearch is up and answering (security just requires
|
# Elasticsearch is up and answering (security just requires
|
||||||
# auth), so it must count as healthy too, not as a failure.
|
# 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
|
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."
|
echo "Elasticsearch $current_version started successfully; data version recorded."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ Webui can be found at `<your-ip>:8096`, or within Home Assistant through Ingress
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `PGID` | int | `0` | Group ID for file permissions |
|
| `PGID` | int | `0` | Group ID for file permissions |
|
||||||
| `PUID` | int | `0` | User ID for file permissions |
|
| `PUID` | int | `0` | User ID for file permissions |
|
||||||
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
| `TZ` | str | | Timezone (e.g., `Europe/London`) |
|
||||||
|
|||||||
0
emby/rootfs/etc/services.d/nginx/run
Executable file → Normal file
0
emby/rootfs/etc/services.d/nginx/run
Executable file → Normal file
@@ -48,7 +48,7 @@ Initial setup requires starting the addon once to initialize configuration templ
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `CONFIG_LOCATION` | str | `/config/myelectricaldata/config.yaml` | Path to configuration file |
|
| `CONFIG_LOCATION` | str | `/config/myelectricaldata/config.yaml` | Path to configuration file |
|
||||||
| `TZ` | str | `Europe/Paris` | Timezone (e.g., `Europe/London`) |
|
| `TZ` | str | `Europe/Paris` | Timezone (e.g., `Europe/London`) |
|
||||||
| `mqtt_autodiscover` | bool | `true` | Enable MQTT autodiscovery |
|
| `mqtt_autodiscover` | bool | `true` | Enable MQTT autodiscovery |
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Initial setup requires starting the addon once to initialize configuration templ
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `CONFIG_LOCATION` | str | `/config/myelectricaldata/config.yaml` | Path to configuration file |
|
| `CONFIG_LOCATION` | str | `/config/myelectricaldata/config.yaml` | Path to configuration file |
|
||||||
| `TZ` | str | `Europe/Paris` | Timezone (e.g., `Europe/London`) |
|
| `TZ` | str | `Europe/Paris` | Timezone (e.g., `Europe/London`) |
|
||||||
| `mqtt_autodiscover` | bool | `true` | Enable MQTT autodiscovery |
|
| `mqtt_autodiscover` | bool | `true` | Enable MQTT autodiscovery |
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Webui can be found at <http://homeassistant:PORT>.
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `ENTE_ENDPOINT_URL` | str | `http://homeassistant.local:8280` | The URL where Ente API will be accessible (used by web UI) |
|
| `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 |
|
| `DB_PASSWORD` | str | `ente` | Database password for internal PostgreSQL |
|
||||||
| `USE_EXTERNAL_DB` | bool | `false` | Use external PostgreSQL database |
|
| `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:
|
If you want to use an external PostgreSQL database, set `USE_EXTERNAL_DB: true` and configure:
|
||||||
|
|
||||||
| Option | Type | Description |
|
| Option | Type | Description |
|
||||||
| -------- | ------ | ------------- |
|
|--------|------|-------------|
|
||||||
| `DB_HOSTNAME` | str | PostgreSQL server hostname |
|
| `DB_HOSTNAME` | str | PostgreSQL server hostname |
|
||||||
| `DB_PORT` | int | PostgreSQL server port (default: 5432) |
|
| `DB_PORT` | int | PostgreSQL server port (default: 5432) |
|
||||||
| `DB_USERNAME` | str | PostgreSQL username |
|
| `DB_USERNAME` | str | PostgreSQL username |
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ Create `/config/addons_config/epicgamesfree/config.json`:
|
|||||||
### Configuration Options
|
### Configuration Options
|
||||||
|
|
||||||
| Option | Type | Description |
|
| Option | Type | Description |
|
||||||
| -------- | ------ | ------------- |
|
|--------|------|-------------|
|
||||||
| `accounts` | array | List of Epic Games accounts |
|
| `accounts` | array | List of Epic Games accounts |
|
||||||
| `cronSchedule` | string | Cron schedule to claim games (default: `0 */6 * * *`) |
|
| `cronSchedule` | string | Cron schedule to claim games (default: `0 */6 * * *`) |
|
||||||
| `runOnStartup` | boolean | Run a claim cycle when the add-on starts |
|
| `runOnStartup` | boolean | Run a claim cycle when the add-on starts |
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ if bashio::config.true 'disable_cron'; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if bashio::command.exists service; then
|
if bashio::command.exists service; then
|
||||||
service cron stop > /dev/null 2>&1 || true
|
service cron stop >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ The web UI can be found at `<your-ip>:8071` or through the Home Assistant sideba
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `ssl` | bool | `false` | Enable HTTPS for web interface |
|
| `ssl` | bool | `false` | Enable HTTPS for web interface |
|
||||||
| `certfile` | str | `fullchain.pem` | SSL certificate file (in `/ssl/`) |
|
| `certfile` | str | `fullchain.pem` | SSL certificate file (in `/ssl/`) |
|
||||||
| `keyfile` | str | `privkey.pem` | SSL private key file (in `/ssl/`) |
|
| `keyfile` | str | `privkey.pem` | SSL private key file (in `/ssl/`) |
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ The web UI can be found at `<your-ip>:8071` or through the Home Assistant sideba
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `auth_method` | list | `password` | Authentication method (`password`, `noauth`, `proxy`, `oidc`) |
|
| `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`). |
|
| `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`) |
|
| `localdisks` | str | _(optional)_ | Local drives to mount (e.g., `sda1,sdb1,MYNAS`) |
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ Configurations can be done through the app webUI, except for the following optio
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `APP_KEY` | str | `CHANGEME_32_CHARS_EuC5dfn3LAPzeO` | **CRITICAL**: 32-character encryption key - change before first run! |
|
| `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 |
|
| `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) |
|
| `DB_CONNECTION` | list | `sqlite_internal` | Database type (sqlite_internal/mariadb_addon/mysql/pgsql) |
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ For complete setup documentation, see: https://docs.firefly-iii.org/data-importe
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Required | Description |
|
| Option | Type | Required | Description |
|
||||||
| -------- | ------ | ---------- | ------------- |
|
|--------|------|----------|-------------|
|
||||||
| `FIREFLY_III_URL` | str | Yes | URL to your Firefly III instance |
|
| `FIREFLY_III_URL` | str | Yes | URL to your Firefly III instance |
|
||||||
| `FIREFLY_III_ACCESS_TOKEN` | str | Yes | Personal Access Token from Firefly III |
|
| `FIREFLY_III_ACCESS_TOKEN` | str | Yes | Personal Access Token from Firefly III |
|
||||||
| `CONFIG_LOCATION` | str | Yes | Location for configuration files |
|
| `CONFIG_LOCATION` | str | Yes | Location for configuration files |
|
||||||
|
|||||||
@@ -4,27 +4,19 @@ set -e
|
|||||||
|
|
||||||
# Source environment variables for cron context
|
# Source environment variables for cron context
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
if [ -f /etc/environment ]; then
|
if [ -f /etc/environment ]; then set -a; . /etc/environment; set +a; fi
|
||||||
set -a
|
|
||||||
. /etc/environment
|
|
||||||
set +a
|
|
||||||
fi
|
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
if [ -f /.env ]; then
|
if [ -f /.env ]; then set -a; . /.env; set +a; fi
|
||||||
set -a
|
|
||||||
. /.env
|
|
||||||
set +a
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use persisted value instead of calling bashio::config (www-data may lack API access)
|
# Use persisted value instead of calling bashio::config (www-data may lack API access)
|
||||||
CONFIGSOURCE="${CONFIG_LOCATION}"
|
CONFIGSOURCE="${CONFIG_LOCATION}"
|
||||||
|
|
||||||
(
|
(
|
||||||
echo "[INFO] Running update according to defined schedule. Files located in $CONFIGSOURCE/import_files will be imported."
|
echo "[INFO] Running update according to defined schedule. Files located in $CONFIGSOURCE/import_files will be imported."
|
||||||
if [ "${SILENT_MODE}" = "true" ]; then
|
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"
|
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
|
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"
|
sudo -Eu www-data /usr/local/bin/php /var/www/html/artisan importer:auto-import "$CONFIGSOURCE/import_files"
|
||||||
fi
|
fi
|
||||||
) &>/proc/1/fd/1
|
) &>/proc/1/fd/1
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ Default password: `homeassistant123` (change via addon options).
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `PGID` | int | `0` | Group ID for file permissions |
|
| `PGID` | int | `0` | Group ID for file permissions |
|
||||||
| `PUID` | int | `0` | User ID for file permissions |
|
| `PUID` | int | `0` | User ID for file permissions |
|
||||||
| `WebuiPass` | str | `homeassistant123` | Web interface password |
|
| `WebuiPass` | str | `homeassistant123` | Web interface password |
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ interaction. Set `VNC_PASSWORD` in `config.env` to protect the VNC session.
|
|||||||
## Add-on options
|
## Add-on options
|
||||||
|
|
||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
| -------- | --------- | ------------- |
|
|--------|---------|-------------|
|
||||||
| `CONFIG_LOCATION` | `/config/config.env` | Persistent environment configuration file |
|
| `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 |
|
| `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` |
|
| `STORES` | empty | Optional comma-separated override, such as `epic,prime,gog,steam` |
|
||||||
|
|||||||
@@ -84,19 +84,19 @@ legacy_commands_to_stores() {
|
|||||||
for command in "${command_list[@]}"; do
|
for command in "${command_list[@]}"; do
|
||||||
normalized="${command,,}"
|
normalized="${command,,}"
|
||||||
case "${normalized}" in
|
case "${normalized}" in
|
||||||
*epic-games* | *epicgames* | *" epic"* | epic*)
|
*epic-games*|*epicgames*|*" epic"*|epic*)
|
||||||
selected="$(append_store "epic" "${selected}")"
|
selected="$(append_store "epic" "${selected}")"
|
||||||
;;
|
;;
|
||||||
*prime-gaming* | *primegaming* | *" prime"* | prime* | *" amazon"* | amazon*)
|
*prime-gaming*|*primegaming*|*" prime"*|prime*|*" amazon"*|amazon*)
|
||||||
selected="$(append_store "prime" "${selected}")"
|
selected="$(append_store "prime" "${selected}")"
|
||||||
;;
|
;;
|
||||||
*steam-games* | *" steam"* | steam*)
|
*steam-games*|*" steam"*|steam*)
|
||||||
selected="$(append_store "steam" "${selected}")"
|
selected="$(append_store "steam" "${selected}")"
|
||||||
;;
|
;;
|
||||||
*gamerpower*)
|
*gamerpower*)
|
||||||
selected="$(append_store "gamerpower" "${selected}")"
|
selected="$(append_store "gamerpower" "${selected}")"
|
||||||
;;
|
;;
|
||||||
*" gog"* | gog*)
|
*" gog"*|gog*)
|
||||||
selected="$(append_store "gog" "${selected}")"
|
selected="$(append_store "gog" "${selected}")"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
# Complete option reference:
|
# Complete option reference:
|
||||||
# https://github.com/P-Adamiec/Free-Games-Claimer-Remaster#configuration
|
# https://github.com/P-Adamiec/Free-Games-Claimer-Remaster#configuration
|
||||||
|
|
||||||
HEIGHT=720
|
|
||||||
LOGIN_TIMEOUT=180
|
|
||||||
NOVNC_PORT=6080
|
|
||||||
# Browser and noVNC
|
# Browser and noVNC
|
||||||
SHOW=1
|
SHOW=1
|
||||||
TIMEOUT=60
|
|
||||||
VNC_LOGIN_TIMEOUT=180
|
|
||||||
WIDTH=1280
|
WIDTH=1280
|
||||||
|
HEIGHT=720
|
||||||
|
TIMEOUT=60
|
||||||
|
LOGIN_TIMEOUT=180
|
||||||
|
VNC_LOGIN_TIMEOUT=180
|
||||||
|
NOVNC_PORT=6080
|
||||||
# Public address used in notification links when behind a reverse proxy.
|
# Public address used in notification links when behind a reverse proxy.
|
||||||
# VNC_URL=https://fgc.example.tld
|
# VNC_URL=https://fgc.example.tld
|
||||||
|
|
||||||
|
|||||||
@@ -47,10 +47,8 @@ def normalize_timestamp(value: Any) -> str:
|
|||||||
return parsed.isoformat(sep=" ", timespec="seconds")
|
return parsed.isoformat(sep=" ", timespec="seconds")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return (
|
return datetime.now(timezone.utc).replace(tzinfo=None).isoformat(
|
||||||
datetime.now(timezone.utc)
|
sep=" ", timespec="seconds"
|
||||||
.replace(tzinfo=None)
|
|
||||||
.isoformat(sep=" ", timespec="seconds")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -101,7 +99,8 @@ def iter_records(store: str, payload: Any, source: Path) -> Iterator[dict[str, A
|
|||||||
|
|
||||||
|
|
||||||
def ensure_schema(connection: sqlite3.Connection) -> None:
|
def ensure_schema(connection: sqlite3.Connection) -> None:
|
||||||
connection.executescript("""
|
connection.executescript(
|
||||||
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS claimed_games (
|
CREATE TABLE IF NOT EXISTS claimed_games (
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
store VARCHAR(32) NOT NULL,
|
store VARCHAR(32) NOT NULL,
|
||||||
@@ -118,7 +117,8 @@ def ensure_schema(connection: sqlite3.Connection) -> None:
|
|||||||
CREATE INDEX IF NOT EXISTS ix_claimed_games_store ON claimed_games (store);
|
CREATE INDEX IF NOT EXISTS ix_claimed_games_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_user ON claimed_games (user);
|
||||||
CREATE INDEX IF NOT EXISTS ix_claimed_games_game_id ON claimed_games (game_id);
|
CREATE INDEX IF NOT EXISTS ix_claimed_games_game_id ON claimed_games (game_id);
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def migrate() -> int:
|
def migrate() -> int:
|
||||||
@@ -209,12 +209,7 @@ def migrate() -> int:
|
|||||||
|
|
||||||
connection.commit()
|
connection.commit()
|
||||||
log(f"Imported {source_imported} record(s) from {source}")
|
log(f"Imported {source_imported} record(s) from {source}")
|
||||||
except (
|
except (OSError, ValueError, json.JSONDecodeError, sqlite3.Error) as err:
|
||||||
OSError,
|
|
||||||
ValueError,
|
|
||||||
json.JSONDecodeError,
|
|
||||||
sqlite3.Error,
|
|
||||||
) as err:
|
|
||||||
connection.rollback()
|
connection.rollback()
|
||||||
message = f"Failed to import {source}: {err}"
|
message = f"Failed to import {source}: {err}"
|
||||||
errors.append(message)
|
errors.append(message)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ This addon fetches gas consumption data from GRDF (French gas utility) and publi
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `CONFIG_LOCATION` | str | `/config/gazpar2mqtt/config.yaml` | Path to configuration file |
|
| `CONFIG_LOCATION` | str | `/config/gazpar2mqtt/config.yaml` | Path to configuration file |
|
||||||
| `TZ` | str | `Europe/Paris` | Timezone (e.g., `Europe/London`) |
|
| `TZ` | str | `Europe/Paris` | Timezone (e.g., `Europe/London`) |
|
||||||
| `mqtt_autodiscover` | bool | `true` | Enable MQTT autodiscovery |
|
| `mqtt_autodiscover` | bool | `true` | Enable MQTT autodiscovery |
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ Configurations can be done through the app webUI, except for the following optio
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ------ | --------- | ------------- |
|
|--------|------|---------|-------------|
|
||||||
| `ssl` | bool | `false` | Enable HTTPS for the web interface |
|
| `ssl` | bool | `false` | Enable HTTPS for the web interface |
|
||||||
| `certfile` | str | `fullchain.pem` | SSL certificate file (must be located in /ssl) |
|
| `certfile` | str | `fullchain.pem` | SSL certificate file (must be located in /ssl) |
|
||||||
| `keyfile` | str | `privkey.pem` | SSL key file (must be located in /ssl) |
|
| `keyfile` | str | `privkey.pem` | SSL key file (must be located in /ssl) |
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user