mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-05-21 23:28:54 +01:00
7fb621234e
* Use Unix socket for Supervisor to Core communication Reintroduce Unix socket support for Supervisor-to-Core communication (reverted in #6735) with the addition of a feature flag gate. The feature is now controlled by the `core_unix_socket` feature flag and disabled by default. When enabled and Core version supports it, Supervisor communicates with Core via a Unix socket at /run/os/core.sock instead of TCP. This eliminates the need for access token authentication on the socket path, as Core authenticates the peer by the socket connection itself. Key changes: - Add FeatureFlag.CORE_UNIX_SOCKET to gate the feature - HomeAssistantAPI: transport-aware session/url/websocket management - WSClient: separate connect() (Unix, no auth) and connect_with_auth() (TCP) class methods with proper error handling - APIProxy delegates websocket setup to api.connect_websocket() - Container state tracking for Unix session lifecycle - CI builder mounts /run/supervisor for integration tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Sort feature flags alphabetically * Drop per-call max_msg_size from WSClient Hardcode the WebSocket message size cap to 64 MB in WSClient and remove the parameter from WSClient.connect, connect_with_auth, _ws_connect, and HomeAssistantAPI.connect_websocket. This was only ever overridden by APIProxy, so threading it through four layers was unnecessary. max_msg_size is a cap, not a pre-allocation; aiohttp only grows buffers to the size of actual incoming messages. Supervisor's own control channel never approaches 64 MB, so unifying the limit has no runtime cost. Addresses review feedback on #6742. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (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.3"
|
|
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: true
|
|
|
|
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
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@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
|
|
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@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@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@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
|
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@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
|
|
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@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
|
|
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", "run_supervisor"]
|
|
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@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@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
|
|
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 failiure
|
|
if: ${{ cancelled() || failure() }}
|
|
run: docker logs hassio_supervisor
|