mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-10-16 13:09:25 +02:00
Compare commits
3 Commits
cc8ebcfe7e
...
ci/version
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2dad6c88e | ||
|
|
c697ed2c06 | ||
|
|
1d70eca70a |
143
.github/scripts/check_version_bump.sh
vendored
Executable file
143
.github/scripts/check_version_bump.sh
vendored
Executable file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env bash
|
||||
# Destination: .github/scripts/check_version_bump.sh
|
||||
#
|
||||
# Fails a pull request that changes what an add-on ships without bumping that
|
||||
# add-on's `version`. Supervisor only offers a rebuild when `version` changes,
|
||||
# so a fix merged without one leaves every user on the old image: merged, inert,
|
||||
# and the issue looks closed. Nothing else in CI checks this.
|
||||
#
|
||||
# It deliberately does NOT reuse check-addon-changes' `changedAddons`. That
|
||||
# output is built from `^<addon>/config\.(json|ya?ml)$` alone, so it is empty
|
||||
# for exactly the pull requests this check exists to catch — an add-on whose
|
||||
# scripts changed while config.yaml did not. Reusing it would make this a no-op.
|
||||
# This scan is local to this check and does not affect which add-ons get linted
|
||||
# or built.
|
||||
#
|
||||
# Env:
|
||||
# BASE_SHA (required) — commit this PR is diffed against
|
||||
# HEAD_SHA (required) — the PR's merge commit
|
||||
#
|
||||
# Exit 0 = every add-on that needs a bump got one (or nothing relevant changed).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${BASE_SHA:?BASE_SHA must be set}"
|
||||
: "${HEAD_SHA:?HEAD_SHA must be set}"
|
||||
|
||||
# Files that ship to users only as repo metadata, or that bots rewrite on their
|
||||
# own schedule. Changing one of these alone does not require anybody to receive
|
||||
# a new image, so it must not demand a version bump — otherwise every changelog
|
||||
# or stats-graph commit would fail CI.
|
||||
#
|
||||
# Everything else under an add-on directory counts: Dockerfile, rootfs/,
|
||||
# build.json|yaml (base images), apparmor.txt and translations/ (Supervisor
|
||||
# re-reads them on update), root-level *.sh that Dockerfiles COPY, and
|
||||
# config.yaml itself — an added option or changed port needs the update offered
|
||||
# just as much as a code change does. Allowlisting the ignorable and treating
|
||||
# the remainder as significant fails closed: a new kind of file defaults to
|
||||
# "needs a bump" rather than silently escaping the check.
|
||||
is_ignorable() {
|
||||
local rel="$1" # path relative to the add-on directory
|
||||
# addon/images/** is artwork; addon/rootfs/**/images/** is shipped content,
|
||||
# so this must be anchored at the add-on root rather than matching any
|
||||
# path that happens to contain an "images" segment.
|
||||
case "$rel" in
|
||||
images/*) return 0 ;;
|
||||
esac
|
||||
# Anything else nested ships inside the image (rootfs/, translations/, ...).
|
||||
case "$rel" in
|
||||
*/*) return 1 ;;
|
||||
esac
|
||||
case "$rel" in
|
||||
CHANGELOG.md | updater.json | stats.png | icon.png | logo.png | *.md) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# An add-on is a top-level directory with a config file. Tested against the
|
||||
# BASE tree so a directory deleted by this PR is still recognised (and then
|
||||
# skipped below), and .github/, .templates/ and .claude/ are excluded for free
|
||||
# by simply not having one.
|
||||
addon_config_at() {
|
||||
local ref="$1" addon="$2" f
|
||||
for f in config.yaml config.yml config.json; do
|
||||
if git cat-file -e "${ref}:${addon}/${f}" 2> /dev/null; then
|
||||
printf '%s' "$f"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Reads the add-on's declared version. JSON goes through jq so a minified or
|
||||
# reordered config.json is read correctly rather than silently returning empty.
|
||||
# YAML is matched at column 0 on purpose: an indented `version:` belongs to a
|
||||
# nested mapping (a schema entry, an option literally named version) and
|
||||
# comparing it would compare the wrong value. A trailing YAML comment is
|
||||
# stripped before quotes so `version: "1.2.3" # note` does not read as a bump.
|
||||
version_at() {
|
||||
local ref="$1" path="$2" content
|
||||
content=$(git show "${ref}:${path}" 2> /dev/null) || return 1
|
||||
case "$path" in
|
||||
*.json)
|
||||
printf '%s' "$content" | jq -r '.version // empty' 2> /dev/null
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "$content" |
|
||||
grep -m1 -E '^version[[:space:]]*:' |
|
||||
sed -E 's/^version[[:space:]]*:[[:space:]]*//; s/[[:space:]]+#.*$//; s/^["'\'']//; s/["'\'']$//; s/[[:space:]]*$//'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
mapfile -t CHANGED < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
|
||||
|
||||
declare -A NEEDS_BUMP=()
|
||||
for file in "${CHANGED[@]}"; do
|
||||
[ -n "$file" ] || continue
|
||||
case "$file" in */*) ;; *) continue ;; esac # top-level files are not add-ons
|
||||
addon="${file%%/*}"
|
||||
is_ignorable "${file#*/}" && continue
|
||||
addon_config_at "$BASE_SHA" "$addon" > /dev/null 2>&1 || continue
|
||||
NEEDS_BUMP["$addon"]=1
|
||||
done
|
||||
|
||||
if [ "${#NEEDS_BUMP[@]}" -eq 0 ]; then
|
||||
echo "No add-on changes that require a version bump."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
FAILED=0
|
||||
for addon in $(printf '%s\n' "${!NEEDS_BUMP[@]}" | sort); do
|
||||
base_cfg=$(addon_config_at "$BASE_SHA" "$addon")
|
||||
# Resolved separately at HEAD: an add-on that renames its manifest between
|
||||
# supported names (config.yaml -> config.yml) while changing shipped files
|
||||
# would otherwise be read at the old path, come back empty, and slip through.
|
||||
if ! head_cfg=$(addon_config_at "$HEAD_SHA" "$addon"); then
|
||||
echo " $addon: removed by this PR, skipping"
|
||||
continue
|
||||
fi
|
||||
old=$(version_at "$BASE_SHA" "$addon/$base_cfg" || true)
|
||||
new=$(version_at "$HEAD_SHA" "$addon/$head_cfg" || true)
|
||||
# Fail closed. An unreadable version used to warn and skip, which let the
|
||||
# job go green on exactly the add-ons whose manifest this check could not
|
||||
# understand — the opposite of what it is for.
|
||||
if [ -z "$old" ] || [ -z "$new" ]; then
|
||||
echo "::error file=$addon/$head_cfg::$addon: could not read a version from $base_cfg (base) or $head_cfg (head). Refusing to pass a check that could not be performed."
|
||||
FAILED=1
|
||||
continue
|
||||
fi
|
||||
if [ "$old" = "$new" ]; then
|
||||
echo "::error file=$addon/$head_cfg::$addon ships changed files but version is still $old. Supervisor only offers a rebuild when version changes, so this would merge without reaching anyone. Bump it following this add-on's own convention: for a local patch counter take the boundary from updater.json's upstream_version (append .1 when version equals it, otherwise increment the digits after it); date-based and LSIO-style versions have no counter and follow their own scheme."
|
||||
FAILED=1
|
||||
else
|
||||
echo " $addon: $old -> $new"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$FAILED" -ne 0 ]; then
|
||||
echo "::error::One or more add-ons changed without a version bump. Add the 'skip-version-check' label and re-run this job if that is deliberate."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All changed add-ons have a version bump."
|
||||
63
.github/workflows/onpr_check-pr.yaml
vendored
63
.github/workflows/onpr_check-pr.yaml
vendored
@@ -58,6 +58,69 @@ jobs:
|
||||
echo "Changed addons: $changed_addons"
|
||||
echo "changed_addons=$changed_addons" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# 1b. A pull request that changes what an add-on ships must bump that add-on's
|
||||
# version. Supervisor only offers a rebuild when `version` changes, so without
|
||||
# one the fix merges and no user ever receives it - inert, while the issue
|
||||
# looks closed. Nothing else in CI checks this (the add-on linter validates
|
||||
# the schema, not that the value moved).
|
||||
#
|
||||
# Deliberately independent of check-addon-changes above: that job derives
|
||||
# `changedAddons` from `^<addon>/config.(json|ya?ml)$` alone, so it is empty
|
||||
# for precisely the pull requests this catches - an add-on whose scripts
|
||||
# changed while config.yaml did not. Gating this on its output would make it
|
||||
# a no-op. It does its own scan and changes nothing about which add-ons are
|
||||
# linted or built.
|
||||
check-version-bump:
|
||||
name: Check add-on version bumped
|
||||
if: ${{ github.repository_owner == 'alexbelgium' }}
|
||||
runs-on: ubuntu-latest
|
||||
# Only reads git history and the PR's labels.
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
steps:
|
||||
# The bypass label is read LIVE here rather than from the job's `if:`.
|
||||
# The event payload is a snapshot from trigger time, and this workflow
|
||||
# deliberately does not listen for `labeled` — adding that activity type
|
||||
# would re-run the ~3 h add-on builds on every label change. Reading it
|
||||
# at run time instead means "label the PR, then re-run this one job"
|
||||
# works, which is the sequence the failure message asks for.
|
||||
- name: Check for the bypass label
|
||||
id: bypass
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR: ${{ github.event.pull_request.number }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
skip=$(gh pr view "$PR" --repo "$REPO" --json labels \
|
||||
--jq '[.labels[].name] | index("skip-version-check") != null')
|
||||
echo "skip=$skip" >> "$GITHUB_OUTPUT"
|
||||
[ "$skip" = "true" ] && echo "skip-version-check label present; skipping." || true
|
||||
|
||||
- name: Checkout repo
|
||||
if: steps.bypass.outputs.skip != 'true'
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
# Same reason as check-addon-changes: HEAD^1 must be resolvable.
|
||||
fetch-depth: 2
|
||||
# Nothing here pushes, and the repo is public, so an anonymous fetch
|
||||
# of the base commit is enough - do not leave a token in .git/config.
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check every changed add-on bumped its version
|
||||
if: steps.bypass.outputs.skip != 'true'
|
||||
env:
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# HEAD^1, not pull_request.base.sha, for the same reason as above: the
|
||||
# event payload's base can be stale if master advanced since trigger.
|
||||
BASE_SHA=$(git rev-parse HEAD^1)
|
||||
git fetch origin "$BASE_SHA"
|
||||
export BASE_SHA
|
||||
bash .github/scripts/check_version_bump.sh
|
||||
|
||||
check-changed-changelog:
|
||||
name: Check if CHANGELOG.md changed
|
||||
needs: check-addon-changes
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
|
||||
## 0.6.27.1 (2026-08-23)
|
||||
- Fix: Ingress login was rejected since 0.6.27, which only accepts the reverse proxy auth header from trusted source addresses. The addon now adds its own ip to that list (https://github.com/alexbelgium/hassio-addons/issues/3003)
|
||||
|
||||
## 0.6.27 (2026-08-13)
|
||||
- Update to latest version from linuxserver/docker-calibre-web (changelog : https://github.com/linuxserver/docker-calibre-web/releases)
|
||||
|
||||
|
||||
@@ -116,5 +116,5 @@ schema:
|
||||
slug: calibre-web
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons/tree/master/calibre_web
|
||||
version: "0.6.27"
|
||||
version: "0.6.27.1"
|
||||
video: true
|
||||
|
||||
@@ -18,6 +18,26 @@ if [ ! -f /config/app.db ]; then
|
||||
bashio::log.warning "First boot : disabling Ingress until addon restart"
|
||||
else
|
||||
sqlite3 /config/app.db 'update settings set config_reverse_proxy_login_header_name="X-WebAuth-User",config_allow_reverse_proxy_header_login=1'
|
||||
|
||||
# Calibre-web 0.6.27 only accepts the ingress auth header from a trusted source address, and
|
||||
# defaults that list to "127.0.0.1,::1". Nginx binds its upstream socket to the addon ip
|
||||
# (proxy_bind $server_addr in ingress.conf) and calibre-web listens dual-stack, so it sees
|
||||
# ::ffff:<addon ip> and drops the header. Both the plain and the ipv4-mapped forms are listed
|
||||
# because an ipv4 entry never matches an ipv6-mapped address on the calibre-web side.
|
||||
# The column only exists once calibre-web 0.6.27+ has migrated app.db, so a failure here is
|
||||
# not fatal : the next start applies it.
|
||||
addon_ip=$(bashio::addon.ip_address)
|
||||
trusted_ips="127.0.0.1,::1,::ffff:127.0.0.1"
|
||||
if bashio::var.has_value "${addon_ip}"; then
|
||||
trusted_ips="${trusted_ips},${addon_ip},::ffff:${addon_ip}"
|
||||
fi
|
||||
trusted_ips_error=$(sqlite3 /config/app.db "update settings set config_reverse_proxy_trusted_ips='${trusted_ips}'" 2>&1) || {
|
||||
if echo "${trusted_ips_error}" | grep -q "no such column"; then
|
||||
bashio::log.warning "Could not set the ingress trusted ip list, it will be applied at next start"
|
||||
else
|
||||
bashio::log.warning "Could not set the ingress trusted ip list: ${trusted_ips_error}"
|
||||
fi
|
||||
}
|
||||
fi
|
||||
|
||||
bashio::log.info "Default username:password is admin:admin123"
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
## 2025.12.7 (2026-08-23)
|
||||
|
||||
- Fix: Docker reported the addon as `unhealthy` in Portainer. The healthcheck script could never run because its shebang required the s6-overlay environment, which this addon's entrypoint does not set up (https://github.com/alexbelgium/hassio-addons/issues/3002)
|
||||
- Remove the unused and broken `wait-for-signal` script; nothing invoked it
|
||||
|
||||
## 2025.12.6 (2026-08-01)
|
||||
|
||||
- Version renamed from `2025.12-6`, which Home Assistant could not order and therefore could not reliably offer as an update: every number of the previous version is kept, as a section of its own. The addon itself and the upstream version it tracks are unchanged
|
||||
|
||||
@@ -81,8 +81,7 @@ RUN chmod 777 /ha_entrypoint.sh
|
||||
COPY bashio-standalone.sh /usr/local/lib/bashio-standalone.sh
|
||||
RUN chmod 0755 /usr/local/lib/bashio-standalone.sh
|
||||
|
||||
RUN chmod a+x /usr/sbin/healthcheck && \
|
||||
chmod a+x /usr/sbin/wait-for-signal
|
||||
RUN chmod a+x /usr/sbin/healthcheck
|
||||
WORKDIR "/app"
|
||||
|
||||
ENTRYPOINT [ "/usr/bin/env" ]
|
||||
|
||||
@@ -41,4 +41,4 @@ schema:
|
||||
slug: portainer_agent
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "2025.12.6"
|
||||
version: "2025.12.7"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck shell=bash
|
||||
# Plain bash on purpose : ha_entrypoint.sh replaces s6-overlay's /init, so
|
||||
# /run/s6/container_environment is never created and a with-contenv shebang
|
||||
# would fail before this script runs. Nothing here needs the s6 environment.
|
||||
|
||||
if [ -f "/tmp/healthcheck-signal" ]; then
|
||||
curl -k --fail https://127.0.0.1:9001/ping || exit 1
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
|
||||
START_TIME="$(date +%s)"
|
||||
|
||||
echo "Waiting for first healthcheck execution (max $FIRST_HEALTHCHECK_TIMEOUT seconds)..."
|
||||
|
||||
while [ ! -f "/tmp/healthcheck-signal" ];
|
||||
do
|
||||
sleep 1
|
||||
|
||||
ELAPSED_TIME="$(expr \date +%s\ - $START_TIME)"
|
||||
|
||||
if [ $ELAPSED_TIME -gt $FIRST_HEALTHCHECK_TIMEOUT ]
|
||||
then
|
||||
echo "Signal from healthcheck not received in time, failing the execution..."
|
||||
exit 20
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Signal from healthcheck received"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user