fix(updater): publish the version the image holds, not the app's newest release (#3061)

The immich addons do not build immich, they build FROM
ghcr.io/imagegenius/immich, a rebuild that lags immich upstream by days to
weeks. updater.json tracked immich-app/immich, so the addon was published as
3.2.0 two days after that release while the image behind the floating :3 tag
still carried immich 3.1.0 (issue #3060), and because the stored upstream
version already read 3.2.0, nothing would have rebuilt the addon once the
image caught up.

Add source: container, which resolves the exact image reference the addon
builds from and reads org.opencontainers.image.version out of it, and point
the four immich addons at it. The manifest digest is recorded alongside the
version so that a publisher republishing the same version under new content
is not mistaken for no change. An image with nothing readable leaves the
addon untouched.

Written as inline curl and jq, in the style of the dockerhub branch above it.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Alexandre
2026-09-12 16:49:40 +02:00
committed by GitHub
parent e06b6d1c68
commit 500c083c5e
17 changed files with 142 additions and 29 deletions

2
.gitignore vendored
View File

@@ -0,0 +1,2 @@
# Bytecode of the python helpers in the addons, which rootfs/ copies into the image
__pycache__/

View File

@@ -1,4 +1,8 @@
- Migrate legacy add-on configuration map names to current app configuration terminology.
## 2026.09 (2026-09-12)
- New `source: container` for addons that build `FROM` an image published by someone else. The version is read from the `org.opencontainers.image.version` label of the exact image reference in `updater.json`, instead of from the releases of the application's own repository. Written against `ghcr.io`; an image with no such label leaves the addon untouched
- `source: container` also records the manifest digest in `upstream_digest` and rebuilds when it moves, so a publisher republishing the same version under a new image is no longer invisible
## 2026.08 (2026-08-01)
- Addon versions written in config.yaml now always comply with Home Assistant versioning: an upstream tag Home Assistant cannot order (`version-bf9e0b4f`, `ubuntu-2026-06-01`, ...) or would sort as older (`1.2.3-2`, `1.2.3+4`) no longer lands in config.yaml. The addon number is incremented instead, while the raw upstream tag stays in updater.json so the same release is never published twice

View File

@@ -67,12 +67,46 @@ You can add the following tags in the file :
- repository: 'name/repo' coming from github
- paused: true # Pauses the updates
- slug: the slug name from your addon
- source: dockerhub/github,gitlab,bitbucket,pip,hg,sf,website-feed,local,helm_chart,wiki,system,wp,codeberg (Codeberg is supported via its Gitea API, which is configured automatically)
- upstream_repo: name/repo, example is 'linuxserver/docker-emby'
- source: container/dockerhub/github,gitlab,bitbucket,pip,hg,sf,website-feed,local,helm_chart,wiki,system,wp,codeberg (Codeberg is supported via its Gitea API, which is configured automatically)
- upstream_repo: name/repo, example is 'linuxserver/docker-emby'. With `source: container` it is instead a full image reference, example is 'ghcr.io/imagegenius/immich:3'
- upstream_version: automatically populated, corresponds to the current upstream version referenced in the addon
- dockerhub_by_date: in dockerhub, uses the last_update date instead of the version
- dockerhub_list_size: in dockerhub, how many containers to consider for latest version
### Addons built on someone else's image
An addon that does not build the application itself, but builds `FROM` an image a third party
publishes, must publish the version that image holds and not the newest release of the
application. `ghcr.io/imagegenius/immich:3` is a rebuild of immich lagging its upstream by days
to weeks: tracking `immich-app/immich` published a version the addon did not contain, and left
the addon with no reason to rebuild once the image finally caught up.
`source: container` reads the version out of the image itself. Set `upstream_repo` to the exact
image reference used in `build.json`, tag included:
```json
{
"source": "container",
"upstream_repo": "ghcr.io/imagegenius/immich:3"
}
```
The version comes from the image's `org.opencontainers.image.version` label, read from the first
linux image the tag publishes, and the addon is left alone when there is no label to read. Written
against `ghcr.io`, which serves anonymous pull tokens from `https://ghcr.io/token`; a registry
authenticating differently, such as Docker Hub, yields no version and the addon is skipped.
The manifest digest is recorded next to the version, in `upstream_digest`, and the addon is
rebuilt when either of the two moves. A publisher rebuilding the same version, for a base image
security fix or a packaging revision, repoints the tag at new content under an unchanged label,
and the digest is the only thing that says so. `upstream_digest` is populated automatically; seed
it by hand when migrating an addon to this source, or the first run counts the unknown digest as
a change and rebuilds once for nothing.
The label is metadata the publisher chooses. Some images carry none, and some record their own
packaging revision rather than the application version, so this source is opt-in per addon and
never a default.
### 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.

View File

@@ -30,4 +30,4 @@ schema:
slug: updater
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/addons_updater
version: "2026.08"
version: "2026.09"

View File

@@ -109,6 +109,7 @@ for f in */; do
EXCLUDE_TEXT="${EXCLUDE_TEXT:-zzzzzzzzzzzzzzzz}"
PAUSED=$(jq -r .paused updater.json)
DATE="$(date "$DATE_FORMAT")"
LASTDIGEST=""
BYDATE=$(jq -r .dockerhub_by_date updater.json)
# Number of elements to check in dockerhub
@@ -195,6 +196,45 @@ for f in */; do
&& LASTVERSION="$LASTVERSION-$DATE"
LOGINFO="... $SLUG : bydate is true, version is $LASTVERSION" && if [ "$VERBOSE" = true ]; then bashio::log.info "$LOGINFO"; fi
elif [[ "$SOURCE" = container ]]; then
# The addon builds FROM an image someone else publishes rather
# than from the application itself, so the version to publish is
# the one inside that image and not the newest release of the
# application repository, which the image can lag by weeks
LOGINFO="... Source is container" && if [ "$VERBOSE" = true ]; then bashio::log.info "$LOGINFO"; fi
ACCEPT="application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.v2+json"
REGISTRY="${UPSTREAM%%/*}"
IMAGE="${UPSTREAM#*/}"
IMAGEREPO="${IMAGE%:*}"
BASEURL="https://$REGISTRY/v2/$IMAGEREPO"
HEADERS="$(mktemp)"
# The digest the tag resolves to is the one the registry answers
# with, and it moves whenever anything behind the tag does
TOKEN="$(curl -f -L -s "https://$REGISTRY/token?scope=repository:$IMAGEREPO:pull&service=$REGISTRY" | jq -r '.token // empty')" || true
MANIFEST="$(curl -f -L -s -D "$HEADERS" -H "Authorization: Bearer $TOKEN" -H "Accept: $ACCEPT" "$BASEURL/manifests/${IMAGE##*:}")" || true
LASTDIGEST="$(sed -n 's/^[Dd]ocker-[Cc]ontent-[Dd]igest:[[:space:]]*//p' "$HEADERS" | tr -d '\r')"
rm -f "$HEADERS"
# A multi architecture tag answers with an index, whose first
# image is read: the attestations buildx attaches alongside
# describe no architecture and hold no image configuration
CHILD="$(echo "$MANIFEST" | jq -r 'first(.manifests[]? | select(.platform.os == "linux" and .platform.architecture != "unknown") | .digest) // empty')" || true
if [ -n "$CHILD" ]; then
MANIFEST="$(curl -f -L -s -H "Authorization: Bearer $TOKEN" -H "Accept: $ACCEPT" "$BASEURL/manifests/$CHILD")" || true
fi
# Publishers record the application version of an image in its
# OCI label, which is what the addon actually ships
LASTVERSION="$(curl -f -L -s -H "Authorization: Bearer $TOKEN" "$BASEURL/blobs/$(echo "$MANIFEST" | jq -r '.config.digest // empty')" \
| jq -r '.config.Labels["org.opencontainers.image.version"] // empty')" || true
if [ -z "$LASTVERSION" ] || [ -z "$LASTDIGEST" ]; then
bashio::log.warning "... $SLUG : no version could be read from $UPSTREAM, skipping"
continue
fi
else
# Use source as upstream
@@ -344,8 +384,19 @@ for f in */; do
continue
fi
# What the addon was last built against. A container source adds the
# digest of the image: a publisher rebuilding the same version, for a
# base image fix or a packaging revision, repoints the tag at new
# content under an unchanged label, and only the digest says so
CURRENTSTATE="$CURRENT"
LASTSTATE="$LASTVERSION"
if [[ "$SOURCE" = container ]]; then
CURRENTSTATE="$CURRENT $(jq -r '.upstream_digest // ""' updater.json)"
LASTSTATE="$LASTVERSION $LASTDIGEST"
fi
# Update if needed
if [ "${CURRENT}" != "${LASTVERSION}" ]; then
if [ "${CURRENTSTATE}" != "${LASTSTATE}" ]; then
LOGINFO="... $SLUG : update from ${CURRENT} to ${LASTVERSION}" && if [ "$VERBOSE" = true ]; then bashio::log.info "$LOGINFO"; fi
ADDONFOLDER="/data/${BASENAME}/${SLUG}"
@@ -405,7 +456,9 @@ for f in */; do
# Replace upstream tag and date, keeping the file intact if jq
# fails as a truncated updater.json would lose the addon source
if ! UPDATERJSON="$(jq --arg version "$LASTVERSION" --arg date "$DATE" '.upstream_version = $version | .last_update = $date' "$ADDONFOLDER/updater.json")"; then
if ! UPDATERJSON="$(jq --arg version "$LASTVERSION" --arg date "$DATE" --arg digest "${LASTDIGEST:-}" \
'.upstream_version = $version | .last_update = $date | if $digest == "" then . else .upstream_digest = $digest end' \
"$ADDONFOLDER/updater.json")"; then
bashio::log.error "... $SLUG : updater.json could not be updated, reverting"
git checkout -- "$ADDONFOLDER"
continue

View File

@@ -1,3 +1,9 @@
## 3.2.0.1 (2026-09-12)
- Track the version inside `ghcr.io/imagegenius/immich:3` instead of the releases of `immich-app/immich`
- The add-on is now rebuilt whenever imagegenius repoints `:3`, whether the immich version changed or the image was simply republished
- The add-on has never built immich itself, it builds on top of the imagegenius rebuild, which lags immich upstream by days to weeks. Version `3.2.0` was published while the image still contained immich `3.1.0` (issue #3060), and nothing would have rebuilt the add-on once the image caught up
- This build still contains immich **3.1.0**, and will be updated automatically once imagegenius publishes a newer image. Home Assistant cannot offer a version lower than the one installed, so the add-on number stays in the `3.2.0.x` range until the image moves past 3.2.0
## 3.2.0 (2026-09-12)
- Update to latest version from immich-app/immich (changelog : https://github.com/immich-app/immich/releases)

View File

@@ -141,6 +141,6 @@ slug: immich
udev: true
url: https://github.com/alexbelgium/hassio-addons
usb: true
version: "3.2.0"
version: "3.2.0.1"
video: true
webui: http://[HOST]:[PORT:8080]

View File

@@ -1,10 +1,9 @@
{
"github_beta": "false",
"github_exclude": "",
"last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons",
"slug": "immich",
"source": "github",
"upstream_repo": "immich-app/immich",
"upstream_version": "3.2.0"
"source": "container",
"upstream_digest": "sha256:e618fe718bb1d62f17ebc9f908a87608f28722776d71819afe38a242ee88845f",
"upstream_repo": "ghcr.io/imagegenius/immich:3",
"upstream_version": "3.1.0"
}

View File

@@ -1,3 +1,9 @@
## 3.2.0.1 (2026-09-12)
- Track the version inside `ghcr.io/imagegenius/immich:3-cuda` instead of the releases of `immich-app/immich`
- The add-on is now rebuilt whenever imagegenius repoints `:3-cuda`, whether the immich version changed or the image was simply republished
- The add-on has never built immich itself, it builds on top of the imagegenius rebuild, which lags immich upstream by days to weeks. Version `3.2.0` was published while the image still contained immich `3.1.0` (issue #3060), and nothing would have rebuilt the add-on once the image caught up
- This build still contains immich **3.1.0**, and will be updated automatically once imagegenius publishes a newer image. Home Assistant cannot offer a version lower than the one installed, so the add-on number stays in the `3.2.0.x` range until the image moves past 3.2.0
## 3.2.0 (2026-09-12)
- Update to latest version from immich-app/immich (changelog : https://github.com/immich-app/immich/releases)

View File

@@ -139,6 +139,6 @@ slug: immich_cuda
udev: true
url: https://github.com/alexbelgium/hassio-addons
usb: true
version: "3.2.0"
version: "3.2.0.1"
video: true
webui: http://[HOST]:[PORT:8080]

View File

@@ -1,10 +1,9 @@
{
"github_beta": "false",
"github_exclude": "",
"last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons",
"slug": "immich",
"source": "github",
"upstream_repo": "immich-app/immich",
"upstream_version": "3.2.0"
"source": "container",
"upstream_digest": "sha256:c4b78d941f637e5da52bbbbc264db6e207d341e3799b87a2332613373082f1e9",
"upstream_repo": "ghcr.io/imagegenius/immich:3-cuda",
"upstream_version": "3.1.0"
}

View File

@@ -1,3 +1,9 @@
## 3.2.0.1 (2026-09-12)
- Track the version inside `ghcr.io/imagegenius/immich:3-noml` instead of the releases of `immich-app/immich`
- The add-on is now rebuilt whenever imagegenius repoints `:3-noml`, whether the immich version changed or the image was simply republished
- The add-on has never built immich itself, it builds on top of the imagegenius rebuild, which lags immich upstream by days to weeks. Version `3.2.0` was published while the image still contained immich `3.1.0` (issue #3060), and nothing would have rebuilt the add-on once the image caught up
- This build still contains immich **3.1.0**, and will be updated automatically once imagegenius publishes a newer image. Home Assistant cannot offer a version lower than the one installed, so the add-on number stays in the `3.2.0.x` range until the image moves past 3.2.0
## 3.2.0 (2026-09-12)
- Update to latest version from immich-app/immich (changelog : https://github.com/immich-app/immich/releases)

View File

@@ -140,6 +140,6 @@ slug: immich_noml
udev: true
url: https://github.com/alexbelgium/hassio-addons
usb: true
version: "3.2.0"
version: "3.2.0.1"
video: true
webui: http://[HOST]:[PORT:8080]

View File

@@ -1,10 +1,9 @@
{
"github_beta": "false",
"github_exclude": "",
"last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons",
"slug": "immich",
"source": "github",
"upstream_repo": "immich-app/immich",
"upstream_version": "3.2.0"
"source": "container",
"upstream_digest": "sha256:8e10fe2fb3e887b04b353e82e793eeb90771977edf8523fc1342a34cac710bb2",
"upstream_repo": "ghcr.io/imagegenius/immich:3-noml",
"upstream_version": "3.1.0"
}

View File

@@ -1,3 +1,9 @@
## 3.2.0.1 (2026-09-12)
- Track the version inside `ghcr.io/imagegenius/immich:3-openvino` instead of the releases of `immich-app/immich`
- The add-on is now rebuilt whenever imagegenius repoints `:3-openvino`, whether the immich version changed or the image was simply republished
- The add-on has never built immich itself, it builds on top of the imagegenius rebuild, which lags immich upstream by days to weeks. Version `3.2.0` was published while the image still contained immich `3.1.0` (issue #3060), and nothing would have rebuilt the add-on once the image caught up
- This build still contains immich **3.1.0**, and will be updated automatically once imagegenius publishes a newer image. Home Assistant cannot offer a version lower than the one installed, so the add-on number stays in the `3.2.0.x` range until the image moves past 3.2.0
## 3.2.0 (2026-09-12)
- Update to latest version from immich-app/immich (changelog : https://github.com/immich-app/immich/releases)

View File

@@ -140,6 +140,6 @@ slug: immich_openvino
udev: true
url: https://github.com/alexbelgium/hassio-addons
usb: true
version: "3.2.0"
version: "3.2.0.1"
video: true
webui: http://[HOST]:[PORT:8080]

View File

@@ -1,10 +1,9 @@
{
"github_beta": "false",
"github_exclude": "",
"last_update": "2026-09-12",
"repository": "alexbelgium/hassio-addons",
"slug": "immich",
"source": "github",
"upstream_repo": "immich-app/immich",
"upstream_version": "3.2.0"
"source": "container",
"upstream_digest": "sha256:745963b8d1d09b652c3631a22495bc11ba2bf7288644a7f86132e4a94e72bbbc",
"upstream_repo": "ghcr.io/imagegenius/immich:3-openvino",
"upstream_version": "3.1.0"
}