Compare commits

..

155 Commits

Author SHA1 Message Date
Joakim Sørensen
2bb014fda5 Update frontend to 0d1c7238 (#2681) 2021-03-04 17:16:17 +01:00
Pascal Vizeli
09203f67b2 Fix defaults for builds (#2675) 2021-03-04 11:48:25 +01:00
Joakim Sørensen
169c7ec004 Update frontend to 419e5644 (#2667) 2021-03-03 16:20:04 +01:00
Joakim Sørensen
202e94615e Update frontend to bee17fce (#2665)
* Update frontend to fe50f422

* revert unrelated change

* Update frontend to bee17fce
2021-03-03 15:15:07 +01:00
Pascal Vizeli
5fe2a815ad Fix error handling with new config handling (#2666) 2021-03-03 12:46:34 +01:00
Joakim Sørensen
a13a0b4770 Guard for missing repository file (#2664)
* Guard for missing repository file

* Update supervisor/store/data.py

Co-authored-by: Pascal Vizeli <pvizeli@syshack.ch>

Co-authored-by: Pascal Vizeli <pvizeli@syshack.ch>
2021-03-03 11:33:58 +01:00
Joakim Sørensen
455bbc457b Update frontend to 9f73f0ca (#2661)
* Update frontend to 9f73f0ca

* Update frontend to 935d97ce
2021-03-02 17:12:36 +01:00
Joakim Sørensen
d50fd3b580 Add network description to translation files (#2660) 2021-03-02 15:41:00 +01:00
Pascal Vizeli
455e80b07c Merge branch 'main' of https://github.com/home-assistant/supervisor into main 2021-03-02 13:16:09 +00:00
Joakim Sørensen
291becbdf9 Update frontend to 1642c684 (#2657) 2021-03-01 17:19:48 +01:00
Pascal Vizeli
33385b46a7 Fix add-on is not installed anymore (#2656)
* Fix add-on is not installed anymore

* Fix and add tests
2021-03-01 17:19:38 +01:00
Pascal Vizeli
df17668369 Fix add-on is not installed anymore 2021-03-01 15:43:50 +00:00
Joakim Sørensen
43449c85bb Fix translation schema (#2654)
* Fix translation schema

* really fix it

* fix migration path

Co-authored-by: Pascal Vizeli <pvizeli@syshack.ch>
2021-03-01 16:39:00 +01:00
Stefan Agner
9e86eda05a Adjust CPU runtime allocation to support multiple add-ons (#2655)
Adjust realtime CPU runtime allocation to support multiple
add-ons/plug-ins with the runtime flag set.

The cpu.rt_runtime_us allocation is somewhat special as it
allocates the guaranteed runtime. Hence we cannot simply allocate all
time to each add-on.

Unfortunately we cannot distribute the CPU time equally based on the
number of add-ons as we don't know the number of add-ons which will use
realtime ahead of time. Also adjusting at runtime is currenlty not
supported by the Docker Python SDK (the underlying http API does
however, so this would be a possible future improvement).

This solution distributes the total available time of 950ms to up to 5
add-ons/plug-ins in equal slices. On a quad-core system this allows up
to 760ms CPU time in the real-time scheduler per 1s (76% of one CPU)
which should be sufficient for most cases.
2021-03-01 15:29:10 +01:00
Pascal Vizeli
b288554d9c Allow dynamic lookup cgroup rules based on Host udev (#2652) 2021-03-01 14:57:44 +01:00
Joakim Sørensen
bee55d08fb Create FileConfiguration baseclass (#2651) 2021-03-01 12:26:43 +01:00
Joakim Sørensen
7a542aeb38 Add support for loading add-on translation files (#2644)
* Add support for loading add-on translation files

* Fix storing translations for installed add-ons

* Allow YAML, force schema

* Adjust schema
2021-03-01 11:44:24 +01:00
dependabot[bot]
8d42513ba8 Bump gitpython from 3.1.13 to 3.1.14 (#2647)
Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.13 to 3.1.14.
- [Release notes](https://github.com/gitpython-developers/GitPython/releases)
- [Changelog](https://github.com/gitpython-developers/GitPython/blob/master/CHANGES)
- [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.13...3.1.14)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-01 11:33:41 +01:00
dependabot[bot]
89b7247aa2 Bump home-assistant/builder from 2021.01.1 to 2021.02.0 (#2650)
Bumps [home-assistant/builder](https://github.com/home-assistant/builder) from 2021.01.1 to 2021.02.0.
- [Release notes](https://github.com/home-assistant/builder/releases)
- [Commits](https://github.com/home-assistant/builder/compare/2021.01.1...8c4b820c467894c8374aca262deac0a8aef4982d)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-01 10:56:10 +01:00
dependabot[bot]
29132e7f4c Bump coverage from 5.4 to 5.5 (#2648)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 5.4 to 5.5.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/coverage-5.4...coverage-5.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-01 10:24:38 +01:00
dependabot[bot]
3fd9baf78e Bump pylint from 2.6.2 to 2.7.2 (#2649)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.6.2 to 2.7.2.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.6.2...pylint-2.7.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-01 10:17:21 +01:00
Philip Allgaier
f3aa3757ce Fixed typos in resolution notify (#2646) 2021-03-01 09:44:16 +01:00
Joakim Sørensen
3760967f59 Allow use YAML for addon and repository config (#2645)
* Allow use YAML for addon and repository config

* pylint
2021-02-28 20:00:02 +01:00
Joakim Sørensen
f7ab8e0f7f Update frontend to 478a4b25 (#2637) 2021-02-26 15:23:15 +01:00
Joakim Sørensen
0e46ea12b2 Allways store docker and repository config (#2636) 2021-02-26 13:40:47 +01:00
Joakim Sørensen
be226b2b01 Make homeassistant optional in partial snapshot (#2635) 2021-02-26 12:51:04 +01:00
dependabot[bot]
9e1239e192 Bump aiohttp from 3.7.3 to 3.7.4 (#2633)
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.7.3 to 3.7.4.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.7.3...v3.7.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-26 09:43:45 +01:00
dependabot[bot]
2eba3d85b0 Bump pulsectl from 20.5.1 to 21.2.0 (#2634)
Bumps [pulsectl](https://github.com/mk-fg/python-pulse-control) from 20.5.1 to 21.2.0.
- [Release notes](https://github.com/mk-fg/python-pulse-control/releases)
- [Changelog](https://github.com/mk-fg/python-pulse-control/blob/master/CHANGES.rst)
- [Commits](https://github.com/mk-fg/python-pulse-control/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-26 09:42:59 +01:00
Joakim Sørensen
9b569268ab Fix add-on updates (#2632)
* Fix add-on updates

* Add to security role
2021-02-25 23:29:39 +01:00
Pascal Vizeli
31f5033dca Add throttle to job execution (#2631)
* Add throttle to job execution

* fix unittests

* Add tests

* address comments

* add comment

* better on __init__

* New text

* Simplify logic
2021-02-25 23:29:03 +01:00
Pascal Vizeli
78d9c60be5 Log issues/suggestion with info (#2629) 2021-02-25 19:52:19 +01:00
Stefan Agner
baa86f09e5 Real-time fixes (#2630)
* Fix path of cpu.rt_runtime_us sysfs file

Fix detection of CPU bandwidth allocation (CONFIG_RT_GROUP_SCHED) option
by checking the correct file location.

* Set priority limit and memory lock limit

Set a soft limit of real-time priority 90 and hard limit of 99. The
maximum is 99, to avoid add-ons using such a high priority without
second thought set the soft limit to 90. This allows a process to higher
the limit to 99, if really required.

Also set max locked memory to 128MB. Locking memory from getting paged
out is often used for the real-time process/thread to avoid delays due
to swapping/pageing in. Ideally a real-time process should only do the
real-time job, hence not need too much memory.

* Remove check for support_cpu_realtime and set correct limit for audio plug-in
2021-02-25 18:48:08 +01:00
Pascal Vizeli
a4c4b39ba8 Fix k-anonymity handling (#2627) 2021-02-25 18:16:20 +01:00
Joakim Sørensen
752068bb56 Add /store API (#2626) 2021-02-25 16:15:51 +01:00
Pascal Vizeli
739cfbb273 Small cleanups with pwned handling (#2625) 2021-02-25 15:36:02 +01:00
Pascal Vizeli
115af4cadf Optimize resolution checks / add endpoint to trigger it manual (#2622) 2021-02-25 14:05:27 +01:00
Joakim Sørensen
ae3274e559 Add home-assistant version to API response (#2621) 2021-02-25 11:44:22 +01:00
dependabot[bot]
c61f096dbd Bump docker from 4.4.3 to 4.4.4 (#2619)
Bumps [docker](https://github.com/docker/docker-py) from 4.4.3 to 4.4.4.
- [Release notes](https://github.com/docker/docker-py/releases)
- [Commits](https://github.com/docker/docker-py/compare/4.4.3...4.4.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-25 10:27:24 +01:00
dependabot[bot]
ee7b5c42fd Bump awesomeversion from 21.2.2 to 21.2.3 (#2620)
Bumps [awesomeversion](https://github.com/ludeeus/awesomeversion) from 21.2.2 to 21.2.3.
- [Release notes](https://github.com/ludeeus/awesomeversion/releases)
- [Commits](https://github.com/ludeeus/awesomeversion/compare/21.2.2...21.2.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-25 10:27:05 +01:00
Pascal Vizeli
85d527bfbc Add check for pwned secrets to resolution center (#2614)
* Add check for pwned secrets to resolution center

* restructure check

* add checks

* Add test

* Add test

* reload secrets before check

* simplify

* create notification

* Use own exceptions

* Check on startup

* Apply suggestions from code review

Co-authored-by: Franck Nijhof <git@frenck.dev>

* Add job decorator

* Update supervisor/resolution/notify.py

Co-authored-by: Franck Nijhof <git@frenck.dev>

* Update supervisor/utils/pwned.py

Co-authored-by: Franck Nijhof <git@frenck.dev>

Co-authored-by: Franck Nijhof <git@frenck.dev>
2021-02-25 09:37:45 +01:00
Pascal Vizeli
dd561da819 Fix landingpage on check_api_state (#2615)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-02-24 21:52:18 +01:00
Joakim Sørensen
cb5932cb8b Fix issue with concurrent reads (#2610)
* Fix issue with concurrent reads

* Use lock

* acquire/release

* handle ConnectionError

* No there was not mulitple issues, it's the same code just different syle.....Which is stated on the same page as that image...
2021-02-24 18:36:21 +01:00
Pascal Vizeli
8630adc54a Add execution limit for jobs (#2612)
* Add execution limit for jobs

* Add test for execution police

* Use better test

* Apply suggestions from code review

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>

* Rename JobExecutionLimit

* fix typing

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-02-24 17:15:13 +01:00
Joakim Sørensen
90d8832cd2 Send event when add-on changes state (#2608)
* Send event when add-on changes state

* fix test
2021-02-23 15:12:30 +01:00
Pascal Vizeli
3802b97bb6 Add kernel docs about the device classes (#2607) 2021-02-22 15:10:28 +01:00
Pascal Vizeli
2de175e181 Remove not needed call_soon (#2606) 2021-02-22 14:41:44 +01:00
Joakim Sørensen
6b7d437b00 Signal frontend to refresh on SU startup (#2603)
* Signal frontend to refresh on SU startup

* Use sync variant
2021-02-22 14:31:48 +01:00
Pascal Vizeli
e2faf906de Use the cpu_rt support check also for rtprio (#2605) 2021-02-22 14:02:36 +01:00
Franck Nijhof
bb44ce5cd2 Fix small typo in log message (#2604) 2021-02-22 13:44:12 +01:00
Joakim Sørensen
15544ae589 Show add-on with bad config in log (#2601) 2021-02-22 13:43:03 +01:00
Pascal Vizeli
e421284471 Detect host support for CPU realtime + add an option for enable it (#2602) 2021-02-22 13:30:32 +01:00
Pascal Vizeli
785dc64787 Imrove the LXC detection (#2599) 2021-02-22 11:39:18 +01:00
Joakim Sørensen
7e7e3a7876 Update frontend to ef0bfb23 (#2598) 2021-02-22 11:22:47 +01:00
Pascal Vizeli
2b45c059e0 Support realtime scheduling for add-ons (#2593)
* Support realtime scheduling for add-ons

* support on audio

* address comments from @jens-maus
2021-02-22 11:22:36 +01:00
Joakim Sørensen
14ec61f9bd Update frontend to 2d3cf7d8 (#2594) 2021-02-22 09:41:52 +01:00
Joakim Sørensen
5cc72756f8 Fix some issues with the issue template (#2592) 2021-02-19 23:35:00 +01:00
Franck Nijhof
44785ef3e2 Update GitHub Issue Form template (#2591) 2021-02-19 23:18:23 +01:00
Franck Nijhof
e60d858feb Allow for static port in Watchdog add-on configuration (#2589) 2021-02-19 15:23:17 +01:00
Joakim Sørensen
b31ecfefcd Initial WS support (#2439)
* Initial WS support

* test

* Update frontend to fc7c4af2

* Fix issue with closing states

* log error

* make data optional

* limit stopping states

* Move wrappers to HomeAssistantWebSocket

* use info

* Use call_soon

* Use lookuptable for WS commands

* Fix tests
2021-02-19 11:57:31 +01:00
dependabot[bot]
c342231052 Bump docker from 4.4.2 to 4.4.3 (#2587)
Bumps [docker](https://github.com/docker/docker-py) from 4.4.2 to 4.4.3.
- [Release notes](https://github.com/docker/docker-py/releases)
- [Commits](https://github.com/docker/docker-py/compare/4.4.2...4.4.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-19 10:09:03 +01:00
dependabot[bot]
673666837e Bump sentry-sdk from 0.20.2 to 0.20.3 (#2588) 2021-02-19 08:59:22 +01:00
dependabot[bot]
c8f74d6c0d Bump actions/stale from v3.0.16 to v3.0.17 (#2586) 2021-02-19 08:42:14 +01:00
Pascal Vizeli
7ed9de8014 Fix device check if not exists (#2585) 2021-02-18 12:39:42 +01:00
Pascal Vizeli
8650947f04 Add capabilities for puleaudio (#2583)
* Add capabilities for puleaudio

* fix import
2021-02-17 17:21:47 +01:00
Pascal Vizeli
a0ac8ced31 Fix handling with full access / blocked devices (#2581)
* Fix handling with full access / blocked devices

* address comment

* Use name

* Add validation warning

* add GPIO check too

* remove warning

* return directly

* fix tests
2021-02-17 15:48:48 +01:00
Joakim Sørensen
2145bbea81 Update frontend to 03d41741 (#2580) 2021-02-17 13:12:37 +01:00
Pascal Vizeli
480000ee7f Fix typo in suggestion (#2578) 2021-02-17 12:49:07 +01:00
dependabot[bot]
9ec2ad022e Bump cryptography from 3.4.5 to 3.4.6 (#2576)
Bumps [cryptography](https://github.com/pyca/cryptography) from 3.4.5 to 3.4.6.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/3.4.5...3.4.6)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-17 12:01:18 +01:00
dependabot[bot]
43e40816dc Bump pylint from 2.6.0 to 2.6.2 (#2577)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.6.0 to 2.6.2.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.6.0...pylint-2.6.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-17 12:00:59 +01:00
Pascal Vizeli
941ea3ee68 Fix audio cgroup issue (#2575) 2021-02-16 09:34:15 +01:00
Pascal Vizeli
a6e4b5159e Discovery for vlc telnet (#2573) 2021-02-16 09:31:07 +01:00
dependabot[bot]
6f542d58d5 Bump docker from 4.4.1 to 4.4.2 (#2571)
Bumps [docker](https://github.com/docker/docker-py) from 4.4.1 to 4.4.2.
- [Release notes](https://github.com/docker/docker-py/releases)
- [Commits](https://github.com/docker/docker-py/compare/4.4.1...4.4.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-16 08:41:01 +01:00
dependabot[bot]
b2b5fcee7d Bump sentry-sdk from 0.20.1 to 0.20.2 (#2572)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 0.20.1 to 0.20.2.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/0.20.1...0.20.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-16 08:40:38 +01:00
Pascal Vizeli
59a82345a9 Drop full privileged from audio plugin (#2570) 2021-02-16 08:40:19 +01:00
Pascal Vizeli
b61a747876 Kernel modules add-on flag need no extra cap (#2569) 2021-02-15 20:42:03 +01:00
dependabot[bot]
72e5d800d5 Bump cryptography from 3.4.4 to 3.4.5 (#2567)
Bumps [cryptography](https://github.com/pyca/cryptography) from 3.4.4 to 3.4.5.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/3.4.4...3.4.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-15 10:09:54 +01:00
dependabot[bot]
c7aa6d4804 Bump sentry-sdk from 0.20.0 to 0.20.1 (#2568)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 0.20.0 to 0.20.1.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/0.20.0...0.20.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-15 10:08:58 +01:00
Pascal Vizeli
b31063449d Fix path for tmpfs (#2565) 2021-02-13 20:34:32 +01:00
dependabot[bot]
477672459d Bump sentry-sdk from 0.19.5 to 0.20.0 (#2563)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 0.19.5 to 0.20.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/0.19.5...0.20.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-12 15:33:20 +01:00
Pascal Vizeli
9c33897296 Add some issues and suggestion (#2557) 2021-02-11 09:06:46 +01:00
Matheson Steplock
100cfb57c5 Fix Typos in LOGGER (#2552)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2021-02-10 21:53:37 +01:00
Pascal Vizeli
40b34071e7 Revert "Mask folder /dev/bus/usb RW (#2555)" (#2556)
This reverts commit 341833fd8f.
2021-02-10 11:35:21 +01:00
Pascal Vizeli
341833fd8f Mask folder /dev/bus/usb RW (#2555) 2021-02-10 10:37:40 +01:00
dependabot[bot]
f647fd6fea Bump cryptography from 3.4.3 to 3.4.4 (#2553)
Bumps [cryptography](https://github.com/pyca/cryptography) from 3.4.3 to 3.4.4.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/3.4.3...3.4.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-10 10:23:42 +01:00
dependabot[bot]
53642f2389 Bump gitpython from 3.1.12 to 3.1.13 (#2554)
Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.12 to 3.1.13.
- [Release notes](https://github.com/gitpython-developers/GitPython/releases)
- [Changelog](https://github.com/gitpython-developers/GitPython/blob/master/CHANGES)
- [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.12...3.1.13)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-10 10:23:06 +01:00
Pascal Vizeli
b9bdd655ab Add hidraw to USB (#2549) 2021-02-09 21:09:57 +01:00
Pascal Vizeli
e9e1b5b54f Add support for hiddev on USB (#2548) 2021-02-09 19:25:16 +01:00
Franck Nijhof
be2163d635 Small tweaks to issue form (#2545) 2021-02-09 17:25:34 +01:00
Franck Nijhof
7f6dde3a5f Fix issue template forms (#2543) 2021-02-09 17:14:44 +01:00
Joakim Sørensen
334aafee23 Update frontend to c7821b9c (#2542) 2021-02-09 13:10:13 +01:00
Pascal Vizeli
1a20c18b19 Use Alpine 3.13 (#2539)
* Use Alpine 3.13

* Update builder.yml
2021-02-09 10:21:14 +01:00
dependabot[bot]
6e655b165c Bump cryptography from 3.3.1 to 3.4.3 (#2537)
* Bump cryptography from 3.3.1 to 3.4.3

Bumps [cryptography](https://github.com/pyca/cryptography) from 3.3.1 to 3.4.3.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/3.3.1...3.4.3)

Signed-off-by: dependabot[bot] <support@github.com>

* Update builder.yml

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pascal Vizeli <pvizeli@syshack.ch>
2021-02-09 10:04:58 +01:00
Pascal Vizeli
d768b2fa1e Fix UI options without filter (#2536) 2021-02-08 21:37:44 +01:00
Joakim Sørensen
85bce1cfba Add core state to /info API (#2531) 2021-02-08 12:39:46 +01:00
Pascal Vizeli
a798a2466f Change Supervisor update flow with AppArmor (#2529) 2021-02-08 10:45:34 +01:00
dependabot[bot]
2a5d8a5c82 Bump actions/stale from v3.0.15 to v3.0.16 (#2527)
Bumps [actions/stale](https://github.com/actions/stale) from v3.0.15 to v3.0.16.
- [Release notes](https://github.com/actions/stale/releases)
- [Commits](https://github.com/actions/stale/compare/v3.0.15...9d6f46564a515a9ea11e7762ab3957ee58ca50da)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-08 09:40:58 +01:00
dependabot[bot]
ea62171d98 Bump pyupgrade from 2.9.0 to 2.10.0 (#2526)
Bumps [pyupgrade](https://github.com/asottile/pyupgrade) from 2.9.0 to 2.10.0.
- [Release notes](https://github.com/asottile/pyupgrade/releases)
- [Commits](https://github.com/asottile/pyupgrade/compare/v2.9.0...v2.10.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-08 09:40:46 +01:00
dependabot[bot]
196389d5ee Bump pre-commit from 2.10.0 to 2.10.1 (#2525)
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 2.10.0 to 2.10.1.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/master/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v2.10.0...v2.10.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-08 09:40:33 +01:00
dependabot[bot]
1776021620 Bump actions/cache from v2 to v2.1.4 (#2528)
Bumps [actions/cache](https://github.com/actions/cache) from v2 to v2.1.4.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2...26968a09c0ea4f3e233fdddbafd1166051a095f6)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-08 09:39:28 +01:00
Pascal Vizeli
c42a9124d3 Fix cleanup with changed image (#2521) 2021-02-05 16:03:57 +01:00
dependabot[bot]
a44647b4cd Bump awesomeversion from 21.2.0 to 21.2.2 (#2520)
Bumps [awesomeversion](https://github.com/ludeeus/awesomeversion) from 21.2.0 to 21.2.2.
- [Release notes](https://github.com/ludeeus/awesomeversion/releases)
- [Commits](https://github.com/ludeeus/awesomeversion/compare/21.2.0...21.2.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-05 11:31:57 +01:00
Pascal Vizeli
e0c3fd87c5 Add API endpoint for add-on for his own config (#2515)
* Add API endpoint for add-on for his own config

* allow access his config
2021-02-04 16:32:48 +01:00
Pascal Vizeli
ed8f2a85b7 Fix memory stats calc (#2518) 2021-02-04 16:06:12 +01:00
Pascal Vizeli
48f8553c75 Show the current core state on message (#2516) 2021-02-04 15:52:48 +01:00
Joakim Sørensen
af4517fd1e Update frontend to 4273b72d (#2517) 2021-02-04 15:50:07 +01:00
Joakim Sørensen
78e6a46318 Update frontend to e06642e8 (#2513) 2021-02-03 18:00:43 +01:00
Pascal Vizeli
49ca923e51 Apply old udev handling like core container (#2511) 2021-02-03 12:56:25 +01:00
Pascal Vizeli
7ad22e0399 Make add-on file schema more future approved (#2510) 2021-02-03 12:06:22 +01:00
Pascal Vizeli
bb8acc6065 Fix add-on state was not fast reflected (#2509) 2021-02-03 12:02:09 +01:00
Joakim Sørensen
c0fa4a19e9 Change verbosity for hardware.trigger API (#2508) 2021-02-02 20:56:37 +01:00
Franck Nijhof
3f1741dd18 Typo: depircated -> deprecated (#2507) 2021-02-02 20:41:56 +01:00
Stefan Agner
9ef02e4110 Add eMMC life-time estimate support (#2413)
* Add eMMC life-time estimate support

Expose life time estimate as Host Info property "ssd_life_time"

* Fix pytest

* Fix path to helper

* Allow protected access in tests

* Apply suggestions from code review

Rename SSD to disk.

Co-authored-by: Pascal Vizeli <pascal.vizeli@syshack.ch>

* Rename functions as well

* Update host.py

Co-authored-by: Pascal Vizeli <pascal.vizeli@syshack.ch>
2021-02-02 10:31:02 +01:00
Pascal Vizeli
15a6f38ebb Make sure device is ready initialize on udev (#2504)
* Make sure device is ready initialize on udev

* fix style

* let's start with 2sec

* better logic

* style

* add change events back
2021-02-02 09:53:09 +01:00
dependabot[bot]
227f2e5a21 Bump pyupgrade from 2.8.0 to 2.9.0 (#2505)
Bumps [pyupgrade](https://github.com/asottile/pyupgrade) from 2.8.0 to 2.9.0.
- [Release notes](https://github.com/asottile/pyupgrade/releases)
- [Commits](https://github.com/asottile/pyupgrade/compare/v2.8.0...v2.9.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-02 09:52:51 +01:00
Pascal Vizeli
517d6ee981 Lookup udev device in monitor thread (#2503) 2021-02-01 18:29:05 +01:00
Pascal Vizeli
184eeb7f49 Udev - move away form depricated callback / increase buffer (#2502)
* Use new callback from udev / only forward

* minimize loop

* Udev - move away form depricated callback / increase buffer

* code cleanup
2021-02-01 17:33:53 +01:00
Joakim Sørensen
a3555c74e8 Bump awesomeversion from 21.1.8 to 21.2.0 (#2501) 2021-02-01 15:45:35 +01:00
Pascal Vizeli
657bafd458 Fix handling with shm and shared /dev (#2499)
* Fix handling with shm and shared /dev

* address commends

* let's share shm
2021-02-01 14:44:02 +01:00
Joakim Sørensen
2ad5df420c Rerun label check on synchronize (#2500) 2021-02-01 14:37:56 +01:00
Joakim Sørensen
b24d489ec5 Update frontend to a9192ae2 (#2498) 2021-02-01 13:26:54 +01:00
Franck Nijhof
6bb0210f1f Fix add-on is_valid check (#2497) 2021-02-01 11:27:34 +01:00
dependabot[bot]
c1de50266a Bump pytz from 2020.5 to 2021.1 (#2496)
Bumps [pytz](https://github.com/stub42/pytz) from 2020.5 to 2021.1.
- [Release notes](https://github.com/stub42/pytz/releases)
- [Commits](https://github.com/stub42/pytz/compare/release_2020.5...release_2021.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-01 11:27:17 +01:00
dependabot[bot]
562e02bc64 Bump pyupgrade from 2.7.4 to 2.8.0 (#2494)
Bumps [pyupgrade](https://github.com/asottile/pyupgrade) from 2.7.4 to 2.8.0.
- [Release notes](https://github.com/asottile/pyupgrade/releases)
- [Commits](https://github.com/asottile/pyupgrade/compare/v2.7.4...v2.8.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-01 11:27:04 +01:00
dependabot[bot]
f71ec7913a Bump awesomeversion from 21.1.5 to 21.1.8 (#2493)
Bumps [awesomeversion](https://github.com/ludeeus/awesomeversion) from 21.1.5 to 21.1.8.
- [Release notes](https://github.com/ludeeus/awesomeversion/releases)
- [Commits](https://github.com/ludeeus/awesomeversion/compare/21.1.5...21.1.8)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-01 11:26:51 +01:00
dependabot[bot]
d98baaf660 Bump jinja2 from 2.11.2 to 2.11.3 (#2495)
Bumps [jinja2](https://github.com/pallets/jinja) from 2.11.2 to 2.11.3.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/master/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/2.11.2...2.11.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-01 11:26:30 +01:00
Pascal Vizeli
72db591576 Validate build env before using it (#2492) 2021-02-01 09:46:48 +01:00
Wouter Schoot
509a37fc04 Typofix exsist -> exist (#2491)
* Typofix exsist -> exist

* Typofix exist -> exists
2021-02-01 09:34:17 +01:00
Pascal Vizeli
17f62b6e86 Handle corrupt store branch / ignore local reset on corruption (#2490)
* Handle corrupt store branch / ignore local reset on corruption

* Update data.py

* Update data.py
2021-01-31 22:55:43 +01:00
Pascal Vizeli
b09aee7644 Filter out virtual vcs devices (#2489)
* Filter out virtual vcs devices

* fix test
2021-01-31 14:40:14 +01:00
Pascal Vizeli
babcc0de0c Cleanup supervisor/udev device action / add GPIO (#2488)
* Cleanup supervisor/udev device action / add GPIO

* Apply francks comment
2021-01-31 14:24:22 +01:00
Franck Nijhof
5cc47c9222 Re-enable old template until form is resolved (#2486) 2021-01-30 19:47:45 +01:00
Franck Nijhof
833559a3b3 Add initial GitHub Issue Form (#2485)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-01-30 19:35:15 +01:00
Pascal Vizeli
b8a976b344 Change handling kernel udev events (#2484)
* Change handling kernel udev events / add USB events

* finish USB

* map dev ro

* fix bind
2021-01-30 14:19:53 +01:00
Joakim Sørensen
10b14132b9 Update frontend to 86847263 (#2483) 2021-01-29 18:23:58 +01:00
Pascal Vizeli
18953f0b7c plug & play devices (#2482)
* Add support for plug&play container & full udev passtrhough

* plug & play devices

* this hack is not needed anymore

* Fix GPIO issue

* Apply suggestions from code review

Co-authored-by: Franck Nijhof <git@frenck.dev>

Co-authored-by: Franck Nijhof <git@frenck.dev>
2021-01-29 17:17:41 +01:00
Joakim Sørensen
19f5fba3aa Bump awesomeversion from 21.1.4 to 21.1.5 (#2481) 2021-01-29 14:10:06 +01:00
Pascal Vizeli
636bc3e61a Handle GPIO / VIDEO map with subystem (#2478)
* Handle GPIO / VIDEO mapping with subystem

* fix tests

* add udev support

* init udev data

* fix
2021-01-29 11:28:16 +01:00
Pascal Vizeli
521037e1a6 Fix mknod creating of the device on runtime time (#2477) 2021-01-28 17:07:33 +01:00
Pascal Vizeli
e024c3e38d Improve device selection and show device by id if available (#2476) 2021-01-28 15:43:06 +01:00
Pascal Vizeli
6a0206c1e7 Next generation hardware handling (#2429)
* Next generation hardware handling

* need daemon for some details

* fix tests

* fix wrong coresys lookup

* test initial import

* test device lookup

* validate if device exists

* Add cgroups rules manager

* mapping udev from host

* Modify validation/options handling

* lookup devices

* add support for host udev mapping

* next

* Add policy support to add-ons

* Depricate hardware trigger call

* next cleanup round

* detect USB linking

* optimize

* readd udev utils for backwards compatibility

* fix tests

* Add more tests

* fix tests

* Make device explicit

* Add filter

* work on tests

* Add migration step

* clean out auto_uart

* Fix all tests

* Expose all device information

* small  improvment

* Fix loop over right devices

* Use migration for new device format

* Update rootfs/etc/cont-init.d/udev.sh

Co-authored-by: Franck Nijhof <git@frenck.dev>

* Fix old helper

* Fix API

* add helper for by-id

* fix tests

* Fix serial helper

* Fix hardware API schema

* Hide some virtual devices from tracking

* Apply suggestions from code review

Co-authored-by: Stefan Agner <stefan@agner.ch>

* Update supervisor/addons/validate.py

Co-authored-by: Stefan Agner <stefan@agner.ch>

* Update supervisor/addons/validate.py

Co-authored-by: Stefan Agner <stefan@agner.ch>

* fix lint

* Apply suggestions from code review

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>

* Apply suggestions from code review

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>

* fix black

* fix lint

Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Stefan Agner <stefan@agner.ch>
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-01-28 15:26:56 +01:00
Joakim Sørensen
69a8a83528 Limit label check (#2475) 2021-01-28 12:07:42 +01:00
Joakim Sørensen
0307d700fa Add TypeError guard for docker stats (#2474) 2021-01-28 11:58:04 +01:00
Joakim Sørensen
919c383b41 Bump awesomeversion from 21.1.3 to 21.1.4 (#2473) 2021-01-28 11:41:11 +01:00
dependabot[bot]
d331af4d5a Bump pre-commit from 2.9.3 to 2.10.0 (#2472)
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 2.9.3 to 2.10.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/master/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v2.9.3...v2.10.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-28 08:46:29 +01:00
Joakim Sørensen
b5467d3c23 Update frontend to 5ee0250b (#2471) 2021-01-28 08:45:33 +01:00
dependabot[bot]
3ef0040d66 Bump coverage from 5.3.1 to 5.4 (#2470)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 5.3.1 to 5.4.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/coverage-5.3.1...coverage-5.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-27 08:41:22 +01:00
Joakim Sørensen
ec4dfd2172 Add action to check PR labels (#2469)
* Check PR labels

* update

* update

* update

* only use lable events

* remove env stuff

* add back types
2021-01-26 14:21:05 +01:00
Pascal Vizeli
8f54d7c8e9 New version logic NM (#2466) 2021-01-26 10:33:35 +01:00
dependabot[bot]
b59e709dc0 Bump home-assistant/builder from 2020.11.0 to 2021.01.1 (#2468)
Bumps [home-assistant/builder](https://github.com/home-assistant/builder) from 2020.11.0 to 2021.01.1.
- [Release notes](https://github.com/home-assistant/builder/releases)
- [Commits](https://github.com/home-assistant/builder/compare/2020.11.0...e33ea8af3ad98e9ed7513cad92cc88fb303b42ff)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-26 10:17:50 +01:00
dependabot[bot]
b236e6c886 Bump pytest from 6.2.1 to 6.2.2 (#2467)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.1 to 6.2.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.1...6.2.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-26 09:47:45 +01:00
Pascal Vizeli
8acbb7d6f0 Fix API info calls access (#2464) 2021-01-25 10:45:08 +01:00
dependabot[bot]
49e4bc9381 Bump actions/stale from v3.0.14 to v3.0.15 (#2463)
Bumps [actions/stale](https://github.com/actions/stale) from v3.0.14 to v3.0.15.
- [Release notes](https://github.com/actions/stale/releases)
- [Commits](https://github.com/actions/stale/compare/v3.0.14...86561461b92875de77a8b2d2e75f004c826e8f45)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-25 09:59:20 +01:00
Philip Allgaier
36106cc08d Small README tweaks (#2460) 2021-01-24 21:18:06 +01:00
Pascal Vizeli
8f1763abe2 Fix OSError with custom component check (#2458) 2021-01-24 16:46:49 +01:00
Joakim Sørensen
480eebc6cb Update alert notification (#2457) 2021-01-23 21:06:55 +01:00
351 changed files with 8606 additions and 4487 deletions

106
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,106 @@
name: Bug Report Form
about: Report an issue related to the Home Assistant Supervisor.
labels: bug
title: ""
issue_body: true
body:
- type: markdown
attributes:
value: |
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].
[fr]: https://community.home-assistant.io/c/feature-requests
- type: textarea
validations:
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-
description: >
Can be found in the Supervisor panel -> System tab. Starts with
`supervisor-....`.
- type: dropdown
validations:
required: true
attributes:
label: What type of installation are you running?
description: >
If you don't know, you can find it in: Configuration panel -> Info.
options:
- Home Assistant OS
- Home Assistant Supervised
- type: dropdown
validations:
required: true
attributes:
label: Which operating system are you running on?
options:
- Home Assistant Operating System
- Debian
- Other (e.g., Raspbian/Raspberry Pi OS/Fedora)
- type: input
validations:
required: true
attributes:
label: What is the version of your installed operating system?
placeholder: "5.11"
description: Can be found in the Supervisor panel -> System tab.
- type: input
validations:
required: true
attributes:
label: What version of Home Assistant Core is installed?
placeholder: core-
description: >
Can be found in the Supervisor panel -> System tab. Starts with
`core-....`.
- type: markdown
attributes:
value: |
# Details
- type: textarea
validations:
required: true
attributes:
label: Steps to reproduce the issue
description: |
Please tell us exactly how to reproduce your issue.
Provide clear and concise step by step instructions and add code snippets if needed.
value: |
1.
2.
3.
...
- type: textarea
validations:
required: true
attributes:
label: Anything in the Supervisor logs that might be useful for us?
description: >
The Supervisor logs can be found in the Supervisor panel -> System tab.
value: |
```txt
# Put your logs below this line
```
- type: markdown
attributes:
value: |
## Additional information
- type: markdown
attributes:
value: |
If you have any additional information for us, use the field below.
Please note, you can attach screenshots or screen recordings here.

View File

@@ -35,7 +35,7 @@ on:
env:
BUILD_NAME: supervisor
BUILD_TYPE: supervisor
WHEELS_TAG: 3.8-alpine3.12
WHEELS_TAG: 3.8-alpine3.13
jobs:
init:
@@ -97,7 +97,7 @@ jobs:
wheels-host: ${{ secrets.WHEELS_HOST }}
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
apk: "build-base;libffi-dev;openssl-dev"
apk: "build-base;libffi-dev;openssl-dev;cargo"
skip-binary: aiohttp
requirements: "requirements.txt"
@@ -119,7 +119,7 @@ jobs:
run: echo "BUILD_ARGS=--test" >> $GITHUB_ENV
- name: Build supervisor
uses: home-assistant/builder@2020.11.0
uses: home-assistant/builder@2021.02.0
with:
args: |
$BUILD_ARGS \
@@ -161,7 +161,7 @@ jobs:
uses: actions/checkout@v2
- name: Build the Supervisor
uses: home-assistant/builder@2020.11.0
uses: home-assistant/builder@2021.02.0
with:
args: |
--test \

19
.github/workflows/check_pr_labels.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Check PR
on:
pull_request:
branches: ["main"]
types: [labeled, unlabeled, synchronize]
jobs:
init:
name: Check labels
runs-on: ubuntu-latest
steps:
- name: Check labels
run: |
labels=$(jq -r '.pull_request.labels[] | .name' ${{github.event_path }})
echo "$labels"
if [ "$labels" == "cla-signed" ]; then
exit 1
fi

View File

@@ -30,7 +30,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: venv
key: |
@@ -47,7 +47,7 @@ jobs:
pip install -r requirements.txt -r requirements_tests.txt
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: ${{ env.PRE_COMMIT_HOME }}
key: |
@@ -74,7 +74,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: venv
key: |
@@ -118,7 +118,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: venv
key: |
@@ -130,7 +130,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: ${{ env.PRE_COMMIT_HOME }}
key: |
@@ -162,7 +162,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: venv
key: |
@@ -194,7 +194,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: venv
key: |
@@ -206,7 +206,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: ${{ env.PRE_COMMIT_HOME }}
key: |
@@ -235,7 +235,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: venv
key: |
@@ -247,7 +247,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: ${{ env.PRE_COMMIT_HOME }}
key: |
@@ -279,7 +279,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: venv
key: |
@@ -311,7 +311,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: venv
key: |
@@ -323,7 +323,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: ${{ env.PRE_COMMIT_HOME }}
key: |
@@ -355,7 +355,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: venv
key: |
@@ -409,7 +409,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v2
uses: actions/cache@v2.1.4
with:
path: venv
key: |

View File

@@ -9,7 +9,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3.0.14
- uses: actions/stale@v3.0.17
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 60

View File

@@ -21,12 +21,12 @@ Development instructions can be found [here][development].
Releases are done in 3 stages (channels) with this structure:
1. Pull requests are merged to the `main` branch
1. Pull requests are merged to the `main` branch.
2. A new build is pushed to the `dev` stage.
3. Releases are published
3. Releases are published.
4. A new build is pushed to the `beta` stage.
5. The [`stable.json][stable] file is updated
6. The build that was pushed to `beta` will now be pushed to `stable`
5. The [`stable.json`][stable] file is updated.
6. The build that was pushed to `beta` will now be pushed to `stable`.
[development]: https://developers.home-assistant.io/docs/supervisor/development
[stable]: https://github.com/home-assistant/version/blob/master/stable.json

View File

@@ -1,11 +1,11 @@
{
"image": "homeassistant/{arch}-hassio-supervisor",
"build_from": {
"aarch64": "homeassistant/aarch64-base-python:3.8-alpine3.12",
"armhf": "homeassistant/armhf-base-python:3.8-alpine3.12",
"armv7": "homeassistant/armv7-base-python:3.8-alpine3.12",
"amd64": "homeassistant/amd64-base-python:3.8-alpine3.12",
"i386": "homeassistant/i386-base-python:3.8-alpine3.12"
"aarch64": "homeassistant/aarch64-base-python:3.8-alpine3.13",
"armhf": "homeassistant/armhf-base-python:3.8-alpine3.13",
"armv7": "homeassistant/armv7-base-python:3.8-alpine3.13",
"amd64": "homeassistant/amd64-base-python:3.8-alpine3.13",
"i386": "homeassistant/i386-base-python:3.8-alpine3.13"
},
"labels": {
"io.hass.type": "supervisor"

View File

@@ -1,21 +1,20 @@
aiohttp==3.7.3
aiohttp==3.7.4
async_timeout==3.0.1
atomicwrites==1.4.0
attrs==20.3.0
awesomeversion==21.1.3
awesomeversion==21.2.3
brotli==1.0.9
cchardet==2.1.7
colorlog==4.7.2
cpe==1.2.1
cryptography==3.3.1
cryptography==3.4.6
debugpy==1.2.1
docker==4.4.1
gitpython==3.1.12
jinja2==2.11.2
packaging==20.4
pulsectl==20.5.1
pytz==2020.5
docker==4.4.4
gitpython==3.1.14
jinja2==2.11.3
pulsectl==21.2.0
pytz==2021.1
pyudev==0.22.0
ruamel.yaml==0.15.100
sentry-sdk==0.19.5
sentry-sdk==0.20.3
voluptuous==0.12.1

View File

@@ -1,14 +1,14 @@
black==20.8b1
codecov==2.1.11
coverage==5.3.1
coverage==5.5
flake8-docstrings==1.5.0
flake8==3.8.4
pre-commit==2.9.3
pre-commit==2.10.1
pydocstyle==5.1.1
pylint==2.6.0
pylint==2.7.2
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-cov==2.11.1
pytest-timeout==1.4.2
pytest==6.2.1
pyupgrade==2.7.4
pytest==6.2.2
pyupgrade==2.10.0

View File

@@ -2,9 +2,17 @@
# ==============================================================================
# Start udev service
# ==============================================================================
if bashio::fs.directory_exists /run/udev && ! bashio::fs.file_exists /run/.old_udev; then
bashio::log.info "Using udev information from host"
bashio::exit.ok
fi
bashio::log.info "Setup udev backend inside container"
udevd --daemon
bashio::log.info "Update udev information"
touch /run/.old_udev
if udevadm trigger; then
udevadm settle || true
else

View File

@@ -23,9 +23,10 @@ function run_supervisor() {
--privileged \
--security-opt seccomp=unconfined \
--security-opt apparmor:unconfined \
-v /run/docker.sock:/run/docker.sock \
-v /run/dbus:/run/dbus \
-v "/workspaces/test_supervisor":/data \
-v /run/docker.sock:/run/docker.sock:rw \
-v /run/dbus:/run/dbus:ro \
-v /run/udev:/run/udev:ro \
-v "/workspaces/test_supervisor":/data:rw \
-v /etc/machine-id:/etc/machine-id:ro \
-v /workspaces/supervisor:/usr/src/supervisor \
-e SUPERVISOR_SHARE="/workspaces/test_supervisor" \
@@ -55,6 +56,24 @@ function init_dbus() {
dbus-daemon --system --print-address
}
function init_udev() {
if pgrep systemd-udevd; then
echo "udev is running"
return 0
fi
echo "Startup udev"
# cleanups
mkdir -p /run/udev
# run
/lib/systemd/systemd-udevd --daemon
sleep 3
udevadm trigger && udevadm settle
}
echo "Run Supervisor"
start_docker
@@ -65,6 +84,7 @@ if [ "$( docker container inspect -f '{{.State.Status}}' hassio_supervisor )" ==
echo "Restarting Supervisor"
docker rm -f hassio_supervisor
init_dbus
init_udev
cleanup_lastboot
run_supervisor
stop_docker
@@ -76,6 +96,7 @@ else
cleanup_lastboot
cleanup_docker
init_dbus
init_udev
run_supervisor
stop_docker
fi

View File

@@ -276,17 +276,18 @@ class AddonManager(CoreSysAttributes):
# Update instance
last_state: AddonState = addon.state
old_image = addon.image
try:
await addon.instance.update(store.version, store.image)
# Cleanup
with suppress(DockerError):
await addon.instance.cleanup()
except DockerError as err:
raise AddonsError() from err
else:
self.data.update(store)
_LOGGER.info("Add-on '%s' successfully updated", slug)
_LOGGER.info("Add-on '%s' successfully updated", slug)
self.data.update(store)
# Cleanup
with suppress(DockerError):
await addon.instance.cleanup(old_image=old_image)
# Setup/Fix AppArmor profile
await addon.install_apparmor()

View File

@@ -10,7 +10,7 @@ import secrets
import shutil
import tarfile
from tempfile import TemporaryDirectory
from typing import Any, Awaitable, Dict, List, Optional
from typing import Any, Awaitable, Dict, List, Optional, Set
import aiohttp
import voluptuous as vol
@@ -22,6 +22,8 @@ from ..const import (
ATTR_AUDIO_OUTPUT,
ATTR_AUTO_UPDATE,
ATTR_BOOT,
ATTR_DATA,
ATTR_EVENT,
ATTR_IMAGE,
ATTR_INGRESS_ENTRY,
ATTR_INGRESS_PANEL,
@@ -32,8 +34,10 @@ from ..const import (
ATTR_PORTS,
ATTR_PROTECTED,
ATTR_SCHEMA,
ATTR_SLUG,
ATTR_STATE,
ATTR_SYSTEM,
ATTR_TYPE,
ATTR_USER,
ATTR_UUID,
ATTR_VERSION,
@@ -50,18 +54,21 @@ from ..exceptions import (
AddonConfigurationError,
AddonsError,
AddonsNotSupportedError,
ConfigurationFileError,
DockerError,
DockerRequestError,
HostAppArmorError,
JsonFileError,
)
from ..hardware.data import Device
from ..homeassistant.const import WSEvent, WSType
from ..utils import check_port
from ..utils.apparmor import adjust_profile
from ..utils.json import read_json_file, write_json_file
from ..utils.tar import atomic_contents_add, secure_path
from .model import AddonModel, Data
from .options import AddonOptions
from .utils import remove_data
from .validate import SCHEMA_ADDON_SNAPSHOT, validate_options
from .validate import SCHEMA_ADDON_SNAPSHOT
_LOGGER: logging.Logger = logging.getLogger(__name__)
@@ -72,7 +79,7 @@ RE_WEBUI = re.compile(
RE_WATCHDOG = re.compile(
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+")
@@ -87,12 +94,34 @@ class Addon(AddonModel):
"""Initialize data holder."""
super().__init__(coresys, slug)
self.instance: DockerAddon = DockerAddon(coresys, self)
self.state: AddonState = AddonState.UNKNOWN
self._state: AddonState = AddonState.UNKNOWN
def __repr__(self) -> str:
"""Return internal representation."""
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
def in_progress(self) -> bool:
"""Return True if a task is in progress."""
@@ -394,6 +423,34 @@ class Addon(AddonModel):
"""Return path to asound config for Docker."""
return Path(self.sys_config.path_extern_tmp, f"{self.slug}_pulse")
@property
def devices(self) -> Set[Device]:
"""Extract devices from 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.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:
"""Save data of add-on."""
self.sys_addons.data.save_data()
@@ -442,22 +499,19 @@ class Addon(AddonModel):
async def write_options(self) -> None:
"""Return True if add-on options is written to data."""
schema = self.schema
options = self.options
# Update secrets for validation
await self.sys_homeassistant.secrets.reload()
try:
options = schema(options)
options = self.schema(self.options)
write_json_file(self.path_options, options)
except vol.Invalid as ex:
_LOGGER.error(
"Add-on %s has invalid options: %s",
self.slug,
humanize_error(options, ex),
humanize_error(self.options, ex),
)
except JsonFileError:
except ConfigurationFileError:
_LOGGER.error("Add-on %s can't write options", self.slug)
else:
_LOGGER.debug("Add-on %s write options: %s", self.slug, options)
@@ -538,7 +592,9 @@ class Addon(AddonModel):
# create voluptuous
new_schema = vol.Schema(
vol.All(dict, validate_options(self.coresys, new_raw_schema))
vol.All(
dict, AddonOptions(self.coresys, new_raw_schema, self.name, self.slug)
)
)
# validate
@@ -570,7 +626,7 @@ class Addon(AddonModel):
try:
await self.instance.run()
except DockerRequestError as err:
self.state = AddonState.STOPPED
self.state = AddonState.ERROR
raise AddonsError() from err
except DockerError as err:
self.state = AddonState.ERROR
@@ -581,8 +637,9 @@ class Addon(AddonModel):
async def stop(self) -> None:
"""Stop add-on."""
try:
return await self.instance.stop()
await self.instance.stop()
except DockerRequestError as err:
self.state = AddonState.ERROR
raise AddonsError() from err
except DockerError as err:
self.state = AddonState.ERROR
@@ -653,7 +710,7 @@ class Addon(AddonModel):
# Store local configs/state
try:
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)
raise AddonsError() from err
@@ -709,7 +766,7 @@ class Addon(AddonModel):
# Read snapshot data
try:
data = read_json_file(Path(temp, "addon.json"))
except JsonFileError as err:
except ConfigurationFileError as err:
raise AddonsError() from err
# Validate

View File

@@ -6,16 +6,23 @@ from typing import TYPE_CHECKING, Dict
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 ..utils.json import JsonConfig
from ..exceptions import ConfigurationFileError
from ..utils.common import FileConfiguration, find_one_filetype
from .validate import SCHEMA_BUILD_CONFIG
if TYPE_CHECKING:
from . import AnyAddon
class AddonBuild(JsonConfig, CoreSysAttributes):
class AddonBuild(FileConfiguration, CoreSysAttributes):
"""Handle build options for add-ons."""
def __init__(self, coresys: CoreSys, addon: AnyAddon) -> None:
@@ -23,9 +30,14 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
self.coresys: CoreSys = coresys
self.addon = addon
super().__init__(
Path(self.addon.path_location, "build.json"), SCHEMA_BUILD_CONFIG
)
try:
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):
"""Ignore save function."""
@@ -48,6 +60,16 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
"""Return additional Docker build arguments."""
return self._data[ATTR_ARGS]
@property
def is_valid(self) -> bool:
"""Return true if the build env is valid."""
return all(
[
self.addon.path_location.is_dir(),
Path(self.addon.path_location, "Dockerfile").is_file(),
]
)
def get_docker_args(self, version: AwesomeVersion):
"""Create a dict with Docker build arguments."""
args = {

View File

@@ -1,6 +1,5 @@
"""Init file for Supervisor add-on data."""
from copy import deepcopy
import logging
from typing import Any, Dict
from ..const import (
@@ -13,16 +12,14 @@ from ..const import (
)
from ..coresys import CoreSys, CoreSysAttributes
from ..store.addon import AddonStore
from ..utils.json import JsonConfig
from ..utils.common import FileConfiguration
from .addon import Addon
from .validate import SCHEMA_ADDONS_FILE
_LOGGER: logging.Logger = logging.getLogger(__name__)
Config = Dict[str, Any]
class AddonsData(JsonConfig, CoreSysAttributes):
class AddonsData(FileConfiguration, CoreSysAttributes):
"""Hold data for installed Add-ons inside Supervisor."""
def __init__(self, coresys: CoreSys):

View File

@@ -12,7 +12,6 @@ from ..const import (
ATTR_ARCH,
ATTR_AUDIO,
ATTR_AUTH_API,
ATTR_AUTO_UART,
ATTR_BOOT,
ATTR_DESCRIPTON,
ATTR_DEVICES,
@@ -46,6 +45,7 @@ from ..const import (
ATTR_PORTS,
ATTR_PORTS_DESCRIPTION,
ATTR_PRIVILEGED,
ATTR_REALTIME,
ATTR_REPOSITORY,
ATTR_SCHEMA,
ATTR_SERVICES,
@@ -56,6 +56,8 @@ from ..const import (
ATTR_STDIN,
ATTR_TIMEOUT,
ATTR_TMPFS,
ATTR_TRANSLATIONS,
ATTR_UART,
ATTR_UDEV,
ATTR_URL,
ATTR_USB,
@@ -71,7 +73,9 @@ from ..const import (
AddonStartup,
)
from ..coresys import CoreSys, CoreSysAttributes
from .validate import RE_SERVICE, RE_VOLUME, schema_ui_options, validate_options
from ..docker.const import Capabilities
from .options import AddonOptions, UiOptions
from .validate import RE_SERVICE, RE_VOLUME
Data = Dict[str, Any]
@@ -182,6 +186,11 @@ class AddonModel(CoreSysAttributes, ABC):
"""Return repository of add-on."""
return self.data[ATTR_REPOSITORY]
@property
def translations(self) -> dict:
"""Return add-on translations."""
return self.data[ATTR_TRANSLATIONS]
@property
def latest_version(self) -> AwesomeVersion:
"""Return latest version of add-on."""
@@ -296,14 +305,9 @@ class AddonModel(CoreSysAttributes, ABC):
return self.data[ATTR_HOST_DBUS]
@property
def devices(self) -> List[str]:
"""Return devices of add-on."""
return self.data.get(ATTR_DEVICES, [])
@property
def tmpfs(self) -> Optional[str]:
"""Return tmpfs of add-on."""
return self.data.get(ATTR_TMPFS)
def static_devices(self) -> List[Path]:
"""Return static devices of add-on."""
return [Path(node) for node in self.data.get(ATTR_DEVICES, [])]
@property
def environment(self) -> Optional[Dict[str, str]]:
@@ -311,7 +315,7 @@ class AddonModel(CoreSysAttributes, ABC):
return self.data.get(ATTR_ENVIRONMENT)
@property
def privileged(self) -> List[str]:
def privileged(self) -> List[Capabilities]:
"""Return list of privilege."""
return self.data.get(ATTR_PRIVILEGED, [])
@@ -387,7 +391,7 @@ class AddonModel(CoreSysAttributes, ABC):
@property
def with_uart(self) -> bool:
"""Return True if we should map all UART device."""
return self.data[ATTR_AUTO_UART]
return self.data[ATTR_UART]
@property
def with_udev(self) -> bool:
@@ -399,6 +403,11 @@ class AddonModel(CoreSysAttributes, ABC):
"""Return True if the add-on access to 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
def with_full_access(self) -> bool:
"""Return True if the add-on want full access to hardware."""
@@ -409,6 +418,11 @@ class AddonModel(CoreSysAttributes, ABC):
"""Return True if the add-on read access to devicetree."""
return self.data[ATTR_DEVICETREE]
@property
def with_tmpfs(self) -> Optional[str]:
"""Return if tmp is in memory of add-on."""
return self.data[ATTR_TMPFS]
@property
def access_auth_api(self) -> bool:
"""Return True if the add-on access to login/auth backend."""
@@ -522,8 +536,10 @@ class AddonModel(CoreSysAttributes, ABC):
raw_schema = self.data[ATTR_SCHEMA]
if isinstance(raw_schema, bool):
return vol.Schema(dict)
return vol.Schema(vol.All(dict, validate_options(self.coresys, raw_schema)))
raw_schema = {}
return vol.Schema(
vol.All(dict, AddonOptions(self.coresys, raw_schema, self.name, self.slug))
)
@property
def schema_ui(self) -> Optional[List[Dict[str, Any]]]:
@@ -532,7 +548,7 @@ class AddonModel(CoreSysAttributes, ABC):
if isinstance(raw_schema, bool):
return None
return schema_ui_options(raw_schema)
return UiOptions(self.coresys)(raw_schema)
def __eq__(self, other):
"""Compaired add-on objects."""

View File

@@ -0,0 +1,410 @@
"""Add-on Options / UI rendering."""
import hashlib
import logging
from pathlib import Path
import re
from typing import Any, Dict, List, Set, Union
import voluptuous as vol
from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import HardwareNotFound
from ..hardware.const import UdevSubsystem
from ..hardware.data import Device
from ..validate import network_port
_LOGGER: logging.Logger = logging.getLogger(__name__)
_STR = "str"
_INT = "int"
_FLOAT = "float"
_BOOL = "bool"
_PASSWORD = "password"
_EMAIL = "email"
_URL = "url"
_PORT = "port"
_MATCH = "match"
_LIST = "list"
_DEVICE = "device"
RE_SCHEMA_ELEMENT = re.compile(
r"^(?:"
r"|bool"
r"|email"
r"|url"
r"|port"
r"|device(?:\((?P<filter>subsystem=[a-z]+)\))?"
r"|str(?:\((?P<s_min>\d+)?,(?P<s_max>\d+)?\))?"
r"|password(?:\((?P<p_min>\d+)?,(?P<p_max>\d+)?\))?"
r"|int(?:\((?P<i_min>\d+)?,(?P<i_max>\d+)?\))?"
r"|float(?:\((?P<f_min>[\d\.]+)?,(?P<f_max>[\d\.]+)?\))?"
r"|match\((?P<match>.*)\)"
r"|list\((?P<list>.+)\)"
r")\??$"
)
_SCHEMA_LENGTH_PARTS = (
"i_min",
"i_max",
"f_min",
"f_max",
"s_min",
"s_max",
"p_min",
"p_max",
)
class AddonOptions(CoreSysAttributes):
"""Validate Add-ons Options."""
def __init__(
self, coresys: CoreSys, raw_schema: Dict[str, Any], name: str, slug: str
):
"""Validate schema."""
self.coresys: CoreSys = coresys
self.raw_schema: Dict[str, Any] = raw_schema
self.devices: Set[Device] = set()
self.pwned: Set[str] = set()
self._name = name
self._slug = slug
def __call__(self, struct):
"""Create schema validator for add-ons options."""
options = {}
# read options
for key, value in struct.items():
# Ignore unknown options / remove from list
if key not in self.raw_schema:
_LOGGER.warning(
"Option '%s' does not exist in the schema for %s (%s)",
key,
self._name,
self._slug,
)
continue
typ = self.raw_schema[key]
try:
if isinstance(typ, list):
# nested value list
options[key] = self._nested_validate_list(typ[0], value, key)
elif isinstance(typ, dict):
# nested value dict
options[key] = self._nested_validate_dict(typ, value, key)
else:
# normal value
options[key] = self._single_validate(typ, value, key)
except (IndexError, KeyError):
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")
return options
# pylint: disable=no-value-for-parameter
def _single_validate(self, typ: str, value: Any, key: str):
"""Validate a single element."""
# if required argument
if value is None:
raise vol.Invalid(
f"Missing required option '{key}' in {self._name} ({self._slug})"
) from None
# Lookup secret
if str(value).startswith("!secret "):
secret: str = value.partition(" ")[2]
value = self.sys_homeassistant.secrets.get(secret)
if value is None:
raise vol.Invalid(
f"Unknown secret '{secret}' in {self._name} ({self._slug})"
) from None
# parse extend data from type
match = RE_SCHEMA_ELEMENT.match(typ)
if not match:
raise vol.Invalid(
f"Unknown type '{typ}' in {self._name} ({self._slug})"
) from None
# prepare range
range_args = {}
for group_name in _SCHEMA_LENGTH_PARTS:
group_value = match.group(group_name)
if group_value:
range_args[group_name[2:]] = float(group_value)
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)
elif typ.startswith(_INT):
return vol.All(vol.Coerce(int), vol.Range(**range_args))(value)
elif typ.startswith(_FLOAT):
return vol.All(vol.Coerce(float), vol.Range(**range_args))(value)
elif typ.startswith(_BOOL):
return vol.Boolean()(value)
elif typ.startswith(_EMAIL):
return vol.Email()(value)
elif typ.startswith(_URL):
return vol.Url()(value)
elif typ.startswith(_PORT):
return network_port(value)
elif typ.startswith(_MATCH):
return vol.Match(match.group("match"))(str(value))
elif typ.startswith(_LIST):
return vol.In(match.group("list").split("|"))(str(value))
elif typ.startswith(_DEVICE):
try:
device = self.sys_hardware.get_by_path(Path(value))
except HardwareNotFound:
raise vol.Invalid(
f"Device '{value}' does not exists! in {self._name} ({self._slug})"
) from None
# Have filter
if match.group("filter"):
str_filter = match.group("filter")
device_filter = _create_device_filter(str_filter)
if device not in self.sys_hardware.filter_devices(**device_filter):
raise vol.Invalid(
f"Device '{value}' don't match the filter {str_filter}! in {self._name} ({self._slug})"
)
# Device valid
self.devices.add(device)
return str(device.path)
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):
"""Validate nested items."""
options = []
# Make sure it is a list
if not isinstance(data_list, list):
raise vol.Invalid(
f"Invalid list for option '{key}' in {self._name} ({self._slug})"
) from None
# Process list
for element in data_list:
# Nested?
if isinstance(typ, dict):
c_options = self._nested_validate_dict(typ, element, key)
options.append(c_options)
else:
options.append(self._single_validate(typ, element, key))
return options
def _nested_validate_dict(
self, typ: Dict[Any, Any], data_dict: Dict[Any, Any], key: str
):
"""Validate nested items."""
options = {}
# Make sure it is a dict
if not isinstance(data_dict, dict):
raise vol.Invalid(
f"Invalid dict for option '{key}' in {self._name} ({self._slug})"
) from None
# Process dict
for c_key, c_value in data_dict.items():
# Ignore unknown options / remove from list
if c_key not in typ:
_LOGGER.warning(
"Unknown option '%s' for %s (%s)", c_key, self._name, self._slug
)
continue
# Nested?
if isinstance(typ[c_key], list):
options[c_key] = self._nested_validate_list(
typ[c_key][0], c_value, c_key
)
else:
options[c_key] = self._single_validate(typ[c_key], c_value, c_key)
self._check_missing_options(typ, options, key)
return options
def _check_missing_options(
self, origin: Dict[Any, Any], exists: Dict[Any, Any], root: str
) -> None:
"""Check if all options are exists."""
missing = set(origin) - set(exists)
for miss_opt in missing:
if isinstance(origin[miss_opt], str) and origin[miss_opt].endswith("?"):
continue
raise vol.Invalid(
f"Missing option '{miss_opt}' in {root} in {self._name} ({self._slug})"
) from None
class UiOptions(CoreSysAttributes):
"""Render UI Add-ons Options."""
def __init__(self, coresys: CoreSys) -> None:
"""Initialize UI option render."""
self.coresys = coresys
def __call__(self, raw_schema: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate UI schema."""
ui_schema: List[Dict[str, Any]] = []
# read options
for key, value in raw_schema.items():
if isinstance(value, list):
# nested value list
self._nested_ui_list(ui_schema, value, key)
elif isinstance(value, dict):
# nested value dict
self._nested_ui_dict(ui_schema, value, key)
else:
# normal value
self._single_ui_option(ui_schema, value, key)
return ui_schema
def _single_ui_option(
self,
ui_schema: List[Dict[str, Any]],
value: str,
key: str,
multiple: bool = False,
) -> None:
"""Validate a single element."""
ui_node: Dict[str, Union[str, bool, float, List[str]]] = {"name": key}
# If multiple
if multiple:
ui_node["multiple"] = True
# Parse extend data from type
match = RE_SCHEMA_ELEMENT.match(value)
if not match:
return
# Prepare range
for group_name in _SCHEMA_LENGTH_PARTS:
group_value = match.group(group_name)
if not group_value:
continue
if group_name[2:] == "min":
ui_node["lengthMin"] = float(group_value)
elif group_name[2:] == "max":
ui_node["lengthMax"] = float(group_value)
# If required
if value.endswith("?"):
ui_node["optional"] = True
else:
ui_node["required"] = True
# Data types
if value.startswith(_STR):
ui_node["type"] = "string"
elif value.startswith(_PASSWORD):
ui_node["type"] = "string"
ui_node["format"] = "password"
elif value.startswith(_INT):
ui_node["type"] = "integer"
elif value.startswith(_FLOAT):
ui_node["type"] = "float"
elif value.startswith(_BOOL):
ui_node["type"] = "boolean"
elif value.startswith(_EMAIL):
ui_node["type"] = "string"
ui_node["format"] = "email"
elif value.startswith(_URL):
ui_node["type"] = "string"
ui_node["format"] = "url"
elif value.startswith(_PORT):
ui_node["type"] = "integer"
elif value.startswith(_MATCH):
ui_node["type"] = "string"
elif value.startswith(_LIST):
ui_node["type"] = "select"
ui_node["options"] = match.group("list").split("|")
elif value.startswith(_DEVICE):
ui_node["type"] = "select"
# Have filter
if match.group("filter"):
device_filter = _create_device_filter(match.group("filter"))
ui_node["options"] = [
(device.by_id or device.path).as_posix()
for device in self.sys_hardware.filter_devices(**device_filter)
]
else:
ui_node["options"] = [
(device.by_id or device.path).as_posix()
for device in self.sys_hardware.devices
]
ui_schema.append(ui_node)
def _nested_ui_list(
self,
ui_schema: List[Dict[str, Any]],
option_list: List[Any],
key: str,
) -> None:
"""UI nested list items."""
try:
element = option_list[0]
except IndexError:
_LOGGER.error("Invalid schema %s", key)
return
if isinstance(element, dict):
self._nested_ui_dict(ui_schema, element, key, multiple=True)
else:
self._single_ui_option(ui_schema, element, key, multiple=True)
def _nested_ui_dict(
self,
ui_schema: List[Dict[str, Any]],
option_dict: Dict[str, Any],
key: str,
multiple: bool = False,
) -> None:
"""UI nested dict items."""
ui_node = {
"name": key,
"type": "schema",
"optional": True,
"multiple": multiple,
}
nested_schema = []
for c_key, c_value in option_dict.items():
# Nested?
if isinstance(c_value, list):
self._nested_ui_list(nested_schema, c_value, c_key)
else:
self._single_ui_option(nested_schema, c_value, c_key)
ui_node["schema"] = nested_schema
ui_schema.append(ui_node)
def _create_device_filter(str_filter: str) -> Dict[str, Any]:
"""Generate device Filter."""
raw_filter = dict(value.split("=") for value in str_filter.split(";"))
clean_filter = {}
for key, value in raw_filter.items():
if key == "subsystem":
clean_filter[key] = UdevSubsystem(value)
else:
clean_filter[key] = value
return clean_filter

View File

@@ -6,18 +6,8 @@ import logging
from pathlib import Path
from typing import TYPE_CHECKING
from ..const import (
PRIVILEGED_DAC_READ_SEARCH,
PRIVILEGED_NET_ADMIN,
PRIVILEGED_SYS_ADMIN,
PRIVILEGED_SYS_MODULE,
PRIVILEGED_SYS_PTRACE,
PRIVILEGED_SYS_RAWIO,
ROLE_ADMIN,
ROLE_MANAGER,
SECURITY_DISABLE,
SECURITY_PROFILE,
)
from ..const import ROLE_ADMIN, ROLE_MANAGER, SECURITY_DISABLE, SECURITY_PROFILE
from ..docker.const import Capabilities
if TYPE_CHECKING:
from .model import AddonModel
@@ -46,16 +36,19 @@ def rating_security(addon: AddonModel) -> int:
rating += 1
# Privileged options
if any(
privilege in addon.privileged
for privilege in (
PRIVILEGED_NET_ADMIN,
PRIVILEGED_SYS_ADMIN,
PRIVILEGED_SYS_RAWIO,
PRIVILEGED_SYS_PTRACE,
PRIVILEGED_SYS_MODULE,
PRIVILEGED_DAC_READ_SEARCH,
if (
any(
privilege in addon.privileged
for privilege in (
Capabilities.NET_ADMIN,
Capabilities.SYS_ADMIN,
Capabilities.SYS_RAWIO,
Capabilities.SYS_PTRACE,
Capabilities.SYS_MODULE,
Capabilities.DAC_READ_SEARCH,
)
)
or addon.with_kernel_modules
):
rating += -1
@@ -73,12 +66,8 @@ def rating_security(addon: AddonModel) -> int:
if addon.host_pid:
rating += -2
# Full Access
if addon.with_full_access:
rating += -2
# Docker Access
if addon.access_docker_api:
# Docker Access & full Access
if addon.access_docker_api or addon.with_full_access:
rating = 1
return max(min(6, rating), 1)

View File

@@ -2,7 +2,7 @@
import logging
import re
import secrets
from typing import Any, Dict, List, Union
from typing import Any, Dict
import uuid
import voluptuous as vol
@@ -18,10 +18,10 @@ from ..const import (
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_AUTH_API,
ATTR_AUTO_UART,
ATTR_AUTO_UPDATE,
ATTR_BOOT,
ATTR_BUILD_FROM,
ATTR_CONFIGURATION,
ATTR_DESCRIPTON,
ATTR_DEVICES,
ATTR_DEVICETREE,
@@ -60,6 +60,7 @@ from ..const import (
ATTR_PORTS_DESCRIPTION,
ATTR_PRIVILEGED,
ATTR_PROTECTED,
ATTR_REALTIME,
ATTR_REPOSITORY,
ATTR_SCHEMA,
ATTR_SERVICES,
@@ -73,6 +74,8 @@ from ..const import (
ATTR_SYSTEM,
ATTR_TIMEOUT,
ATTR_TMPFS,
ATTR_TRANSLATIONS,
ATTR_UART,
ATTR_UDEV,
ATTR_URL,
ATTR_USB,
@@ -82,7 +85,6 @@ from ..const import (
ATTR_VIDEO,
ATTR_WATCHDOG,
ATTR_WEBUI,
PRIVILEGED_ALL,
ROLE_ALL,
ROLE_DEFAULT,
AddonBoot,
@@ -90,8 +92,8 @@ from ..const import (
AddonStartup,
AddonState,
)
from ..coresys import CoreSys
from ..discovery.validate import valid_discovery_service
from ..docker.const import Capabilities
from ..validate import (
docker_image,
docker_ports,
@@ -101,49 +103,13 @@ from ..validate import (
uuid_match,
version_tag,
)
from .options import RE_SCHEMA_ELEMENT
_LOGGER: logging.Logger = logging.getLogger(__name__)
RE_VOLUME = re.compile(r"^(config|ssl|addons|backup|share|media)(?::(rw|ro))?$")
RE_SERVICE = re.compile(r"^(?P<service>mqtt|mysql):(?P<rights>provide|want|need)$")
V_STR = "str"
V_INT = "int"
V_FLOAT = "float"
V_BOOL = "bool"
V_PASSWORD = "password"
V_EMAIL = "email"
V_URL = "url"
V_PORT = "port"
V_MATCH = "match"
V_LIST = "list"
RE_SCHEMA_ELEMENT = re.compile(
r"^(?:"
r"|bool"
r"|email"
r"|url"
r"|port"
r"|str(?:\((?P<s_min>\d+)?,(?P<s_max>\d+)?\))?"
r"|password(?:\((?P<p_min>\d+)?,(?P<p_max>\d+)?\))?"
r"|int(?:\((?P<i_min>\d+)?,(?P<i_max>\d+)?\))?"
r"|float(?:\((?P<f_min>[\d\.]+)?,(?P<f_max>[\d\.]+)?\))?"
r"|match\((?P<match>.*)\)"
r"|list\((?P<list>.+)\)"
r")\??$"
)
_SCHEMA_LENGTH_PARTS = (
"i_min",
"i_max",
"f_min",
"f_max",
"s_min",
"s_max",
"p_min",
"p_max",
)
RE_DOCKER_IMAGE_BUILD = re.compile(
r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)(:[\.\-\w{}]+)?$"
@@ -173,27 +139,92 @@ RE_MACHINE = re.compile(
)
def _simple_startup(value) -> str:
"""Define startup schema."""
if value == "before":
return AddonStartup.SERVICES.value
if value == "after":
return AddonStartup.APPLICATION.value
return value
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):
"""Migrate addon config."""
def _migrate(config: Dict[str, Any]):
name = config.get(ATTR_NAME)
if not name:
raise vol.Invalid("Invalid Add-on config!")
# Startup 2018-03-30
if config.get(ATTR_STARTUP) in ("before", "after"):
value = config[ATTR_STARTUP]
if protocol:
_LOGGER.warning(
"Add-on config 'startup' with '%s' is deprecated. Please report this to the maintainer of %s",
value,
name,
)
if value == "before":
config[ATTR_STARTUP] = AddonStartup.SERVICES.value
elif value == "after":
config[ATTR_STARTUP] = AddonStartup.APPLICATION.value
# UART 2021-01-20
if "auto_uart" in config:
if protocol:
_LOGGER.warning(
"Add-on config 'auto_uart' is deprecated, use 'uart'. Please report this to the maintainer of %s",
name,
)
config[ATTR_UART] = config.pop("auto_uart")
# Device 2021-01-20
if ATTR_DEVICES in config and any(":" in line for line in config[ATTR_DEVICES]):
if protocol:
_LOGGER.warning(
"Add-on config 'devices' use a deprecated format, the new format uses a list of paths only. Please report this to the maintainer of %s",
name,
)
config[ATTR_DEVICES] = [line.split(":")[0] for line in config[ATTR_DEVICES]]
# TMPFS 2021-02-01
if ATTR_TMPFS in config and not isinstance(config[ATTR_TMPFS], bool):
if protocol:
_LOGGER.warning(
"Add-on config 'tmpfs' use a deprecated format, new it's only a boolean. Please report this to the maintainer of %s",
name,
)
config[ATTR_TMPFS] = True
return config
return _migrate
# pylint: disable=no-value-for-parameter
SCHEMA_ADDON_CONFIG = vol.Schema(
_SCHEMA_ADDON_CONFIG = vol.Schema(
{
vol.Required(ATTR_NAME): vol.Coerce(str),
vol.Required(ATTR_NAME): str,
vol.Required(ATTR_VERSION): version_tag,
vol.Required(ATTR_SLUG): vol.Coerce(str),
vol.Required(ATTR_DESCRIPTON): vol.Coerce(str),
vol.Required(ATTR_SLUG): str,
vol.Required(ATTR_DESCRIPTON): str,
vol.Required(ATTR_ARCH): [vol.In(ARCH_ALL)],
vol.Optional(ATTR_MACHINE): vol.All([vol.Match(RE_MACHINE)], vol.Unique()),
vol.Optional(ATTR_URL): vol.Url(),
vol.Optional(ATTR_STARTUP, default=AddonStartup.APPLICATION): vol.All(
_simple_startup, vol.Coerce(AddonStartup)
vol.Optional(ATTR_STARTUP, default=AddonStartup.APPLICATION): vol.Coerce(
AddonStartup
),
vol.Optional(ATTR_BOOT, default=AddonBoot.AUTO): vol.Coerce(AddonBoot),
vol.Optional(ATTR_INIT, default=True): vol.Boolean(),
@@ -202,7 +233,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema(
vol.Optional(ATTR_PORTS): docker_ports,
vol.Optional(ATTR_PORTS_DESCRIPTION): docker_ports_description,
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(
r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"
@@ -211,30 +242,31 @@ SCHEMA_ADDON_CONFIG = vol.Schema(
vol.Optional(ATTR_INGRESS_PORT, default=8099): vol.Any(
network_port, vol.Equal(0)
),
vol.Optional(ATTR_INGRESS_ENTRY): vol.Coerce(str),
vol.Optional(ATTR_PANEL_ICON, default="mdi:puzzle"): vol.Coerce(str),
vol.Optional(ATTR_PANEL_TITLE): vol.Coerce(str),
vol.Optional(ATTR_INGRESS_ENTRY): str,
vol.Optional(ATTR_PANEL_ICON, default="mdi:puzzle"): str,
vol.Optional(ATTR_PANEL_TITLE): str,
vol.Optional(ATTR_PANEL_ADMIN, default=True): vol.Boolean(),
vol.Optional(ATTR_HOMEASSISTANT): vol.Maybe(version_tag),
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
vol.Optional(ATTR_HOST_PID, default=False): vol.Boolean(),
vol.Optional(ATTR_HOST_IPC, default=False): vol.Boolean(),
vol.Optional(ATTR_HOST_DBUS, default=False): vol.Boolean(),
vol.Optional(ATTR_DEVICES): [vol.Match(r"^(.*):(.*):([rwm]{1,3})$")],
vol.Optional(ATTR_AUTO_UART, default=False): vol.Boolean(),
vol.Optional(ATTR_DEVICES): [str],
vol.Optional(ATTR_UDEV, default=False): vol.Boolean(),
vol.Optional(ATTR_TMPFS): vol.Match(r"^size=(\d)*[kmg](,uid=\d{1,4})?(,rw)?$"),
vol.Optional(ATTR_TMPFS, default=False): vol.Boolean(),
vol.Optional(ATTR_MAP, default=list): [vol.Match(RE_VOLUME)],
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)},
vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)],
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): str},
vol.Optional(ATTR_PRIVILEGED): [vol.Coerce(Capabilities)],
vol.Optional(ATTR_APPARMOR, default=True): vol.Boolean(),
vol.Optional(ATTR_FULL_ACCESS, default=False): vol.Boolean(),
vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(),
vol.Optional(ATTR_VIDEO, default=False): vol.Boolean(),
vol.Optional(ATTR_GPIO, default=False): vol.Boolean(),
vol.Optional(ATTR_USB, default=False): vol.Boolean(),
vol.Optional(ATTR_UART, default=False): vol.Boolean(),
vol.Optional(ATTR_DEVICETREE, 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_ROLE, default=ROLE_DEFAULT): vol.In(ROLE_ALL),
vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(),
@@ -244,26 +276,20 @@ SCHEMA_ADDON_CONFIG = vol.Schema(
vol.Optional(ATTR_AUTH_API, default=False): vol.Boolean(),
vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)],
vol.Optional(ATTR_DISCOVERY): [valid_discovery_service],
vol.Optional(ATTR_SNAPSHOT_EXCLUDE): [vol.Coerce(str)],
vol.Optional(ATTR_SNAPSHOT_EXCLUDE): [str],
vol.Optional(ATTR_OPTIONS, default={}): dict,
vol.Optional(ATTR_SCHEMA, default={}): vol.Any(
vol.Schema(
{
vol.Coerce(str): vol.Any(
str: vol.Any(
SCHEMA_ELEMENT,
[
vol.Any(
SCHEMA_ELEMENT,
{
vol.Coerce(str): vol.Any(
SCHEMA_ELEMENT, [SCHEMA_ELEMENT]
)
},
{str: vol.Any(SCHEMA_ELEMENT, [SCHEMA_ELEMENT])},
)
],
vol.Schema(
{vol.Coerce(str): vol.Any(SCHEMA_ELEMENT, [SCHEMA_ELEMENT])}
),
vol.Schema({str: vol.Any(SCHEMA_ELEMENT, [SCHEMA_ELEMENT])}),
)
}
),
@@ -277,6 +303,10 @@ SCHEMA_ADDON_CONFIG = vol.Schema(
extra=vol.REMOVE_EXTRA,
)
SCHEMA_ADDON_CONFIG = vol.All(
_migrate_addon_config(True), _warn_addon_config, _SCHEMA_ADDON_CONFIG
)
# pylint: disable=no-value-for-parameter
SCHEMA_BUILD_CONFIG = vol.Schema(
@@ -292,6 +322,23 @@ SCHEMA_BUILD_CONFIG = vol.Schema(
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
SCHEMA_ADDON_USER = vol.Schema(
@@ -300,15 +347,13 @@ SCHEMA_ADDON_USER = vol.Schema(
vol.Optional(ATTR_IMAGE): docker_image,
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): uuid_match,
vol.Optional(ATTR_ACCESS_TOKEN): token,
vol.Optional(ATTR_INGRESS_TOKEN, default=secrets.token_urlsafe): vol.Coerce(
str
),
vol.Optional(ATTR_INGRESS_TOKEN, default=secrets.token_urlsafe): str,
vol.Optional(ATTR_OPTIONS, default=dict): dict,
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
vol.Optional(ATTR_BOOT): vol.Coerce(AddonBoot),
vol.Optional(ATTR_NETWORK): docker_ports,
vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(str),
vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(str),
vol.Optional(ATTR_PROTECTED, default=True): vol.Boolean(),
vol.Optional(ATTR_INGRESS_PANEL, default=False): vol.Boolean(),
vol.Optional(ATTR_WATCHDOG, default=False): vol.Boolean(),
@@ -316,20 +361,26 @@ SCHEMA_ADDON_USER = vol.Schema(
extra=vol.REMOVE_EXTRA,
)
SCHEMA_ADDON_SYSTEM = SCHEMA_ADDON_CONFIG.extend(
{
vol.Required(ATTR_LOCATON): vol.Coerce(str),
vol.Required(ATTR_REPOSITORY): vol.Coerce(str),
}
SCHEMA_ADDON_SYSTEM = vol.All(
_migrate_addon_config(),
_SCHEMA_ADDON_CONFIG.extend(
{
vol.Required(ATTR_LOCATON): str,
vol.Required(ATTR_REPOSITORY): str,
vol.Required(ATTR_TRANSLATIONS, default=dict): {
str: SCHEMA_ADDON_TRANSLATIONS
},
}
),
)
SCHEMA_ADDONS_FILE = vol.Schema(
{
vol.Optional(ATTR_USER, default=dict): {vol.Coerce(str): SCHEMA_ADDON_USER},
vol.Optional(ATTR_SYSTEM, default=dict): {vol.Coerce(str): SCHEMA_ADDON_SYSTEM},
}
vol.Optional(ATTR_USER, default=dict): {str: SCHEMA_ADDON_USER},
vol.Optional(ATTR_SYSTEM, default=dict): {str: SCHEMA_ADDON_SYSTEM},
},
extra=vol.REMOVE_EXTRA,
)
@@ -338,263 +389,7 @@ SCHEMA_ADDON_SNAPSHOT = vol.Schema(
vol.Required(ATTR_USER): SCHEMA_ADDON_USER,
vol.Required(ATTR_SYSTEM): SCHEMA_ADDON_SYSTEM,
vol.Required(ATTR_STATE): vol.Coerce(AddonState),
vol.Required(ATTR_VERSION): vol.Coerce(str),
vol.Required(ATTR_VERSION): version_tag,
},
extra=vol.REMOVE_EXTRA,
)
def validate_options(coresys: CoreSys, raw_schema: Dict[str, Any]):
"""Validate schema."""
def validate(struct):
"""Create schema validator for add-ons options."""
options = {}
# read options
for key, value in struct.items():
# Ignore unknown options / remove from list
if key not in raw_schema:
_LOGGER.warning("Unknown options %s", key)
continue
typ = raw_schema[key]
try:
if isinstance(typ, list):
# nested value list
options[key] = _nested_validate_list(coresys, typ[0], value, key)
elif isinstance(typ, dict):
# nested value dict
options[key] = _nested_validate_dict(coresys, typ, value, key)
else:
# normal value
options[key] = _single_validate(coresys, typ, value, key)
except (IndexError, KeyError):
raise vol.Invalid(f"Type error for {key}") from None
_check_missing_options(raw_schema, options, "root")
return options
return validate
# pylint: disable=no-value-for-parameter
# pylint: disable=inconsistent-return-statements
def _single_validate(coresys: CoreSys, typ: str, value: Any, key: str):
"""Validate a single element."""
# if required argument
if value is None:
raise vol.Invalid(f"Missing required option '{key}'") from None
# Lookup secret
if str(value).startswith("!secret "):
secret: str = value.partition(" ")[2]
value = coresys.homeassistant.secrets.get(secret)
if value is None:
raise vol.Invalid(f"Unknown secret {secret}") from None
# parse extend data from type
match = RE_SCHEMA_ELEMENT.match(typ)
if not match:
raise vol.Invalid(f"Unknown type {typ}") from None
# prepare range
range_args = {}
for group_name in _SCHEMA_LENGTH_PARTS:
group_value = match.group(group_name)
if group_value:
range_args[group_name[2:]] = float(group_value)
if typ.startswith(V_STR) or typ.startswith(V_PASSWORD):
return vol.All(str(value), vol.Range(**range_args))(value)
elif typ.startswith(V_INT):
return vol.All(vol.Coerce(int), vol.Range(**range_args))(value)
elif typ.startswith(V_FLOAT):
return vol.All(vol.Coerce(float), vol.Range(**range_args))(value)
elif typ.startswith(V_BOOL):
return vol.Boolean()(value)
elif typ.startswith(V_EMAIL):
return vol.Email()(value)
elif typ.startswith(V_URL):
return vol.Url()(value)
elif typ.startswith(V_PORT):
return network_port(value)
elif typ.startswith(V_MATCH):
return vol.Match(match.group("match"))(str(value))
elif typ.startswith(V_LIST):
return vol.In(match.group("list").split("|"))(str(value))
raise vol.Invalid(f"Fatal error for {key} type {typ}") from None
def _nested_validate_list(coresys, typ, data_list, key):
"""Validate nested items."""
options = []
# Make sure it is a list
if not isinstance(data_list, list):
raise vol.Invalid(f"Invalid list for {key}") from None
# Process list
for element in data_list:
# Nested?
if isinstance(typ, dict):
c_options = _nested_validate_dict(coresys, typ, element, key)
options.append(c_options)
else:
options.append(_single_validate(coresys, typ, element, key))
return options
def _nested_validate_dict(coresys, typ, data_dict, key):
"""Validate nested items."""
options = {}
# Make sure it is a dict
if not isinstance(data_dict, dict):
raise vol.Invalid(f"Invalid dict for {key}") from None
# Process dict
for c_key, c_value in data_dict.items():
# Ignore unknown options / remove from list
if c_key not in typ:
_LOGGER.warning("Unknown options %s", c_key)
continue
# Nested?
if isinstance(typ[c_key], list):
options[c_key] = _nested_validate_list(
coresys, typ[c_key][0], c_value, c_key
)
else:
options[c_key] = _single_validate(coresys, typ[c_key], c_value, c_key)
_check_missing_options(typ, options, key)
return options
def _check_missing_options(origin, exists, root):
"""Check if all options are exists."""
missing = set(origin) - set(exists)
for miss_opt in missing:
if isinstance(origin[miss_opt], str) and origin[miss_opt].endswith("?"):
continue
raise vol.Invalid(f"Missing option {miss_opt} in {root}") from None
def schema_ui_options(raw_schema: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate UI schema."""
ui_schema: List[Dict[str, Any]] = []
# read options
for key, value in raw_schema.items():
if isinstance(value, list):
# nested value list
_nested_ui_list(ui_schema, value, key)
elif isinstance(value, dict):
# nested value dict
_nested_ui_dict(ui_schema, value, key)
else:
# normal value
_single_ui_option(ui_schema, value, key)
return ui_schema
def _single_ui_option(
ui_schema: List[Dict[str, Any]], value: str, key: str, multiple: bool = False
) -> None:
"""Validate a single element."""
ui_node: Dict[str, Union[str, bool, float, List[str]]] = {"name": key}
# If multiple
if multiple:
ui_node["multiple"] = True
# Parse extend data from type
match = RE_SCHEMA_ELEMENT.match(value)
if not match:
return
# Prepare range
for group_name in _SCHEMA_LENGTH_PARTS:
group_value = match.group(group_name)
if not group_value:
continue
if group_name[2:] == "min":
ui_node["lengthMin"] = float(group_value)
elif group_name[2:] == "max":
ui_node["lengthMax"] = float(group_value)
# If required
if value.endswith("?"):
ui_node["optional"] = True
else:
ui_node["required"] = True
# Data types
if value.startswith(V_STR):
ui_node["type"] = "string"
elif value.startswith(V_PASSWORD):
ui_node["type"] = "string"
ui_node["format"] = "password"
elif value.startswith(V_INT):
ui_node["type"] = "integer"
elif value.startswith(V_FLOAT):
ui_node["type"] = "float"
elif value.startswith(V_BOOL):
ui_node["type"] = "boolean"
elif value.startswith(V_EMAIL):
ui_node["type"] = "string"
ui_node["format"] = "email"
elif value.startswith(V_URL):
ui_node["type"] = "string"
ui_node["format"] = "url"
elif value.startswith(V_PORT):
ui_node["type"] = "integer"
elif value.startswith(V_MATCH):
ui_node["type"] = "string"
elif value.startswith(V_LIST):
ui_node["type"] = "select"
ui_node["options"] = match.group("list").split("|")
ui_schema.append(ui_node)
def _nested_ui_list(
ui_schema: List[Dict[str, Any]], option_list: List[Any], key: str
) -> None:
"""UI nested list items."""
try:
element = option_list[0]
except IndexError:
_LOGGER.error("Invalid schema %s", key)
return
if isinstance(element, dict):
_nested_ui_dict(ui_schema, element, key, multiple=True)
else:
_single_ui_option(ui_schema, element, key, multiple=True)
def _nested_ui_dict(
ui_schema: List[Dict[str, Any]],
option_dict: Dict[str, Any],
key: str,
multiple: bool = False,
) -> None:
"""UI nested dict items."""
ui_node = {"name": key, "type": "schema", "optional": True, "multiple": multiple}
nested_schema = []
for c_key, c_value in option_dict.items():
# Nested?
if isinstance(c_value, list):
_nested_ui_list(nested_schema, c_value, c_key)
else:
_single_ui_option(nested_schema, c_value, c_key)
ui_node["schema"] = nested_schema
ui_schema.append(ui_node)

View File

@@ -28,6 +28,7 @@ from .resolution import APIResoulution
from .security import SecurityMiddleware
from .services import APIServices
from .snapshots import APISnapshots
from .store import APIStore
from .supervisor import APISupervisor
_LOGGER: logging.Logger = logging.getLogger(__name__)
@@ -80,6 +81,7 @@ class RestAPI(CoreSysAttributes):
self._register_services()
self._register_snapshots()
self._register_supervisor()
self._register_store()
await self.start()
@@ -238,6 +240,7 @@ class RestAPI(CoreSysAttributes):
"/resolution/issue/{issue}",
api_resolution.dismiss_issue,
),
web.post("/resolution/healthcheck", api_resolution.healthcheck),
]
)
@@ -338,16 +341,15 @@ class RestAPI(CoreSysAttributes):
web.get("/addons", api_addons.list),
web.post("/addons/reload", api_addons.reload),
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}/start", api_addons.start),
web.post("/addons/{addon}/stop", api_addons.stop),
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/validate", api_addons.options_validate
),
web.get("/addons/{addon}/options/config", api_addons.options_config),
web.post("/addons/{addon}/rebuild", api_addons.rebuild),
web.get("/addons/{addon}/logs", api_addons.logs),
web.get("/addons/{addon}/icon", api_addons.icon),
@@ -468,6 +470,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:
"""Register panel for Home Assistant."""
panel_dir = Path(__file__).parent.joinpath("panel")

View File

@@ -82,6 +82,8 @@ from ..const import (
ATTR_STARTUP,
ATTR_STATE,
ATTR_STDIN,
ATTR_TRANSLATIONS,
ATTR_UART,
ATTR_UDEV,
ATTR_UPDATE_AVAILABLE,
ATTR_URL,
@@ -101,7 +103,7 @@ from ..const import (
)
from ..coresys import CoreSysAttributes
from ..docker.stats import DockerStats
from ..exceptions import APIError
from ..exceptions import APIError, APIForbidden
from ..validate import docker_ports
from .utils import api_process, api_process_raw, api_validate
@@ -170,6 +172,7 @@ class APIAddons(CoreSysAttributes):
ATTR_INSTALLED: addon.is_installed,
ATTR_AVAILABLE: addon.available,
ATTR_DETACHED: addon.is_detached,
ATTR_HOMEASSISTANT: addon.homeassistant_version,
ATTR_REPOSITORY: addon.repository,
ATTR_BUILD: addon.need_build,
ATTR_URL: addon.url,
@@ -237,7 +240,7 @@ class APIAddons(CoreSysAttributes):
ATTR_PRIVILEGED: addon.privileged,
ATTR_FULL_ACCESS: addon.with_full_access,
ATTR_APPARMOR: addon.apparmor,
ATTR_DEVICES: _pretty_devices(addon),
ATTR_DEVICES: addon.static_devices,
ATTR_ICON: addon.with_icon,
ATTR_LOGO: addon.with_logo,
ATTR_CHANGELOG: addon.with_changelog,
@@ -250,6 +253,7 @@ class APIAddons(CoreSysAttributes):
ATTR_HOMEASSISTANT_API: addon.access_homeassistant_api,
ATTR_GPIO: addon.with_gpio,
ATTR_USB: addon.with_usb,
ATTR_UART: addon.with_uart,
ATTR_KERNEL_MODULES: addon.with_kernel_modules,
ATTR_DEVICETREE: addon.with_devicetree,
ATTR_UDEV: addon.with_udev,
@@ -262,6 +266,7 @@ class APIAddons(CoreSysAttributes):
ATTR_SERVICES: _pretty_services(addon),
ATTR_DISCOVERY: addon.discovery,
ATTR_IP_ADDRESS: None,
ATTR_TRANSLATIONS: addon.translations,
ATTR_INGRESS: addon.with_ingress,
ATTR_INGRESS_ENTRY: None,
ATTR_INGRESS_URL: None,
@@ -286,6 +291,8 @@ class APIAddons(CoreSysAttributes):
ATTR_VERSION: addon.version,
ATTR_UPDATE_AVAILABLE: addon.need_update,
ATTR_WATCHDOG: addon.watchdog,
ATTR_DEVICES: addon.static_devices
+ [device.path for device in addon.devices],
}
)
@@ -339,6 +346,19 @@ class APIAddons(CoreSysAttributes):
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
async def security(self, request: web.Request) -> None:
"""Store security options for add-on."""
@@ -369,12 +389,6 @@ class APIAddons(CoreSysAttributes):
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
def uninstall(self, request: web.Request) -> Awaitable[None]:
"""Uninstall add-on."""
@@ -393,12 +407,6 @@ class APIAddons(CoreSysAttributes):
addon = self._extract_addon_installed(request)
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
def restart(self, request: web.Request) -> Awaitable[None]:
"""Restart add-on."""
@@ -468,14 +476,6 @@ class APIAddons(CoreSysAttributes):
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]:
"""Return a simplified services role list."""
return [f"{name}:{access}" for name, access in addon.services_role.items()]

View File

@@ -1,50 +1,46 @@
"""Init file for Supervisor hardware RESTful API."""
import asyncio
import logging
from typing import Any, Awaitable, Dict, List
from typing import Any, Awaitable, Dict
from aiohttp import web
from ..const import (
ATTR_AUDIO,
ATTR_DISK,
ATTR_GPIO,
ATTR_INPUT,
ATTR_OUTPUT,
ATTR_SERIAL,
ATTR_USB,
)
from ..const import ATTR_AUDIO, ATTR_DEVICES, ATTR_INPUT, ATTR_NAME, ATTR_OUTPUT
from ..coresys import CoreSysAttributes
from ..hardware.const import (
ATTR_ATTRIBUTES,
ATTR_BY_ID,
ATTR_DEV_PATH,
ATTR_SUBSYSTEM,
ATTR_SYSFS,
)
from ..hardware.data import Device
from .utils import api_process
_LOGGER: logging.Logger = logging.getLogger(__name__)
def device_struct(device: Device) -> Dict[str, Any]:
"""Return a dict with information of a interface to be used in th API."""
return {
ATTR_NAME: device.name,
ATTR_SYSFS: device.sysfs,
ATTR_DEV_PATH: device.path,
ATTR_SUBSYSTEM: device.subsystem,
ATTR_BY_ID: device.by_id,
ATTR_ATTRIBUTES: device.attributes,
}
class APIHardware(CoreSysAttributes):
"""Handle RESTful API for hardware functions."""
@api_process
async def info(self, request: web.Request) -> Dict[str, Any]:
"""Show hardware info."""
serial: List[str] = []
# Create Serial list with device links
for device in self.sys_hardware.serial_devices:
serial.append(device.path.as_posix())
for link in device.links:
serial.append(link.as_posix())
return {
ATTR_SERIAL: serial,
ATTR_INPUT: list(self.sys_hardware.input_devices),
ATTR_DISK: [
device.path.as_posix() for device in self.sys_hardware.disk_devices
],
ATTR_GPIO: list(self.sys_hardware.gpio_devices),
ATTR_USB: [
device.path.as_posix() for device in self.sys_hardware.usb_devices
],
ATTR_AUDIO: self.sys_hardware.audio_devices,
ATTR_DEVICES: [
device_struct(device) for device in self.sys_hardware.devices
]
}
@api_process
@@ -64,6 +60,6 @@ class APIHardware(CoreSysAttributes):
}
@api_process
def trigger(self, request: web.Request) -> Awaitable[None]:
async def trigger(self, request: web.Request) -> Awaitable[None]:
"""Trigger a udev device reload."""
return asyncio.shield(self.sys_hardware.udev_trigger())
_LOGGER.debug("Ignoring DEPRECATED hardware trigger function call.")

View File

@@ -11,6 +11,7 @@ from ..const import (
ATTR_DEPLOYMENT,
ATTR_DESCRIPTON,
ATTR_DISK_FREE,
ATTR_DISK_LIFE_TIME,
ATTR_DISK_TOTAL,
ATTR_DISK_USED,
ATTR_FEATURES,
@@ -43,6 +44,7 @@ class APIHost(CoreSysAttributes):
ATTR_DISK_FREE: self.sys_host.info.free_space,
ATTR_DISK_TOTAL: self.sys_host.info.total_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_HOSTNAME: self.sys_host.info.hostname,
ATTR_KERNEL: self.sys_host.info.kernel,

View File

@@ -15,6 +15,7 @@ from ..const import (
ATTR_LOGGING,
ATTR_MACHINE,
ATTR_OPERATING_SYSTEM,
ATTR_STATE,
ATTR_SUPERVISOR,
ATTR_SUPPORTED,
ATTR_SUPPORTED_ARCH,
@@ -42,6 +43,7 @@ class APIInfo(CoreSysAttributes):
ATTR_FEATURES: self.sys_host.features,
ATTR_MACHINE: self.sys_machine,
ATTR_ARCH: self.sys_arch.default,
ATTR_STATE: self.sys_core.state,
ATTR_SUPPORTED_ARCH: self.sys_arch.supported,
ATTR_SUPPORTED: self.sys_core.supported,
ATTR_CHANNEL: self.sys_updater.channel,

View File

@@ -155,7 +155,7 @@ class APINetwork(CoreSysAttributes):
except HostNetworkNotFound:
pass
raise APIError(f"Interface {name} does not exsist") from None
raise APIError(f"Interface {name} does not exist") from None
@api_process
async def info(self, request: web.Request) -> Dict[str, Any]:

View File

@@ -1,9 +1,9 @@
try {
new Function("import('/api/hassio/app/frontend_latest/entrypoint.2471a57c.js')")();
new Function("import('/api/hassio/app/frontend_latest/entrypoint.639ed35e.js')")();
} catch (err) {
var el = document.createElement('script');
el.src = '/api/hassio/app/frontend_es5/entrypoint.43c7c12c.js';
el.src = '/api/hassio/app/frontend_es5/entrypoint.054b9d8c.js';
document.body.appendChild(el);
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"version":3,"file":"chunk.0aeb318c1dfa0b946242.js","sources":["webpack://home-assistant-frontend/chunk.0aeb318c1dfa0b946242.js"],"mappings":"AAAA","sourceRoot":""}

View File

@@ -1 +0,0 @@
{"version":3,"file":"chunk.0ca880f618245e0de2ab.js","sources":["webpack://home-assistant-frontend/chunk.0ca880f618245e0de2ab.js"],"mappings":"AAAA","sourceRoot":""}

View File

@@ -0,0 +1 @@
{"version":3,"file":"chunk.114930542eafd429baaa.js","sources":["webpack://home-assistant-frontend/chunk.114930542eafd429baaa.js"],"mappings":"AAAA","sourceRoot":""}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1 @@
{"version":3,"file":"chunk.3daef259cd4ead95b329.js","sources":["webpack://home-assistant-frontend/chunk.3daef259cd4ead95b329.js"],"mappings":"AAAA","sourceRoot":""}

View File

@@ -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

View File

@@ -0,0 +1 @@
{"version":3,"file":"chunk.42d4a155d238b5760bd6.js","sources":["webpack://home-assistant-frontend/chunk.42d4a155d238b5760bd6.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"version":3,"file":"chunk.650a105f850d7b168b26.js","sources":["webpack://home-assistant-frontend/chunk.650a105f850d7b168b26.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"chunk.66a403e06c27cd823e0b.js","sources":["webpack://home-assistant-frontend/chunk.66a403e06c27cd823e0b.js"],"mappings":"AAAA","sourceRoot":""}

View File

@@ -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

View File

@@ -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

View File

@@ -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.
***************************************************************************** */

View File

@@ -0,0 +1 @@
{"version":3,"file":"chunk.69339833f81fc6a02f8b.js","sources":["webpack://home-assistant-frontend/chunk.69339833f81fc6a02f8b.js"],"mappings":";AAAA","sourceRoot":""}

View File

@@ -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

View File

@@ -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

View File

@@ -1 +0,0 @@
{"version":3,"file":"chunk.712a551bcc67f5a1a334.js","sources":["webpack://home-assistant-frontend/chunk.712a551bcc67f5a1a334.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"chunk.8969c2e64257133e299a.js","sources":["webpack://home-assistant-frontend/chunk.8969c2e64257133e299a.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"version":3,"file":"chunk.8c95d60c89a57c50576d.js","sources":["webpack://home-assistant-frontend/chunk.8c95d60c89a57c50576d.js"],"mappings":";AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"chunk.ae740c7fbd846ae0a428.js","sources":["webpack://home-assistant-frontend/chunk.ae740c7fbd846ae0a428.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -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

View File

@@ -0,0 +1 @@
{"version":3,"file":"chunk.d87d75788ac8ff1fec7b.js","sources":["webpack://home-assistant-frontend/chunk.d87d75788ac8ff1fec7b.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"version":3,"file":"chunk.d93e72d29a82b1218f4a.js","sources":["webpack://home-assistant-frontend/chunk.d93e72d29a82b1218f4a.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"version":3,"file":"chunk.de3430632b35ee0356b5.js","sources":["webpack://home-assistant-frontend/chunk.de3430632b35ee0356b5.js"],"mappings":"AAAA","sourceRoot":""}

View File

@@ -0,0 +1,2 @@
!function(){"use strict";var n,t={14971:function(n,t,e){var r,o,i=e(91107),u=e(9902),s=e.n(u),a=(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,a.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,a.filterXSS)(s()(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(){return 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.23709353009814f6ec3d.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 s in i)r.o(i,s)&&(r.m[s]=i[s]);for(u&&u(r);o.length;)n[o.pop()]=1;e(t)}}();r.x()}();
//# sourceMappingURL=chunk.df460c6964334a453095.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"chunk.df460c6964334a453095.js","sources":["webpack://home-assistant-frontend/chunk.df460c6964334a453095.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"chunk.df526179132dea1206b5.js","sources":["webpack://home-assistant-frontend/chunk.df526179132dea1206b5.js"],"mappings":"AAAA","sourceRoot":""}

Some files were not shown because too many files have changed in this diff Show More