Compare commits

..

2 Commits

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

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

View File

@@ -4,30 +4,29 @@ description: >-
Workflow for alexbelgium/hassio-addons Home Assistant add-on work: diagnose with real
measurements, independent Codex review, implement, open a PR, resolve CodeRabbit / Copilot /
Codex bot review comments, verify in production. Use for any task touching an add-on in this
repo — bugs, RAM/CPU/performance tuning, Dockerfile, config.yaml, build.json,
updater.json, cont-init.d, services.d or s6 changes, version and CHANGELOG bumps, a failing
add-on CI check, opening or iterating PRs — and, on an add-on task, when asked to "check with
codex", "verify with chatgpt", or resolve bot comments. Cheap for small asks: a light path skips
the heavy steps.
repo — bugs, RAM/CPU/performance tuning, Dockerfile, config.yaml, cont-init.d or s6 changes,
version bumps, opening or iterating PRs — and when asked to "check with codex", "verify with
chatgpt", or resolve bot comments. Cheap for small asks: a light path skips the heavy steps.
---
# Home Assistant add-on workflow
**Answer style.** Chat replies are terse no pleasantries, tool-call narration, decorative tables,
emoji or dumped logs; quote the shortest decisive line and don't re-print what is already in
context. Telegraphic fragments are fine *there*. Two things outrank brevity, because dropping a
word from either changes the meaning rather than shortening it: never compress uncertainty markers
("likely", "assumed", "not verified"), negations, numbers, units, technical terms, code or error
strings — step 9's Verified/Checked/Assumed distinction wins every time; and write full prose
wherever a fragment could be read two ways — security warnings, irreversible-action confirmations,
multi-step sequences, and everything persisted (commits, CHANGELOG entries, PR bodies, review-thread
replies, the step 10 report).
**Answer style.** Chat replies are terse: no pleasantries, no tool-call narration, no decorative
tables or emoji, no dumped logs quote the shortest decisive line, and don't re-read or re-print
what is already in context. Fragments and dropped articles are fine. Never compressed: uncertainty
markers ("likely", "assumed", "not verified"), negations (`not`/`never`/`no`/`only`), numbers,
units, technical terms, code blocks, error strings — step 9's Verified/Checked/Assumed distinction
outranks brevity every time. Write in full prose, not fragments, for security warnings,
irreversible-action confirmations, and any multi-step sequence a fragment could make ambiguous.
Persisted text is prose too: commits, CHANGELOG entries, PR bodies, review-thread replies, the
step 10 report.
Triage first, then one of two paths:
- **Light** — typo/doc fixes, CHANGELOG edits, version bumps, one-file edits at ladder levels
1-3 (below), simple questions: scope → implement → validate (step 4) → PR (version bump +
CHANGELOG still required) → resolve bot comments.
1-3 (below), simple questions: scope → implement → validate (`$SKILL/scripts/validate.sh
<addon> --vs-master`; `$SKILL` defined below) → PR (version bump + CHANGELOG still required) →
resolve bot comments.
- **Full loop** — performance/RAM/CPU work, diagnosis, anything changing a shipped default,
ladder levels 4-6, or an explicit Codex-check request: scope → measure → plan → Codex reviews
the plan → implement → simplify → Codex reviews the code → **simplify again** → PR → resolve
@@ -48,8 +47,9 @@ reasoning about a hypothetical *host* either. A defensive branch is complexity l
the input that reaches it and the image or host where that happens, or delete it and let the case
fail visibly instead.
**Skill root.** The canonical copy lives at `.claude/skills/hassio-addon-workflow/`. Set it once;
every `scripts/…` and `references/…` path below is relative to it:
**Repo layout.** `alexbelgium/hassio-addons`; each add-on is a top-level directory. This skill is
checked in at `.claude/skills/hassio-addon-workflow/` (canonical copy). Set the skill root once,
then every `scripts/…` and `references/…` path below is relative to it:
```bash
SKILL="$(git rev-parse --show-toplevel)/.claude/skills/hassio-addon-workflow"
@@ -63,10 +63,9 @@ bash "$SKILL/scripts/preflight.sh" # and likewise for the other scripts
**Delegate heavy output to a subagent.** Codex reviews and multi-thread PR triage produce output
you don't need verbatim in your own context. For Codex's plan review (step 3), Codex's code
review (step 6), and PR-comment listing when there are more than ~5 threads (step 8): if subagents
are available, launch one to run the command and report back only the objections/findings and your
assessment of each, not the raw transcript. Otherwise redirect the output to a file and read the
parts you need.
review (step 6), and PR-comment listing when there are more than ~5 threads (step 8): launch a
subagent to run the command and report back only the objections/findings and your assessment of
each, not the raw transcript.
---
@@ -86,11 +85,9 @@ Measure the running add-on rather than reasoning from source (`$BUILD_VERSION` s
- Is this flag/driver/package actually present? → inspect the artifact: `/proc/<pid>/cmdline`,
`command -v`, `/var/log/apt/history.log`
Verify you're reading the right revision first — `scripts/preflight.sh` compares the checkout
against the running `$BUILD_VERSION`, which catches the usual stale branch before it costs a full
analysis pass (a version match does not prove the source is identical). Read
`references/evidence.md` before interpreting any number you did not get straight from
`measure.sh`, and for the failure modes this step exists to prevent.
Verify you're reading the right revision first — `scripts/preflight.sh` catches a stale branch
before it costs a full analysis pass. Measurement methodology, gotchas, and real failure examples:
`references/evidence.md`.
## 3. Plan — choose the mechanism level, then Codex reviews it (full loop)
@@ -120,60 +117,45 @@ Attack your own plan before implementing:
- What am I **inferring** that I could instead **detect at runtime** or **record explicitly**?
Highest-yield question here — see `references/evidence.md`'s failure-mode section.
- For every branch that exists **only to survive something going wrong**: name the image or host
where that input arrives (the standing rule above) and go and look, before you write it.
where that input actually arrives, and go and look. Naming is the bar, not reproducing it here —
`references/simplify.md` works the `/dev/shm` guard and the `s6-dumpenv` fallback through that
distinction.
Full loop only, before writing code: get Codex's independent read on the plan, delegated as above —
`references/codex-review.md` has the invocation and how to write the prompt.
Full loop only, before writing code: get Codex's independent read on the plan. Spawn a subagent
whose prompt includes the path `references/codex-review.md` and tells it to follow that file's
invocation, then report back only Codex's objections and an assessment of each — not the raw
transcript.
## 4. Implement
Read the `references/traps.md` section matching what you're about to touch. It is ~18 KB and all
but one section is irrelevant to any given edit, so print the one you need rather than reading the
file — run it with no argument to list the sections:
| Touching | Run |
| --- | --- |
| an option or anything a base-image service reads | `bash "$SKILL/scripts/traps.sh" passing` |
| a file the app also writes itself | `bash "$SKILL/scripts/traps.sh" "app's own"` |
| shell, bashio, a symlinked script | `bash "$SKILL/scripts/traps.sh" bashio` |
| `Dockerfile`, `build.json`, an arch guard | `bash "$SKILL/scripts/traps.sh" dockerfile` |
| Chromium, Electron, Xvfb | `bash "$SKILL/scripts/traps.sh" chromium` |
Then validate with `scripts/validate.sh <addon> --vs-master`, and write behavioural tests for
anything with branches, targeting **the regression a reviewer described**, not just the happy
path.
Touching a shell script, Dockerfile, or env option? Read `references/traps.md` first — skim the
headings, read the sections you're about to touch; the bashio, s6-env, arch-guard and versioning
traps are all live. (The light-path facts it holds — versioning format, CHANGELOG heading — are
already inline in step 7.) Validate with `scripts/validate.sh <addon> --vs-master`. Write
behavioural tests for anything with branches, targeting **the regression a reviewer described**,
not just the happy path.
## 5. Simplify
Six questions over your own diff, before anyone else reads it:
- **Level** — did the diff stay at the ladder level chosen in step 3, or creep up one?
- **Deletion** — can this be solved by deleting instead of adding?
- **Size** — is the fix bigger than the thing it fixes?
- **Reuse** — does any hunk reimplement what `.templates/`, another script in this add-on, or a
sibling add-on already does? If a future add-on hits this problem, will it find one way to solve
it or two? Fold near-duplicates in, or justify the divergence in the PR body.
- **Depth** — is this a special case bolted onto shared infrastructure? Fix the shared mechanism
instead once more than one add-on hits it; generalising from a single case is how bespoke
designs get built, so below that bar the special case is the right call.
- **Longevity** — how does this fail in three years, when the base image or upstream has moved?
The standing exception to Reuse and Depth: scripts shared by symlink with the webtop add-ons take a
new numbered script, never an edit (`references/traps.md#shell-and-bashio`). Case studies for the rest, including
what shipped when this pass was skipped: `references/simplify.md`.
Before requesting review, check: did the diff stay at the ladder level chosen in step 3? Can this
be solved by deleting instead of adding? Is the fix bigger than what it fixes? How does it fail in
three years? And on reuse: does any hunk reimplement something `.templates/`, another script in
this add-on, or a sibling add-on already does — and if a future add-on hits this same problem,
will it find one way to solve it or two? Fold a near-duplicate into the existing mechanism, or
justify the divergence in the PR body — but never at the cost of an isolation rule
`references/traps.md` documents: scripts shared by symlink with the webtop add-ons take a new
numbered script, not an edit. Case studies of what happens when this check is skipped:
`references/simplify.md`.
## 6. Codex attacks the code, then simplify what the review added (full loop only)
Same delegated invocation, pointed at `git diff origin/master...HEAD` plus your reasoning per
hunk. Details in `references/codex-review.md`.
Then run step 5's checks again over the hunks the review changed. Adversarial review mostly argues
*for* another branch — that is what it is asked to do — so accepting objections tends to ratchet
the diff upward, and nothing else in the loop walks it back down. Sort each objection before you
write anything:
**"this is wrong"** is a bug and you fix it; **"this is undefended"** is a claim about some host,
and it needs the same demonstration you would demand of a measurement — is the case it defends one
you have now demonstrated, or one you have merely been told about? Taking a
Then run step 5's checks again over the hunks the review changed. Adversarial review only ever
argues *for* another branch — that is its job — so accepting objections ratchets the diff upward,
and nothing else in the loop walks it back down. For each accepted objection: is the case it
defends one you have now demonstrated, or one you have merely been told about? Taking a
correctness objection often deletes the code that made it necessary, and a fix that collapses back
to fewer lines than you started the review with is the normal outcome, not a suspicious one.
@@ -183,23 +165,20 @@ kind of edit that leaves a stray `fi` behind.
## 7. Open the PR
Three hard gates — **`CHANGELOG.md` updated**, the **HA add-on linter**
(`frenck/action-addon-linter`), and the **add-on image build** — but only on a PR that changes a
top-level `config.*`. On a PR that doesn't (docs, `.github/`, `.claude/`) they *skip*, which is not
the same as passing. Super-Linter runs on every PR and is `continue-on-error`, so it never blocks;
fix its real findings anyway. Nothing checks the version bump, so bump it yourself — Supervisor
won't offer a rebuild without one, and `CLAUDE.md` has the format. Update `README.md` if you added
options; write the CHANGELOG heading as `## <version> (<date>)`, matching the date format already
in that file — almost always ISO `YYYY-MM-DD`, see `references/traps.md#ci-and-review-bots`.
CI gates on a PR: **`CHANGELOG.md` updated** (hard fail), the **HA add-on linter**
(`frenck/action-addon-linter`, blocking — not the weekly Super-Linter, which is non-blocking), and
the **add-on image build**. Bump `version` anyway (`X.Y.Z.N`, never `X.Y.Z-N`, see
`references/traps.md#versioning`) — Supervisor won't offer a rebuild without it. Update
`README.md` if you added options; write the CHANGELOG heading as `## <version> (<date>)`,
matching the date format already in that file — almost always ISO `YYYY-MM-DD`, see
`references/traps.md#ci-and-review-bots`.
Write the body to a file, `gh pr create --body-file`: state what was measured, what changed,
**what is not verified**, and how to roll back the riskiest hunk alone.
## 8. Resolve review comments
`scripts/pr_review.sh list|status|watch <PR>` to read, `reply <PR> <COMMENT_ID> <text|@file>` and
`resolve <PR> <THREAD_ID…|--all>` to answer; run it with no arguments for the full usage. For every
comment, **reproduce the
`scripts/pr_review.sh list|reply|resolve|status|watch <PR>`. For every comment, **reproduce the
claim before agreeing or disagreeing** — reviewers are frequently right and occasionally
confidently wrong; a reproduction takes a minute and decides it either way. Reply with the
evidence, then resolve. **Push back when you're right**, on the thread — a resolved-but-wrong
@@ -213,12 +192,13 @@ work" — either it was exercised, or say plainly it wasn't.
Light path: verification is `validate.sh` plus CI; anything beyond that is Assumed. Full loop: CI
passing proves the build works, not that the change does anything — re-run the measurement that
motivated the work once the rebuilt add-on is running. Real "merged and inert" examples, and what
to do when a fix cannot be self-verified: `references/evidence.md`. Then confirm the change
survived the merge: `git fetch origin master` first (the tracking ref is stale otherwise), then
`git diff origin/master -- <the paths you touched>` must come back empty. Ancestry is not the
check, and the builder reverts merges for reasons unrelated to your diff — both explained in
`references/traps.md#ci-and-review-bots`.
motivated the work once the rebuilt add-on is running. After merge, `git fetch origin master`
(the tracking ref is stale otherwise), then confirm the *changes* survived — `git diff
origin/master -- <the paths you touched>` comes back empty. Ancestry is not the check: a revert
leaves your commit in history and undoes its tree, so `--contains` reports success either way. The
builder's revert-on-failure job can revert a merge for reasons unrelated to your diff (see
`references/traps.md#ci-and-review-bots`). Real "merged and inert" examples, and what
to do when a fix can't be self-verified: `references/evidence.md`.
## 10. Calibrate and report

View File

@@ -2,7 +2,9 @@
Used for step 3 (plan review) and step 6 (code review), full loop only. Codex is a genuinely
different model reading the files itself; on this workload it has repeatedly been worth the
minutes. Run it through a subagent, per SKILL.md's delegation note.
minutes. Delegate the invocation to a subagent (see SKILL.md's subagent-delegation note) so its
output doesn't land verbatim in your context — have the subagent return only Codex's objections
and your assessment of each.
## Invocation
@@ -10,12 +12,11 @@ minutes. Run it through a subagent, per SKILL.md's delegation note.
on ~4 KB prompts (2026-08-03); the CLI with the same content succeeded. The MCP tool is still fine
for short questions.
`--sandbox read-only` blocks writes, not reads, and `approval_policy=never` stops it asking for
permission rather than stopping it acting — so it can still run read-only commands and often
falls back to fetching the repo from GitHub instead of reading your worktree. Paste every number
into the prompt rather than expecting it to gather them, and treat what it reports about *local*
state as unverified. `- <` feeds the prompt file on stdin. Run it in the background so you are not
blocked for the several minutes it takes (`&` here, or your harness's background-task mechanism):
`--sandbox read-only` lets Codex read files but blocks writes and command execution, and
`approval_policy=never` means it will not be prompted for permission to run anything either — so
paste every number into the prompt rather than expecting Codex to gather it. `- <` feeds the
prompt file on stdin. Run it in the background so you are not blocked for the several minutes it
takes (`&` here, or your harness's background-task mechanism):
```bash
codex exec --model gpt-5.6-sol --sandbox read-only --skip-git-repo-check \
@@ -39,5 +40,9 @@ codex exec --model gpt-5.6-sol --sandbox read-only --skip-git-repo-check \
confidently, and separately caught a genuine methodology error in the same review. Treat its
confirmations with the same scepticism as its objections — especially about the build.
**Its objections only ever argue for more code** — it is asked what could go wrong, never whether
the branch it wants is reachable. Sort them before writing anything; SKILL.md step 6 is that pass.
**Its objections ratchet complexity upward.** An adversarial reviewer is asked to find what could
go wrong, so its output is a list of arguments for more code; it is never asked whether the branch
it wants is reachable. Separate "this is wrong" from "this is undefended" before you write
anything: the first is a bug and you fix it, the second is a claim about some host, and it needs
the same demonstration you would demand of a measurement. That is what step 6's second simplify
pass is for.

View File

@@ -1,24 +1,16 @@
# Evidence — measurement methodology and case studies
## How to read the numbers
## Why summed RSS and reserved-vs-resident both matter
**Summed RSS overstates savings.** Shared library pages are counted once per process, so removing
a duplicate frees its *private* memory, not its RSS. Measured example: four MCP shims summed to
882 MB RSS but 643 MB PSS / 564 MB private, and per-process private ranged 54 MB down to 2 MB
which completely changes which duplicate is worth removing. Quote private when arguing "removing
this saves N MB".
**A large mapping is often not resident.** SysV/tmpfs segments are lazily populated. Xvfb's
506 MB framebuffer shows `Rss: 0` in `/proc/<pid>/smaps`. Check before calling anything a leak.
**`/proc/meminfo` and `free` show host figures** — there is no memory cgroup namespace here.
Never attribute those totals to the add-on.
**A short CPU sample is not a CPU measurement.** A 3 s sample measured 2.3% where a 20 s sample
measured 21.6% for the same process. Use >= 20 s for anything you report.
`scripts/measure.sh` already reports PSS and private alongside RSS, and resident separately from
reserved, so these three only bite when you compute a figure yourself or quote one from `ps`.
- **Summed RSS double-counts shared pages.** Removing a duplicate process frees its *private*
memory, not its RSS. `scripts/measure.sh` reports PSS and private alongside RSS — quote
**private** when arguing "removing this saves N MB".
- **A big mapping is not necessarily resident.** Large SysV/tmpfs segments are lazily populated;
reserved size is reported separately from resident for this reason.
- `/proc/meminfo` and `free` show **host** figures (no memory cgroup namespace here) — never
attribute those to the add-on.
- Sample duration matters: a 3 s CPU sample measured 2.3% where a 20 s sample measured 21.6% for
the same process. Use ≥20 s for anything you report.
## Before asserting anything, ask what would show it false

View File

@@ -30,24 +30,28 @@ Being able to build the complicated thing is not a reason to.
It took the maintainer asking "is this the simplest way possible?" to run the pass that step 6
now requires.
## Where SKILL.md step 5's questions get hard
## Checks worth running against your own diff
**Deletion is not automatically the safe direction.** The `/dev/shm` case in `evidence.md` is a
removal that reintroduced a crash loop on hosts unlike this one. A removal that depends on a host
default needs the same verification as an addition.
**Naming the case is the bar for a defensive branch, not reproducing it.** Docker's 64 MB
`/dev/shm` default is documented behaviour that Home Assistant does not override, so that guard
stays even though this host measured 7.7 GB. Nobody could name a single image shipping
`with-contenv` without `s6-dumpenv`, so that fallback went. If you cannot name the case, delete the
branch: the situation then fails the way it already fails today, visibly, instead of through a
second path that is never exercised and silently rots as the base images move. Weigh the cost both
ways — a one-flag guard against a crash you cannot rule out is cheap; a second code path that
degrades to the pre-fix behaviour anyway is not. Write in the PR body what you cut and why, so the
next person does not re-add it from the same reasoning.
**"Bigger than the thing it fixes" is a smell, not a rule** — but it usually means the problem was
framed one level too deep.
**Three-year failure** favours code that reads a documented knob. Code that reaches into private
internals does not survive the base image moving.
- **Did the diff stay at the ladder level chosen in step 3?** If it crept up a level, either
justify that out loud or redo it at the level you chose.
- **Can this be solved by deleting instead of adding?** A flag that shouldn't be passed, a
process that shouldn't start, a registration that shouldn't be duplicated. Deleting usually
shrinks the regression surface — but not always: the `/dev/shm` case in
`references/evidence.md` is a removal that reintroduced a crash loop on hosts unlike this one.
A removal that depends on a host default still needs the same verification as an addition.
- **Is the fix bigger than the thing it fixes?** That is a smell, not a rule — but it usually
means the problem was framed one level too deep.
- **For each defensive branch: what input reaches it, on which image or host?** Go and check,
the way you would check a measurement. The bar is being able to **name** the case, not to
reproduce it here: Docker's 64 MB `/dev/shm` default is documented behaviour that HA does not
override, so the bullet above keeps that guard even though this host measured 7.7 GB. Nobody
could name a single image shipping `with-contenv` without `s6-dumpenv`, so that fallback went.
If you cannot name the case, delete the branch — the situation then fails the way it already
fails today, visibly, instead of through a second path that is never exercised and silently
rots as the base images move. Weigh the cost too: a one-flag guard against a crash you cannot
rule out is cheap, a second code path that degrades to the pre-fix behaviour anyway is not.
Write down in the PR body what you cut and why, so the next person does not re-add it from the
same reasoning.
- **How does this fail in three years**, when the base image, Electron, or upstream has moved?
Code that reads a documented knob keeps working. Code that reaches into private internals
does not.

View File

@@ -9,6 +9,7 @@ workflows and lint rules — that is not repeated here.
## Contents
- [Environment and workspace](#environment-and-workspace)
- [Measurement](#measurement)
- [Passing values into base-image services](#passing-values-into-base-image-services)
- [Writing into an app's own config](#writing-into-an-apps-own-config)
- [Shell and bashio](#shell-and-bashio)
@@ -45,6 +46,20 @@ gate. One observed run took ~3 hours, with 20+ runs queued against 2 executing
runner contention, not the diff. Check `gh run list` before concluding your PR is stuck. Poll in
a background task, and never claim the build is verified when it hasn't run.
## Measurement
**Summed RSS overstates savings.** Shared library pages are counted once per process, so removing
a duplicate frees its *private* memory, not its RSS. Measured example: four MCP shims summed to
882 MB RSS but 643 MB PSS / 564 MB private, and per-process private ranged 54 MB down to 2 MB —
which completely changes which duplicate is worth removing. Quote private when arguing "removing
this saves N MB".
**A large mapping is often not resident.** SysV/tmpfs segments are lazily populated. Xvfb's
506 MB framebuffer shows `Rss: 0` in `/proc/<pid>/smaps`. Check before calling anything a leak.
**`/proc/meminfo` and `free` show host figures** — there is no memory cgroup namespace here.
Never attribute those totals to the add-on.
**`rtk` filters some command output.** For a complete listing, redirect to a file and read that
(`ps ... > $SP/ps.txt`), or use `rtk proxy <cmd>`.
@@ -175,9 +190,11 @@ build.
## Versioning
`CLAUDE.md` owns the format (`X.Y.Z.N`, never `X.Y.Z-N`, and why). The one thing it does not say:
date-based versions (`2026.08.03`) are common here, so check whether master has already moved to
the version you were about to use before you pick it.
**`X.Y.Z.N`, never `X.Y.Z-N`.** A hyphen parses as a semver pre-release, which Supervisor treats
as *older* than `X.Y.Z` — the update is never offered.
Date-based versions (`2026.08.03`) are common here. Check whether master has already moved to the
version you were about to use.
## Chromium / Electron under Xvfb

View File

@@ -30,8 +30,14 @@ VAR="${1:?usage: env_trace.sh <VAR> [process-name-or-pid]}"
TARGET="${2:-}"
# VAR is interpolated into grep/sed patterns below — restrict it to a valid env var name
case "$VAR" in
[A-Za-z_]*) [ -z "${VAR//[A-Za-z0-9_]/}" ] || { echo "invalid env var name: $VAR" >&2; exit 1; } ;;
*) echo "invalid env var name: $VAR" >&2; exit 1 ;;
[A-Za-z_]*) [ -z "${VAR//[A-Za-z0-9_]/}" ] || {
echo "invalid env var name: $VAR" >&2
exit 1
} ;;
*)
echo "invalid env var name: $VAR" >&2
exit 1
;;
esac
echo "== tracing ${VAR} =="
@@ -80,7 +86,8 @@ echo "3. s6 container_environment (only read by services using #!/usr/bin/with-c
seen3=0
for d in /var/run/s6/container_environment /run/s6/container_environment; do
if [ -f "$d/$VAR" ]; then
echo " $d/$VAR = [$(cat "$d/$VAR")]"; seen3=1
echo " $d/$VAR = [$(cat "$d/$VAR")]"
seen3=1
fi
done
[ "$seen3" -eq 0 ] && echo " not present in either envdir"
@@ -122,8 +129,8 @@ else
done
fi
# What it was actually launched with beats any theory about its environment.
tr '\0' '\n' < "/proc/$pid/cmdline" 2> /dev/null | tail -n +2 |
grep -iE "res|screen|${VAR}" | head -3 | sed 's/^/ argv: /'
tr '\0' '\n' < "/proc/$pid/cmdline" 2> /dev/null | tail -n +2 \
| grep -iE "res|screen|${VAR}" | head -3 | sed 's/^/ argv: /'
done
fi
fi

View File

@@ -5,14 +5,7 @@
# Usage: preflight.sh [repo-path] [addon-slug]
set -uo pipefail
# Default to the checkout this is run from, never a fixed path: a fixed path silently inspected
# the main checkout while the caller worked in a worktree, reporting a branch nobody was editing —
# the exact stale-checkout trap this script exists to catch. Falling back to one when git cannot
# answer would recreate it, so refuse instead and make the caller say which repo they mean.
REPO="${1:-}"
if [ -z "$REPO" ]; then
REPO=$(git rev-parse --show-toplevel 2> /dev/null) || REPO=""
fi
REPO="${1:-/data/claude/hassio-addons}"
SLUG="${2:-}"
echo "== tools =="
@@ -33,14 +26,9 @@ fi
echo
echo "== repo =="
# git-aware check: in a worktree .git is a file, not a directory
if [ -z "$REPO" ]; then
echo " not inside a git checkout, and no repo path given"
echo " -> pass one explicitly: preflight.sh <repo-path> [addon-slug]"
exit 1
fi
if ! git -C "$REPO" rev-parse --git-dir > /dev/null 2>&1; then
echo " no git repo at $REPO"
exit 1
exit 0
fi
cd "$REPO" || exit 0
branch=$(git branch --show-current 2> /dev/null || echo "(detached)")
@@ -59,7 +47,10 @@ if [ -n "${BUILD_VERSION:-}" ]; then
if [ -z "$SLUG" ] && [ -n "${HOSTNAME:-}" ]; then
base=$(printf '%s' "$HOSTNAME" | sed 's/^[0-9a-f]\{8\}-//')
for cand in "$(printf '%s' "$base" | tr '-' '_')" "$base"; do
[ -f "$REPO/$cand/config.yaml" ] && { SLUG="$cand"; break; }
[ -f "$REPO/$cand/config.yaml" ] && {
SLUG="$cand"
break
}
done
[ -z "$SLUG" ] && SLUG="$base"
fi
@@ -81,8 +72,8 @@ if [ -n "${BUILD_VERSION:-}" ]; then
else
echo " MISMATCH — this branch is NOT what is running."
git fetch origin master --quiet 2> /dev/null
master=$(git show origin/master:"$SLUG/config.yaml" 2> /dev/null |
grep -E '^version:' | head -1 | tr -d "\"'" | awk '{print $2}')
master=$(git show origin/master:"$SLUG/config.yaml" 2> /dev/null \
| grep -E '^version:' | head -1 | tr -d "\"'" | awk '{print $2}')
echo " origin/master version = ${master:-unknown}"
echo " -> work from origin/master; analysing this branch will mislead you."
echo

View File

@@ -1,49 +0,0 @@
#!/usr/bin/env bash
# Print one section of references/traps.md.
#
# traps.md is ~18 KB and every section but one is irrelevant to any given edit: a shell fix needs
# 642 bytes of it, a Dockerfile fix 2.6 KB, and "CI and review bots" — 35% of the file — is needed
# at steps 7-8 and never at step 4. A markdown anchor cannot be loaded on its own, so reading the
# file to reach one section pays for all of them. This prints just the section, so the routing
# table in SKILL.md step 4 costs what it claims to.
#
# Usage: traps.sh <keyword> # substring of a section heading, case-insensitive
# traps.sh # list the sections
set -uo pipefail
TRAPS="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/references/traps.md"
[ -f "$TRAPS" ] || { echo "not found: $TRAPS" >&2; exit 1; }
# The Contents list duplicates the headings; grep the headings themselves so the list cannot drift.
list() {
echo "sections (pass any substring):"
grep '^## ' "$TRAPS" | grep -v '^## Contents' | sed 's/^## / /'
}
[ $# -eq 0 ] && { list; exit 0; }
# awk over exact heading text: section names contain '/' and other characters that would need
# escaping in a sed address, and a keyword matching several headings should be an error, not a
# silent pick of the first.
mapfile -t matches < <(grep '^## ' "$TRAPS" | grep -v '^## Contents' |
grep -iF -- "$1" | sed 's/^## //')
case "${#matches[@]}" in
0)
echo "no section matching '$1'" >&2
list >&2
exit 1
;;
1) ;;
*)
echo "'$1' matches ${#matches[@]} sections — be more specific:" >&2
printf ' %s\n' "${matches[@]}" >&2
exit 1
;;
esac
awk -v want="## ${matches[0]}" '
$0 == want { inside = 1; print; next }
inside && /^## / { exit }
inside { print }
' "$TRAPS"

View File

@@ -13,22 +13,31 @@ set -uo pipefail
if root=$(git rev-parse --show-toplevel 2> /dev/null); then cd "$root" || exit 1; fi
ADDON="${1:-}"
[ "${ADDON:-}" = "--vs-master" ] && { ADDON=""; set -- --vs-master; }
[ "${ADDON:-}" = "--vs-master" ] && {
ADDON=""
set -- --vs-master
}
VS_MASTER=false
for a in "$@"; do [ "$a" = "--vs-master" ] && VS_MASTER=true; done
if [ -z "$ADDON" ]; then
mapfile -t _dirs < <(git diff --name-only origin/master...HEAD 2> /dev/null |
cut -d/ -f1 | sort -u | grep -vE '^\.' )
mapfile -t _dirs < <(git diff --name-only origin/master...HEAD 2> /dev/null \
| cut -d/ -f1 | sort -u | grep -vE '^\.')
if [ "${#_dirs[@]}" -gt 1 ]; then
echo "several changed dirs: ${_dirs[*]}"
echo "pass one explicitly: validate.sh <addon-dir>"; exit 1
echo "pass one explicitly: validate.sh <addon-dir>"
exit 1
fi
ADDON="${_dirs[0]:-}"
fi
[ -z "$ADDON" ] && { echo "usage: validate.sh <addon-dir> [--vs-master]"; exit 1; }
[ -z "$ADDON" ] && {
echo "usage: validate.sh <addon-dir> [--vs-master]"
exit 1
}
git rev-parse --verify origin/master > /dev/null 2>&1 || {
echo "origin/master missing — run: git fetch origin master"; exit 1; }
echo "origin/master missing — run: git fetch origin master"
exit 1
}
export PYTHONDONTWRITEBYTECODE=1
echo "== validating $ADDON =="
@@ -36,14 +45,22 @@ fail=0
note() { printf ' %-13s %s\n' "$1" "$2"; }
# execline `run`/`finish` files are not shell (25 of them here, across 21 add-ons). Neither
# linter below can read one, so anything either says about it is noise.
is_execline() { local l; IFS= read -r l < "$1" 2> /dev/null; [[ $l == '#!'*execlineb* ]]; }
is_execline() {
local l
IFS= read -r l < "$1" 2> /dev/null
[[ $l == '#!'*execlineb* ]]
}
# Shell: bash -n then shellcheck -x (follows sourced files, as CI does). One list for both.
files=()
while IFS= read -r f; do is_execline "$f" || files+=("$f"); done \
< <(find "$ADDON" -type f \( -name '*.sh' -o -name 'run' -o -name 'finish' -o -name 'autostart' \) 2> /dev/null)
for f in "${files[@]}"; do
if ! out=$(bash -n "$f" 2>&1); then note "bash -n" "FAIL $f"; echo "$out" | sed 's/^/ /'; fail=1; fi
if ! out=$(bash -n "$f" 2>&1); then
note "bash -n" "FAIL $f"
echo "$out" | sed 's/^/ /'
fail=1
fi
done
[ "$fail" -eq 0 ] && note "bash -n" "${#files[@]} file(s) checked"
@@ -65,13 +82,16 @@ command -v hadolint > /dev/null 2>&1 && [ -f "$ADDON/Dockerfile" ] && {
if [ -f "$ADDON/config.yaml" ]; then
# path passed as argv, never interpolated into Python source
python3 - "$ADDON/config.yaml" <<'PY' || { note "config.yaml" "FAIL parse"; fail=1; }
python3 - "$ADDON/config.yaml" << 'PY' || {
import yaml,sys
d=yaml.safe_load(open(sys.argv[1]))
print(' %-13s ok (version=%s, %d options)' % ('config.yaml', d.get('version'), len(d.get('options') or {})))
missing=[k for k in (d.get('options') or {}) if k not in (d.get('schema') or {})]
if missing: print(' %-13s options with no schema entry: %s' % ('WARN', missing)); sys.exit(0)
PY
note "config.yaml" "FAIL parse"
fail=1
}
command -v yamllint > /dev/null 2>&1 && {
yl=$(yamllint -f parsable "$ADDON/config.yaml" 2>&1 | grep -c .)
note "yamllint" "$yl finding(s) (compare with --vs-master)"
@@ -79,7 +99,10 @@ PY
fi
while IFS= read -r f; do
python3 -m py_compile "$f" 2> /dev/null || { note "py_compile" "FAIL $f"; fail=1; }
python3 -m py_compile "$f" 2> /dev/null || {
note "py_compile" "FAIL $f"
fail=1
}
done < <(find "$ADDON" -type f -name '*.py' 2> /dev/null)
$VS_MASTER && command -v npx > /dev/null 2>&1 && [ -f "$ADDON/CHANGELOG.md" ] && {
@@ -96,7 +119,8 @@ if git diff --name-only origin/master...HEAD 2> /dev/null | grep -Fxq "$ADDON/CH
note "CHANGELOG" "updated"
else
# This one IS gated: onpr_check-pr.yaml exits 1 without it.
note "CHANGELOG" "NOT UPDATED for $ADDON — CI hard-gates this"; fail=1
note "CHANGELOG" "NOT UPDATED for $ADDON — CI hard-gates this"
fail=1
fi
if git diff origin/master...HEAD -- "$ADDON/config.yaml" 2> /dev/null | grep -q '^+version:'; then
note "version" "bumped"
@@ -110,7 +134,8 @@ note "docker build" "NOT tested locally (dockerd unavailable) — CI is the only
if $VS_MASTER; then
echo
echo "== findings ADDED by this diff (pre-existing ones filtered out) =="
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
added=0
# Fed by process substitution, not a pipe: a pipeline runs this in a subshell, where the
# findings below could never reach $fail and the verdict would contradict the list.
@@ -125,14 +150,26 @@ if $VS_MASTER; then
# cancel it out and report a false clean.
case "$f" in
*.sh | *autostart | */run | */finish)
command -v shellcheck > /dev/null 2>&1 || { echo " $f: SKIPPED (shellcheck not installed)"; continue; }
cmd() { shellcheck -x -f gcc "$1" 2>&1 | sed 's/^[^:]*:[0-9]*:[0-9]*://'; } ;;
command -v shellcheck > /dev/null 2>&1 || {
echo " $f: SKIPPED (shellcheck not installed)"
continue
}
cmd() { shellcheck -x -f gcc "$1" 2>&1 | sed 's/^[^:]*:[0-9]*:[0-9]*://'; }
;;
*.yaml | *.yml)
command -v yamllint > /dev/null 2>&1 || { echo " $f: SKIPPED (yamllint not installed)"; continue; }
cmd() { yamllint -f parsable "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*:[0-9]*//'; } ;;
command -v yamllint > /dev/null 2>&1 || {
echo " $f: SKIPPED (yamllint not installed)"
continue
}
cmd() { yamllint -f parsable "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*:[0-9]*//'; }
;;
*Dockerfile)
command -v hadolint > /dev/null 2>&1 || { echo " $f: SKIPPED (hadolint not installed)"; continue; }
cmd() { hadolint "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*//'; } ;;
command -v hadolint > /dev/null 2>&1 || {
echo " $f: SKIPPED (hadolint not installed)"
continue
}
cmd() { hadolint "$1" 2>&1 | sed 's/^[^:]*//; s/^:[0-9]*//'; }
;;
*) continue ;;
esac
b=$(cmd "$tmp/base" | sort)
@@ -141,7 +178,8 @@ if $VS_MASTER; then
[ -n "$new" ] && {
echo " $f: $(printf '%s\n' "$new" | grep -c .) NEW finding(s)"
printf '%s\n' "$new" | sed 's/^/ /' | head -5
added=1; fail=1
added=1
fail=1
}
done < <(git diff --name-only origin/master...HEAD -- "$ADDON" 2> /dev/null)
[ "$added" -eq 0 ] && echo " (none — this diff introduced no new lint findings)"

View File

@@ -28,7 +28,10 @@ if [ -n "$RAW" ]; then
# Directory list without checking out any of them.
git ls-tree -d --name-only HEAD > "$OUT/dirs.txt"
for guess in "$CAND" "${CAND//_/-}" "${CAND//_/.}"; do
if grep -qxF "$guess" "$OUT/dirs.txt"; then ADDON="$guess"; break; fi
if grep -qxF "$guess" "$OUT/dirs.txt"; then
ADDON="$guess"
break
fi
done
# Separator-insensitive exact match: a title like "[Calibre-web]" (hyphen)
# against a directory named calibre_web (underscore) matches neither exact
@@ -38,7 +41,10 @@ if [ -n "$RAW" ]; then
if [ -z "$ADDON" ]; then
CAND_STRIPPED=$(tr -d '_.-' <<< "$CAND")
while IFS= read -r dir; do
if [ "$(tr -d '_.-' <<<"$dir")" = "$CAND_STRIPPED" ]; then ADDON="$dir"; break; fi
if [ "$(tr -d '_.-' <<< "$dir")" = "$CAND_STRIPPED" ]; then
ADDON="$dir"
break
fi
done < "$OUT/dirs.txt"
fi
# Last resort: longest directory name contained in the candidate.

View File

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

View File

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

View File

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

View File

@@ -166,7 +166,7 @@ jobs:
id: classify
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
continue-on-error: true
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Without this the action falls back to the OIDC -> Claude App token

View File

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

View File

@@ -19,8 +19,8 @@ if ! bashio::supervisor.ping 2>/dev/null; then
if [ ! -f /usr/bin/bashio ]; then
cp -rf /usr/local/lib/bashio-standalone.sh /usr/bin/bashio
fi
grep -rl "^#!.*bashio" /etc |
while IFS= read -r f; do
grep -rl "^#!.*bashio" /etc \
| while IFS= read -r f; do
grep -qF "source /usr/local/lib/bashio-standalone.sh" "$f" && continue
sed -i '1a source /usr/local/lib/bashio-standalone.sh' "$f"
done

View File

@@ -152,8 +152,8 @@ if [[ "${1:-}" == "--self-test" ]]; then
done
fi
[[ "$self_test_rc" -eq 0 ]] &&
echo "${#self_test_values[@]} values round-tripped unchanged (export block + dotenv)"
[[ "$self_test_rc" -eq 0 ]] \
&& echo "${#self_test_values[@]} values round-tripped unchanged (export block + dotenv)"
exit "$self_test_rc"
fi
@@ -193,7 +193,10 @@ SECRETSOURCE=""
resolve_secret() {
local v="$1" name line
[[ "$v" =~ ^[[:space:]]*\!secret[[:space:]]+(.+)$ ]] || { printf '%s' "$v"; return; }
[[ "$v" =~ ^[[:space:]]*\!secret[[:space:]]+(.+)$ ]] || {
printf '%s' "$v"
return
}
name="${BASH_REMATCH[1]}"
[[ -n "$SECRETSOURCE" ]] || bashio::exit.nok "Secrets not mounted"

View File

@@ -11,8 +11,14 @@ pick_exec_dir() {
for d in /dev/shm /run /var/run /mnt /root /; do
if [ -d "$d" ] && [ -w "$d" ]; then
local t="${d%/}/.exec_test_$$"
printf '#!/bin/sh\necho ok\n' >"$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
chmod 700 "$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
printf '#!/bin/sh\necho ok\n' > "$t" 2> /dev/null || {
rm -f "$t" 2> /dev/null || true
continue
}
chmod 700 "$t" 2> /dev/null || {
rm -f "$t" 2> /dev/null || true
continue
}
if "$t" > /dev/null 2>&1; then
rm -f "$t" 2> /dev/null || true
echo "$d"

View File

@@ -46,7 +46,8 @@ _bashio_color() {
}
_bashio_log() {
local c="${1:-}"; shift || true
local c="${1:-}"
shift || true
printf '%s%s%s\n' "$(_bashio_color "$c")" "$*" "$(_bashio_color reset)"
}
@@ -246,7 +247,10 @@ bashio::info.supervisor() { printf '%s' "standalone"; }
# -----------------------------------------------------------------------------
bashio::config() {
local key="${1:-}"
[ -n "$key" ] || { printf '%s' ""; return 0; }
[ -n "$key" ] || {
printf '%s' ""
return 0
}
local v=""
if _bashio_env_get "$key" > /dev/null 2>&1; then
@@ -356,7 +360,10 @@ bashio::host.hostname() {
# bashio::services.available "mqtt"
bashio::services() {
local svc="${1:-}" key="${2:-}"
[ -n "$svc" ] && [ -n "$key" ] || { printf '%s' ""; return 0; }
[ -n "$svc" ] && [ -n "$key" ] || {
printf '%s' ""
return 0
}
local upper svc_upper var v=""
upper="$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')"
@@ -419,7 +426,11 @@ bashio::homeassistant.token() {
# Exit helpers
# -----------------------------------------------------------------------------
bashio::exit.ok() { exit 0; }
bashio::exit.nok() { local m="${1:-}"; [ -n "$m" ] && bashio::log.red "$m"; exit 1; }
bashio::exit.nok() {
local m="${1:-}"
[ -n "$m" ] && bashio::log.red "$m"
exit 1
}
# -----------------------------------------------------------------------------
# Core config check shim

View File

@@ -16,54 +16,6 @@ else
echo "Starting custom scripts"
fi
##########################################
# Install the stop handler #
##########################################
# As namespace PID 1 -- which is what "init: false" makes this script -- the kernel
# discards any signal whose handler is still SIG_DFL. Installed at the end of startup,
# as it used to be, the handler missed every stop that arrived while the Supervisor
# probe or the cont-init chain was still running: Home Assistant waited out the grace
# period for a SIGKILL and reported the add-on as failed. Nothing here is interrupted
# mid-write by the move -- only PID 1 is signalled, and bash runs a trap at a command
# boundary, so whatever external command is in flight reaps first.
terminate() {
local local_pid
# Best-effort, so errexit must not apply: this runs with `set -e` in force (validate_shebang
# leaves it on), and under errexit the first failing command aborts the handler and exits with
# its status, skipping the child-kill loop and the `exit 0` below. Every command in the body
# here is already guarded, but add-ons patch this function at build time -- postgres_15 and
# postgres_17 sed an unguarded `pg_ctl ... stop` in right after the echo -- and that command
# fails whenever the stop arrives before the database is up.
set +e
echo "Termination signal received, forwarding to subprocesses..."
if command -v pgrep >/dev/null 2>&1; then
while read -r pid; do
[ -n "$pid" ] || continue
echo "Terminating child PID $pid"
kill -TERM "$pid" 2>/dev/null || echo "Failed to terminate PID $pid"
done < <(pgrep -P "$$" || true)
else
for p in /proc/[0-9]*/; do
local_pid="${p#/proc/}"
local_pid="${local_pid%/}"
if [ "$local_pid" -ne 1 ] && grep -q "^PPid:[[:space:]]*$$" "/proc/$local_pid/status" 2>/dev/null; then
echo "Terminating child PID $local_pid"
kill -TERM "$local_pid" 2>/dev/null || echo "Failed to terminate PID $local_pid"
fi
done
fi
wait || true
echo "All subprocesses terminated. Exiting."
exit 0
}
# Only when this script is PID 1. Under Docker's own init the entrypoint is an ordinary
# child and the signal handling above belongs to that init, not to us.
if $PID1; then
trap terminate SIGTERM SIGINT
fi
##########################################
# Pick an exec-capable directory #
##########################################
@@ -76,8 +28,14 @@ pick_exec_dir() {
if [ -d "$d" ] && [ -w "$d" ]; then
# Create a tiny test executable to confirm "exec" works
local t="${d%/}/.exec_test_$$"
printf '#!/bin/sh\necho ok\n' >"$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
chmod 700 "$t" 2>/dev/null || { rm -f "$t" 2>/dev/null || true; continue; }
printf '#!/bin/sh\necho ok\n' > "$t" 2> /dev/null || {
rm -f "$t" 2> /dev/null || true
continue
}
chmod 700 "$t" 2> /dev/null || {
rm -f "$t" 2> /dev/null || true
continue
}
if "$t" > /dev/null 2>&1; then
rm -f "$t" 2> /dev/null || true
echo "$d"
@@ -257,8 +215,8 @@ if $PID1 && { [ -x /command/with-contenv ] || [ -x /usr/bin/with-contenv ]; }; t
# accepts a surviving symlink-to-directory and would let the dump follow it. rm does not traverse
# a symlink, but it would empty anything bind-mounted at this exact path -- not a configuration
# any add-on uses, and not one s6 would tolerate either.
if rm -rf "$S6_CONTAINER_ENV" "$S6_CONTAINER_ENV.tmp" && mkdir -p "$S6_CONTAINER_ENV.tmp" &&
s6-dumpenv -- "$S6_CONTAINER_ENV.tmp" && mv "$S6_CONTAINER_ENV.tmp" "$S6_CONTAINER_ENV"; then
if rm -rf "$S6_CONTAINER_ENV" "$S6_CONTAINER_ENV.tmp" && mkdir -p "$S6_CONTAINER_ENV.tmp" \
&& s6-dumpenv -- "$S6_CONTAINER_ENV.tmp" && mv "$S6_CONTAINER_ENV.tmp" "$S6_CONTAINER_ENV"; then
echo "Populated $S6_CONTAINER_ENV for with-contenv"
else
# Leaves the directory absent, which is exactly how this fails today -- so the failure mode is
@@ -476,6 +434,31 @@ if $PID1; then
echo " "
echo -e "\033[0;32mEverything started!\033[0m"
terminate() {
local local_pid
echo "Termination signal received, forwarding to subprocesses..."
if command -v pgrep > /dev/null 2>&1; then
while read -r pid; do
[ -n "$pid" ] || continue
echo "Terminating child PID $pid"
kill -TERM "$pid" 2> /dev/null || echo "Failed to terminate PID $pid"
done < <(pgrep -P "$$" || true)
else
for p in /proc/[0-9]*/; do
local_pid="${p#/proc/}"
local_pid="${local_pid%/}"
if [ "$local_pid" -ne 1 ] && grep -q "^PPid:[[:space:]]*$$" "/proc/$local_pid/status" 2> /dev/null; then
echo "Terminating child PID $local_pid"
kill -TERM "$local_pid" 2> /dev/null || echo "Failed to terminate PID $local_pid"
fi
done
fi
wait || true
echo "All subprocesses terminated. Exiting."
exit 0
}
trap terminate SIGTERM SIGINT
while :; do
sleep infinity &
wait $!

View File

@@ -75,7 +75,7 @@ You can add the following tags in the file :
### Addon version numbering
The `version` written in the addon `config.yaml` is the one Home Assistant compares to decide whether an update is available. Home Assistant hides the update when it can order both versions and the new one is not strictly newer (`1.2.3` -> `1.2.3-2` is a semver pre-release, so it is *older*), and it cannot order tags such as `version-bf9e0b4f` or `ubuntu-2026-06-01` at all.
The `version` written in the addon `config.yaml` is the one Home Assistant compares to decide whether an update is available. Home Assistant hides the update when it can order both versions and the new one is not strictly newer (`1.2.3` -> `1.2.3-2` is a semver pre-release, so it is _older_), and it cannot order tags such as `version-bf9e0b4f` or `ubuntu-2026-06-01` at all.
The addon version is therefore derived from the upstream tag:

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

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

View File

@@ -1,25 +1,3 @@
## 20260909.7 (10-09-2026)
- Correct the "first daily detection consensus" README section (review feedback on PR #3056): a merged-upstream setting stays in the build rather than disappearing, every attempt for a species is held back until one is accepted rather than only the first, and the "known to every active bird model" exemption is scoped per audio source, matching the implementation.
## 20260909.6 (10-09-2026)
- Document the fork-only "first daily detection consensus" setting (alexbelgium/birdnet-go#63): requires a second model to confirm each bird species' first detection of the day. Off by default; no behaviour change unless enabled.
## 20260909.5 (09-09-2026)
- Minor bugs fixed
## 20260909.4 (09-09-2026)
- Minor bugs fixed
## 20260909.3 (09-09-2026)
- Minor bugs fixed
## 20260909.2 (09-09-2026)
- Minor bugs fixed
## 20260909 (09-09-2026)
- Minor bugs fixed
## 20260908.2 (08-09-2026)
- Minor bugs fixed
## 20260908.1 (08-09-2026)
- Synced with upstream birdnet-go (4 commits); re-merges the open fork PRs, adding fork PR #62 (reanalyze a clip with every loaded model + one-click correction)
## 20260908 (08-09-2026)
- Synced with upstream birdnet-go (7 commits); re-merges the open fork PRs
## 20260907 (07-09-2026)
- Rebuild: re-merges the open fork PRs, picking up the updated fork PR #57
## 20260901.4 (01-09-2026)
- Minor bugs fixed
## 20260901.3 (01-09-2026)

View File

@@ -1,6 +1,6 @@
# Home assistant add-on: Birdnet-Go (from source)
> **⚠️ Test build.** This is a special variant of the [standard birdnet-go add-on](https://github.com/alexbelgium/hassio-addons/tree/master/birdnet-go). Instead of pulling the prebuilt `ghcr.io/tphakala/birdnet-go` image, it **compiles BirdNET-Go from the [`alexbelgium/birdnet-go`](https://github.com/alexbelgium/birdnet-go) fork**. At build time it syncs the fork's `main` with the `tphakala/birdnet-go` upstream and **merges every open non-draft ("in review") pull request on the fly** (see [`merge-prs.sh`](./merge-prs.sh)), so the binary reflects upstream main plus all work currently under review. Everything below is identical to the standard add-on, except the fork-only settings listed under [Fork-only settings](#fork-only-settings).
> **⚠️ Test build.** This is a special variant of the [standard birdnet-go add-on](https://github.com/alexbelgium/hassio-addons/tree/master/birdnet-go). Instead of pulling the prebuilt `ghcr.io/tphakala/birdnet-go` image, it **compiles BirdNET-Go from the [`alexbelgium/birdnet-go`](https://github.com/alexbelgium/birdnet-go) fork**. At build time it syncs the fork's `main` with the `tphakala/birdnet-go` upstream and **merges every open non-draft ("in review") pull request on the fly** (see [`merge-prs.sh`](./merge-prs.sh)), so the binary reflects upstream main plus all work currently under review. Everything below is identical to the standard add-on.
@@ -65,38 +65,6 @@ Additional variables can be configured using the config.yaml file found in /conf
- Config_env.yaml
Additional environment variables can be configured there
### Fork-only settings
These are currently fork-only settings from the [`alexbelgium/birdnet-go`](https://github.com/alexbelgium/birdnet-go) fork and are **not** in the standard add-on. [`merge-prs.sh`](./merge-prs.sh) syncs the fork's `main` with upstream before applying open PRs, so once a PR merges upstream the setting stays in this build — it just arrives via that sync instead of the PR-merge step, and stops being fork-only. Only a PR that is **closed without merging** drops its setting from later builds.
#### First daily detection consensus
Requires a second model to confirm the **first** detection of each bird species each day. Until one is accepted, every attempt for that species is held to the same two-model bar; only once a detection clears it does every later detection that day behave exactly as it does today, on a single model.
The first detection of a species in a day is the weakest evidence the pipeline produces, and it is the one that creates a "new species today" entry. Asking two models to agree on just that one detection removes most spurious new-species entries without slowing anything else down.
**Off by default.** Turn it on in the web UI under *Settings → Filters → First Daily Detection Consensus*, or in `config.yaml`:
```yaml
realtime:
firstdailyconsensus:
enabled: true
```
The setting is re-read on each detection cycle, so it takes effect without restarting the add-on.
It deliberately does **nothing** in these cases, all of which keep today's single-model behaviour:
- you run only one bird model (the default) — a second opinion does not exist, so the rule can never trigger
- the species is not a bird — bats and the non-bird sound classes Perch reports (insects, amphibians, mammals, `power_tool`, and so on)
- the species is not known to *every* active bird model analyzing that audio source — a species only one of them can name could never reach two confirmations. With several sources running different model combinations, this is decided per source, not add-on-wide
- a dynamic threshold has actually lowered the bar for that species, meaning you asked for a more permissive gate
- the taxonomy or the database cannot be consulted — it fails open and accepts the detection
In practice it only bites when a single audio source has two or more bird models (for example BirdNET plus Perch) analyzing it, on species all of them can identify. The trade is fewer false new-species entries, at the cost of occasionally delaying a genuine first sighting until a second model agrees.
Requires [alexbelgium/birdnet-go#63](https://github.com/alexbelgium/birdnet-go/pull/63).
### MQTT and MariaDB auto-configuration (opt-in)
If the Home Assistant **MQTT** addon is installed and running and you set `mqtt_auto_config: true` in the addon options, the addon writes the HA Mosquitto credentials directly into BirdNET-Go's `config.yaml` on every startup: `realtime.mqtt.enabled`, `broker`, `username`, and `password` are populated, and the topic defaults to `birdnet`. In addition, it enables BirdNET-Go's **native Home Assistant MQTT auto-discovery** (`realtime.mqtt.homeassistant.enabled`), so the detection sensors show up in Home Assistant automatically — **no manual MQTT sensor YAML required** (the hand-written sensors in [HAINTEGRATION.md](./HAINTEGRATION.md) remain available if you prefer to build your own). Messages are also retained (`realtime.mqtt.retain: true`) so sensor states survive Home Assistant restarts. When the option is `false` (the default), the addon still logs the broker details and reminds you about the option whenever Mosquitto is detected — nothing is written.

View File

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

View File

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

View File

@@ -9,9 +9,9 @@ Here are some example tests I did (the whole threads are really interesting also
**My recommendation:**
- **Best entry system (< 50€):** Boya By-lm40 (30€) + deadcat (10€)
- **Best middle end system (< 150€):** Clippy EM272 TRS/TRRS (55€) + Rode AI micro trs/trrs to usb (70€) + Rycote deadcat (27€)
- **Best high end system (<400€):** Clippy EM272 XLR (85€) or LOM Ucho Pro (75€) + Focusrite Scarlet 2i2 4th Gen (200€) + Bubblebee Pro Extreme deadcat (45€)
- **Best entry system (< 50 €):** Boya By-lm40 (30 €) + deadcat (10 €)
- **Best middle end system (< 150 €):** Clippy EM272 TRS/TRRS (55 €) + Rode AI micro trs/trrs to usb (70 €) + Rycote deadcat (27 €)
- **Best high end system (<400 €):** Clippy EM272 XLR (85 €) or LOM Ucho Pro (75 €) + Focusrite Scarlet 2i2 4th Gen (200 €) + Bubblebee Pro Extreme deadcat (45 €)
**Sources for high end microphones in Europe:**

View File

@@ -112,6 +112,7 @@ def automatic_mqtt_publish(file, detection, path):
mqttc.publish(mqtt_topic, json_bird, 1)
log.info("Posted to MQTT: ok")
# Create MQTT client using legacy callback API when available for
# compatibility with paho-mqtt >= 2.0
callback_api = getattr(mqtt, "CallbackAPIVersion", None)

View File

@@ -9,9 +9,9 @@ Here are some example tests I did (the whole threads are really interesting also
**My recommendation:**
- **Best entry system (< 50€):** Boya By-lm40 (30€) + deadcat (10€)
- **Best middle end system (< 150€):** Clippy EM272 TRS/TRRS (55€) + Rode AI micro trs/trrs to usb (70€) + Rycote deadcat (27€)
- **Best high end system (<400€):** Clippy EM272 XLR (85€) or LOM Ucho Pro (75€) + Focusrite Scarlet 2i2 4th Gen (200€) + Bubblebee Pro Extreme deadcat (45€)
- **Best entry system (< 50 €):** Boya By-lm40 (30 €) + deadcat (10 €)
- **Best middle end system (< 150 €):** Clippy EM272 TRS/TRRS (55 €) + Rode AI micro trs/trrs to usb (70 €) + Rycote deadcat (27 €)
- **Best high end system (<400 €):** Clippy EM272 XLR (85 €) or LOM Ucho Pro (75 €) + Focusrite Scarlet 2i2 4th Gen (200 €) + Bubblebee Pro Extreme deadcat (45 €)
**Sources for high end microphones in Europe:**

View File

@@ -112,6 +112,7 @@ def automatic_mqtt_publish(file, detection, path):
mqttc.publish(mqtt_topic, json_bird, 1)
log.info("Posted to MQTT: ok")
# Create MQTT client using legacy callback API when available for
# compatibility with paho-mqtt >= 2.0
callback_api = getattr(mqtt, "CallbackAPIVersion", None)

View File

@@ -73,7 +73,7 @@ map:
name: BirdNET-PiPy
options:
env_vars: []
ICECAST_PASSWORD: ''
ICECAST_PASSWORD: ""
data_location: /config/data
panel_icon: mdi:bird
ports:

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

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

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

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

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

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

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

View File

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

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

View File

@@ -99,7 +99,7 @@ This addon supports mounting both local drives and remote SMB shares:
### Optional Calibre-Web features
Calibre-Web documents optional extras that a manual installation adds with `pip install calibreweb[metadata]` and similar. **You do not need to install anything here**: the LinuxServer base image this add-on builds on installs Calibre-Web's `requirements.txt` *and* its full `optional-requirements.txt` into the application's virtualenv, so the gdrive, gmail, goodreads, ldap, oauth, metadata, comics and kobo dependencies are all present already. Running `pip install calibreweb[...]` inside the container is not a supported way to enable them: it installs the PyPI distribution of Calibre-Web over an installation that already has those dependencies, and it can disturb the versions the base image pinned. It is also thrown away, because the Supervisor recreates the add-on container on restart.
Calibre-Web documents optional extras that a manual installation adds with `pip install calibreweb[metadata]` and similar. **You do not need to install anything here**: the LinuxServer base image this add-on builds on installs Calibre-Web's `requirements.txt` _and_ its full `optional-requirements.txt` into the application's virtualenv, so the gdrive, gmail, goodreads, ldap, oauth, metadata, comics and kobo dependencies are all present already. Running `pip install calibreweb[...]` inside the container is not a supported way to enable them: it installs the PyPI distribution of Calibre-Web over an installation that already has those dependencies, and it can disturb the versions the base image pinned. It is also thrown away, because the Supervisor recreates the add-on container on restart.
Optional features are switched on in the Calibre-Web web interface, not in the add-on options, under `Admin` -> `Basic Configuration` -> `Feature Configuration` (for example `Enable Uploads`, `Enable Kobo sync`, `Use Goodreads`).

View File

@@ -30,16 +30,16 @@ else
# duplicates it can leave behind are entries calibre-web skips or already trusts.
# The column only exists once calibre-web 0.6.27+ has migrated app.db and cont-init runs
# before calibre-web, so a failure here is not fatal : the next start applies it.
trusted_ips_error=$(sqlite3 /config/app.db "update settings set config_reverse_proxy_trusted_ips='127.0.0.1,::1,::ffff:127.0.0.1,172.30.32.0/23,::ffff:172.30.32.0/119,'||coalesce(config_reverse_proxy_trusted_ips,'') where coalesce(config_reverse_proxy_trusted_ips,'') not like '%::ffff:172.30.32.0/119%'" 2>&1) ||
bashio::log.warning "Could not set the ingress trusted ip list, it will be applied at next start (${trusted_ips_error})"
trusted_ips_error=$(sqlite3 /config/app.db "update settings set config_reverse_proxy_trusted_ips='127.0.0.1,::1,::ffff:127.0.0.1,172.30.32.0/23,::ffff:172.30.32.0/119,'||coalesce(config_reverse_proxy_trusted_ips,'') where coalesce(config_reverse_proxy_trusted_ips,'') not like '%::ffff:172.30.32.0/119%'" 2>&1) \
|| bashio::log.warning "Could not set the ingress trusted ip list, it will be applied at next start (${trusted_ips_error})"
# Calibre-web autodetects kepubify only while this setting is still NULL. On every install
# that predates the Dockerfile fix above, that detection already ran, found nothing usable and
# stored an empty string, so it is never retried. Put an empty value back to NULL and
# calibre-web detects /opt/kepubify itself when it starts, a few seconds after this runs.
# A path the user set by hand is not empty and is left alone.
kepubify_error=$(sqlite3 /config/app.db "update settings set config_kepubifypath = NULL where config_kepubifypath = ''" 2>&1) ||
bashio::log.warning "Could not reset the kepubify path, it will be applied at next start (${kepubify_error})"
kepubify_error=$(sqlite3 /config/app.db "update settings set config_kepubifypath = NULL where config_kepubifypath = ''" 2>&1) \
|| bashio::log.warning "Could not reset the kepubify path, it will be applied at next start (${kepubify_error})"
fi
bashio::log.info "Default username:password is admin:admin123"

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

View File

@@ -338,8 +338,7 @@ ln -sfn "$CODEX_BIN" "$CODEX_LINK"
CODEX_SANDBOX_MODE="$(bashio::config 'codex_sandbox_mode' 'danger-full-access')"
run_as_runtime_user mkdir -p "$RUNTIME_HOME/.codex"
CODEX_SANDBOX_MODE="$CODEX_SANDBOX_MODE" RUNTIME_HOME="$RUNTIME_HOME" \
run_as_runtime_user python3 - <<'PY' \
|| bashio::log.warning "Unable to update the managed Codex configuration block"
run_as_runtime_user python3 - << 'PY' || bashio::log.warning "Unable to update the managed Codex configuration block"
import os
import re
import tomllib

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

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

@@ -133,7 +133,9 @@ def rest(method, base, token, path, body=None):
if body is not None:
data = json.dumps(body).encode("utf-8")
headers["Content-Type"] = "application/json"
req = urllib.request.Request(_url(base, path), data=data, method=method, headers=headers)
req = urllib.request.Request(
_url(base, path), data=data, method=method, headers=headers
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
text = resp.read().decode("utf-8")
@@ -141,7 +143,9 @@ def rest(method, base, token, path, body=None):
detail = exc.read().decode("utf-8", "replace").strip()
sys.exit(f"ha-cli: HTTP {exc.code} {exc.reason} on {method} {path}\n{detail}")
except urllib.error.URLError as exc:
sys.exit(f"ha-cli: cannot reach Home Assistant ({exc.reason}) on {method} {path}")
sys.exit(
f"ha-cli: cannot reach Home Assistant ({exc.reason}) on {method} {path}"
)
try:
return json.loads(text) if text.strip() else None
except ValueError:
@@ -214,7 +218,9 @@ def main(argv):
sys.exit("usage: ha-cli call <domain.service> [BODY]")
domain, service = args[0].split(".", 1)
body = _read_body(args[1]) if len(args) > 1 else None
_print(rest("POST", rest_base, token, f"services/{domain}/{service}", body or {}))
_print(
rest("POST", rest_base, token, f"services/{domain}/{service}", body or {})
)
elif cmd == "states":
path = f"states/{args[0]}" if args else "states"
_print(rest("GET", rest_base, token, path))

View File

@@ -76,7 +76,11 @@ def main() -> int:
# Find big string/array fields before paying the headroom import cost.
def is_string_array(value):
return isinstance(value, list) and len(value) > ARRAY_KEEP and all(isinstance(v, str) for v in value)
return (
isinstance(value, list)
and len(value) > ARRAY_KEEP
and all(isinstance(v, str) for v in value)
)
if isinstance(response, str):
string_candidates = ["__whole__"] if len(response) >= MIN_CHARS else []
@@ -85,10 +89,14 @@ def main() -> int:
string_candidates = [
key
for key, value in response.items()
if key not in SKIP_KEYS and isinstance(value, str) and len(value) >= MIN_CHARS
if key not in SKIP_KEYS
and isinstance(value, str)
and len(value) >= MIN_CHARS
]
array_candidates = [
key for key, value in response.items() if key not in SKIP_KEYS and is_string_array(value)
key
for key, value in response.items()
if key not in SKIP_KEYS and is_string_array(value)
]
else:
string_candidates = []

View File

@@ -1,8 +1,7 @@
arch:
- aarch64
- amd64
description:
Automatically removes stuck and unwanted downloads from your *arr and download clients
description: Automatically removes stuck and unwanted downloads from your *arr and download clients
devices:
- /dev/dri
- /dev/dri/card0

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

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

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

View File

@@ -4,9 +4,17 @@ set -e
# Source environment variables for cron context
# shellcheck disable=SC1091
if [ -f /etc/environment ]; then set -a; . /etc/environment; set +a; fi
if [ -f /etc/environment ]; then
set -a
. /etc/environment
set +a
fi
# shellcheck disable=SC1091
if [ -f /.env ]; then set -a; . /.env; set +a; fi
if [ -f /.env ]; then
set -a
. /.env
set +a
fi
# Use persisted value instead of calling bashio::config (www-data may lack API access)
CONFIGSOURCE="${CONFIG_LOCATION}"

View File

@@ -2,14 +2,14 @@
# Complete option reference:
# https://github.com/P-Adamiec/Free-Games-Claimer-Remaster#configuration
HEIGHT=720
LOGIN_TIMEOUT=180
NOVNC_PORT=6080
# Browser and noVNC
SHOW=1
WIDTH=1280
HEIGHT=720
TIMEOUT=60
LOGIN_TIMEOUT=180
VNC_LOGIN_TIMEOUT=180
NOVNC_PORT=6080
WIDTH=1280
# Public address used in notification links when behind a reverse proxy.
# VNC_URL=https://fgc.example.tld

View File

@@ -47,8 +47,10 @@ def normalize_timestamp(value: Any) -> str:
return parsed.isoformat(sep=" ", timespec="seconds")
except ValueError:
pass
return datetime.now(timezone.utc).replace(tzinfo=None).isoformat(
sep=" ", timespec="seconds"
return (
datetime.now(timezone.utc)
.replace(tzinfo=None)
.isoformat(sep=" ", timespec="seconds")
)
@@ -99,8 +101,7 @@ def iter_records(store: str, payload: Any, source: Path) -> Iterator[dict[str, A
def ensure_schema(connection: sqlite3.Connection) -> None:
connection.executescript(
"""
connection.executescript("""
CREATE TABLE IF NOT EXISTS claimed_games (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
store VARCHAR(32) NOT NULL,
@@ -117,8 +118,7 @@ def ensure_schema(connection: sqlite3.Connection) -> None:
CREATE INDEX IF NOT EXISTS ix_claimed_games_store ON claimed_games (store);
CREATE INDEX IF NOT EXISTS ix_claimed_games_user ON claimed_games (user);
CREATE INDEX IF NOT EXISTS ix_claimed_games_game_id ON claimed_games (game_id);
"""
)
""")
def migrate() -> int:
@@ -209,7 +209,12 @@ def migrate() -> int:
connection.commit()
log(f"Imported {source_imported} record(s) from {source}")
except (OSError, ValueError, json.JSONDecodeError, sqlite3.Error) as err:
except (
OSError,
ValueError,
json.JSONDecodeError,
sqlite3.Error,
) as err:
connection.rollback()
message = f"Failed to import {source}: {err}"
errors.append(message)

View File

@@ -2,8 +2,7 @@ arch:
- amd64
backup_exclude:
- "**/machine-learning/*"
description:
Self-hosted photo and video backup solution directly from your mobile phone
description: Self-hosted photo and video backup solution directly from your mobile phone
devices:
- /dev/dri
- /dev/dxg

View File

@@ -121,7 +121,10 @@ ACCOUNT_SCHEMA_OPTS="Albums ExcludedAlbums People Tags ShowFavorites ShowMemorie
for opt in $GENERAL_SCHEMA_OPTS; do
if config_has ".$opt"; then
$GENERAL_STARTED || { echo "General:"; GENERAL_STARTED=true; }
$GENERAL_STARTED || {
echo "General:"
GENERAL_STARTED=true
}
yaml_kv " " "$opt" "$(config_val ".$opt")"
fi
done
@@ -129,7 +132,10 @@ ACCOUNT_SCHEMA_OPTS="Albums ExcludedAlbums People Tags ShowFavorites ShowMemorie
# Add general env_vars (skip if already set via schema option)
for key in "${!GENERAL_ENVS[@]}"; do
if ! config_has ".$key"; then
$GENERAL_STARTED || { echo "General:"; GENERAL_STARTED=true; }
$GENERAL_STARTED || {
echo "General:"
GENERAL_STARTED=true
}
yaml_kv " " "$key" "${GENERAL_ENVS[$key]}"
fi
done

View File

@@ -3,8 +3,7 @@ arch:
- amd64
backup_exclude:
- "**/machine-learning/*"
description:
Self-hosted photo and video backup solution directly from your mobile phone
description: Self-hosted photo and video backup solution directly from your mobile phone
devices:
- /dev/dri
- /dev/dxg

View File

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

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

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

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

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

View File

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

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

View File

View File

View File

@@ -1,6 +1,3 @@
## 0.6.1.2 (2026-09-07)
- Rebuild to pick up a shared `ha_entrypoint.sh` fix: the SIGTERM/SIGINT handler is now installed at the top of the entrypoint instead of at the end of startup. With `init: false` the entrypoint is namespace PID 1, and the kernel discards a signal that PID 1 has no handler for, so a stop arriving while the Supervisor probe or the `cont-init.d` chain was still running was lost entirely and the add-on died only when the grace period expired into SIGKILL. Stops are now honoured from the first moment of startup. No change to add-on behaviour otherwise.
## 0.6.1.1 (2026-09-07)
- Fix the add-on refusing to stop: Home Assistant reported an Error status after a few seconds and the container kept running and serving the web UI. `cont-init.d/99-run.sh` started nginx in the foreground, and `ha_entrypoint.sh` runs every cont-init script in the foreground, so the entrypoint never reached the point where it installs the `terminate()` handler that forwards SIGTERM to the application on shutdown. The application is now started in the background, and `init: false` makes the entrypoint run as PID 1 so the orphaned process is reparented to it and receives that signal. Closes #3049.

View File

@@ -21,5 +21,5 @@ schema:
value: str?
slug: omni-tools
url: https://github.com/alexbelgium/hassio-addons
version: 0.6.1.2
version: 0.6.1.1
webui: "[PROTO:ssl]://[HOST]:[PORT:80]"

View File

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

View File

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

View File

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

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

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

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

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

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

View File

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

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

View File

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

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

View File

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

View File

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

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