mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-08-27 18:09:20 +00:00
Compare commits
203 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c51496ad2f | ||
![]() |
fbe409337b | ||
![]() |
443a43cc5b | ||
![]() |
0e25fad1c0 | ||
![]() |
1ebbf2b693 | ||
![]() |
62d198111c | ||
![]() |
1fc0ab71aa | ||
![]() |
f4402a1633 | ||
![]() |
13a17bcb34 | ||
![]() |
e1b49d90c2 | ||
![]() |
85ab25ea16 | ||
![]() |
80131ddfa8 | ||
![]() |
e9c123459f | ||
![]() |
d3e4bb7219 | ||
![]() |
fd98d38125 | ||
![]() |
3237611034 | ||
![]() |
ce2bffda15 | ||
![]() |
977e7b7adc | ||
![]() |
5082078527 | ||
![]() |
3615091c93 | ||
![]() |
fb1eb44d82 | ||
![]() |
13910d44bf | ||
![]() |
cda1d15070 | ||
![]() |
d0a1de23a6 | ||
![]() |
44fd75220f | ||
![]() |
ed594d653f | ||
![]() |
40bb3a7581 | ||
![]() |
df7f0345e8 | ||
![]() |
f7ab76bb9a | ||
![]() |
45e24bfa65 | ||
![]() |
8cd149783c | ||
![]() |
8e8e6e48a9 | ||
![]() |
816e0d503a | ||
![]() |
c43acd50f4 | ||
![]() |
16ce4296a2 | ||
![]() |
65386b753f | ||
![]() |
2be1529cb8 | ||
![]() |
98f8e032e3 | ||
![]() |
900b785789 | ||
![]() |
9194088947 | ||
![]() |
58c40cbef6 | ||
![]() |
e6c57dfc80 | ||
![]() |
82f76f60bd | ||
![]() |
b9af4aec6b | ||
![]() |
f71ce27248 | ||
![]() |
5b2b1765bc | ||
![]() |
2a892544c2 | ||
![]() |
bedb37ca6b | ||
![]() |
a456cd645f | ||
![]() |
9c68094cf6 | ||
![]() |
379cef9e35 | ||
![]() |
cb3e2dab71 | ||
![]() |
3e89f83e0b | ||
![]() |
af0bdd890a | ||
![]() |
f93f5d0e71 | ||
![]() |
667672a20b | ||
![]() |
9e1f899274 | ||
![]() |
75e0741665 | ||
![]() |
392d0e929b | ||
![]() |
b342073ba9 | ||
![]() |
ff4e550ba3 | ||
![]() |
17aa544be5 | ||
![]() |
390676dbc4 | ||
![]() |
d423252bc7 | ||
![]() |
790e887b70 | ||
![]() |
47e377683e | ||
![]() |
b1232c0d8d | ||
![]() |
059233c111 | ||
![]() |
55382d000b | ||
![]() |
75ab6eec43 | ||
![]() |
e30171746b | ||
![]() |
73849b7468 | ||
![]() |
a52713611c | ||
![]() |
85a66c663c | ||
![]() |
e478e68b70 | ||
![]() |
16095c319a | ||
![]() |
f4a6100fba | ||
![]() |
82060dd242 | ||
![]() |
a58cfb797c | ||
![]() |
c8256a50f4 | ||
![]() |
3ae974e9e2 | ||
![]() |
ac5e74a375 | ||
![]() |
05e3d3b779 | ||
![]() |
681a1ecff5 | ||
![]() |
2b411b0bf9 | ||
![]() |
fee16847d3 | ||
![]() |
501a52a3c6 | ||
![]() |
2bb014fda5 | ||
![]() |
09203f67b2 | ||
![]() |
169c7ec004 | ||
![]() |
202e94615e | ||
![]() |
5fe2a815ad | ||
![]() |
a13a0b4770 | ||
![]() |
455bbc457b | ||
![]() |
d50fd3b580 | ||
![]() |
455e80b07c | ||
![]() |
291becbdf9 | ||
![]() |
33385b46a7 | ||
![]() |
df17668369 | ||
![]() |
43449c85bb | ||
![]() |
9e86eda05a | ||
![]() |
b288554d9c | ||
![]() |
bee55d08fb | ||
![]() |
7a542aeb38 | ||
![]() |
8d42513ba8 | ||
![]() |
89b7247aa2 | ||
![]() |
29132e7f4c | ||
![]() |
3fd9baf78e | ||
![]() |
f3aa3757ce | ||
![]() |
3760967f59 | ||
![]() |
f7ab8e0f7f | ||
![]() |
0e46ea12b2 | ||
![]() |
be226b2b01 | ||
![]() |
9e1239e192 | ||
![]() |
2eba3d85b0 | ||
![]() |
9b569268ab | ||
![]() |
31f5033dca | ||
![]() |
78d9c60be5 | ||
![]() |
baa86f09e5 | ||
![]() |
a4c4b39ba8 | ||
![]() |
752068bb56 | ||
![]() |
739cfbb273 | ||
![]() |
115af4cadf | ||
![]() |
ae3274e559 | ||
![]() |
c61f096dbd | ||
![]() |
ee7b5c42fd | ||
![]() |
85d527bfbc | ||
![]() |
dd561da819 | ||
![]() |
cb5932cb8b | ||
![]() |
8630adc54a | ||
![]() |
90d8832cd2 | ||
![]() |
3802b97bb6 | ||
![]() |
2de175e181 | ||
![]() |
6b7d437b00 | ||
![]() |
e2faf906de | ||
![]() |
bb44ce5cd2 | ||
![]() |
15544ae589 | ||
![]() |
e421284471 | ||
![]() |
785dc64787 | ||
![]() |
7e7e3a7876 | ||
![]() |
2b45c059e0 | ||
![]() |
14ec61f9bd | ||
![]() |
5cc72756f8 | ||
![]() |
44785ef3e2 | ||
![]() |
e60d858feb | ||
![]() |
b31ecfefcd | ||
![]() |
c342231052 | ||
![]() |
673666837e | ||
![]() |
c8f74d6c0d | ||
![]() |
7ed9de8014 | ||
![]() |
8650947f04 | ||
![]() |
a0ac8ced31 | ||
![]() |
2145bbea81 | ||
![]() |
480000ee7f | ||
![]() |
9ec2ad022e | ||
![]() |
43e40816dc | ||
![]() |
941ea3ee68 | ||
![]() |
a6e4b5159e | ||
![]() |
6f542d58d5 | ||
![]() |
b2b5fcee7d | ||
![]() |
59a82345a9 | ||
![]() |
b61a747876 | ||
![]() |
72e5d800d5 | ||
![]() |
c7aa6d4804 | ||
![]() |
b31063449d | ||
![]() |
477672459d | ||
![]() |
9c33897296 | ||
![]() |
100cfb57c5 | ||
![]() |
40b34071e7 | ||
![]() |
341833fd8f | ||
![]() |
f647fd6fea | ||
![]() |
53642f2389 | ||
![]() |
b9bdd655ab | ||
![]() |
e9e1b5b54f | ||
![]() |
be2163d635 | ||
![]() |
7f6dde3a5f | ||
![]() |
334aafee23 | ||
![]() |
1a20c18b19 | ||
![]() |
6e655b165c | ||
![]() |
d768b2fa1e | ||
![]() |
85bce1cfba | ||
![]() |
a798a2466f | ||
![]() |
2a5d8a5c82 | ||
![]() |
ea62171d98 | ||
![]() |
196389d5ee | ||
![]() |
1776021620 | ||
![]() |
c42a9124d3 | ||
![]() |
a44647b4cd | ||
![]() |
e0c3fd87c5 | ||
![]() |
ed8f2a85b7 | ||
![]() |
48f8553c75 | ||
![]() |
af4517fd1e | ||
![]() |
78e6a46318 | ||
![]() |
49ca923e51 | ||
![]() |
7ad22e0399 | ||
![]() |
bb8acc6065 | ||
![]() |
c0fa4a19e9 | ||
![]() |
3f1741dd18 | ||
![]() |
9ef02e4110 | ||
![]() |
488a2327fb | ||
![]() |
b99ed631c5 | ||
![]() |
726dd3a8f9 | ||
![]() |
b94810d044 |
@@ -1,5 +1,9 @@
|
|||||||
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.8
|
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.8
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
|
||||||
WORKDIR /workspaces
|
WORKDIR /workspaces
|
||||||
|
|
||||||
# Set Docker daemon config
|
# Set Docker daemon config
|
||||||
@@ -8,12 +12,13 @@ RUN \
|
|||||||
&& echo '{"storage-driver": "vfs"}' > /etc/docker/daemon.json
|
&& echo '{"storage-driver": "vfs"}' > /etc/docker/daemon.json
|
||||||
|
|
||||||
# Install Node/Yarn for Frontent
|
# Install Node/Yarn for Frontent
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
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 \
|
curl \
|
||||||
git \
|
git \
|
||||||
apt-utils \
|
apt-utils \
|
||||||
apt-transport-https \
|
apt-transport-https \
|
||||||
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
|
|
||||||
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
|
&& 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 \
|
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||||
nodejs \
|
nodejs \
|
||||||
@@ -44,6 +49,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
dbus \
|
dbus \
|
||||||
network-manager \
|
network-manager \
|
||||||
libpulse0 \
|
libpulse0 \
|
||||||
|
&& bash <(curl https://getvcn.codenotary.com -L) \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install Python dependencies from requirements.txt if it exists
|
# Install Python dependencies from requirements.txt if it exists
|
||||||
|
@@ -3,62 +3,78 @@ about: Report an issue related to the Home Assistant Supervisor.
|
|||||||
labels: bug
|
labels: bug
|
||||||
title: ""
|
title: ""
|
||||||
issue_body: true
|
issue_body: true
|
||||||
inputs:
|
body:
|
||||||
- type: description
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
This issue form is for reporting bugs with **supported** setups only!
|
This issue form is for reporting bugs with **supported** setups only!
|
||||||
|
|
||||||
If you have a feature or enhancement request, please use the [feature request][fr] section of our [Community Forum][fr].
|
If you have a feature or enhancement request, please use the [feature request][fr] section of our [Community Forum][fr].
|
||||||
|
|
||||||
[fr]: https://community.home-assistant.io/c/feature-requests
|
[fr]: https://community.home-assistant.io/c/feature-requests
|
||||||
- type: input
|
- type: textarea
|
||||||
attributes:
|
validations:
|
||||||
label: What is the version of the Supervisor used?
|
|
||||||
required: true
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Describe the issue you are experiencing
|
||||||
|
description: Provide a clear and concise description of what the bug is.
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Environment
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: What is the used version of the Supervisor?
|
||||||
placeholder: supervisor-
|
placeholder: supervisor-
|
||||||
description: >
|
description: >
|
||||||
Can be found in the Supervisor panel -> System tab. Starts with
|
Can be found in the Supervisor panel -> System tab. Starts with
|
||||||
`supervisor-....`.
|
`supervisor-....`.
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
attributes:
|
attributes:
|
||||||
label: What type of installation are you running?
|
label: What type of installation are you running?
|
||||||
required: true
|
|
||||||
description: >
|
description: >
|
||||||
If you don't know, you can find it in: Configuration panel -> Info.
|
If you don't know, you can find it in: Configuration panel -> Info.
|
||||||
choices:
|
options:
|
||||||
- Home Assistant OS
|
- Home Assistant OS
|
||||||
- Home Assistant Supervised
|
- Home Assistant Supervised
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
attributes:
|
attributes:
|
||||||
label: Which operating system are you running on?
|
label: Which operating system are you running on?
|
||||||
required: true
|
options:
|
||||||
choices:
|
|
||||||
- Home Assistant Operating System
|
- Home Assistant Operating System
|
||||||
- Debian
|
- Debian
|
||||||
- Other (e.g., Raspbian/Raspberry Pi OS/Fedora)
|
- Other (e.g., Raspbian/Raspberry Pi OS/Fedora)
|
||||||
- type: input
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
attributes:
|
attributes:
|
||||||
label: What is the version of your installed operating system?
|
label: What is the version of your installed operating system?
|
||||||
required: true
|
placeholder: "5.11"
|
||||||
placeholder: 5.10
|
|
||||||
description: Can be found in the Supervisor panel -> System tab.
|
description: Can be found in the Supervisor panel -> System tab.
|
||||||
- type: input
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
attributes:
|
attributes:
|
||||||
label: What version of Home Assistant Core is installed?
|
label: What version of Home Assistant Core is installed?
|
||||||
required: true
|
|
||||||
placeholder: core-
|
placeholder: core-
|
||||||
description: >
|
description: >
|
||||||
Can be found in the Supervisor panel -> System tab. Starts with
|
Can be found in the Supervisor panel -> System tab. Starts with
|
||||||
`core-....`.
|
`core-....`.
|
||||||
- type: textarea
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
label: Describe the issue you are experiencing
|
value: |
|
||||||
required: true
|
# Details
|
||||||
description: Provide a clear and concise description of what the bug is.
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
attributes:
|
attributes:
|
||||||
label: Steps to reproduce the issue
|
label: Steps to reproduce the issue
|
||||||
required: true
|
|
||||||
description: |
|
description: |
|
||||||
Please tell us exactly how to reproduce your issue.
|
Please tell us exactly how to reproduce your issue.
|
||||||
Provide clear and concise step by step instructions and add code snippets if needed.
|
Provide clear and concise step by step instructions and add code snippets if needed.
|
||||||
@@ -68,12 +84,22 @@ inputs:
|
|||||||
3.
|
3.
|
||||||
...
|
...
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
attributes:
|
attributes:
|
||||||
label: Anything in the Supervisor logs that might be useful for us?
|
label: Anything in the Supervisor logs that might be useful for us?
|
||||||
required: false
|
|
||||||
description: >
|
description: >
|
||||||
The Supervisor logs can be found in the Supervisor panel -> System tab.
|
The Supervisor logs can be found in the Supervisor panel -> System tab.
|
||||||
- type: description
|
value: |
|
||||||
|
```txt
|
||||||
|
# Put your logs below this line
|
||||||
|
|
||||||
|
```
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Additional information
|
||||||
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
If you have any additional information for us, use the field below.
|
If you have any additional information for us, use the field below.
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -37,6 +37,7 @@
|
|||||||
- This PR fixes or closes issue: fixes #
|
- This PR fixes or closes issue: fixes #
|
||||||
- This PR is related to issue:
|
- This PR is related to issue:
|
||||||
- Link to documentation pull request:
|
- Link to documentation pull request:
|
||||||
|
- Link to cli pull request:
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
|
47
.github/workflows/builder.yml
vendored
47
.github/workflows/builder.yml
vendored
@@ -35,7 +35,7 @@ on:
|
|||||||
env:
|
env:
|
||||||
BUILD_NAME: supervisor
|
BUILD_NAME: supervisor
|
||||||
BUILD_TYPE: supervisor
|
BUILD_TYPE: supervisor
|
||||||
WHEELS_TAG: 3.8-alpine3.12
|
WHEELS_TAG: 3.8-alpine3.13
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
init:
|
init:
|
||||||
@@ -97,7 +97,7 @@ jobs:
|
|||||||
wheels-host: ${{ secrets.WHEELS_HOST }}
|
wheels-host: ${{ secrets.WHEELS_HOST }}
|
||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
wheels-user: wheels
|
wheels-user: wheels
|
||||||
apk: "build-base;libffi-dev;openssl-dev"
|
apk: "build-base;libffi-dev;openssl-dev;cargo"
|
||||||
skip-binary: aiohttp
|
skip-binary: aiohttp
|
||||||
requirements: "requirements.txt"
|
requirements: "requirements.txt"
|
||||||
|
|
||||||
@@ -114,19 +114,56 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
if: needs.init.outputs.publish == 'true'
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ secrets.GIT_USER }}
|
||||||
|
password: ${{ secrets.GIT_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.01.1
|
uses: home-assistant/builder@2021.04.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 }}" \
|
||||||
|
--validate-cache "${{ secrets.VCN_ORG }}" \
|
||||||
--generic ${{ needs.init.outputs.version }}
|
--generic ${{ needs.init.outputs.version }}
|
||||||
|
|
||||||
|
codenotary:
|
||||||
|
name: CodeNotary signature
|
||||||
|
needs: init
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout the repository
|
||||||
|
if: needs.init.outputs.publish == 'true'
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set version
|
||||||
|
if: needs.init.outputs.publish == 'true'
|
||||||
|
uses: home-assistant/actions/helpers/version@master
|
||||||
|
with:
|
||||||
|
type: ${{ env.BUILD_TYPE }}
|
||||||
|
|
||||||
|
- name: Signing image
|
||||||
|
if: needs.init.outputs.publish == 'true'
|
||||||
|
uses: home-assistant/actions/helpers/codenotary@master
|
||||||
|
with:
|
||||||
|
source: dir://${{ github.workspace }}
|
||||||
|
user: ${{ secrets.VCN_USER }}
|
||||||
|
password: ${{ secrets.VCN_PASSWORD }}
|
||||||
|
organisation: ${{ secrets.VCN_ORG }}
|
||||||
|
|
||||||
version:
|
version:
|
||||||
name: Update version
|
name: Update version
|
||||||
needs: ["init", "run_supervisor"]
|
needs: ["init", "run_supervisor"]
|
||||||
@@ -155,13 +192,13 @@ jobs:
|
|||||||
run_supervisor:
|
run_supervisor:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Run the Supervisor
|
name: Run the Supervisor
|
||||||
needs: ["build"]
|
needs: ["build", "codenotary"]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Build the Supervisor
|
- name: Build the Supervisor
|
||||||
uses: home-assistant/builder@2021.01.1
|
uses: home-assistant/builder@2021.04.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
--test \
|
--test \
|
||||||
|
58
.github/workflows/ci.yaml
vendored
58
.github/workflows/ci.yaml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v2.2.1
|
uses: actions/setup-python@v2.2.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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -47,7 +47,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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_HOME }}
|
path: ${{ env.PRE_COMMIT_HOME }}
|
||||||
key: |
|
key: |
|
||||||
@@ -68,13 +68,13 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.1
|
uses: actions/setup-python@v2.2.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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -112,13 +112,13 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.1
|
uses: actions/setup-python@v2.2.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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -130,7 +130,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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_HOME }}
|
path: ${{ env.PRE_COMMIT_HOME }}
|
||||||
key: |
|
key: |
|
||||||
@@ -156,13 +156,13 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.1
|
uses: actions/setup-python@v2.2.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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -188,13 +188,13 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.1
|
uses: actions/setup-python@v2.2.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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -206,7 +206,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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_HOME }}
|
path: ${{ env.PRE_COMMIT_HOME }}
|
||||||
key: |
|
key: |
|
||||||
@@ -229,13 +229,13 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.1
|
uses: actions/setup-python@v2.2.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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -247,7 +247,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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_HOME }}
|
path: ${{ env.PRE_COMMIT_HOME }}
|
||||||
key: |
|
key: |
|
||||||
@@ -273,13 +273,13 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.1
|
uses: actions/setup-python@v2.2.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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -305,13 +305,13 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.1
|
uses: actions/setup-python@v2.2.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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -323,7 +323,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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_HOME }}
|
path: ${{ env.PRE_COMMIT_HOME }}
|
||||||
key: |
|
key: |
|
||||||
@@ -349,13 +349,17 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2.2.1
|
uses: actions/setup-python@v2.2.2
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Install CodeNotary
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
bash <(curl https://getvcn.codenotary.com -L)
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -390,7 +394,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.2
|
uses: actions/upload-artifact@v2.2.3
|
||||||
with:
|
with:
|
||||||
name: coverage-${{ matrix.python-version }}
|
name: coverage-${{ matrix.python-version }}
|
||||||
path: .coverage
|
path: .coverage
|
||||||
@@ -403,13 +407,13 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v2.2.1
|
uses: actions/setup-python@v2.2.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
|
uses: actions/cache@v2.1.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: |
|
key: |
|
||||||
@@ -428,4 +432,4 @@ jobs:
|
|||||||
coverage report
|
coverage report
|
||||||
coverage xml
|
coverage xml
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v1.2.1
|
uses: codecov/codecov-action@v1.3.2
|
||||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v3.0.15
|
- uses: actions/stale@v3.0.18
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
ignored:
|
ignored:
|
||||||
- DL3018
|
- DL3003
|
||||||
- DL3006
|
- DL3006
|
||||||
- DL3013
|
- DL3013
|
||||||
|
- DL3018
|
||||||
- SC2155
|
- SC2155
|
||||||
|
21
.vcnignore
Normal file
21
.vcnignore
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# General files
|
||||||
|
.git
|
||||||
|
.github
|
||||||
|
.devcontainer
|
||||||
|
.vscode
|
||||||
|
.tox
|
||||||
|
|
||||||
|
# Data
|
||||||
|
home-assistant-polymer/
|
||||||
|
script/
|
||||||
|
tests/
|
||||||
|
data/
|
||||||
|
venv/
|
44
Dockerfile
44
Dockerfile
@@ -1,13 +1,18 @@
|
|||||||
ARG BUILD_FROM
|
ARG BUILD_FROM
|
||||||
FROM $BUILD_FROM
|
FROM ${BUILD_FROM}
|
||||||
|
|
||||||
ENV \
|
ENV \
|
||||||
S6_SERVICES_GRACETIME=10000 \
|
S6_SERVICES_GRACETIME=10000 \
|
||||||
SUPERVISOR_API=http://localhost
|
SUPERVISOR_API=http://localhost
|
||||||
|
|
||||||
|
ARG BUILD_ARCH
|
||||||
|
ARG VCN_VERSION
|
||||||
|
WORKDIR /usr/src
|
||||||
|
|
||||||
# Install base
|
# Install base
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache \
|
set -x \
|
||||||
|
&& apk add --no-cache \
|
||||||
eudev \
|
eudev \
|
||||||
eudev-libs \
|
eudev-libs \
|
||||||
git \
|
git \
|
||||||
@@ -15,10 +20,37 @@ RUN \
|
|||||||
libffi \
|
libffi \
|
||||||
libpulse \
|
libpulse \
|
||||||
musl \
|
musl \
|
||||||
openssl
|
openssl \
|
||||||
|
&& apk add --no-cache --virtual .build-dependencies \
|
||||||
ARG BUILD_ARCH
|
build-base \
|
||||||
WORKDIR /usr/src
|
go \
|
||||||
|
\
|
||||||
|
&& git clone -b v${VCN_VERSION} --depth 1 \
|
||||||
|
https://github.com/codenotary/vcn \
|
||||||
|
&& cd vcn \
|
||||||
|
\
|
||||||
|
# Fix: https://github.com/codenotary/vcn/issues/131
|
||||||
|
&& go get github.com/codenotary/immudb@4cf9e2ae06ac2e6ec98a60364c3de3eab5524757 \
|
||||||
|
\
|
||||||
|
&& if [ "${BUILD_ARCH}" = "armhf" ]; then \
|
||||||
|
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 \
|
||||||
|
&& rm -rf /usr/src/vcn
|
||||||
|
|
||||||
# Install requirements
|
# Install requirements
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
17
build.json
17
build.json
@@ -1,13 +1,18 @@
|
|||||||
{
|
{
|
||||||
"image": "homeassistant/{arch}-hassio-supervisor",
|
"image": "homeassistant/{arch}-hassio-supervisor",
|
||||||
|
"shadow_repository": "ghcr.io/home-assistant",
|
||||||
"build_from": {
|
"build_from": {
|
||||||
"aarch64": "homeassistant/aarch64-base-python:3.8-alpine3.12",
|
"aarch64": "ghcr.io/home-assistant/aarch64-base-python:3.8-alpine3.13",
|
||||||
"armhf": "homeassistant/armhf-base-python:3.8-alpine3.12",
|
"armhf": "ghcr.io/home-assistant/armhf-base-python:3.8-alpine3.13",
|
||||||
"armv7": "homeassistant/armv7-base-python:3.8-alpine3.12",
|
"armv7": "ghcr.io/home-assistant/armv7-base-python:3.8-alpine3.13",
|
||||||
"amd64": "homeassistant/amd64-base-python:3.8-alpine3.12",
|
"amd64": "ghcr.io/home-assistant/amd64-base-python:3.8-alpine3.13",
|
||||||
"i386": "homeassistant/i386-base-python:3.8-alpine3.12"
|
"i386": "ghcr.io/home-assistant/i386-base-python:3.8-alpine3.13"
|
||||||
|
},
|
||||||
|
"args": {
|
||||||
|
"VCN_VERSION": "0.9.4"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"io.hass.type": "supervisor"
|
"io.hass.type": "supervisor",
|
||||||
|
"org.opencontainers.image.source": "https://github.com/home-assistant/supervisor"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Submodule home-assistant-polymer updated: a9192ae2e1...8dd3d78f21
@@ -1,20 +1,20 @@
|
|||||||
aiohttp==3.7.3
|
aiohttp==3.7.4.post0
|
||||||
async_timeout==3.0.1
|
async_timeout==3.0.1
|
||||||
atomicwrites==1.4.0
|
atomicwrites==1.4.0
|
||||||
attrs==20.3.0
|
attrs==20.3.0
|
||||||
awesomeversion==21.2.0
|
awesomeversion==21.4.0
|
||||||
brotli==1.0.9
|
brotli==1.0.9
|
||||||
cchardet==2.1.7
|
cchardet==2.1.7
|
||||||
colorlog==4.7.2
|
colorlog==4.8.0
|
||||||
cpe==1.2.1
|
cpe==1.2.1
|
||||||
cryptography==3.3.1
|
cryptography==3.4.6
|
||||||
debugpy==1.2.1
|
debugpy==1.2.1
|
||||||
docker==4.4.1
|
docker==5.0.0
|
||||||
gitpython==3.1.12
|
gitpython==3.1.14
|
||||||
jinja2==2.11.3
|
jinja2==2.11.3
|
||||||
pulsectl==20.5.1
|
pulsectl==21.3.4
|
||||||
pytz==2021.1
|
pytz==2021.1
|
||||||
pyudev==0.22.0
|
pyudev==0.22.0
|
||||||
ruamel.yaml==0.15.100
|
ruamel.yaml==0.15.100
|
||||||
sentry-sdk==0.19.5
|
sentry-sdk==1.0.0
|
||||||
voluptuous==0.12.1
|
voluptuous==0.12.1
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
black==20.8b1
|
black==20.8b1
|
||||||
codecov==2.1.11
|
codecov==2.1.11
|
||||||
coverage==5.4
|
coverage==5.5
|
||||||
flake8-docstrings==1.5.0
|
flake8-docstrings==1.6.0
|
||||||
flake8==3.8.4
|
flake8==3.9.0
|
||||||
pre-commit==2.10.0
|
pre-commit==2.12.0
|
||||||
pydocstyle==5.1.1
|
pydocstyle==6.0.0
|
||||||
pylint==2.6.0
|
pylint==2.7.4
|
||||||
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.11.1
|
pytest-cov==2.11.1
|
||||||
pytest-timeout==1.4.2
|
pytest-timeout==1.4.2
|
||||||
pytest==6.2.2
|
pytest==6.2.3
|
||||||
pyupgrade==2.9.0
|
pyupgrade==2.12.0
|
||||||
|
@@ -3,15 +3,16 @@
|
|||||||
# Start udev service
|
# Start udev service
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
if bashio::fs.directory_exists /run/udev; then
|
if bashio::fs.directory_exists /run/udev && ! bashio::fs.file_exists /run/.old_udev; then
|
||||||
bashio::log.info "Using udev information from host"
|
bashio::log.info "Using udev information from host"
|
||||||
bashio::exit.ok
|
bashio::exit.ok
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
bashio::log.info "Setup udev backend inside container"
|
bashio::log.info "Setup udev backend inside container"
|
||||||
udevd --daemon
|
udevd --daemon
|
||||||
|
|
||||||
|
bashio::log.info "Update udev information"
|
||||||
|
touch /run/.old_udev
|
||||||
if udevadm trigger; then
|
if udevadm trigger; then
|
||||||
udevadm settle || true
|
udevadm settle || true
|
||||||
else
|
else
|
||||||
|
2
setup.py
2
setup.py
@@ -44,7 +44,9 @@ setup(
|
|||||||
"supervisor.jobs",
|
"supervisor.jobs",
|
||||||
"supervisor.misc",
|
"supervisor.misc",
|
||||||
"supervisor.plugins",
|
"supervisor.plugins",
|
||||||
|
"supervisor.resolution.checks",
|
||||||
"supervisor.resolution.evaluations",
|
"supervisor.resolution.evaluations",
|
||||||
|
"supervisor.resolution.fixups",
|
||||||
"supervisor.resolution",
|
"supervisor.resolution",
|
||||||
"supervisor.services.modules",
|
"supervisor.services.modules",
|
||||||
"supervisor.services",
|
"supervisor.services",
|
||||||
|
@@ -154,17 +154,16 @@ class AddonManager(CoreSysAttributes):
|
|||||||
async def install(self, slug: str) -> None:
|
async def install(self, slug: str) -> None:
|
||||||
"""Install an add-on."""
|
"""Install an add-on."""
|
||||||
if slug in self.local:
|
if slug in self.local:
|
||||||
_LOGGER.warning("Add-on %s is already installed", slug)
|
raise AddonsError(f"Add-on {slug} is already installed", _LOGGER.warning)
|
||||||
return
|
|
||||||
store = self.store.get(slug)
|
store = self.store.get(slug)
|
||||||
|
|
||||||
if not store:
|
if not store:
|
||||||
_LOGGER.error("Add-on %s not exists", slug)
|
raise AddonsError(f"Add-on {slug} not exists", _LOGGER.error)
|
||||||
raise AddonsError()
|
|
||||||
|
|
||||||
if not store.available:
|
if not store.available:
|
||||||
_LOGGER.error("Add-on %s not supported on that platform", slug)
|
raise AddonsNotSupportedError(
|
||||||
raise AddonsNotSupportedError()
|
f"Add-on {slug} not supported on that platform", _LOGGER.error
|
||||||
|
)
|
||||||
|
|
||||||
self.data.install(store)
|
self.data.install(store)
|
||||||
addon = Addon(self.coresys, slug)
|
addon = Addon(self.coresys, slug)
|
||||||
@@ -256,37 +255,38 @@ class AddonManager(CoreSysAttributes):
|
|||||||
async def update(self, slug: str) -> None:
|
async def update(self, slug: str) -> None:
|
||||||
"""Update add-on."""
|
"""Update 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]
|
||||||
|
|
||||||
if addon.version == store.version:
|
if addon.version == store.version:
|
||||||
_LOGGER.warning("No update available for add-on %s", slug)
|
raise AddonsError(f"No update available for add-on {slug}", _LOGGER.warning)
|
||||||
return
|
|
||||||
|
|
||||||
# Check if available, Maybe something have changed
|
# Check if available, Maybe something have changed
|
||||||
if not store.available:
|
if not store.available:
|
||||||
_LOGGER.error("Add-on %s not supported on that platform", slug)
|
raise AddonsNotSupportedError(
|
||||||
raise AddonsNotSupportedError()
|
f"Add-on {slug} not supported on that platform", _LOGGER.error
|
||||||
|
)
|
||||||
|
|
||||||
# Update instance
|
# Update instance
|
||||||
last_state: AddonState = addon.state
|
last_state: AddonState = addon.state
|
||||||
|
old_image = addon.image
|
||||||
try:
|
try:
|
||||||
await addon.instance.update(store.version, store.image)
|
await addon.instance.update(store.version, store.image)
|
||||||
|
except DockerError as err:
|
||||||
|
raise AddonsError() from err
|
||||||
|
|
||||||
|
_LOGGER.info("Add-on '%s' successfully updated", slug)
|
||||||
|
self.data.update(store)
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
with suppress(DockerError):
|
with suppress(DockerError):
|
||||||
await addon.instance.cleanup()
|
await addon.instance.cleanup(old_image=old_image)
|
||||||
except DockerError as err:
|
|
||||||
raise AddonsError() from err
|
|
||||||
else:
|
|
||||||
self.data.update(store)
|
|
||||||
_LOGGER.info("Add-on '%s' successfully updated", slug)
|
|
||||||
|
|
||||||
# Setup/Fix AppArmor profile
|
# Setup/Fix AppArmor profile
|
||||||
await addon.install_apparmor()
|
await addon.install_apparmor()
|
||||||
|
@@ -22,6 +22,8 @@ from ..const import (
|
|||||||
ATTR_AUDIO_OUTPUT,
|
ATTR_AUDIO_OUTPUT,
|
||||||
ATTR_AUTO_UPDATE,
|
ATTR_AUTO_UPDATE,
|
||||||
ATTR_BOOT,
|
ATTR_BOOT,
|
||||||
|
ATTR_DATA,
|
||||||
|
ATTR_EVENT,
|
||||||
ATTR_IMAGE,
|
ATTR_IMAGE,
|
||||||
ATTR_INGRESS_ENTRY,
|
ATTR_INGRESS_ENTRY,
|
||||||
ATTR_INGRESS_PANEL,
|
ATTR_INGRESS_PANEL,
|
||||||
@@ -32,8 +34,10 @@ from ..const import (
|
|||||||
ATTR_PORTS,
|
ATTR_PORTS,
|
||||||
ATTR_PROTECTED,
|
ATTR_PROTECTED,
|
||||||
ATTR_SCHEMA,
|
ATTR_SCHEMA,
|
||||||
|
ATTR_SLUG,
|
||||||
ATTR_STATE,
|
ATTR_STATE,
|
||||||
ATTR_SYSTEM,
|
ATTR_SYSTEM,
|
||||||
|
ATTR_TYPE,
|
||||||
ATTR_USER,
|
ATTR_USER,
|
||||||
ATTR_UUID,
|
ATTR_UUID,
|
||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
@@ -50,12 +54,13 @@ from ..exceptions import (
|
|||||||
AddonConfigurationError,
|
AddonConfigurationError,
|
||||||
AddonsError,
|
AddonsError,
|
||||||
AddonsNotSupportedError,
|
AddonsNotSupportedError,
|
||||||
|
ConfigurationFileError,
|
||||||
DockerError,
|
DockerError,
|
||||||
DockerRequestError,
|
DockerRequestError,
|
||||||
HostAppArmorError,
|
HostAppArmorError,
|
||||||
JsonFileError,
|
|
||||||
)
|
)
|
||||||
from ..hardware.data import Device
|
from ..hardware.data import Device
|
||||||
|
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
|
||||||
@@ -74,7 +79,7 @@ RE_WEBUI = re.compile(
|
|||||||
|
|
||||||
RE_WATCHDOG = re.compile(
|
RE_WATCHDOG = re.compile(
|
||||||
r"^(?:(?P<s_prefix>https?|tcp)|\[PROTO:(?P<t_proto>\w+)\])"
|
r"^(?:(?P<s_prefix>https?|tcp)|\[PROTO:(?P<t_proto>\w+)\])"
|
||||||
r":\/\/\[HOST\]:\[PORT:(?P<t_port>\d+)\](?P<s_suffix>.*)$"
|
r":\/\/\[HOST\]:(?:\[PORT:)?(?P<t_port>\d+)\]?(?P<s_suffix>.*)$"
|
||||||
)
|
)
|
||||||
|
|
||||||
RE_OLD_AUDIO = re.compile(r"\d+,\d+")
|
RE_OLD_AUDIO = re.compile(r"\d+,\d+")
|
||||||
@@ -89,12 +94,34 @@ class Addon(AddonModel):
|
|||||||
"""Initialize data holder."""
|
"""Initialize data holder."""
|
||||||
super().__init__(coresys, slug)
|
super().__init__(coresys, slug)
|
||||||
self.instance: DockerAddon = DockerAddon(coresys, self)
|
self.instance: DockerAddon = DockerAddon(coresys, self)
|
||||||
self.state: AddonState = AddonState.UNKNOWN
|
self._state: AddonState = AddonState.UNKNOWN
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
"""Return internal representation."""
|
"""Return internal representation."""
|
||||||
return f"<Addon: {self.slug}>"
|
return f"<Addon: {self.slug}>"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> AddonState:
|
||||||
|
"""Return state of the add-on."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@state.setter
|
||||||
|
def state(self, new_state: AddonState) -> None:
|
||||||
|
"""Set the add-on into new state."""
|
||||||
|
if self._state == new_state:
|
||||||
|
return
|
||||||
|
self._state = new_state
|
||||||
|
self.sys_homeassistant.websocket.send_command(
|
||||||
|
{
|
||||||
|
ATTR_TYPE: WSType.SUPERVISOR_EVENT,
|
||||||
|
ATTR_DATA: {
|
||||||
|
ATTR_EVENT: WSEvent.ADDON,
|
||||||
|
ATTR_SLUG: self.slug,
|
||||||
|
ATTR_STATE: new_state,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def in_progress(self) -> bool:
|
def in_progress(self) -> bool:
|
||||||
"""Return True if a task is in progress."""
|
"""Return True if a task is in progress."""
|
||||||
@@ -398,18 +425,32 @@ class Addon(AddonModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def devices(self) -> Set[Device]:
|
def devices(self) -> Set[Device]:
|
||||||
"""Create a schema for add-on options."""
|
"""Extract devices from add-on options."""
|
||||||
raw_schema = self.data[ATTR_SCHEMA]
|
raw_schema = self.data[ATTR_SCHEMA]
|
||||||
if isinstance(raw_schema, bool) or not raw_schema:
|
if isinstance(raw_schema, bool) or not raw_schema:
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
# Validate devices
|
# Validate devices
|
||||||
options_validator = AddonOptions(self.coresys, raw_schema)
|
options_validator = AddonOptions(self.coresys, raw_schema, self.name, self.slug)
|
||||||
with suppress(vol.Invalid):
|
with suppress(vol.Invalid):
|
||||||
options_validator(self.options)
|
options_validator(self.options)
|
||||||
|
|
||||||
return options_validator.devices
|
return options_validator.devices
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pwned(self) -> Set[str]:
|
||||||
|
"""Extract pwned data for add-on options."""
|
||||||
|
raw_schema = self.data[ATTR_SCHEMA]
|
||||||
|
if isinstance(raw_schema, bool) or not raw_schema:
|
||||||
|
return set()
|
||||||
|
|
||||||
|
# Validate devices
|
||||||
|
options_validator = AddonOptions(self.coresys, raw_schema, self.name, self.slug)
|
||||||
|
with suppress(vol.Invalid):
|
||||||
|
options_validator(self.options)
|
||||||
|
|
||||||
|
return options_validator.pwned
|
||||||
|
|
||||||
def save_persist(self) -> None:
|
def save_persist(self) -> None:
|
||||||
"""Save data of add-on."""
|
"""Save data of add-on."""
|
||||||
self.sys_addons.data.save_data()
|
self.sys_addons.data.save_data()
|
||||||
@@ -470,7 +511,7 @@ class Addon(AddonModel):
|
|||||||
self.slug,
|
self.slug,
|
||||||
humanize_error(self.options, ex),
|
humanize_error(self.options, ex),
|
||||||
)
|
)
|
||||||
except JsonFileError:
|
except ConfigurationFileError:
|
||||||
_LOGGER.error("Add-on %s can't write options", self.slug)
|
_LOGGER.error("Add-on %s can't write options", self.slug)
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug("Add-on %s write options: %s", self.slug, options)
|
_LOGGER.debug("Add-on %s write options: %s", self.slug, options)
|
||||||
@@ -551,7 +592,9 @@ class Addon(AddonModel):
|
|||||||
|
|
||||||
# create voluptuous
|
# create voluptuous
|
||||||
new_schema = vol.Schema(
|
new_schema = vol.Schema(
|
||||||
vol.All(dict, AddonOptions(self.coresys, new_raw_schema))
|
vol.All(
|
||||||
|
dict, AddonOptions(self.coresys, new_raw_schema, self.name, self.slug)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# validate
|
# validate
|
||||||
@@ -583,7 +626,7 @@ class Addon(AddonModel):
|
|||||||
try:
|
try:
|
||||||
await self.instance.run()
|
await self.instance.run()
|
||||||
except DockerRequestError as err:
|
except DockerRequestError as err:
|
||||||
self.state = AddonState.STOPPED
|
self.state = AddonState.ERROR
|
||||||
raise AddonsError() from err
|
raise AddonsError() from err
|
||||||
except DockerError as err:
|
except DockerError as err:
|
||||||
self.state = AddonState.ERROR
|
self.state = AddonState.ERROR
|
||||||
@@ -594,8 +637,9 @@ class Addon(AddonModel):
|
|||||||
async def stop(self) -> None:
|
async def stop(self) -> None:
|
||||||
"""Stop add-on."""
|
"""Stop add-on."""
|
||||||
try:
|
try:
|
||||||
return await self.instance.stop()
|
await self.instance.stop()
|
||||||
except DockerRequestError as err:
|
except DockerRequestError as err:
|
||||||
|
self.state = AddonState.ERROR
|
||||||
raise AddonsError() from err
|
raise AddonsError() from err
|
||||||
except DockerError as err:
|
except DockerError as err:
|
||||||
self.state = AddonState.ERROR
|
self.state = AddonState.ERROR
|
||||||
@@ -666,7 +710,7 @@ class Addon(AddonModel):
|
|||||||
# Store local configs/state
|
# Store local configs/state
|
||||||
try:
|
try:
|
||||||
write_json_file(temp_path.joinpath("addon.json"), data)
|
write_json_file(temp_path.joinpath("addon.json"), data)
|
||||||
except JsonFileError as err:
|
except ConfigurationFileError as err:
|
||||||
_LOGGER.error("Can't save meta for %s", self.slug)
|
_LOGGER.error("Can't save meta for %s", self.slug)
|
||||||
raise AddonsError() from err
|
raise AddonsError() from err
|
||||||
|
|
||||||
@@ -722,7 +766,7 @@ class Addon(AddonModel):
|
|||||||
# Read snapshot data
|
# Read snapshot data
|
||||||
try:
|
try:
|
||||||
data = read_json_file(Path(temp, "addon.json"))
|
data = read_json_file(Path(temp, "addon.json"))
|
||||||
except JsonFileError as err:
|
except ConfigurationFileError as err:
|
||||||
raise AddonsError() from err
|
raise AddonsError() from err
|
||||||
|
|
||||||
# Validate
|
# Validate
|
||||||
|
@@ -6,16 +6,23 @@ from typing import TYPE_CHECKING, Dict
|
|||||||
|
|
||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
|
|
||||||
from ..const import ATTR_ARGS, ATTR_BUILD_FROM, ATTR_SQUASH, META_ADDON
|
from ..const import (
|
||||||
|
ATTR_ARGS,
|
||||||
|
ATTR_BUILD_FROM,
|
||||||
|
ATTR_SQUASH,
|
||||||
|
FILE_SUFFIX_CONFIGURATION,
|
||||||
|
META_ADDON,
|
||||||
|
)
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..utils.json import JsonConfig
|
from ..exceptions import ConfigurationFileError
|
||||||
|
from ..utils.common import FileConfiguration, find_one_filetype
|
||||||
from .validate import SCHEMA_BUILD_CONFIG
|
from .validate import SCHEMA_BUILD_CONFIG
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import AnyAddon
|
from . import AnyAddon
|
||||||
|
|
||||||
|
|
||||||
class AddonBuild(JsonConfig, CoreSysAttributes):
|
class AddonBuild(FileConfiguration, CoreSysAttributes):
|
||||||
"""Handle build options for add-ons."""
|
"""Handle build options for add-ons."""
|
||||||
|
|
||||||
def __init__(self, coresys: CoreSys, addon: AnyAddon) -> None:
|
def __init__(self, coresys: CoreSys, addon: AnyAddon) -> None:
|
||||||
@@ -23,9 +30,14 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
|
|||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.addon = addon
|
self.addon = addon
|
||||||
|
|
||||||
super().__init__(
|
try:
|
||||||
Path(self.addon.path_location, "build.json"), SCHEMA_BUILD_CONFIG
|
build_file = find_one_filetype(
|
||||||
|
self.addon.path_location, "build", FILE_SUFFIX_CONFIGURATION
|
||||||
)
|
)
|
||||||
|
except ConfigurationFileError:
|
||||||
|
build_file = self.addon.path_location / "build.json"
|
||||||
|
|
||||||
|
super().__init__(build_file, SCHEMA_BUILD_CONFIG)
|
||||||
|
|
||||||
def save_data(self):
|
def save_data(self):
|
||||||
"""Ignore save function."""
|
"""Ignore save function."""
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
"""Init file for Supervisor add-on data."""
|
"""Init file for Supervisor add-on data."""
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import logging
|
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
@@ -13,16 +12,14 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..store.addon import AddonStore
|
from ..store.addon import AddonStore
|
||||||
from ..utils.json import JsonConfig
|
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
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
Config = Dict[str, Any]
|
Config = Dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
class AddonsData(JsonConfig, CoreSysAttributes):
|
class AddonsData(FileConfiguration, CoreSysAttributes):
|
||||||
"""Hold data for installed Add-ons inside Supervisor."""
|
"""Hold data for installed Add-ons inside Supervisor."""
|
||||||
|
|
||||||
def __init__(self, coresys: CoreSys):
|
def __init__(self, coresys: CoreSys):
|
||||||
|
@@ -32,6 +32,7 @@ from ..const import (
|
|||||||
ATTR_IMAGE,
|
ATTR_IMAGE,
|
||||||
ATTR_INGRESS,
|
ATTR_INGRESS,
|
||||||
ATTR_INIT,
|
ATTR_INIT,
|
||||||
|
ATTR_JOURNALD,
|
||||||
ATTR_KERNEL_MODULES,
|
ATTR_KERNEL_MODULES,
|
||||||
ATTR_LEGACY,
|
ATTR_LEGACY,
|
||||||
ATTR_LOCATON,
|
ATTR_LOCATON,
|
||||||
@@ -45,6 +46,7 @@ from ..const import (
|
|||||||
ATTR_PORTS,
|
ATTR_PORTS,
|
||||||
ATTR_PORTS_DESCRIPTION,
|
ATTR_PORTS_DESCRIPTION,
|
||||||
ATTR_PRIVILEGED,
|
ATTR_PRIVILEGED,
|
||||||
|
ATTR_REALTIME,
|
||||||
ATTR_REPOSITORY,
|
ATTR_REPOSITORY,
|
||||||
ATTR_SCHEMA,
|
ATTR_SCHEMA,
|
||||||
ATTR_SERVICES,
|
ATTR_SERVICES,
|
||||||
@@ -55,6 +57,7 @@ from ..const import (
|
|||||||
ATTR_STDIN,
|
ATTR_STDIN,
|
||||||
ATTR_TIMEOUT,
|
ATTR_TIMEOUT,
|
||||||
ATTR_TMPFS,
|
ATTR_TMPFS,
|
||||||
|
ATTR_TRANSLATIONS,
|
||||||
ATTR_UART,
|
ATTR_UART,
|
||||||
ATTR_UDEV,
|
ATTR_UDEV,
|
||||||
ATTR_URL,
|
ATTR_URL,
|
||||||
@@ -71,6 +74,7 @@ from ..const import (
|
|||||||
AddonStartup,
|
AddonStartup,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
from ..docker.const import Capabilities
|
||||||
from .options import AddonOptions, UiOptions
|
from .options import AddonOptions, UiOptions
|
||||||
from .validate import RE_SERVICE, RE_VOLUME
|
from .validate import RE_SERVICE, RE_VOLUME
|
||||||
|
|
||||||
@@ -183,6 +187,11 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
"""Return repository of add-on."""
|
"""Return repository of add-on."""
|
||||||
return self.data[ATTR_REPOSITORY]
|
return self.data[ATTR_REPOSITORY]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def translations(self) -> dict:
|
||||||
|
"""Return add-on translations."""
|
||||||
|
return self.data[ATTR_TRANSLATIONS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def latest_version(self) -> AwesomeVersion:
|
def latest_version(self) -> AwesomeVersion:
|
||||||
"""Return latest version of add-on."""
|
"""Return latest version of add-on."""
|
||||||
@@ -307,7 +316,7 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return self.data.get(ATTR_ENVIRONMENT)
|
return self.data.get(ATTR_ENVIRONMENT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def privileged(self) -> List[str]:
|
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, [])
|
||||||
|
|
||||||
@@ -395,6 +404,11 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
"""Return True if the add-on access to kernel modules."""
|
"""Return True if the add-on access to kernel modules."""
|
||||||
return self.data[ATTR_KERNEL_MODULES]
|
return self.data[ATTR_KERNEL_MODULES]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_realtime(self) -> bool:
|
||||||
|
"""Return True if the add-on need realtime schedule functions."""
|
||||||
|
return self.data[ATTR_REALTIME]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def with_full_access(self) -> bool:
|
def with_full_access(self) -> bool:
|
||||||
"""Return True if the add-on want full access to hardware."""
|
"""Return True if the add-on want full access to hardware."""
|
||||||
@@ -524,7 +538,9 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
|
|
||||||
if isinstance(raw_schema, bool):
|
if isinstance(raw_schema, bool):
|
||||||
raw_schema = {}
|
raw_schema = {}
|
||||||
return vol.Schema(vol.All(dict, AddonOptions(self.coresys, raw_schema)))
|
return vol.Schema(
|
||||||
|
vol.All(dict, AddonOptions(self.coresys, raw_schema, self.name, self.slug))
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def schema_ui(self) -> Optional[List[Dict[str, Any]]]:
|
def schema_ui(self) -> Optional[List[Dict[str, Any]]]:
|
||||||
@@ -535,6 +551,11 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return None
|
return None
|
||||||
return UiOptions(self.coresys)(raw_schema)
|
return UiOptions(self.coresys)(raw_schema)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_journald(self) -> bool:
|
||||||
|
"""Return True if the add-on accesses the system journal."""
|
||||||
|
return self.data[ATTR_JOURNALD]
|
||||||
|
|
||||||
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):
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
"""Add-on Options / UI rendering."""
|
"""Add-on Options / UI rendering."""
|
||||||
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
@@ -58,14 +59,15 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
"""Validate Add-ons Options."""
|
"""Validate Add-ons Options."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self, coresys: CoreSys, raw_schema: Dict[str, Any], name: str, slug: str
|
||||||
coresys: CoreSys,
|
|
||||||
raw_schema: Dict[str, Any],
|
|
||||||
):
|
):
|
||||||
"""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._name = name
|
||||||
|
self._slug = slug
|
||||||
|
|
||||||
def __call__(self, struct):
|
def __call__(self, struct):
|
||||||
"""Create schema validator for add-ons options."""
|
"""Create schema validator for add-ons options."""
|
||||||
@@ -75,7 +77,12 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
for key, value in struct.items():
|
for key, value in struct.items():
|
||||||
# Ignore unknown options / remove from list
|
# Ignore unknown options / remove from list
|
||||||
if key not in self.raw_schema:
|
if key not in self.raw_schema:
|
||||||
_LOGGER.warning("Unknown options %s", key)
|
_LOGGER.warning(
|
||||||
|
"Option '%s' does not exist in the schema for %s (%s)",
|
||||||
|
key,
|
||||||
|
self._name,
|
||||||
|
self._slug,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
typ = self.raw_schema[key]
|
typ = self.raw_schema[key]
|
||||||
@@ -90,7 +97,9 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
# normal value
|
# normal value
|
||||||
options[key] = self._single_validate(typ, value, key)
|
options[key] = self._single_validate(typ, value, key)
|
||||||
except (IndexError, KeyError):
|
except (IndexError, KeyError):
|
||||||
raise vol.Invalid(f"Type error for {key}") from None
|
raise vol.Invalid(
|
||||||
|
f"Type error for option '{key}' in {self._name} ({self._slug})"
|
||||||
|
) from None
|
||||||
|
|
||||||
self._check_missing_options(self.raw_schema, options, "root")
|
self._check_missing_options(self.raw_schema, options, "root")
|
||||||
return options
|
return options
|
||||||
@@ -100,20 +109,26 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
"""Validate a single element."""
|
"""Validate a single element."""
|
||||||
# if required argument
|
# if required argument
|
||||||
if value is None:
|
if value is None:
|
||||||
raise vol.Invalid(f"Missing required option '{key}'") from None
|
raise vol.Invalid(
|
||||||
|
f"Missing required option '{key}' in {self._name} ({self._slug})"
|
||||||
|
) from None
|
||||||
|
|
||||||
# Lookup secret
|
# Lookup secret
|
||||||
if str(value).startswith("!secret "):
|
if str(value).startswith("!secret "):
|
||||||
secret: str = value.partition(" ")[2]
|
secret: str = value.partition(" ")[2]
|
||||||
value = self.sys_homeassistant.secrets.get(secret)
|
value = self.sys_homeassistant.secrets.get(secret)
|
||||||
if value is None:
|
if value is None:
|
||||||
raise vol.Invalid(f"Unknown secret {secret}") from None
|
raise vol.Invalid(
|
||||||
|
f"Unknown secret '{secret}' in {self._name} ({self._slug})"
|
||||||
|
) from None
|
||||||
|
|
||||||
# parse extend data from type
|
# parse extend data from type
|
||||||
match = RE_SCHEMA_ELEMENT.match(typ)
|
match = RE_SCHEMA_ELEMENT.match(typ)
|
||||||
|
|
||||||
if not match:
|
if not match:
|
||||||
raise vol.Invalid(f"Unknown type {typ}") from None
|
raise vol.Invalid(
|
||||||
|
f"Unknown type '{typ}' in {self._name} ({self._slug})"
|
||||||
|
) from None
|
||||||
|
|
||||||
# prepare range
|
# prepare range
|
||||||
range_args = {}
|
range_args = {}
|
||||||
@@ -123,6 +138,8 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
range_args[group_name[2:]] = float(group_value)
|
range_args[group_name[2:]] = float(group_value)
|
||||||
|
|
||||||
if typ.startswith(_STR) or typ.startswith(_PASSWORD):
|
if typ.startswith(_STR) or typ.startswith(_PASSWORD):
|
||||||
|
if typ.startswith(_PASSWORD) and value:
|
||||||
|
self.pwned.add(hashlib.sha1(str(value).encode()).hexdigest())
|
||||||
return vol.All(str(value), vol.Range(**range_args))(value)
|
return vol.All(str(value), vol.Range(**range_args))(value)
|
||||||
elif typ.startswith(_INT):
|
elif typ.startswith(_INT):
|
||||||
return vol.All(vol.Coerce(int), vol.Range(**range_args))(value)
|
return vol.All(vol.Coerce(int), vol.Range(**range_args))(value)
|
||||||
@@ -144,7 +161,9 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
try:
|
try:
|
||||||
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(f"Device {value} does not exists!") from None
|
raise vol.Invalid(
|
||||||
|
f"Device '{value}' does not exists! in {self._name} ({self._slug})"
|
||||||
|
) from None
|
||||||
|
|
||||||
# Have filter
|
# Have filter
|
||||||
if match.group("filter"):
|
if match.group("filter"):
|
||||||
@@ -152,14 +171,16 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
device_filter = _create_device_filter(str_filter)
|
device_filter = _create_device_filter(str_filter)
|
||||||
if device not in self.sys_hardware.filter_devices(**device_filter):
|
if device not in self.sys_hardware.filter_devices(**device_filter):
|
||||||
raise vol.Invalid(
|
raise vol.Invalid(
|
||||||
f"Device {value} don't match the filter {str_filter}!"
|
f"Device '{value}' don't match the filter {str_filter}! in {self._name} ({self._slug})"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Device valid
|
# Device valid
|
||||||
self.devices.add(device)
|
self.devices.add(device)
|
||||||
return str(device.path)
|
return str(device.path)
|
||||||
|
|
||||||
raise vol.Invalid(f"Fatal error for {key} type {typ}") from None
|
raise vol.Invalid(
|
||||||
|
f"Fatal error for option '{key}' with type '{typ}' in {self._name} ({self._slug})"
|
||||||
|
) 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."""
|
||||||
@@ -167,7 +188,9 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
|
|
||||||
# Make sure it is a list
|
# Make sure it is a list
|
||||||
if not isinstance(data_list, list):
|
if not isinstance(data_list, list):
|
||||||
raise vol.Invalid(f"Invalid list for {key}") from None
|
raise vol.Invalid(
|
||||||
|
f"Invalid list for option '{key}' in {self._name} ({self._slug})"
|
||||||
|
) from None
|
||||||
|
|
||||||
# Process list
|
# Process list
|
||||||
for element in data_list:
|
for element in data_list:
|
||||||
@@ -188,13 +211,17 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
|
|
||||||
# Make sure it is a dict
|
# Make sure it is a dict
|
||||||
if not isinstance(data_dict, dict):
|
if not isinstance(data_dict, dict):
|
||||||
raise vol.Invalid(f"Invalid dict for {key}") from None
|
raise vol.Invalid(
|
||||||
|
f"Invalid dict for option '{key}' in {self._name} ({self._slug})"
|
||||||
|
) from None
|
||||||
|
|
||||||
# Process dict
|
# Process dict
|
||||||
for c_key, c_value in data_dict.items():
|
for c_key, c_value in data_dict.items():
|
||||||
# Ignore unknown options / remove from list
|
# Ignore unknown options / remove from list
|
||||||
if c_key not in typ:
|
if c_key not in typ:
|
||||||
_LOGGER.warning("Unknown options %s", c_key)
|
_LOGGER.warning(
|
||||||
|
"Unknown option '%s' for %s (%s)", c_key, self._name, self._slug
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Nested?
|
# Nested?
|
||||||
@@ -214,9 +241,18 @@ class AddonOptions(CoreSysAttributes):
|
|||||||
"""Check if all options are exists."""
|
"""Check if all options are exists."""
|
||||||
missing = set(origin) - set(exists)
|
missing = set(origin) - set(exists)
|
||||||
for miss_opt in missing:
|
for miss_opt in missing:
|
||||||
if isinstance(origin[miss_opt], str) and origin[miss_opt].endswith("?"):
|
miss_schema = origin[miss_opt]
|
||||||
|
|
||||||
|
# If its a list then value in list decides if its optional like ["str?"]
|
||||||
|
if isinstance(miss_schema, list) and len(miss_schema) > 0:
|
||||||
|
miss_schema = miss_schema[0]
|
||||||
|
|
||||||
|
if isinstance(miss_schema, str) and miss_schema.endswith("?"):
|
||||||
continue
|
continue
|
||||||
raise vol.Invalid(f"Missing option {miss_opt} in {root}") from None
|
|
||||||
|
raise vol.Invalid(
|
||||||
|
f"Missing option '{miss_opt}' in {root} in {self._name} ({self._slug})"
|
||||||
|
) from None
|
||||||
|
|
||||||
|
|
||||||
class UiOptions(CoreSysAttributes):
|
class UiOptions(CoreSysAttributes):
|
||||||
@@ -317,7 +353,7 @@ class UiOptions(CoreSysAttributes):
|
|||||||
else:
|
else:
|
||||||
ui_node["options"] = [
|
ui_node["options"] = [
|
||||||
(device.by_id or device.path).as_posix()
|
(device.by_id or device.path).as_posix()
|
||||||
for device in self.sys_hardware.devices()
|
for device in self.sys_hardware.devices
|
||||||
]
|
]
|
||||||
|
|
||||||
ui_schema.append(ui_node)
|
ui_schema.append(ui_node)
|
||||||
|
@@ -6,18 +6,8 @@ import logging
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ..const import (
|
from ..const import ROLE_ADMIN, ROLE_MANAGER, SECURITY_DISABLE, SECURITY_PROFILE
|
||||||
PRIVILEGED_DAC_READ_SEARCH,
|
from ..docker.const import Capabilities
|
||||||
PRIVILEGED_NET_ADMIN,
|
|
||||||
PRIVILEGED_SYS_ADMIN,
|
|
||||||
PRIVILEGED_SYS_MODULE,
|
|
||||||
PRIVILEGED_SYS_PTRACE,
|
|
||||||
PRIVILEGED_SYS_RAWIO,
|
|
||||||
ROLE_ADMIN,
|
|
||||||
ROLE_MANAGER,
|
|
||||||
SECURITY_DISABLE,
|
|
||||||
SECURITY_PROFILE,
|
|
||||||
)
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .model import AddonModel
|
from .model import AddonModel
|
||||||
@@ -46,16 +36,19 @@ def rating_security(addon: AddonModel) -> int:
|
|||||||
rating += 1
|
rating += 1
|
||||||
|
|
||||||
# Privileged options
|
# Privileged options
|
||||||
if any(
|
if (
|
||||||
|
any(
|
||||||
privilege in addon.privileged
|
privilege in addon.privileged
|
||||||
for privilege in (
|
for privilege in (
|
||||||
PRIVILEGED_NET_ADMIN,
|
Capabilities.NET_ADMIN,
|
||||||
PRIVILEGED_SYS_ADMIN,
|
Capabilities.SYS_ADMIN,
|
||||||
PRIVILEGED_SYS_RAWIO,
|
Capabilities.SYS_RAWIO,
|
||||||
PRIVILEGED_SYS_PTRACE,
|
Capabilities.SYS_PTRACE,
|
||||||
PRIVILEGED_SYS_MODULE,
|
Capabilities.SYS_MODULE,
|
||||||
PRIVILEGED_DAC_READ_SEARCH,
|
Capabilities.DAC_READ_SEARCH,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
or addon.with_kernel_modules
|
||||||
):
|
):
|
||||||
rating += -1
|
rating += -1
|
||||||
|
|
||||||
@@ -73,12 +66,8 @@ def rating_security(addon: AddonModel) -> int:
|
|||||||
if addon.host_pid:
|
if addon.host_pid:
|
||||||
rating += -2
|
rating += -2
|
||||||
|
|
||||||
# Full Access
|
# Docker Access & full Access
|
||||||
if addon.with_full_access:
|
if addon.access_docker_api or addon.with_full_access:
|
||||||
rating += -2
|
|
||||||
|
|
||||||
# Docker Access
|
|
||||||
if addon.access_docker_api:
|
|
||||||
rating = 1
|
rating = 1
|
||||||
|
|
||||||
return max(min(6, rating), 1)
|
return max(min(6, rating), 1)
|
||||||
|
@@ -21,6 +21,7 @@ from ..const import (
|
|||||||
ATTR_AUTO_UPDATE,
|
ATTR_AUTO_UPDATE,
|
||||||
ATTR_BOOT,
|
ATTR_BOOT,
|
||||||
ATTR_BUILD_FROM,
|
ATTR_BUILD_FROM,
|
||||||
|
ATTR_CONFIGURATION,
|
||||||
ATTR_DESCRIPTON,
|
ATTR_DESCRIPTON,
|
||||||
ATTR_DEVICES,
|
ATTR_DEVICES,
|
||||||
ATTR_DEVICETREE,
|
ATTR_DEVICETREE,
|
||||||
@@ -44,6 +45,7 @@ from ..const import (
|
|||||||
ATTR_INGRESS_PORT,
|
ATTR_INGRESS_PORT,
|
||||||
ATTR_INGRESS_TOKEN,
|
ATTR_INGRESS_TOKEN,
|
||||||
ATTR_INIT,
|
ATTR_INIT,
|
||||||
|
ATTR_JOURNALD,
|
||||||
ATTR_KERNEL_MODULES,
|
ATTR_KERNEL_MODULES,
|
||||||
ATTR_LEGACY,
|
ATTR_LEGACY,
|
||||||
ATTR_LOCATON,
|
ATTR_LOCATON,
|
||||||
@@ -59,6 +61,7 @@ from ..const import (
|
|||||||
ATTR_PORTS_DESCRIPTION,
|
ATTR_PORTS_DESCRIPTION,
|
||||||
ATTR_PRIVILEGED,
|
ATTR_PRIVILEGED,
|
||||||
ATTR_PROTECTED,
|
ATTR_PROTECTED,
|
||||||
|
ATTR_REALTIME,
|
||||||
ATTR_REPOSITORY,
|
ATTR_REPOSITORY,
|
||||||
ATTR_SCHEMA,
|
ATTR_SCHEMA,
|
||||||
ATTR_SERVICES,
|
ATTR_SERVICES,
|
||||||
@@ -72,6 +75,7 @@ from ..const import (
|
|||||||
ATTR_SYSTEM,
|
ATTR_SYSTEM,
|
||||||
ATTR_TIMEOUT,
|
ATTR_TIMEOUT,
|
||||||
ATTR_TMPFS,
|
ATTR_TMPFS,
|
||||||
|
ATTR_TRANSLATIONS,
|
||||||
ATTR_UART,
|
ATTR_UART,
|
||||||
ATTR_UDEV,
|
ATTR_UDEV,
|
||||||
ATTR_URL,
|
ATTR_URL,
|
||||||
@@ -82,7 +86,6 @@ from ..const import (
|
|||||||
ATTR_VIDEO,
|
ATTR_VIDEO,
|
||||||
ATTR_WATCHDOG,
|
ATTR_WATCHDOG,
|
||||||
ATTR_WEBUI,
|
ATTR_WEBUI,
|
||||||
PRIVILEGED_ALL,
|
|
||||||
ROLE_ALL,
|
ROLE_ALL,
|
||||||
ROLE_DEFAULT,
|
ROLE_DEFAULT,
|
||||||
AddonBoot,
|
AddonBoot,
|
||||||
@@ -91,6 +94,7 @@ from ..const import (
|
|||||||
AddonState,
|
AddonState,
|
||||||
)
|
)
|
||||||
from ..discovery.validate import valid_discovery_service
|
from ..discovery.validate import valid_discovery_service
|
||||||
|
from ..docker.const import Capabilities
|
||||||
from ..validate import (
|
from ..validate import (
|
||||||
docker_image,
|
docker_image,
|
||||||
docker_ports,
|
docker_ports,
|
||||||
@@ -136,6 +140,26 @@ RE_MACHINE = re.compile(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _warn_addon_config(config: Dict[str, Any]):
|
||||||
|
"""Warn about miss configs."""
|
||||||
|
name = config.get(ATTR_NAME)
|
||||||
|
if not name:
|
||||||
|
raise vol.Invalid("Invalid Add-on config!")
|
||||||
|
|
||||||
|
if config.get(ATTR_FULL_ACCESS, False) and (
|
||||||
|
config.get(ATTR_DEVICES)
|
||||||
|
or config.get(ATTR_UART)
|
||||||
|
or config.get(ATTR_USB)
|
||||||
|
or config.get(ATTR_GPIO)
|
||||||
|
):
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Add-on have full device access, and selective device access in the configuration. Please report this to the maintainer of %s",
|
||||||
|
name,
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
def _migrate_addon_config(protocol=False):
|
def _migrate_addon_config(protocol=False):
|
||||||
"""Migrate addon config."""
|
"""Migrate addon config."""
|
||||||
|
|
||||||
@@ -149,7 +173,7 @@ def _migrate_addon_config(protocol=False):
|
|||||||
value = config[ATTR_STARTUP]
|
value = config[ATTR_STARTUP]
|
||||||
if protocol:
|
if protocol:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Add-on config 'startup' with '%s' is depircated. Please report this to the maintainer of %s",
|
"Add-on config 'startup' with '%s' is deprecated. Please report this to the maintainer of %s",
|
||||||
value,
|
value,
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
@@ -210,7 +234,7 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_PORTS): docker_ports,
|
vol.Optional(ATTR_PORTS): docker_ports,
|
||||||
vol.Optional(ATTR_PORTS_DESCRIPTION): docker_ports_description,
|
vol.Optional(ATTR_PORTS_DESCRIPTION): docker_ports_description,
|
||||||
vol.Optional(ATTR_WATCHDOG): vol.Match(
|
vol.Optional(ATTR_WATCHDOG): vol.Match(
|
||||||
r"^(?:https?|\[PROTO:\w+\]|tcp):\/\/\[HOST\]:\[PORT:\d+\].*$"
|
r"^(?:https?|\[PROTO:\w+\]|tcp):\/\/\[HOST\]:(\[PORT:\d+\]|\d+).*$"
|
||||||
),
|
),
|
||||||
vol.Optional(ATTR_WEBUI): vol.Match(
|
vol.Optional(ATTR_WEBUI): vol.Match(
|
||||||
r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"
|
r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"
|
||||||
@@ -233,7 +257,7 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_TMPFS, default=False): vol.Boolean(),
|
vol.Optional(ATTR_TMPFS, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_MAP, default=list): [vol.Match(RE_VOLUME)],
|
vol.Optional(ATTR_MAP, default=list): [vol.Match(RE_VOLUME)],
|
||||||
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): str},
|
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): str},
|
||||||
vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)],
|
vol.Optional(ATTR_PRIVILEGED): [vol.Coerce(Capabilities)],
|
||||||
vol.Optional(ATTR_APPARMOR, default=True): vol.Boolean(),
|
vol.Optional(ATTR_APPARMOR, default=True): vol.Boolean(),
|
||||||
vol.Optional(ATTR_FULL_ACCESS, default=False): vol.Boolean(),
|
vol.Optional(ATTR_FULL_ACCESS, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(),
|
vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(),
|
||||||
@@ -243,6 +267,7 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_UART, default=False): vol.Boolean(),
|
vol.Optional(ATTR_UART, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_DEVICETREE, default=False): vol.Boolean(),
|
vol.Optional(ATTR_DEVICETREE, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_KERNEL_MODULES, default=False): vol.Boolean(),
|
vol.Optional(ATTR_KERNEL_MODULES, default=False): vol.Boolean(),
|
||||||
|
vol.Optional(ATTR_REALTIME, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_HASSIO_ROLE, default=ROLE_DEFAULT): vol.In(ROLE_ALL),
|
vol.Optional(ATTR_HASSIO_ROLE, default=ROLE_DEFAULT): vol.In(ROLE_ALL),
|
||||||
vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(),
|
||||||
@@ -275,11 +300,14 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_TIMEOUT, default=10): vol.All(
|
vol.Optional(ATTR_TIMEOUT, default=10): vol.All(
|
||||||
vol.Coerce(int), vol.Range(min=10, max=300)
|
vol.Coerce(int), vol.Range(min=10, max=300)
|
||||||
),
|
),
|
||||||
|
vol.Optional(ATTR_JOURNALD, default=False): vol.Boolean(),
|
||||||
},
|
},
|
||||||
extra=vol.REMOVE_EXTRA,
|
extra=vol.REMOVE_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
SCHEMA_ADDON_CONFIG = vol.All(_migrate_addon_config(True), _SCHEMA_ADDON_CONFIG)
|
SCHEMA_ADDON_CONFIG = vol.All(
|
||||||
|
_migrate_addon_config(True), _warn_addon_config, _SCHEMA_ADDON_CONFIG
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
@@ -296,6 +324,23 @@ SCHEMA_BUILD_CONFIG = vol.Schema(
|
|||||||
extra=vol.REMOVE_EXTRA,
|
extra=vol.REMOVE_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SCHEMA_TRANSLATION_CONFIGURATION = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_NAME): str,
|
||||||
|
vol.Optional(ATTR_DESCRIPTON): vol.Maybe(str),
|
||||||
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_ADDON_TRANSLATIONS = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_CONFIGURATION): {str: SCHEMA_TRANSLATION_CONFIGURATION},
|
||||||
|
vol.Optional(ATTR_NETWORK): {str: str},
|
||||||
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_ADDON_USER = vol.Schema(
|
SCHEMA_ADDON_USER = vol.Schema(
|
||||||
@@ -318,13 +363,15 @@ SCHEMA_ADDON_USER = vol.Schema(
|
|||||||
extra=vol.REMOVE_EXTRA,
|
extra=vol.REMOVE_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_ADDON_SYSTEM = vol.All(
|
SCHEMA_ADDON_SYSTEM = vol.All(
|
||||||
_migrate_addon_config(),
|
_migrate_addon_config(),
|
||||||
_SCHEMA_ADDON_CONFIG.extend(
|
_SCHEMA_ADDON_CONFIG.extend(
|
||||||
{
|
{
|
||||||
vol.Required(ATTR_LOCATON): str,
|
vol.Required(ATTR_LOCATON): str,
|
||||||
vol.Required(ATTR_REPOSITORY): str,
|
vol.Required(ATTR_REPOSITORY): str,
|
||||||
|
vol.Required(ATTR_TRANSLATIONS, default=dict): {
|
||||||
|
str: SCHEMA_ADDON_TRANSLATIONS
|
||||||
|
},
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -334,7 +381,8 @@ SCHEMA_ADDONS_FILE = vol.Schema(
|
|||||||
{
|
{
|
||||||
vol.Optional(ATTR_USER, default=dict): {str: SCHEMA_ADDON_USER},
|
vol.Optional(ATTR_USER, default=dict): {str: SCHEMA_ADDON_USER},
|
||||||
vol.Optional(ATTR_SYSTEM, default=dict): {str: SCHEMA_ADDON_SYSTEM},
|
vol.Optional(ATTR_SYSTEM, default=dict): {str: SCHEMA_ADDON_SYSTEM},
|
||||||
}
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -28,6 +28,7 @@ from .resolution import APIResoulution
|
|||||||
from .security import SecurityMiddleware
|
from .security import SecurityMiddleware
|
||||||
from .services import APIServices
|
from .services import APIServices
|
||||||
from .snapshots import APISnapshots
|
from .snapshots import APISnapshots
|
||||||
|
from .store import APIStore
|
||||||
from .supervisor import APISupervisor
|
from .supervisor import APISupervisor
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
@@ -80,6 +81,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
self._register_services()
|
self._register_services()
|
||||||
self._register_snapshots()
|
self._register_snapshots()
|
||||||
self._register_supervisor()
|
self._register_supervisor()
|
||||||
|
self._register_store()
|
||||||
|
|
||||||
await self.start()
|
await self.start()
|
||||||
|
|
||||||
@@ -226,6 +228,10 @@ class RestAPI(CoreSysAttributes):
|
|||||||
self.webapp.add_routes(
|
self.webapp.add_routes(
|
||||||
[
|
[
|
||||||
web.get("/resolution/info", api_resolution.info),
|
web.get("/resolution/info", api_resolution.info),
|
||||||
|
web.post(
|
||||||
|
"/resolution/check/{check}/options", api_resolution.options_check
|
||||||
|
),
|
||||||
|
web.post("/resolution/check/{check}/run", api_resolution.run_check),
|
||||||
web.post(
|
web.post(
|
||||||
"/resolution/suggestion/{suggestion}",
|
"/resolution/suggestion/{suggestion}",
|
||||||
api_resolution.apply_suggestion,
|
api_resolution.apply_suggestion,
|
||||||
@@ -238,6 +244,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
"/resolution/issue/{issue}",
|
"/resolution/issue/{issue}",
|
||||||
api_resolution.dismiss_issue,
|
api_resolution.dismiss_issue,
|
||||||
),
|
),
|
||||||
|
web.post("/resolution/healthcheck", api_resolution.healthcheck),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -338,16 +345,15 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get("/addons", api_addons.list),
|
web.get("/addons", api_addons.list),
|
||||||
web.post("/addons/reload", api_addons.reload),
|
web.post("/addons/reload", api_addons.reload),
|
||||||
web.get("/addons/{addon}/info", api_addons.info),
|
web.get("/addons/{addon}/info", api_addons.info),
|
||||||
web.post("/addons/{addon}/install", api_addons.install),
|
|
||||||
web.post("/addons/{addon}/uninstall", api_addons.uninstall),
|
web.post("/addons/{addon}/uninstall", api_addons.uninstall),
|
||||||
web.post("/addons/{addon}/start", api_addons.start),
|
web.post("/addons/{addon}/start", api_addons.start),
|
||||||
web.post("/addons/{addon}/stop", api_addons.stop),
|
web.post("/addons/{addon}/stop", api_addons.stop),
|
||||||
web.post("/addons/{addon}/restart", api_addons.restart),
|
web.post("/addons/{addon}/restart", api_addons.restart),
|
||||||
web.post("/addons/{addon}/update", api_addons.update),
|
|
||||||
web.post("/addons/{addon}/options", api_addons.options),
|
web.post("/addons/{addon}/options", api_addons.options),
|
||||||
web.post(
|
web.post(
|
||||||
"/addons/{addon}/options/validate", api_addons.options_validate
|
"/addons/{addon}/options/validate", api_addons.options_validate
|
||||||
),
|
),
|
||||||
|
web.get("/addons/{addon}/options/config", api_addons.options_config),
|
||||||
web.post("/addons/{addon}/rebuild", api_addons.rebuild),
|
web.post("/addons/{addon}/rebuild", api_addons.rebuild),
|
||||||
web.get("/addons/{addon}/logs", api_addons.logs),
|
web.get("/addons/{addon}/logs", api_addons.logs),
|
||||||
web.get("/addons/{addon}/icon", api_addons.icon),
|
web.get("/addons/{addon}/icon", api_addons.icon),
|
||||||
@@ -468,6 +474,46 @@ class RestAPI(CoreSysAttributes):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _register_store(self) -> None:
|
||||||
|
"""Register store endpoints."""
|
||||||
|
api_store = APIStore()
|
||||||
|
api_store.coresys = self.coresys
|
||||||
|
|
||||||
|
self.webapp.add_routes(
|
||||||
|
[
|
||||||
|
web.get("/store", api_store.store_info),
|
||||||
|
web.get("/store/addons", api_store.addons_list),
|
||||||
|
web.get("/store/addons/{addon}", api_store.addons_addon_info),
|
||||||
|
web.get("/store/addons/{addon}/{version}", api_store.addons_addon_info),
|
||||||
|
web.post(
|
||||||
|
"/store/addons/{addon}/install", api_store.addons_addon_install
|
||||||
|
),
|
||||||
|
web.post(
|
||||||
|
"/store/addons/{addon}/install/{version}",
|
||||||
|
api_store.addons_addon_install,
|
||||||
|
),
|
||||||
|
web.post("/store/addons/{addon}/update", api_store.addons_addon_update),
|
||||||
|
web.post(
|
||||||
|
"/store/addons/{addon}/update/{version}",
|
||||||
|
api_store.addons_addon_update,
|
||||||
|
),
|
||||||
|
web.post("/store/reload", api_store.reload),
|
||||||
|
web.get("/store/repositories", api_store.repositories_list),
|
||||||
|
web.get(
|
||||||
|
"/store/repositories/{repository}",
|
||||||
|
api_store.repositories_repository_info,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Reroute from legacy
|
||||||
|
self.webapp.add_routes(
|
||||||
|
[
|
||||||
|
web.post("/addons/{addon}/install", api_store.addons_addon_install),
|
||||||
|
web.post("/addons/{addon}/update", api_store.addons_addon_update),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def _register_panel(self) -> None:
|
def _register_panel(self) -> None:
|
||||||
"""Register panel for Home Assistant."""
|
"""Register panel for Home Assistant."""
|
||||||
panel_dir = Path(__file__).parent.joinpath("panel")
|
panel_dir = Path(__file__).parent.joinpath("panel")
|
||||||
|
@@ -82,6 +82,7 @@ from ..const import (
|
|||||||
ATTR_STARTUP,
|
ATTR_STARTUP,
|
||||||
ATTR_STATE,
|
ATTR_STATE,
|
||||||
ATTR_STDIN,
|
ATTR_STDIN,
|
||||||
|
ATTR_TRANSLATIONS,
|
||||||
ATTR_UART,
|
ATTR_UART,
|
||||||
ATTR_UDEV,
|
ATTR_UDEV,
|
||||||
ATTR_UPDATE_AVAILABLE,
|
ATTR_UPDATE_AVAILABLE,
|
||||||
@@ -102,7 +103,8 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..docker.stats import DockerStats
|
from ..docker.stats import DockerStats
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError, APIForbidden, PwnedError, PwnedSecret
|
||||||
|
from ..utils.pwned import check_pwned_password
|
||||||
from ..validate import docker_ports
|
from ..validate import docker_ports
|
||||||
from .utils import api_process, api_process_raw, api_validate
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
@@ -171,6 +173,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_INSTALLED: addon.is_installed,
|
ATTR_INSTALLED: addon.is_installed,
|
||||||
ATTR_AVAILABLE: addon.available,
|
ATTR_AVAILABLE: addon.available,
|
||||||
ATTR_DETACHED: addon.is_detached,
|
ATTR_DETACHED: addon.is_detached,
|
||||||
|
ATTR_HOMEASSISTANT: addon.homeassistant_version,
|
||||||
ATTR_REPOSITORY: addon.repository,
|
ATTR_REPOSITORY: addon.repository,
|
||||||
ATTR_BUILD: addon.need_build,
|
ATTR_BUILD: addon.need_build,
|
||||||
ATTR_URL: addon.url,
|
ATTR_URL: addon.url,
|
||||||
@@ -264,6 +267,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_SERVICES: _pretty_services(addon),
|
ATTR_SERVICES: _pretty_services(addon),
|
||||||
ATTR_DISCOVERY: addon.discovery,
|
ATTR_DISCOVERY: addon.discovery,
|
||||||
ATTR_IP_ADDRESS: None,
|
ATTR_IP_ADDRESS: None,
|
||||||
|
ATTR_TRANSLATIONS: addon.translations,
|
||||||
ATTR_INGRESS: addon.with_ingress,
|
ATTR_INGRESS: addon.with_ingress,
|
||||||
ATTR_INGRESS_ENTRY: None,
|
ATTR_INGRESS_ENTRY: None,
|
||||||
ATTR_INGRESS_URL: None,
|
ATTR_INGRESS_URL: None,
|
||||||
@@ -335,14 +339,45 @@ class APIAddons(CoreSysAttributes):
|
|||||||
"""Validate user options for add-on."""
|
"""Validate user options for add-on."""
|
||||||
addon = self._extract_addon_installed(request)
|
addon = self._extract_addon_installed(request)
|
||||||
data = {ATTR_MESSAGE: "", ATTR_VALID: True}
|
data = {ATTR_MESSAGE: "", ATTR_VALID: True}
|
||||||
|
|
||||||
|
# Validate config
|
||||||
try:
|
try:
|
||||||
addon.schema(addon.options)
|
addon.schema(addon.options)
|
||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
data[ATTR_MESSAGE] = humanize_error(addon.options, ex)
|
data[ATTR_MESSAGE] = humanize_error(addon.options, ex)
|
||||||
data[ATTR_VALID] = False
|
data[ATTR_VALID] = False
|
||||||
|
|
||||||
|
# Validate security
|
||||||
|
if self.sys_config.force_security:
|
||||||
|
for secret in addon.pwned:
|
||||||
|
try:
|
||||||
|
await check_pwned_password(self.sys_websession, secret)
|
||||||
|
continue
|
||||||
|
except PwnedSecret:
|
||||||
|
data[ATTR_MESSAGE] = "Add-on use pwned secrets!"
|
||||||
|
except PwnedError as err:
|
||||||
|
data[
|
||||||
|
ATTR_MESSAGE
|
||||||
|
] = f"Error happening on pwned secrets check: {err!s}!"
|
||||||
|
|
||||||
|
data[ATTR_VALID] = False
|
||||||
|
break
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def options_config(self, request: web.Request) -> None:
|
||||||
|
"""Validate user options for add-on."""
|
||||||
|
slug: str = request.match_info.get("addon")
|
||||||
|
if slug != "self":
|
||||||
|
raise APIForbidden("This can be only read by the Add-on itself!")
|
||||||
|
|
||||||
|
addon = self._extract_addon_installed(request)
|
||||||
|
try:
|
||||||
|
return addon.schema(addon.options)
|
||||||
|
except vol.Invalid:
|
||||||
|
raise APIError("Invalid configuration data for the add-on") from None
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
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."""
|
||||||
@@ -373,12 +408,6 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_BLK_WRITE: stats.blk_write,
|
ATTR_BLK_WRITE: stats.blk_write,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
|
||||||
def install(self, request: web.Request) -> Awaitable[None]:
|
|
||||||
"""Install add-on."""
|
|
||||||
addon = self._extract_addon(request)
|
|
||||||
return asyncio.shield(addon.install())
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def uninstall(self, request: web.Request) -> Awaitable[None]:
|
def uninstall(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Uninstall add-on."""
|
"""Uninstall add-on."""
|
||||||
@@ -397,12 +426,6 @@ class APIAddons(CoreSysAttributes):
|
|||||||
addon = self._extract_addon_installed(request)
|
addon = self._extract_addon_installed(request)
|
||||||
return asyncio.shield(addon.stop())
|
return asyncio.shield(addon.stop())
|
||||||
|
|
||||||
@api_process
|
|
||||||
def update(self, request: web.Request) -> Awaitable[None]:
|
|
||||||
"""Update add-on."""
|
|
||||||
addon: Addon = self._extract_addon_installed(request)
|
|
||||||
return asyncio.shield(addon.update())
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request: web.Request) -> Awaitable[None]:
|
def restart(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Restart add-on."""
|
"""Restart add-on."""
|
||||||
@@ -472,14 +495,6 @@ class APIAddons(CoreSysAttributes):
|
|||||||
await asyncio.shield(addon.write_stdin(data))
|
await asyncio.shield(addon.write_stdin(data))
|
||||||
|
|
||||||
|
|
||||||
def _pretty_devices(addon: AnyAddon) -> List[str]:
|
|
||||||
"""Return a simplified device list."""
|
|
||||||
dev_list = addon.devices
|
|
||||||
if not dev_list:
|
|
||||||
return []
|
|
||||||
return [row.split(":")[0] for row in dev_list]
|
|
||||||
|
|
||||||
|
|
||||||
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()]
|
||||||
|
@@ -13,7 +13,7 @@ from ..const import (
|
|||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..discovery.validate import valid_discovery_service
|
from ..discovery.validate import valid_discovery_service
|
||||||
from ..exceptions import APIError, APIForbidden
|
from ..exceptions import APIError, APIForbidden
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate, require_home_assistant
|
||||||
|
|
||||||
SCHEMA_DISCOVERY = vol.Schema(
|
SCHEMA_DISCOVERY = vol.Schema(
|
||||||
{
|
{
|
||||||
@@ -33,15 +33,10 @@ class APIDiscovery(CoreSysAttributes):
|
|||||||
raise APIError("Discovery message not found")
|
raise APIError("Discovery message not found")
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def _check_permission_ha(self, request):
|
|
||||||
"""Check permission for API call / Home Assistant."""
|
|
||||||
if request[REQUEST_FROM] != self.sys_homeassistant:
|
|
||||||
raise APIForbidden("Only HomeAssistant can use this API!")
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@require_home_assistant
|
||||||
async def list(self, request):
|
async def list(self, request):
|
||||||
"""Show register services."""
|
"""Show register services."""
|
||||||
self._check_permission_ha(request)
|
|
||||||
|
|
||||||
# Get available discovery
|
# Get available discovery
|
||||||
discovery = []
|
discovery = []
|
||||||
@@ -79,13 +74,11 @@ class APIDiscovery(CoreSysAttributes):
|
|||||||
return {ATTR_UUID: message.uuid}
|
return {ATTR_UUID: message.uuid}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@require_home_assistant
|
||||||
async def get_discovery(self, request):
|
async def get_discovery(self, request):
|
||||||
"""Read data into a discovery message."""
|
"""Read data into a discovery message."""
|
||||||
message = self._extract_message(request)
|
message = self._extract_message(request)
|
||||||
|
|
||||||
# HomeAssistant?
|
|
||||||
self._check_permission_ha(request)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ATTR_ADDON: message.addon,
|
ATTR_ADDON: message.addon,
|
||||||
ATTR_SERVICE: message.service,
|
ATTR_SERVICE: message.service,
|
||||||
|
@@ -62,4 +62,4 @@ class APIHardware(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def trigger(self, request: web.Request) -> Awaitable[None]:
|
async def trigger(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Trigger a udev device reload."""
|
"""Trigger a udev device reload."""
|
||||||
_LOGGER.warning("Ignoring DEPRECATED hardware trigger function call.")
|
_LOGGER.debug("Ignoring DEPRECATED hardware trigger function call.")
|
||||||
|
@@ -11,6 +11,7 @@ from ..const import (
|
|||||||
ATTR_DEPLOYMENT,
|
ATTR_DEPLOYMENT,
|
||||||
ATTR_DESCRIPTON,
|
ATTR_DESCRIPTON,
|
||||||
ATTR_DISK_FREE,
|
ATTR_DISK_FREE,
|
||||||
|
ATTR_DISK_LIFE_TIME,
|
||||||
ATTR_DISK_TOTAL,
|
ATTR_DISK_TOTAL,
|
||||||
ATTR_DISK_USED,
|
ATTR_DISK_USED,
|
||||||
ATTR_FEATURES,
|
ATTR_FEATURES,
|
||||||
@@ -43,6 +44,7 @@ class APIHost(CoreSysAttributes):
|
|||||||
ATTR_DISK_FREE: self.sys_host.info.free_space,
|
ATTR_DISK_FREE: self.sys_host.info.free_space,
|
||||||
ATTR_DISK_TOTAL: self.sys_host.info.total_space,
|
ATTR_DISK_TOTAL: self.sys_host.info.total_space,
|
||||||
ATTR_DISK_USED: self.sys_host.info.used_space,
|
ATTR_DISK_USED: self.sys_host.info.used_space,
|
||||||
|
ATTR_DISK_LIFE_TIME: self.sys_host.info.disk_life_time,
|
||||||
ATTR_FEATURES: self.sys_host.features,
|
ATTR_FEATURES: self.sys_host.features,
|
||||||
ATTR_HOSTNAME: self.sys_host.info.hostname,
|
ATTR_HOSTNAME: self.sys_host.info.hostname,
|
||||||
ATTR_KERNEL: self.sys_host.info.kernel,
|
ATTR_KERNEL: self.sys_host.info.kernel,
|
||||||
|
@@ -15,6 +15,7 @@ from ..const import (
|
|||||||
ATTR_LOGGING,
|
ATTR_LOGGING,
|
||||||
ATTR_MACHINE,
|
ATTR_MACHINE,
|
||||||
ATTR_OPERATING_SYSTEM,
|
ATTR_OPERATING_SYSTEM,
|
||||||
|
ATTR_STATE,
|
||||||
ATTR_SUPERVISOR,
|
ATTR_SUPERVISOR,
|
||||||
ATTR_SUPPORTED,
|
ATTR_SUPPORTED,
|
||||||
ATTR_SUPPORTED_ARCH,
|
ATTR_SUPPORTED_ARCH,
|
||||||
@@ -42,6 +43,7 @@ class APIInfo(CoreSysAttributes):
|
|||||||
ATTR_FEATURES: self.sys_host.features,
|
ATTR_FEATURES: self.sys_host.features,
|
||||||
ATTR_MACHINE: self.sys_machine,
|
ATTR_MACHINE: self.sys_machine,
|
||||||
ATTR_ARCH: self.sys_arch.default,
|
ATTR_ARCH: self.sys_arch.default,
|
||||||
|
ATTR_STATE: self.sys_core.state,
|
||||||
ATTR_SUPPORTED_ARCH: self.sys_arch.supported,
|
ATTR_SUPPORTED_ARCH: self.sys_arch.supported,
|
||||||
ATTR_SUPPORTED: self.sys_core.supported,
|
ATTR_SUPPORTED: self.sys_core.supported,
|
||||||
ATTR_CHANNEL: self.sys_updater.channel,
|
ATTR_CHANNEL: self.sys_updater.channel,
|
||||||
|
@@ -25,10 +25,9 @@ from ..const import (
|
|||||||
COOKIE_INGRESS,
|
COOKIE_INGRESS,
|
||||||
HEADER_TOKEN,
|
HEADER_TOKEN,
|
||||||
HEADER_TOKEN_OLD,
|
HEADER_TOKEN_OLD,
|
||||||
REQUEST_FROM,
|
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate, require_home_assistant
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -50,11 +49,6 @@ class APIIngress(CoreSysAttributes):
|
|||||||
|
|
||||||
return addon
|
return addon
|
||||||
|
|
||||||
def _check_ha_access(self, request: web.Request) -> None:
|
|
||||||
if request[REQUEST_FROM] != self.sys_homeassistant:
|
|
||||||
_LOGGER.warning("Ingress is only available behind Home Assistant")
|
|
||||||
raise HTTPUnauthorized()
|
|
||||||
|
|
||||||
def _create_url(self, addon: Addon, path: str) -> str:
|
def _create_url(self, addon: Addon, path: str) -> str:
|
||||||
"""Create URL to container."""
|
"""Create URL to container."""
|
||||||
return f"http://{addon.ip_address}:{addon.ingress_port}/{path}"
|
return f"http://{addon.ip_address}:{addon.ingress_port}/{path}"
|
||||||
@@ -74,18 +68,16 @@ class APIIngress(CoreSysAttributes):
|
|||||||
return {ATTR_PANELS: addons}
|
return {ATTR_PANELS: addons}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@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."""
|
||||||
self._check_ha_access(request)
|
|
||||||
|
|
||||||
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
|
||||||
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."""
|
||||||
self._check_ha_access(request)
|
|
||||||
|
|
||||||
data = await api_validate(VALIDATE_SESSION_DATA, request)
|
data = await api_validate(VALIDATE_SESSION_DATA, request)
|
||||||
|
|
||||||
# Check Ingress Session
|
# Check Ingress Session
|
||||||
@@ -93,11 +85,11 @@ class APIIngress(CoreSysAttributes):
|
|||||||
_LOGGER.warning("No valid ingress session %s", data[ATTR_SESSION])
|
_LOGGER.warning("No valid ingress session %s", data[ATTR_SESSION])
|
||||||
raise HTTPUnauthorized()
|
raise HTTPUnauthorized()
|
||||||
|
|
||||||
|
@require_home_assistant
|
||||||
async def handler(
|
async def handler(
|
||||||
self, request: web.Request
|
self, request: web.Request
|
||||||
) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]:
|
) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]:
|
||||||
"""Route data to Supervisor ingress service."""
|
"""Route data to Supervisor ingress service."""
|
||||||
self._check_ha_access(request)
|
|
||||||
|
|
||||||
# Check Ingress Session
|
# Check Ingress Session
|
||||||
session = request.cookies.get(COOKIE_INGRESS)
|
session = request.cookies.get(COOKIE_INGRESS)
|
||||||
|
@@ -36,6 +36,8 @@ class APIJobs(CoreSysAttributes):
|
|||||||
|
|
||||||
self.sys_jobs.save_data()
|
self.sys_jobs.save_data()
|
||||||
|
|
||||||
|
await self.sys_resolution.evaluate.evaluate_system()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def reset(self, request: web.Request) -> None:
|
async def reset(self, request: web.Request) -> None:
|
||||||
"""Reset options for JobManager."""
|
"""Reset options for JobManager."""
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
new Function("import('/api/hassio/app/frontend_latest/entrypoint.cf81f6d9.js')")();
|
new Function("import('/api/hassio/app/frontend_latest/entrypoint.4050b348.js')")();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
var el = document.createElement('script');
|
var el = document.createElement('script');
|
||||||
el.src = '/api/hassio/app/frontend_es5/entrypoint.c258a457.js';
|
el.src = '/api/hassio/app/frontend_es5/entrypoint.bcf8e8ff.js';
|
||||||
document.body.appendChild(el);
|
document.body.appendChild(el);
|
||||||
}
|
}
|
||||||
|
|
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":"chunk.0aeb318c1dfa0b946242.js","sources":["webpack://home-assistant-frontend/chunk.0aeb318c1dfa0b946242.js"],"mappings":"AAAA","sourceRoot":""}
|
|
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"chunk.0ca880f618245e0de2ab.js","sources":["webpack://home-assistant-frontend/chunk.0ca880f618245e0de2ab.js"],"mappings":"AAAA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.1274df0d4f9b61110840.js","sources":["webpack://home-assistant-frontend/chunk.1274df0d4f9b61110840.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.144785285e77c1827c0e.js","sources":["webpack://home-assistant-frontend/chunk.144785285e77c1827c0e.js"],"mappings":"AAAA","sourceRoot":""}
|
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"chunk.1da2af7a753728051dc0.js","sources":["webpack://home-assistant-frontend/chunk.1da2af7a753728051dc0.js"],"mappings":";AAAA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.23709353009814f6ec3d.js","sources":["webpack://home-assistant-frontend/chunk.23709353009814f6ec3d.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.28ab065538efa89db557.js","sources":["webpack://home-assistant-frontend/chunk.28ab065538efa89db557.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.2e5192dd0552c13e5623.js","sources":["webpack://home-assistant-frontend/chunk.2e5192dd0552c13e5623.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.3a2a2f6b4ddd0c4883f8.js","sources":["webpack://home-assistant-frontend/chunk.3a2a2f6b4ddd0c4883f8.js"],"mappings":";AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.3a76f0ea2b0888f970db.js","sources":["webpack://home-assistant-frontend/chunk.3a76f0ea2b0888f970db.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.3c156057c501c13568ee.js","sources":["webpack://home-assistant-frontend/chunk.3c156057c501c13568ee.js"],"mappings":"AAAA","sourceRoot":""}
|
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"chunk.42b19ffa1fa277adc9cd.js","sources":["webpack://home-assistant-frontend/chunk.42b19ffa1fa277adc9cd.js"],"mappings":"AAAA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.480c85c27dcb0da9fc2a.js","sources":["webpack://home-assistant-frontend/chunk.480c85c27dcb0da9fc2a.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.4e6beec37a57a13ae7aa.js","sources":["webpack://home-assistant-frontend/chunk.4e6beec37a57a13ae7aa.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.555bab815b35ce22d731.js","sources":["webpack://home-assistant-frontend/chunk.555bab815b35ce22d731.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":"chunk.63747632f287a94ecaa7.js","sources":["webpack://home-assistant-frontend/chunk.63747632f287a94ecaa7.js"],"mappings":"AAAA","sourceRoot":""}
|
|
@@ -1,2 +0,0 @@
|
|||||||
!function(){"use strict";var n,t={14971:function(n,t,e){var r,o,i=e(91107),u=e(9902),a=e.n(u),s=(e(58556),e(62173)),f={renderMarkdown:function(n,t){var e,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return r||(r=Object.assign({},(0,s.getDefaultWhiteList)(),{"ha-icon":["icon"],"ha-svg-icon":["path"]})),i.allowSvg?(o||(o=Object.assign({},r,{svg:["xmlns","height","width"],path:["transform","stroke","d"],img:["src"]})),e=o):e=r,(0,s.filterXSS)(a()(n,t),{whiteList:e})}};(0,i.Jj)(f)}},e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={exports:{}};return t[n].call(o.exports,o,o.exports,r),o.exports}r.m=t,r.x=function(){r(14971)},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(t,{a:t}),t},r.d=function(n,t){for(var e in t)r.o(t,e)&&!r.o(n,e)&&Object.defineProperty(n,e,{enumerable:!0,get:t[e]})},r.f={},r.e=function(n){return Promise.all(Object.keys(r.f).reduce((function(t,e){return r.f[e](n,t),t}),[]))},r.u=function(n){return"chunk.d93e72d29a82b1218f4a.js"},r.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},n=r.x,r.x=function(){return r.e(474).then(n)},r.p="/api/hassio/app/frontend_es5/",function(){var n={971:1};r.f.i=function(t,e){n[t]||importScripts(""+r.u(t))};var t=self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[],e=t.push.bind(t);t.push=function(t){var o=t[0],i=t[1],u=t[2];for(var a in i)r.o(i,a)&&(r.m[a]=i[a]);for(u&&u(r);o.length;)n[o.pop()]=1;e(t)}}(),r.x()}();
|
|
||||||
//# sourceMappingURL=chunk.67e3627d3c04d75f6f9d.js.map
|
|
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"chunk.67e3627d3c04d75f6f9d.js","sources":["webpack://home-assistant-frontend/chunk.67e3627d3c04d75f6f9d.js"],"mappings":"AAAA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
|||||||
|
/*! *****************************************************************************
|
||||||
|
Copyright (c) Microsoft Corporation.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
***************************************************************************** */
|
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.69339833f81fc6a02f8b.js","sources":["webpack://home-assistant-frontend/chunk.69339833f81fc6a02f8b.js"],"mappings":";AAAA","sourceRoot":""}
|
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([[914],{92914:function(n,e,r){"use strict";r.r(e),r.d(e,{codeMirror:function(){return c},codeMirrorCss:function(){return i}});var t=r(79074),s=r.n(t),o=r(49338),a=(r(22080),r(19393),r(47181));s().commands.save=function(n){(0,a.B)(n.getWrapperElement(),"editor-save")};var c=s(),i=o.Z}}]);
|
|
||||||
//# sourceMappingURL=chunk.6e5ce6bddf1cc3ddee5c.js.map
|
|
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"chunk.6e5ce6bddf1cc3ddee5c.js","sources":["webpack://home-assistant-frontend/chunk.6e5ce6bddf1cc3ddee5c.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":"chunk.712a551bcc67f5a1a334.js","sources":["webpack://home-assistant-frontend/chunk.712a551bcc67f5a1a334.js"],"mappings":"AAAA","sourceRoot":""}
|
|
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"chunk.79b4e240c46ccbe25e4d.js","sources":["webpack://home-assistant-frontend/chunk.79b4e240c46ccbe25e4d.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":"chunk.d0222f0b152d21991ec4.js","sources":["webpack://home-assistant-frontend/chunk.d0222f0b152d21991ec4.js"],"mappings":"AAAA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.d30d63ed3a1b37c81772.js","sources":["webpack://home-assistant-frontend/chunk.d30d63ed3a1b37c81772.js"],"mappings":"AAAA","sourceRoot":""}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user