mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-09-03 18:34:44 +00:00
Compare commits
232 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
205f3a74dd | ||
![]() |
21a5479a2e | ||
![]() |
a9ab64a29a | ||
![]() |
3edfaa1ee7 | ||
![]() |
71903d906b | ||
![]() |
36f4e494a2 | ||
![]() |
9104b287e5 | ||
![]() |
842c4b3864 | ||
![]() |
244005471b | ||
![]() |
0ca837903f | ||
![]() |
8683d46ab6 | ||
![]() |
c17006cc37 | ||
![]() |
136d8613a5 | ||
![]() |
670d05df95 | ||
![]() |
93fc4e97a0 | ||
![]() |
b86df0696e | ||
![]() |
b0af73b0b5 | ||
![]() |
f1e884b264 | ||
![]() |
a2f43d8c7b | ||
![]() |
b4cfbe46c1 | ||
![]() |
2a006ae76d | ||
![]() |
40812450df | ||
![]() |
d2e0b0417c | ||
![]() |
d4fd8f3f0d | ||
![]() |
199b57c833 | ||
![]() |
597a27ba33 | ||
![]() |
d6e44b43b4 | ||
![]() |
84c2053b57 | ||
![]() |
2df3678fef | ||
![]() |
920f9846ac | ||
![]() |
3478005e70 | ||
![]() |
e5d64f6c75 | ||
![]() |
787695a763 | ||
![]() |
a495fd6b3a | ||
![]() |
80e67b3c57 | ||
![]() |
a52272a7fe | ||
![]() |
fb24ed3f1a | ||
![]() |
8d8704e049 | ||
![]() |
a60f25100d | ||
![]() |
5be4a1f4dc | ||
![]() |
caacb421c1 | ||
![]() |
724eaddf19 | ||
![]() |
c3019bce7e | ||
![]() |
4ae61814d4 | ||
![]() |
b651d63758 | ||
![]() |
400d3981a2 | ||
![]() |
69c2517d52 | ||
![]() |
c8b49aba42 | ||
![]() |
8071b107e7 | ||
![]() |
603d19b075 | ||
![]() |
a5ce2ef7cb | ||
![]() |
f392dc5492 | ||
![]() |
0c63883269 | ||
![]() |
612d4f950b | ||
![]() |
1799c765b4 | ||
![]() |
809ac1ffca | ||
![]() |
fefc99e825 | ||
![]() |
d994170a9d | ||
![]() |
d8c934365a | ||
![]() |
e0fd31c390 | ||
![]() |
22238c9c0e | ||
![]() |
5ff96cfa5e | ||
![]() |
e22a19df1a | ||
![]() |
f57bc0db25 | ||
![]() |
6ba6b5ea56 | ||
![]() |
5dc9f9235e | ||
![]() |
323fa2e637 | ||
![]() |
0986419b2f | ||
![]() |
f0bc952269 | ||
![]() |
9266997482 | ||
![]() |
75d252e21a | ||
![]() |
368e94f95f | ||
![]() |
3fbecf89db | ||
![]() |
54e6ae5fd9 | ||
![]() |
5b96074055 | ||
![]() |
5503f93a75 | ||
![]() |
eadc629cd9 | ||
![]() |
cde45e2e7a | ||
![]() |
050851a9ac | ||
![]() |
86bd16b2ba | ||
![]() |
ce9181b05f | ||
![]() |
f7ba364076 | ||
![]() |
3511c19726 | ||
![]() |
d9ed58696b | ||
![]() |
373f452774 | ||
![]() |
e54efa681f | ||
![]() |
79cd8ac390 | ||
![]() |
dc24f332f8 | ||
![]() |
99cdf7b028 | ||
![]() |
54edfa53bc | ||
![]() |
571c9a05c6 | ||
![]() |
864b7bf023 | ||
![]() |
e303431d74 | ||
![]() |
19dd40275c | ||
![]() |
4cf970e37a | ||
![]() |
7947c27089 | ||
![]() |
d0e2c8b694 | ||
![]() |
19e3a859b0 | ||
![]() |
e6557ded34 | ||
![]() |
f4aae4522d | ||
![]() |
2066aefd6d | ||
![]() |
2f56cab953 | ||
![]() |
883399f583 | ||
![]() |
47f53501e5 | ||
![]() |
b23a89e6fb | ||
![]() |
7764decc37 | ||
![]() |
88490140af | ||
![]() |
61d56dce9c | ||
![]() |
838af87ad7 | ||
![]() |
8f263ab345 | ||
![]() |
6b76086652 | ||
![]() |
efa5205800 | ||
![]() |
a0c8b77737 | ||
![]() |
9ee0efe6c0 | ||
![]() |
a2af63d050 | ||
![]() |
da246dc40a | ||
![]() |
3c52f87cdc | ||
![]() |
d80d76a24d | ||
![]() |
8653f7a0e1 | ||
![]() |
8458d9e0f6 | ||
![]() |
5d4ce94155 | ||
![]() |
828cf773cc | ||
![]() |
a902b55df7 | ||
![]() |
f38cde4c68 | ||
![]() |
4c9cbb112e | ||
![]() |
3d814f3c44 | ||
![]() |
f269f72082 | ||
![]() |
f07193dc3c | ||
![]() |
d2b706df05 | ||
![]() |
e5817e9445 | ||
![]() |
85313f26ea | ||
![]() |
f864613ffb | ||
![]() |
36ea8b2bb4 | ||
![]() |
df9d62f874 | ||
![]() |
4a6aaa8559 | ||
![]() |
435f479984 | ||
![]() |
e2f39059c6 | ||
![]() |
531073d5ec | ||
![]() |
ef5b6a5f4c | ||
![]() |
03f0a136ab | ||
![]() |
7a6663ba80 | ||
![]() |
9dd5eee1ae | ||
![]() |
bb474a5c14 | ||
![]() |
6ab4dda5e8 | ||
![]() |
8a553dbb59 | ||
![]() |
1ee6c0491c | ||
![]() |
cc50a91a42 | ||
![]() |
637377f81d | ||
![]() |
a90f70e017 | ||
![]() |
949ecb255d | ||
![]() |
15f62837c8 | ||
![]() |
e5246a5b1d | ||
![]() |
394d66290d | ||
![]() |
79d541185f | ||
![]() |
b433d129ef | ||
![]() |
4b0278fee8 | ||
![]() |
8c59e6d05a | ||
![]() |
5c66278a1c | ||
![]() |
7abe9487a0 | ||
![]() |
73832dd6d6 | ||
![]() |
6cc3df54e9 | ||
![]() |
c07c7c5146 | ||
![]() |
a6d1078fe3 | ||
![]() |
eba6da485d | ||
![]() |
de880e24ed | ||
![]() |
f344df9e5c | ||
![]() |
5af62a8834 | ||
![]() |
800fb683f8 | ||
![]() |
ad2566d58a | ||
![]() |
6c679b07e1 | ||
![]() |
aa4f4c8d47 | ||
![]() |
b83da5d89f | ||
![]() |
0afff9a9e2 | ||
![]() |
0433d72ae6 | ||
![]() |
d33beb06cd | ||
![]() |
279d6ccd79 | ||
![]() |
af628293f3 | ||
![]() |
df6b815175 | ||
![]() |
d6127832a7 | ||
![]() |
8240623806 | ||
![]() |
2b4527fa64 | ||
![]() |
23143aede4 | ||
![]() |
8b93f0aee7 | ||
![]() |
5cc4a9a929 | ||
![]() |
288d2e5bdb | ||
![]() |
73d84113ea | ||
![]() |
4b15945ca1 | ||
![]() |
10720b2988 | ||
![]() |
bb991b69bb | ||
![]() |
7c9f6067c0 | ||
![]() |
e960a70217 | ||
![]() |
9b0a2e6da9 | ||
![]() |
cd0c151bd9 | ||
![]() |
b03c8c24dd | ||
![]() |
4416b6524e | ||
![]() |
c9d3f65cc8 | ||
![]() |
0407122fbe | ||
![]() |
5e871d9399 | ||
![]() |
6df7a88666 | ||
![]() |
5933b66b1c | ||
![]() |
a85e816cd7 | ||
![]() |
96f6c07912 | ||
![]() |
40bcee38f3 | ||
![]() |
6d2a38c96e | ||
![]() |
dafc2cfec2 | ||
![]() |
04f36e92e1 | ||
![]() |
4f97013df4 | ||
![]() |
53eae96a98 | ||
![]() |
74530baeb7 | ||
![]() |
271e4f0cc4 | ||
![]() |
f4c7f2cae1 | ||
![]() |
24cdb4787a | ||
![]() |
57b1c21af4 | ||
![]() |
f0eddb6926 | ||
![]() |
7c74c1bd8c | ||
![]() |
2d4a85ae43 | ||
![]() |
d48c439737 | ||
![]() |
874c50d3e8 | ||
![]() |
4beaf571c2 | ||
![]() |
58a948447e | ||
![]() |
32af7ef28b | ||
![]() |
208fb549b7 | ||
![]() |
ab704c11cf | ||
![]() |
966b962ccf | ||
![]() |
4ea3695982 | ||
![]() |
b2abe37d72 | ||
![]() |
9bf8d15b01 | ||
![]() |
70acbffc23 | ||
![]() |
c9b1eb751e | ||
![]() |
ad8d850ed7 | ||
![]() |
1b0eb9397d | ||
![]() |
8572f8c4e5 |
@@ -1,64 +0,0 @@
|
|||||||
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.9
|
|
||||||
|
|
||||||
ENV \
|
|
||||||
DEBIAN_FRONTEND=noninteractive \
|
|
||||||
VCN_VERSION=0.9.8
|
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-c"]
|
|
||||||
|
|
||||||
WORKDIR /workspaces
|
|
||||||
|
|
||||||
# Set Docker daemon config
|
|
||||||
RUN \
|
|
||||||
mkdir -p /etc/docker \
|
|
||||||
&& echo '{"storage-driver": "vfs"}' > /etc/docker/daemon.json
|
|
||||||
|
|
||||||
# Install Node/Yarn for Frontent
|
|
||||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
curl \
|
|
||||||
git \
|
|
||||||
apt-utils \
|
|
||||||
apt-transport-https \
|
|
||||||
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
|
|
||||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
nodejs \
|
|
||||||
yarn \
|
|
||||||
&& curl -o - https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
ENV NVM_DIR /root/.nvm
|
|
||||||
|
|
||||||
# Install docker
|
|
||||||
# https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
apt-transport-https \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
software-properties-common \
|
|
||||||
gpg-agent \
|
|
||||||
&& curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \
|
|
||||||
&& add-apt-repository "deb https://download.docker.com/linux/debian $(lsb_release -cs) stable" \
|
|
||||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
docker-ce \
|
|
||||||
docker-ce-cli \
|
|
||||||
containerd.io \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Install tools
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
jq \
|
|
||||||
dbus \
|
|
||||||
network-manager \
|
|
||||||
apparmor-utils \
|
|
||||||
libpulse0 \
|
|
||||||
&& curl -Lo /bin/vcn https://github.com/codenotary/vcn/releases/download/${VCN_VERSION}/vcn-${VCN_VERSION}-linux-amd64-static \
|
|
||||||
&& chmod a+x /bin/vcn \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Install Python dependencies from requirements.txt if it exists
|
|
||||||
COPY requirements.txt requirements_tests.txt ./
|
|
||||||
RUN pip3 install -U setuptools pip \
|
|
||||||
&& pip3 install -r requirements.txt -r requirements_tests.txt \
|
|
||||||
&& pip3 install tox \
|
|
||||||
&& rm -f requirements.txt requirements_tests.txt
|
|
@@ -1,11 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "Supervisor dev",
|
"name": "Supervisor dev",
|
||||||
"context": "..",
|
"image": "ghcr.io/home-assistant/devcontainer:supervisor",
|
||||||
"dockerFile": "Dockerfile",
|
"appPort": ["9123:8123", "7357:4357"],
|
||||||
"appPort": "9123:8123",
|
"postCreateCommand": "bash devcontainer_bootstrap",
|
||||||
"postCreateCommand": "pre-commit install",
|
|
||||||
"runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"],
|
"runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"],
|
||||||
"containerEnv": { "NVM_DIR": "/usr/local/share/nvm" },
|
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
"ms-python.vscode-pylance",
|
"ms-python.vscode-pylance",
|
||||||
@@ -13,7 +11,12 @@
|
|||||||
"esbenp.prettier-vscode"
|
"esbenp.prettier-vscode"
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"terminal.integrated.shell.linux": "/bin/bash",
|
"terminal.integrated.profiles.linux": {
|
||||||
|
"zsh": {
|
||||||
|
"path": "/usr/bin/zsh"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"terminal.integrated.defaultProfile.linux": "zsh",
|
||||||
"editor.formatOnPaste": false,
|
"editor.formatOnPaste": false,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.formatOnType": true,
|
"editor.formatOnType": true,
|
||||||
|
95
.github/workflows/builder.yml
vendored
95
.github/workflows/builder.yml
vendored
@@ -27,12 +27,13 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "rootfs/**"
|
- "rootfs/**"
|
||||||
- "supervisor/**"
|
- "supervisor/**"
|
||||||
- build.json
|
- build.yaml
|
||||||
- Dockerfile
|
- Dockerfile
|
||||||
- requirements.txt
|
- requirements.txt
|
||||||
- setup.py
|
- setup.py
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
DEFAULT_PYTHON: 3.9
|
||||||
BUILD_NAME: supervisor
|
BUILD_NAME: supervisor
|
||||||
BUILD_TYPE: supervisor
|
BUILD_TYPE: supervisor
|
||||||
WHEELS_TAG: 3.9-alpine3.14
|
WHEELS_TAG: 3.9-alpine3.14
|
||||||
@@ -49,7 +50,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@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -84,7 +85,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@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -109,59 +110,72 @@ jobs:
|
|||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
uses: docker/login-action@v1.10.0
|
uses: docker/login-action@v1.13.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- 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@v1.10.0
|
uses: docker/login-action@v1.13.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ secrets.GIT_USER }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GIT_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set build arguments
|
- name: Set build arguments
|
||||||
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
|
||||||
|
|
||||||
- name: Build supervisor
|
- name: Build supervisor
|
||||||
uses: home-assistant/builder@2021.07.0
|
uses: home-assistant/builder@2022.01.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_ARGS \
|
$BUILD_ARGS \
|
||||||
--${{ matrix.arch }} \
|
--${{ matrix.arch }} \
|
||||||
--target /data \
|
--target /data \
|
||||||
--with-codenotary "${{ secrets.VCN_USER }}" "${{ secrets.VCN_PASSWORD }}" "${{ secrets.VCN_ORG }}" \
|
|
||||||
--validate-from "${{ secrets.VCN_ORG }}" \
|
|
||||||
--generic ${{ needs.init.outputs.version }}
|
--generic ${{ needs.init.outputs.version }}
|
||||||
|
env:
|
||||||
|
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
|
||||||
|
|
||||||
codenotary:
|
codenotary:
|
||||||
name: CodeNotary signature
|
name: CAS signature
|
||||||
needs: init
|
needs: init
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
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@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
|
if: needs.init.outputs.publish == 'true'
|
||||||
|
uses: actions/setup-python@v2.3.2
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
- name: Set version
|
- name: Set version
|
||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
uses: home-assistant/actions/helpers/version@master
|
uses: home-assistant/actions/helpers/version@master
|
||||||
with:
|
with:
|
||||||
type: ${{ env.BUILD_TYPE }}
|
type: ${{ env.BUILD_TYPE }}
|
||||||
|
|
||||||
- name: Signing image
|
- name: Install dirhash and calc hash
|
||||||
|
if: needs.init.outputs.publish == 'true'
|
||||||
|
id: dirhash
|
||||||
|
run: |
|
||||||
|
pip3 install dirhash
|
||||||
|
dir_hash="$(dirhash "${{ github.workspace }}/supervisor" -a sha256 --match "*.py")"
|
||||||
|
echo "::set-output name=dirhash::${dir_hash}"
|
||||||
|
|
||||||
|
- name: Signing Source
|
||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
uses: home-assistant/actions/helpers/codenotary@master
|
uses: home-assistant/actions/helpers/codenotary@master
|
||||||
with:
|
with:
|
||||||
source: dir://${{ github.workspace }}
|
source: hash://${{ steps.dirhash.outputs.dirhash }}
|
||||||
user: ${{ secrets.VCN_USER }}
|
asset: supervisor-${{ needs.init.outputs.version }}
|
||||||
password: ${{ secrets.VCN_PASSWORD }}
|
token: ${{ secrets.CAS_TOKEN }}
|
||||||
organisation: ${{ secrets.VCN_ORG }}
|
|
||||||
|
|
||||||
version:
|
version:
|
||||||
name: Update version
|
name: Update version
|
||||||
@@ -170,7 +184,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@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
- name: Initialize git
|
- name: Initialize git
|
||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
@@ -192,13 +206,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Run the Supervisor
|
name: Run the Supervisor
|
||||||
needs: ["build", "codenotary", "init"]
|
needs: ["build", "codenotary", "init"]
|
||||||
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
- name: Build the Supervisor
|
- name: Build the Supervisor
|
||||||
if: needs.init.outputs.publish != 'true'
|
if: needs.init.outputs.publish != 'true'
|
||||||
uses: home-assistant/builder@2021.07.0
|
uses: home-assistant/builder@2022.01.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
--test \
|
--test \
|
||||||
@@ -209,8 +224,8 @@ jobs:
|
|||||||
- name: Pull Supervisor
|
- name: Pull Supervisor
|
||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
run: |
|
run: |
|
||||||
docker pull homeassistant/amd64-hassio-supervisor:${{ needs.init.outputs.version }}
|
docker pull ghcr.io/home-assistant/amd64-hassio-supervisor:${{ needs.init.outputs.version }}
|
||||||
docker tag homeassistant/amd64-hassio-supervisor:${{ needs.init.outputs.version }} homeassistant/amd64-hassio-supervisor:runner
|
docker tag ghcr.io/home-assistant/amd64-hassio-supervisor:${{ needs.init.outputs.version }} homeassistant/amd64-hassio-supervisor:runner
|
||||||
|
|
||||||
- name: Create the Supervisor
|
- name: Create the Supervisor
|
||||||
run: |
|
run: |
|
||||||
@@ -218,7 +233,7 @@ jobs:
|
|||||||
docker create --name hassio_supervisor \
|
docker create --name hassio_supervisor \
|
||||||
--privileged \
|
--privileged \
|
||||||
--security-opt seccomp=unconfined \
|
--security-opt seccomp=unconfined \
|
||||||
--security-opt apparmor:unconfined \
|
--security-opt apparmor=unconfined \
|
||||||
-v /run/docker.sock:/run/docker.sock \
|
-v /run/docker.sock:/run/docker.sock \
|
||||||
-v /run/dbus:/run/dbus \
|
-v /run/dbus:/run/dbus \
|
||||||
-v /tmp/supervisor/data:/data \
|
-v /tmp/supervisor/data:/data \
|
||||||
@@ -240,21 +255,18 @@ jobs:
|
|||||||
ping=$(curl -sSL "http://$SUPERVISOR/supervisor/ping" | jq -r '.result')
|
ping=$(curl -sSL "http://$SUPERVISOR/supervisor/ping" | jq -r '.result')
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
docker logs hassio_supervisor
|
|
||||||
|
|
||||||
- name: Check the Supervisor
|
- name: Check the Supervisor
|
||||||
run: |
|
run: |
|
||||||
echo "Checking supervisor info"
|
echo "Checking supervisor info"
|
||||||
test=$(docker exec hassio_cli ha supervisor info --no-progress --raw-json | jq -r '.result')
|
test=$(docker exec hassio_cli ha supervisor info --no-progress --raw-json | jq -r '.result')
|
||||||
if [ "$test" != "ok" ];then
|
if [ "$test" != "ok" ]; then
|
||||||
docker logs hassio_supervisor
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Checking supervisor network info"
|
echo "Checking supervisor network info"
|
||||||
test=$(docker exec hassio_cli ha network info --no-progress --raw-json | jq -r '.result')
|
test=$(docker exec hassio_cli ha network info --no-progress --raw-json | jq -r '.result')
|
||||||
if [ "$test" != "ok" ];then
|
if [ "$test" != "ok" ]; then
|
||||||
docker logs hassio_supervisor
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -262,15 +274,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "Install Core SSH Add-on"
|
echo "Install Core SSH Add-on"
|
||||||
test=$(docker exec hassio_cli ha addons install core_ssh --no-progress --raw-json | jq -r '.result')
|
test=$(docker exec hassio_cli ha addons install core_ssh --no-progress --raw-json | jq -r '.result')
|
||||||
if [ "$test" != "ok" ];then
|
if [ "$test" != "ok" ]; then
|
||||||
docker logs hassio_supervisor
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Start Core SSH Add-on"
|
echo "Start Core SSH Add-on"
|
||||||
test=$(docker exec hassio_cli ha addons start core_ssh --no-progress --raw-json | jq -r '.result')
|
test=$(docker exec hassio_cli ha addons start core_ssh --no-progress --raw-json | jq -r '.result')
|
||||||
if [ "$test" != "ok" ];then
|
if [ "$test" != "ok" ]; then
|
||||||
docker logs hassio_supervisor
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -278,22 +288,29 @@ jobs:
|
|||||||
if: needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.publish == 'true'
|
||||||
run: |
|
run: |
|
||||||
echo "Enable Content-Trust"
|
echo "Enable Content-Trust"
|
||||||
test=$(docker exec hassio_cli ha supervisor options --content-trust=true --no-progress --raw-json | jq -r '.result')
|
test=$(docker exec hassio_cli ha security options --content-trust=true --no-progress --raw-json | jq -r '.result')
|
||||||
if [ "$test" != "ok" ];then
|
if [ "$test" != "ok" ]; then
|
||||||
docker logs hassio_supervisor
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Run supervisor health check"
|
echo "Run supervisor health check"
|
||||||
test=$(docker exec hassio_cli ha resolution healthcheck --no-progress --raw-json | jq -r '.result')
|
test=$(docker exec hassio_cli ha resolution healthcheck --no-progress --raw-json | jq -r '.result')
|
||||||
if [ "$test" != "ok" ];then
|
if [ "$test" != "ok" ]; then
|
||||||
docker logs hassio_supervisor
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Check supervisor unhealthy"
|
echo "Check supervisor unhealthy"
|
||||||
test=$(docker exec hassio_cli ha resolution info --no-progress --raw-json | jq -r '.data.unhealthy[]')
|
test=$(docker exec hassio_cli ha resolution info --no-progress --raw-json | jq -r '.data.unhealthy[]')
|
||||||
if [ "$test" != "" ];then
|
if [ "$test" != "" ]; then
|
||||||
docker logs hassio_supervisor
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "Check supervisor supported"
|
||||||
|
test=$(docker exec hassio_cli ha resolution info --no-progress --raw-json | jq -r '.data.unsupported[]')
|
||||||
|
if [[ "$test" =~ source_mods ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Get supervisor logs on failiure
|
||||||
|
if: ${{ cancelled() || failure() }}
|
||||||
|
run: docker logs hassio_supervisor
|
||||||
|
87
.github/workflows/ci.yaml
vendored
87
.github/workflows/ci.yaml
vendored
@@ -10,7 +10,7 @@ on:
|
|||||||
env:
|
env:
|
||||||
DEFAULT_PYTHON: 3.9
|
DEFAULT_PYTHON: 3.9
|
||||||
PRE_COMMIT_HOME: ~/.cache/pre-commit
|
PRE_COMMIT_HOME: ~/.cache/pre-commit
|
||||||
DEFAULT_VCN: v0.9.8
|
DEFAULT_CAS: v1.0.2
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Separate job to pre-populate the base dependency cache
|
# Separate job to pre-populate the base dependency cache
|
||||||
@@ -23,22 +23,19 @@ jobs:
|
|||||||
name: Prepare Python ${{ matrix.python-version }} dependencies
|
name: Prepare Python ${{ matrix.python-version }} dependencies
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v2.2.2
|
uses: actions/setup-python@v2.3.2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
${{ runner.os }}-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_tests.txt') }}
|
${{ runner.os }}-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_tests.txt') }}
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}
|
|
||||||
${{ runner.os }}-venv-${{ steps.python.outputs.python-version }}-
|
|
||||||
- name: Create Python virtual environment
|
- name: Create Python virtual environment
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@@ -48,7 +45,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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_HOME }}
|
path: ${{ env.PRE_COMMIT_HOME }}
|
||||||
key: |
|
key: |
|
||||||
@@ -67,15 +64,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.2
|
uses: actions/setup-python@v2.3.2
|
||||||
id: python
|
id: python
|
||||||
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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -96,7 +93,7 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.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"
|
||||||
@@ -111,15 +108,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.2
|
uses: actions/setup-python@v2.3.2
|
||||||
id: python
|
id: python
|
||||||
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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -131,7 +128,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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_HOME }}
|
path: ${{ env.PRE_COMMIT_HOME }}
|
||||||
key: |
|
key: |
|
||||||
@@ -155,15 +152,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.2
|
uses: actions/setup-python@v2.3.2
|
||||||
id: python
|
id: python
|
||||||
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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -187,15 +184,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.2
|
uses: actions/setup-python@v2.3.2
|
||||||
id: python
|
id: python
|
||||||
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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -207,7 +204,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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_HOME }}
|
path: ${{ env.PRE_COMMIT_HOME }}
|
||||||
key: |
|
key: |
|
||||||
@@ -228,15 +225,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.2
|
uses: actions/setup-python@v2.3.2
|
||||||
id: python
|
id: python
|
||||||
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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -248,7 +245,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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_HOME }}
|
path: ${{ env.PRE_COMMIT_HOME }}
|
||||||
key: |
|
key: |
|
||||||
@@ -272,15 +269,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.2
|
uses: actions/setup-python@v2.3.2
|
||||||
id: python
|
id: python
|
||||||
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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -304,15 +301,15 @@ jobs:
|
|||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.2
|
uses: actions/setup-python@v2.3.2
|
||||||
id: python
|
id: python
|
||||||
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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -324,7 +321,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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_HOME }}
|
path: ${{ env.PRE_COMMIT_HOME }}
|
||||||
key: |
|
key: |
|
||||||
@@ -348,19 +345,19 @@ jobs:
|
|||||||
name: Run tests Python ${{ matrix.python-version }}
|
name: Run tests Python ${{ matrix.python-version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2.2.2
|
uses: actions/setup-python@v2.3.2
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install VCN tools
|
- name: Install CAS tools
|
||||||
uses: home-assistant/actions/helpers/vcn@master
|
uses: home-assistant/actions/helpers/cas@master
|
||||||
with:
|
with:
|
||||||
vnc_version: ${{ env.DEFAULT_VCN }}
|
version: ${{ env.DEFAULT_CAS }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -395,7 +392,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@v2.2.4
|
uses: actions/upload-artifact@v2.3.1
|
||||||
with:
|
with:
|
||||||
name: coverage-${{ matrix.python-version }}
|
name: coverage-${{ matrix.python-version }}
|
||||||
path: .coverage
|
path: .coverage
|
||||||
@@ -406,15 +403,15 @@ jobs:
|
|||||||
needs: pytest
|
needs: pytest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.2
|
uses: actions/setup-python@v2.3.2
|
||||||
id: python
|
id: python
|
||||||
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@v2.1.6
|
uses: actions/cache@v2.1.7
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -433,4 +430,4 @@ jobs:
|
|||||||
coverage report
|
coverage report
|
||||||
coverage xml
|
coverage xml
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v2.0.3
|
uses: codecov/codecov-action@v2.1.0
|
||||||
|
10
.github/workflows/lock.yml
vendored
10
.github/workflows/lock.yml
vendored
@@ -9,12 +9,12 @@ jobs:
|
|||||||
lock:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v2.1.2
|
- uses: dessant/lock-threads@v3
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-lock-inactive-days: "30"
|
issue-inactive-days: "30"
|
||||||
issue-exclude-created-before: "2020-10-01T00:00:00Z"
|
exclude-issue-created-before: "2020-10-01T00:00:00Z"
|
||||||
issue-lock-reason: ""
|
issue-lock-reason: ""
|
||||||
pr-lock-inactive-days: "1"
|
pr-inactive-days: "1"
|
||||||
pr-exclude-created-before: "2020-11-01T00:00:00Z"
|
exclude-pr-created-before: "2020-11-01T00:00:00Z"
|
||||||
pr-lock-reason: ""
|
pr-lock-reason: ""
|
||||||
|
2
.github/workflows/release-drafter.yml
vendored
2
.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@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
2
.github/workflows/sentry.yaml
vendored
2
.github/workflows/sentry.yaml
vendored
@@ -10,7 +10,7 @@ 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@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
- name: Sentry Release
|
- name: Sentry Release
|
||||||
uses: getsentry/action-release@v1.1.6
|
uses: getsentry/action-release@v1.1.6
|
||||||
env:
|
env:
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 21.6b0
|
rev: 22.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args:
|
args:
|
||||||
- --safe
|
- --safe
|
||||||
- --quiet
|
- --quiet
|
||||||
- --target-version
|
- --target-version
|
||||||
- py38
|
- py39
|
||||||
files: ^((supervisor|tests)/.+)?[^/]+\.py$
|
files: ^((supervisor|tests)/.+)?[^/]+\.py$
|
||||||
- repo: https://gitlab.com/pycqa/flake8
|
- repo: https://gitlab.com/pycqa/flake8
|
||||||
rev: 3.8.3
|
rev: 3.8.3
|
||||||
@@ -23,12 +23,12 @@ repos:
|
|||||||
- id: check-executables-have-shebangs
|
- id: check-executables-have-shebangs
|
||||||
stages: [manual]
|
stages: [manual]
|
||||||
- id: check-json
|
- id: check-json
|
||||||
- repo: https://github.com/pre-commit/mirrors-isort
|
- repo: https://github.com/PyCQA/isort
|
||||||
rev: v4.3.21
|
rev: 5.9.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.6.2
|
rev: v2.31.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py37-plus]
|
args: [--py39-plus]
|
||||||
|
11
.vscode/tasks.json
vendored
11
.vscode/tasks.json
vendored
@@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
"label": "Run Supervisor",
|
"label": "Run Supervisor",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "./scripts/supervisor.sh",
|
"command": "supervisor_run",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
{
|
{
|
||||||
"label": "Update Supervisor Panel",
|
"label": "Update Supervisor Panel",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "./scripts/update-frontend.sh",
|
"command": "LOKALISE_TOKEN='${input:localiseToken}' ./scripts/update-frontend.sh",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -86,5 +86,12 @@
|
|||||||
},
|
},
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "localiseToken",
|
||||||
|
"type": "promptString",
|
||||||
|
"description": "Paste your lokalise token to download frontend translations"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
36
Dockerfile
36
Dockerfile
@@ -5,18 +5,18 @@ ENV \
|
|||||||
S6_SERVICES_GRACETIME=10000 \
|
S6_SERVICES_GRACETIME=10000 \
|
||||||
SUPERVISOR_API=http://localhost
|
SUPERVISOR_API=http://localhost
|
||||||
|
|
||||||
ARG BUILD_ARCH
|
ARG \
|
||||||
ARG VCN_VERSION
|
BUILD_ARCH \
|
||||||
WORKDIR /usr/src
|
CAS_VERSION
|
||||||
|
|
||||||
# Install base
|
# Install base
|
||||||
|
WORKDIR /usr/src
|
||||||
RUN \
|
RUN \
|
||||||
set -x \
|
set -x \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
eudev \
|
eudev \
|
||||||
eudev-libs \
|
eudev-libs \
|
||||||
git \
|
git \
|
||||||
glib \
|
|
||||||
libffi \
|
libffi \
|
||||||
libpulse \
|
libpulse \
|
||||||
musl \
|
musl \
|
||||||
@@ -25,29 +25,15 @@ RUN \
|
|||||||
build-base \
|
build-base \
|
||||||
go \
|
go \
|
||||||
\
|
\
|
||||||
&& git clone -b v${VCN_VERSION} --depth 1 \
|
&& git clone -b "v${CAS_VERSION}" --depth 1 \
|
||||||
https://github.com/codenotary/vcn \
|
https://github.com/codenotary/cas \
|
||||||
&& cd vcn \
|
&& cd cas \
|
||||||
\
|
&& make cas \
|
||||||
&& if [ "${BUILD_ARCH}" = "armhf" ]; then \
|
&& mv cas /usr/bin/cas \
|
||||||
GOARM=6 GOARCH=arm go build -o vcn -ldflags="-s -w" ./cmd/vcn; \
|
|
||||||
elif [ "${BUILD_ARCH}" = "armv7" ]; then \
|
|
||||||
GOARM=7 GOARCH=arm go build -o vcn -ldflags="-s -w" ./cmd/vcn; \
|
|
||||||
elif [ "${BUILD_ARCH}" = "aarch64" ]; then \
|
|
||||||
GOARCH=arm64 go build -o vcn -ldflags="-s -w" ./cmd/vcn; \
|
|
||||||
elif [ "${BUILD_ARCH}" = "i386" ]; then \
|
|
||||||
GOARCH=386 go build -o vcn -ldflags="-s -w" ./cmd/vcn; \
|
|
||||||
elif [ "${BUILD_ARCH}" = "amd64" ]; then \
|
|
||||||
GOARCH=amd64 go build -o vcn -ldflags="-s -w" ./cmd/vcn; \
|
|
||||||
else \
|
|
||||||
exit 1; \
|
|
||||||
fi \
|
|
||||||
\
|
|
||||||
&& rm -rf /root/go /root/.cache \
|
|
||||||
&& mv vcn /usr/bin/vcn \
|
|
||||||
\
|
\
|
||||||
&& apk del .build-dependencies \
|
&& apk del .build-dependencies \
|
||||||
&& rm -rf /usr/src/vcn
|
&& rm -rf /root/go /root/.cache \
|
||||||
|
&& rm -rf /usr/src/cas
|
||||||
|
|
||||||
# Install requirements
|
# Install requirements
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
24
build.json
24
build.json
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"image": "homeassistant/{arch}-hassio-supervisor",
|
|
||||||
"shadow_repository": "ghcr.io/home-assistant",
|
|
||||||
"build_from": {
|
|
||||||
"aarch64": "ghcr.io/home-assistant/aarch64-base-python:3.9-alpine3.14",
|
|
||||||
"armhf": "ghcr.io/home-assistant/armhf-base-python:3.9-alpine3.14",
|
|
||||||
"armv7": "ghcr.io/home-assistant/armv7-base-python:3.9-alpine3.14",
|
|
||||||
"amd64": "ghcr.io/home-assistant/amd64-base-python:3.9-alpine3.14",
|
|
||||||
"i386": "ghcr.io/home-assistant/i386-base-python:3.9-alpine3.14"
|
|
||||||
},
|
|
||||||
"args": {
|
|
||||||
"VCN_VERSION": "0.9.8"
|
|
||||||
},
|
|
||||||
"labels": {
|
|
||||||
"io.hass.type": "supervisor",
|
|
||||||
"org.opencontainers.image.title": "Home Assistant Supervisor",
|
|
||||||
"org.opencontainers.image.description": "Container-based system for managing Home Assistant Core installation",
|
|
||||||
"org.opencontainers.image.source": "https://github.com/home-assistant/supervisor",
|
|
||||||
"org.opencontainers.image.authors": "The Home Assistant Authors",
|
|
||||||
"org.opencontainers.image.url": "https://www.home-assistant.io/",
|
|
||||||
"org.opencontainers.image.documentation": "https://www.home-assistant.io/docs/",
|
|
||||||
"org.opencontainers.image.licenses": "Apache License 2.0"
|
|
||||||
}
|
|
||||||
}
|
|
22
build.yaml
Normal file
22
build.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
image: homeassistant/{arch}-hassio-supervisor
|
||||||
|
shadow_repository: ghcr.io/home-assistant
|
||||||
|
build_from:
|
||||||
|
aarch64: ghcr.io/home-assistant/aarch64-base-python:3.9-alpine3.14
|
||||||
|
armhf: ghcr.io/home-assistant/armhf-base-python:3.9-alpine3.14
|
||||||
|
armv7: ghcr.io/home-assistant/armv7-base-python:3.9-alpine3.14
|
||||||
|
amd64: ghcr.io/home-assistant/amd64-base-python:3.9-alpine3.14
|
||||||
|
i386: ghcr.io/home-assistant/i386-base-python:3.9-alpine3.14
|
||||||
|
codenotary:
|
||||||
|
signer: notary@home-assistant.io
|
||||||
|
base_image: notary@home-assistant.io
|
||||||
|
args:
|
||||||
|
CAS_VERSION: 1.0.2
|
||||||
|
labels:
|
||||||
|
io.hass.type: supervisor
|
||||||
|
org.opencontainers.image.title: Home Assistant Supervisor
|
||||||
|
org.opencontainers.image.description: Container-based system for managing Home Assistant Core installation
|
||||||
|
org.opencontainers.image.source: https://github.com/home-assistant/supervisor
|
||||||
|
org.opencontainers.image.authors: The Home Assistant Authors
|
||||||
|
org.opencontainers.image.url: https://www.home-assistant.io/
|
||||||
|
org.opencontainers.image.documentation: https://www.home-assistant.io/docs/
|
||||||
|
org.opencontainers.image.licenses: Apache License 2.0
|
Submodule home-assistant-polymer updated: 44548fdc33...7e68393c84
2
pylintrc
2
pylintrc
@@ -2,7 +2,7 @@
|
|||||||
reports=no
|
reports=no
|
||||||
jobs=2
|
jobs=2
|
||||||
|
|
||||||
good-names=id,i,j,k,ex,Run,_,fp,T
|
good-names=id,i,j,k,ex,Run,_,fp,T,os
|
||||||
|
|
||||||
extension-pkg-whitelist=
|
extension-pkg-whitelist=
|
||||||
ciso8601
|
ciso8601
|
||||||
|
@@ -1,20 +1,24 @@
|
|||||||
aiohttp==3.7.4.post0
|
aiohttp==3.8.1
|
||||||
async_timeout==3.0.1
|
async_timeout==4.0.2
|
||||||
atomicwrites==1.4.0
|
atomicwrites==1.4.0
|
||||||
attrs==21.2.0
|
attrs==21.4.0
|
||||||
awesomeversion==21.8.0
|
awesomeversion==22.2.0
|
||||||
brotlipy==0.7.0
|
brotli==1.0.9
|
||||||
cchardet==2.1.7
|
cchardet==2.1.7
|
||||||
ciso8601==2.2.0
|
ciso8601==2.2.0
|
||||||
colorlog==6.4.1
|
colorlog==6.6.0
|
||||||
cpe==1.2.1
|
cpe==1.2.1
|
||||||
cryptography==3.4.6
|
cryptography==36.0.1
|
||||||
debugpy==1.4.1
|
debugpy==1.5.1
|
||||||
docker==5.0.0
|
deepmerge==1.0.1
|
||||||
gitpython==3.1.18
|
dirhash==0.2.1
|
||||||
jinja2==3.0.1
|
docker==5.0.3
|
||||||
pulsectl==21.5.18
|
gitpython==3.1.27
|
||||||
pyudev==0.22.0
|
jinja2==3.0.3
|
||||||
ruamel.yaml==0.15.100
|
pulsectl==22.1.3
|
||||||
sentry-sdk==1.3.1
|
pyudev==0.23.2
|
||||||
voluptuous==0.12.1
|
ruamel.yaml==0.17.17
|
||||||
|
securetar==2022.2.0
|
||||||
|
sentry-sdk==1.5.6
|
||||||
|
voluptuous==0.12.2
|
||||||
|
dbus-next==0.2.3
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
black==21.7b0
|
black==22.1.0
|
||||||
codecov==2.1.12
|
codecov==2.1.12
|
||||||
coverage==5.5
|
coverage==6.3.2
|
||||||
flake8-docstrings==1.6.0
|
flake8-docstrings==1.6.0
|
||||||
flake8==3.9.2
|
flake8==4.0.1
|
||||||
pre-commit==2.14.0
|
pre-commit==2.17.0
|
||||||
pydocstyle==6.1.1
|
pydocstyle==6.1.1
|
||||||
pylint==2.10.2
|
pylint==2.12.2
|
||||||
pytest-aiohttp==0.3.0
|
pytest-aiohttp==0.3.0
|
||||||
pytest-asyncio==0.12.0 # NB!: Versions over 0.12.0 breaks pytest-aiohttp (https://github.com/aio-libs/pytest-aiohttp/issues/16)
|
pytest-asyncio==0.12.0 # NB!: Versions over 0.12.0 breaks pytest-aiohttp (https://github.com/aio-libs/pytest-aiohttp/issues/16)
|
||||||
pytest-cov==2.12.1
|
pytest-cov==3.0.0
|
||||||
pytest-timeout==1.4.2
|
pytest-timeout==2.1.0
|
||||||
pytest==6.2.4
|
pytest==7.0.1
|
||||||
pyupgrade==2.24.0
|
pyupgrade==2.31.0
|
||||||
|
@@ -31,4 +31,4 @@ do
|
|||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
basio::exit.nok "Watchdog detected issue with Supervisor - taking container down!"
|
bashio::exit.nok "Watchdog detected issue with Supervisor - taking container down!"
|
||||||
|
4
rootfs/root/.cas-trusted-signing-pub-key
Normal file
4
rootfs/root/.cas-trusted-signing-pub-key
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE03LvYuz79GTJx4uKp3w6NrSe5JZI
|
||||||
|
iBtgzzYi0YQYtZO/r+xFpgDJEa0gLHkXtl94fpqrFiN89In83lzaszbZtA==
|
||||||
|
-----END PUBLIC KEY-----
|
8
rootfs/root/.cas/config.json
Normal file
8
rootfs/root/.cas/config.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"currentcontext": {
|
||||||
|
"LcHost": "cas.codenotary.com",
|
||||||
|
"LcPort": "443"
|
||||||
|
},
|
||||||
|
"schemaversion": 3,
|
||||||
|
"users": null
|
||||||
|
}
|
@@ -1,61 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
function start_docker() {
|
|
||||||
local starttime
|
|
||||||
local endtime
|
|
||||||
|
|
||||||
update-alternatives --set iptables /usr/sbin/iptables-legacy || echo "Fails adjust iptables"
|
|
||||||
update-alternatives --set ip6tables /usr/sbin/iptables-legacy || echo "Fails adjust ip6tables"
|
|
||||||
|
|
||||||
echo "Starting docker."
|
|
||||||
dockerd 2> /dev/null &
|
|
||||||
DOCKER_PID=$!
|
|
||||||
|
|
||||||
echo "Waiting for docker to initialize..."
|
|
||||||
starttime="$(date +%s)"
|
|
||||||
endtime="$(date +%s)"
|
|
||||||
until docker info >/dev/null 2>&1; do
|
|
||||||
if [ $((endtime - starttime)) -le $DOCKER_TIMEOUT ]; then
|
|
||||||
sleep 1
|
|
||||||
endtime=$(date +%s)
|
|
||||||
else
|
|
||||||
echo "Timeout while waiting for docker to come up"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "Docker was initialized"
|
|
||||||
}
|
|
||||||
|
|
||||||
function stop_docker() {
|
|
||||||
local starttime
|
|
||||||
local endtime
|
|
||||||
|
|
||||||
echo "Stopping in container docker..."
|
|
||||||
if [ "$DOCKER_PID" -gt 0 ] && kill -0 "$DOCKER_PID" 2> /dev/null; then
|
|
||||||
starttime="$(date +%s)"
|
|
||||||
endtime="$(date +%s)"
|
|
||||||
|
|
||||||
# Now wait for it to die
|
|
||||||
kill "$DOCKER_PID"
|
|
||||||
while kill -0 "$DOCKER_PID" 2> /dev/null; do
|
|
||||||
if [ $((endtime - starttime)) -le $DOCKER_TIMEOUT ]; then
|
|
||||||
sleep 1
|
|
||||||
endtime=$(date +%s)
|
|
||||||
else
|
|
||||||
echo "Timeout while waiting for container docker to die"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo "Your host might have been left with unreleased resources"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanup_lastboot() {
|
|
||||||
if [[ -f /workspaces/test_supervisor/config.json ]]; then
|
|
||||||
echo "Cleaning up last boot"
|
|
||||||
cp /workspaces/test_supervisor/config.json /tmp/config.json
|
|
||||||
jq -rM 'del(.last_boot)' /tmp/config.json > /workspaces/test_supervisor/config.json
|
|
||||||
rm /tmp/config.json
|
|
||||||
fi
|
|
||||||
}
|
|
@@ -1,133 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
source "${BASH_SOURCE[0]%/*}/common.sh"
|
|
||||||
|
|
||||||
set -eE
|
|
||||||
|
|
||||||
DOCKER_TIMEOUT=30
|
|
||||||
DOCKER_PID=0
|
|
||||||
|
|
||||||
function build_supervisor() {
|
|
||||||
docker pull homeassistant/amd64-builder:dev
|
|
||||||
|
|
||||||
docker run --rm \
|
|
||||||
--privileged \
|
|
||||||
-v /run/docker.sock:/run/docker.sock \
|
|
||||||
-v "$(pwd):/data" \
|
|
||||||
homeassistant/amd64-builder:dev \
|
|
||||||
--generic latest \
|
|
||||||
--target /data \
|
|
||||||
--test \
|
|
||||||
--amd64 \
|
|
||||||
--no-cache
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function cleanup_docker() {
|
|
||||||
echo "Cleaning up stopped containers..."
|
|
||||||
docker rm $(docker ps -a -q) || true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function run_supervisor() {
|
|
||||||
mkdir -p /workspaces/test_supervisor
|
|
||||||
|
|
||||||
echo "Start Supervisor"
|
|
||||||
docker run --rm --privileged \
|
|
||||||
--name hassio_supervisor \
|
|
||||||
--privileged \
|
|
||||||
--security-opt seccomp=unconfined \
|
|
||||||
--security-opt apparmor:unconfined \
|
|
||||||
-v /run/docker.sock:/run/docker.sock:rw \
|
|
||||||
-v /run/dbus:/run/dbus:ro \
|
|
||||||
-v /run/udev:/run/udev:ro \
|
|
||||||
-v "/workspaces/test_supervisor":/data:rw \
|
|
||||||
-v /etc/machine-id:/etc/machine-id:ro \
|
|
||||||
-v /workspaces/supervisor:/usr/src/supervisor \
|
|
||||||
-e SUPERVISOR_SHARE="/workspaces/test_supervisor" \
|
|
||||||
-e SUPERVISOR_NAME=hassio_supervisor \
|
|
||||||
-e SUPERVISOR_DEV=1 \
|
|
||||||
-e SUPERVISOR_MACHINE="qemux86-64" \
|
|
||||||
homeassistant/amd64-hassio-supervisor:latest
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function init_dbus() {
|
|
||||||
if pgrep dbus-daemon; then
|
|
||||||
echo "Dbus is running"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Startup dbus"
|
|
||||||
mkdir -p /var/lib/dbus
|
|
||||||
cp -f /etc/machine-id /var/lib/dbus/machine-id
|
|
||||||
|
|
||||||
# cleanups
|
|
||||||
mkdir -p /run/dbus
|
|
||||||
rm -f /run/dbus/pid
|
|
||||||
|
|
||||||
# run
|
|
||||||
dbus-daemon --system --print-address
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function init_udev() {
|
|
||||||
if pgrep systemd-udevd; then
|
|
||||||
echo "udev is running"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Startup udev"
|
|
||||||
|
|
||||||
# cleanups
|
|
||||||
mkdir -p /run/udev
|
|
||||||
|
|
||||||
# run
|
|
||||||
/lib/systemd/systemd-udevd --daemon
|
|
||||||
sleep 3
|
|
||||||
udevadm trigger && udevadm settle
|
|
||||||
}
|
|
||||||
|
|
||||||
function init_os-agent() {
|
|
||||||
if pgrep os-agent; then
|
|
||||||
echo "os-agent is running"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f /usr/sbin/os-agent ]; then
|
|
||||||
curl -Lo /usr/sbin/os-agent https://github.com/home-assistant/os-agent/releases/latest/download/os-agent-debian-amd64.bin
|
|
||||||
curl -Lo /etc/dbus-1/system.d/io.hass.conf https://raw.githubusercontent.com/home-assistant/os-agent/main/contrib/io.hass.conf
|
|
||||||
chmod a+x /usr/sbin/os-agent
|
|
||||||
fi
|
|
||||||
|
|
||||||
/usr/sbin/os-agent &
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Run Supervisor"
|
|
||||||
|
|
||||||
start_docker
|
|
||||||
trap "stop_docker" ERR
|
|
||||||
|
|
||||||
|
|
||||||
if [ "$( docker container inspect -f '{{.State.Status}}' hassio_supervisor )" == "running" ]; then
|
|
||||||
echo "Restarting Supervisor"
|
|
||||||
docker rm -f hassio_supervisor
|
|
||||||
init_dbus
|
|
||||||
init_udev
|
|
||||||
init_os-agent
|
|
||||||
cleanup_lastboot
|
|
||||||
run_supervisor
|
|
||||||
stop_docker
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "Starting Supervisor"
|
|
||||||
docker system prune -f
|
|
||||||
build_supervisor
|
|
||||||
cleanup_lastboot
|
|
||||||
cleanup_docker
|
|
||||||
init_dbus
|
|
||||||
init_udev
|
|
||||||
init_os-agent
|
|
||||||
run_supervisor
|
|
||||||
stop_docker
|
|
||||||
fi
|
|
@@ -1,4 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
source "/etc/supervisor_scripts/common"
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Update frontend
|
# Update frontend
|
||||||
@@ -9,6 +11,10 @@ cd home-assistant-polymer
|
|||||||
nvm install
|
nvm install
|
||||||
script/bootstrap
|
script/bootstrap
|
||||||
|
|
||||||
|
# Download translations
|
||||||
|
start_docker
|
||||||
|
./script/translations_download
|
||||||
|
|
||||||
# build frontend
|
# build frontend
|
||||||
cd hassio
|
cd hassio
|
||||||
./script/build_hassio
|
./script/build_hassio
|
||||||
@@ -16,3 +22,9 @@ cd hassio
|
|||||||
# Copy frontend
|
# Copy frontend
|
||||||
rm -rf ../../supervisor/api/panel/*
|
rm -rf ../../supervisor/api/panel/*
|
||||||
cp -rf build/* ../../supervisor/api/panel/
|
cp -rf build/* ../../supervisor/api/panel/
|
||||||
|
|
||||||
|
# Reset frontend git
|
||||||
|
cd ..
|
||||||
|
git reset --hard HEAD
|
||||||
|
|
||||||
|
stop_docker
|
@@ -4,9 +4,8 @@ include_trailing_comma=True
|
|||||||
force_grid_wrap=0
|
force_grid_wrap=0
|
||||||
line_length=88
|
line_length=88
|
||||||
indent = " "
|
indent = " "
|
||||||
not_skip = __init__.py
|
|
||||||
force_sort_within_sections = true
|
force_sort_within_sections = true
|
||||||
sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
|
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
|
||||||
default_section = THIRDPARTY
|
default_section = THIRDPARTY
|
||||||
forced_separate = tests
|
forced_separate = tests
|
||||||
combine_as_imports = true
|
combine_as_imports = true
|
||||||
|
2
setup.py
2
setup.py
@@ -35,7 +35,7 @@ setup(
|
|||||||
"supervisor.api",
|
"supervisor.api",
|
||||||
"supervisor.backups",
|
"supervisor.backups",
|
||||||
"supervisor.dbus.network",
|
"supervisor.dbus.network",
|
||||||
"supervisor.dbus.payloads",
|
"supervisor.dbus.network.setting",
|
||||||
"supervisor.dbus",
|
"supervisor.dbus",
|
||||||
"supervisor.discovery.services",
|
"supervisor.discovery.services",
|
||||||
"supervisor.discovery",
|
"supervisor.discovery",
|
||||||
|
@@ -39,6 +39,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
_LOGGER.info("Initializing Supervisor setup")
|
_LOGGER.info("Initializing Supervisor setup")
|
||||||
coresys = loop.run_until_complete(bootstrap.initialize_coresys())
|
coresys = loop.run_until_complete(bootstrap.initialize_coresys())
|
||||||
|
loop.set_debug(coresys.config.debug)
|
||||||
loop.run_until_complete(coresys.core.connect())
|
loop.run_until_complete(coresys.core.connect())
|
||||||
|
|
||||||
bootstrap.supervisor_debugger(coresys)
|
bootstrap.supervisor_debugger(coresys)
|
||||||
|
@@ -3,7 +3,7 @@ import asyncio
|
|||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
import tarfile
|
import tarfile
|
||||||
from typing import Dict, List, Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from ..const import AddonBoot, AddonStartup, AddonState
|
from ..const import AddonBoot, AddonStartup, AddonState
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
@@ -38,17 +38,17 @@ class AddonManager(CoreSysAttributes):
|
|||||||
"""Initialize Docker base wrapper."""
|
"""Initialize Docker base wrapper."""
|
||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.data: AddonsData = AddonsData(coresys)
|
self.data: AddonsData = AddonsData(coresys)
|
||||||
self.local: Dict[str, Addon] = {}
|
self.local: dict[str, Addon] = {}
|
||||||
self.store: Dict[str, AddonStore] = {}
|
self.store: dict[str, AddonStore] = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def all(self) -> List[AnyAddon]:
|
def all(self) -> list[AnyAddon]:
|
||||||
"""Return a list of all add-ons."""
|
"""Return a list of all add-ons."""
|
||||||
addons: Dict[str, AnyAddon] = {**self.store, **self.local}
|
addons: dict[str, AnyAddon] = {**self.store, **self.local}
|
||||||
return list(addons.values())
|
return list(addons.values())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def installed(self) -> List[Addon]:
|
def installed(self) -> list[Addon]:
|
||||||
"""Return a list of all installed add-ons."""
|
"""Return a list of all installed add-ons."""
|
||||||
return list(self.local.values())
|
return list(self.local.values())
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ class AddonManager(CoreSysAttributes):
|
|||||||
|
|
||||||
async def boot(self, stage: AddonStartup) -> None:
|
async def boot(self, stage: AddonStartup) -> None:
|
||||||
"""Boot add-ons with mode auto."""
|
"""Boot add-ons with mode auto."""
|
||||||
tasks: List[Addon] = []
|
tasks: list[Addon] = []
|
||||||
for addon in self.installed:
|
for addon in self.installed:
|
||||||
if addon.boot != AddonBoot.AUTO or addon.startup != stage:
|
if addon.boot != AddonBoot.AUTO or addon.startup != stage:
|
||||||
continue
|
continue
|
||||||
@@ -123,7 +123,7 @@ class AddonManager(CoreSysAttributes):
|
|||||||
|
|
||||||
async def shutdown(self, stage: AddonStartup) -> None:
|
async def shutdown(self, stage: AddonStartup) -> None:
|
||||||
"""Shutdown addons."""
|
"""Shutdown addons."""
|
||||||
tasks: List[Addon] = []
|
tasks: list[Addon] = []
|
||||||
for addon in self.installed:
|
for addon in self.installed:
|
||||||
if addon.state != AddonState.STARTED or addon.startup != stage:
|
if addon.state != AddonState.STARTED or addon.startup != stage:
|
||||||
continue
|
continue
|
||||||
@@ -158,11 +158,11 @@ class AddonManager(CoreSysAttributes):
|
|||||||
store = self.store.get(slug)
|
store = self.store.get(slug)
|
||||||
|
|
||||||
if not store:
|
if not store:
|
||||||
raise AddonsError(f"Add-on {slug} not exists", _LOGGER.error)
|
raise AddonsError(f"Add-on {slug} does not exist", _LOGGER.error)
|
||||||
|
|
||||||
if not store.available:
|
if not store.available:
|
||||||
raise AddonsNotSupportedError(
|
raise AddonsNotSupportedError(
|
||||||
f"Add-on {slug} not supported on that platform", _LOGGER.error
|
f"Add-on {slug} not supported on this platform", _LOGGER.error
|
||||||
)
|
)
|
||||||
|
|
||||||
self.data.install(store)
|
self.data.install(store)
|
||||||
@@ -252,7 +252,7 @@ class AddonManager(CoreSysAttributes):
|
|||||||
],
|
],
|
||||||
on_condition=AddonsJobError,
|
on_condition=AddonsJobError,
|
||||||
)
|
)
|
||||||
async def update(self, slug: str) -> None:
|
async def update(self, slug: str, backup: Optional[bool] = False) -> None:
|
||||||
"""Update add-on."""
|
"""Update add-on."""
|
||||||
if slug not in self.local:
|
if slug not in self.local:
|
||||||
raise AddonsError(f"Add-on {slug} is not installed", _LOGGER.error)
|
raise AddonsError(f"Add-on {slug} is not installed", _LOGGER.error)
|
||||||
@@ -273,6 +273,13 @@ class AddonManager(CoreSysAttributes):
|
|||||||
f"Add-on {slug} not supported on that platform", _LOGGER.error
|
f"Add-on {slug} not supported on that platform", _LOGGER.error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if backup:
|
||||||
|
await self.sys_backups.do_backup_partial(
|
||||||
|
name=f"addon_{addon.slug}_{addon.version}",
|
||||||
|
homeassistant=False,
|
||||||
|
addons=[addon.slug],
|
||||||
|
)
|
||||||
|
|
||||||
# Update instance
|
# Update instance
|
||||||
last_state: AddonState = addon.state
|
last_state: AddonState = addon.state
|
||||||
old_image = addon.image
|
old_image = addon.image
|
||||||
@@ -306,22 +313,24 @@ class AddonManager(CoreSysAttributes):
|
|||||||
async def rebuild(self, slug: str) -> None:
|
async def rebuild(self, slug: str) -> None:
|
||||||
"""Perform a rebuild of local build add-on."""
|
"""Perform a rebuild of local build add-on."""
|
||||||
if slug not in self.local:
|
if slug not in self.local:
|
||||||
_LOGGER.error("Add-on %s is not installed", slug)
|
raise AddonsError(f"Add-on {slug} is not installed", _LOGGER.error)
|
||||||
raise AddonsError()
|
|
||||||
addon = self.local[slug]
|
addon = self.local[slug]
|
||||||
|
|
||||||
if addon.is_detached:
|
if addon.is_detached:
|
||||||
_LOGGER.error("Add-on %s is not available inside store", slug)
|
raise AddonsError(
|
||||||
raise AddonsError()
|
f"Add-on {slug} is not available inside store", _LOGGER.error
|
||||||
|
)
|
||||||
store = self.store[slug]
|
store = self.store[slug]
|
||||||
|
|
||||||
# Check if a rebuild is possible now
|
# Check if a rebuild is possible now
|
||||||
if addon.version != store.version:
|
if addon.version != store.version:
|
||||||
_LOGGER.error("Version changed, use Update instead Rebuild")
|
raise AddonsError(
|
||||||
raise AddonsError()
|
"Version changed, use Update instead Rebuild", _LOGGER.error
|
||||||
|
)
|
||||||
if not addon.need_build:
|
if not addon.need_build:
|
||||||
_LOGGER.error("Can't rebuild a image based add-on")
|
raise AddonsNotSupportedError(
|
||||||
raise AddonsNotSupportedError()
|
"Can't rebuild a image based add-on", _LOGGER.error
|
||||||
|
)
|
||||||
|
|
||||||
# remove docker container but not addon config
|
# remove docker container but not addon config
|
||||||
last_state: AddonState = addon.state
|
last_state: AddonState = addon.state
|
||||||
@@ -371,7 +380,7 @@ class AddonManager(CoreSysAttributes):
|
|||||||
@Job(conditions=[JobCondition.FREE_SPACE, JobCondition.INTERNET_HOST])
|
@Job(conditions=[JobCondition.FREE_SPACE, JobCondition.INTERNET_HOST])
|
||||||
async def repair(self) -> None:
|
async def repair(self) -> None:
|
||||||
"""Repair local add-ons."""
|
"""Repair local add-ons."""
|
||||||
needs_repair: List[Addon] = []
|
needs_repair: list[Addon] = []
|
||||||
|
|
||||||
# Evaluate Add-ons to repair
|
# Evaluate Add-ons to repair
|
||||||
for addon in self.installed:
|
for addon in self.installed:
|
||||||
|
@@ -10,9 +10,11 @@ import secrets
|
|||||||
import shutil
|
import shutil
|
||||||
import tarfile
|
import tarfile
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from typing import Any, Awaitable, Dict, List, Optional, Set
|
from typing import Any, Awaitable, Final, Optional
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
from deepmerge import Merger
|
||||||
|
from securetar import atomic_contents_add, secure_path
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
@@ -64,7 +66,6 @@ from ..homeassistant.const import WSEvent, WSType
|
|||||||
from ..utils import check_port
|
from ..utils import check_port
|
||||||
from ..utils.apparmor import adjust_profile
|
from ..utils.apparmor import adjust_profile
|
||||||
from ..utils.json import read_json_file, write_json_file
|
from ..utils.json import read_json_file, write_json_file
|
||||||
from ..utils.tar import atomic_contents_add, secure_path
|
|
||||||
from .const import AddonBackupMode
|
from .const import AddonBackupMode
|
||||||
from .model import AddonModel, Data
|
from .model import AddonModel, Data
|
||||||
from .options import AddonOptions
|
from .options import AddonOptions
|
||||||
@@ -87,6 +88,12 @@ RE_OLD_AUDIO = re.compile(r"\d+,\d+")
|
|||||||
|
|
||||||
WATCHDOG_TIMEOUT = aiohttp.ClientTimeout(total=10)
|
WATCHDOG_TIMEOUT = aiohttp.ClientTimeout(total=10)
|
||||||
|
|
||||||
|
_OPTIONS_MERGER: Final = Merger(
|
||||||
|
type_strategies=[(dict, ["merge"])],
|
||||||
|
fallback_strategies=["override"],
|
||||||
|
type_conflict_strategies=["override"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Addon(AddonModel):
|
class Addon(AddonModel):
|
||||||
"""Hold data for add-on inside Supervisor."""
|
"""Hold data for add-on inside Supervisor."""
|
||||||
@@ -112,7 +119,7 @@ class Addon(AddonModel):
|
|||||||
if self._state == new_state:
|
if self._state == new_state:
|
||||||
return
|
return
|
||||||
self._state = new_state
|
self._state = new_state
|
||||||
self.sys_homeassistant.websocket.send_command(
|
self.sys_homeassistant.websocket.send_message(
|
||||||
{
|
{
|
||||||
ATTR_TYPE: WSType.SUPERVISOR_EVENT,
|
ATTR_TYPE: WSType.SUPERVISOR_EVENT,
|
||||||
ATTR_DATA: {
|
ATTR_DATA: {
|
||||||
@@ -187,17 +194,19 @@ class Addon(AddonModel):
|
|||||||
return self.version != self.latest_version
|
return self.version != self.latest_version
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dns(self) -> List[str]:
|
def dns(self) -> list[str]:
|
||||||
"""Return list of DNS name for that add-on."""
|
"""Return list of DNS name for that add-on."""
|
||||||
return [f"{self.hostname}.{DNS_SUFFIX}"]
|
return [f"{self.hostname}.{DNS_SUFFIX}"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def options(self) -> Dict[str, Any]:
|
def options(self) -> dict[str, Any]:
|
||||||
"""Return options with local changes."""
|
"""Return options with local changes."""
|
||||||
return {**self.data[ATTR_OPTIONS], **self.persist[ATTR_OPTIONS]}
|
return _OPTIONS_MERGER.merge(
|
||||||
|
deepcopy(self.data[ATTR_OPTIONS]), deepcopy(self.persist[ATTR_OPTIONS])
|
||||||
|
)
|
||||||
|
|
||||||
@options.setter
|
@options.setter
|
||||||
def options(self, value: Optional[Dict[str, Any]]) -> None:
|
def options(self, value: Optional[dict[str, Any]]) -> None:
|
||||||
"""Store user add-on options."""
|
"""Store user add-on options."""
|
||||||
self.persist[ATTR_OPTIONS] = {} if value is None else deepcopy(value)
|
self.persist[ATTR_OPTIONS] = {} if value is None else deepcopy(value)
|
||||||
|
|
||||||
@@ -274,12 +283,12 @@ class Addon(AddonModel):
|
|||||||
self.persist[ATTR_PROTECTED] = value
|
self.persist[ATTR_PROTECTED] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ports(self) -> Optional[Dict[str, Optional[int]]]:
|
def ports(self) -> Optional[dict[str, Optional[int]]]:
|
||||||
"""Return ports of add-on."""
|
"""Return ports of add-on."""
|
||||||
return self.persist.get(ATTR_NETWORK, super().ports)
|
return self.persist.get(ATTR_NETWORK, super().ports)
|
||||||
|
|
||||||
@ports.setter
|
@ports.setter
|
||||||
def ports(self, value: Optional[Dict[str, Optional[int]]]) -> None:
|
def ports(self, value: Optional[dict[str, Optional[int]]]) -> None:
|
||||||
"""Set custom ports of add-on."""
|
"""Set custom ports of add-on."""
|
||||||
if value is None:
|
if value is None:
|
||||||
self.persist.pop(ATTR_NETWORK, None)
|
self.persist.pop(ATTR_NETWORK, None)
|
||||||
@@ -425,7 +434,7 @@ class Addon(AddonModel):
|
|||||||
return Path(self.sys_config.path_extern_tmp, f"{self.slug}_pulse")
|
return Path(self.sys_config.path_extern_tmp, f"{self.slug}_pulse")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def devices(self) -> Set[Device]:
|
def devices(self) -> set[Device]:
|
||||||
"""Extract devices from add-on options."""
|
"""Extract devices from add-on options."""
|
||||||
options_schema = self.schema
|
options_schema = self.schema
|
||||||
with suppress(vol.Invalid):
|
with suppress(vol.Invalid):
|
||||||
@@ -434,7 +443,7 @@ class Addon(AddonModel):
|
|||||||
return options_schema.devices
|
return options_schema.devices
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pwned(self) -> Set[str]:
|
def pwned(self) -> set[str]:
|
||||||
"""Extract pwned data for add-on options."""
|
"""Extract pwned data for add-on options."""
|
||||||
options_schema = self.schema
|
options_schema = self.schema
|
||||||
with suppress(vol.Invalid):
|
with suppress(vol.Invalid):
|
||||||
@@ -530,8 +539,7 @@ class Addon(AddonModel):
|
|||||||
|
|
||||||
# Write pulse config
|
# Write pulse config
|
||||||
try:
|
try:
|
||||||
with self.path_pulse.open("w") as config_file:
|
self.path_pulse.write_text(pulse_config, encoding="utf-8")
|
||||||
config_file.write(pulse_config)
|
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Add-on %s can't write pulse/client.config: %s", self.slug, err
|
"Add-on %s can't write pulse/client.config: %s", self.slug, err
|
||||||
@@ -579,7 +587,9 @@ class Addon(AddonModel):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# merge options
|
# merge options
|
||||||
options = {**self.persist[ATTR_OPTIONS], **default_options}
|
options = _OPTIONS_MERGER.merge(
|
||||||
|
deepcopy(default_options), deepcopy(self.persist[ATTR_OPTIONS])
|
||||||
|
)
|
||||||
|
|
||||||
# create voluptuous
|
# create voluptuous
|
||||||
new_schema = vol.Schema(
|
new_schema = vol.Schema(
|
||||||
@@ -671,8 +681,9 @@ class Addon(AddonModel):
|
|||||||
Return a coroutine.
|
Return a coroutine.
|
||||||
"""
|
"""
|
||||||
if not self.with_stdin:
|
if not self.with_stdin:
|
||||||
_LOGGER.error("Add-on %s does not support writing to stdin!", self.slug)
|
raise AddonsNotSupportedError(
|
||||||
raise AddonsNotSupportedError()
|
f"Add-on {self.slug} does not support writing to stdin!", _LOGGER.error
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return await self.instance.write_stdin(data)
|
return await self.instance.write_stdin(data)
|
||||||
@@ -719,24 +730,25 @@ class Addon(AddonModel):
|
|||||||
try:
|
try:
|
||||||
write_json_file(temp_path.joinpath("addon.json"), data)
|
write_json_file(temp_path.joinpath("addon.json"), data)
|
||||||
except ConfigurationFileError as err:
|
except ConfigurationFileError as err:
|
||||||
_LOGGER.error("Can't save meta for %s", self.slug)
|
raise AddonsError(
|
||||||
raise AddonsError() from err
|
f"Can't save meta for {self.slug}", _LOGGER.error
|
||||||
|
) from err
|
||||||
|
|
||||||
# Store AppArmor Profile
|
# Store AppArmor Profile
|
||||||
if self.sys_host.apparmor.exists(self.slug):
|
if self.sys_host.apparmor.exists(self.slug):
|
||||||
profile = temp_path.joinpath("apparmor.txt")
|
profile = temp_path.joinpath("apparmor.txt")
|
||||||
try:
|
try:
|
||||||
self.sys_host.apparmor.backup_profile(self.slug, profile)
|
await self.sys_host.apparmor.backup_profile(self.slug, profile)
|
||||||
except HostAppArmorError as err:
|
except HostAppArmorError as err:
|
||||||
_LOGGER.error("Can't backup AppArmor profile")
|
raise AddonsError(
|
||||||
raise AddonsError() from err
|
"Can't backup AppArmor profile", _LOGGER.error
|
||||||
|
) from err
|
||||||
|
|
||||||
# write into tarfile
|
# write into tarfile
|
||||||
def _write_tarfile():
|
def _write_tarfile():
|
||||||
"""Write tar inside loop."""
|
"""Write tar inside loop."""
|
||||||
with tar_file as backup:
|
with tar_file as backup:
|
||||||
# Backup system
|
# Backup metadata
|
||||||
|
|
||||||
backup.add(temp, arcname=".")
|
backup.add(temp, arcname=".")
|
||||||
|
|
||||||
# Backup data
|
# Backup data
|
||||||
@@ -761,8 +773,9 @@ class Addon(AddonModel):
|
|||||||
_LOGGER.info("Building backup for add-on %s", self.slug)
|
_LOGGER.info("Building backup for add-on %s", self.slug)
|
||||||
await self.sys_run_in_executor(_write_tarfile)
|
await self.sys_run_in_executor(_write_tarfile)
|
||||||
except (tarfile.TarError, OSError) as err:
|
except (tarfile.TarError, OSError) as err:
|
||||||
_LOGGER.error("Can't write tarfile %s: %s", tar_file, err)
|
raise AddonsError(
|
||||||
raise AddonsError() from err
|
f"Can't write tarfile {tar_file}: {err}", _LOGGER.error
|
||||||
|
) from err
|
||||||
finally:
|
finally:
|
||||||
if (
|
if (
|
||||||
is_running
|
is_running
|
||||||
@@ -788,8 +801,9 @@ class Addon(AddonModel):
|
|||||||
try:
|
try:
|
||||||
await self.sys_run_in_executor(_extract_tarfile)
|
await self.sys_run_in_executor(_extract_tarfile)
|
||||||
except tarfile.TarError as err:
|
except tarfile.TarError as err:
|
||||||
_LOGGER.error("Can't read tarfile %s: %s", tar_file, err)
|
raise AddonsError(
|
||||||
raise AddonsError() from err
|
f"Can't read tarfile {tar_file}: {err}", _LOGGER.error
|
||||||
|
) from err
|
||||||
|
|
||||||
# Read backup data
|
# Read backup data
|
||||||
try:
|
try:
|
||||||
@@ -801,17 +815,17 @@ class Addon(AddonModel):
|
|||||||
try:
|
try:
|
||||||
data = SCHEMA_ADDON_BACKUP(data)
|
data = SCHEMA_ADDON_BACKUP(data)
|
||||||
except vol.Invalid as err:
|
except vol.Invalid as err:
|
||||||
_LOGGER.error(
|
raise AddonsError(
|
||||||
"Can't validate %s, backup data: %s",
|
f"Can't validate {self.slug}, backup data: {humanize_error(data, err)}",
|
||||||
self.slug,
|
_LOGGER.error,
|
||||||
humanize_error(data, err),
|
) from err
|
||||||
)
|
|
||||||
raise AddonsError() from err
|
|
||||||
|
|
||||||
# If available
|
# If available
|
||||||
if not self._available(data[ATTR_SYSTEM]):
|
if not self._available(data[ATTR_SYSTEM]):
|
||||||
_LOGGER.error("Add-on %s is not available for this platform", self.slug)
|
raise AddonsNotSupportedError(
|
||||||
raise AddonsNotSupportedError()
|
f"Add-on {self.slug} is not available for this platform",
|
||||||
|
_LOGGER.error,
|
||||||
|
)
|
||||||
|
|
||||||
# Restore local add-on information
|
# Restore local add-on information
|
||||||
_LOGGER.info("Restore config for addon %s", self.slug)
|
_LOGGER.info("Restore config for addon %s", self.slug)
|
||||||
@@ -844,7 +858,11 @@ class Addon(AddonModel):
|
|||||||
# Restore data
|
# Restore data
|
||||||
def _restore_data():
|
def _restore_data():
|
||||||
"""Restore data."""
|
"""Restore data."""
|
||||||
shutil.copytree(Path(temp, "data"), self.path_data, symlinks=True)
|
temp_data = Path(temp, "data")
|
||||||
|
if temp_data.is_dir():
|
||||||
|
shutil.copytree(temp_data, self.path_data, symlinks=True)
|
||||||
|
else:
|
||||||
|
self.path_data.mkdir()
|
||||||
|
|
||||||
_LOGGER.info("Restoring data for addon %s", self.slug)
|
_LOGGER.info("Restoring data for addon %s", self.slug)
|
||||||
if self.path_data.is_dir():
|
if self.path_data.is_dir():
|
||||||
@@ -852,8 +870,9 @@ class Addon(AddonModel):
|
|||||||
try:
|
try:
|
||||||
await self.sys_run_in_executor(_restore_data)
|
await self.sys_run_in_executor(_restore_data)
|
||||||
except shutil.Error as err:
|
except shutil.Error as err:
|
||||||
_LOGGER.error("Can't restore origin data: %s", err)
|
raise AddonsError(
|
||||||
raise AddonsError() from err
|
f"Can't restore origin data: {err}", _LOGGER.error
|
||||||
|
) from err
|
||||||
|
|
||||||
# Restore AppArmor
|
# Restore AppArmor
|
||||||
profile_file = Path(temp, "apparmor.txt")
|
profile_file = Path(temp, "apparmor.txt")
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Dict
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
|
|
||||||
@@ -60,12 +60,12 @@ class AddonBuild(FileConfiguration, CoreSysAttributes):
|
|||||||
return self._data[ATTR_SQUASH]
|
return self._data[ATTR_SQUASH]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def additional_args(self) -> Dict[str, str]:
|
def additional_args(self) -> dict[str, str]:
|
||||||
"""Return additional Docker build arguments."""
|
"""Return additional Docker build arguments."""
|
||||||
return self._data[ATTR_ARGS]
|
return self._data[ATTR_ARGS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def additional_labels(self) -> Dict[str, str]:
|
def additional_labels(self) -> dict[str, str]:
|
||||||
"""Return additional Docker labels."""
|
"""Return additional Docker labels."""
|
||||||
return self._data[ATTR_LABELS]
|
return self._data[ATTR_LABELS]
|
||||||
|
|
||||||
|
@@ -10,3 +10,4 @@ class AddonBackupMode(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
ATTR_BACKUP = "backup"
|
ATTR_BACKUP = "backup"
|
||||||
|
ATTR_CODENOTARY = "codenotary"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
"""Init file for Supervisor add-on data."""
|
"""Init file for Supervisor add-on data."""
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_IMAGE,
|
ATTR_IMAGE,
|
||||||
@@ -16,7 +16,7 @@ from ..utils.common import FileConfiguration
|
|||||||
from .addon import Addon
|
from .addon import Addon
|
||||||
from .validate import SCHEMA_ADDONS_FILE
|
from .validate import SCHEMA_ADDONS_FILE
|
||||||
|
|
||||||
Config = Dict[str, Any]
|
Config = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
class AddonsData(FileConfiguration, CoreSysAttributes):
|
class AddonsData(FileConfiguration, CoreSysAttributes):
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Init file for Supervisor add-ons."""
|
"""Init file for Supervisor add-ons."""
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Awaitable, Dict, List, Optional
|
from typing import Any, Awaitable, Optional
|
||||||
|
|
||||||
from awesomeversion import AwesomeVersion, AwesomeVersionException
|
from awesomeversion import AwesomeVersion, AwesomeVersionException
|
||||||
|
|
||||||
@@ -79,11 +79,11 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..docker.const import Capabilities
|
from ..docker.const import Capabilities
|
||||||
from .const import ATTR_BACKUP
|
from .const import ATTR_BACKUP, ATTR_CODENOTARY
|
||||||
from .options import AddonOptions, UiOptions
|
from .options import AddonOptions, UiOptions
|
||||||
from .validate import RE_SERVICE, RE_VOLUME
|
from .validate import RE_SERVICE, RE_VOLUME
|
||||||
|
|
||||||
Data = Dict[str, Any]
|
Data = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
class AddonModel(CoreSysAttributes, ABC):
|
class AddonModel(CoreSysAttributes, ABC):
|
||||||
@@ -115,7 +115,7 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return self._available(self.data)
|
return self._available(self.data)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def options(self) -> Dict[str, Any]:
|
def options(self) -> dict[str, Any]:
|
||||||
"""Return options with local changes."""
|
"""Return options with local changes."""
|
||||||
return self.data[ATTR_OPTIONS]
|
return self.data[ATTR_OPTIONS]
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return self.slug.replace("_", "-")
|
return self.slug.replace("_", "-")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dns(self) -> List[str]:
|
def dns(self) -> list[str]:
|
||||||
"""Return list of DNS name for that add-on."""
|
"""Return list of DNS name for that add-on."""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -184,8 +184,7 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Return data
|
# Return data
|
||||||
with readme.open("r") as readme_file:
|
return readme.read_text(encoding="utf-8")
|
||||||
return readme_file.read()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def repository(self) -> str:
|
def repository(self) -> str:
|
||||||
@@ -228,7 +227,7 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return self.data[ATTR_STAGE]
|
return self.data[ATTR_STAGE]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def services_role(self) -> Dict[str, str]:
|
def services_role(self) -> dict[str, str]:
|
||||||
"""Return dict of services with rights."""
|
"""Return dict of services with rights."""
|
||||||
services_list = self.data.get(ATTR_SERVICES, [])
|
services_list = self.data.get(ATTR_SERVICES, [])
|
||||||
|
|
||||||
@@ -241,17 +240,17 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return services
|
return services
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def discovery(self) -> List[str]:
|
def discovery(self) -> list[str]:
|
||||||
"""Return list of discoverable components/platforms."""
|
"""Return list of discoverable components/platforms."""
|
||||||
return self.data.get(ATTR_DISCOVERY, [])
|
return self.data.get(ATTR_DISCOVERY, [])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ports_description(self) -> Optional[Dict[str, str]]:
|
def ports_description(self) -> Optional[dict[str, str]]:
|
||||||
"""Return descriptions of ports."""
|
"""Return descriptions of ports."""
|
||||||
return self.data.get(ATTR_PORTS_DESCRIPTION)
|
return self.data.get(ATTR_PORTS_DESCRIPTION)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ports(self) -> Optional[Dict[str, Optional[int]]]:
|
def ports(self) -> Optional[dict[str, Optional[int]]]:
|
||||||
"""Return ports of add-on."""
|
"""Return ports of add-on."""
|
||||||
return self.data.get(ATTR_PORTS)
|
return self.data.get(ATTR_PORTS)
|
||||||
|
|
||||||
@@ -311,17 +310,17 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return self.data[ATTR_HOST_DBUS]
|
return self.data[ATTR_HOST_DBUS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def static_devices(self) -> List[Path]:
|
def static_devices(self) -> list[Path]:
|
||||||
"""Return static devices of add-on."""
|
"""Return static devices of add-on."""
|
||||||
return [Path(node) for node in self.data.get(ATTR_DEVICES, [])]
|
return [Path(node) for node in self.data.get(ATTR_DEVICES, [])]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def environment(self) -> Optional[Dict[str, str]]:
|
def environment(self) -> Optional[dict[str, str]]:
|
||||||
"""Return environment of add-on."""
|
"""Return environment of add-on."""
|
||||||
return self.data.get(ATTR_ENVIRONMENT)
|
return self.data.get(ATTR_ENVIRONMENT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def privileged(self) -> List[Capabilities]:
|
def privileged(self) -> list[Capabilities]:
|
||||||
"""Return list of privilege."""
|
"""Return list of privilege."""
|
||||||
return self.data.get(ATTR_PRIVILEGED, [])
|
return self.data.get(ATTR_PRIVILEGED, [])
|
||||||
|
|
||||||
@@ -360,7 +359,7 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return self.data[ATTR_HASSIO_ROLE]
|
return self.data[ATTR_HASSIO_ROLE]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def backup_exclude(self) -> List[str]:
|
def backup_exclude(self) -> list[str]:
|
||||||
"""Return Exclude list for backup."""
|
"""Return Exclude list for backup."""
|
||||||
return self.data.get(ATTR_BACKUP_EXCLUDE, [])
|
return self.data.get(ATTR_BACKUP_EXCLUDE, [])
|
||||||
|
|
||||||
@@ -495,12 +494,12 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return self.path_documentation.exists()
|
return self.path_documentation.exists()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_arch(self) -> List[str]:
|
def supported_arch(self) -> list[str]:
|
||||||
"""Return list of supported arch."""
|
"""Return list of supported arch."""
|
||||||
return self.data[ATTR_ARCH]
|
return self.data[ATTR_ARCH]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_machine(self) -> List[str]:
|
def supported_machine(self) -> list[str]:
|
||||||
"""Return list of supported machine."""
|
"""Return list of supported machine."""
|
||||||
return self.data.get(ATTR_MACHINE, [])
|
return self.data.get(ATTR_MACHINE, [])
|
||||||
|
|
||||||
@@ -515,7 +514,7 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return ATTR_IMAGE not in self.data
|
return ATTR_IMAGE not in self.data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def map_volumes(self) -> Dict[str, str]:
|
def map_volumes(self) -> dict[str, str]:
|
||||||
"""Return a dict of {volume: policy} from add-on."""
|
"""Return a dict of {volume: policy} from add-on."""
|
||||||
volumes = {}
|
volumes = {}
|
||||||
for volume in self.data[ATTR_MAP]:
|
for volume in self.data[ATTR_MAP]:
|
||||||
@@ -566,7 +565,7 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return AddonOptions(self.coresys, raw_schema, self.name, self.slug)
|
return AddonOptions(self.coresys, raw_schema, self.name, self.slug)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def schema_ui(self) -> Optional[List[Dict[any, any]]]:
|
def schema_ui(self) -> Optional[list[dict[any, any]]]:
|
||||||
"""Create a UI schema for add-on options."""
|
"""Create a UI schema for add-on options."""
|
||||||
raw_schema = self.data[ATTR_SCHEMA]
|
raw_schema = self.data[ATTR_SCHEMA]
|
||||||
|
|
||||||
@@ -579,6 +578,16 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
"""Return True if the add-on accesses the system journal."""
|
"""Return True if the add-on accesses the system journal."""
|
||||||
return self.data[ATTR_JOURNALD]
|
return self.data[ATTR_JOURNALD]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def signed(self) -> bool:
|
||||||
|
"""Return True if the image is signed."""
|
||||||
|
return ATTR_CODENOTARY in self.data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def codenotary(self) -> Optional[str]:
|
||||||
|
"""Return Signer email address for CAS."""
|
||||||
|
return self.data.get(ATTR_CODENOTARY)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""Compaired add-on objects."""
|
"""Compaired add-on objects."""
|
||||||
if not isinstance(other, AddonModel):
|
if not isinstance(other, AddonModel):
|
||||||
@@ -623,9 +632,9 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
"""Uninstall this add-on."""
|
"""Uninstall this add-on."""
|
||||||
return self.sys_addons.uninstall(self.slug)
|
return self.sys_addons.uninstall(self.slug)
|
||||||
|
|
||||||
def update(self) -> Awaitable[None]:
|
def update(self, backup: Optional[bool] = False) -> Awaitable[None]:
|
||||||
"""Update this add-on."""
|
"""Update this add-on."""
|
||||||
return self.sys_addons.update(self.slug)
|
return self.sys_addons.update(self.slug, backup=backup)
|
||||||
|
|
||||||
def rebuild(self) -> Awaitable[None]:
|
def rebuild(self) -> Awaitable[None]:
|
||||||
"""Rebuild this add-on."""
|
"""Rebuild this add-on."""
|
||||||
|
@@ -3,7 +3,7 @@ import hashlib
|
|||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
from typing import Any, Dict, List, Set, Union
|
from typing import Any, Union
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@@ -59,13 +59,13 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
"""Validate Add-ons Options."""
|
"""Validate Add-ons Options."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, coresys: CoreSys, raw_schema: Dict[str, Any], name: str, slug: str
|
self, coresys: CoreSys, raw_schema: dict[str, Any], name: str, slug: str
|
||||||
):
|
):
|
||||||
"""Validate schema."""
|
"""Validate schema."""
|
||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.raw_schema: Dict[str, Any] = raw_schema
|
self.raw_schema: dict[str, Any] = raw_schema
|
||||||
self.devices: Set[Device] = set()
|
self.devices: set[Device] = set()
|
||||||
self.pwned: Set[str] = set()
|
self.pwned: set[str] = set()
|
||||||
self._name = name
|
self._name = name
|
||||||
self._slug = slug
|
self._slug = slug
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
device = self.sys_hardware.get_by_path(Path(value))
|
device = self.sys_hardware.get_by_path(Path(value))
|
||||||
except HardwareNotFound:
|
except HardwareNotFound:
|
||||||
raise vol.Invalid(
|
raise vol.Invalid(
|
||||||
f"Device '{value}' does not exists! in {self._name} ({self._slug})"
|
f"Device '{value}' does not exist in {self._name} ({self._slug})"
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
# Have filter
|
# Have filter
|
||||||
@@ -187,7 +187,7 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
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):
|
||||||
"""Validate nested items."""
|
"""Validate nested items."""
|
||||||
options = []
|
options = []
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
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
|
||||||
):
|
):
|
||||||
"""Validate nested items."""
|
"""Validate nested items."""
|
||||||
options = {}
|
options = {}
|
||||||
@@ -241,7 +241,7 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
return options
|
return options
|
||||||
|
|
||||||
def _check_missing_options(
|
def _check_missing_options(
|
||||||
self, origin: Dict[Any, Any], exists: Dict[Any, Any], root: str
|
self, origin: dict[Any, Any], exists: dict[Any, Any], root: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Check if all options are exists."""
|
"""Check if all options are exists."""
|
||||||
missing = set(origin) - set(exists)
|
missing = set(origin) - set(exists)
|
||||||
@@ -267,9 +267,9 @@ class UiOptions(CoreSysAttributes):
|
|||||||
"""Initialize UI option render."""
|
"""Initialize UI option render."""
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
|
|
||||||
def __call__(self, raw_schema: Dict[str, Any]) -> List[Dict[str, Any]]:
|
def __call__(self, raw_schema: dict[str, Any]) -> list[dict[str, Any]]:
|
||||||
"""Generate UI schema."""
|
"""Generate UI schema."""
|
||||||
ui_schema: List[Dict[str, Any]] = []
|
ui_schema: list[dict[str, Any]] = []
|
||||||
|
|
||||||
# read options
|
# read options
|
||||||
for key, value in raw_schema.items():
|
for key, value in raw_schema.items():
|
||||||
@@ -287,13 +287,13 @@ class UiOptions(CoreSysAttributes):
|
|||||||
|
|
||||||
def _single_ui_option(
|
def _single_ui_option(
|
||||||
self,
|
self,
|
||||||
ui_schema: List[Dict[str, Any]],
|
ui_schema: list[dict[str, Any]],
|
||||||
value: str,
|
value: str,
|
||||||
key: str,
|
key: str,
|
||||||
multiple: bool = False,
|
multiple: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Validate a single element."""
|
"""Validate a single element."""
|
||||||
ui_node: Dict[str, Union[str, bool, float, List[str]]] = {"name": key}
|
ui_node: dict[str, Union[str, bool, float, list[str]]] = {"name": key}
|
||||||
|
|
||||||
# If multiple
|
# If multiple
|
||||||
if multiple:
|
if multiple:
|
||||||
@@ -365,8 +365,8 @@ class UiOptions(CoreSysAttributes):
|
|||||||
|
|
||||||
def _nested_ui_list(
|
def _nested_ui_list(
|
||||||
self,
|
self,
|
||||||
ui_schema: List[Dict[str, Any]],
|
ui_schema: list[dict[str, Any]],
|
||||||
option_list: List[Any],
|
option_list: list[Any],
|
||||||
key: str,
|
key: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""UI nested list items."""
|
"""UI nested list items."""
|
||||||
@@ -383,8 +383,8 @@ class UiOptions(CoreSysAttributes):
|
|||||||
|
|
||||||
def _nested_ui_dict(
|
def _nested_ui_dict(
|
||||||
self,
|
self,
|
||||||
ui_schema: List[Dict[str, Any]],
|
ui_schema: list[dict[str, Any]],
|
||||||
option_dict: Dict[str, Any],
|
option_dict: dict[str, Any],
|
||||||
key: str,
|
key: str,
|
||||||
multiple: bool = False,
|
multiple: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -408,7 +408,7 @@ class UiOptions(CoreSysAttributes):
|
|||||||
ui_schema.append(ui_node)
|
ui_schema.append(ui_node)
|
||||||
|
|
||||||
|
|
||||||
def _create_device_filter(str_filter: str) -> Dict[str, Any]:
|
def _create_device_filter(str_filter: str) -> dict[str, Any]:
|
||||||
"""Generate device Filter."""
|
"""Generate device Filter."""
|
||||||
raw_filter = dict(value.split("=") for value in str_filter.split(";"))
|
raw_filter = dict(value.split("=") for value in str_filter.split(";"))
|
||||||
|
|
||||||
|
@@ -16,10 +16,10 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def rating_security(addon: AddonModel) -> int:
|
def rating_security(addon: AddonModel) -> int:
|
||||||
"""Return 1-6 for security rating.
|
"""Return 1-8 for security rating.
|
||||||
|
|
||||||
1 = not secure
|
1 = not secure
|
||||||
6 = high secure
|
8 = high secure
|
||||||
"""
|
"""
|
||||||
rating = 5
|
rating = 5
|
||||||
|
|
||||||
@@ -35,6 +35,10 @@ def rating_security(addon: AddonModel) -> int:
|
|||||||
elif addon.access_auth_api:
|
elif addon.access_auth_api:
|
||||||
rating += 1
|
rating += 1
|
||||||
|
|
||||||
|
# Signed
|
||||||
|
if addon.signed:
|
||||||
|
rating += 1
|
||||||
|
|
||||||
# Privileged options
|
# Privileged options
|
||||||
if (
|
if (
|
||||||
any(
|
any(
|
||||||
@@ -70,7 +74,7 @@ def rating_security(addon: AddonModel) -> int:
|
|||||||
if addon.access_docker_api or addon.with_full_access:
|
if addon.access_docker_api or addon.with_full_access:
|
||||||
rating = 1
|
rating = 1
|
||||||
|
|
||||||
return max(min(6, rating), 1)
|
return max(min(8, rating), 1)
|
||||||
|
|
||||||
|
|
||||||
async def remove_data(folder: Path) -> None:
|
async def remove_data(folder: Path) -> None:
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -110,7 +110,7 @@ from ..validate import (
|
|||||||
uuid_match,
|
uuid_match,
|
||||||
version_tag,
|
version_tag,
|
||||||
)
|
)
|
||||||
from .const import ATTR_BACKUP
|
from .const import ATTR_BACKUP, ATTR_CODENOTARY
|
||||||
from .options import RE_SCHEMA_ELEMENT
|
from .options import RE_SCHEMA_ELEMENT
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
@@ -148,7 +148,7 @@ RE_MACHINE = re.compile(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _warn_addon_config(config: Dict[str, Any]):
|
def _warn_addon_config(config: dict[str, Any]):
|
||||||
"""Warn about miss configs."""
|
"""Warn about miss configs."""
|
||||||
name = config.get(ATTR_NAME)
|
name = config.get(ATTR_NAME)
|
||||||
if not name:
|
if not name:
|
||||||
@@ -179,7 +179,7 @@ def _warn_addon_config(config: Dict[str, Any]):
|
|||||||
def _migrate_addon_config(protocol=False):
|
def _migrate_addon_config(protocol=False):
|
||||||
"""Migrate addon config."""
|
"""Migrate addon config."""
|
||||||
|
|
||||||
def _migrate(config: Dict[str, Any]):
|
def _migrate(config: dict[str, Any]):
|
||||||
name = config.get(ATTR_NAME)
|
name = config.get(ATTR_NAME)
|
||||||
if not name:
|
if not name:
|
||||||
raise vol.Invalid("Invalid Add-on config!")
|
raise vol.Invalid("Invalid Add-on config!")
|
||||||
@@ -281,7 +281,7 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_PANEL_ICON, default="mdi:puzzle"): str,
|
vol.Optional(ATTR_PANEL_ICON, default="mdi:puzzle"): str,
|
||||||
vol.Optional(ATTR_PANEL_TITLE): str,
|
vol.Optional(ATTR_PANEL_TITLE): str,
|
||||||
vol.Optional(ATTR_PANEL_ADMIN, default=True): vol.Boolean(),
|
vol.Optional(ATTR_PANEL_ADMIN, default=True): vol.Boolean(),
|
||||||
vol.Optional(ATTR_HOMEASSISTANT): vol.Maybe(version_tag),
|
vol.Optional(ATTR_HOMEASSISTANT): version_tag,
|
||||||
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_HOST_PID, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HOST_PID, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_HOST_IPC, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HOST_IPC, default=False): vol.Boolean(),
|
||||||
@@ -317,6 +317,7 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_BACKUP, default=AddonBackupMode.HOT): vol.Coerce(
|
vol.Optional(ATTR_BACKUP, default=AddonBackupMode.HOT): vol.Coerce(
|
||||||
AddonBackupMode
|
AddonBackupMode
|
||||||
),
|
),
|
||||||
|
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(
|
||||||
|
@@ -17,7 +17,6 @@ from .docker import APIDocker
|
|||||||
from .hardware import APIHardware
|
from .hardware import APIHardware
|
||||||
from .homeassistant import APIHomeAssistant
|
from .homeassistant import APIHomeAssistant
|
||||||
from .host import APIHost
|
from .host import APIHost
|
||||||
from .info import APIInfo
|
|
||||||
from .ingress import APIIngress
|
from .ingress import APIIngress
|
||||||
from .jobs import APIJobs
|
from .jobs import APIJobs
|
||||||
from .middleware.security import SecurityMiddleware
|
from .middleware.security import SecurityMiddleware
|
||||||
@@ -27,6 +26,7 @@ from .observer import APIObserver
|
|||||||
from .os import APIOS
|
from .os import APIOS
|
||||||
from .proxy import APIProxy
|
from .proxy import APIProxy
|
||||||
from .resolution import APIResoulution
|
from .resolution import APIResoulution
|
||||||
|
from .root import APIRoot
|
||||||
from .security import APISecurity
|
from .security import APISecurity
|
||||||
from .services import APIServices
|
from .services import APIServices
|
||||||
from .store import APIStore
|
from .store import APIStore
|
||||||
@@ -35,7 +35,7 @@ from .supervisor import APISupervisor
|
|||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
MAX_CLIENT_SIZE: int = 1024 ** 2 * 16
|
MAX_CLIENT_SIZE: int = 1024**2 * 16
|
||||||
|
|
||||||
|
|
||||||
class RestAPI(CoreSysAttributes):
|
class RestAPI(CoreSysAttributes):
|
||||||
@@ -70,7 +70,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
self._register_hardware()
|
self._register_hardware()
|
||||||
self._register_homeassistant()
|
self._register_homeassistant()
|
||||||
self._register_host()
|
self._register_host()
|
||||||
self._register_info()
|
self._register_root()
|
||||||
self._register_ingress()
|
self._register_ingress()
|
||||||
self._register_multicast()
|
self._register_multicast()
|
||||||
self._register_network()
|
self._register_network()
|
||||||
@@ -145,6 +145,8 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get("/os/info", api_os.info),
|
web.get("/os/info", api_os.info),
|
||||||
web.post("/os/update", api_os.update),
|
web.post("/os/update", api_os.update),
|
||||||
web.post("/os/config/sync", api_os.config_sync),
|
web.post("/os/config/sync", api_os.config_sync),
|
||||||
|
web.post("/os/datadisk/move", api_os.migrate_data),
|
||||||
|
web.get("/os/datadisk/list", api_os.list_data),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -226,12 +228,21 @@ class RestAPI(CoreSysAttributes):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _register_info(self) -> None:
|
def _register_root(self) -> None:
|
||||||
"""Register info functions."""
|
"""Register root functions."""
|
||||||
api_info = APIInfo()
|
api_root = APIRoot()
|
||||||
api_info.coresys = self.coresys
|
api_root.coresys = self.coresys
|
||||||
|
|
||||||
self.webapp.add_routes([web.get("/info", api_info.info)])
|
self.webapp.add_routes([web.get("/info", api_root.info)])
|
||||||
|
self.webapp.add_routes([web.post("/refresh_updates", api_root.refresh_updates)])
|
||||||
|
self.webapp.add_routes(
|
||||||
|
[web.get("/available_updates", api_root.available_updates)]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove 2023
|
||||||
|
self.webapp.add_routes(
|
||||||
|
[web.get("/supervisor/available_updates", api_root.available_updates)]
|
||||||
|
)
|
||||||
|
|
||||||
def _register_resolution(self) -> None:
|
def _register_resolution(self) -> None:
|
||||||
"""Register info functions."""
|
"""Register info functions."""
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Init file for Supervisor Home Assistant RESTful API."""
|
"""Init file for Supervisor Home Assistant RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Awaitable, Dict, List
|
from typing import Any, Awaitable
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -106,11 +106,12 @@ from ..coresys import CoreSysAttributes
|
|||||||
from ..docker.stats import DockerStats
|
from ..docker.stats import DockerStats
|
||||||
from ..exceptions import APIError, APIForbidden, PwnedError, PwnedSecret
|
from ..exceptions import APIError, APIForbidden, PwnedError, PwnedSecret
|
||||||
from ..validate import docker_ports
|
from ..validate import docker_ports
|
||||||
|
from .const import ATTR_SIGNED
|
||||||
from .utils import api_process, api_process_raw, api_validate, json_loads
|
from .utils import api_process, api_process_raw, api_validate, json_loads
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): str})
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_OPTIONS = vol.Schema(
|
SCHEMA_OPTIONS = vol.Schema(
|
||||||
@@ -118,8 +119,8 @@ SCHEMA_OPTIONS = vol.Schema(
|
|||||||
vol.Optional(ATTR_BOOT): vol.Coerce(AddonBoot),
|
vol.Optional(ATTR_BOOT): vol.Coerce(AddonBoot),
|
||||||
vol.Optional(ATTR_NETWORK): vol.Maybe(docker_ports),
|
vol.Optional(ATTR_NETWORK): vol.Maybe(docker_ports),
|
||||||
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
|
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
|
||||||
vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(vol.Coerce(str)),
|
vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(str),
|
||||||
vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(vol.Coerce(str)),
|
vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(str),
|
||||||
vol.Optional(ATTR_INGRESS_PANEL): vol.Boolean(),
|
vol.Optional(ATTR_INGRESS_PANEL): vol.Boolean(),
|
||||||
vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
|
vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
|
||||||
}
|
}
|
||||||
@@ -156,7 +157,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
return addon
|
return addon
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def list(self, request: web.Request) -> Dict[str, Any]:
|
async def list(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return all add-ons or repositories."""
|
"""Return all add-ons or repositories."""
|
||||||
data_addons = [
|
data_addons = [
|
||||||
{
|
{
|
||||||
@@ -201,7 +202,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
await asyncio.shield(self.sys_store.reload())
|
await asyncio.shield(self.sys_store.reload())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return add-on information."""
|
"""Return add-on information."""
|
||||||
addon: AnyAddon = self._extract_addon(request)
|
addon: AnyAddon = self._extract_addon(request)
|
||||||
|
|
||||||
@@ -269,6 +270,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_IP_ADDRESS: None,
|
ATTR_IP_ADDRESS: None,
|
||||||
ATTR_TRANSLATIONS: addon.translations,
|
ATTR_TRANSLATIONS: addon.translations,
|
||||||
ATTR_INGRESS: addon.with_ingress,
|
ATTR_INGRESS: addon.with_ingress,
|
||||||
|
ATTR_SIGNED: addon.signed,
|
||||||
ATTR_INGRESS_ENTRY: None,
|
ATTR_INGRESS_ENTRY: None,
|
||||||
ATTR_INGRESS_URL: None,
|
ATTR_INGRESS_URL: None,
|
||||||
ATTR_INGRESS_PORT: None,
|
ATTR_INGRESS_PORT: None,
|
||||||
@@ -309,7 +311,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
# Extend schema with add-on specific validation
|
# Extend schema with add-on specific validation
|
||||||
addon_schema = SCHEMA_OPTIONS.extend(
|
addon_schema = SCHEMA_OPTIONS.extend(
|
||||||
{vol.Optional(ATTR_OPTIONS): vol.Any(None, addon.schema)}
|
{vol.Optional(ATTR_OPTIONS): vol.Maybe(addon.schema)}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate/Process Body
|
# Validate/Process Body
|
||||||
@@ -379,8 +381,10 @@ class APIAddons(CoreSysAttributes):
|
|||||||
slug: str = request.match_info.get("addon")
|
slug: str = request.match_info.get("addon")
|
||||||
if slug != "self":
|
if slug != "self":
|
||||||
raise APIForbidden("This can be only read by the Add-on itself!")
|
raise APIForbidden("This can be only read by the Add-on itself!")
|
||||||
|
|
||||||
addon = self._extract_addon_installed(request)
|
addon = self._extract_addon_installed(request)
|
||||||
|
|
||||||
|
# Lookup/reload secrets
|
||||||
|
await self.sys_homeassistant.secrets.reload()
|
||||||
try:
|
try:
|
||||||
return addon.schema.validate(addon.options)
|
return addon.schema.validate(addon.options)
|
||||||
except vol.Invalid:
|
except vol.Invalid:
|
||||||
@@ -390,7 +394,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
async def security(self, request: web.Request) -> None:
|
async def security(self, request: web.Request) -> None:
|
||||||
"""Store security options for add-on."""
|
"""Store security options for add-on."""
|
||||||
addon = self._extract_addon_installed(request)
|
addon = self._extract_addon_installed(request)
|
||||||
body: Dict[str, Any] = await api_validate(SCHEMA_SECURITY, request)
|
body: dict[str, Any] = await api_validate(SCHEMA_SECURITY, request)
|
||||||
|
|
||||||
if ATTR_PROTECTED in body:
|
if ATTR_PROTECTED in body:
|
||||||
_LOGGER.warning("Changing protected flag for %s!", addon.slug)
|
_LOGGER.warning("Changing protected flag for %s!", addon.slug)
|
||||||
@@ -399,7 +403,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
addon.save_persist()
|
addon.save_persist()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
async def stats(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
addon = self._extract_addon_installed(request)
|
addon = self._extract_addon_installed(request)
|
||||||
|
|
||||||
@@ -503,6 +507,6 @@ class APIAddons(CoreSysAttributes):
|
|||||||
await asyncio.shield(addon.write_stdin(data))
|
await asyncio.shield(addon.write_stdin(data))
|
||||||
|
|
||||||
|
|
||||||
def _pretty_services(addon: AnyAddon) -> List[str]:
|
def _pretty_services(addon: AnyAddon) -> list[str]:
|
||||||
"""Return a simplified services role list."""
|
"""Return a simplified services role list."""
|
||||||
return [f"{name}:{access}" for name, access in addon.services_role.items()]
|
return [f"{name}:{access}" for name, access in addon.services_role.items()]
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Init file for Supervisor Audio RESTful API."""
|
"""Init file for Supervisor Audio RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Awaitable, Dict
|
from typing import Any, Awaitable
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import attr
|
import attr
|
||||||
@@ -56,10 +56,10 @@ SCHEMA_MUTE = vol.Schema(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
SCHEMA_DEFAULT = vol.Schema({vol.Required(ATTR_NAME): vol.Coerce(str)})
|
SCHEMA_DEFAULT = vol.Schema({vol.Required(ATTR_NAME): str})
|
||||||
|
|
||||||
SCHEMA_PROFILE = vol.Schema(
|
SCHEMA_PROFILE = vol.Schema(
|
||||||
{vol.Required(ATTR_CARD): vol.Coerce(str), vol.Required(ATTR_NAME): vol.Coerce(str)}
|
{vol.Required(ATTR_CARD): str, vol.Required(ATTR_NAME): str}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ class APIAudio(CoreSysAttributes):
|
|||||||
"""Handle RESTful API for Audio functions."""
|
"""Handle RESTful API for Audio functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return Audio information."""
|
"""Return Audio information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_plugins.audio.version,
|
ATTR_VERSION: self.sys_plugins.audio.version,
|
||||||
@@ -89,7 +89,7 @@ class APIAudio(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
async def stats(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_plugins.audio.stats()
|
stats = await self.sys_plugins.audio.stats()
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
"""Init file for Supervisor auth/SSO RESTful API."""
|
"""Init file for Supervisor auth/SSO RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
from aiohttp import BasicAuth, web
|
from aiohttp import BasicAuth, web
|
||||||
from aiohttp.hdrs import AUTHORIZATION, CONTENT_TYPE, WWW_AUTHENTICATE
|
from aiohttp.hdrs import AUTHORIZATION, CONTENT_TYPE, WWW_AUTHENTICATE
|
||||||
@@ -24,12 +23,12 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
SCHEMA_PASSWORD_RESET = vol.Schema(
|
SCHEMA_PASSWORD_RESET = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(ATTR_USERNAME): vol.Coerce(str),
|
vol.Required(ATTR_USERNAME): str,
|
||||||
vol.Required(ATTR_PASSWORD): vol.Coerce(str),
|
vol.Required(ATTR_PASSWORD): str,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
REALM_HEADER: Dict[str, str] = {
|
REALM_HEADER: dict[str, str] = {
|
||||||
WWW_AUTHENTICATE: 'Basic realm="Home Assistant Authentication"'
|
WWW_AUTHENTICATE: 'Basic realm="Home Assistant Authentication"'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +45,7 @@ class APIAuth(CoreSysAttributes):
|
|||||||
return self.sys_auth.check_login(addon, auth.login, auth.password)
|
return self.sys_auth.check_login(addon, auth.login, auth.password)
|
||||||
|
|
||||||
def _process_dict(
|
def _process_dict(
|
||||||
self, request: web.Request, addon: Addon, data: Dict[str, str]
|
self, request: web.Request, addon: Addon, data: dict[str, str]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Process login with dict data.
|
"""Process login with dict data.
|
||||||
|
|
||||||
@@ -86,7 +85,7 @@ class APIAuth(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def reset(self, request: web.Request) -> None:
|
async def reset(self, request: web.Request) -> None:
|
||||||
"""Process reset password request."""
|
"""Process reset password request."""
|
||||||
body: Dict[str, str] = await api_validate(SCHEMA_PASSWORD_RESET, request)
|
body: dict[str, str] = await api_validate(SCHEMA_PASSWORD_RESET, request)
|
||||||
await asyncio.shield(
|
await asyncio.shield(
|
||||||
self.sys_auth.change_password(body[ATTR_USERNAME], body[ATTR_PASSWORD])
|
self.sys_auth.change_password(body[ATTR_USERNAME], body[ATTR_PASSWORD])
|
||||||
)
|
)
|
||||||
|
@@ -13,6 +13,7 @@ from ..backups.validate import ALL_FOLDERS
|
|||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_ADDONS,
|
ATTR_ADDONS,
|
||||||
ATTR_BACKUPS,
|
ATTR_BACKUPS,
|
||||||
|
ATTR_COMPRESSED,
|
||||||
ATTR_CONTENT,
|
ATTR_CONTENT,
|
||||||
ATTR_DATE,
|
ATTR_DATE,
|
||||||
ATTR_FOLDERS,
|
ATTR_FOLDERS,
|
||||||
@@ -38,27 +39,26 @@ RE_SLUGIFY_NAME = re.compile(r"[^A-Za-z0-9]+")
|
|||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_RESTORE_PARTIAL = vol.Schema(
|
SCHEMA_RESTORE_PARTIAL = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
vol.Optional(ATTR_PASSWORD): vol.Maybe(str),
|
||||||
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
|
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
|
||||||
vol.Optional(ATTR_ADDONS): vol.All([vol.Coerce(str)], vol.Unique()),
|
vol.Optional(ATTR_ADDONS): vol.All([str], vol.Unique()),
|
||||||
vol.Optional(ATTR_FOLDERS): vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
vol.Optional(ATTR_FOLDERS): vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
SCHEMA_RESTORE_FULL = vol.Schema(
|
SCHEMA_RESTORE_FULL = vol.Schema({vol.Optional(ATTR_PASSWORD): vol.Maybe(str)})
|
||||||
{vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str))}
|
|
||||||
)
|
|
||||||
|
|
||||||
SCHEMA_BACKUP_FULL = vol.Schema(
|
SCHEMA_BACKUP_FULL = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(ATTR_NAME): vol.Coerce(str),
|
vol.Optional(ATTR_NAME): str,
|
||||||
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
vol.Optional(ATTR_PASSWORD): vol.Maybe(str),
|
||||||
|
vol.Optional(ATTR_COMPRESSED): vol.Maybe(vol.Boolean()),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
SCHEMA_BACKUP_PARTIAL = SCHEMA_BACKUP_FULL.extend(
|
SCHEMA_BACKUP_PARTIAL = SCHEMA_BACKUP_FULL.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(ATTR_ADDONS): vol.All([vol.Coerce(str)], vol.Unique()),
|
vol.Optional(ATTR_ADDONS): vol.All([str], vol.Unique()),
|
||||||
vol.Optional(ATTR_FOLDERS): vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
vol.Optional(ATTR_FOLDERS): vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
||||||
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
|
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,9 @@ class APIBackups(CoreSysAttributes):
|
|||||||
ATTR_NAME: backup.name,
|
ATTR_NAME: backup.name,
|
||||||
ATTR_DATE: backup.date,
|
ATTR_DATE: backup.date,
|
||||||
ATTR_TYPE: backup.sys_type,
|
ATTR_TYPE: backup.sys_type,
|
||||||
|
ATTR_SIZE: backup.size,
|
||||||
ATTR_PROTECTED: backup.protected,
|
ATTR_PROTECTED: backup.protected,
|
||||||
|
ATTR_COMPRESSED: backup.compressed,
|
||||||
ATTR_CONTENT: {
|
ATTR_CONTENT: {
|
||||||
ATTR_HOMEASSISTANT: backup.homeassistant_version is not None,
|
ATTR_HOMEASSISTANT: backup.homeassistant_version is not None,
|
||||||
ATTR_ADDONS: backup.addon_list,
|
ATTR_ADDONS: backup.addon_list,
|
||||||
@@ -129,6 +131,7 @@ class APIBackups(CoreSysAttributes):
|
|||||||
ATTR_NAME: backup.name,
|
ATTR_NAME: backup.name,
|
||||||
ATTR_DATE: backup.date,
|
ATTR_DATE: backup.date,
|
||||||
ATTR_SIZE: backup.size,
|
ATTR_SIZE: backup.size,
|
||||||
|
ATTR_COMPRESSED: backup.compressed,
|
||||||
ATTR_PROTECTED: backup.protected,
|
ATTR_PROTECTED: backup.protected,
|
||||||
ATTR_HOMEASSISTANT: backup.homeassistant_version,
|
ATTR_HOMEASSISTANT: backup.homeassistant_version,
|
||||||
ATTR_ADDONS: data_addons,
|
ATTR_ADDONS: data_addons,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Init file for Supervisor HA cli RESTful API."""
|
"""Init file for Supervisor HA cli RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -32,7 +32,7 @@ class APICli(CoreSysAttributes):
|
|||||||
"""Handle RESTful API for HA Cli functions."""
|
"""Handle RESTful API for HA Cli functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return HA cli information."""
|
"""Return HA cli information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_plugins.cli.version,
|
ATTR_VERSION: self.sys_plugins.cli.version,
|
||||||
@@ -41,7 +41,7 @@ class APICli(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
async def stats(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_plugins.cli.stats()
|
stats = await self.sys_plugins.cli.stats()
|
||||||
|
|
||||||
|
@@ -1,6 +1,16 @@
|
|||||||
"""Const for API."""
|
"""Const for API."""
|
||||||
|
|
||||||
ATTR_USE_RTC = "use_rtc"
|
ATTR_AGENT_VERSION = "agent_version"
|
||||||
ATTR_USE_NTP = "use_ntp"
|
ATTR_BOOT_TIMESTAMP = "boot_timestamp"
|
||||||
ATTR_DT_UTC = "dt_utc"
|
ATTR_DATA_DISK = "data_disk"
|
||||||
|
ATTR_DEVICE = "device"
|
||||||
ATTR_DT_SYNCHRONIZED = "dt_synchronized"
|
ATTR_DT_SYNCHRONIZED = "dt_synchronized"
|
||||||
|
ATTR_DT_UTC = "dt_utc"
|
||||||
|
ATTR_STARTUP_TIME = "startup_time"
|
||||||
|
ATTR_USE_NTP = "use_ntp"
|
||||||
|
ATTR_USE_RTC = "use_rtc"
|
||||||
|
ATTR_APPARMOR_VERSION = "apparmor_version"
|
||||||
|
ATTR_PANEL_PATH = "panel_path"
|
||||||
|
ATTR_UPDATE_TYPE = "update_type"
|
||||||
|
ATTR_AVAILABLE_UPDATES = "available_updates"
|
||||||
|
ATTR_SIGNED = "signed"
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Init file for Supervisor DNS RESTful API."""
|
"""Init file for Supervisor DNS RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Awaitable, Dict
|
from typing import Any, Awaitable
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -40,7 +40,7 @@ class APICoreDNS(CoreSysAttributes):
|
|||||||
"""Handle RESTful API for DNS functions."""
|
"""Handle RESTful API for DNS functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return DNS information."""
|
"""Return DNS information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_plugins.dns.version,
|
ATTR_VERSION: self.sys_plugins.dns.version,
|
||||||
@@ -63,7 +63,7 @@ class APICoreDNS(CoreSysAttributes):
|
|||||||
self.sys_plugins.dns.save_data()
|
self.sys_plugins.dns.save_data()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
async def stats(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_plugins.dns.stats()
|
stats = await self.sys_plugins.dns.stats()
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
"""Init file for Supervisor Home Assistant RESTful API."""
|
"""Init file for Supervisor Home Assistant RESTful API."""
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -21,7 +21,7 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
SCHEMA_DOCKER_REGISTRY = vol.Schema(
|
SCHEMA_DOCKER_REGISTRY = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Coerce(str): {
|
str: {
|
||||||
vol.Required(ATTR_USERNAME): str,
|
vol.Required(ATTR_USERNAME): str,
|
||||||
vol.Required(ATTR_PASSWORD): str,
|
vol.Required(ATTR_PASSWORD): str,
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ class APIDocker(CoreSysAttributes):
|
|||||||
"""Handle RESTful API for Docker configuration."""
|
"""Handle RESTful API for Docker configuration."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def registries(self, request) -> Dict[str, Any]:
|
async def registries(self, request) -> dict[str, Any]:
|
||||||
"""Return the list of registries."""
|
"""Return the list of registries."""
|
||||||
data_registries = {}
|
data_registries = {}
|
||||||
for hostname, registry in self.sys_docker.config.registries.items():
|
for hostname, registry in self.sys_docker.config.registries.items():
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
"""Init file for Supervisor hardware RESTful API."""
|
"""Init file for Supervisor hardware RESTful API."""
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ from .utils import api_process
|
|||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def device_struct(device: Device) -> Dict[str, Any]:
|
def device_struct(device: Device) -> dict[str, Any]:
|
||||||
"""Return a dict with information of a interface to be used in th API."""
|
"""Return a dict with information of a interface to be used in th API."""
|
||||||
return {
|
return {
|
||||||
ATTR_NAME: device.name,
|
ATTR_NAME: device.name,
|
||||||
@@ -35,7 +35,7 @@ class APIHardware(CoreSysAttributes):
|
|||||||
"""Handle RESTful API for hardware functions."""
|
"""Handle RESTful API for hardware functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Show hardware info."""
|
"""Show hardware info."""
|
||||||
return {
|
return {
|
||||||
ATTR_DEVICES: [
|
ATTR_DEVICES: [
|
||||||
@@ -44,7 +44,7 @@ class APIHardware(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def audio(self, request: web.Request) -> Dict[str, Any]:
|
async def audio(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Show pulse audio profiles."""
|
"""Show pulse audio profiles."""
|
||||||
return {
|
return {
|
||||||
ATTR_AUDIO: {
|
ATTR_AUDIO: {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Init file for Supervisor Home Assistant RESTful API."""
|
"""Init file for Supervisor Home Assistant RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Awaitable, Dict
|
from typing import Any, Awaitable
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -10,6 +10,7 @@ from ..const import (
|
|||||||
ATTR_ARCH,
|
ATTR_ARCH,
|
||||||
ATTR_AUDIO_INPUT,
|
ATTR_AUDIO_INPUT,
|
||||||
ATTR_AUDIO_OUTPUT,
|
ATTR_AUDIO_OUTPUT,
|
||||||
|
ATTR_BACKUP,
|
||||||
ATTR_BLK_READ,
|
ATTR_BLK_READ,
|
||||||
ATTR_BLK_WRITE,
|
ATTR_BLK_WRITE,
|
||||||
ATTR_BOOT,
|
ATTR_BOOT,
|
||||||
@@ -43,25 +44,30 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
|
|||||||
SCHEMA_OPTIONS = vol.Schema(
|
SCHEMA_OPTIONS = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(ATTR_BOOT): vol.Boolean(),
|
vol.Optional(ATTR_BOOT): vol.Boolean(),
|
||||||
vol.Optional(ATTR_IMAGE): docker_image,
|
vol.Optional(ATTR_IMAGE): vol.Maybe(docker_image),
|
||||||
vol.Optional(ATTR_PORT): network_port,
|
vol.Optional(ATTR_PORT): network_port,
|
||||||
vol.Optional(ATTR_SSL): vol.Boolean(),
|
vol.Optional(ATTR_SSL): vol.Boolean(),
|
||||||
vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
|
vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
|
||||||
vol.Optional(ATTR_WAIT_BOOT): vol.All(vol.Coerce(int), vol.Range(min=60)),
|
vol.Optional(ATTR_WAIT_BOOT): vol.All(vol.Coerce(int), vol.Range(min=60)),
|
||||||
vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(vol.Coerce(str)),
|
vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(str),
|
||||||
vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(vol.Coerce(str)),
|
vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(str),
|
||||||
vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(vol.Coerce(str)),
|
vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(str),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): version_tag})
|
SCHEMA_UPDATE = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_VERSION): version_tag,
|
||||||
|
vol.Optional(ATTR_BACKUP): bool,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class APIHomeAssistant(CoreSysAttributes):
|
class APIHomeAssistant(CoreSysAttributes):
|
||||||
"""Handle RESTful API for Home Assistant functions."""
|
"""Handle RESTful API for Home Assistant functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return host information."""
|
"""Return host information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_homeassistant.version,
|
ATTR_VERSION: self.sys_homeassistant.version,
|
||||||
@@ -117,7 +123,7 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
self.sys_homeassistant.save_data()
|
self.sys_homeassistant.save_data()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request: web.Request) -> Dict[Any, str]:
|
async def stats(self, request: web.Request) -> dict[Any, str]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_homeassistant.core.stats()
|
stats = await self.sys_homeassistant.core.stats()
|
||||||
if not stats:
|
if not stats:
|
||||||
@@ -137,10 +143,14 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def update(self, request: web.Request) -> None:
|
async def update(self, request: web.Request) -> None:
|
||||||
"""Update Home Assistant."""
|
"""Update Home Assistant."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_UPDATE, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_homeassistant.latest_version)
|
|
||||||
|
|
||||||
await asyncio.shield(self.sys_homeassistant.core.update(version))
|
await asyncio.shield(
|
||||||
|
self.sys_homeassistant.core.update(
|
||||||
|
version=body.get(ATTR_VERSION, self.sys_homeassistant.latest_version),
|
||||||
|
backup=body.get(ATTR_BACKUP),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def stop(self, request: web.Request) -> Awaitable[None]:
|
def stop(self, request: web.Request) -> Awaitable[None]:
|
||||||
|
@@ -25,12 +25,21 @@ from ..const import (
|
|||||||
CONTENT_TYPE_BINARY,
|
CONTENT_TYPE_BINARY,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from .const import ATTR_DT_SYNCHRONIZED, ATTR_DT_UTC, ATTR_USE_NTP, ATTR_USE_RTC
|
from .const import (
|
||||||
|
ATTR_AGENT_VERSION,
|
||||||
|
ATTR_APPARMOR_VERSION,
|
||||||
|
ATTR_BOOT_TIMESTAMP,
|
||||||
|
ATTR_DT_SYNCHRONIZED,
|
||||||
|
ATTR_DT_UTC,
|
||||||
|
ATTR_STARTUP_TIME,
|
||||||
|
ATTR_USE_NTP,
|
||||||
|
ATTR_USE_RTC,
|
||||||
|
)
|
||||||
from .utils import api_process, api_process_raw, api_validate
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
SERVICE = "service"
|
SERVICE = "service"
|
||||||
|
|
||||||
SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_HOSTNAME): vol.Coerce(str)})
|
SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_HOSTNAME): str})
|
||||||
|
|
||||||
|
|
||||||
class APIHost(CoreSysAttributes):
|
class APIHost(CoreSysAttributes):
|
||||||
@@ -40,6 +49,8 @@ class APIHost(CoreSysAttributes):
|
|||||||
async def info(self, request):
|
async def info(self, request):
|
||||||
"""Return host information."""
|
"""Return host information."""
|
||||||
return {
|
return {
|
||||||
|
ATTR_AGENT_VERSION: self.sys_dbus.agent.version,
|
||||||
|
ATTR_APPARMOR_VERSION: self.sys_host.apparmor.version,
|
||||||
ATTR_CHASSIS: self.sys_host.info.chassis,
|
ATTR_CHASSIS: self.sys_host.info.chassis,
|
||||||
ATTR_CPE: self.sys_host.info.cpe,
|
ATTR_CPE: self.sys_host.info.cpe,
|
||||||
ATTR_DEPLOYMENT: self.sys_host.info.deployment,
|
ATTR_DEPLOYMENT: self.sys_host.info.deployment,
|
||||||
@@ -56,6 +67,8 @@ class APIHost(CoreSysAttributes):
|
|||||||
ATTR_DT_SYNCHRONIZED: self.sys_host.info.dt_synchronized,
|
ATTR_DT_SYNCHRONIZED: self.sys_host.info.dt_synchronized,
|
||||||
ATTR_USE_NTP: self.sys_host.info.use_ntp,
|
ATTR_USE_NTP: self.sys_host.info.use_ntp,
|
||||||
ATTR_USE_RTC: self.sys_host.info.use_rtc,
|
ATTR_USE_RTC: self.sys_host.info.use_rtc,
|
||||||
|
ATTR_STARTUP_TIME: self.sys_host.info.startup_time,
|
||||||
|
ATTR_BOOT_TIMESTAMP: self.sys_host.info.boot_timestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@@ -1,52 +0,0 @@
|
|||||||
"""Init file for Supervisor info RESTful API."""
|
|
||||||
import logging
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from aiohttp import web
|
|
||||||
|
|
||||||
from ..const import (
|
|
||||||
ATTR_ARCH,
|
|
||||||
ATTR_CHANNEL,
|
|
||||||
ATTR_DOCKER,
|
|
||||||
ATTR_FEATURES,
|
|
||||||
ATTR_HASSOS,
|
|
||||||
ATTR_HOMEASSISTANT,
|
|
||||||
ATTR_HOSTNAME,
|
|
||||||
ATTR_LOGGING,
|
|
||||||
ATTR_MACHINE,
|
|
||||||
ATTR_OPERATING_SYSTEM,
|
|
||||||
ATTR_STATE,
|
|
||||||
ATTR_SUPERVISOR,
|
|
||||||
ATTR_SUPPORTED,
|
|
||||||
ATTR_SUPPORTED_ARCH,
|
|
||||||
ATTR_TIMEZONE,
|
|
||||||
)
|
|
||||||
from ..coresys import CoreSysAttributes
|
|
||||||
from .utils import api_process
|
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class APIInfo(CoreSysAttributes):
|
|
||||||
"""Handle RESTful API for info functions."""
|
|
||||||
|
|
||||||
@api_process
|
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
|
||||||
"""Show system info."""
|
|
||||||
return {
|
|
||||||
ATTR_SUPERVISOR: self.sys_supervisor.version,
|
|
||||||
ATTR_HOMEASSISTANT: self.sys_homeassistant.version,
|
|
||||||
ATTR_HASSOS: self.sys_hassos.version,
|
|
||||||
ATTR_DOCKER: self.sys_docker.info.version,
|
|
||||||
ATTR_HOSTNAME: self.sys_host.info.hostname,
|
|
||||||
ATTR_OPERATING_SYSTEM: self.sys_host.info.operating_system,
|
|
||||||
ATTR_FEATURES: self.sys_host.features,
|
|
||||||
ATTR_MACHINE: self.sys_machine,
|
|
||||||
ATTR_ARCH: self.sys_arch.default,
|
|
||||||
ATTR_STATE: self.sys_core.state,
|
|
||||||
ATTR_SUPPORTED_ARCH: self.sys_arch.supported,
|
|
||||||
ATTR_SUPPORTED: self.sys_core.supported,
|
|
||||||
ATTR_CHANNEL: self.sys_updater.channel,
|
|
||||||
ATTR_LOGGING: self.sys_config.logging,
|
|
||||||
ATTR_TIMEZONE: self.sys_timezone,
|
|
||||||
}
|
|
@@ -2,7 +2,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from ipaddress import ip_address
|
from ipaddress import ip_address
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, Union
|
from typing import Any, Union
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from aiohttp import ClientTimeout, hdrs, web
|
from aiohttp import ClientTimeout, hdrs, web
|
||||||
@@ -54,7 +54,7 @@ class APIIngress(CoreSysAttributes):
|
|||||||
return f"http://{addon.ip_address}:{addon.ingress_port}/{path}"
|
return f"http://{addon.ip_address}:{addon.ingress_port}/{path}"
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def panels(self, request: web.Request) -> Dict[str, Any]:
|
async def panels(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Create a list of panel data."""
|
"""Create a list of panel data."""
|
||||||
addons = {}
|
addons = {}
|
||||||
for addon in self.sys_ingress.addons:
|
for addon in self.sys_ingress.addons:
|
||||||
@@ -69,14 +69,14 @@ class APIIngress(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
@require_home_assistant
|
@require_home_assistant
|
||||||
async def create_session(self, request: web.Request) -> Dict[str, Any]:
|
async def create_session(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Create a new session."""
|
"""Create a new session."""
|
||||||
session = self.sys_ingress.create_session()
|
session = self.sys_ingress.create_session()
|
||||||
return {ATTR_SESSION: session}
|
return {ATTR_SESSION: session}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
@require_home_assistant
|
@require_home_assistant
|
||||||
async def validate_session(self, request: web.Request) -> Dict[str, Any]:
|
async def validate_session(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Validate session and extending how long it's valid for."""
|
"""Validate session and extending how long it's valid for."""
|
||||||
data = await api_validate(VALIDATE_SESSION_DATA, request)
|
data = await api_validate(VALIDATE_SESSION_DATA, request)
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ class APIIngress(CoreSysAttributes):
|
|||||||
|
|
||||||
def _init_header(
|
def _init_header(
|
||||||
request: web.Request, addon: str
|
request: web.Request, addon: str
|
||||||
) -> Union[CIMultiDict, Dict[str, str]]:
|
) -> Union[CIMultiDict, dict[str, str]]:
|
||||||
"""Create initial header."""
|
"""Create initial header."""
|
||||||
headers = {}
|
headers = {}
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ def _init_header(
|
|||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
|
||||||
def _response_header(response: aiohttp.ClientResponse) -> Dict[str, str]:
|
def _response_header(response: aiohttp.ClientResponse) -> dict[str, str]:
|
||||||
"""Create response header."""
|
"""Create response header."""
|
||||||
headers = {}
|
headers = {}
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
"""Init file for Supervisor Jobs RESTful API."""
|
"""Init file for Supervisor Jobs RESTful API."""
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -20,7 +20,7 @@ class APIJobs(CoreSysAttributes):
|
|||||||
"""Handle RESTful API for OS functions."""
|
"""Handle RESTful API for OS functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return JobManager information."""
|
"""Return JobManager information."""
|
||||||
return {
|
return {
|
||||||
ATTR_IGNORE_CONDITIONS: self.sys_jobs.ignore_conditions,
|
ATTR_IGNORE_CONDITIONS: self.sys_jobs.ignore_conditions,
|
||||||
|
@@ -173,7 +173,7 @@ class SecurityMiddleware(CoreSysAttributes):
|
|||||||
|
|
||||||
# Observer
|
# Observer
|
||||||
if supervisor_token == self.sys_plugins.observer.supervisor_token:
|
if supervisor_token == self.sys_plugins.observer.supervisor_token:
|
||||||
if not OBSERVER_CHECK.match(request.url):
|
if not OBSERVER_CHECK.match(request.path):
|
||||||
_LOGGER.warning("%s invalid Observer access", request.path)
|
_LOGGER.warning("%s invalid Observer access", request.path)
|
||||||
raise HTTPForbidden()
|
raise HTTPForbidden()
|
||||||
_LOGGER.debug("%s access from Observer", request.path)
|
_LOGGER.debug("%s access from Observer", request.path)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Init file for Supervisor Multicast RESTful API."""
|
"""Init file for Supervisor Multicast RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Awaitable, Dict
|
from typing import Any, Awaitable
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -34,7 +34,7 @@ class APIMulticast(CoreSysAttributes):
|
|||||||
"""Handle RESTful API for Multicast functions."""
|
"""Handle RESTful API for Multicast functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return Multicast information."""
|
"""Return Multicast information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_plugins.multicast.version,
|
ATTR_VERSION: self.sys_plugins.multicast.version,
|
||||||
@@ -43,7 +43,7 @@ class APIMulticast(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
async def stats(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_plugins.multicast.stats()
|
stats = await self.sys_plugins.multicast.stats()
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""REST API for network."""
|
"""REST API for network."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from ipaddress import ip_address, ip_interface
|
from ipaddress import ip_address, ip_interface
|
||||||
from typing import Any, Awaitable, Dict
|
from typing import Any, Awaitable
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import attr
|
import attr
|
||||||
@@ -82,7 +82,7 @@ SCHEMA_UPDATE = vol.Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def ipconfig_struct(config: IpConfig) -> Dict[str, Any]:
|
def ipconfig_struct(config: IpConfig) -> dict[str, Any]:
|
||||||
"""Return a dict with information about ip configuration."""
|
"""Return a dict with information about ip configuration."""
|
||||||
return {
|
return {
|
||||||
ATTR_METHOD: config.method,
|
ATTR_METHOD: config.method,
|
||||||
@@ -92,7 +92,7 @@ def ipconfig_struct(config: IpConfig) -> Dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def wifi_struct(config: WifiConfig) -> Dict[str, Any]:
|
def wifi_struct(config: WifiConfig) -> dict[str, Any]:
|
||||||
"""Return a dict with information about wifi configuration."""
|
"""Return a dict with information about wifi configuration."""
|
||||||
return {
|
return {
|
||||||
ATTR_MODE: config.mode,
|
ATTR_MODE: config.mode,
|
||||||
@@ -102,7 +102,7 @@ def wifi_struct(config: WifiConfig) -> Dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def vlan_struct(config: VlanConfig) -> Dict[str, Any]:
|
def vlan_struct(config: VlanConfig) -> dict[str, Any]:
|
||||||
"""Return a dict with information about VLAN configuration."""
|
"""Return a dict with information about VLAN configuration."""
|
||||||
return {
|
return {
|
||||||
ATTR_ID: config.id,
|
ATTR_ID: config.id,
|
||||||
@@ -110,7 +110,7 @@ def vlan_struct(config: VlanConfig) -> Dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def interface_struct(interface: Interface) -> Dict[str, Any]:
|
def interface_struct(interface: Interface) -> dict[str, Any]:
|
||||||
"""Return a dict with information of a interface to be used in th API."""
|
"""Return a dict with information of a interface to be used in th API."""
|
||||||
return {
|
return {
|
||||||
ATTR_INTERFACE: interface.name,
|
ATTR_INTERFACE: interface.name,
|
||||||
@@ -125,7 +125,7 @@ def interface_struct(interface: Interface) -> Dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def accesspoint_struct(accesspoint: AccessPoint) -> Dict[str, Any]:
|
def accesspoint_struct(accesspoint: AccessPoint) -> dict[str, Any]:
|
||||||
"""Return a dict for AccessPoint."""
|
"""Return a dict for AccessPoint."""
|
||||||
return {
|
return {
|
||||||
ATTR_MODE: accesspoint.mode,
|
ATTR_MODE: accesspoint.mode,
|
||||||
@@ -158,7 +158,7 @@ class APINetwork(CoreSysAttributes):
|
|||||||
raise APIError(f"Interface {name} does not exist") from None
|
raise APIError(f"Interface {name} does not exist") from None
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return network information."""
|
"""Return network information."""
|
||||||
return {
|
return {
|
||||||
ATTR_INTERFACES: [
|
ATTR_INTERFACES: [
|
||||||
@@ -176,7 +176,7 @@ class APINetwork(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def interface_info(self, request: web.Request) -> Dict[str, Any]:
|
async def interface_info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return network information for a interface."""
|
"""Return network information for a interface."""
|
||||||
interface = self._get_interface(request.match_info.get(ATTR_INTERFACE))
|
interface = self._get_interface(request.match_info.get(ATTR_INTERFACE))
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ class APINetwork(CoreSysAttributes):
|
|||||||
return asyncio.shield(self.sys_host.network.update())
|
return asyncio.shield(self.sys_host.network.update())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def scan_accesspoints(self, request: web.Request) -> Dict[str, Any]:
|
async def scan_accesspoints(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Scan and return a list of available networks."""
|
"""Scan and return a list of available networks."""
|
||||||
interface = self._get_interface(request.match_info.get(ATTR_INTERFACE))
|
interface = self._get_interface(request.match_info.get(ATTR_INTERFACE))
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Init file for Supervisor Observer RESTful API."""
|
"""Init file for Supervisor Observer RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -33,7 +33,7 @@ class APIObserver(CoreSysAttributes):
|
|||||||
"""Handle RESTful API for Observer functions."""
|
"""Handle RESTful API for Observer functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return HA Observer information."""
|
"""Return HA Observer information."""
|
||||||
return {
|
return {
|
||||||
ATTR_HOST: str(self.sys_docker.network.observer),
|
ATTR_HOST: str(self.sys_docker.network.observer),
|
||||||
@@ -43,7 +43,7 @@ class APIObserver(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
async def stats(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_plugins.observer.stats()
|
stats = await self.sys_plugins.observer.stats()
|
||||||
|
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
"""Init file for Supervisor HassOS RESTful API."""
|
"""Init file for Supervisor HassOS RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Awaitable, Dict
|
from pathlib import Path
|
||||||
|
from typing import Any, Awaitable
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -9,42 +10,60 @@ import voluptuous as vol
|
|||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_BOARD,
|
ATTR_BOARD,
|
||||||
ATTR_BOOT,
|
ATTR_BOOT,
|
||||||
|
ATTR_DEVICES,
|
||||||
ATTR_UPDATE_AVAILABLE,
|
ATTR_UPDATE_AVAILABLE,
|
||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
ATTR_VERSION_LATEST,
|
ATTR_VERSION_LATEST,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import version_tag
|
from ..validate import version_tag
|
||||||
|
from .const import ATTR_DATA_DISK, ATTR_DEVICE
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): version_tag})
|
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): version_tag})
|
||||||
|
SCHEMA_DISK = vol.Schema({vol.Required(ATTR_DEVICE): vol.All(str, vol.Coerce(Path))})
|
||||||
|
|
||||||
|
|
||||||
class APIOS(CoreSysAttributes):
|
class APIOS(CoreSysAttributes):
|
||||||
"""Handle RESTful API for OS functions."""
|
"""Handle RESTful API for OS functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return OS information."""
|
"""Return OS information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_hassos.version,
|
ATTR_VERSION: self.sys_os.version,
|
||||||
ATTR_VERSION_LATEST: self.sys_hassos.latest_version,
|
ATTR_VERSION_LATEST: self.sys_os.latest_version,
|
||||||
ATTR_UPDATE_AVAILABLE: self.sys_hassos.need_update,
|
ATTR_UPDATE_AVAILABLE: self.sys_os.need_update,
|
||||||
ATTR_BOARD: self.sys_hassos.board,
|
ATTR_BOARD: self.sys_os.board,
|
||||||
ATTR_BOOT: self.sys_dbus.rauc.boot_slot,
|
ATTR_BOOT: self.sys_dbus.rauc.boot_slot,
|
||||||
|
ATTR_DATA_DISK: self.sys_os.datadisk.disk_used,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def update(self, request: web.Request) -> None:
|
async def update(self, request: web.Request) -> None:
|
||||||
"""Update OS."""
|
"""Update OS."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_hassos.latest_version)
|
version = body.get(ATTR_VERSION, self.sys_os.latest_version)
|
||||||
|
|
||||||
await asyncio.shield(self.sys_hassos.update(version))
|
await asyncio.shield(self.sys_os.update(version))
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def config_sync(self, request: web.Request) -> Awaitable[None]:
|
def config_sync(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Trigger config reload on OS."""
|
"""Trigger config reload on OS."""
|
||||||
return asyncio.shield(self.sys_hassos.config_sync())
|
return asyncio.shield(self.sys_os.config_sync())
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def migrate_data(self, request: web.Request) -> None:
|
||||||
|
"""Trigger data disk migration on Host."""
|
||||||
|
body = await api_validate(SCHEMA_DISK, request)
|
||||||
|
|
||||||
|
await asyncio.shield(self.sys_os.datadisk.migrate_disk(body[ATTR_DEVICE]))
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def list_data(self, request: web.Request) -> dict[str, Any]:
|
||||||
|
"""Return possible data targets."""
|
||||||
|
return {
|
||||||
|
ATTR_DEVICES: self.sys_os.datadisk.available_disks,
|
||||||
|
}
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
|
|
||||||
function loadES5() {
|
function loadES5() {
|
||||||
var el = document.createElement('script');
|
var el = document.createElement('script');
|
||||||
el.src = '/api/hassio/app/frontend_es5/entrypoint.ef32933f.js';
|
el.src = '/api/hassio/app/frontend_es5/entrypoint.646ba882.js';
|
||||||
document.body.appendChild(el);
|
document.body.appendChild(el);
|
||||||
}
|
}
|
||||||
if (/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent)) {
|
if (/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent)) {
|
||||||
loadES5();
|
loadES5();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
new Function("import('/api/hassio/app/frontend_latest/entrypoint.7ca7c83a.js')")();
|
new Function("import('/api/hassio/app/frontend_latest/entrypoint.6ec72023.js')")();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
loadES5();
|
loadES5();
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([[534],{68441:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=new WeakMap;t.default=function(e){var t=l.get(e);return t||(t=Object.create(null),l.set(e,t)),t}},78643:function(e,t,l){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PluralRules=void 0;var a=l(87480),n=l(43965),r=a.__importDefault(l(68441));function o(e,t){if(!(e instanceof u))throw new TypeError("Method Intl.PluralRules.prototype."+t+" called on incompatible receiver "+String(e))}function i(e,t,l,a){var n=a.IntegerDigits,r=a.NumberOfFractionDigits,o=a.FractionDigits;return u.localeData[e].fn(r?n+"."+o:n,"ordinal"===t)}var u=function(){function e(t,l){if(!(this&&this instanceof e?this.constructor:void 0))throw new TypeError("Intl.PluralRules must be called with 'new'");return n.InitializePluralRules(this,t,l,{availableLocales:e.availableLocales,relevantExtensionKeys:e.relevantExtensionKeys,localeData:e.localeData,getDefaultLocale:e.getDefaultLocale,getInternalSlots:r.default})}return e.prototype.resolvedOptions=function(){o(this,"resolvedOptions");var t=Object.create(null),l=r.default(this);return t.locale=l.locale,t.type=l.type,["minimumIntegerDigits","minimumFractionDigits","maximumFractionDigits","minimumSignificantDigits","maximumSignificantDigits"].forEach((function(e){var a=l[e];void 0!==a&&(t[e]=a)})),t.pluralCategories=a.__spreadArray([],e.localeData[t.locale].categories[t.type]),t},e.prototype.select=function(e){o(this,"select");var t=n.ToNumber(e);return n.ResolvePlural(this,t,{getInternalSlots:r.default,PluralRuleSelect:i})},e.prototype.toString=function(){return"[object Intl.PluralRules]"},e.supportedLocalesOf=function(t,l){return n.SupportedLocales(e.availableLocales,n.CanonicalizeLocaleList(t),l)},e.__addLocaleData=function(){for(var t=[],l=0;l<arguments.length;l++)t[l]=arguments[l];for(var a=0,n=t;a<n.length;a++){var r=n[a],o=r.data,i=r.locale;e.localeData[i]=o,e.availableLocales.add(i),e.__defaultLocale||(e.__defaultLocale=i)}},e.getDefaultLocale=function(){return e.__defaultLocale},e.localeData={},e.availableLocales=new Set,e.__defaultLocale="",e.relevantExtensionKeys=[],e.polyfilled=!0,e}();t.PluralRules=u;try{"undefined"!=typeof Symbol&&Object.defineProperty(u.prototype,Symbol.toStringTag,{value:"Intl.PluralRules",writable:!1,enumerable:!1,configurable:!0});try{Object.defineProperty(u,"length",{value:0,writable:!1,enumerable:!1,configurable:!0})}catch(c){}Object.defineProperty(u.prototype.constructor,"length",{value:0,writable:!1,enumerable:!1,configurable:!0}),Object.defineProperty(u.supportedLocalesOf,"length",{value:1,writable:!1,enumerable:!1,configurable:!0})}catch(s){}},25534:function(e,t,l){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var a=l(78643);l(27815).shouldPolyfill()&&Object.defineProperty(Intl,"PluralRules",{value:a.PluralRules,writable:!0,enumerable:!1,configurable:!0})},27815:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.shouldPolyfill=void 0,t.shouldPolyfill=function(){return"undefined"==typeof Intl||!("PluralRules"in Intl)||"one"===new Intl.PluralRules("en",{minimumFractionDigits:2}).select(1)}}}]);
|
|
||||||
//# sourceMappingURL=01378878.js.map
|
|
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"01378878.js","sources":["webpack://home-assistant-frontend/01378878.js"],"mappings":"AAAA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"05b049f0.js","sources":["webpack://home-assistant-frontend/05b049f0.js"],"mappings":"AAAA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"0bbd5196.js","sources":["webpack://home-assistant-frontend/0bbd5196.js"],"mappings":";AAAA","sourceRoot":""}
|
|
1
supervisor/api/panel/frontend_es5/0c8a1a3d.js
Normal file
1
supervisor/api/panel/frontend_es5/0c8a1a3d.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/0c8a1a3d.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/0c8a1a3d.js.gz
Normal file
Binary file not shown.
2
supervisor/api/panel/frontend_es5/0ef95294.js
Normal file
2
supervisor/api/panel/frontend_es5/0ef95294.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/0ef95294.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/0ef95294.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/1199eafb.js
Normal file
1
supervisor/api/panel/frontend_es5/1199eafb.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1199eafb.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1199eafb.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"14a4eab7.js","sources":["webpack://home-assistant-frontend/14a4eab7.js"],"mappings":"AAAA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"2995ba79.js","sources":["webpack://home-assistant-frontend/2995ba79.js"],"mappings":"AAAA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"2cb85263.js","sources":["webpack://home-assistant-frontend/2cb85263.js"],"mappings":"AAAA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"2d8922b0.js","sources":["webpack://home-assistant-frontend/2d8922b0.js"],"mappings":"AAAA","sourceRoot":""}
|
|
1
supervisor/api/panel/frontend_es5/2dbdaab4.js
Normal file
1
supervisor/api/panel/frontend_es5/2dbdaab4.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/2dbdaab4.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/2dbdaab4.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/3119daa2.js
Normal file
1
supervisor/api/panel/frontend_es5/3119daa2.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/3119daa2.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/3119daa2.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/340c7a77.js
Normal file
1
supervisor/api/panel/frontend_es5/340c7a77.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/340c7a77.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/340c7a77.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"3f8f8f2b.js","sources":["webpack://home-assistant-frontend/3f8f8f2b.js"],"mappings":"AAAA","sourceRoot":""}
|
|
2
supervisor/api/panel/frontend_es5/42f67c6f.js
Normal file
2
supervisor/api/panel/frontend_es5/42f67c6f.js
Normal file
File diff suppressed because one or more lines are too long
73
supervisor/api/panel/frontend_es5/42f67c6f.js.LICENSE.txt
Normal file
73
supervisor/api/panel/frontend_es5/42f67c6f.js.LICENSE.txt
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright (c) 2015 - 2022 Vaadin Ltd.
|
||||||
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright (c) 2017 - 2022 Vaadin Ltd.
|
||||||
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
* Code distributed by Google as part of the polymer project is also
|
||||||
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
||||||
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright (c) 2021 Vaadin Ltd.
|
||||||
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2017 Google LLC
|
||||||
|
* SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2018 Google LLC
|
||||||
|
* SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.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 (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
BIN
supervisor/api/panel/frontend_es5/42f67c6f.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/42f67c6f.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/4a274bef.js
Normal file
1
supervisor/api/panel/frontend_es5/4a274bef.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/4a274bef.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/4a274bef.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
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