name: Build supervisor on: workflow_dispatch: inputs: channel: description: "Channel" required: true default: "dev" version: description: "Version" required: true publish: description: "Publish" required: true default: "false" stable: description: "Stable" required: true default: "false" pull_request: branches: ["main"] release: types: ["published"] push: branches: ["main"] paths: - "rootfs/**" - "supervisor/**" - build.yaml - Dockerfile - requirements.txt - setup.py env: DEFAULT_PYTHON: "3.14.3" COSIGN_VERSION: "v2.5.3" CRANE_VERSION: "v0.20.7" CRANE_SHA256: "8ef3564d264e6b5ca93f7b7f5652704c4dd29d33935aff6947dd5adefd05953e" BUILD_NAME: supervisor BUILD_TYPE: supervisor concurrency: group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true jobs: init: name: Initialize build runs-on: ubuntu-latest outputs: architectures: ${{ steps.info.outputs.architectures }} version: ${{ steps.version.outputs.version }} channel: ${{ steps.version.outputs.channel }} publish: ${{ steps.version.outputs.publish }} build_wheels: ${{ steps.requirements.outputs.build_wheels }} steps: - name: Checkout the repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Get information id: info uses: home-assistant/actions/helpers/info@master - name: Get version id: version uses: home-assistant/actions/helpers/version@master with: type: ${{ env.BUILD_TYPE }} - name: Get changed files id: changed_files if: github.event_name == 'pull_request' || github.event_name == 'push' uses: masesgroup/retrieve-changed-files@491e80760c0e28d36ca6240a27b1ccb8e1402c13 # v3.0.0 - name: Check if requirements files changed id: requirements run: | # No wheels build necessary for releases if [[ "${{ github.event_name }}" == "release" ]]; then echo "build_wheels=false" >> "$GITHUB_OUTPUT" # Always build wheels for manual dispatches elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then echo "build_wheels=true" >> "$GITHUB_OUTPUT" elif [[ "${{ steps.changed_files.outputs.all }}" =~ (requirements\.txt|build\.yaml|\.github/workflows/builder\.yml) ]]; then echo "build_wheels=true" >> "$GITHUB_OUTPUT" else echo "build_wheels=false" >> "$GITHUB_OUTPUT" fi build: name: Build ${{ matrix.arch }} supervisor needs: init runs-on: ${{ matrix.runs-on }} permissions: contents: read id-token: write packages: write strategy: matrix: arch: ${{ fromJson(needs.init.outputs.architectures) }} include: - runs-on: ubuntu-24.04 - runs-on: ubuntu-24.04-arm arch: aarch64 env: WHEELS_ABI: cp314 WHEELS_TAG: musllinux_1_2 WHEELS_APK_DEPS: "libffi-dev;openssl-dev;yaml-dev" WHEELS_SKIP_BINARY: aiohttp steps: - name: Checkout the repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Write env-file for wheels build if: needs.init.outputs.build_wheels == 'true' run: | ( # Fix out of memory issues with rust echo "CARGO_NET_GIT_FETCH_WITH_CLI=true" ) > .env_file - name: Build and publish wheels if: needs.init.outputs.build_wheels == 'true' && needs.init.outputs.publish == 'true' uses: home-assistant/wheels@e5742a69d69f0e274e2689c998900c7d19652c21 # 2025.12.0 with: wheels-key: ${{ secrets.WHEELS_KEY }} abi: ${{ env.WHEELS_ABI }} tag: ${{ env.WHEELS_TAG }} arch: ${{ matrix.arch }} apk: ${{ env.WHEELS_APK_DEPS }} skip-binary: ${{ env.WHEELS_SKIP_BINARY }} env-file: true requirements: "requirements.txt" - name: Build local wheels if: needs.init.outputs.build_wheels == 'true' && needs.init.outputs.publish == 'false' uses: home-assistant/wheels@e5742a69d69f0e274e2689c998900c7d19652c21 # 2025.12.0 with: wheels-host: "" wheels-user: "" wheels-key: "" local-wheels-repo-path: "wheels/" abi: ${{ env.WHEELS_ABI }} tag: ${{ env.WHEELS_TAG }} arch: ${{ matrix.arch }} apk: ${{ env.WHEELS_APK_DEPS }} skip-binary: ${{ env.WHEELS_SKIP_BINARY }} env-file: true requirements: "requirements.txt" - name: Upload local wheels artifact if: needs.init.outputs.build_wheels == 'true' && needs.init.outputs.publish == 'false' uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: wheels-${{ matrix.arch }} path: wheels retention-days: 1 - name: Set version if: needs.init.outputs.publish == 'true' uses: home-assistant/actions/helpers/version@master with: type: ${{ env.BUILD_TYPE }} - name: Set up Python ${{ env.DEFAULT_PYTHON }} if: needs.init.outputs.publish == 'true' uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Install Cosign if: needs.init.outputs.publish == 'true' uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 with: cosign-release: ${{ env.COSIGN_VERSION }} - name: Install dirhash and calc hash if: needs.init.outputs.publish == 'true' run: | pip3 install setuptools dirhash dir_hash="$(dirhash "${{ github.workspace }}/supervisor" -a sha256 --match "*.py")" echo "${dir_hash}" > rootfs/supervisor.sha256 - name: Sign supervisor SHA256 if: needs.init.outputs.publish == 'true' run: | cosign sign-blob --yes rootfs/supervisor.sha256 --bundle rootfs/supervisor.sha256.sig - name: Login to GitHub Container Registry if: needs.init.outputs.publish == 'true' uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set build arguments if: needs.init.outputs.publish == 'false' run: echo "BUILD_ARGS=--test" >> $GITHUB_ENV # home-assistant/builder doesn't support sha pinning - name: Build supervisor uses: home-assistant/builder@2026.02.1 with: image: ${{ matrix.arch }} args: | $BUILD_ARGS \ --${{ matrix.arch }} \ --target /data \ --cosign \ --generic ${{ needs.init.outputs.version }} version: name: Update version needs: ["init", "run_supervisor", "retag_deprecated"] runs-on: ubuntu-latest steps: - name: Checkout the repository if: needs.init.outputs.publish == 'true' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Initialize git if: needs.init.outputs.publish == 'true' uses: home-assistant/actions/helpers/git-init@master with: name: ${{ secrets.GIT_NAME }} email: ${{ secrets.GIT_EMAIL }} token: ${{ secrets.GIT_TOKEN }} - name: Update version file if: needs.init.outputs.publish == 'true' uses: home-assistant/actions/helpers/version-push@master with: key: ${{ env.BUILD_NAME }} version: ${{ needs.init.outputs.version }} channel: ${{ needs.init.outputs.channel }} run_supervisor: runs-on: ubuntu-latest name: Run the Supervisor needs: ["build", "init"] timeout-minutes: 60 steps: - name: Checkout the repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download local wheels artifact if: needs.init.outputs.build_wheels == 'true' && needs.init.outputs.publish == 'false' uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: name: wheels-amd64 path: wheels # home-assistant/builder doesn't support sha pinning - name: Build the Supervisor if: needs.init.outputs.publish != 'true' uses: home-assistant/builder@2026.02.1 with: args: | --test \ --amd64 \ --target /data \ --generic runner - name: Pull Supervisor if: needs.init.outputs.publish == 'true' run: | docker pull ghcr.io/home-assistant/amd64-hassio-supervisor:${{ needs.init.outputs.version }} docker tag ghcr.io/home-assistant/amd64-hassio-supervisor:${{ needs.init.outputs.version }} ghcr.io/home-assistant/amd64-hassio-supervisor:runner - name: Create the Supervisor run: | mkdir -p /tmp/supervisor/data docker create --name hassio_supervisor \ --privileged \ --security-opt seccomp=unconfined \ --security-opt apparmor=unconfined \ -v /run/docker.sock:/run/docker.sock \ -v /run/dbus:/run/dbus \ -v /tmp/supervisor/data:/data \ -v /etc/machine-id:/etc/machine-id:ro \ -e SUPERVISOR_SHARE="/tmp/supervisor/data" \ -e SUPERVISOR_NAME=hassio_supervisor \ -e SUPERVISOR_DEV=1 \ -e SUPERVISOR_MACHINE="qemux86-64" \ ghcr.io/home-assistant/amd64-hassio-supervisor:runner - name: Start the Supervisor run: docker start hassio_supervisor - &wait_for_supervisor name: Wait for Supervisor to come up run: | until SUPERVISOR=$(docker inspect --format='{{.NetworkSettings.Networks.hassio.IPAddress}}' hassio_supervisor 2>/dev/null) && \ [ -n "$SUPERVISOR" ] && [ "$SUPERVISOR" != "" ]; do echo "Waiting for network configuration..." sleep 1 done echo "Waiting for Supervisor API at http://${SUPERVISOR}/supervisor/ping" timeout=300 elapsed=0 while [ $elapsed -lt $timeout ]; do if response=$(curl -sSf "http://${SUPERVISOR}/supervisor/ping" 2>/dev/null); then if echo "$response" | jq -e '.result == "ok"' >/dev/null 2>&1; then echo "Supervisor is up! (took ${elapsed}s)" exit 0 fi fi if [ $((elapsed % 15)) -eq 0 ]; then echo "Still waiting... (${elapsed}s/${timeout}s)" fi sleep 5 elapsed=$((elapsed + 5)) done echo "ERROR: Supervisor failed to start within ${timeout}s" echo "Last response: $response" echo "Checking supervisor logs..." docker logs --tail 50 hassio_supervisor exit 1 # Wait for Core to come up so subsequent steps (backup, addon install) succeed. # On first startup, Supervisor installs Core via the "home_assistant_core_install" # job (which pulls the image and then starts Core). Jobs with cleanup=True are # removed from the jobs list once done, so we poll until it's gone. - name: Wait for Core to be started run: | echo "Waiting for Home Assistant Core to be installed and started..." timeout=300 elapsed=0 while [ $elapsed -lt $timeout ]; do jobs=$(docker exec hassio_cli ha jobs info --no-progress --raw-json | jq -r '.data.jobs[] | select(.name == "home_assistant_core_install" and .done == false) | .name' 2>/dev/null) if [ -z "$jobs" ]; then echo "Home Assistant Core install/start complete (took ${elapsed}s)" exit 0 fi if [ $((elapsed % 15)) -eq 0 ]; then echo "Core still installing... (${elapsed}s/${timeout}s)" fi sleep 5 elapsed=$((elapsed + 5)) done echo "ERROR: Home Assistant Core failed to install/start within ${timeout}s" docker logs --tail 50 hassio_supervisor exit 1 - name: Check the Supervisor run: | echo "Checking supervisor info" test=$(docker exec hassio_cli ha supervisor info --no-progress --raw-json | jq -r '.result') if [ "$test" != "ok" ]; then exit 1 fi echo "Checking supervisor network info" test=$(docker exec hassio_cli ha network info --no-progress --raw-json | jq -r '.result') if [ "$test" != "ok" ]; then exit 1 fi - name: Check the Store / App run: | echo "Install Core SSH app" test=$(docker exec hassio_cli ha apps install core_ssh --no-progress --raw-json | jq -r '.result') if [ "$test" != "ok" ]; then exit 1 fi # Make sure it actually installed test=$(docker exec hassio_cli ha apps info core_ssh --no-progress --raw-json | jq -r '.data.version') if [[ "$test" == "null" ]]; then exit 1 fi echo "Start Core SSH app" test=$(docker exec hassio_cli ha apps start core_ssh --no-progress --raw-json | jq -r '.result') if [ "$test" != "ok" ]; then exit 1 fi # Make sure its state is started test="$(docker exec hassio_cli ha apps info core_ssh --no-progress --raw-json | jq -r '.data.state')" if [ "$test" != "started" ]; then exit 1 fi - name: Create full backup id: backup run: | test=$(docker exec hassio_cli ha backups new --no-progress --raw-json) if [ "$(echo $test | jq -r '.result')" != "ok" ]; then exit 1 fi echo "slug=$(echo $test | jq -r '.data.slug')" >> "$GITHUB_OUTPUT" - name: Uninstall SSH app run: | test=$(docker exec hassio_cli ha apps uninstall core_ssh --no-progress --raw-json | jq -r '.result') if [ "$test" != "ok" ]; then exit 1 fi - name: Restart supervisor run: | test=$(docker exec hassio_cli ha supervisor restart --no-progress --raw-json | jq -r '.result') if [ "$test" != "ok" ]; then exit 1 fi - *wait_for_supervisor - name: Restore SSH app from backup run: | test=$(docker exec hassio_cli ha backups restore ${{ steps.backup.outputs.slug }} --app core_ssh --no-progress --raw-json | jq -r '.result') if [ "$test" != "ok" ]; then exit 1 fi # Make sure it actually installed test=$(docker exec hassio_cli ha apps info core_ssh --no-progress --raw-json | jq -r '.data.version') if [[ "$test" == "null" ]]; then exit 1 fi # Make sure its state is started test="$(docker exec hassio_cli ha apps info core_ssh --no-progress --raw-json | jq -r '.data.state')" if [ "$test" != "started" ]; then exit 1 fi - name: Restore SSL directory from backup run: | test=$(docker exec hassio_cli ha backups restore ${{ steps.backup.outputs.slug }} --folders ssl --no-progress --raw-json | jq -r '.result') if [ "$test" != "ok" ]; then exit 1 fi - name: Get supervisor logs on failiure if: ${{ cancelled() || failure() }} run: docker logs hassio_supervisor retag_deprecated: needs: ["build", "init"] name: Re-tag deprecated ${{ matrix.arch }} images if: needs.init.outputs.publish == 'true' runs-on: ubuntu-latest permissions: contents: read id-token: write packages: write strategy: matrix: arch: ["armhf", "armv7", "i386"] env: # Last available release for deprecated architectures FROZEN_VERSION: "2025.11.5" steps: - name: Login to GitHub Container Registry uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Install Cosign uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 with: cosign-release: ${{ env.COSIGN_VERSION }} - name: Install crane run: | curl -sLO https://github.com/google/go-containerregistry/releases/download/${{ env.CRANE_VERSION }}/go-containerregistry_Linux_x86_64.tar.gz echo "${{ env.CRANE_SHA256 }} go-containerregistry_Linux_x86_64.tar.gz" | sha256sum -c - tar xzf go-containerregistry_Linux_x86_64.tar.gz crane sudo mv crane /usr/local/bin/ - name: Re-tag deprecated image with updated version label run: | crane auth login ghcr.io -u ${{ github.repository_owner }} -p ${{ secrets.GITHUB_TOKEN }} crane mutate \ --label io.hass.version=${{ needs.init.outputs.version }} \ --tag ghcr.io/home-assistant/${{ matrix.arch }}-hassio-supervisor:${{ needs.init.outputs.version }} \ ghcr.io/home-assistant/${{ matrix.arch }}-hassio-supervisor:${{ env.FROZEN_VERSION }} - name: Sign image with Cosign run: | cosign sign --yes ghcr.io/home-assistant/${{ matrix.arch }}-hassio-supervisor:${{ needs.init.outputs.version }}