mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-10-11 20:59:37 +00:00
Compare commits
130 Commits
handle-git
...
autoupdate
Author | SHA1 | Date | |
---|---|---|---|
![]() |
caeeb6b419 | ||
![]() |
3c21a8b8ef | ||
![]() |
ddb8588d77 | ||
![]() |
81e46b20b8 | ||
![]() |
5041a1ed5c | ||
![]() |
337731a55a | ||
![]() |
53a8044aff | ||
![]() |
c71553f37d | ||
![]() |
c1eb97d8ab | ||
![]() |
190b734332 | ||
![]() |
559b6982a3 | ||
![]() |
301362e9e5 | ||
![]() |
fc928d294c | ||
![]() |
f42aeb4937 | ||
![]() |
fd21886de9 | ||
![]() |
e4bb415e30 | ||
![]() |
622dda5382 | ||
![]() |
78a2e15ebb | ||
![]() |
f3e1e0f423 | ||
![]() |
5779b567f1 | ||
![]() |
3c5f4920a0 | ||
![]() |
64f94a159c | ||
![]() |
ab3b147876 | ||
![]() |
e9cac9db06 | ||
![]() |
67c15678c6 | ||
![]() |
b0145a8507 | ||
![]() |
9f6b154097 | ||
![]() |
90c0d014db | ||
![]() |
fabfe760fb | ||
![]() |
092013e457 | ||
![]() |
e13f216b2e | ||
![]() |
97c7686b95 | ||
![]() |
42f93d0176 | ||
![]() |
ed7155604c | ||
![]() |
595e33ac68 | ||
![]() |
ae70ffd1b2 | ||
![]() |
17cb18a371 | ||
![]() |
9f5bebd0eb | ||
![]() |
c712d3cc53 | ||
![]() |
46fc5c8aa1 | ||
![]() |
8b23383e26 | ||
![]() |
c1ccb00946 | ||
![]() |
5693a5be0d | ||
![]() |
01911a44cd | ||
![]() |
857dae7736 | ||
![]() |
d2ddd9579c | ||
![]() |
ac9947d599 | ||
![]() |
2e22e1e884 | ||
![]() |
e7f3573e32 | ||
![]() |
b26451a59a | ||
![]() |
4e882f7c76 | ||
![]() |
5fa50ccf05 | ||
![]() |
3891df5266 | ||
![]() |
5aad32c15b | ||
![]() |
4a40490af7 | ||
![]() |
0a46e030f5 | ||
![]() |
bd00f90304 | ||
![]() |
819f097f01 | ||
![]() |
4513592993 | ||
![]() |
7e526a26af | ||
![]() |
b3af22f048 | ||
![]() |
bbb9469c1c | ||
![]() |
859c32a706 | ||
![]() |
87fc84c65c | ||
![]() |
e38ca5acb4 | ||
![]() |
09cd8eede2 | ||
![]() |
d1c537b280 | ||
![]() |
e6785d6a89 | ||
![]() |
59e051ad93 | ||
![]() |
3397def8b9 | ||
![]() |
b832edc10d | ||
![]() |
f69071878c | ||
![]() |
e065ba6081 | ||
![]() |
38611ad12f | ||
![]() |
8beb66d46c | ||
![]() |
c277f3cad6 | ||
![]() |
236c39cbb0 | ||
![]() |
7ed83a15fe | ||
![]() |
a3a5f6ba98 | ||
![]() |
8d3ededf2f | ||
![]() |
3d62c9afb1 | ||
![]() |
ef313d1fb5 | ||
![]() |
cae31637ae | ||
![]() |
9392d10625 | ||
![]() |
5ce62f324f | ||
![]() |
f84d514958 | ||
![]() |
3c39f2f785 | ||
![]() |
30db72df78 | ||
![]() |
00a78f372b | ||
![]() |
b69546f2c1 | ||
![]() |
78be155b94 | ||
![]() |
9900dfc8ca | ||
![]() |
3a1ebc9d37 | ||
![]() |
580c3273dc | ||
![]() |
b889f94ca4 | ||
![]() |
2d12920b35 | ||
![]() |
8a95113ebd | ||
![]() |
3fc1abf661 | ||
![]() |
207b665e1d | ||
![]() |
1fb15772d7 | ||
![]() |
9740de7a83 | ||
![]() |
8e8d77d90c | ||
![]() |
dbce22bd08 | ||
![]() |
192d446888 | ||
![]() |
d95ca401ec | ||
![]() |
07d8fd006a | ||
![]() |
b49ce96df8 | ||
![]() |
4109c15a36 | ||
![]() |
d0e2778255 | ||
![]() |
014082eda8 | ||
![]() |
2324b70084 | ||
![]() |
43f20fe24f | ||
![]() |
8ef5eae22a | ||
![]() |
e5dd09ab6b | ||
![]() |
3f2db956cb | ||
![]() |
603df92618 | ||
![]() |
8a82b98e5b | ||
![]() |
07dd0b7394 | ||
![]() |
cf0a85a4b1 | ||
![]() |
9924165cd3 | ||
![]() |
91392a5443 | ||
![]() |
fd205ce2ef | ||
![]() |
9ec56d9266 | ||
![]() |
886b1bd281 | ||
![]() |
ee0474edf5 | ||
![]() |
f173489e69 | ||
![]() |
cee495bde3 | ||
![]() |
59104a4438 | ||
![]() |
e4eaeb91cd | ||
![]() |
e61d88779d |
27
.github/workflows/builder.yml
vendored
27
.github/workflows/builder.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
|||||||
requirements: ${{ steps.requirements.outputs.changed }}
|
requirements: ${{ steps.requirements.outputs.changed }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed_files
|
id: changed_files
|
||||||
if: steps.version.outputs.publish == 'false'
|
if: steps.version.outputs.publish == 'false'
|
||||||
uses: masesgroup/retrieve-changed-files@v3.0.0
|
uses: masesgroup/retrieve-changed-files@491e80760c0e28d36ca6240a27b1ccb8e1402c13 # v3.0.0
|
||||||
|
|
||||||
- name: Check if requirements files changed
|
- name: Check if requirements files changed
|
||||||
id: requirements
|
id: requirements
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -104,9 +104,10 @@ jobs:
|
|||||||
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
|
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
|
||||||
) > .env_file
|
) > .env_file
|
||||||
|
|
||||||
|
# home-assistant/wheels doesn't support sha pinning
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
if: needs.init.outputs.requirements == 'true'
|
if: needs.init.outputs.requirements == 'true'
|
||||||
uses: home-assistant/wheels@2025.07.0
|
uses: home-assistant/wheels@2025.09.1
|
||||||
with:
|
with:
|
||||||
abi: cp313
|
abi: cp313
|
||||||
tag: musllinux_1_2
|
tag: musllinux_1_2
|
||||||
@@ -125,15 +126,15 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
uses: sigstore/cosign-installer@v3.9.2
|
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
|
||||||
with:
|
with:
|
||||||
cosign-release: "v2.4.3"
|
cosign-release: "v2.5.3"
|
||||||
|
|
||||||
- name: Install dirhash and calc hash
|
- name: Install dirhash and calc hash
|
||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
@@ -149,7 +150,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
uses: docker/login-action@v3.5.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -159,8 +160,9 @@ jobs:
|
|||||||
if: needs.init.outputs.publish == 'false'
|
if: needs.init.outputs.publish == 'false'
|
||||||
run: echo "BUILD_ARGS=--test" >> $GITHUB_ENV
|
run: echo "BUILD_ARGS=--test" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# home-assistant/builder doesn't support sha pinning
|
||||||
- name: Build supervisor
|
- name: Build supervisor
|
||||||
uses: home-assistant/builder@2025.03.0
|
uses: home-assistant/builder@2025.09.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_ARGS \
|
$BUILD_ARGS \
|
||||||
@@ -178,7 +180,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Initialize git
|
- name: Initialize git
|
||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
@@ -203,11 +205,12 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
|
# home-assistant/builder doesn't support sha pinning
|
||||||
- name: Build the Supervisor
|
- name: Build the Supervisor
|
||||||
if: needs.init.outputs.publish != 'true'
|
if: needs.init.outputs.publish != 'true'
|
||||||
uses: home-assistant/builder@2025.03.0
|
uses: home-assistant/builder@2025.09.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
--test \
|
--test \
|
||||||
|
78
.github/workflows/ci.yaml
vendored
78
.github/workflows/ci.yaml
vendored
@@ -26,15 +26,15 @@ jobs:
|
|||||||
name: Prepare Python dependencies
|
name: Prepare Python dependencies
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
pip install -r requirements.txt -r requirements_tests.txt
|
pip install -r requirements.txt -r requirements_tests.txt
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
lookup-only: true
|
lookup-only: true
|
||||||
@@ -68,15 +68,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
key: |
|
key: |
|
||||||
@@ -111,15 +111,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -131,7 +131,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
key: |
|
key: |
|
||||||
@@ -154,7 +154,7 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Register hadolint problem matcher
|
- name: Register hadolint problem matcher
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
|
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
|
||||||
@@ -169,15 +169,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -189,7 +189,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
key: |
|
key: |
|
||||||
@@ -213,15 +213,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -233,7 +233,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
key: |
|
key: |
|
||||||
@@ -257,15 +257,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -293,9 +293,9 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||||
@@ -307,7 +307,7 @@ jobs:
|
|||||||
echo "key=mypy-${{ env.MYPY_CACHE_VERSION }}-$mypy_version-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
echo "key=mypy-${{ env.MYPY_CACHE_VERSION }}-$mypy_version-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: >-
|
key: >-
|
||||||
@@ -318,7 +318,7 @@ jobs:
|
|||||||
echo "Failed to restore Python virtual environment from cache"
|
echo "Failed to restore Python virtual environment from cache"
|
||||||
exit 1
|
exit 1
|
||||||
- name: Restore mypy cache
|
- name: Restore mypy cache
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: .mypy_cache
|
path: .mypy_cache
|
||||||
key: >-
|
key: >-
|
||||||
@@ -339,19 +339,19 @@ jobs:
|
|||||||
name: Run tests Python ${{ needs.prepare.outputs.python-version }}
|
name: Run tests Python ${{ needs.prepare.outputs.python-version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@v3.9.2
|
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
|
||||||
with:
|
with:
|
||||||
cosign-release: "v2.4.3"
|
cosign-release: "v2.5.3"
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -386,7 +386,7 @@ jobs:
|
|||||||
-o console_output_style=count \
|
-o console_output_style=count \
|
||||||
tests
|
tests
|
||||||
- name: Upload coverage artifact
|
- name: Upload coverage artifact
|
||||||
uses: actions/upload-artifact@v4.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: coverage
|
name: coverage
|
||||||
path: .coverage
|
path: .coverage
|
||||||
@@ -398,15 +398,15 @@ jobs:
|
|||||||
needs: ["pytest", "prepare"]
|
needs: ["pytest", "prepare"]
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -417,7 +417,7 @@ jobs:
|
|||||||
echo "Failed to restore Python virtual environment from cache"
|
echo "Failed to restore Python virtual environment from cache"
|
||||||
exit 1
|
exit 1
|
||||||
- name: Download all coverage artifacts
|
- name: Download all coverage artifacts
|
||||||
uses: actions/download-artifact@v5.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: coverage
|
name: coverage
|
||||||
path: coverage/
|
path: coverage/
|
||||||
@@ -428,4 +428,4 @@ jobs:
|
|||||||
coverage report
|
coverage report
|
||||||
coverage xml
|
coverage xml
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v5.4.3
|
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
|
||||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
lock:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v5.0.1
|
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-inactive-days: "30"
|
issue-inactive-days: "30"
|
||||||
|
4
.github/workflows/release-drafter.yml
vendored
4
.github/workflows/release-drafter.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
name: Release Drafter
|
name: Release Drafter
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
echo "version=$datepre.$newpost" >> "$GITHUB_OUTPUT"
|
echo "version=$datepre.$newpost" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Run Release Drafter
|
- name: Run Release Drafter
|
||||||
uses: release-drafter/release-drafter@v6.1.0
|
uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
|
||||||
with:
|
with:
|
||||||
tag: ${{ steps.version.outputs.version }}
|
tag: ${{ steps.version.outputs.version }}
|
||||||
name: ${{ steps.version.outputs.version }}
|
name: ${{ steps.version.outputs.version }}
|
||||||
|
4
.github/workflows/restrict-task-creation.yml
vendored
4
.github/workflows/restrict-task-creation.yml
vendored
@@ -9,10 +9,10 @@ jobs:
|
|||||||
check-authorization:
|
check-authorization:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Only run if this is a Task issue type (from the issue form)
|
# Only run if this is a Task issue type (from the issue form)
|
||||||
if: github.event.issue.issue_type == 'Task'
|
if: github.event.issue.type.name == 'Task'
|
||||||
steps:
|
steps:
|
||||||
- name: Check if user is authorized
|
- name: Check if user is authorized
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const issueAuthor = context.payload.issue.user.login;
|
const issueAuthor = context.payload.issue.user.login;
|
||||||
|
4
.github/workflows/sentry.yaml
vendored
4
.github/workflows/sentry.yaml
vendored
@@ -10,9 +10,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Sentry Release
|
- name: Sentry Release
|
||||||
uses: getsentry/action-release@v3.2.0
|
uses: getsentry/action-release@4f502acc1df792390abe36f2dcb03612ef144818 # v3.3.0
|
||||||
env:
|
env:
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
|
3
.github/workflows/stale.yml
vendored
3
.github/workflows/stale.yml
vendored
@@ -9,13 +9,14 @@ jobs:
|
|||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9.1.0
|
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: 30
|
days-before-stale: 30
|
||||||
days-before-close: 7
|
days-before-close: 7
|
||||||
stale-issue-label: "stale"
|
stale-issue-label: "stale"
|
||||||
exempt-issue-labels: "no-stale,Help%20wanted,help-wanted,pinned,rfc,security"
|
exempt-issue-labels: "no-stale,Help%20wanted,help-wanted,pinned,rfc,security"
|
||||||
|
only-issue-types: "bug"
|
||||||
stale-issue-message: >
|
stale-issue-message: >
|
||||||
There hasn't been any activity on this issue recently. Due to the
|
There hasn't been any activity on this issue recently. Due to the
|
||||||
high number of incoming GitHub notifications, we have to clean some
|
high number of incoming GitHub notifications, we have to clean some
|
||||||
|
10
.github/workflows/update_frontend.yml
vendored
10
.github/workflows/update_frontend.yml
vendored
@@ -14,10 +14,10 @@ jobs:
|
|||||||
latest_version: ${{ steps.latest_frontend_version.outputs.latest_tag }}
|
latest_version: ${{ steps.latest_frontend_version.outputs.latest_tag }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Get latest frontend release
|
- name: Get latest frontend release
|
||||||
id: latest_frontend_version
|
id: latest_frontend_version
|
||||||
uses: abatilo/release-info-action@v1.3.3
|
uses: abatilo/release-info-action@32cb932219f1cee3fc4f4a298fd65ead5d35b661 # v1.3.3
|
||||||
with:
|
with:
|
||||||
owner: home-assistant
|
owner: home-assistant
|
||||||
repo: frontend
|
repo: frontend
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
if: needs.check-version.outputs.skip != 'true'
|
if: needs.check-version.outputs.skip != 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Clear www folder
|
- name: Clear www folder
|
||||||
run: |
|
run: |
|
||||||
rm -rf supervisor/api/panel/*
|
rm -rf supervisor/api/panel/*
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "${{ needs.check-version.outputs.latest_version }}" > .ha-frontend-version
|
echo "${{ needs.check-version.outputs.latest_version }}" > .ha-frontend-version
|
||||||
- name: Download release assets
|
- name: Download release assets
|
||||||
uses: robinraju/release-downloader@v1
|
uses: robinraju/release-downloader@daf26c55d821e836577a15f77d86ddc078948b05 # v1.12
|
||||||
with:
|
with:
|
||||||
repository: 'home-assistant/frontend'
|
repository: 'home-assistant/frontend'
|
||||||
tag: ${{ needs.check-version.outputs.latest_version }}
|
tag: ${{ needs.check-version.outputs.latest_version }}
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
rm -f supervisor/api/panel/home_assistant_frontend_supervisor-*.tar.gz
|
rm -f supervisor/api/panel/home_assistant_frontend_supervisor-*.tar.gz
|
||||||
- name: Create PR
|
- name: Create PR
|
||||||
uses: peter-evans/create-pull-request@v7
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||||
with:
|
with:
|
||||||
commit-message: "Update frontend to version ${{ needs.check-version.outputs.latest_version }}"
|
commit-message: "Update frontend to version ${{ needs.check-version.outputs.latest_version }}"
|
||||||
branch: autoupdate-frontend
|
branch: autoupdate-frontend
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -100,3 +100,6 @@ ENV/
|
|||||||
# mypy
|
# mypy
|
||||||
/.mypy_cache/*
|
/.mypy_cache/*
|
||||||
/.dmypy.json
|
/.dmypy.json
|
||||||
|
|
||||||
|
# Mac
|
||||||
|
.DS_Store
|
@@ -1 +1 @@
|
|||||||
20250806.0
|
20251001.2
|
||||||
|
@@ -29,7 +29,7 @@ RUN \
|
|||||||
\
|
\
|
||||||
&& curl -Lso /usr/bin/cosign "https://github.com/home-assistant/cosign/releases/download/${COSIGN_VERSION}/cosign_${BUILD_ARCH}" \
|
&& curl -Lso /usr/bin/cosign "https://github.com/home-assistant/cosign/releases/download/${COSIGN_VERSION}/cosign_${BUILD_ARCH}" \
|
||||||
&& chmod a+x /usr/bin/cosign \
|
&& chmod a+x /usr/bin/cosign \
|
||||||
&& pip3 install uv==0.6.17
|
&& pip3 install uv==0.8.9
|
||||||
|
|
||||||
# Install requirements
|
# Install requirements
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
@@ -12,7 +12,7 @@ cosign:
|
|||||||
base_identity: https://github.com/home-assistant/docker-base/.*
|
base_identity: https://github.com/home-assistant/docker-base/.*
|
||||||
identity: https://github.com/home-assistant/supervisor/.*
|
identity: https://github.com/home-assistant/supervisor/.*
|
||||||
args:
|
args:
|
||||||
COSIGN_VERSION: 2.4.3
|
COSIGN_VERSION: 2.5.3
|
||||||
labels:
|
labels:
|
||||||
io.hass.type: supervisor
|
io.hass.type: supervisor
|
||||||
org.opencontainers.image.title: Home Assistant Supervisor
|
org.opencontainers.image.title: Home Assistant Supervisor
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
aiodns==3.5.0
|
aiodns==3.5.0
|
||||||
aiohttp==3.12.15
|
aiohttp==3.13.0
|
||||||
atomicwrites-homeassistant==1.4.1
|
atomicwrites-homeassistant==1.4.1
|
||||||
attrs==25.3.0
|
attrs==25.4.0
|
||||||
awesomeversion==25.8.0
|
awesomeversion==25.8.0
|
||||||
blockbuster==1.5.25
|
blockbuster==1.5.25
|
||||||
brotli==1.1.0
|
brotli==1.1.0
|
||||||
ciso8601==2.3.2
|
ciso8601==2.3.3
|
||||||
colorlog==6.9.0
|
colorlog==6.9.0
|
||||||
cpe==1.3.1
|
cpe==1.3.1
|
||||||
cryptography==45.0.6
|
cryptography==46.0.2
|
||||||
debugpy==1.8.16
|
debugpy==1.8.17
|
||||||
deepmerge==2.0
|
deepmerge==2.0
|
||||||
dirhash==0.5.0
|
dirhash==0.5.0
|
||||||
docker==7.1.0
|
docker==7.1.0
|
||||||
@@ -17,14 +17,14 @@ faust-cchardet==2.1.19
|
|||||||
gitpython==3.1.45
|
gitpython==3.1.45
|
||||||
jinja2==3.1.6
|
jinja2==3.1.6
|
||||||
log-rate-limit==1.4.2
|
log-rate-limit==1.4.2
|
||||||
orjson==3.11.1
|
orjson==3.11.3
|
||||||
pulsectl==24.12.0
|
pulsectl==24.12.0
|
||||||
pyudev==0.24.3
|
pyudev==0.24.4
|
||||||
PyYAML==6.0.2
|
PyYAML==6.0.3
|
||||||
requests==2.32.4
|
requests==2.32.5
|
||||||
securetar==2025.2.1
|
securetar==2025.2.1
|
||||||
sentry-sdk==2.34.1
|
sentry-sdk==2.41.0
|
||||||
setuptools==80.9.0
|
setuptools==80.9.0
|
||||||
voluptuous==0.15.2
|
voluptuous==0.15.2
|
||||||
dbus-fast==2.44.3
|
dbus-fast==2.44.5
|
||||||
zlib-fast==0.2.1
|
zlib-fast==0.2.1
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
astroid==3.3.11
|
astroid==3.3.11
|
||||||
coverage==7.10.2
|
coverage==7.10.7
|
||||||
mypy==1.17.1
|
mypy==1.18.2
|
||||||
pre-commit==4.2.0
|
pre-commit==4.3.0
|
||||||
pylint==3.3.7
|
pylint==3.3.9
|
||||||
pytest-aiohttp==1.1.0
|
pytest-aiohttp==1.1.0
|
||||||
pytest-asyncio==0.25.2
|
pytest-asyncio==0.25.2
|
||||||
pytest-cov==6.2.1
|
pytest-cov==7.0.0
|
||||||
pytest-timeout==2.4.0
|
pytest-timeout==2.4.0
|
||||||
pytest==8.4.1
|
pytest==8.4.2
|
||||||
ruff==0.12.7
|
ruff==0.14.0
|
||||||
time-machine==2.17.0
|
time-machine==2.19.0
|
||||||
types-docker==7.1.0.20250705
|
types-docker==7.1.0.20251009
|
||||||
types-pyyaml==6.0.12.20250516
|
types-pyyaml==6.0.12.20250915
|
||||||
types-requests==2.32.4.20250611
|
types-requests==2.32.4.20250913
|
||||||
urllib3==2.5.0
|
urllib3==2.5.0
|
||||||
|
@@ -66,10 +66,23 @@ if __name__ == "__main__":
|
|||||||
_LOGGER.info("Setting up Supervisor")
|
_LOGGER.info("Setting up Supervisor")
|
||||||
loop.run_until_complete(coresys.core.setup())
|
loop.run_until_complete(coresys.core.setup())
|
||||||
|
|
||||||
bootstrap.register_signal_handlers(loop, coresys)
|
# Create startup task that can be cancelled gracefully
|
||||||
|
startup_task = loop.create_task(coresys.core.start())
|
||||||
|
|
||||||
|
def shutdown_handler() -> None:
|
||||||
|
"""Handle shutdown signals gracefully during startup."""
|
||||||
|
if not startup_task.done():
|
||||||
|
_LOGGER.warning("Supervisor startup interrupted by shutdown signal")
|
||||||
|
startup_task.cancel()
|
||||||
|
|
||||||
|
coresys.create_task(coresys.core.stop())
|
||||||
|
|
||||||
|
bootstrap.register_signal_handlers(loop, shutdown_handler)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(coresys.core.start())
|
loop.run_until_complete(startup_task)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
_LOGGER.warning("Supervisor startup cancelled")
|
||||||
except Exception as err: # pylint: disable=broad-except
|
except Exception as err: # pylint: disable=broad-except
|
||||||
# Supervisor itself is running at this point, just something didn't
|
# Supervisor itself is running at this point, just something didn't
|
||||||
# start as expected. Log with traceback to get more insights for
|
# start as expected. Log with traceback to get more insights for
|
||||||
|
@@ -67,12 +67,11 @@ from ..docker.monitor import DockerContainerStateEvent
|
|||||||
from ..docker.stats import DockerStats
|
from ..docker.stats import DockerStats
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
AddonConfigurationError,
|
AddonConfigurationError,
|
||||||
|
AddonNotSupportedError,
|
||||||
AddonsError,
|
AddonsError,
|
||||||
AddonsJobError,
|
AddonsJobError,
|
||||||
AddonsNotSupportedError,
|
|
||||||
ConfigurationFileError,
|
ConfigurationFileError,
|
||||||
DockerError,
|
DockerError,
|
||||||
HomeAssistantAPIError,
|
|
||||||
HostAppArmorError,
|
HostAppArmorError,
|
||||||
)
|
)
|
||||||
from ..hardware.data import Device
|
from ..hardware.data import Device
|
||||||
@@ -227,6 +226,7 @@ class Addon(AddonModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
await self._check_ingress_port()
|
await self._check_ingress_port()
|
||||||
|
|
||||||
default_image = self._image(self.data)
|
default_image = self._image(self.data)
|
||||||
try:
|
try:
|
||||||
await self.instance.attach(version=self.version)
|
await self.instance.attach(version=self.version)
|
||||||
@@ -775,7 +775,6 @@ class Addon(AddonModel):
|
|||||||
raise AddonsError("Missing from store, cannot install!")
|
raise AddonsError("Missing from store, cannot install!")
|
||||||
|
|
||||||
await self.sys_addons.data.install(self.addon_store)
|
await self.sys_addons.data.install(self.addon_store)
|
||||||
await self.load()
|
|
||||||
|
|
||||||
def setup_data():
|
def setup_data():
|
||||||
if not self.path_data.is_dir():
|
if not self.path_data.is_dir():
|
||||||
@@ -798,6 +797,9 @@ class Addon(AddonModel):
|
|||||||
await self.sys_addons.data.uninstall(self)
|
await self.sys_addons.data.uninstall(self)
|
||||||
raise AddonsError() from err
|
raise AddonsError() from err
|
||||||
|
|
||||||
|
# Finish initialization and set up listeners
|
||||||
|
await self.load()
|
||||||
|
|
||||||
# Add to addon manager
|
# Add to addon manager
|
||||||
self.sys_addons.local[self.slug] = self
|
self.sys_addons.local[self.slug] = self
|
||||||
|
|
||||||
@@ -842,8 +844,7 @@ class Addon(AddonModel):
|
|||||||
# Cleanup Ingress panel from sidebar
|
# Cleanup Ingress panel from sidebar
|
||||||
if self.ingress_panel:
|
if self.ingress_panel:
|
||||||
self.ingress_panel = False
|
self.ingress_panel = False
|
||||||
with suppress(HomeAssistantAPIError):
|
await self.sys_ingress.update_hass_panel(self)
|
||||||
await self.sys_ingress.update_hass_panel(self)
|
|
||||||
|
|
||||||
# Cleanup Ingress dynamic port assignment
|
# Cleanup Ingress dynamic port assignment
|
||||||
need_ingress_token_cleanup = False
|
need_ingress_token_cleanup = False
|
||||||
@@ -1172,7 +1173,7 @@ class Addon(AddonModel):
|
|||||||
async def write_stdin(self, data) -> None:
|
async def write_stdin(self, data) -> None:
|
||||||
"""Write data to add-on stdin."""
|
"""Write data to add-on stdin."""
|
||||||
if not self.with_stdin:
|
if not self.with_stdin:
|
||||||
raise AddonsNotSupportedError(
|
raise AddonNotSupportedError(
|
||||||
f"Add-on {self.slug} does not support writing to stdin!", _LOGGER.error
|
f"Add-on {self.slug} does not support writing to stdin!", _LOGGER.error
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1419,7 +1420,7 @@ class Addon(AddonModel):
|
|||||||
|
|
||||||
# If available
|
# If available
|
||||||
if not self._available(data[ATTR_SYSTEM]):
|
if not self._available(data[ATTR_SYSTEM]):
|
||||||
raise AddonsNotSupportedError(
|
raise AddonNotSupportedError(
|
||||||
f"Add-on {self.slug} is not available for this platform",
|
f"Add-on {self.slug} is not available for this platform",
|
||||||
_LOGGER.error,
|
_LOGGER.error,
|
||||||
)
|
)
|
||||||
|
@@ -9,19 +9,18 @@ from typing import Self, Union
|
|||||||
|
|
||||||
from attr import evolve
|
from attr import evolve
|
||||||
|
|
||||||
from supervisor.jobs.const import JobConcurrency
|
|
||||||
|
|
||||||
from ..const import AddonBoot, AddonStartup, AddonState
|
from ..const import AddonBoot, AddonStartup, AddonState
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
|
AddonNotSupportedError,
|
||||||
AddonsError,
|
AddonsError,
|
||||||
AddonsJobError,
|
AddonsJobError,
|
||||||
AddonsNotSupportedError,
|
|
||||||
CoreDNSError,
|
CoreDNSError,
|
||||||
DockerError,
|
DockerError,
|
||||||
HassioError,
|
HassioError,
|
||||||
HomeAssistantAPIError,
|
|
||||||
)
|
)
|
||||||
|
from ..jobs import ChildJobSyncFilter
|
||||||
|
from ..jobs.const import JobConcurrency
|
||||||
from ..jobs.decorator import Job, JobCondition
|
from ..jobs.decorator import Job, JobCondition
|
||||||
from ..resolution.const import ContextType, IssueType, SuggestionType
|
from ..resolution.const import ContextType, IssueType, SuggestionType
|
||||||
from ..store.addon import AddonStore
|
from ..store.addon import AddonStore
|
||||||
@@ -183,8 +182,13 @@ class AddonManager(CoreSysAttributes):
|
|||||||
conditions=ADDON_UPDATE_CONDITIONS,
|
conditions=ADDON_UPDATE_CONDITIONS,
|
||||||
on_condition=AddonsJobError,
|
on_condition=AddonsJobError,
|
||||||
concurrency=JobConcurrency.QUEUE,
|
concurrency=JobConcurrency.QUEUE,
|
||||||
|
child_job_syncs=[
|
||||||
|
ChildJobSyncFilter("docker_interface_install", progress_allocation=1.0)
|
||||||
|
],
|
||||||
)
|
)
|
||||||
async def install(self, slug: str) -> None:
|
async def install(
|
||||||
|
self, slug: str, *, validation_complete: asyncio.Event | None = None
|
||||||
|
) -> None:
|
||||||
"""Install an add-on."""
|
"""Install an add-on."""
|
||||||
self.sys_jobs.current.reference = slug
|
self.sys_jobs.current.reference = slug
|
||||||
|
|
||||||
@@ -197,6 +201,10 @@ class AddonManager(CoreSysAttributes):
|
|||||||
|
|
||||||
store.validate_availability()
|
store.validate_availability()
|
||||||
|
|
||||||
|
# If being run in the background, notify caller that validation has completed
|
||||||
|
if validation_complete:
|
||||||
|
validation_complete.set()
|
||||||
|
|
||||||
await Addon(self.coresys, slug).install()
|
await Addon(self.coresys, slug).install()
|
||||||
|
|
||||||
_LOGGER.info("Add-on '%s' successfully installed", slug)
|
_LOGGER.info("Add-on '%s' successfully installed", slug)
|
||||||
@@ -224,9 +232,20 @@ class AddonManager(CoreSysAttributes):
|
|||||||
name="addon_manager_update",
|
name="addon_manager_update",
|
||||||
conditions=ADDON_UPDATE_CONDITIONS,
|
conditions=ADDON_UPDATE_CONDITIONS,
|
||||||
on_condition=AddonsJobError,
|
on_condition=AddonsJobError,
|
||||||
|
# We assume for now the docker image pull is 100% of this task for progress
|
||||||
|
# allocation. But from a user perspective that isn't true. Other steps
|
||||||
|
# that take time which is not accounted for in progress include:
|
||||||
|
# partial backup, image cleanup, apparmor update, and addon restart
|
||||||
|
child_job_syncs=[
|
||||||
|
ChildJobSyncFilter("docker_interface_install", progress_allocation=1.0)
|
||||||
|
],
|
||||||
)
|
)
|
||||||
async def update(
|
async def update(
|
||||||
self, slug: str, backup: bool | None = False
|
self,
|
||||||
|
slug: str,
|
||||||
|
backup: bool | None = False,
|
||||||
|
*,
|
||||||
|
validation_complete: asyncio.Event | None = None,
|
||||||
) -> asyncio.Task | None:
|
) -> asyncio.Task | None:
|
||||||
"""Update add-on.
|
"""Update add-on.
|
||||||
|
|
||||||
@@ -251,6 +270,10 @@ class AddonManager(CoreSysAttributes):
|
|||||||
# Check if available, Maybe something have changed
|
# Check if available, Maybe something have changed
|
||||||
store.validate_availability()
|
store.validate_availability()
|
||||||
|
|
||||||
|
# If being run in the background, notify caller that validation has completed
|
||||||
|
if validation_complete:
|
||||||
|
validation_complete.set()
|
||||||
|
|
||||||
if backup:
|
if backup:
|
||||||
await self.sys_backups.do_backup_partial(
|
await self.sys_backups.do_backup_partial(
|
||||||
name=f"addon_{addon.slug}_{addon.version}",
|
name=f"addon_{addon.slug}_{addon.version}",
|
||||||
@@ -258,7 +281,10 @@ class AddonManager(CoreSysAttributes):
|
|||||||
addons=[addon.slug],
|
addons=[addon.slug],
|
||||||
)
|
)
|
||||||
|
|
||||||
return await addon.update()
|
task = await addon.update()
|
||||||
|
|
||||||
|
_LOGGER.info("Add-on '%s' successfully updated", slug)
|
||||||
|
return task
|
||||||
|
|
||||||
@Job(
|
@Job(
|
||||||
name="addon_manager_rebuild",
|
name="addon_manager_rebuild",
|
||||||
@@ -293,7 +319,7 @@ class AddonManager(CoreSysAttributes):
|
|||||||
"Version changed, use Update instead Rebuild", _LOGGER.error
|
"Version changed, use Update instead Rebuild", _LOGGER.error
|
||||||
)
|
)
|
||||||
if not force and not addon.need_build:
|
if not force and not addon.need_build:
|
||||||
raise AddonsNotSupportedError(
|
raise AddonNotSupportedError(
|
||||||
"Can't rebuild a image based add-on", _LOGGER.error
|
"Can't rebuild a image based add-on", _LOGGER.error
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -337,8 +363,7 @@ class AddonManager(CoreSysAttributes):
|
|||||||
# Update ingress
|
# Update ingress
|
||||||
if had_ingress != addon.ingress_panel:
|
if had_ingress != addon.ingress_panel:
|
||||||
await self.sys_ingress.reload()
|
await self.sys_ingress.reload()
|
||||||
with suppress(HomeAssistantAPIError):
|
await self.sys_ingress.update_hass_panel(addon)
|
||||||
await self.sys_ingress.update_hass_panel(addon)
|
|
||||||
|
|
||||||
return wait_for_start
|
return wait_for_start
|
||||||
|
|
||||||
|
@@ -72,6 +72,7 @@ from ..const import (
|
|||||||
ATTR_TYPE,
|
ATTR_TYPE,
|
||||||
ATTR_UART,
|
ATTR_UART,
|
||||||
ATTR_UDEV,
|
ATTR_UDEV,
|
||||||
|
ATTR_ULIMITS,
|
||||||
ATTR_URL,
|
ATTR_URL,
|
||||||
ATTR_USB,
|
ATTR_USB,
|
||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
@@ -89,7 +90,12 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..coresys import CoreSys
|
from ..coresys import CoreSys
|
||||||
from ..docker.const import Capabilities
|
from ..docker.const import Capabilities
|
||||||
from ..exceptions import AddonsNotSupportedError
|
from ..exceptions import (
|
||||||
|
AddonNotSupportedArchitectureError,
|
||||||
|
AddonNotSupportedError,
|
||||||
|
AddonNotSupportedHomeAssistantVersionError,
|
||||||
|
AddonNotSupportedMachineTypeError,
|
||||||
|
)
|
||||||
from ..jobs.const import JOB_GROUP_ADDON
|
from ..jobs.const import JOB_GROUP_ADDON
|
||||||
from ..jobs.job_group import JobGroup
|
from ..jobs.job_group import JobGroup
|
||||||
from ..utils import version_is_new_enough
|
from ..utils import version_is_new_enough
|
||||||
@@ -457,6 +463,11 @@ class AddonModel(JobGroup, ABC):
|
|||||||
"""Return True if the add-on have his own udev."""
|
"""Return True if the add-on have his own udev."""
|
||||||
return self.data[ATTR_UDEV]
|
return self.data[ATTR_UDEV]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ulimits(self) -> dict[str, Any]:
|
||||||
|
"""Return ulimits configuration."""
|
||||||
|
return self.data[ATTR_ULIMITS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def with_kernel_modules(self) -> bool:
|
def with_kernel_modules(self) -> bool:
|
||||||
"""Return True if the add-on access to kernel modules."""
|
"""Return True if the add-on access to kernel modules."""
|
||||||
@@ -680,9 +691,8 @@ class AddonModel(JobGroup, ABC):
|
|||||||
"""Validate if addon is available for current system."""
|
"""Validate if addon is available for current system."""
|
||||||
# Architecture
|
# Architecture
|
||||||
if not self.sys_arch.is_supported(config[ATTR_ARCH]):
|
if not self.sys_arch.is_supported(config[ATTR_ARCH]):
|
||||||
raise AddonsNotSupportedError(
|
raise AddonNotSupportedArchitectureError(
|
||||||
f"Add-on {self.slug} not supported on this platform, supported architectures: {', '.join(config[ATTR_ARCH])}",
|
logger, slug=self.slug, architectures=config[ATTR_ARCH]
|
||||||
logger,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Machine / Hardware
|
# Machine / Hardware
|
||||||
@@ -690,9 +700,8 @@ class AddonModel(JobGroup, ABC):
|
|||||||
if machine and (
|
if machine and (
|
||||||
f"!{self.sys_machine}" in machine or self.sys_machine not in machine
|
f"!{self.sys_machine}" in machine or self.sys_machine not in machine
|
||||||
):
|
):
|
||||||
raise AddonsNotSupportedError(
|
raise AddonNotSupportedMachineTypeError(
|
||||||
f"Add-on {self.slug} not supported on this machine, supported machine types: {', '.join(machine)}",
|
logger, slug=self.slug, machine_types=machine
|
||||||
logger,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Home Assistant
|
# Home Assistant
|
||||||
@@ -701,16 +710,15 @@ class AddonModel(JobGroup, ABC):
|
|||||||
if version and not version_is_new_enough(
|
if version and not version_is_new_enough(
|
||||||
self.sys_homeassistant.version, version
|
self.sys_homeassistant.version, version
|
||||||
):
|
):
|
||||||
raise AddonsNotSupportedError(
|
raise AddonNotSupportedHomeAssistantVersionError(
|
||||||
f"Add-on {self.slug} not supported on this system, requires Home Assistant version {version} or greater",
|
logger, slug=self.slug, version=str(version)
|
||||||
logger,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _available(self, config) -> bool:
|
def _available(self, config) -> bool:
|
||||||
"""Return True if this add-on is available on this platform."""
|
"""Return True if this add-on is available on this platform."""
|
||||||
try:
|
try:
|
||||||
self._validate_availability(config)
|
self._validate_availability(config)
|
||||||
except AddonsNotSupportedError:
|
except AddonNotSupportedError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@@ -93,15 +93,7 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
|
|
||||||
typ = self.raw_schema[key]
|
typ = self.raw_schema[key]
|
||||||
try:
|
try:
|
||||||
if isinstance(typ, list):
|
options[key] = self._validate_element(typ, value, key)
|
||||||
# nested value list
|
|
||||||
options[key] = self._nested_validate_list(typ[0], value, key)
|
|
||||||
elif isinstance(typ, dict):
|
|
||||||
# nested value dict
|
|
||||||
options[key] = self._nested_validate_dict(typ, value, key)
|
|
||||||
else:
|
|
||||||
# normal value
|
|
||||||
options[key] = self._single_validate(typ, value, key)
|
|
||||||
except (IndexError, KeyError):
|
except (IndexError, KeyError):
|
||||||
raise vol.Invalid(
|
raise vol.Invalid(
|
||||||
f"Type error for option '{key}' in {self._name} ({self._slug})"
|
f"Type error for option '{key}' in {self._name} ({self._slug})"
|
||||||
@@ -111,7 +103,20 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
return options
|
return options
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
def _single_validate(self, typ: str, value: Any, key: str):
|
def _validate_element(self, typ: Any, value: Any, key: str) -> Any:
|
||||||
|
"""Validate a value against a type specification."""
|
||||||
|
if isinstance(typ, list):
|
||||||
|
# nested value list
|
||||||
|
return self._nested_validate_list(typ[0], value, key)
|
||||||
|
elif isinstance(typ, dict):
|
||||||
|
# nested value dict
|
||||||
|
return self._nested_validate_dict(typ, value, key)
|
||||||
|
else:
|
||||||
|
# normal value
|
||||||
|
return self._single_validate(typ, value, key)
|
||||||
|
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
|
def _single_validate(self, typ: str, value: Any, key: str) -> Any:
|
||||||
"""Validate a single element."""
|
"""Validate a single element."""
|
||||||
# if required argument
|
# if required argument
|
||||||
if value is None:
|
if value is None:
|
||||||
@@ -182,13 +187,15 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
|
|
||||||
# Device valid
|
# Device valid
|
||||||
self.devices.add(device)
|
self.devices.add(device)
|
||||||
return str(device.path)
|
return str(value)
|
||||||
|
|
||||||
raise vol.Invalid(
|
raise vol.Invalid(
|
||||||
f"Fatal error for option '{key}' with type '{typ}' in {self._name} ({self._slug})"
|
f"Fatal error for option '{key}' with type '{typ}' in {self._name} ({self._slug})"
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
def _nested_validate_list(self, typ: Any, data_list: list[Any], key: str):
|
def _nested_validate_list(
|
||||||
|
self, typ: Any, data_list: list[Any], key: str
|
||||||
|
) -> list[Any]:
|
||||||
"""Validate nested items."""
|
"""Validate nested items."""
|
||||||
options = []
|
options = []
|
||||||
|
|
||||||
@@ -201,17 +208,13 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
# Process list
|
# Process list
|
||||||
for element in data_list:
|
for element in data_list:
|
||||||
# Nested?
|
# Nested?
|
||||||
if isinstance(typ, dict):
|
options.append(self._validate_element(typ, element, key))
|
||||||
c_options = self._nested_validate_dict(typ, element, key)
|
|
||||||
options.append(c_options)
|
|
||||||
else:
|
|
||||||
options.append(self._single_validate(typ, element, key))
|
|
||||||
|
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def _nested_validate_dict(
|
def _nested_validate_dict(
|
||||||
self, typ: dict[Any, Any], data_dict: dict[Any, Any], key: str
|
self, typ: dict[Any, Any], data_dict: dict[Any, Any], key: str
|
||||||
):
|
) -> dict[Any, Any]:
|
||||||
"""Validate nested items."""
|
"""Validate nested items."""
|
||||||
options = {}
|
options = {}
|
||||||
|
|
||||||
@@ -231,12 +234,7 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Nested?
|
# Nested?
|
||||||
if isinstance(typ[c_key], list):
|
options[c_key] = self._validate_element(typ[c_key], c_value, c_key)
|
||||||
options[c_key] = self._nested_validate_list(
|
|
||||||
typ[c_key][0], c_value, c_key
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
options[c_key] = self._single_validate(typ[c_key], c_value, c_key)
|
|
||||||
|
|
||||||
self._check_missing_options(typ, options, key)
|
self._check_missing_options(typ, options, key)
|
||||||
return options
|
return options
|
||||||
@@ -274,18 +272,28 @@ class UiOptions(CoreSysAttributes):
|
|||||||
|
|
||||||
# read options
|
# read options
|
||||||
for key, value in raw_schema.items():
|
for key, value in raw_schema.items():
|
||||||
if isinstance(value, list):
|
self._ui_schema_element(ui_schema, value, key)
|
||||||
# nested value list
|
|
||||||
self._nested_ui_list(ui_schema, value, key)
|
|
||||||
elif isinstance(value, dict):
|
|
||||||
# nested value dict
|
|
||||||
self._nested_ui_dict(ui_schema, value, key)
|
|
||||||
else:
|
|
||||||
# normal value
|
|
||||||
self._single_ui_option(ui_schema, value, key)
|
|
||||||
|
|
||||||
return ui_schema
|
return ui_schema
|
||||||
|
|
||||||
|
def _ui_schema_element(
|
||||||
|
self,
|
||||||
|
ui_schema: list[dict[str, Any]],
|
||||||
|
value: str,
|
||||||
|
key: str,
|
||||||
|
multiple: bool = False,
|
||||||
|
):
|
||||||
|
if isinstance(value, list):
|
||||||
|
# nested value list
|
||||||
|
assert not multiple
|
||||||
|
self._nested_ui_list(ui_schema, value, key)
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
# nested value dict
|
||||||
|
self._nested_ui_dict(ui_schema, value, key, multiple)
|
||||||
|
else:
|
||||||
|
# normal value
|
||||||
|
self._single_ui_option(ui_schema, value, key, multiple)
|
||||||
|
|
||||||
def _single_ui_option(
|
def _single_ui_option(
|
||||||
self,
|
self,
|
||||||
ui_schema: list[dict[str, Any]],
|
ui_schema: list[dict[str, Any]],
|
||||||
@@ -377,10 +385,7 @@ class UiOptions(CoreSysAttributes):
|
|||||||
_LOGGER.error("Invalid schema %s", key)
|
_LOGGER.error("Invalid schema %s", key)
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(element, dict):
|
self._ui_schema_element(ui_schema, element, key, multiple=True)
|
||||||
self._nested_ui_dict(ui_schema, element, key, multiple=True)
|
|
||||||
else:
|
|
||||||
self._single_ui_option(ui_schema, element, key, multiple=True)
|
|
||||||
|
|
||||||
def _nested_ui_dict(
|
def _nested_ui_dict(
|
||||||
self,
|
self,
|
||||||
@@ -399,11 +404,7 @@ class UiOptions(CoreSysAttributes):
|
|||||||
|
|
||||||
nested_schema: list[dict[str, Any]] = []
|
nested_schema: list[dict[str, Any]] = []
|
||||||
for c_key, c_value in option_dict.items():
|
for c_key, c_value in option_dict.items():
|
||||||
# Nested?
|
self._ui_schema_element(nested_schema, c_value, c_key)
|
||||||
if isinstance(c_value, list):
|
|
||||||
self._nested_ui_list(nested_schema, c_value, c_key)
|
|
||||||
else:
|
|
||||||
self._single_ui_option(nested_schema, c_value, c_key)
|
|
||||||
|
|
||||||
ui_node["schema"] = nested_schema
|
ui_node["schema"] = nested_schema
|
||||||
ui_schema.append(ui_node)
|
ui_schema.append(ui_node)
|
||||||
|
@@ -32,6 +32,7 @@ from ..const import (
|
|||||||
ATTR_DISCOVERY,
|
ATTR_DISCOVERY,
|
||||||
ATTR_DOCKER_API,
|
ATTR_DOCKER_API,
|
||||||
ATTR_ENVIRONMENT,
|
ATTR_ENVIRONMENT,
|
||||||
|
ATTR_FIELDS,
|
||||||
ATTR_FULL_ACCESS,
|
ATTR_FULL_ACCESS,
|
||||||
ATTR_GPIO,
|
ATTR_GPIO,
|
||||||
ATTR_HASSIO_API,
|
ATTR_HASSIO_API,
|
||||||
@@ -87,6 +88,7 @@ from ..const import (
|
|||||||
ATTR_TYPE,
|
ATTR_TYPE,
|
||||||
ATTR_UART,
|
ATTR_UART,
|
||||||
ATTR_UDEV,
|
ATTR_UDEV,
|
||||||
|
ATTR_ULIMITS,
|
||||||
ATTR_URL,
|
ATTR_URL,
|
||||||
ATTR_USB,
|
ATTR_USB,
|
||||||
ATTR_USER,
|
ATTR_USER,
|
||||||
@@ -137,7 +139,19 @@ RE_DOCKER_IMAGE_BUILD = re.compile(
|
|||||||
r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)(:[\.\-\w{}]+)?$"
|
r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)(:[\.\-\w{}]+)?$"
|
||||||
)
|
)
|
||||||
|
|
||||||
SCHEMA_ELEMENT = vol.Match(RE_SCHEMA_ELEMENT)
|
SCHEMA_ELEMENT = vol.Schema(
|
||||||
|
vol.Any(
|
||||||
|
vol.Match(RE_SCHEMA_ELEMENT),
|
||||||
|
[
|
||||||
|
# A list may not directly contain another list
|
||||||
|
vol.Any(
|
||||||
|
vol.Match(RE_SCHEMA_ELEMENT),
|
||||||
|
{str: vol.Self},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
{str: vol.Self},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
RE_MACHINE = re.compile(
|
RE_MACHINE = re.compile(
|
||||||
r"^!?(?:"
|
r"^!?(?:"
|
||||||
@@ -266,10 +280,23 @@ def _migrate_addon_config(protocol=False):
|
|||||||
volumes = []
|
volumes = []
|
||||||
for entry in config.get(ATTR_MAP, []):
|
for entry in config.get(ATTR_MAP, []):
|
||||||
if isinstance(entry, dict):
|
if isinstance(entry, dict):
|
||||||
|
# Validate that dict entries have required 'type' field
|
||||||
|
if ATTR_TYPE not in entry:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Add-on config has invalid map entry missing 'type' field: %s. Skipping invalid entry for %s",
|
||||||
|
entry,
|
||||||
|
name,
|
||||||
|
)
|
||||||
|
continue
|
||||||
volumes.append(entry)
|
volumes.append(entry)
|
||||||
if isinstance(entry, str):
|
if isinstance(entry, str):
|
||||||
result = RE_VOLUME.match(entry)
|
result = RE_VOLUME.match(entry)
|
||||||
if not result:
|
if not result:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Add-on config has invalid map entry: %s. Skipping invalid entry for %s",
|
||||||
|
entry,
|
||||||
|
name,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
volumes.append(
|
volumes.append(
|
||||||
{
|
{
|
||||||
@@ -278,8 +305,8 @@ def _migrate_addon_config(protocol=False):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if volumes:
|
# Always update config to clear potentially malformed ones
|
||||||
config[ATTR_MAP] = volumes
|
config[ATTR_MAP] = volumes
|
||||||
|
|
||||||
# 2023-10 "config" became "homeassistant" so /config can be used for addon's public config
|
# 2023-10 "config" became "homeassistant" so /config can be used for addon's public config
|
||||||
if any(volume[ATTR_TYPE] == MappingType.CONFIG for volume in volumes):
|
if any(volume[ATTR_TYPE] == MappingType.CONFIG for volume in volumes):
|
||||||
@@ -393,23 +420,24 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_CODENOTARY): vol.Email(),
|
vol.Optional(ATTR_CODENOTARY): vol.Email(),
|
||||||
vol.Optional(ATTR_OPTIONS, default={}): dict,
|
vol.Optional(ATTR_OPTIONS, default={}): dict,
|
||||||
vol.Optional(ATTR_SCHEMA, default={}): vol.Any(
|
vol.Optional(ATTR_SCHEMA, default={}): vol.Any(
|
||||||
vol.Schema(
|
vol.Schema({str: SCHEMA_ELEMENT}),
|
||||||
{
|
|
||||||
str: vol.Any(
|
|
||||||
SCHEMA_ELEMENT,
|
|
||||||
[
|
|
||||||
vol.Any(
|
|
||||||
SCHEMA_ELEMENT,
|
|
||||||
{str: vol.Any(SCHEMA_ELEMENT, [SCHEMA_ELEMENT])},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
vol.Schema({str: vol.Any(SCHEMA_ELEMENT, [SCHEMA_ELEMENT])}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
False,
|
False,
|
||||||
),
|
),
|
||||||
vol.Optional(ATTR_IMAGE): docker_image,
|
vol.Optional(ATTR_IMAGE): docker_image,
|
||||||
|
vol.Optional(ATTR_ULIMITS, default=dict): vol.Any(
|
||||||
|
{str: vol.Coerce(int)}, # Simple format: {name: limit}
|
||||||
|
{
|
||||||
|
str: vol.Any(
|
||||||
|
vol.Coerce(int), # Simple format for individual entries
|
||||||
|
vol.Schema(
|
||||||
|
{ # Detailed format for individual entries
|
||||||
|
vol.Required("soft"): vol.Coerce(int),
|
||||||
|
vol.Required("hard"): vol.Coerce(int),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
vol.Optional(ATTR_TIMEOUT, default=10): vol.All(
|
vol.Optional(ATTR_TIMEOUT, default=10): vol.All(
|
||||||
vol.Coerce(int), vol.Range(min=10, max=300)
|
vol.Coerce(int), vol.Range(min=10, max=300)
|
||||||
),
|
),
|
||||||
@@ -442,6 +470,7 @@ SCHEMA_TRANSLATION_CONFIGURATION = vol.Schema(
|
|||||||
{
|
{
|
||||||
vol.Required(ATTR_NAME): str,
|
vol.Required(ATTR_NAME): str,
|
||||||
vol.Optional(ATTR_DESCRIPTON): vol.Maybe(str),
|
vol.Optional(ATTR_DESCRIPTON): vol.Maybe(str),
|
||||||
|
vol.Optional(ATTR_FIELDS): {str: vol.Self},
|
||||||
},
|
},
|
||||||
extra=vol.REMOVE_EXTRA,
|
extra=vol.REMOVE_EXTRA,
|
||||||
)
|
)
|
||||||
|
@@ -146,6 +146,14 @@ class RestAPI(CoreSysAttributes):
|
|||||||
follow=True,
|
follow=True,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
web.get(
|
||||||
|
f"{path}/logs/latest",
|
||||||
|
partial(
|
||||||
|
self._api_host.advanced_logs,
|
||||||
|
identifier=syslog_identifier,
|
||||||
|
latest=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
web.get(
|
web.get(
|
||||||
f"{path}/logs/boots/{{bootid}}",
|
f"{path}/logs/boots/{{bootid}}",
|
||||||
partial(self._api_host.advanced_logs, identifier=syslog_identifier),
|
partial(self._api_host.advanced_logs, identifier=syslog_identifier),
|
||||||
@@ -198,6 +206,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.post("/host/reload", api_host.reload),
|
web.post("/host/reload", api_host.reload),
|
||||||
web.post("/host/options", api_host.options),
|
web.post("/host/options", api_host.options),
|
||||||
web.get("/host/services", api_host.services),
|
web.get("/host/services", api_host.services),
|
||||||
|
web.get("/host/disks/default/usage", api_host.disk_usage),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -439,6 +448,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
# is known and reported to the user using the resolution center.
|
# is known and reported to the user using the resolution center.
|
||||||
await async_capture_exception(err)
|
await async_capture_exception(err)
|
||||||
kwargs.pop("follow", None) # Follow is not supported for Docker logs
|
kwargs.pop("follow", None) # Follow is not supported for Docker logs
|
||||||
|
kwargs.pop("latest", None) # Latest is not supported for Docker logs
|
||||||
return await api_supervisor.logs(*args, **kwargs)
|
return await api_supervisor.logs(*args, **kwargs)
|
||||||
|
|
||||||
self.webapp.add_routes(
|
self.webapp.add_routes(
|
||||||
@@ -448,6 +458,10 @@ class RestAPI(CoreSysAttributes):
|
|||||||
"/supervisor/logs/follow",
|
"/supervisor/logs/follow",
|
||||||
partial(get_supervisor_logs, follow=True),
|
partial(get_supervisor_logs, follow=True),
|
||||||
),
|
),
|
||||||
|
web.get(
|
||||||
|
"/supervisor/logs/latest",
|
||||||
|
partial(get_supervisor_logs, latest=True),
|
||||||
|
),
|
||||||
web.get("/supervisor/logs/boots/{bootid}", get_supervisor_logs),
|
web.get("/supervisor/logs/boots/{bootid}", get_supervisor_logs),
|
||||||
web.get(
|
web.get(
|
||||||
"/supervisor/logs/boots/{bootid}/follow",
|
"/supervisor/logs/boots/{bootid}/follow",
|
||||||
@@ -560,6 +574,10 @@ class RestAPI(CoreSysAttributes):
|
|||||||
"/addons/{addon}/logs/follow",
|
"/addons/{addon}/logs/follow",
|
||||||
partial(get_addon_logs, follow=True),
|
partial(get_addon_logs, follow=True),
|
||||||
),
|
),
|
||||||
|
web.get(
|
||||||
|
"/addons/{addon}/logs/latest",
|
||||||
|
partial(get_addon_logs, latest=True),
|
||||||
|
),
|
||||||
web.get("/addons/{addon}/logs/boots/{bootid}", get_addon_logs),
|
web.get("/addons/{addon}/logs/boots/{bootid}", get_addon_logs),
|
||||||
web.get(
|
web.get(
|
||||||
"/addons/{addon}/logs/boots/{bootid}/follow",
|
"/addons/{addon}/logs/boots/{bootid}/follow",
|
||||||
@@ -734,6 +752,10 @@ class RestAPI(CoreSysAttributes):
|
|||||||
"/store/addons/{addon}/documentation",
|
"/store/addons/{addon}/documentation",
|
||||||
api_store.addons_addon_documentation,
|
api_store.addons_addon_documentation,
|
||||||
),
|
),
|
||||||
|
web.get(
|
||||||
|
"/store/addons/{addon}/availability",
|
||||||
|
api_store.addons_addon_availability,
|
||||||
|
),
|
||||||
web.post(
|
web.post(
|
||||||
"/store/addons/{addon}/install", api_store.addons_addon_install
|
"/store/addons/{addon}/install", api_store.addons_addon_install
|
||||||
),
|
),
|
||||||
|
@@ -306,7 +306,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Validate/Process Body
|
# Validate/Process Body
|
||||||
body = await api_validate(addon_schema, request, origin=[ATTR_OPTIONS])
|
body = await api_validate(addon_schema, request)
|
||||||
if ATTR_OPTIONS in body:
|
if ATTR_OPTIONS in body:
|
||||||
addon.options = body[ATTR_OPTIONS]
|
addon.options = body[ATTR_OPTIONS]
|
||||||
if ATTR_BOOT in body:
|
if ATTR_BOOT in body:
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Callable
|
|
||||||
import errno
|
import errno
|
||||||
from io import IOBase
|
from io import IOBase
|
||||||
import logging
|
import logging
|
||||||
@@ -46,12 +45,9 @@ from ..const import (
|
|||||||
ATTR_TYPE,
|
ATTR_TYPE,
|
||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
REQUEST_FROM,
|
REQUEST_FROM,
|
||||||
BusEvent,
|
|
||||||
CoreState,
|
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError, APIForbidden, APINotFound
|
from ..exceptions import APIError, APIForbidden, APINotFound
|
||||||
from ..jobs import JobSchedulerOptions, SupervisorJob
|
|
||||||
from ..mounts.const import MountUsage
|
from ..mounts.const import MountUsage
|
||||||
from ..resolution.const import UnhealthyReason
|
from ..resolution.const import UnhealthyReason
|
||||||
from .const import (
|
from .const import (
|
||||||
@@ -61,7 +57,7 @@ from .const import (
|
|||||||
ATTR_LOCATIONS,
|
ATTR_LOCATIONS,
|
||||||
CONTENT_TYPE_TAR,
|
CONTENT_TYPE_TAR,
|
||||||
)
|
)
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate, background_task
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -289,41 +285,6 @@ class APIBackups(CoreSysAttributes):
|
|||||||
f"Location {LOCATION_CLOUD_BACKUP} is only available for Home Assistant"
|
f"Location {LOCATION_CLOUD_BACKUP} is only available for Home Assistant"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _background_backup_task(
|
|
||||||
self, backup_method: Callable, *args, **kwargs
|
|
||||||
) -> tuple[asyncio.Task, str]:
|
|
||||||
"""Start backup task in background and return task and job ID."""
|
|
||||||
event = asyncio.Event()
|
|
||||||
job, backup_task = cast(
|
|
||||||
tuple[SupervisorJob, asyncio.Task],
|
|
||||||
self.sys_jobs.schedule_job(
|
|
||||||
backup_method, JobSchedulerOptions(), *args, **kwargs
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
async def release_on_freeze(new_state: CoreState):
|
|
||||||
if new_state == CoreState.FREEZE:
|
|
||||||
event.set()
|
|
||||||
|
|
||||||
# Wait for system to get into freeze state before returning
|
|
||||||
# If the backup fails validation it will raise before getting there
|
|
||||||
listener = self.sys_bus.register_event(
|
|
||||||
BusEvent.SUPERVISOR_STATE_CHANGE, release_on_freeze
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
event_task = self.sys_create_task(event.wait())
|
|
||||||
_, pending = await asyncio.wait(
|
|
||||||
(backup_task, event_task),
|
|
||||||
return_when=asyncio.FIRST_COMPLETED,
|
|
||||||
)
|
|
||||||
# It seems backup returned early (error or something), make sure to cancel
|
|
||||||
# the event task to avoid "Task was destroyed but it is pending!" errors.
|
|
||||||
if event_task in pending:
|
|
||||||
event_task.cancel()
|
|
||||||
return (backup_task, job.uuid)
|
|
||||||
finally:
|
|
||||||
self.sys_bus.remove_listener(listener)
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def backup_full(self, request: web.Request):
|
async def backup_full(self, request: web.Request):
|
||||||
"""Create full backup."""
|
"""Create full backup."""
|
||||||
@@ -342,8 +303,8 @@ class APIBackups(CoreSysAttributes):
|
|||||||
body[ATTR_ADDITIONAL_LOCATIONS] = locations
|
body[ATTR_ADDITIONAL_LOCATIONS] = locations
|
||||||
|
|
||||||
background = body.pop(ATTR_BACKGROUND)
|
background = body.pop(ATTR_BACKGROUND)
|
||||||
backup_task, job_id = await self._background_backup_task(
|
backup_task, job_id = await background_task(
|
||||||
self.sys_backups.do_backup_full, **body
|
self, self.sys_backups.do_backup_full, **body
|
||||||
)
|
)
|
||||||
|
|
||||||
if background and not backup_task.done():
|
if background and not backup_task.done():
|
||||||
@@ -378,8 +339,8 @@ class APIBackups(CoreSysAttributes):
|
|||||||
body[ATTR_ADDONS] = list(self.sys_addons.local)
|
body[ATTR_ADDONS] = list(self.sys_addons.local)
|
||||||
|
|
||||||
background = body.pop(ATTR_BACKGROUND)
|
background = body.pop(ATTR_BACKGROUND)
|
||||||
backup_task, job_id = await self._background_backup_task(
|
backup_task, job_id = await background_task(
|
||||||
self.sys_backups.do_backup_partial, **body
|
self, self.sys_backups.do_backup_partial, **body
|
||||||
)
|
)
|
||||||
|
|
||||||
if background and not backup_task.done():
|
if background and not backup_task.done():
|
||||||
@@ -402,8 +363,8 @@ class APIBackups(CoreSysAttributes):
|
|||||||
request, body.get(ATTR_LOCATION, backup.location)
|
request, body.get(ATTR_LOCATION, backup.location)
|
||||||
)
|
)
|
||||||
background = body.pop(ATTR_BACKGROUND)
|
background = body.pop(ATTR_BACKGROUND)
|
||||||
restore_task, job_id = await self._background_backup_task(
|
restore_task, job_id = await background_task(
|
||||||
self.sys_backups.do_restore_full, backup, **body
|
self, self.sys_backups.do_restore_full, backup, **body
|
||||||
)
|
)
|
||||||
|
|
||||||
if background and not restore_task.done() or await restore_task:
|
if background and not restore_task.done() or await restore_task:
|
||||||
@@ -422,8 +383,8 @@ class APIBackups(CoreSysAttributes):
|
|||||||
request, body.get(ATTR_LOCATION, backup.location)
|
request, body.get(ATTR_LOCATION, backup.location)
|
||||||
)
|
)
|
||||||
background = body.pop(ATTR_BACKGROUND)
|
background = body.pop(ATTR_BACKGROUND)
|
||||||
restore_task, job_id = await self._background_backup_task(
|
restore_task, job_id = await background_task(
|
||||||
self.sys_backups.do_restore_partial, backup, **body
|
self, self.sys_backups.do_restore_partial, backup, **body
|
||||||
)
|
)
|
||||||
|
|
||||||
if background and not restore_task.done() or await restore_task:
|
if background and not restore_task.done() or await restore_task:
|
||||||
|
@@ -49,6 +49,7 @@ ATTR_LLMNR_HOSTNAME = "llmnr_hostname"
|
|||||||
ATTR_LOCAL_ONLY = "local_only"
|
ATTR_LOCAL_ONLY = "local_only"
|
||||||
ATTR_LOCATION_ATTRIBUTES = "location_attributes"
|
ATTR_LOCATION_ATTRIBUTES = "location_attributes"
|
||||||
ATTR_LOCATIONS = "locations"
|
ATTR_LOCATIONS = "locations"
|
||||||
|
ATTR_MAX_DEPTH = "max_depth"
|
||||||
ATTR_MDNS = "mdns"
|
ATTR_MDNS = "mdns"
|
||||||
ATTR_MODEL = "model"
|
ATTR_MODEL = "model"
|
||||||
ATTR_MOUNTS = "mounts"
|
ATTR_MOUNTS = "mounts"
|
||||||
|
@@ -12,6 +12,7 @@ from ..const import (
|
|||||||
ATTR_ENABLE_IPV6,
|
ATTR_ENABLE_IPV6,
|
||||||
ATTR_HOSTNAME,
|
ATTR_HOSTNAME,
|
||||||
ATTR_LOGGING,
|
ATTR_LOGGING,
|
||||||
|
ATTR_MTU,
|
||||||
ATTR_PASSWORD,
|
ATTR_PASSWORD,
|
||||||
ATTR_REGISTRIES,
|
ATTR_REGISTRIES,
|
||||||
ATTR_STORAGE,
|
ATTR_STORAGE,
|
||||||
@@ -34,7 +35,12 @@ SCHEMA_DOCKER_REGISTRY = vol.Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_ENABLE_IPV6): vol.Maybe(vol.Boolean())})
|
SCHEMA_OPTIONS = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_ENABLE_IPV6): vol.Maybe(vol.Boolean()),
|
||||||
|
vol.Optional(ATTR_MTU): vol.Maybe(vol.All(int, vol.Range(min=68, max=65535))),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class APIDocker(CoreSysAttributes):
|
class APIDocker(CoreSysAttributes):
|
||||||
@@ -51,6 +57,7 @@ class APIDocker(CoreSysAttributes):
|
|||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_docker.info.version,
|
ATTR_VERSION: self.sys_docker.info.version,
|
||||||
ATTR_ENABLE_IPV6: self.sys_docker.config.enable_ipv6,
|
ATTR_ENABLE_IPV6: self.sys_docker.config.enable_ipv6,
|
||||||
|
ATTR_MTU: self.sys_docker.config.mtu,
|
||||||
ATTR_STORAGE: self.sys_docker.info.storage,
|
ATTR_STORAGE: self.sys_docker.info.storage,
|
||||||
ATTR_LOGGING: self.sys_docker.info.logging,
|
ATTR_LOGGING: self.sys_docker.info.logging,
|
||||||
ATTR_REGISTRIES: data_registries,
|
ATTR_REGISTRIES: data_registries,
|
||||||
@@ -61,12 +68,23 @@ class APIDocker(CoreSysAttributes):
|
|||||||
"""Set docker options."""
|
"""Set docker options."""
|
||||||
body = await api_validate(SCHEMA_OPTIONS, request)
|
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||||
|
|
||||||
|
reboot_required = False
|
||||||
|
|
||||||
if (
|
if (
|
||||||
ATTR_ENABLE_IPV6 in body
|
ATTR_ENABLE_IPV6 in body
|
||||||
and self.sys_docker.config.enable_ipv6 != body[ATTR_ENABLE_IPV6]
|
and self.sys_docker.config.enable_ipv6 != body[ATTR_ENABLE_IPV6]
|
||||||
):
|
):
|
||||||
self.sys_docker.config.enable_ipv6 = body[ATTR_ENABLE_IPV6]
|
self.sys_docker.config.enable_ipv6 = body[ATTR_ENABLE_IPV6]
|
||||||
_LOGGER.info("Host system reboot required to apply new IPv6 configuration")
|
reboot_required = True
|
||||||
|
|
||||||
|
if ATTR_MTU in body and self.sys_docker.config.mtu != body[ATTR_MTU]:
|
||||||
|
self.sys_docker.config.mtu = body[ATTR_MTU]
|
||||||
|
reboot_required = True
|
||||||
|
|
||||||
|
if reboot_required:
|
||||||
|
_LOGGER.info(
|
||||||
|
"Host system reboot required to apply Docker configuration changes"
|
||||||
|
)
|
||||||
self.sys_resolution.create_issue(
|
self.sys_resolution.create_issue(
|
||||||
IssueType.REBOOT_REQUIRED,
|
IssueType.REBOOT_REQUIRED,
|
||||||
ContextType.SYSTEM,
|
ContextType.SYSTEM,
|
||||||
|
@@ -20,6 +20,7 @@ from ..const import (
|
|||||||
ATTR_CPU_PERCENT,
|
ATTR_CPU_PERCENT,
|
||||||
ATTR_IMAGE,
|
ATTR_IMAGE,
|
||||||
ATTR_IP_ADDRESS,
|
ATTR_IP_ADDRESS,
|
||||||
|
ATTR_JOB_ID,
|
||||||
ATTR_MACHINE,
|
ATTR_MACHINE,
|
||||||
ATTR_MEMORY_LIMIT,
|
ATTR_MEMORY_LIMIT,
|
||||||
ATTR_MEMORY_PERCENT,
|
ATTR_MEMORY_PERCENT,
|
||||||
@@ -37,8 +38,8 @@ from ..const import (
|
|||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIDBMigrationInProgress, APIError
|
from ..exceptions import APIDBMigrationInProgress, APIError
|
||||||
from ..validate import docker_image, network_port, version_tag
|
from ..validate import docker_image, network_port, version_tag
|
||||||
from .const import ATTR_FORCE, ATTR_SAFE_MODE
|
from .const import ATTR_BACKGROUND, ATTR_FORCE, ATTR_SAFE_MODE
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate, background_task
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ SCHEMA_UPDATE = vol.Schema(
|
|||||||
{
|
{
|
||||||
vol.Optional(ATTR_VERSION): version_tag,
|
vol.Optional(ATTR_VERSION): version_tag,
|
||||||
vol.Optional(ATTR_BACKUP): bool,
|
vol.Optional(ATTR_BACKUP): bool,
|
||||||
|
vol.Optional(ATTR_BACKGROUND, default=False): bool,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -170,18 +172,24 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def update(self, request: web.Request) -> None:
|
async def update(self, request: web.Request) -> dict[str, str] | None:
|
||||||
"""Update Home Assistant."""
|
"""Update Home Assistant."""
|
||||||
body = await api_validate(SCHEMA_UPDATE, request)
|
body = await api_validate(SCHEMA_UPDATE, request)
|
||||||
await self._check_offline_migration()
|
await self._check_offline_migration()
|
||||||
|
|
||||||
await asyncio.shield(
|
background = body[ATTR_BACKGROUND]
|
||||||
self.sys_homeassistant.core.update(
|
update_task, job_id = await background_task(
|
||||||
version=body.get(ATTR_VERSION, self.sys_homeassistant.latest_version),
|
self,
|
||||||
backup=body.get(ATTR_BACKUP),
|
self.sys_homeassistant.core.update,
|
||||||
)
|
version=body.get(ATTR_VERSION, self.sys_homeassistant.latest_version),
|
||||||
|
backup=body.get(ATTR_BACKUP),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if background and not update_task.done():
|
||||||
|
return {ATTR_JOB_ID: job_id}
|
||||||
|
|
||||||
|
return await update_task
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stop(self, request: web.Request) -> Awaitable[None]:
|
async def stop(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Stop Home Assistant."""
|
"""Stop Home Assistant."""
|
||||||
|
@@ -2,10 +2,17 @@
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import ClientConnectionResetError, ClientPayloadError, web
|
from aiohttp import (
|
||||||
|
ClientConnectionResetError,
|
||||||
|
ClientError,
|
||||||
|
ClientPayloadError,
|
||||||
|
ClientTimeout,
|
||||||
|
web,
|
||||||
|
)
|
||||||
from aiohttp.hdrs import ACCEPT, RANGE
|
from aiohttp.hdrs import ACCEPT, RANGE
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from voluptuous.error import CoerceInvalid
|
from voluptuous.error import CoerceInvalid
|
||||||
@@ -51,6 +58,7 @@ from .const import (
|
|||||||
ATTR_FORCE,
|
ATTR_FORCE,
|
||||||
ATTR_IDENTIFIERS,
|
ATTR_IDENTIFIERS,
|
||||||
ATTR_LLMNR_HOSTNAME,
|
ATTR_LLMNR_HOSTNAME,
|
||||||
|
ATTR_MAX_DEPTH,
|
||||||
ATTR_STARTUP_TIME,
|
ATTR_STARTUP_TIME,
|
||||||
ATTR_USE_NTP,
|
ATTR_USE_NTP,
|
||||||
ATTR_VIRTUALIZATION,
|
ATTR_VIRTUALIZATION,
|
||||||
@@ -193,7 +201,11 @@ class APIHost(CoreSysAttributes):
|
|||||||
return possible_offset
|
return possible_offset
|
||||||
|
|
||||||
async def advanced_logs_handler(
|
async def advanced_logs_handler(
|
||||||
self, request: web.Request, identifier: str | None = None, follow: bool = False
|
self,
|
||||||
|
request: web.Request,
|
||||||
|
identifier: str | None = None,
|
||||||
|
follow: bool = False,
|
||||||
|
latest: bool = False,
|
||||||
) -> web.StreamResponse:
|
) -> web.StreamResponse:
|
||||||
"""Return systemd-journald logs."""
|
"""Return systemd-journald logs."""
|
||||||
log_formatter = LogFormatter.PLAIN
|
log_formatter = LogFormatter.PLAIN
|
||||||
@@ -212,6 +224,20 @@ class APIHost(CoreSysAttributes):
|
|||||||
if follow:
|
if follow:
|
||||||
params[PARAM_FOLLOW] = ""
|
params[PARAM_FOLLOW] = ""
|
||||||
|
|
||||||
|
if latest:
|
||||||
|
if not identifier:
|
||||||
|
raise APIError(
|
||||||
|
"Latest logs can only be fetched for a specific identifier."
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
epoch = await self._get_container_last_epoch(identifier)
|
||||||
|
params["CONTAINER_LOG_EPOCH"] = epoch
|
||||||
|
except HostLogError as err:
|
||||||
|
raise APIError(
|
||||||
|
f"Cannot determine CONTAINER_LOG_EPOCH of {identifier}, latest logs not available."
|
||||||
|
) from err
|
||||||
|
|
||||||
if ACCEPT in request.headers and request.headers[ACCEPT] not in [
|
if ACCEPT in request.headers and request.headers[ACCEPT] not in [
|
||||||
CONTENT_TYPE_TEXT,
|
CONTENT_TYPE_TEXT,
|
||||||
CONTENT_TYPE_X_LOG,
|
CONTENT_TYPE_X_LOG,
|
||||||
@@ -240,6 +266,8 @@ class APIHost(CoreSysAttributes):
|
|||||||
lines = max(2, lines)
|
lines = max(2, lines)
|
||||||
# entries=cursor[[:num_skip]:num_entries]
|
# entries=cursor[[:num_skip]:num_entries]
|
||||||
range_header = f"entries=:-{lines - 1}:{SYSTEMD_JOURNAL_GATEWAYD_LINES_MAX if follow else lines}"
|
range_header = f"entries=:-{lines - 1}:{SYSTEMD_JOURNAL_GATEWAYD_LINES_MAX if follow else lines}"
|
||||||
|
elif latest:
|
||||||
|
range_header = f"entries=:0:{SYSTEMD_JOURNAL_GATEWAYD_LINES_MAX}"
|
||||||
elif RANGE in request.headers:
|
elif RANGE in request.headers:
|
||||||
range_header = request.headers[RANGE]
|
range_header = request.headers[RANGE]
|
||||||
else:
|
else:
|
||||||
@@ -285,7 +313,81 @@ class APIHost(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_TEXT, error_type=CONTENT_TYPE_TEXT)
|
@api_process_raw(CONTENT_TYPE_TEXT, error_type=CONTENT_TYPE_TEXT)
|
||||||
async def advanced_logs(
|
async def advanced_logs(
|
||||||
self, request: web.Request, identifier: str | None = None, follow: bool = False
|
self,
|
||||||
|
request: web.Request,
|
||||||
|
identifier: str | None = None,
|
||||||
|
follow: bool = False,
|
||||||
|
latest: bool = False,
|
||||||
) -> web.StreamResponse:
|
) -> web.StreamResponse:
|
||||||
"""Return systemd-journald logs. Wrapped as standard API handler."""
|
"""Return systemd-journald logs. Wrapped as standard API handler."""
|
||||||
return await self.advanced_logs_handler(request, identifier, follow)
|
return await self.advanced_logs_handler(request, identifier, follow, latest)
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def disk_usage(self, request: web.Request) -> dict:
|
||||||
|
"""Return a breakdown of storage usage for the system."""
|
||||||
|
|
||||||
|
max_depth = request.query.get(ATTR_MAX_DEPTH, 1)
|
||||||
|
try:
|
||||||
|
max_depth = int(max_depth)
|
||||||
|
except ValueError:
|
||||||
|
max_depth = 1
|
||||||
|
|
||||||
|
disk = self.sys_hardware.disk
|
||||||
|
|
||||||
|
total, used, _ = await self.sys_run_in_executor(
|
||||||
|
disk.disk_usage, self.sys_config.path_supervisor
|
||||||
|
)
|
||||||
|
|
||||||
|
known_paths = await self.sys_run_in_executor(
|
||||||
|
disk.get_dir_sizes,
|
||||||
|
{
|
||||||
|
"addons_data": self.sys_config.path_addons_data,
|
||||||
|
"addons_config": self.sys_config.path_addon_configs,
|
||||||
|
"media": self.sys_config.path_media,
|
||||||
|
"share": self.sys_config.path_share,
|
||||||
|
"backup": self.sys_config.path_backup,
|
||||||
|
"ssl": self.sys_config.path_ssl,
|
||||||
|
"homeassistant": self.sys_config.path_homeassistant,
|
||||||
|
},
|
||||||
|
max_depth,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
# this can be the disk/partition ID in the future
|
||||||
|
"id": "root",
|
||||||
|
"label": "Root",
|
||||||
|
"total_bytes": total,
|
||||||
|
"used_bytes": used,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "system",
|
||||||
|
"label": "System",
|
||||||
|
"used_bytes": used
|
||||||
|
- sum(path["used_bytes"] for path in known_paths),
|
||||||
|
},
|
||||||
|
*known_paths,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _get_container_last_epoch(self, identifier: str) -> str | None:
|
||||||
|
"""Get Docker's internal log epoch of the latest log entry for the given identifier."""
|
||||||
|
try:
|
||||||
|
async with self.sys_host.logs.journald_logs(
|
||||||
|
params={"CONTAINER_NAME": identifier},
|
||||||
|
range_header="entries=:-1:2", # -1 = next to the last entry
|
||||||
|
accept=LogFormat.JSON,
|
||||||
|
timeout=ClientTimeout(total=10),
|
||||||
|
) as resp:
|
||||||
|
text = await resp.text()
|
||||||
|
except (ClientError, TimeoutError) as err:
|
||||||
|
raise HostLogError(
|
||||||
|
"Could not get last container epoch from systemd-journal-gatewayd",
|
||||||
|
_LOGGER.error,
|
||||||
|
) from err
|
||||||
|
|
||||||
|
try:
|
||||||
|
return json.loads(text.strip().split("\n")[-1])["CONTAINER_LOG_EPOCH"]
|
||||||
|
except (json.JSONDecodeError, KeyError, IndexError) as err:
|
||||||
|
raise HostLogError(
|
||||||
|
f"Failed to parse CONTAINER_LOG_EPOCH of {identifier} container, got: {text}",
|
||||||
|
_LOGGER.error,
|
||||||
|
) from err
|
||||||
|
@@ -199,21 +199,25 @@ class APIIngress(CoreSysAttributes):
|
|||||||
url = f"{url}?{request.query_string}"
|
url = f"{url}?{request.query_string}"
|
||||||
|
|
||||||
# Start proxy
|
# Start proxy
|
||||||
async with self.sys_websession.ws_connect(
|
try:
|
||||||
url,
|
_LOGGER.debug("Proxing WebSocket to %s, upstream url: %s", addon.slug, url)
|
||||||
headers=source_header,
|
async with self.sys_websession.ws_connect(
|
||||||
protocols=req_protocols,
|
url,
|
||||||
autoclose=False,
|
headers=source_header,
|
||||||
autoping=False,
|
protocols=req_protocols,
|
||||||
) as ws_client:
|
autoclose=False,
|
||||||
# Proxy requests
|
autoping=False,
|
||||||
await asyncio.wait(
|
) as ws_client:
|
||||||
[
|
# Proxy requests
|
||||||
self.sys_create_task(_websocket_forward(ws_server, ws_client)),
|
await asyncio.wait(
|
||||||
self.sys_create_task(_websocket_forward(ws_client, ws_server)),
|
[
|
||||||
],
|
self.sys_create_task(_websocket_forward(ws_server, ws_client)),
|
||||||
return_when=asyncio.FIRST_COMPLETED,
|
self.sys_create_task(_websocket_forward(ws_client, ws_server)),
|
||||||
)
|
],
|
||||||
|
return_when=asyncio.FIRST_COMPLETED,
|
||||||
|
)
|
||||||
|
except TimeoutError:
|
||||||
|
_LOGGER.warning("WebSocket proxy to %s timed out", addon.slug)
|
||||||
|
|
||||||
return ws_server
|
return ws_server
|
||||||
|
|
||||||
@@ -286,6 +290,7 @@ class APIIngress(CoreSysAttributes):
|
|||||||
aiohttp.ClientError,
|
aiohttp.ClientError,
|
||||||
aiohttp.ClientPayloadError,
|
aiohttp.ClientPayloadError,
|
||||||
ConnectionResetError,
|
ConnectionResetError,
|
||||||
|
ConnectionError,
|
||||||
) as err:
|
) as err:
|
||||||
_LOGGER.error("Stream error with %s: %s", url, err)
|
_LOGGER.error("Stream error with %s: %s", url, err)
|
||||||
|
|
||||||
@@ -386,9 +391,9 @@ async def _websocket_forward(ws_from, ws_to):
|
|||||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||||
await ws_to.send_bytes(msg.data)
|
await ws_to.send_bytes(msg.data)
|
||||||
elif msg.type == aiohttp.WSMsgType.PING:
|
elif msg.type == aiohttp.WSMsgType.PING:
|
||||||
await ws_to.ping()
|
await ws_to.ping(msg.data)
|
||||||
elif msg.type == aiohttp.WSMsgType.PONG:
|
elif msg.type == aiohttp.WSMsgType.PONG:
|
||||||
await ws_to.pong()
|
await ws_to.pong(msg.data)
|
||||||
elif ws_to.closed:
|
elif ws_to.closed:
|
||||||
await ws_to.close(code=ws_to.close_code, message=msg.extra)
|
await ws_to.close(code=ws_to.close_code, message=msg.extra)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
|
@@ -26,7 +26,9 @@ from ..const import (
|
|||||||
ATTR_IP6_PRIVACY,
|
ATTR_IP6_PRIVACY,
|
||||||
ATTR_IPV4,
|
ATTR_IPV4,
|
||||||
ATTR_IPV6,
|
ATTR_IPV6,
|
||||||
|
ATTR_LLMNR,
|
||||||
ATTR_MAC,
|
ATTR_MAC,
|
||||||
|
ATTR_MDNS,
|
||||||
ATTR_METHOD,
|
ATTR_METHOD,
|
||||||
ATTR_MODE,
|
ATTR_MODE,
|
||||||
ATTR_NAMESERVERS,
|
ATTR_NAMESERVERS,
|
||||||
@@ -54,6 +56,7 @@ from ..host.configuration import (
|
|||||||
Ip6Setting,
|
Ip6Setting,
|
||||||
IpConfig,
|
IpConfig,
|
||||||
IpSetting,
|
IpSetting,
|
||||||
|
MulticastDnsMode,
|
||||||
VlanConfig,
|
VlanConfig,
|
||||||
WifiConfig,
|
WifiConfig,
|
||||||
)
|
)
|
||||||
@@ -97,6 +100,8 @@ SCHEMA_UPDATE = vol.Schema(
|
|||||||
vol.Optional(ATTR_IPV6): _SCHEMA_IPV6_CONFIG,
|
vol.Optional(ATTR_IPV6): _SCHEMA_IPV6_CONFIG,
|
||||||
vol.Optional(ATTR_WIFI): _SCHEMA_WIFI_CONFIG,
|
vol.Optional(ATTR_WIFI): _SCHEMA_WIFI_CONFIG,
|
||||||
vol.Optional(ATTR_ENABLED): vol.Boolean(),
|
vol.Optional(ATTR_ENABLED): vol.Boolean(),
|
||||||
|
vol.Optional(ATTR_MDNS): vol.Coerce(MulticastDnsMode),
|
||||||
|
vol.Optional(ATTR_LLMNR): vol.Coerce(MulticastDnsMode),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -160,6 +165,8 @@ def interface_struct(interface: Interface) -> dict[str, Any]:
|
|||||||
else None,
|
else None,
|
||||||
ATTR_WIFI: wifi_struct(interface.wifi) if interface.wifi else None,
|
ATTR_WIFI: wifi_struct(interface.wifi) if interface.wifi else None,
|
||||||
ATTR_VLAN: vlan_struct(interface.vlan) if interface.vlan else None,
|
ATTR_VLAN: vlan_struct(interface.vlan) if interface.vlan else None,
|
||||||
|
ATTR_MDNS: interface.mdns,
|
||||||
|
ATTR_LLMNR: interface.llmnr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -260,6 +267,10 @@ class APINetwork(CoreSysAttributes):
|
|||||||
)
|
)
|
||||||
elif key == ATTR_ENABLED:
|
elif key == ATTR_ENABLED:
|
||||||
interface.enabled = config
|
interface.enabled = config
|
||||||
|
elif key == ATTR_MDNS:
|
||||||
|
interface.mdns = config
|
||||||
|
elif key == ATTR_LLMNR:
|
||||||
|
interface.llmnr = config
|
||||||
|
|
||||||
await asyncio.shield(self.sys_host.network.apply_changes(interface))
|
await asyncio.shield(self.sys_host.network.apply_changes(interface))
|
||||||
|
|
||||||
@@ -300,6 +311,15 @@ class APINetwork(CoreSysAttributes):
|
|||||||
|
|
||||||
vlan_config = VlanConfig(vlan, interface.name)
|
vlan_config = VlanConfig(vlan, interface.name)
|
||||||
|
|
||||||
|
mdns_mode = MulticastDnsMode.DEFAULT
|
||||||
|
llmnr_mode = MulticastDnsMode.DEFAULT
|
||||||
|
|
||||||
|
if ATTR_MDNS in body:
|
||||||
|
mdns_mode = body[ATTR_MDNS]
|
||||||
|
|
||||||
|
if ATTR_LLMNR in body:
|
||||||
|
llmnr_mode = body[ATTR_LLMNR]
|
||||||
|
|
||||||
ipv4_setting = None
|
ipv4_setting = None
|
||||||
if ATTR_IPV4 in body:
|
if ATTR_IPV4 in body:
|
||||||
ipv4_setting = IpSetting(
|
ipv4_setting = IpSetting(
|
||||||
@@ -325,7 +345,7 @@ class APINetwork(CoreSysAttributes):
|
|||||||
)
|
)
|
||||||
|
|
||||||
vlan_interface = Interface(
|
vlan_interface = Interface(
|
||||||
"",
|
f"{interface.name}.{vlan}",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
True,
|
True,
|
||||||
@@ -338,5 +358,7 @@ class APINetwork(CoreSysAttributes):
|
|||||||
ipv6_setting,
|
ipv6_setting,
|
||||||
None,
|
None,
|
||||||
vlan_config,
|
vlan_config,
|
||||||
|
mdns=mdns_mode,
|
||||||
|
llmnr=llmnr_mode,
|
||||||
)
|
)
|
||||||
await asyncio.shield(self.sys_host.network.apply_changes(vlan_interface))
|
await asyncio.shield(self.sys_host.network.create_vlan(vlan_interface))
|
||||||
|
@@ -1 +1 @@
|
|||||||
!function(){function d(d){var e=document.createElement("script");e.src=d,document.body.appendChild(e)}if(/Edge?\/(12[89]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Firefox\/(12[89]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Chrom(ium|e)\/(109|1[1-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|(Maci|X1{2}).+ Version\/(18\.\d+|(19|[2-9]\d|\d{3,})\.\d+)([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/(1{2}[3-9]|1[2-9]\d|[2-9]\d{2}|\d{4,})\.\d+\.\d+|(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(16[._]([6-9]|\d{2,})|(1[7-9]|[2-9]\d|\d{3,})[._]\d+)([._]\d+|)|Android:?[ /-](12[89]|1[3-9]\d|[2-9]\d{2}|\d{4,})(\.\d+|)(\.\d+|)|Mobile Safari.+OPR\/([89]\d|\d{3,})\.\d+\.\d+|Android.+Firefox\/(13\d|1[4-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Android.+Chrom(ium|e)\/(12[89]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|SamsungBrowser\/(2[7-9]|[3-9]\d|\d{3,})\.\d+|Home As{2}istant\/[\d.]+ \(.+; macOS (1[3-9]|[2-9]\d|\d{3,})\.\d+(\.\d+)?\)/.test(navigator.userAgent))try{new Function("import('/api/hassio/app/frontend_latest/entrypoint.9b475c5882bbf15f.js')")()}catch(e){d("/api/hassio/app/frontend_es5/entrypoint.73551a66a38f3359.js")}else d("/api/hassio/app/frontend_es5/entrypoint.73551a66a38f3359.js")}()
|
!function(){function d(d){var e=document.createElement("script");e.src=d,document.body.appendChild(e)}if(/Edge?\/(13\d|1[4-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Firefox\/(13[2-9]|1[4-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Chrom(ium|e)\/(10[5-9]|1[1-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|(Maci|X1{2}).+ Version\/(18\.([1-9]|\d{2,})|(19|[2-9]\d|\d{3,})\.\d+)([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/(1{2}[5-9]|1[2-9]\d|[2-9]\d{2}|\d{4,})\.\d+\.\d+|(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(18[._]([1-9]|\d{2,})|(19|[2-9]\d|\d{3,})[._]\d+)([._]\d+|)|Android:?[ /-](13\d|1[4-9]\d|[2-9]\d{2}|\d{4,})(\.\d+|)(\.\d+|)|Mobile Safari.+OPR\/([89]\d|\d{3,})\.\d+\.\d+|Android.+Firefox\/(13[2-9]|1[4-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Android.+Chrom(ium|e)\/(13\d|1[4-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|SamsungBrowser\/(2[89]|[3-9]\d|\d{3,})\.\d+|Home As{2}istant\/[\d.]+ \(.+; macOS (1[3-9]|[2-9]\d|\d{3,})\.\d+(\.\d+)?\)/.test(navigator.userAgent))try{new Function("import('/api/hassio/app/frontend_latest/entrypoint.3e96fb75803214a8.js')")()}catch(e){d("/api/hassio/app/frontend_es5/entrypoint.2bf70759ca3f252f.js")}else d("/api/hassio/app/frontend_es5/entrypoint.2bf70759ca3f252f.js")}()
|
Binary file not shown.
Binary file not shown.
2
supervisor/api/panel/frontend_es5/10.02c74d8ffd9bf568.js
Normal file
2
supervisor/api/panel/frontend_es5/10.02c74d8ffd9bf568.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/10.02c74d8ffd9bf568.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/10.02c74d8ffd9bf568.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/10.02c74d8ffd9bf568.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/10.02c74d8ffd9bf568.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1008.c2e44b88f5829db4.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/1008.c2e44b88f5829db4.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/1008.c2e44b88f5829db4.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1008.c2e44b88f5829db4.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1057.d306824fd6aa0497.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/1057.d306824fd6aa0497.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/1057.d306824fd6aa0497.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1057.d306824fd6aa0497.js.gz
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"1057.d306824fd6aa0497.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20251001.2/src/data/auth.ts","https://raw.githubusercontent.com/home-assistant/frontend/20251001.2/src/data/entity.ts","https://raw.githubusercontent.com/home-assistant/frontend/20251001.2/src/data/media-player.ts","https://raw.githubusercontent.com/home-assistant/frontend/20251001.2/src/data/tts.ts","https://raw.githubusercontent.com/home-assistant/frontend/20251001.2/src/util/brands-url.ts"],"names":["autocompleteLoginFields","schema","map","field","type","name","Object","assign","autocomplete","autofocus","getSignedPath","hass","path","callWS","UNAVAILABLE","UNKNOWN","ON","OFF","UNAVAILABLE_STATES","OFF_STATES","isUnavailableState","arrayLiteralIncludes","MediaPlayerEntityFeature","BROWSER_PLAYER","MediaClassBrowserSettings","album","icon","layout","app","show_list_images","artist","mdiAccountMusic","channel","mdiTelevisionClassic","thumbnail_ratio","composer","contributing_artist","directory","episode","game","genre","image","movie","music","playlist","podcast","season","track","tv_show","url","video","browseMediaPlayer","entityId","mediaContentId","mediaContentType","entity_id","media_content_id","media_content_type","convertTextToSpeech","data","callApi","TTS_MEDIA_SOURCE_PREFIX","isTTSMediaSource","startsWith","getProviderFromTTSMediaSource","substring","listTTSEngines","language","country","getTTSEngine","engine_id","listTTSVoices","brandsUrl","options","brand","useFallback","domain","darkOptimized","extractDomainFromBrandUrl","split","isBrandUrl","thumbnail"],"mappings":"2QAyBO,MAEMA,EAA2BC,GACtCA,EAAOC,IAAKC,IACV,GAAmB,WAAfA,EAAMC,KAAmB,OAAOD,EACpC,OAAQA,EAAME,MACZ,IAAK,WACH,OAAAC,OAAAC,OAAAD,OAAAC,OAAA,GAAYJ,GAAK,IAAEK,aAAc,WAAYC,WAAW,IAC1D,IAAK,WACH,OAAAH,OAAAC,OAAAD,OAAAC,OAAA,GAAYJ,GAAK,IAAEK,aAAc,qBACnC,IAAK,OACH,OAAAF,OAAAC,OAAAD,OAAAC,OAAA,GAAYJ,GAAK,IAAEK,aAAc,gBAAiBC,WAAW,IAC/D,QACE,OAAON,KAIFO,EAAgBA,CAC3BC,EACAC,IACwBD,EAAKE,OAAO,CAAET,KAAM,iBAAkBQ,Q,gMC3CzD,MAAME,EAAc,cACdC,EAAU,UACVC,EAAK,KACLC,EAAM,MAENC,EAAqB,CAACJ,EAAaC,GACnCI,EAAa,CAACL,EAAaC,EAASE,GAEpCG,GAAqBC,EAAAA,EAAAA,GAAqBH,IAC7BG,EAAAA,EAAAA,GAAqBF,E,+gCCuExC,IAAWG,EAAA,SAAAA,G,qnBAAAA,C,CAAA,C,IAyBX,MAAMC,EAAiB,UAWjBC,EAGT,CACFC,MAAO,CAAEC,K,mQAAgBC,OAAQ,QACjCC,IAAK,CAAEF,K,6GAAsBC,OAAQ,OAAQE,kBAAkB,GAC/DC,OAAQ,CAAEJ,KAAMK,EAAiBJ,OAAQ,OAAQE,kBAAkB,GACnEG,QAAS,CACPN,KAAMO,EACNC,gBAAiB,WACjBP,OAAQ,OACRE,kBAAkB,GAEpBM,SAAU,CACRT,K,4cACAC,OAAQ,OACRE,kBAAkB,GAEpBO,oBAAqB,CACnBV,KAAMK,EACNJ,OAAQ,OACRE,kBAAkB,GAEpBQ,UAAW,CAAEX,K,gGAAiBC,OAAQ,OAAQE,kBAAkB,GAChES,QAAS,CACPZ,KAAMO,EACNN,OAAQ,OACRO,gBAAiB,WACjBL,kBAAkB,GAEpBU,KAAM,CACJb,K,qWACAC,OAAQ,OACRO,gBAAiB,YAEnBM,MAAO,CAAEd,K,4hCAAqBC,OAAQ,OAAQE,kBAAkB,GAChEY,MAAO,CAAEf,K,sHAAgBC,OAAQ,OAAQE,kBAAkB,GAC3Da,MAAO,CACLhB,K,6GACAQ,gBAAiB,WACjBP,OAAQ,OACRE,kBAAkB,GAEpBc,MAAO,CAAEjB,K,+NAAgBG,kBAAkB,GAC3Ce,SAAU,CAAElB,K,mJAAwBC,OAAQ,OAAQE,kBAAkB,GACtEgB,QAAS,CAAEnB,K,qpBAAkBC,OAAQ,QACrCmB,OAAQ,CACNpB,KAAMO,EACNN,OAAQ,OACRO,gBAAiB,WACjBL,kBAAkB,GAEpBkB,MAAO,CAAErB,K,mLACTsB,QAAS,CACPtB,KAAMO,EACNN,OAAQ,OACRO,gBAAiB,YAEnBe,IAAK,CAAEvB,K,w5BACPwB,MAAO,CAAExB,K,2GAAgBC,OAAQ,OAAQE,kBAAkB,IAkChDsB,EAAoBA,CAC/BxC,EACAyC,EACAC,EACAC,IAEA3C,EAAKE,OAAwB,CAC3BT,KAAM,4BACNmD,UAAWH,EACXI,iBAAkBH,EAClBI,mBAAoBH,G,yLC/MjB,MAAMI,EAAsBA,CACjC/C,EACAgD,IAOGhD,EAAKiD,QAAuC,OAAQ,cAAeD,GAElEE,EAA0B,sBAEnBC,EAAoBT,GAC/BA,EAAeU,WAAWF,GAEfG,EAAiCX,GAC5CA,EAAeY,UAAUJ,IAEdK,EAAiBA,CAC5BvD,EACAwD,EACAC,IAEAzD,EAAKE,OAAO,CACVT,KAAM,kBACN+D,WACAC,YAGSC,EAAeA,CAC1B1D,EACA2D,IAEA3D,EAAKE,OAAO,CACVT,KAAM,iBACNkE,cAGSC,EAAgBA,CAC3B5D,EACA2D,EACAH,IAEAxD,EAAKE,OAAO,CACVT,KAAM,oBACNkE,YACAH,Y,kHC9CG,MAAMK,EAAaC,GACxB,oCAAoCA,EAAQC,MAAQ,UAAY,KAC9DD,EAAQE,YAAc,KAAO,KAC5BF,EAAQG,UAAUH,EAAQI,cAAgB,QAAU,KACrDJ,EAAQrE,WAQC0E,EAA6B7B,GAAgBA,EAAI8B,MAAM,KAAK,GAE5DC,EAAcC,GACzBA,EAAUlB,WAAW,oC"}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1076.205340b2a7c5d559.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/1076.205340b2a7c5d559.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/1076.205340b2a7c5d559.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1076.205340b2a7c5d559.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["113"],{51383:function(t,o,e){e.r(o),e.d(o,{HaIconButtonArrowNext:function(){return c}});e(26847),e(27530);var n=e(73742),a=e(59048),i=e(7616),r=e(88479);e(81777);let s,d=t=>t;class c extends a.oi{render(){var t;return(0,a.dy)(s||(s=d` <ha-icon-button .disabled="${0}" .label="${0}" .path="${0}"></ha-icon-button> `),this.disabled,this.label||(null===(t=this.hass)||void 0===t?void 0:t.localize("ui.common.next"))||"Next",this._icon)}constructor(...t){super(...t),this.disabled=!1,this._icon="rtl"===r.E.document.dir?"M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z":"M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z"}}(0,n.__decorate)([(0,i.Cb)({attribute:!1})],c.prototype,"hass",void 0),(0,n.__decorate)([(0,i.Cb)({type:Boolean})],c.prototype,"disabled",void 0),(0,n.__decorate)([(0,i.Cb)()],c.prototype,"label",void 0),(0,n.__decorate)([(0,i.SB)()],c.prototype,"_icon",void 0),c=(0,n.__decorate)([(0,i.Mo)("ha-icon-button-arrow-next")],c)}}]);
|
|
||||||
//# sourceMappingURL=113.13cc15fa81bd492f.js.map
|
|
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"113.13cc15fa81bd492f.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250806.0/src/components/ha-icon-button-arrow-next.ts"],"names":["HaIconButtonArrowNext","LitElement","render","_this$hass","html","_t","_","this","disabled","label","hass","localize","_icon","args","mainWindow","attribute","type","Boolean"],"mappings":"4RASO,MAAMA,UAA8BC,EAAAA,GAU/BC,MAAAA,GAAyB,IAAAC,EACjC,OAAOC,EAAAA,EAAAA,IAAIC,IAAAA,EAAAC,CAAA,mFAEKC,KAAKC,SACRD,KAAKE,QAAkB,QAAbN,EAAII,KAAKG,YAAI,IAAAP,OAAA,EAATA,EAAWQ,SAAS,oBAAqB,OACxDJ,KAAKK,MAGnB,C,kBAlBK,SAAAC,GAAA,KAG+BL,UAAW,OAI9BI,MACa,QAA5BE,EAAAA,EAAAA,SAAAA,I,gLAPUC,WAAW,K,uDAEXC,KAAMC,W"}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1180.89c3426e7a24fa5c.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/1180.89c3426e7a24fa5c.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/1180.89c3426e7a24fa5c.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1180.89c3426e7a24fa5c.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/120.c5f670671b56cb1c.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/120.c5f670671b56cb1c.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/120.c5f670671b56cb1c.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/120.c5f670671b56cb1c.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1303"],{29815:function(e,t,a){a.r(t),a.d(t,{HaFormConstant:function(){return h}});var s=a(73742),o=a(59048),n=a(7616);let r,l,i=e=>e;class h extends o.oi{render(){return(0,o.dy)(r||(r=i`<span class="label">${0}</span>${0}`),this.label,this.schema.value?`: ${this.schema.value}`:"")}}h.styles=(0,o.iv)(l||(l=i`:host{display:block}.label{font-weight:var(--ha-font-weight-medium)}`)),(0,s.__decorate)([(0,n.Cb)({attribute:!1})],h.prototype,"schema",void 0),(0,s.__decorate)([(0,n.Cb)()],h.prototype,"label",void 0),h=(0,s.__decorate)([(0,n.Mo)("ha-form-constant")],h)}}]);
|
|
||||||
//# sourceMappingURL=1303.586b15f0603e938e.js.map
|
|
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"1303.586b15f0603e938e.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250806.0/src/components/ha-form/ha-form-constant.ts"],"names":["HaFormConstant","LitElement","render","html","_t","_","this","label","schema","value","styles","css","_t2","attribute"],"mappings":"kPAMO,MAAMA,UAAuBC,EAAAA,GAKxBC,MAAAA,GACR,OAAOC,EAAAA,EAAAA,IAAIC,IAAAA,EAAAC,CAAA,uBAAuB,WAAvB,KAAuBC,KAAKC,MAAeD,KAAKE,OAAOC,MAC5D,KAAKH,KAAKE,OAAOC,QACjB,GACR,EATWT,EAWJU,QAASC,EAAAA,EAAAA,IAAGC,IAAAA,EAAAP,CAAA,0E,2BAVPQ,WAAW,K"}
|
|
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/131.566df1af9c07775a.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/131.566df1af9c07775a.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/131.566df1af9c07775a.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/131.566df1af9c07775a.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2021 Google LLC
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2021 Google LLC
|
|
||||||
* SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2022 Google LLC
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2023 Google LLC
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2024 Google LLC
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1369.9e14eed263bd6e8b.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/1369.9e14eed263bd6e8b.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/1369.9e14eed263bd6e8b.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1369.9e14eed263bd6e8b.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1374"],{5786:function(t,e,n){n.r(e),n.d(e,{HaIconNext:function(){return c}});n(26847),n(27530);var o=n(73742),s=n(7616),a=n(88479),r=n(993);class c extends r.HaSvgIcon{constructor(...t){super(...t),this.path="rtl"===a.E.document.dir?"M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z":"M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"}}(0,o.__decorate)([(0,s.Cb)()],c.prototype,"path",void 0),c=(0,o.__decorate)([(0,s.Mo)("ha-icon-next")],c)}}]);
|
|
||||||
//# sourceMappingURL=1374.3202a7d8086556de.js.map
|
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user