Files
supervisor/.github/workflows/builder.yml
T
Stefan AgnerandClaude Opus 4.8 81e235376e Fix typos repo-wide and add codespell pre-commit hook (#6949)
* Fix typos in comments, docstrings and log messages

Correct 39 spelling mistakes across comments, docstrings and log/error
message strings throughout the package (e.g. "conection" -> "connection",
"Incomming" -> "Incoming", "Rasie" -> "Raise"). All changes are confined
to human-readable text; no identifiers, attributes or D-Bus contracts are
touched, so there is no behavior change. Found with codespell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix typos in tests and CI workflow

Correct spelling mistakes in test comments, docstrings and data, plus one
in the builder workflow, so the whole tree is clean for the codespell hook
added next. The assertion in test_network_manager.py is updated to match
the corrected "Unknown error while processing" log message in the source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add codespell pre-commit hook

Wire up codespell so spelling mistakes in comments, docstrings and strings
are caught automatically. The vendored frontend panel is excluded, and
"hass" and "astroid" are added to the ignore list as known false positives
(the Home Assistant abbreviation and the pylint dependency package).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address review feedback

Improve grammar in several of the touched comments and docstrings: use the
plural "ignore conditions" for the list-returning property, add the missing
auxiliary verb and fix agreement in the timezone-filter comment, fix
"backups ... use" agreement, and reword "underlay" to "underlying" in the
arch module docstring.

Also drop the "*.json" skip from the codespell hook. It was carried over
from another project but is unnecessary here (all tracked JSON is clean),
and skipping it would needlessly leave translation and data JSON unchecked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Reword onboarding comment

"overflight" was a literal calque of the German "überflogen"; use the
idiomatic "skimmed through" instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:09:16 +02:00

458 lines
16 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:
- ".github/workflows/builder.yml"
- "rootfs/**"
- "supervisor/**"
- Dockerfile
- requirements.txt
- setup.py
env:
DEFAULT_PYTHON: "3.14.5"
COSIGN_VERSION: "v2.5.3"
BUILD_NAME: supervisor
BUILD_TYPE: supervisor
IMAGE_NAME: hassio-supervisor
ARCHITECTURES: '["amd64", "aarch64"]'
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
# Cancel in-progress runs only for PRs; queue runs on main so that wheels
# builds published from sequential merges don't clobber each other and leave
# downstream CI without the wheels for the latest requirements.txt.
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
init:
name: Initialize build
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
channel: ${{ steps.version.outputs.channel }}
publish: ${{ steps.version.outputs.publish }}
build_wheels: ${{ steps.requirements.outputs.build_wheels }}
matrix: ${{ steps.matrix.outputs.matrix }}
steps:
- name: Checkout the repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
- 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@45a8b3b496d2d6037cbd553e8a3450989b9384a2 # v4.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|\.github/workflows/builder\.yml) ]]; then
echo "build_wheels=true" >> "$GITHUB_OUTPUT"
else
echo "build_wheels=false" >> "$GITHUB_OUTPUT"
fi
- name: Get build matrix
id: matrix
uses: home-assistant/builder/actions/prepare-multi-arch-matrix@4de35182ce1e329181bffcbcc84d33db5e2c7e10 # 2026.06.0
with:
architectures: ${{ env.ARCHITECTURES }}
image-name: ${{ env.IMAGE_NAME }}
build:
name: Build ${{ matrix.arch }} supervisor
needs: init
runs-on: ${{ matrix.os }}
permissions:
contents: read
id-token: write
packages: write
strategy:
matrix: ${{ fromJSON(needs.init.outputs.matrix) }}
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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@34957438948e0b3dcde73c77750643dadae594f5 # 2026.06.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@34957438948e0b3dcde73c77750643dadae594f5 # 2026.06.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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: wheels-${{ matrix.arch }}
path: wheels
retention-days: 1
- 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@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
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: Build supervisor
uses: home-assistant/builder/actions/build-image@4de35182ce1e329181bffcbcc84d33db5e2c7e10 # 2026.06.0
with:
arch: ${{ matrix.arch }}
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
cosign-base-identity: 'https://github.com/home-assistant/docker-base/.*'
cosign-base-verify: ghcr.io/home-assistant/base-python:3.14-alpine3.22
image: ${{ matrix.image }}
image-tags: |
${{ needs.init.outputs.version }}
latest
push: ${{ needs.init.outputs.publish == 'true' }}
version: ${{ needs.init.outputs.version }}
manifest:
name: Publish multi-arch manifest
needs: ["init", "build"]
if: needs.init.outputs.publish == 'true'
runs-on: ubuntu-latest
permissions:
id-token: write
packages: write
steps:
- name: Publish multi-arch manifest
uses: home-assistant/builder/actions/publish-multi-arch-manifest@4de35182ce1e329181bffcbcc84d33db5e2c7e10 # 2026.06.0
with:
architectures: ${{ env.ARCHITECTURES }}
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
image-name: ${{ env.IMAGE_NAME }}
image-tags: |
${{ needs.init.outputs.version }}
latest
version:
name: Update version
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
needs: ["init", "run_supervisor"]
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Initialize git
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
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Download local wheels artifact
if: needs.init.outputs.build_wheels == 'true' && needs.init.outputs.publish == 'false'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: wheels-amd64
path: wheels
# Build the Supervisor for non-publish runs (e.g. PRs)
- name: Build the Supervisor
if: needs.init.outputs.publish != 'true'
uses: home-assistant/builder/actions/build-image@4de35182ce1e329181bffcbcc84d33db5e2c7e10 # 2026.06.0
with:
arch: amd64
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
image: ghcr.io/home-assistant/amd64-hassio-supervisor
image-tags: runner
load: true
version: ${{ needs.init.outputs.version }}
# Pull the Supervisor for publish runs to test the published image
- 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:rw \
-v /run/dbus:/run/dbus:ro \
-v /run/supervisor:/run/os:rw \
-v /tmp/supervisor/data:/data:rw,slave \
-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 failure
if: ${{ cancelled() || failure() }}
run: docker logs hassio_supervisor