From 500c083c5ea8f4c19781621b018f6800e89367f5 Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Sat, 12 Sep 2026 16:49:40 +0200 Subject: [PATCH] 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 --- .gitignore | 2 + addons_updater/CHANGELOG.md | 4 ++ addons_updater/README.md | 38 ++++++++++++- addons_updater/config.yaml | 2 +- .../rootfs/etc/cont-init.d/99-run.sh | 57 ++++++++++++++++++- immich/CHANGELOG.md | 6 ++ immich/config.yaml | 2 +- immich/updater.json | 9 ++- immich_cuda/CHANGELOG.md | 6 ++ immich_cuda/config.yaml | 2 +- immich_cuda/updater.json | 9 ++- immich_noml/CHANGELOG.md | 6 ++ immich_noml/config.yaml | 2 +- immich_noml/updater.json | 9 ++- immich_openvino/CHANGELOG.md | 6 ++ immich_openvino/config.yaml | 2 +- immich_openvino/updater.json | 9 ++- 17 files changed, 142 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index e69de29bb2..2baffd48d3 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,2 @@ +# Bytecode of the python helpers in the addons, which rootfs/ copies into the image +__pycache__/ diff --git a/addons_updater/CHANGELOG.md b/addons_updater/CHANGELOG.md index 4ab191b93c..86a76f02cb 100644 --- a/addons_updater/CHANGELOG.md +++ b/addons_updater/CHANGELOG.md @@ -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 diff --git a/addons_updater/README.md b/addons_updater/README.md index defdca69e7..830019047d 100644 --- a/addons_updater/README.md +++ b/addons_updater/README.md @@ -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. diff --git a/addons_updater/config.yaml b/addons_updater/config.yaml index 4f30e91d92..cd3efa5582 100644 --- a/addons_updater/config.yaml +++ b/addons_updater/config.yaml @@ -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" diff --git a/addons_updater/rootfs/etc/cont-init.d/99-run.sh b/addons_updater/rootfs/etc/cont-init.d/99-run.sh index 4f9bc65942..b434acd499 100755 --- a/addons_updater/rootfs/etc/cont-init.d/99-run.sh +++ b/addons_updater/rootfs/etc/cont-init.d/99-run.sh @@ -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 diff --git a/immich/CHANGELOG.md b/immich/CHANGELOG.md index f073d1b5c9..96f10911e2 100644 --- a/immich/CHANGELOG.md +++ b/immich/CHANGELOG.md @@ -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) diff --git a/immich/config.yaml b/immich/config.yaml index 4704f2a38e..ace48332ab 100644 --- a/immich/config.yaml +++ b/immich/config.yaml @@ -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] diff --git a/immich/updater.json b/immich/updater.json index 053e25801e..dc6011d28b 100644 --- a/immich/updater.json +++ b/immich/updater.json @@ -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" } diff --git a/immich_cuda/CHANGELOG.md b/immich_cuda/CHANGELOG.md index 35c6c5b44c..974e01ae70 100644 --- a/immich_cuda/CHANGELOG.md +++ b/immich_cuda/CHANGELOG.md @@ -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) diff --git a/immich_cuda/config.yaml b/immich_cuda/config.yaml index 9b17ba3b2a..5a685f15f8 100644 --- a/immich_cuda/config.yaml +++ b/immich_cuda/config.yaml @@ -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] diff --git a/immich_cuda/updater.json b/immich_cuda/updater.json index 053e25801e..af3a6ed506 100644 --- a/immich_cuda/updater.json +++ b/immich_cuda/updater.json @@ -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" } diff --git a/immich_noml/CHANGELOG.md b/immich_noml/CHANGELOG.md index 29b8713dba..14a06b88e1 100644 --- a/immich_noml/CHANGELOG.md +++ b/immich_noml/CHANGELOG.md @@ -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) diff --git a/immich_noml/config.yaml b/immich_noml/config.yaml index 504f73cebb..28eca89f1d 100644 --- a/immich_noml/config.yaml +++ b/immich_noml/config.yaml @@ -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] diff --git a/immich_noml/updater.json b/immich_noml/updater.json index 053e25801e..d14294a30c 100644 --- a/immich_noml/updater.json +++ b/immich_noml/updater.json @@ -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" } diff --git a/immich_openvino/CHANGELOG.md b/immich_openvino/CHANGELOG.md index ab3b023236..1543b0c921 100644 --- a/immich_openvino/CHANGELOG.md +++ b/immich_openvino/CHANGELOG.md @@ -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) diff --git a/immich_openvino/config.yaml b/immich_openvino/config.yaml index 1e646d5e5b..dd6cf1fef2 100644 --- a/immich_openvino/config.yaml +++ b/immich_openvino/config.yaml @@ -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] diff --git a/immich_openvino/updater.json b/immich_openvino/updater.json index 053e25801e..db1e3c4328 100644 --- a/immich_openvino/updater.json +++ b/immich_openvino/updater.json @@ -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" }