mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-10-02 22:42:42 +02:00
Compare commits
2 Commits
ci/version
...
f70fbe9705
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f70fbe9705 | ||
|
|
9204b31af8 |
143
.github/scripts/check_version_bump.sh
vendored
143
.github/scripts/check_version_bump.sh
vendored
@@ -1,143 +0,0 @@
|
||||
#!/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,69 +58,6 @@ 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.2 (2026-08-23)
|
||||
- Fix: trust the whole supervisor network range for the ingress auth header instead of the addon's own address, which changes across restarts. The list is only written when that range is missing, so an entry added in the calibre-web admin page is no longer erased on every start (https://github.com/alexbelgium/hassio-addons/pull/3010)
|
||||
|
||||
## 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)
|
||||
|
||||
|
||||
@@ -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.1"
|
||||
version: "0.6.27.2"
|
||||
video: true
|
||||
|
||||
@@ -19,25 +19,19 @@ if [ ! -f /config/app.db ]; then
|
||||
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
|
||||
}
|
||||
# Calibre-web 0.6.27 only accepts that header from a trusted source address, and defaults the
|
||||
# list to "127.0.0.1,::1". Ingress reaches calibre-web from the addon's own address on the
|
||||
# supervisor network (proxy_bind $server_addr in ingress.conf) and calibre-web listens
|
||||
# dual-stack, so it sees ::ffff:<addon ip> and drops the header. The supervisor range is
|
||||
# listed in both forms because an ipv4 entry never matches an ipv4-mapped address.
|
||||
# Prepended to whatever is already there, and only when the mapped form is missing : that form
|
||||
# is the one ingress needs and the one nobody types by hand, so it doubles as the marker that
|
||||
# this already ran. Anything the user added is kept, the statement runs at most once, and the
|
||||
# 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})"
|
||||
fi
|
||||
|
||||
bashio::log.info "Default username:password is admin:admin123"
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
## 2.44.0 (2026-08-23)
|
||||
|
||||
- Fix: Use `portainer/agent:alpine-sts` to match the STS release channel configured in `updater.json`
|
||||
- Fix: Align add-on versioning with Portainer STS releases
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
ARG BUILD_FROM
|
||||
|
||||
# Get agent
|
||||
FROM portainer/agent:alpine as original_agent
|
||||
FROM portainer/agent:alpine-sts as original_agent
|
||||
ENV PORTAINER_AGENT_ARGS=""
|
||||
|
||||
# Build using base
|
||||
|
||||
@@ -41,4 +41,4 @@ schema:
|
||||
slug: portainer_agent
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "2025.12.7"
|
||||
version: "2.44.0"
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"github_tagfilter": "alpine",
|
||||
"last_update": "24-12-2025",
|
||||
"github_tagfilter": "sts",
|
||||
"last_update": "23-08-2026",
|
||||
"repository": "alexbelgium/hassio-addons",
|
||||
"slug": "portainer_agent",
|
||||
"source": "dockerhub",
|
||||
|
||||
Reference in New Issue
Block a user