Compare commits

...

2 Commits

Author SHA1 Message Date
Robert Resch c9656acb27 test 2026-05-18 20:58:26 +00:00
Erik a68ad41469 Don't spin up docker container when validating go2rtc version 2026-05-18 22:04:37 +02:00
5 changed files with 509 additions and 473 deletions
+397 -397
View File
@@ -53,10 +53,10 @@ jobs:
with:
type: ${{ env.BUILD_TYPE }}
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master # zizmor: ignore[unpinned-uses]
with:
ignore-dev: true
# - name: Verify version
# uses: home-assistant/actions/helpers/verify-version@master # zizmor: ignore[unpinned-uses]
# with:
# ignore-dev: true
- name: Fail if translations files are checked in
run: |
@@ -66,442 +66,442 @@ jobs:
exit 1
fi
- name: Download Translations
run: python3 -m script.translations download
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} # zizmor: ignore[secrets-outside-env]
# - name: Download Translations
# run: python3 -m script.translations download
# env:
# LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} # zizmor: ignore[secrets-outside-env]
- name: Archive translations
shell: bash
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
# - name: Archive translations
# shell: bash
# run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
- name: Upload translations
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: translations
path: translations.tar.gz
if-no-files-found: error
# - name: Upload translations
# uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
# with:
# name: translations
# path: translations.tar.gz
# if-no-files-found: error
build_base:
name: Build ${{ matrix.arch }} base core image
if: github.repository_owner == 'home-assistant'
needs: init
runs-on: ${{ matrix.os }}
permissions:
contents: read # To check out the repository
packages: write # To push to GHCR
id-token: write # For cosign signing
strategy:
fail-fast: false
matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
include:
- arch: amd64
os: ubuntu-24.04
- arch: aarch64
os: ubuntu-24.04-arm
steps:
- name: Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# build_base:
# name: Build ${{ matrix.arch }} base core image
# if: github.repository_owner == 'home-assistant'
# needs: init
# runs-on: ${{ matrix.os }}
# permissions:
# contents: read # To check out the repository
# packages: write # To push to GHCR
# id-token: write # For cosign signing
# strategy:
# fail-fast: false
# matrix:
# arch: ${{ fromJson(needs.init.outputs.architectures) }}
# include:
# - arch: amd64
# os: ubuntu-24.04
# - arch: aarch64
# os: ubuntu-24.04-arm
# steps:
# - name: Checkout the repository
# uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# with:
# persist-credentials: false
- name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/frontend
branch: dev
workflow: nightly.yaml
workflow_conclusion: success
name: wheels
# - name: Download nightly wheels of frontend
# if: needs.init.outputs.channel == 'dev'
# uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
# with:
# github_token: ${{secrets.GITHUB_TOKEN}}
# repo: home-assistant/frontend
# branch: dev
# workflow: nightly.yaml
# workflow_conclusion: success
# name: wheels
- name: Download nightly wheels of intents
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: OHF-Voice/intents-package
branch: main
workflow: nightly.yaml
workflow_conclusion: success
name: package
# - name: Download nightly wheels of intents
# if: needs.init.outputs.channel == 'dev'
# uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
# with:
# github_token: ${{secrets.GITHUB_TOKEN}}
# repo: OHF-Voice/intents-package
# branch: main
# workflow: nightly.yaml
# workflow_conclusion: success
# name: package
- name: Set up Python
if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version-file: ".python-version"
# - name: Set up Python
# if: needs.init.outputs.channel == 'dev'
# uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
# with:
# python-version-file: ".python-version"
- name: Adjust nightly version
if: needs.init.outputs.channel == 'dev'
shell: bash
env:
UV_PRERELEASE: allow
VERSION: ${{ needs.init.outputs.version }}
run: |
python3 -m pip install "$(grep '^uv' < requirements.txt)"
uv pip install packaging tomli
uv pip install .
python3 script/version_bump.py nightly --set-nightly-version "${VERSION}"
# - name: Adjust nightly version
# if: needs.init.outputs.channel == 'dev'
# shell: bash
# env:
# UV_PRERELEASE: allow
# VERSION: ${{ needs.init.outputs.version }}
# run: |
# python3 -m pip install "$(grep '^uv' < requirements.txt)"
# uv pip install packaging tomli
# uv pip install .
# python3 script/version_bump.py nightly --set-nightly-version "${VERSION}"
if [[ "$(ls home_assistant_frontend*.whl)" =~ ^home_assistant_frontend-(.*)-py3-none-any.whl$ ]]; then
echo "Found frontend wheel, setting version to: ${BASH_REMATCH[1]}"
frontend_version="${BASH_REMATCH[1]}" yq \
--inplace e -o json \
'.requirements = ["home-assistant-frontend=="+env(frontend_version)]' \
homeassistant/components/frontend/manifest.json
# if [[ "$(ls home_assistant_frontend*.whl)" =~ ^home_assistant_frontend-(.*)-py3-none-any.whl$ ]]; then
# echo "Found frontend wheel, setting version to: ${BASH_REMATCH[1]}"
# frontend_version="${BASH_REMATCH[1]}" yq \
# --inplace e -o json \
# '.requirements = ["home-assistant-frontend=="+env(frontend_version)]' \
# homeassistant/components/frontend/manifest.json
sed -i "s|home-assistant-frontend==.*|home-assistant-frontend==${BASH_REMATCH[1]}|" \
homeassistant/package_constraints.txt
# sed -i "s|home-assistant-frontend==.*|home-assistant-frontend==${BASH_REMATCH[1]}|" \
# homeassistant/package_constraints.txt
sed -i "s|home-assistant-frontend==.*||" requirements_all.txt
fi
# sed -i "s|home-assistant-frontend==.*||" requirements_all.txt
# fi
if [[ "$(ls home_assistant_intents*.whl)" =~ ^home_assistant_intents-(.*)-py3-none-any.whl$ ]]; then
echo "Found intents wheel, setting version to: ${BASH_REMATCH[1]}"
yq \
--inplace e -o json \
'del(.requirements[] | select(contains("home-assistant-intents")))' \
homeassistant/components/conversation/manifest.json
# if [[ "$(ls home_assistant_intents*.whl)" =~ ^home_assistant_intents-(.*)-py3-none-any.whl$ ]]; then
# echo "Found intents wheel, setting version to: ${BASH_REMATCH[1]}"
# yq \
# --inplace e -o json \
# 'del(.requirements[] | select(contains("home-assistant-intents")))' \
# homeassistant/components/conversation/manifest.json
intents_version="${BASH_REMATCH[1]}" yq \
--inplace e -o json \
'.requirements += ["home-assistant-intents=="+env(intents_version)]' \
homeassistant/components/conversation/manifest.json
# intents_version="${BASH_REMATCH[1]}" yq \
# --inplace e -o json \
# '.requirements += ["home-assistant-intents=="+env(intents_version)]' \
# homeassistant/components/conversation/manifest.json
sed -i "s|home-assistant-intents==.*|home-assistant-intents==${BASH_REMATCH[1]}|" \
homeassistant/package_constraints.txt
# sed -i "s|home-assistant-intents==.*|home-assistant-intents==${BASH_REMATCH[1]}|" \
# homeassistant/package_constraints.txt
sed -i "s|home-assistant-intents==.*||" requirements_all.txt requirements.txt
fi
# sed -i "s|home-assistant-intents==.*||" requirements_all.txt requirements.txt
# fi
- name: Download translations
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: translations
# - name: Download translations
# uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# with:
# name: translations
- name: Extract translations
run: |
tar xvf translations.tar.gz
rm translations.tar.gz
# - name: Extract translations
# run: |
# tar xvf translations.tar.gz
# rm translations.tar.gz
- name: Write meta info file
shell: bash
run: |
echo "${GITHUB_SHA};${GITHUB_REF};${GITHUB_EVENT_NAME};${GITHUB_ACTOR}" > rootfs/OFFICIAL_IMAGE
# - name: Write meta info file
# shell: bash
# run: |
# echo "${GITHUB_SHA};${GITHUB_REF};${GITHUB_EVENT_NAME};${GITHUB_ACTOR}" > rootfs/OFFICIAL_IMAGE
- name: Build base image
uses: home-assistant/builder/actions/build-image@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
with:
arch: ${{ matrix.arch }}
build-args: |
BUILD_FROM=ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant-base:${{ needs.init.outputs.base_image_version }}
cache-gha: false
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
cosign-base-identity: "https://github.com/home-assistant/docker/.*"
cosign-base-verify: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant-base:${{ needs.init.outputs.base_image_version }}
image: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant
image-tags: ${{ needs.init.outputs.version }}
push: true
version: ${{ needs.init.outputs.version }}
# - name: Build base image
# uses: home-assistant/builder/actions/build-image@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
# with:
# arch: ${{ matrix.arch }}
# build-args: |
# BUILD_FROM=ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant-base:${{ needs.init.outputs.base_image_version }}
# cache-gha: false
# container-registry-password: ${{ secrets.GITHUB_TOKEN }}
# cosign-base-identity: "https://github.com/home-assistant/docker/.*"
# cosign-base-verify: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant-base:${{ needs.init.outputs.base_image_version }}
# image: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant
# image-tags: ${{ needs.init.outputs.version }}
# push: true
# version: ${{ needs.init.outputs.version }}
build_machine:
name: Build ${{ matrix.machine }} machine core image
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"]
runs-on: ${{ matrix.runs-on }}
permissions:
contents: read # To check out the repository
packages: write # To push to GHCR
id-token: write # For cosign signing
strategy:
matrix:
machine:
- generic-x86-64
- khadas-vim3
- odroid-c2
- odroid-c4
- odroid-m1
- odroid-n2
- qemuarm-64
- qemux86-64
- raspberrypi3-64
- raspberrypi4-64
- raspberrypi5-64
- yellow
- green
include:
# Default: aarch64 on native ARM runner
- arch: aarch64
runs-on: ubuntu-24.04-arm
# Overrides for amd64 machines
- machine: generic-x86-64
arch: amd64
runs-on: ubuntu-24.04
- machine: qemux86-64
arch: amd64
runs-on: ubuntu-24.04
steps:
- name: Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# build_machine:
# name: Build ${{ matrix.machine }} machine core image
# if: github.repository_owner == 'home-assistant'
# needs: ["init", "build_base"]
# runs-on: ${{ matrix.runs-on }}
# permissions:
# contents: read # To check out the repository
# packages: write # To push to GHCR
# id-token: write # For cosign signing
# strategy:
# matrix:
# machine:
# - generic-x86-64
# - khadas-vim3
# - odroid-c2
# - odroid-c4
# - odroid-m1
# - odroid-n2
# - qemuarm-64
# - qemux86-64
# - raspberrypi3-64
# - raspberrypi4-64
# - raspberrypi5-64
# - yellow
# - green
# include:
# # Default: aarch64 on native ARM runner
# - arch: aarch64
# runs-on: ubuntu-24.04-arm
# # Overrides for amd64 machines
# - machine: generic-x86-64
# arch: amd64
# runs-on: ubuntu-24.04
# - machine: qemux86-64
# arch: amd64
# runs-on: ubuntu-24.04
# steps:
# - name: Checkout the repository
# uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# with:
# persist-credentials: false
- name: Compute extra tags
id: tags
shell: bash
env:
VERSION: ${{ needs.init.outputs.version }}
run: |
if [[ "${VERSION}" =~ d ]]; then
echo "extra_tags=dev" >> "$GITHUB_OUTPUT"
elif [[ "${VERSION}" =~ b ]]; then
echo "extra_tags=beta" >> "$GITHUB_OUTPUT"
else
echo "extra_tags=stable" >> "$GITHUB_OUTPUT"
fi
# - name: Compute extra tags
# id: tags
# shell: bash
# env:
# VERSION: ${{ needs.init.outputs.version }}
# run: |
# if [[ "${VERSION}" =~ d ]]; then
# echo "extra_tags=dev" >> "$GITHUB_OUTPUT"
# elif [[ "${VERSION}" =~ b ]]; then
# echo "extra_tags=beta" >> "$GITHUB_OUTPUT"
# else
# echo "extra_tags=stable" >> "$GITHUB_OUTPUT"
# fi
- name: Build machine image
uses: home-assistant/builder/actions/build-image@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
with:
arch: ${{ matrix.arch }}
build-args: |
BUILD_FROM=ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}
cache-gha: false
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
context: machine/
cosign-base-identity: "https://github.com/home-assistant/core/.*"
cosign-base-verify: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}
file: machine/${{ matrix.machine }}
image: ghcr.io/home-assistant/${{ matrix.machine }}-homeassistant
image-tags: |
${{ needs.init.outputs.version }}
${{ steps.tags.outputs.extra_tags }}
push: true
version: ${{ needs.init.outputs.version }}
# - name: Build machine image
# uses: home-assistant/builder/actions/build-image@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
# with:
# arch: ${{ matrix.arch }}
# build-args: |
# BUILD_FROM=ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}
# cache-gha: false
# container-registry-password: ${{ secrets.GITHUB_TOKEN }}
# context: machine/
# cosign-base-identity: "https://github.com/home-assistant/core/.*"
# cosign-base-verify: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}
# file: machine/${{ matrix.machine }}
# image: ghcr.io/home-assistant/${{ matrix.machine }}-homeassistant
# image-tags: |
# ${{ needs.init.outputs.version }}
# ${{ steps.tags.outputs.extra_tags }}
# push: true
# version: ${{ needs.init.outputs.version }}
publish_ha:
name: Publish version files
environment: ${{ needs.init.outputs.channel }}
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_machine"]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# publish_ha:
# name: Publish version files
# environment: ${{ needs.init.outputs.channel }}
# if: github.repository_owner == 'home-assistant'
# needs: ["init", "build_machine"]
# runs-on: ubuntu-latest
# permissions:
# contents: read
# steps:
# - name: Checkout the repository
# uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# with:
# persist-credentials: false
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master # zizmor: ignore[unpinned-uses]
with:
name: ${{ secrets.GIT_NAME }}
email: ${{ secrets.GIT_EMAIL }}
token: ${{ secrets.GIT_TOKEN }}
# - name: Initialize git
# uses: home-assistant/actions/helpers/git-init@master # zizmor: ignore[unpinned-uses]
# with:
# name: ${{ secrets.GIT_NAME }}
# email: ${{ secrets.GIT_EMAIL }}
# token: ${{ secrets.GIT_TOKEN }}
- name: Update version file
uses: home-assistant/actions/helpers/version-push@master # zizmor: ignore[unpinned-uses]
with:
key: "homeassistant[]"
key-description: "Home Assistant Core"
version: ${{ needs.init.outputs.version }}
channel: ${{ needs.init.outputs.channel }}
exclude-list: '["odroid-xu","qemuarm","qemux86","raspberrypi","raspberrypi2","raspberrypi3","raspberrypi4","tinker"]'
# - name: Update version file
# uses: home-assistant/actions/helpers/version-push@master # zizmor: ignore[unpinned-uses]
# with:
# key: "homeassistant[]"
# key-description: "Home Assistant Core"
# version: ${{ needs.init.outputs.version }}
# channel: ${{ needs.init.outputs.channel }}
# exclude-list: '["odroid-xu","qemuarm","qemux86","raspberrypi","raspberrypi2","raspberrypi3","raspberrypi4","tinker"]'
- name: Update version file (stable -> beta)
if: needs.init.outputs.channel == 'stable'
uses: home-assistant/actions/helpers/version-push@master # zizmor: ignore[unpinned-uses]
with:
key: "homeassistant[]"
key-description: "Home Assistant Core"
version: ${{ needs.init.outputs.version }}
channel: beta
exclude-list: '["odroid-xu","qemuarm","qemux86","raspberrypi","raspberrypi2","raspberrypi3","raspberrypi4","tinker"]'
# - name: Update version file (stable -> beta)
# if: needs.init.outputs.channel == 'stable'
# uses: home-assistant/actions/helpers/version-push@master # zizmor: ignore[unpinned-uses]
# with:
# key: "homeassistant[]"
# key-description: "Home Assistant Core"
# version: ${{ needs.init.outputs.version }}
# channel: beta
# exclude-list: '["odroid-xu","qemuarm","qemux86","raspberrypi","raspberrypi2","raspberrypi3","raspberrypi4","tinker"]'
publish_container:
name: Publish meta container for ${{ matrix.registry }}
environment: ${{ needs.init.outputs.channel }}
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"]
runs-on: ubuntu-latest
permissions:
contents: read # To check out the repository
packages: write # To push to GHCR
id-token: write # For cosign signing
strategy:
fail-fast: false
matrix:
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
with:
cosign-release: "v2.5.3"
# publish_container:
# name: Publish meta container for ${{ matrix.registry }}
# environment: ${{ needs.init.outputs.channel }}
# if: github.repository_owner == 'home-assistant'
# needs: ["init", "build_base"]
# runs-on: ubuntu-latest
# permissions:
# contents: read # To check out the repository
# packages: write # To push to GHCR
# id-token: write # For cosign signing
# strategy:
# fail-fast: false
# matrix:
# registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
# steps:
# - name: Install Cosign
# uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
# with:
# cosign-release: "v2.5.3"
- name: Login to DockerHub
if: matrix.registry == 'docker.io/homeassistant'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# - name: Login to DockerHub
# if: matrix.registry == 'docker.io/homeassistant'
# uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
# with:
# username: ${{ secrets.DOCKERHUB_USERNAME }}
# password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
# - name: Login to GitHub Container Registry
# uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
# with:
# registry: ghcr.io
# username: ${{ github.repository_owner }}
# password: ${{ secrets.GITHUB_TOKEN }}
- name: Verify architecture image signatures
shell: bash
env:
ARCHITECTURES: ${{ needs.init.outputs.architectures }}
VERSION: ${{ needs.init.outputs.version }}
run: |
ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
for arch in $ARCHS; do
echo "Verifying ${arch} image signature..."
cosign verify \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp https://github.com/home-assistant/core/.* \
"ghcr.io/home-assistant/${arch}-homeassistant:${VERSION}"
done
echo "✓ All images verified successfully"
# - name: Verify architecture image signatures
# shell: bash
# env:
# ARCHITECTURES: ${{ needs.init.outputs.architectures }}
# VERSION: ${{ needs.init.outputs.version }}
# run: |
# ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
# for arch in $ARCHS; do
# echo "Verifying ${arch} image signature..."
# cosign verify \
# --certificate-oidc-issuer https://token.actions.githubusercontent.com \
# --certificate-identity-regexp https://github.com/home-assistant/core/.* \
# "ghcr.io/home-assistant/${arch}-homeassistant:${VERSION}"
# done
# echo "✓ All images verified successfully"
# Generate all Docker tags based on version string
# Version format: YYYY.MM.PATCH, YYYY.MM.PATCHbN (beta), or YYYY.MM.PATCH.devYYYYMMDDHHMM (dev)
# Examples:
# 2025.12.1 (stable) -> tags: 2025.12.1, 2025.12, stable, latest, beta, rc
# 2025.12.0b3 (beta) -> tags: 2025.12.0b3, beta, rc
# 2025.12.0.dev202511250240 -> tags: 2025.12.0.dev202511250240, dev
- name: Generate Docker metadata
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: ${{ matrix.registry }}/home-assistant
sep-tags: ","
tags: |
type=raw,value=${{ needs.init.outputs.version }},priority=9999
type=raw,value=dev,enable=${{ contains(needs.init.outputs.version, 'd') }}
type=raw,value=beta,enable=${{ !contains(needs.init.outputs.version, 'd') }}
type=raw,value=rc,enable=${{ !contains(needs.init.outputs.version, 'd') }}
type=raw,value=stable,enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
type=raw,value=latest,enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.init.outputs.version }},enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
# # Generate all Docker tags based on version string
# # Version format: YYYY.MM.PATCH, YYYY.MM.PATCHbN (beta), or YYYY.MM.PATCH.devYYYYMMDDHHMM (dev)
# # Examples:
# # 2025.12.1 (stable) -> tags: 2025.12.1, 2025.12, stable, latest, beta, rc
# # 2025.12.0b3 (beta) -> tags: 2025.12.0b3, beta, rc
# # 2025.12.0.dev202511250240 -> tags: 2025.12.0.dev202511250240, dev
# - name: Generate Docker metadata
# id: meta
# uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
# with:
# images: ${{ matrix.registry }}/home-assistant
# sep-tags: ","
# tags: |
# type=raw,value=${{ needs.init.outputs.version }},priority=9999
# type=raw,value=dev,enable=${{ contains(needs.init.outputs.version, 'd') }}
# type=raw,value=beta,enable=${{ !contains(needs.init.outputs.version, 'd') }}
# type=raw,value=rc,enable=${{ !contains(needs.init.outputs.version, 'd') }}
# type=raw,value=stable,enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
# type=raw,value=latest,enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
# type=semver,pattern={{major}}.{{minor}},value=${{ needs.init.outputs.version }},enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v3.7.1
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v3.7.1
- name: Copy architecture images to DockerHub
if: matrix.registry == 'docker.io/homeassistant'
shell: bash
env:
ARCHITECTURES: ${{ needs.init.outputs.architectures }}
VERSION: ${{ needs.init.outputs.version }}
run: |
# Use imagetools to copy image blobs directly between registries
# This preserves provenance/attestations and seems to be much faster than pull/push
ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
for arch in $ARCHS; do
echo "Copying ${arch} image to DockerHub..."
for attempt in 1 2 3; do
if docker buildx imagetools create \
--tag "docker.io/homeassistant/${arch}-homeassistant:${VERSION}" \
"ghcr.io/home-assistant/${arch}-homeassistant:${VERSION}"; then
break
fi
echo "Attempt ${attempt} failed, retrying in 10 seconds..."
sleep 10
if [ "${attempt}" -eq 3 ]; then
echo "Failed after 3 attempts"
exit 1
fi
done
cosign sign --yes "docker.io/homeassistant/${arch}-homeassistant:${VERSION}"
done
# - name: Copy architecture images to DockerHub
# if: matrix.registry == 'docker.io/homeassistant'
# shell: bash
# env:
# ARCHITECTURES: ${{ needs.init.outputs.architectures }}
# VERSION: ${{ needs.init.outputs.version }}
# run: |
# # Use imagetools to copy image blobs directly between registries
# # This preserves provenance/attestations and seems to be much faster than pull/push
# ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
# for arch in $ARCHS; do
# echo "Copying ${arch} image to DockerHub..."
# for attempt in 1 2 3; do
# if docker buildx imagetools create \
# --tag "docker.io/homeassistant/${arch}-homeassistant:${VERSION}" \
# "ghcr.io/home-assistant/${arch}-homeassistant:${VERSION}"; then
# break
# fi
# echo "Attempt ${attempt} failed, retrying in 10 seconds..."
# sleep 10
# if [ "${attempt}" -eq 3 ]; then
# echo "Failed after 3 attempts"
# exit 1
# fi
# done
# cosign sign --yes "docker.io/homeassistant/${arch}-homeassistant:${VERSION}"
# done
- name: Create and push multi-arch manifests
shell: bash
env:
ARCHITECTURES: ${{ needs.init.outputs.architectures }}
REGISTRY: ${{ matrix.registry }}
VERSION: ${{ needs.init.outputs.version }}
META_TAGS: ${{ steps.meta.outputs.tags }}
run: |
# Build list of architecture images dynamically
ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
ARCH_IMAGES=()
for arch in $ARCHS; do
ARCH_IMAGES+=("${REGISTRY}/${arch}-homeassistant:${VERSION}")
done
# - name: Create and push multi-arch manifests
# shell: bash
# env:
# ARCHITECTURES: ${{ needs.init.outputs.architectures }}
# REGISTRY: ${{ matrix.registry }}
# VERSION: ${{ needs.init.outputs.version }}
# META_TAGS: ${{ steps.meta.outputs.tags }}
# run: |
# # Build list of architecture images dynamically
# ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
# ARCH_IMAGES=()
# for arch in $ARCHS; do
# ARCH_IMAGES+=("${REGISTRY}/${arch}-homeassistant:${VERSION}")
# done
# Build list of all tags for single manifest creation
# Note: Using sep-tags=',' in metadata-action for easier parsing
TAG_ARGS=()
IFS=',' read -ra TAGS <<< "${META_TAGS}"
for tag in "${TAGS[@]}"; do
TAG_ARGS+=("--tag" "${tag}")
done
# # Build list of all tags for single manifest creation
# # Note: Using sep-tags=',' in metadata-action for easier parsing
# TAG_ARGS=()
# IFS=',' read -ra TAGS <<< "${META_TAGS}"
# for tag in "${TAGS[@]}"; do
# TAG_ARGS+=("--tag" "${tag}")
# done
# Create manifest with ALL tags in a single operation (much faster!)
echo "Creating multi-arch manifest with tags: ${TAGS[*]}"
docker buildx imagetools create "${TAG_ARGS[@]}" "${ARCH_IMAGES[@]}"
# # Create manifest with ALL tags in a single operation (much faster!)
# echo "Creating multi-arch manifest with tags: ${TAGS[*]}"
# docker buildx imagetools create "${TAG_ARGS[@]}" "${ARCH_IMAGES[@]}"
# Sign each tag separately (signing requires individual tag names)
echo "Signing all tags..."
for tag in "${TAGS[@]}"; do
echo "Signing ${tag}"
cosign sign --yes "${tag}"
done
# # Sign each tag separately (signing requires individual tag names)
# echo "Signing all tags..."
# for tag in "${TAGS[@]}"; do
# echo "Signing ${tag}"
# cosign sign --yes "${tag}"
# done
echo "All manifests created and signed successfully"
# echo "All manifests created and signed successfully"
build_python:
name: Build PyPi package
environment: ${{ needs.init.outputs.channel }}
needs: ["init", "build_base"]
runs-on: ubuntu-latest
permissions:
contents: read # To check out the repository
id-token: write # For PyPI trusted publishing
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
steps:
- name: Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# build_python:
# name: Build PyPi package
# environment: ${{ needs.init.outputs.channel }}
# needs: ["init", "build_base"]
# runs-on: ubuntu-latest
# permissions:
# contents: read # To check out the repository
# id-token: write # For PyPI trusted publishing
# if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
# steps:
# - name: Checkout the repository
# uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# with:
# persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version-file: ".python-version"
# - name: Set up Python
# uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
# with:
# python-version-file: ".python-version"
- name: Download translations
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: translations
# - name: Download translations
# uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# with:
# name: translations
- name: Extract translations
run: |
tar xvf translations.tar.gz
rm translations.tar.gz
# - name: Extract translations
# run: |
# tar xvf translations.tar.gz
# rm translations.tar.gz
- name: Build package
shell: bash
run: |
# Remove dist, build, and homeassistant.egg-info
# when build locally for testing!
pip install build
python -m build
# - name: Build package
# shell: bash
# run: |
# # Remove dist, build, and homeassistant.egg-info
# # when build locally for testing!
# pip install build
# python -m build
- name: Upload package to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
skip-existing: true
# - name: Upload package to PyPI
# uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
# with:
# skip-existing: true
hassfest-image:
name: Build and test hassfest image
+2 -2
View File
@@ -6,6 +6,6 @@ CONF_DEBUG_UI = "debug_ui"
DEBUG_UI_URL_MESSAGE = "Url and debug_ui cannot be set at the same time."
HA_MANAGED_API_PORT = 11984
HA_MANAGED_URL = f"http://localhost:{HA_MANAGED_API_PORT}/"
# When changing this version, also update the corresponding SHA hash (_GO2RTC_SHA)
# in script/hassfest/docker.py.
# When changing this version, also update _GO2RTC_VERSION and the corresponding
# SHA hash (_GO2RTC_SHA) in script/hassfest/docker.py.
RECOMMENDED_VERSION = "1.9.14"
+86 -2
View File
@@ -1,7 +1,9 @@
"""Generate and validate the dockerfile."""
from dataclasses import dataclass
import json
from pathlib import Path
import urllib.request
from homeassistant import core
from homeassistant.util import executor, thread
@@ -13,10 +15,86 @@ _DOCKERFILE_SYNTAX_SHA = (
"2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769" # 1.23.0
)
_GO2RTC_SHA = (
"675c318b23c06fd862a61d262240c9a63436b4050d177ffc68a32710d9e05bae" # 1.9.14
# go2rtc image pinned by digest. _GO2RTC_VERSION is the version that digest
# resolves to and MUST be kept in sync with _GO2RTC_SHA: both are updated
# together when bumping go2rtc. When either changes, hassfest verifies against
# the GHCR registry that the digest really is the one published for that
# version and records the verified pair in script/hassfest/generated/go2rtc.json
# so the check is not repeated on every CI run.
_GO2RTC_IMAGE = "alexxit/go2rtc"
_GO2RTC_VERSION = "1.9.14"
_GO2RTC_SHA = "675c318b23c06fd862a61d262240c9a63436b4050d177ffc68a32710d9e05bae"
_GO2RTC_RECORD_PATH = "script/hassfest/generated/go2rtc.json"
_MANIFEST_ACCEPT = (
"application/vnd.oci.image.index.v1+json,"
"application/vnd.docker.distribution.manifest.list.v2+json,"
"application/vnd.docker.distribution.manifest.v2+json"
)
def _go2rtc_record_content() -> str:
"""Return the content of the generated go2rtc SHA->version record."""
return (
json.dumps(
{"version": _GO2RTC_VERSION, "sha": _GO2RTC_SHA},
indent=2,
)
+ "\n"
)
def _registry_digest_for_version(version: str) -> str:
"""Resolve a go2rtc version tag to its image digest via the GHCR registry."""
with urllib.request.urlopen(
f"https://ghcr.io/token?scope=repository:{_GO2RTC_IMAGE}:pull&service=ghcr.io",
timeout=30,
) as response:
token = json.load(response)["token"]
request = urllib.request.Request(
f"https://ghcr.io/v2/{_GO2RTC_IMAGE}/manifests/{version}",
method="HEAD",
headers={
"Authorization": f"Bearer {token}",
"Accept": _MANIFEST_ACCEPT,
},
)
with urllib.request.urlopen(request, timeout=30) as response:
return response.headers.get("Docker-Content-Digest", "")
def _verify_go2rtc_record(config: Config) -> None:
"""Verify the pinned go2rtc digest matches the pinned version.
The (relatively expensive, network-bound) registry lookup only runs when
the pinned SHA/version differs from the previously verified pair recorded
in the generated file, i.e. when go2rtc is being bumped.
"""
record_path = config.root / _GO2RTC_RECORD_PATH
if record_path.is_file() and record_path.read_text() == _go2rtc_record_content():
return
try:
digest = _registry_digest_for_version(_GO2RTC_VERSION)
except OSError as err:
config.add_error(
"docker",
f"Unable to verify go2rtc {_GO2RTC_VERSION} against the registry: {err}",
)
return
expected = f"sha256:{_GO2RTC_SHA}"
if digest != expected:
actual = digest or "an unknown digest"
config.add_error(
"docker",
f"go2rtc {_GO2RTC_VERSION} resolves to {actual}, not {expected}. "
"Update _GO2RTC_VERSION and _GO2RTC_SHA in script/hassfest/docker.py "
"so they refer to the same release.",
)
DOCKERFILE_TEMPLATE = r"""# syntax=docker/dockerfile@sha256:{dockerfile_syntax}
# Automatically generated by hassfest.
#
@@ -215,6 +293,10 @@ def _generate_files(config: Config) -> list[File]:
),
config.root / "script/hassfest/docker/Dockerfile",
),
File(
_go2rtc_record_content(),
config.root / _GO2RTC_RECORD_PATH,
),
]
for machine_name, machine_config in sorted(_MACHINES.items()):
@@ -230,6 +312,8 @@ def _generate_files(config: Config) -> list[File]:
def validate(integrations: dict[str, Integration], config: Config) -> None:
"""Validate dockerfile."""
_verify_go2rtc_record(config)
docker_files = _generate_files(config)
config.cache["docker"] = docker_files
+4
View File
@@ -0,0 +1,4 @@
{
"version": "1.9.14",
"sha": "675c318b23c06fd862a61d262240c9a63436b4050d177ffc68a32710d9e05bae"
}
+20 -72
View File
@@ -1,85 +1,33 @@
"""Test that the go2rtc Docker image version matches or exceeds the recommended version.
"""Test that the pinned go2rtc Docker image matches or exceeds the recommended version.
This test ensures that the go2rtc Docker image SHA pinned in
script/hassfest/docker.py corresponds to a version that is equal to or
greater than the RECOMMENDED_VERSION defined in homeassistant/components/go2rtc/const.py.
The go2rtc image does not expose its version as an OCI label (the version is
only compiled into the binary). The SHA->version mapping is therefore recorded
in script/hassfest/docker.py as the ``_GO2RTC_VERSION`` constant paired with
``_GO2RTC_SHA``. hassfest verifies, against the GHCR registry, that the digest
really is the one published for that version whenever the pair changes, and
stores the verified pair in script/hassfest/generated/go2rtc.json so the check
is not repeated on every CI run.
The test pulls the Docker image using the pinned SHA and runs the
`go2rtc --version` command inside the container to extract the version,
then compares it against RECOMMENDED_VERSION.
Given that verified mapping, this test only needs to assert, offline, that the
pinned version is equal to or greater than the RECOMMENDED_VERSION defined in
homeassistant/components/go2rtc/const.py, catching a RECOMMENDED_VERSION bump
without a corresponding image bump (or vice versa).
"""
import asyncio
import os
import re
from awesomeversion import AwesomeVersion
import pytest
from homeassistant.components.go2rtc.const import RECOMMENDED_VERSION
from script.hassfest.docker import _GO2RTC_SHA as DOCKER_SHA
from script.hassfest.docker import _GO2RTC_VERSION as DOCKER_VERSION
async def _get_version_from_docker_sha() -> str:
"""Extract go2rtc version from Docker image using the pinned SHA."""
image = f"ghcr.io/alexxit/go2rtc@sha256:{DOCKER_SHA}"
pull_process = await asyncio.create_subprocess_exec(
"docker",
"pull",
image,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
_, pull_stderr = await pull_process.communicate()
if pull_process.returncode != 0:
raise RuntimeError(f"Failed to pull go2rtc image: {pull_stderr.decode()}")
# Run the container to get version
run_process = await asyncio.create_subprocess_exec(
"docker",
"run",
"--rm",
image,
"go2rtc",
"--version",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
run_stdout, run_stderr = await run_process.communicate()
if run_process.returncode != 0:
raise RuntimeError(f"Failed to run go2rtc --version: {run_stderr.decode()}")
# Parse version from output
# Expected output format: "go2rtc version 1.9.12 (commit) linux/amd64" or similar
output = run_stdout.decode().strip()
version_match = re.search(r"version\s+([\d.]+)", output)
if not version_match:
raise RuntimeError(f"Could not parse version from go2rtc output: {output}")
return version_match.group(1)
@pytest.mark.skipif(
not os.environ.get("CI"),
reason="This test requires Docker and only runs in CI",
)
async def test_docker_version_matches_recommended() -> None:
"""Test that the go2rtc Docker SHA version matches or exceeds RECOMMENDED_VERSION."""
# Extract version from the actual Docker container
docker_version_str = await _get_version_from_docker_sha()
# Parse versions
docker_version = AwesomeVersion(docker_version_str)
def test_docker_version_matches_recommended() -> None:
"""Test that the pinned go2rtc Docker version is >= RECOMMENDED_VERSION."""
docker_version = AwesomeVersion(DOCKER_VERSION)
recommended_version = AwesomeVersion(RECOMMENDED_VERSION)
# Assert that Docker version is equal to or greater than recommended version
assert docker_version >= recommended_version, (
f"go2rtc Docker version ({docker_version}) is less than "
f"RECOMMENDED_VERSION ({recommended_version}). "
"Please update _GO2RTC_SHA in script/hassfest/docker.py to a newer version"
f"The pinned go2rtc Docker version ({docker_version}) is less than "
f"RECOMMENDED_VERSION ({recommended_version}). Please update "
"_GO2RTC_VERSION and _GO2RTC_SHA in script/hassfest/docker.py to a "
"newer version"
)