mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-02-15 07:27:13 +00:00
* Fix getting Supervisor IP address in testing Newer Docker versions (probably newer than 29.x) do not have a global IPAddress attribute under .NetworkSettings anymore. There is a network specific map under Networks. For our case the hassio has the relevant IP address. This network specific maps already existed before, hence the new inspect format works for old as well as new Docker versions. While at it, also adjust the test fixture. * Actively wait for hassio IPAddress to become valid
500 lines
18 KiB
YAML
500 lines
18 KiB
YAML
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.13"
|
|
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: cp313
|
|
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.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@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.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@2025.11.0
|
|
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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.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@2025.11.0
|
|
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" != "<no value>" ]; 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@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.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 }}
|