mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-09-02 19:42:58 +01:00
* 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>
458 lines
16 KiB
YAML
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
|