Compare commits

...

198 Commits

Author SHA1 Message Date
Mike Degatano
0786e06eb9 Re-add typeerror handling to format message 2025-03-01 15:52:29 +00:00
Mike Degatano
5b18fb6b12 No executor task in sentry call when not initialized (#5703) 2025-03-01 10:46:11 -05:00
Stefan Agner
d42ec12ae8 Fix cloning of add-on store repository (#5701)
* Fix cloning of add-on store repository

Since #5669, the add-on store reset no longer deletes the root
directory. However, if the root directory is not present, the current
code no longer invokes cloning, instead tries to load the git
repository directly.

With this change, the code clones whenever there is no .git directory,
which works for both cases.

* Fix pytest
2025-03-01 16:17:07 +01:00
Mike Degatano
86133f8ecd Move read_text to executor (#5688)
* Move read_text to executor

* Fix issues found by coderabbit

* formated to formatted

* switch to async_capture_exception

* Find and replace got one too many

* Update patch mock to async_capture_exception

* Drop Sentry capture from format_message

The error handling got introduced in #2052, however, #2100 essentially
makes sure there will never be a byte object passed to this function.
And even if, the Sentry aiohttp plug-in will properly catch such an
exception.

---------

Co-authored-by: Stefan Agner <stefan@agner.ch>
2025-03-01 16:02:43 +01:00
Stefan Agner
12c951f62d Fix tests in devcontainer by removing resolution center (#5702)
Since #5696 we don't need to load the resolution center early. In fact,
with #5686 this is even problematic for pytests in devcontainer, since
the Supervisor Core state is valid and this causes AppArmor evaluations
to run (and fail).

Actually, #5696 removed the resolution center. #5686 brought it
accidentally back. This was seemingly a merge error.
2025-03-01 16:00:49 +01:00
Stefan Agner
fcb3e2eb55 Update Supervisor bug form (#5700)
Update Supervisor bug form to reflect today's naming in the frontend.
2025-03-01 13:06:44 +01:00
Stefan Agner
176e511180 Capture warnings and report to sentry (#5697)
By default, warnings are simply printed to stderr. This makes them
easy to miss in the log. Capture warnings and user Python logger to log
them with warning level.

Also, if the message is an instance of Exception (which it typically
is), report the warning to Sentry. This is e.g. useful for asyncio
RuntimeWarning warnings "coroutine was never awaited".
2025-02-28 21:28:40 +01:00
Stefan Agner
696dcf6149 Initialize Supervisor Core state in constructor (#5686)
* Initialize Supervisor Core state in constructor

Make sure the Supervisor Core state is set to a value early on. This
makes sure that the state is always of type CoreState, and makes sure
that any use of the state can rely on it being an actual value from the
CoreState enum.

This fixes Sentry filter during early startup, where the state
previously was None. Because of that, the Sentry filter tried to
collect more Context, which lead to an exception and not reporting
errors.

* Fix pytest

It seems that with initializing the state early, the pytest actually
runs a system evaluation with:
Starting system evaluation with state initialize

Before it did that with:
Starting system evaluation with state None

It detects that the container runs as privileged, and declares the
system as unhealthy.

It is unclear to me why coresys.core.healthy was checked in this
context, it doesn't seem useful. Just remove the check, and validate
the state through the getter instead.

* Update supervisor/core.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Make sure Supervisor container is privileged in pytest

With the Supervisor Core state being valid now, some evaluations
now actually run when loading the resolution center. This leads to
Supervisor getting declared unhealthy due to not running in a privileged
container under pytest.

Fake the host container to be privileged to make evaluations not
causing the system to be declared unhealthy under pytest.

* Avoid writing actual Supervisor run state file

With the Supervisor Core state being valid from the very start, we end
up writing a state everytime.

Instead of actually writing a state file, simply validate the the
necessary calls are being made. This is more conform to typical unit
tests and avoids writing a file for every test.

* Extend WebSocket client fixture and use it consistently

Extend the ha_ws_client WebSocket client fixture to set Supervisor Core
into run state and clear all pending messages.

Currently only some tests use the ha_ws_client WebSocket client fixture.
Use it consistently for all tests.

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-02-28 18:01:55 +01:00
Stefan Agner
8030b346e0 Load resolution evaluation, check and fixups early (#5696)
* Load resolution evaluation, check and fixups early

Before #5652, these modules were loaded in the constructor, hence early
in `initialize_coresys()`. Moving them late actually exposed an issue
where NetworkManager connectivity setter couldn't get the
`connectivity_check` evaluation, leading to an exception early in
bootstrap.

Technically, it might be safe to load the resolution modules only in
`Core.connect()`, however then we'd have to load them separately for
pytest. Let's go conservative and load them the same place where they
got loaded before #5652.

* Load resolution modules in a single executor call

* Fix pytest
2025-02-28 16:59:22 +01:00
Stefan Agner
53d97ce0c6 Improve plug-in update error message (#5695)
The current error message does not share any information about the
underlying problem why updating failed. Print the error to the logs.
2025-02-28 09:34:35 -05:00
Stefan Agner
77523f7bec Avoid space in update link of frontend update PR (#5694)
A newline is converted to a space as per YAML folding rules. The space
breaks markdown parsing of the link. Use a single line for the target
version link.
2025-02-28 13:13:11 +01:00
Stefan Agner
f4d69f1811 Make advanced logs error test work in all test environments (#5692)
When developing/testing in a Supervised environment, the
systemd-journal-gatewayd socket is actually available. Mock the
socket Path file to make the test independent of the pytest
environment.
2025-02-28 12:59:20 +01:00
Stefan Agner
cf5a0dc548 Add body with update information to frontend update prs (#5691)
Overwrite the default body with useful version update information and
a link to the new release.

Also rename the title and use lower caps for local shell variables.
2025-02-28 11:57:30 +01:00
dependabot[bot]
a8cc3ae6ef Bump actions/cache from 4.2.1 to 4.2.2 (#5690)
Bumps [actions/cache](https://github.com/actions/cache) from 4.2.1 to 4.2.2.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4.2.1...v4.2.2)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-28 10:21:43 +01:00
Stefan Agner
362bd8fd21 Enable Sentry asyncio integration (#5685)
Enable the Sentry asyncio integration. This makes sure that exception
in non-awaited tasks get reported to sentry.

While at it, use partial instead of lambda for the filter function.
2025-02-28 09:57:11 +01:00
Mike Degatano
2274de969f File open calls to executor (#5678) 2025-02-28 09:56:59 +01:00
dependabot[bot]
dfed251c7a Bump ruff from 0.9.7 to 0.9.8 (#5689)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.7 to 0.9.8.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.7...0.9.8)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-28 09:33:00 +01:00
Mike Degatano
151d4bdd73 Temporary directory to executor (#5673)
* Move temporary directory usage to executor

* Use temp_folder.name in Path constructor
2025-02-27 17:58:55 +01:00
Stefan Agner
c5d4ebcd48 Correctly handle aiohttp requests in Sentry reporting (#5681)
* Correctly handle aiohttp requests

The request header seems to be a dictionary in current Sentry SDK.
The previous code actually failed with an exception when trying to
unpack the header. However, it seems that Exceptions are not handled
or printed in this filter function, so those issues were simply
swallowed.

The new code has been tested to correctly sanitize and report issues
during aiohttp requests.

* Fix pytests
2025-02-27 15:54:51 +01:00
Stefan Agner
0ad559adcd Add more context to Sentry reports early during startup (#5682)
* Initialize machine information before Sentry

* Set user and machine for all reports

Now that we initialize machine earlier we can report user and machine
for all events, even before Supervisor is completely initialized.

Also use the new tag format which is a dictionary.

Note that it seems that with the current Sentry SDK version the
AioHttpIntegration no longer sets the URL as a tag. So sanitation is
no longer reuqired.

* Update pytests
2025-02-27 15:45:11 +01:00
Stefan Agner
39f5b91f12 Use await for all FileConfiguration calls (#5683)
Some calls got missed in PR #5652. Update all calls to await the
save_data() coroutine.
2025-02-27 15:38:57 +01:00
dependabot[bot]
ddee79d209 Bump codecov/codecov-action from 5.3.1 to 5.4.0 (#5680)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.3.1...v5.4.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-27 09:04:24 +01:00
dependabot[bot]
ff111253d5 Bump setuptools from 75.8.1 to 75.8.2 (#5679)
Bumps [setuptools](https://github.com/pypa/setuptools) from 75.8.1 to 75.8.2.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v75.8.1...v75.8.2)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-27 09:04:03 +01:00
Mike Degatano
31193abb7b FileConfiguration uses executor for I/O (#5652)
* FileConfiguration uses executor for I/O

* Fix credentials tests

* Remove migrate_system_env as its very deprecated
2025-02-26 19:11:11 +01:00
Stefan Agner
ae266e1692 Improve Supervisor restart detection message (#5672)
The word "reboot" is usually used when a operating system is restarted.
The current log message could be interpreted that the Supervisor
detected an operating system reboot.

Use restart to make it clear that the Supervisor detected a restart of
itself.
2025-02-26 13:10:40 -05:00
dependabot[bot]
c315a15816 Bump securetar from 2025.2.0 to 2025.2.1 (#5671)
* Bump securetar from 2025.2.0 to 2025.2.1

Bumps [securetar](https://github.com/pvizeli/securetar) from 2025.2.0 to 2025.2.1.
- [Release notes](https://github.com/pvizeli/securetar/releases)
- [Commits](https://github.com/pvizeli/securetar/compare/2025.2.0...2025.2.1)

---
updated-dependencies:
- dependency-name: securetar
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Handle new AddFileError where atomic_contents_add is used

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Stefan Agner <stefan@agner.ch>
2025-02-26 09:30:22 -05:00
dependabot[bot]
3bd732147c Bump actions/download-artifact from 4.1.8 to 4.1.9 (#5675)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-26 08:51:39 +01:00
dependabot[bot]
ddbde93a6d Bump setuptools from 75.8.0 to 75.8.1 (#5676)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-26 08:51:16 +01:00
dependabot[bot]
6db11a8ade Bump home-assistant/builder from 2024.08.2 to 2025.02.0 (#5674)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-26 08:46:38 +01:00
Stefan Agner
42e78408a7 Fix add-on store reset (#5669)
Make sure that add-on store resets do not delete the root folder. This
is important so that successive reset attempts do not fail (the
directory passed to `remove_folder` must exist, otherwise find fails
with an non-zero exit code).

While at it, handle find errors properly and report errors as critical.
2025-02-25 17:11:34 +01:00
Stefan Agner
15e8940c7f Improve D-Bus timeout error handling (#5664)
* Improve D-Bus timeout error handling

Typically D-Bus timeouts are related to systemd activation timing out
after 25s. The current dbus-fast timeout of 10s is well below that
so we never get the actual D-Bus error. This increases the dbus-fast
timeout to 30s, which will make sure we wait long enought to get the
actual D-Bus error from the broker.

Note that this should not slow down a typical system, since we tried
three times each waiting for 10s. With the new error handling typically
we'll end up waiting 25s and then receive the actual D-Bus error. There
is no point in waiting for multiple D-Bus/systemd caused timeouts.

* Create D-Bus TimedOut exception
2025-02-25 17:11:23 +01:00
dependabot[bot]
644ec45ded Bump aiohttp from 3.11.12 to 3.11.13 (#5665)
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-25 12:02:45 +01:00
Stefan Agner
a8d2743f56 Define CPU architecture to fix armhf builds (#5670) 2025-02-25 11:36:35 +01:00
dependabot[bot]
0acef4a6e6 Bump dbus-fast from 2.33.0 to 2.34.0 (#5666)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-25 09:15:24 +01:00
Stefan Agner
5733db94aa Revert "Fix add-on store reset"
This reverts commit da8c6cf111.
2025-02-25 09:10:27 +01:00
Stefan Agner
da8c6cf111 Fix add-on store reset
Make sure that add-on store resets do not delete the root folder. This
is important so that successive reset attempts do not fail (the
directory passed to `remove_folder` must exist, otherwise find fails
with an non-zero exit code).

While at it, handle find errors properly and report errors as critical.
2025-02-25 09:02:09 +01:00
Stefan Agner
802ee25a8b Build Python wheels for Python 3.13 (#5667) 2025-02-25 08:48:07 +01:00
Stefan Agner
ce8b107f1e Handle OS errors on backup create (#5662)
* Handle permission error on backup create

Make sure we handle (write) permission errors when creating a backup.

* Introduce BackupFileExistError and BackupPermissionError exceptions

* Make error messages a bit more uniform

* Drop use of exclusive mode

SecureTar does not handle exclusive mode nicely. Drop use of it for now.
2025-02-24 21:34:23 +01:00
Stefan Agner
32936e5de0 Handle non-zero subprocess exits (#5660)
With PR #5634 (which had the goal to remove I/O in event loop for backup
operations) the semantics of `remove_folder` changed slightly: Non-zero
exits of subprocesses were no longer handled, but lead to a
CalledProcessError.

Now to restore the semantics of `remove_folder` we should simply log an
error. However, this semantic change actually uncovered a potential
problem in deployed systems: There are 34 users on beta channel which
regularly seem to run `FixupStoreExecuteReset`, and with the semantic
change we see those errors in Sentry.

An obvious problem could be no storage. But in a quick test that would
not execute the repair in first place since the fixup has the job
condition `FREE_SPACE` set. So the problem is likely elsewhere.

With this change, we log the stderr of find, while still raising the
exception. With that we should get more context in Sentry to see what
could be the underlying error.
2025-02-24 12:30:39 +01:00
dependabot[bot]
c35746c3e1 Bump actions/upload-artifact from 4.6.0 to 4.6.1 (#5659) 2025-02-24 08:33:31 +01:00
dependabot[bot]
392dd9f904 Bump zlib-fast from 0.2.0 to 0.2.1 (#5658) 2025-02-24 08:31:37 +01:00
github-actions[bot]
d8f792950b Autoupdate frontend to version 20250221.0 (#5616)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-22 21:44:20 +01:00
dependabot[bot]
1f6cdc3018 Bump sigstore/cosign-installer from 3.8.0 to 3.8.1 (#5654)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-21 19:36:11 +01:00
dependabot[bot]
616f1903b7 Bump ruff from 0.9.6 to 0.9.7 (#5653) 2025-02-21 14:50:51 +01:00
Stefan Agner
997a51fc42 Remove I/O in event loop for add-on backup and restore (#5649)
* Remove I/O in event loop for add-on backup and restore

Remove I/O in event loop for add-on backup and restore operations. On
backup, this moves the add-on shutdown before metadata is stored in the
backup, which slightly lenghens the time the add-on is actually stopped.

However, the biggest contributor here is likely adding the image
itself if it is a local backup. However, since that is the minority of
cases, I've opted for simplicity over optimizing for this case.

* Use partial to explicitly bind arguments
2025-02-21 00:24:36 +01:00
dependabot[bot]
cda6325be4 Bump actions/cache from 4.2.0 to 4.2.1 (#5650) 2025-02-20 09:07:48 +01:00
Stefan Agner
c8cc6fe003 Remove I/O in event loop for Home Assistant Core backup (#5648)
* Remove I/O in event loop for Home Assistant Core backup

The Home Assistant Core backup still contains some I/O in the event
loop. Move all I/O into the executor.

* Update supervisor/homeassistant/module.py

Co-authored-by: Mike Degatano <michael.degatano@gmail.com>

---------

Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
2025-02-19 20:11:37 +01:00
Stefan Agner
34939cfe52 Remove I/O in event loop for backup load, import and remove (#5647)
* Avoid IO in event loop when removing backup

* Refactor backup size calculation

Currently size is lazy loaded when required via properties. This
however is blocking the async event loop.

Backup sizes don't change. Instead of lazy loading the size of a backup
simply determine it on loading/after creation.

* Fix tests for backup size change

* Avoid IO in event loop when loading backups

* Avoid IO in event loop when importing a backup
2025-02-19 16:00:17 +01:00
Stefan Agner
37bc703bbb Disable uv cache when creating container image (#5646)
We don't intent to run uv again, so the cache is not really useful.
The cache directory size is around 80MB, however, the files are mostly
hardlinks to the original files in `/usr/local/lib/python3.13/site-packages`
so the actual saving is much smaller.
2025-02-19 10:45:22 +01:00
Stefan Agner
5f8e41b441 Capture errors correctly while copying backups (#5644)
Make sure we correctly capture errors while copying backups by using
the current job instance.
2025-02-19 09:12:36 +01:00
Stefan Agner
606db3585c Remove I/O in event loop for backup create and restore operations (#5634)
* Remove I/O from backup create() function

* Move mount check into exectutor thread

* Remove I/O from backup open() function

* Remove I/O from _folder_save()

* Refactor remove_folder and remove_folder_with_excludes

Make remove_folder and remove_folder_with_excludes synchronous
functions which need to be run in an executor thread to be safely used
in asyncio. This makes them better composable with other I/O operations
like checking for file existence etc.

* Fix logger typo

* Use return values for functions running in an exectutor

* Move location check into a separate function

* Fix extract
2025-02-18 20:59:09 +01:00
Robert Resch
4054749eb2 Use uv to install supervisor (#5642) 2025-02-18 14:54:23 -05:00
Robert Resch
ad5827d33f Bump uv to 0.6.1 (#5641)
* Bump uv to 0.6.0

* Bump uv to 0.6.1
2025-02-18 19:26:36 +01:00
Jan Čermák
249464e928 Generate Python bytecode for site-packages during build (#5640)
Since transition from pip to uv in #5152, Supervisor container doesn't
contain bytecode for site-packages anymore, and because our AppArmor
profile denies mkdir operations, the compiled *.pyc files are never
created. Enable uv --compile option to opt for the same behavior as pip
had, to fix of the AA errors and the potential penalty of compilation on
every import.
2025-02-18 18:44:37 +01:00
dependabot[bot]
3bc55c054a Bump sentry-sdk from 2.21.0 to 2.22.0 (#5638)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.21.0 to 2.22.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/2.21.0...2.22.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-18 12:28:17 +01:00
Stefan Agner
4c108eea64 Always validate Backup before restoring (#5632)
* Validate Backup always before restoring

Since #5519 we check the encryption password early in restore case.
This has the side effect that we check the file existance early too.
However, in the non-encryption case, the file is not checked early.

This PR changes the behavior to always validate the backup file before
restoring, ensuring both encryption and non-encryption cases are
handled consistently.

In particular, the last case of test_restore_immediate_errors actually
validates that behavior. That test should actually have failed so far.
But it seems that because we validate the backup shortly after freeze
anyways, the exception still got raised early enough.

A simply `await asyncio.sleep(10)` right after the freeze makes the
test case fail. With this change, the test works consistently.

* Address pylint

* Fix backup_manager tests

* Drop warning message
2025-02-14 18:19:35 +01:00
Stefan Agner
9b2dbd634d Avoid exception when handling closed WebSocket connection (#5630)
When delivering multiple messages to Core, and the first fails with a
connection error, the second message will also fail with the same error.
But at this point the connection is already clsoed, which leads to an
exception in the exception handler. Avoid this compunding error by
checking if the connection is still exists before trying to close.

Fixes: #5629
2025-02-14 13:12:56 +01:00
dependabot[bot]
2cb2a48184 Bump securetar from 2025.1.4 to 2025.2.0 (#5628)
Bumps [securetar](https://github.com/pvizeli/securetar) from 2025.1.4 to 2025.2.0.
- [Release notes](https://github.com/pvizeli/securetar/releases)
- [Commits](https://github.com/pvizeli/securetar/compare/2025.1.4...2025.2.0)

---
updated-dependencies:
- dependency-name: securetar
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-14 11:05:21 +01:00
dependabot[bot]
ed5a0b511e Bump sentry-sdk from 2.20.0 to 2.21.0 (#5625)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.20.0 to 2.21.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/2.20.0...2.21.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-13 10:32:49 +01:00
dependabot[bot]
1475dcb50b Bump cryptography from 44.0.0 to 44.0.1 (#5621)
Bumps [cryptography](https://github.com/pyca/cryptography) from 44.0.0 to 44.0.1.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/44.0.0...44.0.1)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-02-12 13:43:01 +01:00
dependabot[bot]
5cd7f6fd84 Bump coverage from 7.6.11 to 7.6.12 (#5622) 2025-02-12 08:04:09 +01:00
Mike Degatano
52cc17fa3f Delay initial version fetch until there is connectivity (#5603)
* Delay inital version fetch until there is connectivity

* Add test

* Only mock get not whole websession object

* drive delayed fetch off of supervisor connectivity not host

* Fix test to not rely on sleep guessing to track tasks

* Use fixture to remove job throttle temporarily
2025-02-11 13:22:33 +01:00
dependabot[bot]
fa6949f4e4 Bump getsentry/action-release from 1.10.2 to 1.10.4 (#5619)
Bumps [getsentry/action-release](https://github.com/getsentry/action-release) from 1.10.2 to 1.10.4.
- [Release notes](https://github.com/getsentry/action-release/releases)
- [Changelog](https://github.com/getsentry/action-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/action-release/compare/v1.10.2...v1.10.4)

---
updated-dependencies:
- dependency-name: getsentry/action-release
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-11 11:39:24 +01:00
dependabot[bot]
63a4cee770 Bump ruff from 0.9.5 to 0.9.6 (#5618) 2025-02-11 07:48:02 +01:00
dependabot[bot]
7aed0c1b0d Bump getsentry/action-release from 1.10.1 to 1.10.2 (#5615) 2025-02-10 07:57:30 +01:00
dependabot[bot]
de592a6ef4 Bump coverage from 7.6.10 to 7.6.11 (#5614) 2025-02-10 07:57:14 +01:00
dependabot[bot]
ff7086c0d0 Bump getsentry/action-release from 1.9.0 to 1.10.1 (#5611)
Bumps [getsentry/action-release](https://github.com/getsentry/action-release) from 1.9.0 to 1.10.1.
- [Release notes](https://github.com/getsentry/action-release/releases)
- [Changelog](https://github.com/getsentry/action-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/action-release/compare/v1.9.0...v1.10.1)

---
updated-dependencies:
- dependency-name: getsentry/action-release
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-07 11:30:45 +01:00
dependabot[bot]
ef0352ecd6 Bump ruff from 0.9.4 to 0.9.5 (#5612) 2025-02-07 09:18:45 +01:00
Stefan Agner
7348745049 Print the exact reason if the WebSocket event to Core fails (#5609)
* Print the exact reason if the WebSocket event to Core fails

* Improve error at backup end too, fix tests

* Fix text

* Address ruff check issue
2025-02-06 18:17:46 +01:00
github-actions[bot]
2078044062 Autoupdate frontend to version 20250205.0 (#5543)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-06 17:34:18 +01:00
Stefan Agner
d254937590 Drop Docker config from Supervisor backup (#5605)
* Drop Docker config from Supervisor backup

The Docker config is part of the main backup metadata. Because we
consolidate encrypted and unencrypted backups today, this leads to
potential bugs when restoring a backup.

* Drop obsolete encrypt/decrypt functions

* Drop unused Backup Job stage
2025-02-06 11:15:56 +01:00
dependabot[bot]
9a8e52d1fc Bump aiohttp from 3.11.11 to 3.11.12 (#5608)
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-06 09:37:03 +01:00
dependabot[bot]
6e7fac5493 Bump dbus-fast from 2.32.0 to 2.33.0 (#5607)
Bumps [dbus-fast](https://github.com/bluetooth-devices/dbus-fast) from 2.32.0 to 2.33.0.
- [Release notes](https://github.com/bluetooth-devices/dbus-fast/releases)
- [Changelog](https://github.com/Bluetooth-Devices/dbus-fast/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bluetooth-devices/dbus-fast/compare/v2.32.0...v2.33.0)

---
updated-dependencies:
- dependency-name: dbus-fast
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-06 09:28:19 +01:00
Mike Degatano
129a37a1f4 Prevent race condition with location reload and backups list (#5602) 2025-02-05 14:24:37 +01:00
dependabot[bot]
01382e774e Bump sigstore/cosign-installer from 3.7.0 to 3.8.0 (#5604)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.7.0 to 3.8.0.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.7.0...v3.8.0)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-05 09:16:40 +01:00
Stefan Agner
9164d35615 Fix restoring unencrypted backup in corner case (#5600)
* Fix restoring unencrypted backup in corner case

If a backup has a encrypted and unencrypted location, and the encrypted
location is beeing restored first, the encryption key is still cached.
When the user restores the unencrypted backup next, it will fail because
the Supervisor tries to use encryption key still.

* Add integration test for restoring backups with and without encryption

* Rename _validate_location_password to _set_location_password

* Reload backup metadata from restore location

* Revert "Reload backup metadata from restore location"

This reverts commit 9b47a1cfe9.

* Make pytest work/punt the ball on docker config restore issue

* Address pylint error
2025-02-04 17:53:22 +01:00
Stefan Agner
58df65541c Handle non-existing file in Backup password check too (#5599)
* Handle non-existing file in Backup password check too

Make sure we handle a non-existing backup file also when validating
the password.

* Update supervisor/backups/manager.py

Co-authored-by: Mike Degatano <michael.degatano@gmail.com>

* Add test case and fix password check when multiple locations

* Mock default backup unprotected by default

Instead of setting the protected property which we might not use
everywhere, simply mock the default backup to be unprotected.

* Fix mock of protected backup

* Introduce test for validate_password

Testing showed that validate_password doesn't return anything. Extend
tests to cover this case and fix the actual code.

---------

Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
2025-02-04 11:23:05 +01:00
Mike Degatano
4c04f364a3 Use full match in homeassistant backup excludes (#5597) 2025-02-03 13:47:12 +01:00
Mike Degatano
7f39538231 Update cache if a backup file is missing (#5596)
* Update cache if a backup file is missing

* Remove references to single file reload
2025-02-03 13:46:57 +01:00
dependabot[bot]
be98e0c0f4 Bump dbus-fast from 2.30.2 to 2.32.0 (#5598)
Bumps [dbus-fast](https://github.com/bluetooth-devices/dbus-fast) from 2.30.2 to 2.32.0.
- [Release notes](https://github.com/bluetooth-devices/dbus-fast/releases)
- [Changelog](https://github.com/Bluetooth-Devices/dbus-fast/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bluetooth-devices/dbus-fast/compare/v2.30.2...v2.32.0)

---
updated-dependencies:
- dependency-name: dbus-fast
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 09:29:59 +01:00
Stefan Agner
9491b1ff89 Avoid reordering add-on repositories on Backup load (#5595)
* Avoid reordering add-on repositories on Backup load

The `ensure_builtin_repositories` function uses a set to deduplicate
items, which sometimes led to a change of order in elements. This is
problematic when deduplicating Backups.

Simply avoid mangling the list of add-on repositories on load. Instead
rely on `update_repositories` which uses the same function to ensure
built-in repositories when loading the store configuration and restoring
a backup file.

* Update tests

* ruff format

* ruff check

* ruff check fixes

* ruff format

* Update tests/store/test_validate.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Simplify test

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-01-31 12:10:47 -05:00
Stefan Agner
30cbb039d0 Handle non-existing backup file (#5590)
* Make the API return 404 for non-existing backup files

* Introduce BackupFileNotFoundError exception

* Return 404 on full restore as well

* Fix remaining API tests

* Improve error handling in delete

* Fix pytest

* Fix tests and change error handling to agreed logic

---------

Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
2025-01-31 14:27:24 +01:00
Jan Čermák
1aabca9489 Make sure the oldest boot ID is included in the boot list (#5591)
If the system is running for a long time, or the logging is particularly
chatty, the Systemd journal message we use to detect boot will be
rotated out of the journal. Currently we only handled it if there was
one boot, but we usually always missed the oldest boot if there were
more boots.

Adjust the method for getting boot IDs to always get the very first log
line in the journal instead of the last one, and make sure its boot ID
is included in the list.
2025-01-31 11:55:05 +01:00
Stefan Agner
28a87db515 Avoid test failure by not checking exact size of backup (#5594)
* Avoid test failure by not checking exact size of backup

This is a workaround for the fact that the backup size is not exactly
the same every time. This is due to the fact that the inner gziped tar
file can vary in size due to difference in json file (key order) and
potentially also different field values (UUID, backup slug).

It seems that sorting the keys makes the actual difference today, but
this has runtime overhead and might not catch all cases.

Simply check if size property is there and a number bigger than 0
instead.

* Fix pytest
2025-01-31 11:30:43 +01:00
dependabot[bot]
05b648629f Bump ruff from 0.9.3 to 0.9.4 (#5592)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.3 to 0.9.4.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.3...0.9.4)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-31 08:27:43 +01:00
dependabot[bot]
d1d8446480 Bump pylint from 3.3.3 to 3.3.4 (#5586) 2025-01-29 08:17:18 +01:00
Mike Degatano
8b897ba537 Fix bug when uploading backup to a mount (#5585) 2025-01-28 18:30:37 +01:00
Mike Degatano
c8f1b222c0 Add sizes per location and support .local (#5581) 2025-01-28 11:41:51 +01:00
dependabot[bot]
257e2ceb82 Bump actions/setup-python from 5.3.0 to 5.4.0 (#5583)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5.3.0...v5.4.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-28 11:36:24 +01:00
dependabot[bot]
67a27cae40 Bump securetar from 2025.1.3 to 2025.1.4 (#5582)
Bumps [securetar](https://github.com/pvizeli/securetar) from 2025.1.3 to 2025.1.4.
- [Release notes](https://github.com/pvizeli/securetar/releases)
- [Commits](https://github.com/pvizeli/securetar/compare/2025.1.3...2025.1.4)

---
updated-dependencies:
- dependency-name: securetar
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-28 11:35:28 +01:00
Stefan Agner
8ff9c08e82 Support systemd-journal-gatewayd using a TCP socket (#5576) 2025-01-27 13:57:59 +01:00
Stefan Agner
1b0aa30881 Extend backup upload API with file name parameter (#5568)
* Extend backup upload API with file name parameter

Add a query parameter which allows to specify the file name on upload.
All locations will store the backup with the same file name.

* ruff format

* Update tests to cover bad filename

* Fix ruff check error

* Drop unnecessary logging
2025-01-27 10:01:29 +01:00
dependabot[bot]
2a8d2d2b48 Bump codecov/codecov-action from 5.3.0 to 5.3.1 (#5580)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.3.0...v5.3.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-27 09:07:39 +01:00
dependabot[bot]
44bd787276 Bump attrs from 24.3.0 to 25.1.0 (#5579)
Bumps [attrs](https://github.com/sponsors/hynek) from 24.3.0 to 25.1.0.
- [Commits](https://github.com/sponsors/hynek/commits)

---
updated-dependencies:
- dependency-name: attrs
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-27 09:07:21 +01:00
Stefan Agner
690f1c07a7 Use version which is treated CalVer by AwesomeVersion (#5572)
* Use version which is treated CalVer by AwesomeVersion

The current dev version `99.9.9dev` is treated as unkown version type
by AwesomeVersion. This prevents the version from comparing with
actual Supervisor versions, e.g. from an exsiting backup file.

Make the development version a valid CalVer version so development
versions can handle non-development backups.

* Bump to year 9999
2025-01-24 09:59:50 +01:00
dependabot[bot]
8e185a8413 Bump pytest-aiohttp from 1.0.5 to 1.1.0 (#5573)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-24 08:34:21 +01:00
dependabot[bot]
1f7df73964 Bump codecov/codecov-action from 5.2.0 to 5.3.0 (#5575)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-24 08:34:05 +01:00
dependabot[bot]
a10afc45b1 Bump ruff from 0.9.2 to 0.9.3 (#5574) 2025-01-24 07:30:28 +01:00
Mike Degatano
61a2101d8a Backup protected status can vary per location (#5569)
* Backup protected status can vary per location

* Fix test_backup_remove_error test

* Update supervisor/backups/backup.py

* Add Docker registry configuration to backup metadata

* Make use of backup location fixture

* Address pylint

---------

Co-authored-by: Stefan Agner <stefan@agner.ch>
2025-01-23 15:05:35 -05:00
Stefan Agner
088832c253 Extend backup API with file name field (#5567)
* Extend backup API with file name field

Allow to specify a backup file name when creating a backup. This allows
for user friendly backup file names. If none is specified, the current
behavior remains (backup file name is the backup slug).

* Check passed file name using regex

* Use custom filename on download only if backup file name is backup slug

* ruff format

* Remove path from location for download file name
2025-01-23 15:24:47 +01:00
dependabot[bot]
a545b680b3 Bump codecov/codecov-action from 5.1.2 to 5.2.0 (#5571)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-23 08:52:49 +01:00
dependabot[bot]
805017eabf Bump dbus-fast from 2.28.0 to 2.30.2 (#5562)
Bumps [dbus-fast](https://github.com/bluetooth-devices/dbus-fast) from 2.28.0 to 2.30.2.
- [Release notes](https://github.com/bluetooth-devices/dbus-fast/releases)
- [Changelog](https://github.com/Bluetooth-Devices/dbus-fast/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bluetooth-devices/dbus-fast/compare/v2.28.0...v2.30.2)

---
updated-dependencies:
- dependency-name: dbus-fast
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-21 12:06:41 +01:00
Stefan Agner
b7412b0679 Update Python to 3.13 (#5564)
* Bump Supervisor to Python 3.13

* Update ruff configuration to 0.9.1

Adjust pyproject.toml for ruff 0.9.1. Also make sure that latest version
of ruff is used in pre-commit.

* Set default configuration for pytest-asyncio

* Run ruff check

* Drop deprecated decorator no_type_check_decorator

The upstream PR (https://github.com/python/cpython/issues/106309) says
this never got really implemented by type checkers.

* Bump devcontainer to latest release
2025-01-21 11:57:30 +01:00
dependabot[bot]
fff3bfd01e Bump pre-commit from 4.0.1 to 4.1.0 (#5566) 2025-01-21 07:40:07 +01:00
dependabot[bot]
5f165a79ba Bump actions/stale from 9.0.0 to 9.1.0 (#5565) 2025-01-21 07:39:51 +01:00
dependabot[bot]
0d3acd1aca Bump release-drafter/release-drafter from 6.0.0 to 6.1.0 (#5563) 2025-01-20 08:09:23 +01:00
dependabot[bot]
463f196472 Bump securetar from 2024.11.0 to 2025.1.3 (#5553)
* Bump securetar from 2024.11.0 to 2025.1.3

Bumps [securetar](https://github.com/pvizeli/securetar) from 2024.11.0 to 2025.1.3.
- [Release notes](https://github.com/pvizeli/securetar/releases)
- [Commits](https://github.com/pvizeli/securetar/compare/2024.11.0...2025.1.3)

---
updated-dependencies:
- dependency-name: securetar
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Use file_filter and add test for addon backup_exclude

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
2025-01-17 11:18:55 +01:00
dependabot[bot]
52d5df6778 Bump ruff from 0.9.1 to 0.9.2 (#5558)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-17 08:28:19 +01:00
dependabot[bot]
ce75c85e65 Bump debugpy from 1.8.11 to 1.8.12 (#5559) 2025-01-17 07:42:59 +01:00
puddly
12fd61142d Trigger rescan on device remove (#5447) 2025-01-16 18:16:47 +01:00
Mike Degatano
0073227785 Add env on core restart due to restore (#5548)
* Add env on core restart due to restore

* Move is_restore to backup manager
2025-01-16 18:15:06 +01:00
Stefan Agner
89a215cc1f Revert "Bump dbus-fast from 2.28.0 to 2.29.0 (#5551)" (#5555)
This reverts commit da6bdfa795.
2025-01-16 12:51:16 +01:00
Stefan Agner
b2aece8208 Revert "Bump orjson from 3.10.12 to 3.10.13 (#5514)" (#5554)
This reverts commit dbd37d6575.
2025-01-16 11:35:48 +01:00
Mike Degatano
600bf91c4f Sort jobs by creation in API (#5545)
* Sort jobs by creation in API

* Fix tests missing new field

* Fix sorting logic around child jobs
2025-01-16 09:51:44 +01:00
dependabot[bot]
da6bdfa795 Bump dbus-fast from 2.28.0 to 2.29.0 (#5551)
Bumps [dbus-fast](https://github.com/bluetooth-devices/dbus-fast) from 2.28.0 to 2.29.0.
- [Release notes](https://github.com/bluetooth-devices/dbus-fast/releases)
- [Changelog](https://github.com/Bluetooth-Devices/dbus-fast/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bluetooth-devices/dbus-fast/compare/v2.28.0...v2.29.0)

---
updated-dependencies:
- dependency-name: dbus-fast
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-16 09:51:29 +01:00
dependabot[bot]
5d4894a1ba Bump getsentry/action-release from 1.8.0 to 1.9.0 (#5552)
Bumps [getsentry/action-release](https://github.com/getsentry/action-release) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/getsentry/action-release/releases)
- [Changelog](https://github.com/getsentry/action-release/blob/master/docs/publishing-a-release.md)
- [Commits](https://github.com/getsentry/action-release/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: getsentry/action-release
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-16 09:51:03 +01:00
Stefan Agner
d4c047bd01 Add tun to static device list in hardware manager (#5547)
Some devices are provided by kernel modules which potentially get loaded
later at startup. Those are not listed by udev, and hence add-ons do
not get permissions for these types of devices as long as the kernel
module is not loaded.

Typically, such devices are created by the kmod-static-nodes.service
systemd service. Ideally, we would read the output of that service and
add those specifically. However, there are very few devices which
use static nodes, and we actually only really interested in tun. So
let's simply add this static node in case udev does not list it already.
2025-01-15 09:56:32 +01:00
dependabot[bot]
6183b9719c Bump sentry-sdk from 2.19.2 to 2.20.0 (#5549)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.19.2 to 2.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/2.19.2...2.20.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-15 09:54:07 +01:00
Alexander Akhmetov
f02d67ee47 Add support for CAP_CHECKPOINT_RESTORE privileges (#5426) 2025-01-14 11:28:36 +01:00
Stefan Agner
bd156ebb53 Add X-Accel-Buffering to disable buffers in proxies (#5544) 2025-01-14 10:42:41 +01:00
Wendelin
b07236b544 Add frontend auto update workflow (#5501)
* Remove git submodule

* Add frontend auto update workflow

* Update .github/workflows/update_frontend.yml

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

---------

Co-authored-by: Stefan Agner <stefan@agner.ch>
2025-01-13 12:35:54 +01:00
dependabot[bot]
5928a31fc4 Bump dbus-fast from 2.24.4 to 2.28.0 (#5532)
Bumps [dbus-fast](https://github.com/bluetooth-devices/dbus-fast) from 2.24.4 to 2.28.0.
- [Release notes](https://github.com/bluetooth-devices/dbus-fast/releases)
- [Changelog](https://github.com/Bluetooth-Devices/dbus-fast/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bluetooth-devices/dbus-fast/compare/v2.24.4...v2.28.0)

---
updated-dependencies:
- dependency-name: dbus-fast
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-13 11:38:46 +01:00
dependabot[bot]
3a71ea7003 Bump ruff from 0.9.0 to 0.9.1 (#5542)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.0...0.9.1)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-13 11:38:12 +01:00
dependabot[bot]
96900b1f1b Bump actions/upload-artifact from 4.5.0 to 4.6.0 (#5534)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-11 13:28:31 +01:00
dependabot[bot]
65b39661a6 Bump getsentry/action-release from 1.7.0 to 1.8.0 (#5535)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-11 13:27:24 +01:00
dependabot[bot]
18251ae8ae Bump ruff from 0.8.6 to 0.9.0 (#5538) 2025-01-11 13:06:45 +01:00
dependabot[bot]
c418e0ea76 Bump setuptools from 75.7.0 to 75.8.0 (#5536) 2025-01-11 12:33:30 +01:00
dependabot[bot]
74b009ccd7 Bump ruff from 0.8.5 to 0.8.6 (#5528)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-06 12:18:44 +01:00
dependabot[bot]
d2631bf398 Bump setuptools from 75.6.0 to 75.7.0 (#5529) 2025-01-06 08:12:21 +01:00
dependabot[bot]
c62358d851 Bump ruff from 0.8.4 to 0.8.5 (#5522)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-03 10:19:05 +01:00
dependabot[bot]
e3af04701a Bump gitpython from 3.1.43 to 3.1.44 (#5523)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-03 09:54:05 +01:00
Stefan Agner
c2f6e319f2 Check password early on backup restore (#5519)
Introduce a validate password method which only peaks into the archive
to validate the password before starting the actual restore process.
This makes sure that a wrong password returns an error even when
restoring the backup in background.
2024-12-31 13:58:12 +01:00
Stefan Agner
61b37877be Avoid lingering tasks when using background backup tasks (#5518)
When a backup tasks is run in background, but actually has an error
early the secondary event task to release the callee is lingering around
still, ultimately leading to a "Task was destroyed but it is pending!"
asyncio error.

Make sure we cancel the event task in case the backup returns early.
2024-12-31 13:16:18 +01:00
Stefan Agner
e72c5a037b Drop dead folder restore code (#5517)
The inner function _folder_restore has been converted to a Job with
PR #4802. The inner function is no longer used. Drop this dead code.
2024-12-31 13:16:04 +01:00
Stefan Agner
578383411c Fix backup remove for alternate locations (#5515)
Currently the API converts backup locations on network mounts to
the Supervisor's Mount representation. However, the locations stored
in the backup representations is a dictionary with the location
string as key.

Make sure to use the backup location string to validate the remove
requests. This fixes removing backups from network storage mounts.
2024-12-30 14:18:11 +01:00
dependabot[bot]
dbd37d6575 Bump orjson from 3.10.12 to 3.10.13 (#5514)
Bumps [orjson](https://github.com/ijl/orjson) from 3.10.12 to 3.10.13.
- [Release notes](https://github.com/ijl/orjson/releases)
- [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ijl/orjson/compare/3.10.12...3.10.13)

---
updated-dependencies:
- dependency-name: orjson
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-30 11:16:25 +01:00
dependabot[bot]
c7cf1e7593 Bump pulsectl from 24.11.0 to 24.12.0 (#5512)
Bumps [pulsectl](https://github.com/mk-fg/python-pulse-control) from 24.11.0 to 24.12.0.
- [Changelog](https://github.com/mk-fg/python-pulse-control/blob/master/CHANGES.rst)
- [Commits](https://github.com/mk-fg/python-pulse-control/compare/24.11.0...24.12.0)

---
updated-dependencies:
- dependency-name: pulsectl
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-30 09:28:16 +01:00
dependabot[bot]
c06fb069ab Bump coverage from 7.6.9 to 7.6.10 (#5513) 2024-12-27 08:03:29 +01:00
dependabot[bot]
b6c2259bd7 Bump pylint from 3.3.2 to 3.3.3 (#5511)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-24 09:00:09 +01:00
dependabot[bot]
d0b7cc8ab3 Bump astroid from 3.3.7 to 3.3.8 (#5510) 2024-12-24 08:18:42 +01:00
dependabot[bot]
0f77021bcc Bump astroid from 3.3.6 to 3.3.7 (#5507)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Stefan Agner <stefan@agner.ch>
2024-12-23 14:26:07 +01:00
dependabot[bot]
b44e6d8cd3 Bump urllib3 from 2.2.3 to 2.3.0 (#5504)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.3 to 2.3.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.2.3...2.3.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-23 12:40:52 +01:00
dependabot[bot]
dfe9e94f87 Bump jinja2 from 3.1.4 to 3.1.5 (#5503)
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.4...3.1.5)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-23 12:40:38 +01:00
Stefan Agner
53ccc5249a Add astroid to pytest requirements (#5506) 2024-12-23 12:27:49 +01:00
dependabot[bot]
5993818c16 Bump ruff from 0.8.3 to 0.8.4 (#5500)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-20 10:23:28 +01:00
dependabot[bot]
a631dea01a Bump codecov/codecov-action from 5.1.1 to 5.1.2 (#5499)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.1.1 to 5.1.2.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.1.1...v5.1.2)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 18:40:22 +01:00
J. Nick Koston
c5b85b2831 Bump aiohttp to 3.11.11 (#5498)
changelog: https://github.com/aio-libs/aiohttp/compare/v3.11.10...v3.11.11

https://github.com/aio-libs/aiohttp/releases/tag/v3.11.11
2024-12-18 23:14:36 +01:00
Stefan Agner
3c1920e4e1 Bump frontend to 20241127.8 release (#5495) 2024-12-18 15:54:40 +01:00
dependabot[bot]
ca6ae7f4ce Bump actions/upload-artifact from 4.4.3 to 4.5.0 (#5494) 2024-12-18 07:45:01 +01:00
dependabot[bot]
031ad0dbe6 Bump attrs from 24.2.0 to 24.3.0 (#5492)
Bumps [attrs](https://github.com/sponsors/hynek) from 24.2.0 to 24.3.0.
- [Commits](https://github.com/sponsors/hynek/commits)

---
updated-dependencies:
- dependency-name: attrs
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-17 14:11:40 +01:00
Mike Degatano
d8101ddba8 Use status 404 in more places when appropriate (#5480) 2024-12-17 11:18:32 +01:00
Mike Degatano
de68868788 Restore backup from specific location (#5491) 2024-12-17 11:09:32 +01:00
Mike Degatano
90590ae2de Add all addons flag to partial backups (#5490) 2024-12-16 18:25:58 +01:00
dependabot[bot]
5e6bef7189 Bump debugpy from 1.8.9 to 1.8.11 (#5488) 2024-12-16 07:27:26 +01:00
dependabot[bot]
7ab5555087 Bump ruff from 0.8.2 to 0.8.3 (#5483)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-13 09:22:18 +01:00
Mike Degatano
02ceb713ea Add location to backup download and remove APIs (#5482) 2024-12-12 19:44:40 +01:00
Mike Degatano
774aef74e8 Backup not found returns 404 instead of 400 (#5479) 2024-12-10 22:30:07 +01:00
dependabot[bot]
045454b597 Bump ciso8601 from 2.3.1 to 2.3.2 (#5478)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-10 08:24:28 +01:00
Mike Degatano
829193fe84 Support CGroup v2 on Supervised with manual restarts (#5419) 2024-12-09 15:09:54 +01:00
Mike Degatano
1f893117cc Fix backup consolidate and upload duplicate (#5472) 2024-12-09 10:03:49 +01:00
dependabot[bot]
9008009727 Bump sentry-sdk from 2.19.1 to 2.19.2 (#5476)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 08:27:33 +01:00
dependabot[bot]
3bf3bffabf Bump coverage from 7.6.8 to 7.6.9 (#5475)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 08:17:26 +01:00
Mike Degatano
d44e995aed Add size in bytes to backups (#5473) 2024-12-07 10:27:23 +01:00
dependabot[bot]
5a22599b93 Bump orjson from 3.10.7 to 3.10.12 (#5449)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-06 21:19:14 +01:00
dependabot[bot]
ae60e947f3 Bump sentry-sdk from 2.19.0 to 2.19.1 (#5467)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-06 11:51:27 -06:00
dependabot[bot]
8115fd98bc Bump ruff from 0.8.1 to 0.8.2 (#5468)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-06 11:51:11 -06:00
dependabot[bot]
3201061ada Bump codecov/codecov-action from 5.0.7 to 5.1.1 (#5470) 2024-12-06 08:22:42 +01:00
dependabot[bot]
b68caecbce Bump actions/cache from 4.1.2 to 4.2.0 (#5469) 2024-12-06 08:22:16 +01:00
J. Nick Koston
5e780293c7 Bump aiohttp to 3.11.10 (#5466)
changelog: https://github.com/aio-libs/aiohttp/compare/v3.11.9...v3.11.10
2024-12-05 23:44:19 -05:00
Mike Degatano
6e32144e9a Fix and extend cloud backup support (#5464)
* Fix and extend cloud backup support

* Clean up task for cloud backup and remove by location

* Args to kwargs on backup methods

* Fix backup remove error test and typing clean up
2024-12-05 00:07:04 -05:00
dependabot[bot]
9b52fee0a3 Bump aiohttp from 3.11.8 to 3.11.9 (#5461)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 14:43:08 +01:00
dependabot[bot]
7af4b17430 Bump cryptography from 43.0.3 to 44.0.0 (#5456)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 13:15:27 +01:00
dependabot[bot]
4195c0fb33 Bump pytest from 8.3.3 to 8.3.4 (#5462) 2024-12-02 08:42:13 +01:00
dependabot[bot]
8fe1cfbb20 Bump coverage from 7.6.7 to 7.6.8 (#5451) 2024-12-02 08:41:58 +01:00
dependabot[bot]
623c532c9e Bump pylint from 3.3.1 to 3.3.2 (#5460) 2024-12-02 08:05:04 +01:00
dependabot[bot]
3a904383af Bump ruff from 0.8.0 to 0.8.1 (#5459)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-01 16:47:50 -06:00
dependabot[bot]
28299affef Bump aiohttp from 3.11.7 to 3.11.8 (#5457) 2024-11-28 07:35:49 +01:00
Mike Degatano
11ca772ada Disable backup complete ws message (#5452) 2024-11-26 09:08:12 +01:00
Mike Degatano
42e704d563 Fix flaky backup test (#5453) 2024-11-26 00:47:00 +01:00
dependabot[bot]
ec7241c0fd Bump ruff from 0.7.4 to 0.8.0 (#5450)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.4 to 0.8.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.7.4...0.8.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 08:51:31 +01:00
Mike Degatano
d11d59dd92 Add null check on user path in mounts (#5446) 2024-11-22 09:54:21 -05:00
dependabot[bot]
7a55f58a5f Bump debugpy from 1.8.8 to 1.8.9 (#5444)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 08:55:42 +01:00
dependabot[bot]
0b5b5f7fd4 Bump sentry-sdk from 2.18.0 to 2.19.0 (#5443)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 08:52:40 +01:00
dependabot[bot]
56f3d384d6 Bump aiohttp from 3.11.6 to 3.11.7 (#5442)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 08:43:45 +01:00
dependabot[bot]
29117bb90b Bump securetar from 2024.2.1 to 2024.11.0 (#5445)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 08:43:26 +01:00
Mike Degatano
5519f6a53b Add support for cloud backups in Core (#5438)
* Add support for cloud backups in Core

* Test cases and small fixes identified

* Add test for partial reload no file failure
2024-11-21 18:14:20 -05:00
dependabot[bot]
a45d507bee Bump setuptools from 75.5.0 to 75.6.0 (#5440)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-21 08:12:18 +01:00
dependabot[bot]
0a663b5c27 Bump codecov/codecov-action from 5.0.4 to 5.0.7 (#5441)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-21 08:06:37 +01:00
dependabot[bot]
0f1fed525c Bump aiohttp from 3.11.4 to 3.11.6 (#5436)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-20 09:06:59 +01:00
dependabot[bot]
209cddc843 Bump codecov/codecov-action from 5.0.2 to 5.0.4 (#5437)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-20 08:45:11 +01:00
dependabot[bot]
4e0de93096 Bump aiohttp from 3.11.2 to 3.11.4 (#5434)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-19 08:26:46 +01:00
dependabot[bot]
3b6c5d5d33 Bump ruff from 0.7.3 to 0.7.4 (#5428)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 08:57:09 +01:00
dependabot[bot]
0843971e95 Bump dbus-fast from 2.24.3 to 2.24.4 (#5430)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 08:56:01 +01:00
dependabot[bot]
12d7496cd1 Bump codecov/codecov-action from 5.0.0 to 5.0.2 (#5427)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 08:55:35 +01:00
dependabot[bot]
ed34348c80 Bump coverage from 7.6.5 to 7.6.7 (#5429) 2024-11-18 07:59:39 +01:00
dependabot[bot]
fefb83558a Bump aiohttp from 3.10.11 to 3.11.2 (#5421)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-15 10:33:11 +01:00
dependabot[bot]
93a0ae4030 Bump coverage from 7.6.4 to 7.6.5 (#5424)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-15 09:44:08 +01:00
dependabot[bot]
5394cff296 Bump codecov/codecov-action from 4.6.0 to 5.0.0 (#5422)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-15 09:40:59 +01:00
4181 changed files with 9634 additions and 4664 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "Supervisor dev",
"image": "ghcr.io/home-assistant/devcontainer:supervisor",
"image": "ghcr.io/home-assistant/devcontainer:2-supervisor",
"containerEnv": {
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
},
@@ -44,5 +44,8 @@
}
}
},
"mounts": ["type=volume,target=/var/lib/docker"]
"mounts": [
"type=volume,target=/var/lib/docker",
"type=volume,target=/mnt/supervisor"
]
}

View File

@@ -26,7 +26,7 @@ body:
attributes:
label: What type of installation are you running?
description: >
If you don't know, can be found in [Settings -> System -> Repairs -> System Information](https://my.home-assistant.io/redirect/system_health/).
If you don't know, can be found in [Settings -> System -> Repairs -> (three dot menu) -> System Information](https://my.home-assistant.io/redirect/system_health/).
It is listed as the `Installation Type` value.
options:
- Home Assistant OS
@@ -72,9 +72,9 @@ body:
validations:
required: true
attributes:
label: System Health information
label: System information
description: >
System Health information can be found in the top right menu in [Settings -> System -> Repairs](https://my.home-assistant.io/redirect/repairs/).
The System information can be found in [Settings -> System -> Repairs -> (three dot menu) -> System Information](https://my.home-assistant.io/redirect/system_health/).
Click the copy button at the bottom of the pop-up and paste it here.
[![Open your Home Assistant instance and show health information about your system.](https://my.home-assistant.io/badges/system_health.svg)](https://my.home-assistant.io/redirect/system_health/)
@@ -83,8 +83,9 @@ body:
label: Supervisor diagnostics
placeholder: "drag-and-drop the diagnostics data file here (do not copy-and-paste the content)"
description: >-
Supervisor diagnostics can be found in [Settings -> Integrations](https://my.home-assistant.io/redirect/integrations/).
Find the card that says `Home Assistant Supervisor`, open its menu and select 'Download diagnostics'.
Supervisor diagnostics can be found in [Settings -> Devices & services](https://my.home-assistant.io/redirect/integrations/).
Find the card that says `Home Assistant Supervisor`, open it, and select the three dot menu of the Supervisor integration entry
and select 'Download diagnostics'.
**Please drag-and-drop the downloaded file into the textbox below. Do not copy and paste its contents.**
- type: textarea

View File

@@ -33,7 +33,7 @@ on:
- setup.py
env:
DEFAULT_PYTHON: "3.12"
DEFAULT_PYTHON: "3.13"
BUILD_NAME: supervisor
BUILD_TYPE: supervisor
@@ -108,7 +108,7 @@ jobs:
if: needs.init.outputs.requirements == 'true'
uses: home-assistant/wheels@2024.11.0
with:
abi: cp312
abi: cp313
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
@@ -125,13 +125,13 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.publish == 'true'
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@v5.4.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Install Cosign
if: needs.init.outputs.publish == 'true'
uses: sigstore/cosign-installer@v3.7.0
uses: sigstore/cosign-installer@v3.8.1
with:
cosign-release: "v2.4.0"
@@ -160,7 +160,7 @@ jobs:
run: echo "BUILD_ARGS=--test" >> $GITHUB_ENV
- name: Build supervisor
uses: home-assistant/builder@2024.08.2
uses: home-assistant/builder@2025.02.0
with:
args: |
$BUILD_ARGS \
@@ -207,7 +207,7 @@ jobs:
- name: Build the Supervisor
if: needs.init.outputs.publish != 'true'
uses: home-assistant/builder@2024.08.2
uses: home-assistant/builder@2025.02.0
with:
args: |
--test \

View File

@@ -8,7 +8,7 @@ on:
pull_request: ~
env:
DEFAULT_PYTHON: "3.12"
DEFAULT_PYTHON: "3.13"
PRE_COMMIT_CACHE: ~/.cache/pre-commit
concurrency:
@@ -28,12 +28,12 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python
id: python
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@v5.4.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
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@v4.1.2
uses: actions/cache@v4.2.2
with:
path: ${{ env.PRE_COMMIT_CACHE }}
lookup-only: true
@@ -69,13 +69,13 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@v5.4.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
with:
path: venv
key: |
@@ -87,7 +87,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: |
@@ -112,13 +112,13 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@v5.4.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
with:
path: venv
key: |
@@ -130,7 +130,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: |
@@ -170,13 +170,13 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@v5.4.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
with:
path: venv
key: |
@@ -188,7 +188,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: |
@@ -214,13 +214,13 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@v5.4.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
with:
path: venv
key: |
@@ -232,7 +232,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: |
@@ -258,13 +258,13 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@v5.4.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
with:
path: venv
key: |
@@ -274,6 +274,10 @@ jobs:
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Install additional system dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends libpulse0
- name: Register pylint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/pylint.json"
@@ -290,17 +294,17 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@v5.4.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Install Cosign
uses: sigstore/cosign-installer@v3.7.0
uses: sigstore/cosign-installer@v3.8.1
with:
cosign-release: "v2.4.0"
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
with:
path: venv
key: |
@@ -335,7 +339,7 @@ jobs:
-o console_output_style=count \
tests
- name: Upload coverage artifact
uses: actions/upload-artifact@v4.4.3
uses: actions/upload-artifact@v4.6.1
with:
name: coverage-${{ matrix.python-version }}
path: .coverage
@@ -349,13 +353,13 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@v5.4.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.2
with:
path: venv
key: |
@@ -366,7 +370,7 @@ jobs:
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Download all coverage artifacts
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@v4.1.9
- name: Combine coverage results
run: |
. venv/bin/activate
@@ -374,4 +378,4 @@ jobs:
coverage report
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4.6.0
uses: codecov/codecov-action@v5.4.0

View File

@@ -36,7 +36,7 @@ jobs:
echo "version=$datepre.$newpost" >> "$GITHUB_OUTPUT"
- name: Run Release Drafter
uses: release-drafter/release-drafter@v6.0.0
uses: release-drafter/release-drafter@v6.1.0
with:
tag: ${{ steps.version.outputs.version }}
name: ${{ steps.version.outputs.version }}

View File

@@ -12,7 +12,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Sentry Release
uses: getsentry/action-release@v1.7.0
uses: getsentry/action-release@v1.10.4
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}

View File

@@ -9,7 +9,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9.0.0
- uses: actions/stale@v9.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30

79
.github/workflows/update_frontend.yml vendored Normal file
View File

@@ -0,0 +1,79 @@
name: Update frontend
on:
schedule: # once a day
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
check-version:
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.check_version.outputs.skip || steps.check_existing_pr.outputs.skip }}
current_version: ${{ steps.check_version.outputs.current_version }}
latest_version: ${{ steps.latest_frontend_version.outputs.latest_tag }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get latest frontend release
id: latest_frontend_version
uses: abatilo/release-info-action@v1.3.3
with:
owner: home-assistant
repo: frontend
- name: Check if version is up to date
id: check_version
run: |
current_version="$(cat .ha-frontend-version)"
latest_version="${{ steps.latest_frontend_version.outputs.latest_tag }}"
echo "current_version=${current_version}" >> $GITHUB_OUTPUT
echo "LATEST_VERSION=${latest_version}" >> $GITHUB_ENV
if [[ ! "$current_version" < "$latest_version" ]]; then
echo "Frontend version is up to date"
echo "skip=true" >> $GITHUB_OUTPUT
fi
- name: Check if there is no open PR with this version
if: steps.check_version.outputs.skip != 'true'
id: check_existing_pr
env:
GH_TOKEN: ${{ github.token }}
run: |
PR=$(gh pr list --state open --base main --json title --search "Update frontend to version $LATEST_VERSION")
if [[ "$PR" != "[]" ]]; then
echo "Skipping - There is already a PR open for version $LATEST_VERSION"
echo "skip=true" >> $GITHUB_OUTPUT
fi
create-pr:
runs-on: ubuntu-latest
needs: check-version
if: needs.check-version.outputs.skip != 'true'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Clear www folder
run: |
rm -rf supervisor/api/panel/*
- name: Update version file
run: |
echo "${{ needs.check-version.outputs.latest_version }}" > .ha-frontend-version
- name: Download release assets
uses: robinraju/release-downloader@v1
with:
repository: 'home-assistant/frontend'
tag: ${{ needs.check-version.outputs.latest_version }}
fileName: home_assistant_frontend_supervisor-${{ needs.check-version.outputs.latest_version }}.tar.gz
extract: true
out-file-path: supervisor/api/panel/
- name: Create PR
uses: peter-evans/create-pull-request@v7
with:
commit-message: "Update frontend to version ${{ needs.check-version.outputs.latest_version }}"
branch: autoupdate-frontend
base: main
draft: true
sign-commits: true
title: "Update frontend to version ${{ needs.check-version.outputs.latest_version }}"
body: >
Update frontend from ${{ needs.check-version.outputs.current_version }} to
[${{ needs.check-version.outputs.latest_version }}](https://github.com/home-assistant/frontend/releases/tag/${{ needs.check-version.outputs.latest_version }})

4
.gitmodules vendored
View File

@@ -1,4 +0,0 @@
[submodule "home-assistant-polymer"]
path = home-assistant-polymer
url = https://github.com/home-assistant/home-assistant-polymer
branch = dev

1
.ha-frontend-version Normal file
View File

@@ -0,0 +1 @@
20250221.0

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.7
rev: v0.9.1
hooks:
- id: ruff
args:
@@ -8,7 +8,7 @@ repos:
- id: ruff-format
files: ^((supervisor|tests)/.+)?[^/]+\.py$
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v5.0.0
hooks:
- id: check-executables-have-shebangs
stages: [manual]

View File

@@ -9,7 +9,8 @@ ENV \
ARG \
COSIGN_VERSION \
BUILD_ARCH
BUILD_ARCH \
QEMU_CPU
# Install base
WORKDIR /usr/src
@@ -28,22 +29,23 @@ RUN \
\
&& curl -Lso /usr/bin/cosign "https://github.com/home-assistant/cosign/releases/download/${COSIGN_VERSION}/cosign_${BUILD_ARCH}" \
&& chmod a+x /usr/bin/cosign \
&& pip3 install uv==0.2.21
&& pip3 install uv==0.6.1
# Install requirements
COPY requirements.txt .
RUN \
if [ "${BUILD_ARCH}" = "i386" ]; then \
linux32 uv pip install --no-build -r requirements.txt; \
setarch="linux32"; \
else \
uv pip install --no-build -r requirements.txt; \
setarch=""; \
fi \
&& ${setarch} uv pip install --compile-bytecode --no-cache --no-build -r requirements.txt \
&& rm -f requirements.txt
# Install Home Assistant Supervisor
COPY . supervisor
RUN \
pip3 install -e ./supervisor \
uv pip install --no-cache -e ./supervisor \
&& python3 -m compileall ./supervisor/supervisor

View File

@@ -1,10 +1,10 @@
image: ghcr.io/home-assistant/{arch}-hassio-supervisor
build_from:
aarch64: ghcr.io/home-assistant/aarch64-base-python:3.12-alpine3.20
armhf: ghcr.io/home-assistant/armhf-base-python:3.12-alpine3.20
armv7: ghcr.io/home-assistant/armv7-base-python:3.12-alpine3.20
amd64: ghcr.io/home-assistant/amd64-base-python:3.12-alpine3.20
i386: ghcr.io/home-assistant/i386-base-python:3.12-alpine3.20
aarch64: ghcr.io/home-assistant/aarch64-base-python:3.13-alpine3.21
armhf: ghcr.io/home-assistant/armhf-base-python:3.13-alpine3.21
armv7: ghcr.io/home-assistant/armv7-base-python:3.13-alpine3.21
amd64: ghcr.io/home-assistant/amd64-base-python:3.13-alpine3.21
i386: ghcr.io/home-assistant/i386-base-python:3.13-alpine3.21
codenotary:
signer: notary@home-assistant.io
base_image: notary@home-assistant.io

View File

@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools~=75.5.0", "wheel~=0.45.0"]
requires = ["setuptools~=75.8.0", "wheel~=0.45.0"]
build-backend = "setuptools.build_meta"
[project]
@@ -12,7 +12,7 @@ authors = [
{ name = "The Home Assistant Authors", email = "hello@home-assistant.io" },
]
keywords = ["docker", "home-assistant", "api"]
requires-python = ">=3.12.0"
requires-python = ">=3.13.0"
[project.urls]
"Homepage" = "https://www.home-assistant.io/"
@@ -31,7 +31,7 @@ include-package-data = true
include = ["supervisor*"]
[tool.pylint.MAIN]
py-version = "3.12"
py-version = "3.13"
# Use a conservative default here; 2 should speed up most setups and not hurt
# any too bad. Override on command line as appropriate.
jobs = 2
@@ -147,7 +147,7 @@ disable = [
# "pointless-statement", # B018, ruff catches new occurrences, needs more work
"raise-missing-from", # TRY200
# "redefined-builtin", # A001, ruff is way more stricter, needs work
"try-except-raise", # TRY302
"try-except-raise", # TRY203
"unused-argument", # ARG001, we don't use it
"unused-format-string-argument", #F507
"unused-format-string-key", # F504
@@ -223,6 +223,7 @@ testpaths = ["tests"]
norecursedirs = [".git"]
log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s"
log_date_format = "%Y-%m-%d %H:%M:%S"
asyncio_default_fixture_loop_scope = "function"
asyncio_mode = "auto"
filterwarnings = [
"error",
@@ -289,7 +290,7 @@ lint.select = [
"T20", # flake8-print
"TID251", # Banned imports
"TRY004", # Prefer TypeError exception for invalid type
"TRY302", # Remove exception handler; error is immediately re-raised
"TRY203", # Remove exception handler; error is immediately re-raised
"UP", # pyupgrade
"W", # pycodestyle
]

View File

@@ -1,29 +1,29 @@
aiodns==3.2.0
aiohttp==3.10.11
aiohttp==3.11.13
atomicwrites-homeassistant==1.4.1
attrs==24.2.0
attrs==25.1.0
awesomeversion==24.6.0
brotli==1.1.0
ciso8601==2.3.1
ciso8601==2.3.2
colorlog==6.9.0
cpe==1.3.1
cryptography==43.0.3
debugpy==1.8.8
cryptography==44.0.1
debugpy==1.8.12
deepmerge==2.0
dirhash==0.5.0
docker==7.1.0
faust-cchardet==2.1.19
gitpython==3.1.43
jinja2==3.1.4
orjson==3.10.7
pulsectl==24.11.0
gitpython==3.1.44
jinja2==3.1.5
orjson==3.10.12
pulsectl==24.12.0
pyudev==0.24.3
PyYAML==6.0.2
requests==2.32.3
securetar==2024.2.1
sentry-sdk==2.18.0
setuptools==75.5.0
securetar==2025.2.1
sentry-sdk==2.22.0
setuptools==75.8.2
voluptuous==0.15.2
dbus-fast==2.24.3
dbus-fast==2.34.0
typing_extensions==4.12.2
zlib-fast==0.2.0
zlib-fast==0.2.1

View File

@@ -1,12 +1,13 @@
coverage==7.6.4
pre-commit==4.0.1
pylint==3.3.1
pytest-aiohttp==1.0.5
pytest-asyncio==0.23.6
astroid==3.3.8
coverage==7.6.12
pre-commit==4.1.0
pylint==3.3.4
pytest-aiohttp==1.1.0
pytest-asyncio==0.25.2
pytest-cov==6.0.0
pytest-timeout==2.3.1
pytest==8.3.3
ruff==0.7.3
pytest==8.3.4
ruff==0.9.8
time-machine==2.16.0
typing_extensions==4.12.2
urllib3==2.2.3
urllib3==2.3.0

View File

@@ -1,27 +0,0 @@
#!/bin/bash
source "/etc/supervisor_scripts/common"
set -e
# Update frontend
git submodule update --init --recursive --remote
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
cd home-assistant-polymer
nvm install
script/bootstrap
# Download translations
./script/translations_download
# build frontend
cd hassio
./script/build_hassio
# Copy frontend
rm -rf ../../supervisor/api/panel/*
cp -rf build/* ../../supervisor/api/panel/
# Reset frontend git
cd ..
git reset --hard HEAD

View File

@@ -19,7 +19,7 @@ def _get_supervisor_version():
for line in CONSTANTS.split("/n"):
if match := RE_SUPERVISOR_VERSION.match(line):
return match.group(1)
return "99.9.9dev"
return "9999.09.9.dev9999"
setup(

View File

@@ -54,8 +54,7 @@ if __name__ == "__main__":
loop.set_debug(coresys.config.debug)
loop.run_until_complete(coresys.core.connect())
bootstrap.supervisor_debugger(coresys)
bootstrap.migrate_system_env(coresys)
loop.run_until_complete(bootstrap.supervisor_debugger(coresys))
# Signal health startup for container
run_os_startup_check_cleanup()

View File

@@ -6,6 +6,7 @@ from contextlib import suppress
from copy import deepcopy
from datetime import datetime
import errno
from functools import partial
from ipaddress import IPv4Address
import logging
from pathlib import Path, PurePath
@@ -19,7 +20,7 @@ from typing import Any, Final
import aiohttp
from awesomeversion import AwesomeVersionCompareException
from deepmerge import Merger
from securetar import atomic_contents_add, secure_path
from securetar import AddFileError, atomic_contents_add, secure_path
import voluptuous as vol
from voluptuous.humanize import humanize_error
@@ -87,7 +88,7 @@ from ..store.addon import AddonStore
from ..utils import check_port
from ..utils.apparmor import adjust_profile
from ..utils.json import read_json_file, write_json_file
from ..utils.sentry import capture_exception
from ..utils.sentry import async_capture_exception
from .const import (
WATCHDOG_MAX_ATTEMPTS,
WATCHDOG_RETRY_SECONDS,
@@ -148,6 +149,9 @@ class Addon(AddonModel):
self._boot_failed_issue = Issue(
IssueType.BOOT_FAIL, ContextType.ADDON, reference=self.slug
)
self._device_access_missing_issue = Issue(
IssueType.DEVICE_ACCESS_MISSING, ContextType.ADDON, reference=self.slug
)
def __repr__(self) -> str:
"""Return internal representation."""
@@ -158,6 +162,11 @@ class Addon(AddonModel):
"""Get issue used if start on boot failed."""
return self._boot_failed_issue
@property
def device_access_missing_issue(self) -> Issue:
"""Get issue used if device access is missing and can't be automatically added."""
return self._device_access_missing_issue
@property
def state(self) -> AddonState:
"""Return state of the add-on."""
@@ -182,6 +191,13 @@ class Addon(AddonModel):
):
self.sys_resolution.dismiss_issue(self.boot_failed_issue)
# Dismiss device access missing issue if present and we stopped
if (
new_state == AddonState.STOPPED
and self.device_access_missing_issue in self.sys_resolution.issues
):
self.sys_resolution.dismiss_issue(self.device_access_missing_issue)
self.sys_homeassistant.websocket.send_message(
{
ATTR_TYPE: WSType.SUPERVISOR_EVENT,
@@ -227,7 +243,7 @@ class Addon(AddonModel):
await self.instance.install(self.version, default_image, arch=self.arch)
self.persist[ATTR_IMAGE] = default_image
self.save_persist()
await self.save_persist()
@property
def ip_address(self) -> IPv4Address:
@@ -651,9 +667,9 @@ class Addon(AddonModel):
"""Is add-on loaded."""
return bool(self._listeners)
def save_persist(self) -> None:
async def save_persist(self) -> None:
"""Save data of add-on."""
self.sys_addons.data.save_data()
await self.sys_addons.data.save_data()
async def watchdog_application(self) -> bool:
"""Return True if application is running."""
@@ -756,7 +772,7 @@ class Addon(AddonModel):
)
async def install(self) -> None:
"""Install and setup this addon."""
self.sys_addons.data.install(self.addon_store)
await self.sys_addons.data.install(self.addon_store)
await self.load()
if not self.path_data.is_dir():
@@ -774,7 +790,7 @@ class Addon(AddonModel):
self.latest_version, self.addon_store.image, arch=self.arch
)
except DockerError as err:
self.sys_addons.data.uninstall(self)
await self.sys_addons.data.uninstall(self)
raise AddonsError() from err
# Add to addon manager
@@ -823,23 +839,23 @@ class Addon(AddonModel):
# Cleanup Ingress dynamic port assignment
if self.with_ingress:
await self.sys_ingress.del_dynamic_port(self.slug)
self.sys_create_task(self.sys_ingress.reload())
self.sys_ingress.del_dynamic_port(self.slug)
# Cleanup discovery data
for message in self.sys_discovery.list_messages:
if message.addon != self.slug:
continue
self.sys_discovery.remove(message)
await self.sys_discovery.remove(message)
# Cleanup services data
for service in self.sys_services.list_services:
if self.slug not in service.active:
continue
service.del_service_data(self)
await service.del_service_data(self)
# Remove from addon manager
self.sys_addons.data.uninstall(self)
await self.sys_addons.data.uninstall(self)
self.sys_addons.local.pop(self.slug)
@Job(
@@ -868,7 +884,7 @@ class Addon(AddonModel):
try:
_LOGGER.info("Add-on '%s' successfully updated", self.slug)
self.sys_addons.data.update(store)
await self.sys_addons.data.update(store)
await self._check_ingress_port()
# Cleanup
@@ -909,7 +925,7 @@ class Addon(AddonModel):
except DockerError as err:
raise AddonsError() from err
self.sys_addons.data.update(self.addon_store)
await self.sys_addons.data.update(self.addon_store)
await self._check_ingress_port()
_LOGGER.info("Add-on '%s' successfully rebuilt", self.slug)
@@ -961,11 +977,21 @@ class Addon(AddonModel):
return
# Need install/update
with TemporaryDirectory(dir=self.sys_config.path_tmp) as tmp_folder:
profile_file = Path(tmp_folder, "apparmor.txt")
tmp_folder: TemporaryDirectory | None = None
def install_update_profile() -> Path:
nonlocal tmp_folder
tmp_folder = TemporaryDirectory(dir=self.sys_config.path_tmp)
profile_file = Path(tmp_folder.name, "apparmor.txt")
adjust_profile(self.slug, self.path_apparmor, profile_file)
return profile_file
try:
profile_file = await self.sys_run_in_executor(install_update_profile)
await self.sys_host.apparmor.load_profile(self.slug, profile_file)
finally:
if tmp_folder:
await self.sys_run_in_executor(tmp_folder.cleanup)
async def uninstall_apparmor(self) -> None:
"""Remove AppArmor profile for Add-on."""
@@ -1037,7 +1063,7 @@ class Addon(AddonModel):
# Access Token
self.persist[ATTR_ACCESS_TOKEN] = secrets.token_hex(56)
self.save_persist()
await self.save_persist()
# Options
await self.write_options()
@@ -1192,6 +1218,25 @@ class Addon(AddonModel):
await self._backup_command(self.backup_post)
return None
def _is_excluded_by_filter(
self, origin_path: Path, arcname: str, item_arcpath: PurePath
) -> bool:
"""Filter out files from backup based on filters provided by addon developer.
This tests the dev provided filters against the full path of the file as
Supervisor sees them using match. This is done for legacy reasons, testing
against the relative path makes more sense and may be changed in the future.
"""
full_path = origin_path / item_arcpath.relative_to(arcname)
for exclude in self.backup_exclude:
if not full_path.match(exclude):
continue
_LOGGER.debug("Ignoring %s because of %s", full_path, exclude)
return True
return False
@Job(
name="addon_backup",
limit=JobExecutionLimit.GROUP_ONCE,
@@ -1203,46 +1248,45 @@ class Addon(AddonModel):
Returns a Task that completes when addon has state 'started' (see start)
for cold backup. Else nothing is returned.
"""
wait_for_start: Awaitable[None] | None = None
with TemporaryDirectory(dir=self.sys_config.path_tmp) as temp:
temp_path = Path(temp)
def _addon_backup(
store_image: bool,
metadata: dict[str, Any],
apparmor_profile: str | None,
addon_config_used: bool,
):
"""Start the backup process."""
with TemporaryDirectory(dir=self.sys_config.path_tmp) as temp:
temp_path = Path(temp)
# store local image
if self.need_build:
# store local image
if store_image:
try:
self.instance.export_image(temp_path.joinpath("image.tar"))
except DockerError as err:
raise AddonsError() from err
# Store local configs/state
try:
await self.instance.export_image(temp_path.joinpath("image.tar"))
except DockerError as err:
raise AddonsError() from err
data = {
ATTR_USER: self.persist,
ATTR_SYSTEM: self.data,
ATTR_VERSION: self.version,
ATTR_STATE: _MAP_ADDON_STATE.get(self.state, self.state),
}
# Store local configs/state
try:
write_json_file(temp_path.joinpath("addon.json"), data)
except ConfigurationFileError as err:
raise AddonsError(
f"Can't save meta for {self.slug}", _LOGGER.error
) from err
# Store AppArmor Profile
if self.sys_host.apparmor.exists(self.slug):
profile = temp_path.joinpath("apparmor.txt")
try:
await self.sys_host.apparmor.backup_profile(self.slug, profile)
except HostAppArmorError as err:
write_json_file(temp_path.joinpath("addon.json"), metadata)
except ConfigurationFileError as err:
raise AddonsError(
"Can't backup AppArmor profile", _LOGGER.error
f"Can't save meta for {self.slug}", _LOGGER.error
) from err
# write into tarfile
def _write_tarfile():
"""Write tar inside loop."""
# Store AppArmor Profile
if apparmor_profile:
profile_backup_file = temp_path.joinpath("apparmor.txt")
try:
self.sys_host.apparmor.backup_profile(
apparmor_profile, profile_backup_file
)
except HostAppArmorError as err:
raise AddonsError(
"Can't backup AppArmor profile", _LOGGER.error
) from err
# Write tarfile
with tar_file as backup:
# Backup metadata
backup.add(temp, arcname=".")
@@ -1251,32 +1295,56 @@ class Addon(AddonModel):
atomic_contents_add(
backup,
self.path_data,
excludes=self.backup_exclude,
file_filter=partial(
self._is_excluded_by_filter, self.path_data, "data"
),
arcname="data",
)
# Backup config
if self.addon_config_used:
if addon_config_used:
atomic_contents_add(
backup,
self.path_config,
excludes=self.backup_exclude,
file_filter=partial(
self._is_excluded_by_filter, self.path_config, "config"
),
arcname="config",
)
is_running = await self.begin_backup()
try:
_LOGGER.info("Building backup for add-on %s", self.slug)
await self.sys_run_in_executor(_write_tarfile)
except (tarfile.TarError, OSError) as err:
raise AddonsError(
f"Can't write tarfile {tar_file}: {err}", _LOGGER.error
) from err
finally:
if is_running:
wait_for_start = await self.end_backup()
wait_for_start: Awaitable[None] | None = None
data = {
ATTR_USER: self.persist,
ATTR_SYSTEM: self.data,
ATTR_VERSION: self.version,
ATTR_STATE: _MAP_ADDON_STATE.get(self.state, self.state),
}
apparmor_profile = (
self.slug if self.sys_host.apparmor.exists(self.slug) else None
)
was_running = await self.begin_backup()
try:
_LOGGER.info("Building backup for add-on %s", self.slug)
await self.sys_run_in_executor(
partial(
_addon_backup,
store_image=self.need_build,
metadata=data,
apparmor_profile=apparmor_profile,
addon_config_used=self.addon_config_used,
)
)
_LOGGER.info("Finish backup for addon %s", self.slug)
except (tarfile.TarError, OSError, AddFileError) as err:
raise AddonsError(
f"Can't write tarfile {tar_file}: {err}", _LOGGER.error
) from err
finally:
if was_running:
wait_for_start = await self.end_backup()
_LOGGER.info("Finish backup for addon %s", self.slug)
return wait_for_start
@Job(
@@ -1291,30 +1359,36 @@ class Addon(AddonModel):
if addon is started after restore. Else nothing is returned.
"""
wait_for_start: Awaitable[None] | None = None
with TemporaryDirectory(dir=self.sys_config.path_tmp) as temp:
# extract backup
def _extract_tarfile():
"""Extract tar backup."""
# Extract backup
def _extract_tarfile() -> tuple[TemporaryDirectory, dict[str, Any]]:
"""Extract tar backup."""
tmp = TemporaryDirectory(dir=self.sys_config.path_tmp)
try:
with tar_file as backup:
backup.extractall(
path=Path(temp),
path=tmp.name,
members=secure_path(backup),
filter="fully_trusted",
)
try:
await self.sys_run_in_executor(_extract_tarfile)
except tarfile.TarError as err:
raise AddonsError(
f"Can't read tarfile {tar_file}: {err}", _LOGGER.error
) from err
data = read_json_file(Path(tmp.name, "addon.json"))
except:
tmp.cleanup()
raise
# Read backup data
try:
data = read_json_file(Path(temp, "addon.json"))
except ConfigurationFileError as err:
raise AddonsError() from err
return tmp, data
try:
tmp, data = await self.sys_run_in_executor(_extract_tarfile)
except tarfile.TarError as err:
raise AddonsError(
f"Can't read tarfile {tar_file}: {err}", _LOGGER.error
) from err
except ConfigurationFileError as err:
raise AddonsError() from err
try:
# Validate
try:
data = SCHEMA_ADDON_BACKUP(data)
@@ -1334,7 +1408,7 @@ class Addon(AddonModel):
# Restore local add-on information
_LOGGER.info("Restore config for addon %s", self.slug)
restore_image = self._image(data[ATTR_SYSTEM])
self.sys_addons.data.restore(
await self.sys_addons.data.restore(
self.slug, data[ATTR_USER], data[ATTR_SYSTEM], restore_image
)
@@ -1348,7 +1422,7 @@ class Addon(AddonModel):
if not await self.instance.exists():
_LOGGER.info("Restore/Install of image for addon %s", self.slug)
image_file = Path(temp, "image.tar")
image_file = Path(tmp.name, "image.tar")
if image_file.is_file():
with suppress(DockerError):
await self.instance.import_image(image_file)
@@ -1367,13 +1441,13 @@ class Addon(AddonModel):
# Restore data and config
def _restore_data():
"""Restore data and config."""
temp_data = Path(temp, "data")
temp_data = Path(tmp.name, "data")
if temp_data.is_dir():
shutil.copytree(temp_data, self.path_data, symlinks=True)
else:
self.path_data.mkdir()
temp_config = Path(temp, "config")
temp_config = Path(tmp.name, "config")
if temp_config.is_dir():
shutil.copytree(temp_config, self.path_config, symlinks=True)
elif self.addon_config_used:
@@ -1393,7 +1467,7 @@ class Addon(AddonModel):
) from err
# Restore AppArmor
profile_file = Path(temp, "apparmor.txt")
profile_file = Path(tmp.name, "apparmor.txt")
if profile_file.exists():
try:
await self.sys_host.apparmor.load_profile(
@@ -1401,7 +1475,8 @@ class Addon(AddonModel):
)
except HostAppArmorError as err:
_LOGGER.error(
"Can't restore AppArmor profile for add-on %s", self.slug
"Can't restore AppArmor profile for add-on %s",
self.slug,
)
raise AddonsError() from err
@@ -1413,7 +1488,8 @@ class Addon(AddonModel):
# Run add-on
if data[ATTR_STATE] == AddonState.STARTED:
wait_for_start = await self.start()
finally:
tmp.cleanup()
_LOGGER.info("Finished restore for add-on %s", self.slug)
return wait_for_start
@@ -1454,7 +1530,7 @@ class Addon(AddonModel):
except AddonsError as err:
attempts = attempts + 1
_LOGGER.error("Watchdog restart of addon %s failed!", self.name)
capture_exception(err)
await async_capture_exception(err)
else:
break

View File

@@ -34,16 +34,29 @@ class AddonBuild(FileConfiguration, CoreSysAttributes):
self.coresys: CoreSys = coresys
self.addon = addon
# Search for build file later in executor
super().__init__(None, SCHEMA_BUILD_CONFIG)
def _get_build_file(self) -> Path:
"""Get build file.
Must be run in executor.
"""
try:
build_file = find_one_filetype(
return find_one_filetype(
self.addon.path_location, "build", FILE_SUFFIX_CONFIGURATION
)
except ConfigurationFileError:
build_file = self.addon.path_location / "build.json"
return self.addon.path_location / "build.json"
super().__init__(build_file, SCHEMA_BUILD_CONFIG)
async def read_data(self) -> None:
"""Load data from file."""
if not self._file:
self._file = await self.sys_run_in_executor(self._get_build_file)
def save_data(self):
await super().read_data()
async def save_data(self):
"""Ignore save function."""
raise RuntimeError()

View File

@@ -38,7 +38,7 @@ class AddonsData(FileConfiguration, CoreSysAttributes):
"""Return local add-on data."""
return self._data[ATTR_SYSTEM]
def install(self, addon: AddonStore) -> None:
async def install(self, addon: AddonStore) -> None:
"""Set addon as installed."""
self.system[addon.slug] = deepcopy(addon.data)
self.user[addon.slug] = {
@@ -46,26 +46,28 @@ class AddonsData(FileConfiguration, CoreSysAttributes):
ATTR_VERSION: addon.version,
ATTR_IMAGE: addon.image,
}
self.save_data()
await self.save_data()
def uninstall(self, addon: Addon) -> None:
async def uninstall(self, addon: Addon) -> None:
"""Set add-on as uninstalled."""
self.system.pop(addon.slug, None)
self.user.pop(addon.slug, None)
self.save_data()
await self.save_data()
def update(self, addon: AddonStore) -> None:
async def update(self, addon: AddonStore) -> None:
"""Update version of add-on."""
self.system[addon.slug] = deepcopy(addon.data)
self.user[addon.slug].update(
{ATTR_VERSION: addon.version, ATTR_IMAGE: addon.image}
)
self.save_data()
await self.save_data()
def restore(self, slug: str, user: Config, system: Config, image: str) -> None:
async def restore(
self, slug: str, user: Config, system: Config, image: str
) -> None:
"""Restore data to add-on."""
self.user[slug] = deepcopy(user)
self.system[slug] = deepcopy(system)
self.user[slug][ATTR_IMAGE] = image
self.save_data()
await self.save_data()

View File

@@ -5,7 +5,7 @@ from collections.abc import Awaitable
from contextlib import suppress
import logging
import tarfile
from typing import Union
from typing import Self, Union
from attr import evolve
@@ -23,7 +23,7 @@ from ..exceptions import (
from ..jobs.decorator import Job, JobCondition
from ..resolution.const import ContextType, IssueType, SuggestionType
from ..store.addon import AddonStore
from ..utils.sentry import capture_exception
from ..utils.sentry import async_capture_exception
from .addon import Addon
from .const import ADDON_UPDATE_CONDITIONS
from .data import AddonsData
@@ -74,6 +74,11 @@ class AddonManager(CoreSysAttributes):
return addon
return None
async def load_config(self) -> Self:
"""Load config in executor."""
await self.data.read_data()
return self
async def load(self) -> None:
"""Start up add-on management."""
# Refresh cache for all store addons
@@ -165,7 +170,7 @@ class AddonManager(CoreSysAttributes):
await addon.stop()
except Exception as err: # pylint: disable=broad-except
_LOGGER.warning("Can't stop Add-on %s: %s", addon.slug, err)
capture_exception(err)
await async_capture_exception(err)
@Job(
name="addon_manager_install",
@@ -383,7 +388,7 @@ class AddonManager(CoreSysAttributes):
reference=addon.slug,
suggestions=[SuggestionType.EXECUTE_REPAIR],
)
capture_exception(err)
await async_capture_exception(err)
else:
add_host_coros.append(
self.sys_plugins.dns.add_host(

View File

@@ -47,7 +47,7 @@ from ..const import (
ATTR_JOURNALD,
ATTR_KERNEL_MODULES,
ATTR_LEGACY,
ATTR_LOCATON,
ATTR_LOCATION,
ATTR_MACHINE,
ATTR_MAP,
ATTR_NAME,
@@ -210,18 +210,6 @@ class AddonModel(JobGroup, ABC):
"""Return description of add-on."""
return self.data[ATTR_DESCRIPTON]
@property
def long_description(self) -> str | None:
"""Return README.md as long_description."""
readme = Path(self.path_location, "README.md")
# If readme not exists
if not readme.exists():
return None
# Return data
return readme.read_text(encoding="utf-8")
@property
def repository(self) -> str:
"""Return repository of add-on."""
@@ -581,7 +569,7 @@ class AddonModel(JobGroup, ABC):
@property
def path_location(self) -> Path:
"""Return path to this add-on."""
return Path(self.data[ATTR_LOCATON])
return Path(self.data[ATTR_LOCATION])
@property
def path_icon(self) -> Path:
@@ -646,6 +634,21 @@ class AddonModel(JobGroup, ABC):
"""Return breaking versions of addon."""
return self.data[ATTR_BREAKING_VERSIONS]
async def long_description(self) -> str | None:
"""Return README.md as long_description."""
def read_readme() -> str | None:
readme = Path(self.path_location, "README.md")
# If readme not exists
if not readme.exists():
return None
# Return data
return readme.read_text(encoding="utf-8")
return await self.sys_run_in_executor(read_readme)
def refresh_path_cache(self) -> Awaitable[None]:
"""Refresh cache of existing paths."""

View File

@@ -46,6 +46,7 @@ def rating_security(addon: AddonModel) -> int:
privilege in addon.privileged
for privilege in (
Capabilities.BPF,
Capabilities.CHECKPOINT_RESTORE,
Capabilities.DAC_READ_SEARCH,
Capabilities.NET_ADMIN,
Capabilities.NET_RAW,

View File

@@ -55,7 +55,7 @@ from ..const import (
ATTR_KERNEL_MODULES,
ATTR_LABELS,
ATTR_LEGACY,
ATTR_LOCATON,
ATTR_LOCATION,
ATTR_MACHINE,
ATTR_MAP,
ATTR_NAME,
@@ -483,7 +483,7 @@ SCHEMA_ADDON_SYSTEM = vol.All(
_migrate_addon_config(),
_SCHEMA_ADDON_CONFIG.extend(
{
vol.Required(ATTR_LOCATON): str,
vol.Required(ATTR_LOCATION): str,
vol.Required(ATTR_REPOSITORY): str,
vol.Required(ATTR_TRANSLATIONS, default=dict): {
str: SCHEMA_ADDON_TRANSLATIONS

View File

@@ -10,7 +10,7 @@ from aiohttp import web
from ..const import AddonState
from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import APIAddonNotInstalled, HostNotSupportedError
from ..utils.sentry import capture_exception
from ..utils.sentry import async_capture_exception
from .addons import APIAddons
from .audio import APIAudio
from .auth import APIAuth
@@ -412,7 +412,7 @@ class RestAPI(CoreSysAttributes):
if not isinstance(err, HostNotSupportedError):
# No need to capture HostNotSupportedError to Sentry, the cause
# is known and reported to the user using the resolution center.
capture_exception(err)
await async_capture_exception(err)
kwargs.pop("follow", None) # Follow is not supported for Docker logs
return await api_supervisor.logs(*args, **kwargs)

View File

@@ -106,6 +106,7 @@ from ..exceptions import (
APIAddonNotInstalled,
APIError,
APIForbidden,
APINotFound,
PwnedError,
PwnedSecret,
)
@@ -161,7 +162,7 @@ class APIAddons(CoreSysAttributes):
addon = self.sys_addons.get(addon_slug)
if not addon:
raise APIError(f"Addon {addon_slug} does not exist")
raise APINotFound(f"Addon {addon_slug} does not exist")
if not isinstance(addon, Addon) or not addon.is_installed:
raise APIAddonNotInstalled("Addon is not installed")
@@ -211,7 +212,7 @@ class APIAddons(CoreSysAttributes):
ATTR_HOSTNAME: addon.hostname,
ATTR_DNS: addon.dns,
ATTR_DESCRIPTON: addon.description,
ATTR_LONG_DESCRIPTION: addon.long_description,
ATTR_LONG_DESCRIPTION: await addon.long_description(),
ATTR_ADVANCED: addon.advanced,
ATTR_STAGE: addon.stage,
ATTR_REPOSITORY: addon.repository,
@@ -321,7 +322,7 @@ class APIAddons(CoreSysAttributes):
if ATTR_WATCHDOG in body:
addon.watchdog = body[ATTR_WATCHDOG]
addon.save_persist()
await addon.save_persist()
@api_process
async def sys_options(self, request: web.Request) -> None:
@@ -335,7 +336,7 @@ class APIAddons(CoreSysAttributes):
if ATTR_SYSTEM_MANAGED_CONFIG_ENTRY in body:
addon.system_managed_config_entry = body[ATTR_SYSTEM_MANAGED_CONFIG_ENTRY]
addon.save_persist()
await addon.save_persist()
@api_process
async def options_validate(self, request: web.Request) -> None:
@@ -401,7 +402,7 @@ class APIAddons(CoreSysAttributes):
_LOGGER.warning("Changing protected flag for %s!", addon.slug)
addon.protected = body[ATTR_PROTECTED]
addon.save_persist()
await addon.save_persist()
@api_process
async def stats(self, request: web.Request) -> dict[str, Any]:

View File

@@ -99,7 +99,7 @@ class APIAuth(CoreSysAttributes):
@api_process
async def cache(self, request: web.Request) -> None:
"""Process cache reset request."""
self.sys_auth.reset_data()
await self.sys_auth.reset_data()
@api_process
async def list_users(self, request: web.Request) -> dict[str, list[dict[str, Any]]]:

View File

@@ -1,8 +1,11 @@
"""Backups RESTful API."""
from __future__ import annotations
import asyncio
from collections.abc import Callable
import errno
from io import IOBase
import logging
from pathlib import Path
import re
@@ -12,8 +15,10 @@ from typing import Any
from aiohttp import web
from aiohttp.hdrs import CONTENT_DISPOSITION
import voluptuous as vol
from voluptuous.humanize import humanize_error
from ..backups.backup import Backup
from ..backups.const import LOCATION_CLOUD_BACKUP, LOCATION_TYPE
from ..backups.validate import ALL_FOLDERS, FOLDER_HOMEASSISTANT, days_until_stale
from ..const import (
ATTR_ADDONS,
@@ -22,44 +27,81 @@ from ..const import (
ATTR_CONTENT,
ATTR_DATE,
ATTR_DAYS_UNTIL_STALE,
ATTR_EXTRA,
ATTR_FILENAME,
ATTR_FOLDERS,
ATTR_HOMEASSISTANT,
ATTR_HOMEASSISTANT_EXCLUDE_DATABASE,
ATTR_LOCATON,
ATTR_JOB_ID,
ATTR_LOCATION,
ATTR_NAME,
ATTR_PASSWORD,
ATTR_PATH,
ATTR_PROTECTED,
ATTR_REPOSITORIES,
ATTR_SIZE,
ATTR_SIZE_BYTES,
ATTR_SLUG,
ATTR_SUPERVISOR_VERSION,
ATTR_TIMEOUT,
ATTR_TYPE,
ATTR_VERSION,
REQUEST_FROM,
BusEvent,
CoreState,
)
from ..coresys import CoreSysAttributes
from ..exceptions import APIError
from ..exceptions import APIError, APIForbidden, APINotFound
from ..jobs import JobSchedulerOptions
from ..mounts.const import MountUsage
from ..resolution.const import UnhealthyReason
from .const import ATTR_BACKGROUND, ATTR_JOB_ID, CONTENT_TYPE_TAR
from .const import (
ATTR_ADDITIONAL_LOCATIONS,
ATTR_BACKGROUND,
ATTR_LOCATION_ATTRIBUTES,
ATTR_LOCATIONS,
CONTENT_TYPE_TAR,
)
from .utils import api_process, api_validate
_LOGGER: logging.Logger = logging.getLogger(__name__)
ALL_ADDONS_FLAG = "ALL"
LOCATION_LOCAL = ".local"
RE_SLUGIFY_NAME = re.compile(r"[^A-Za-z0-9]+")
RE_BACKUP_FILENAME = re.compile(r"^[^\\\/]+\.tar$")
# Backwards compatible
# Remove: 2022.08
_ALL_FOLDERS = ALL_FOLDERS + [FOLDER_HOMEASSISTANT]
def _ensure_list(item: Any) -> list:
"""Ensure value is a list."""
if not isinstance(item, list):
return [item]
return item
def _convert_local_location(item: str | None) -> str | None:
"""Convert local location value."""
if item in {LOCATION_LOCAL, ""}:
return None
return item
# pylint: disable=no-value-for-parameter
SCHEMA_FOLDERS = vol.All([vol.In(_ALL_FOLDERS)], vol.Unique())
SCHEMA_LOCATION = vol.All(vol.Maybe(str), _convert_local_location)
SCHEMA_LOCATION_LIST = vol.All(_ensure_list, [SCHEMA_LOCATION], vol.Unique())
SCHEMA_RESTORE_FULL = vol.Schema(
{
vol.Optional(ATTR_PASSWORD): vol.Maybe(str),
vol.Optional(ATTR_BACKGROUND, default=False): vol.Boolean(),
vol.Optional(ATTR_LOCATION): SCHEMA_LOCATION,
}
)
@@ -67,40 +109,36 @@ SCHEMA_RESTORE_PARTIAL = SCHEMA_RESTORE_FULL.extend(
{
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
vol.Optional(ATTR_ADDONS): vol.All([str], vol.Unique()),
vol.Optional(ATTR_FOLDERS): vol.All([vol.In(_ALL_FOLDERS)], vol.Unique()),
vol.Optional(ATTR_FOLDERS): SCHEMA_FOLDERS,
}
)
SCHEMA_BACKUP_FULL = vol.Schema(
{
vol.Optional(ATTR_NAME): str,
vol.Optional(ATTR_FILENAME): vol.Match(RE_BACKUP_FILENAME),
vol.Optional(ATTR_PASSWORD): vol.Maybe(str),
vol.Optional(ATTR_COMPRESSED): vol.Maybe(vol.Boolean()),
vol.Optional(ATTR_LOCATON): vol.Maybe(str),
vol.Optional(ATTR_LOCATION): SCHEMA_LOCATION_LIST,
vol.Optional(ATTR_HOMEASSISTANT_EXCLUDE_DATABASE): vol.Boolean(),
vol.Optional(ATTR_BACKGROUND, default=False): vol.Boolean(),
vol.Optional(ATTR_EXTRA): dict,
}
)
SCHEMA_BACKUP_PARTIAL = SCHEMA_BACKUP_FULL.extend(
{
vol.Optional(ATTR_ADDONS): vol.All([str], vol.Unique()),
vol.Optional(ATTR_FOLDERS): vol.All([vol.In(_ALL_FOLDERS)], vol.Unique()),
vol.Optional(ATTR_ADDONS): vol.Or(
ALL_ADDONS_FLAG, vol.All([str], vol.Unique())
),
vol.Optional(ATTR_FOLDERS): SCHEMA_FOLDERS,
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
}
)
SCHEMA_OPTIONS = vol.Schema(
{
vol.Optional(ATTR_DAYS_UNTIL_STALE): days_until_stale,
}
)
SCHEMA_FREEZE = vol.Schema(
{
vol.Optional(ATTR_TIMEOUT): vol.All(int, vol.Range(min=1)),
}
)
SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_DAYS_UNTIL_STALE): days_until_stale})
SCHEMA_FREEZE = vol.Schema({vol.Optional(ATTR_TIMEOUT): vol.All(int, vol.Range(min=1))})
SCHEMA_REMOVE = vol.Schema({vol.Optional(ATTR_LOCATION): SCHEMA_LOCATION_LIST})
class APIBackups(CoreSysAttributes):
@@ -110,9 +148,19 @@ class APIBackups(CoreSysAttributes):
"""Return backup, throw an exception if it doesn't exist."""
backup = self.sys_backups.get(request.match_info.get("slug"))
if not backup:
raise APIError("Backup does not exist")
raise APINotFound("Backup does not exist")
return backup
def _make_location_attributes(self, backup: Backup) -> dict[str, dict[str, Any]]:
"""Make location attributes dictionary."""
return {
loc if loc else LOCATION_LOCAL: {
ATTR_PROTECTED: backup.all_locations[loc][ATTR_PROTECTED],
ATTR_SIZE_BYTES: backup.all_locations[loc][ATTR_SIZE_BYTES],
}
for loc in backup.locations
}
def _list_backups(self):
"""Return list of backups."""
return [
@@ -122,8 +170,11 @@ class APIBackups(CoreSysAttributes):
ATTR_DATE: backup.date,
ATTR_TYPE: backup.sys_type,
ATTR_SIZE: backup.size,
ATTR_LOCATON: backup.location,
ATTR_SIZE_BYTES: backup.size_bytes,
ATTR_LOCATION: backup.location,
ATTR_LOCATIONS: backup.locations,
ATTR_PROTECTED: backup.protected,
ATTR_LOCATION_ATTRIBUTES: self._make_location_attributes(backup),
ATTR_COMPRESSED: backup.compressed,
ATTR_CONTENT: {
ATTR_HOMEASSISTANT: backup.homeassistant_version is not None,
@@ -132,6 +183,7 @@ class APIBackups(CoreSysAttributes):
},
}
for backup in self.sys_backups.list_backups
if backup.location != LOCATION_CLOUD_BACKUP
]
@api_process
@@ -161,7 +213,7 @@ class APIBackups(CoreSysAttributes):
if ATTR_DAYS_UNTIL_STALE in body:
self.sys_backups.days_until_stale = body[ATTR_DAYS_UNTIL_STALE]
self.sys_backups.save_data()
await self.sys_backups.save_data()
@api_process
async def reload(self, _):
@@ -191,30 +243,53 @@ class APIBackups(CoreSysAttributes):
ATTR_NAME: backup.name,
ATTR_DATE: backup.date,
ATTR_SIZE: backup.size,
ATTR_SIZE_BYTES: backup.size_bytes,
ATTR_COMPRESSED: backup.compressed,
ATTR_PROTECTED: backup.protected,
ATTR_LOCATION_ATTRIBUTES: self._make_location_attributes(backup),
ATTR_SUPERVISOR_VERSION: backup.supervisor_version,
ATTR_HOMEASSISTANT: backup.homeassistant_version,
ATTR_LOCATON: backup.location,
ATTR_LOCATION: backup.location,
ATTR_LOCATIONS: backup.locations,
ATTR_ADDONS: data_addons,
ATTR_REPOSITORIES: backup.repositories,
ATTR_FOLDERS: backup.folders,
ATTR_HOMEASSISTANT_EXCLUDE_DATABASE: backup.homeassistant_exclude_database,
ATTR_EXTRA: backup.extra,
}
def _location_to_mount(self, body: dict[str, Any]) -> dict[str, Any]:
"""Change location field to mount if necessary."""
if not body.get(ATTR_LOCATON):
return body
def _location_to_mount(self, location: str | None) -> LOCATION_TYPE:
"""Convert a single location to a mount if possible."""
if not location or location == LOCATION_CLOUD_BACKUP:
return location
body[ATTR_LOCATON] = self.sys_mounts.get(body[ATTR_LOCATON])
if body[ATTR_LOCATON].usage != MountUsage.BACKUP:
mount = self.sys_mounts.get(location)
if mount.usage != MountUsage.BACKUP:
raise APIError(
f"Mount {body[ATTR_LOCATON].name} is not used for backups, cannot backup to there"
f"Mount {mount.name} is not used for backups, cannot backup to there"
)
return mount
def _location_field_to_mount(self, body: dict[str, Any]) -> dict[str, Any]:
"""Change location field to mount if necessary."""
body[ATTR_LOCATION] = self._location_to_mount(body.get(ATTR_LOCATION))
return body
def _validate_cloud_backup_location(
self, request: web.Request, location: list[str | None] | str | None
) -> None:
"""Cloud backup location is only available to Home Assistant."""
if not isinstance(location, list):
location = [location]
if (
LOCATION_CLOUD_BACKUP in location
and request.get(REQUEST_FROM) != self.sys_homeassistant
):
raise APIForbidden(
f"Location {LOCATION_CLOUD_BACKUP} is only available for Home Assistant"
)
async def _background_backup_task(
self, backup_method: Callable, *args, **kwargs
) -> tuple[asyncio.Task, str]:
@@ -234,24 +309,42 @@ class APIBackups(CoreSysAttributes):
BusEvent.SUPERVISOR_STATE_CHANGE, release_on_freeze
)
try:
await asyncio.wait(
event_task = self.sys_create_task(event.wait())
_, pending = await asyncio.wait(
(
backup_task,
self.sys_create_task(event.wait()),
event_task,
),
return_when=asyncio.FIRST_COMPLETED,
)
# It seems backup returned early (error or something), make sure to cancel
# the event task to avoid "Task was destroyed but it is pending!" errors.
if event_task in pending:
event_task.cancel()
return (backup_task, job.uuid)
finally:
self.sys_bus.remove_listener(listener)
@api_process
async def backup_full(self, request):
async def backup_full(self, request: web.Request):
"""Create full backup."""
body = await api_validate(SCHEMA_BACKUP_FULL, request)
locations: list[LOCATION_TYPE] | None = None
if ATTR_LOCATION in body:
location_names: list[str | None] = body.pop(ATTR_LOCATION)
self._validate_cloud_backup_location(request, location_names)
locations = [
self._location_to_mount(location) for location in location_names
]
body[ATTR_LOCATION] = locations.pop(0)
if locations:
body[ATTR_ADDITIONAL_LOCATIONS] = locations
background = body.pop(ATTR_BACKGROUND)
backup_task, job_id = await self._background_backup_task(
self.sys_backups.do_backup_full, **self._location_to_mount(body)
self.sys_backups.do_backup_full, **body
)
if background and not backup_task.done():
@@ -266,12 +359,28 @@ class APIBackups(CoreSysAttributes):
)
@api_process
async def backup_partial(self, request):
async def backup_partial(self, request: web.Request):
"""Create a partial backup."""
body = await api_validate(SCHEMA_BACKUP_PARTIAL, request)
locations: list[LOCATION_TYPE] | None = None
if ATTR_LOCATION in body:
location_names: list[str | None] = body.pop(ATTR_LOCATION)
self._validate_cloud_backup_location(request, location_names)
locations = [
self._location_to_mount(location) for location in location_names
]
body[ATTR_LOCATION] = locations.pop(0)
if locations:
body[ATTR_ADDITIONAL_LOCATIONS] = locations
if body.get(ATTR_ADDONS) == ALL_ADDONS_FLAG:
body[ATTR_ADDONS] = list(self.sys_addons.local)
background = body.pop(ATTR_BACKGROUND)
backup_task, job_id = await self._background_backup_task(
self.sys_backups.do_backup_partial, **self._location_to_mount(body)
self.sys_backups.do_backup_partial, **body
)
if background and not backup_task.done():
@@ -286,10 +395,13 @@ class APIBackups(CoreSysAttributes):
)
@api_process
async def restore_full(self, request):
async def restore_full(self, request: web.Request):
"""Full restore of a backup."""
backup = self._extract_slug(request)
body = await api_validate(SCHEMA_RESTORE_FULL, request)
self._validate_cloud_backup_location(
request, body.get(ATTR_LOCATION, backup.location)
)
background = body.pop(ATTR_BACKGROUND)
restore_task, job_id = await self._background_backup_task(
self.sys_backups.do_restore_full, backup, **body
@@ -303,10 +415,13 @@ class APIBackups(CoreSysAttributes):
)
@api_process
async def restore_partial(self, request):
async def restore_partial(self, request: web.Request):
"""Partial restore a backup."""
backup = self._extract_slug(request)
body = await api_validate(SCHEMA_RESTORE_PARTIAL, request)
self._validate_cloud_backup_location(
request, body.get(ATTR_LOCATION, backup.location)
)
background = body.pop(ATTR_BACKGROUND)
restore_task, job_id = await self._background_backup_task(
self.sys_backups.do_restore_partial, backup, **body
@@ -320,59 +435,136 @@ class APIBackups(CoreSysAttributes):
)
@api_process
async def freeze(self, request):
async def freeze(self, request: web.Request):
"""Initiate manual freeze for external backup."""
body = await api_validate(SCHEMA_FREEZE, request)
await asyncio.shield(self.sys_backups.freeze_all(**body))
@api_process
async def thaw(self, request):
async def thaw(self, request: web.Request):
"""Begin thaw after manual freeze."""
await self.sys_backups.thaw_all()
@api_process
async def remove(self, request):
async def remove(self, request: web.Request):
"""Remove a backup."""
backup = self._extract_slug(request)
return self.sys_backups.remove(backup)
body = await api_validate(SCHEMA_REMOVE, request)
locations: list[LOCATION_TYPE] | None = None
async def download(self, request):
if ATTR_LOCATION in body:
self._validate_cloud_backup_location(request, body[ATTR_LOCATION])
locations = [self._location_to_mount(name) for name in body[ATTR_LOCATION]]
else:
self._validate_cloud_backup_location(request, backup.location)
await self.sys_backups.remove(backup, locations=locations)
@api_process
async def download(self, request: web.Request):
"""Download a backup file."""
backup = self._extract_slug(request)
# Query will give us '' for /backups, convert value to None
location = _convert_local_location(
request.query.get(ATTR_LOCATION, backup.location)
)
self._validate_cloud_backup_location(request, location)
if location not in backup.all_locations:
raise APIError(f"Backup {backup.slug} is not in location {location}")
_LOGGER.info("Downloading backup %s", backup.slug)
response = web.FileResponse(backup.tarfile)
filename = backup.all_locations[location][ATTR_PATH]
# If the file is missing, return 404 and trigger reload of location
if not filename.is_file():
self.sys_create_task(self.sys_backups.reload(location))
return web.Response(status=404)
response = web.FileResponse(filename)
response.content_type = CONTENT_TYPE_TAR
download_filename = filename.name
if download_filename == f"{backup.slug}.tar":
download_filename = f"{RE_SLUGIFY_NAME.sub('_', backup.name)}.tar"
response.headers[CONTENT_DISPOSITION] = (
f"attachment; filename={RE_SLUGIFY_NAME.sub('_', backup.name)}.tar"
f"attachment; filename={download_filename}"
)
return response
@api_process
async def upload(self, request):
async def upload(self, request: web.Request):
"""Upload a backup file."""
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp_dir:
tar_file = Path(temp_dir, "backup.tar")
location: LOCATION_TYPE = None
locations: list[LOCATION_TYPE] | None = None
tmp_path = self.sys_config.path_tmp
if ATTR_LOCATION in request.query:
location_names: list[str] = request.query.getall(ATTR_LOCATION)
self._validate_cloud_backup_location(request, location_names)
# Convert empty string to None if necessary
locations = [
self._location_to_mount(location)
if _convert_local_location(location)
else None
for location in location_names
]
location = locations.pop(0)
if location and location != LOCATION_CLOUD_BACKUP:
tmp_path = location.local_where
filename: str | None = None
if ATTR_FILENAME in request.query:
filename = request.query.get(ATTR_FILENAME)
try:
vol.Match(RE_BACKUP_FILENAME)(filename)
except vol.Invalid as ex:
raise APIError(humanize_error(filename, ex)) from None
temp_dir: TemporaryDirectory | None = None
backup_file_stream: IOBase | None = None
def open_backup_file() -> Path:
nonlocal temp_dir, backup_file_stream
temp_dir = TemporaryDirectory(dir=tmp_path.as_posix())
tar_file = Path(temp_dir.name, "backup.tar")
backup_file_stream = tar_file.open("wb")
return tar_file
def close_backup_file() -> None:
if backup_file_stream:
backup_file_stream.close()
if temp_dir:
temp_dir.cleanup()
try:
reader = await request.multipart()
contents = await reader.next()
try:
with tar_file.open("wb") as backup:
while True:
chunk = await contents.read_chunk()
if not chunk:
break
backup.write(chunk)
tar_file = await self.sys_run_in_executor(open_backup_file)
while chunk := await contents.read_chunk(size=2**16):
await self.sys_run_in_executor(backup_file_stream.write, chunk)
except OSError as err:
if err.errno == errno.EBADMSG:
self.sys_resolution.unhealthy = UnhealthyReason.OSERROR_BAD_MESSAGE
_LOGGER.error("Can't write new backup file: %s", err)
return False
backup = await asyncio.shield(
self.sys_backups.import_backup(
tar_file,
filename,
location=location,
additional_locations=locations,
)
)
except OSError as err:
if err.errno == errno.EBADMSG and location in {
LOCATION_CLOUD_BACKUP,
None,
}:
self.sys_resolution.unhealthy = UnhealthyReason.OSERROR_BAD_MESSAGE
_LOGGER.error("Can't write new backup file: %s", err)
return False
except asyncio.CancelledError:
return False
except asyncio.CancelledError:
return False
backup = await asyncio.shield(self.sys_backups.import_backup(tar_file))
finally:
if temp_dir or backup:
await self.sys_run_in_executor(close_backup_file)
if backup:
return {ATTR_SLUG: backup.slug}

View File

@@ -12,6 +12,7 @@ CONTENT_TYPE_X_LOG = "text/x-log"
COOKIE_INGRESS = "ingress_session"
ATTR_ADDITIONAL_LOCATIONS = "additional_locations"
ATTR_AGENT_VERSION = "agent_version"
ATTR_APPARMOR_VERSION = "apparmor_version"
ATTR_ATTRIBUTES = "attributes"
@@ -42,11 +43,12 @@ ATTR_GROUP_IDS = "group_ids"
ATTR_IDENTIFIERS = "identifiers"
ATTR_IS_ACTIVE = "is_active"
ATTR_IS_OWNER = "is_owner"
ATTR_JOB_ID = "job_id"
ATTR_JOBS = "jobs"
ATTR_LLMNR = "llmnr"
ATTR_LLMNR_HOSTNAME = "llmnr_hostname"
ATTR_LOCAL_ONLY = "local_only"
ATTR_LOCATION_ATTRIBUTES = "location_attributes"
ATTR_LOCATIONS = "locations"
ATTR_MDNS = "mdns"
ATTR_MODEL = "model"
ATTR_MOUNTS = "mounts"
@@ -68,6 +70,7 @@ ATTR_UPDATE_TYPE = "update_type"
ATTR_USAGE = "usage"
ATTR_USE_NTP = "use_ntp"
ATTR_USERS = "users"
ATTR_USER_PATH = "user_path"
ATTR_VENDOR = "vendor"
ATTR_VIRTUALIZATION = "virtualization"

View File

@@ -16,7 +16,7 @@ from ..const import (
AddonState,
)
from ..coresys import CoreSysAttributes
from ..exceptions import APIError, APIForbidden
from ..exceptions import APIForbidden, APINotFound
from .utils import api_process, api_validate, require_home_assistant
_LOGGER: logging.Logger = logging.getLogger(__name__)
@@ -36,7 +36,7 @@ class APIDiscovery(CoreSysAttributes):
"""Extract discovery message from URL."""
message = self.sys_discovery.get(request.match_info.get("uuid"))
if not message:
raise APIError("Discovery message not found")
raise APINotFound("Discovery message not found")
return message
@api_process
@@ -83,7 +83,7 @@ class APIDiscovery(CoreSysAttributes):
)
# Process discovery message
message = self.sys_discovery.send(addon, **body)
message = await self.sys_discovery.send(addon, **body)
return {ATTR_UUID: message.uuid}
@@ -110,5 +110,5 @@ class APIDiscovery(CoreSysAttributes):
if message.addon != addon.slug:
raise APIForbidden("Can't remove discovery message")
self.sys_discovery.remove(message)
await self.sys_discovery.remove(message)
return True

View File

@@ -78,7 +78,7 @@ class APICoreDNS(CoreSysAttributes):
if restart_required:
self.sys_create_task(self.sys_plugins.dns.restart())
self.sys_plugins.dns.save_data()
await self.sys_plugins.dns.save_data()
@api_process
async def stats(self, request: web.Request) -> dict[str, Any]:

View File

@@ -16,6 +16,7 @@ from ..const import (
ATTR_VERSION,
)
from ..coresys import CoreSysAttributes
from ..exceptions import APINotFound
from .utils import api_process, api_validate
_LOGGER: logging.Logger = logging.getLogger(__name__)
@@ -52,14 +53,17 @@ class APIDocker(CoreSysAttributes):
for hostname, registry in body.items():
self.sys_docker.config.registries[hostname] = registry
self.sys_docker.config.save_data()
await self.sys_docker.config.save_data()
@api_process
async def remove_registry(self, request: web.Request):
"""Delete a docker registry."""
hostname = request.match_info.get(ATTR_HOSTNAME)
if hostname not in self.sys_docker.config.registries:
raise APINotFound(f"Hostname {hostname} does not exist in registries")
del self.sys_docker.config.registries[hostname]
self.sys_docker.config.save_data()
await self.sys_docker.config.save_data()
@api_process
async def info(self, request: web.Request):

View File

@@ -149,7 +149,7 @@ class APIHomeAssistant(CoreSysAttributes):
ATTR_BACKUPS_EXCLUDE_DATABASE
]
self.sys_homeassistant.save_data()
await self.sys_homeassistant.save_data()
@api_process
async def stats(self, request: web.Request) -> dict[Any, str]:

View File

@@ -98,10 +98,10 @@ class APIHost(CoreSysAttributes):
ATTR_VIRTUALIZATION: self.sys_host.info.virtualization,
ATTR_CPE: self.sys_host.info.cpe,
ATTR_DEPLOYMENT: self.sys_host.info.deployment,
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_DISK_FREE: await self.sys_host.info.free_space(),
ATTR_DISK_TOTAL: await self.sys_host.info.total_space(),
ATTR_DISK_USED: await self.sys_host.info.used_space(),
ATTR_DISK_LIFE_TIME: await self.sys_host.info.disk_life_time(),
ATTR_FEATURES: self.sys_host.features,
ATTR_HOSTNAME: self.sys_host.info.hostname,
ATTR_LLMNR_HOSTNAME: self.sys_host.info.llmnr_hostname,
@@ -239,12 +239,12 @@ class APIHost(CoreSysAttributes):
# return 2 lines at minimum.
lines = max(2, lines)
# entries=cursor[[:num_skip]:num_entries]
range_header = f"entries=:-{lines-1}:{'' if follow else lines}"
range_header = f"entries=:-{lines - 1}:{'' if follow else lines}"
elif RANGE in request.headers:
range_header = request.headers.get(RANGE)
else:
range_header = (
f"entries=:-{DEFAULT_LINES-1}:{'' if follow else DEFAULT_LINES}"
f"entries=:-{DEFAULT_LINES - 1}:{'' if follow else DEFAULT_LINES}"
)
async with self.sys_host.logs.journald_logs(
@@ -258,6 +258,7 @@ class APIHost(CoreSysAttributes):
if not headers_returned:
if cursor:
response.headers["X-First-Cursor"] = cursor
response.headers["X-Accel-Buffering"] = "no"
await response.prepare(request)
headers_returned = True
# When client closes the connection while reading busy logs, we

View File

@@ -277,6 +277,7 @@ class APIIngress(CoreSysAttributes):
response.content_type = content_type
try:
response.headers["X-Accel-Buffering"] = "no"
await response.prepare(request)
async for data in result.content.iter_chunked(4096):
await response.write(data)

View File

@@ -7,7 +7,7 @@ from aiohttp import web
import voluptuous as vol
from ..coresys import CoreSysAttributes
from ..exceptions import APIError
from ..exceptions import APIError, APINotFound, JobNotFound
from ..jobs import SupervisorJob
from ..jobs.const import ATTR_IGNORE_CONDITIONS, JobCondition
from .const import ATTR_JOBS
@@ -23,10 +23,24 @@ SCHEMA_OPTIONS = vol.Schema(
class APIJobs(CoreSysAttributes):
"""Handle RESTful API for OS functions."""
def _extract_job(self, request: web.Request) -> SupervisorJob:
"""Extract job from request or raise."""
try:
return self.sys_jobs.get_job(request.match_info.get("uuid"))
except JobNotFound:
raise APINotFound("Job does not exist") from None
def _list_jobs(self, start: SupervisorJob | None = None) -> list[dict[str, Any]]:
"""Return current job tree."""
"""Return current job tree.
Jobs are added to cache as they are created so by default they are in oldest to newest.
This is correct ordering for child jobs as it makes logical sense to present those in
the order they occurred within the parent. For the list as a whole, sort from newest
to oldest as its likely any client is most interested in the newer ones.
"""
# Initially sort oldest to newest so all child lists end up in correct order
jobs_by_parent: dict[str | None, list[SupervisorJob]] = {}
for job in self.sys_jobs.jobs:
for job in sorted(self.sys_jobs.jobs):
if job.internal:
continue
@@ -35,11 +49,15 @@ class APIJobs(CoreSysAttributes):
else:
jobs_by_parent[job.parent_id].append(job)
# After parent-child organization, sort the root jobs only from newest to oldest
job_list: list[dict[str, Any]] = []
queue: list[tuple[list[dict[str, Any]], SupervisorJob]] = (
[(job_list, start)]
if start
else [(job_list, job) for job in jobs_by_parent.get(None, [])]
else [
(job_list, job)
for job in sorted(jobs_by_parent.get(None, []), reverse=True)
]
)
while queue:
@@ -74,25 +92,25 @@ class APIJobs(CoreSysAttributes):
if ATTR_IGNORE_CONDITIONS in body:
self.sys_jobs.ignore_conditions = body[ATTR_IGNORE_CONDITIONS]
self.sys_jobs.save_data()
await self.sys_jobs.save_data()
await self.sys_resolution.evaluate.evaluate_system()
@api_process
async def reset(self, request: web.Request) -> None:
"""Reset options for JobManager."""
self.sys_jobs.reset_data()
await self.sys_jobs.reset_data()
@api_process
async def job_info(self, request: web.Request) -> dict[str, Any]:
"""Get details of a job by ID."""
job = self.sys_jobs.get_job(request.match_info.get("uuid"))
job = self._extract_job(request)
return self._list_jobs(job)[0]
@api_process
async def remove_job(self, request: web.Request) -> None:
"""Remove a completed job."""
job = self.sys_jobs.get_job(request.match_info.get("uuid"))
job = self._extract_job(request)
if not job.done:
raise APIError(f"Job {job.uuid} is not done!")

View File

@@ -7,11 +7,11 @@ import voluptuous as vol
from ..const import ATTR_NAME, ATTR_STATE
from ..coresys import CoreSysAttributes
from ..exceptions import APIError
from ..exceptions import APIError, APINotFound
from ..mounts.const import ATTR_DEFAULT_BACKUP_MOUNT, MountUsage
from ..mounts.mount import Mount
from ..mounts.validate import SCHEMA_MOUNT_CONFIG
from .const import ATTR_MOUNTS
from .const import ATTR_MOUNTS, ATTR_USER_PATH
from .utils import api_process, api_validate
SCHEMA_OPTIONS = vol.Schema(
@@ -24,6 +24,13 @@ SCHEMA_OPTIONS = vol.Schema(
class APIMounts(CoreSysAttributes):
"""Handle REST API for mounting options."""
def _extract_mount(self, request: web.Request) -> Mount:
"""Extract mount from request or raise."""
name = request.match_info.get("mount")
if name not in self.sys_mounts:
raise APINotFound(f"No mount exists with name {name}")
return self.sys_mounts.get(name)
@api_process
async def info(self, request: web.Request) -> dict[str, Any]:
"""Return MountManager info."""
@@ -32,7 +39,13 @@ class APIMounts(CoreSysAttributes):
if self.sys_mounts.default_backup_mount
else None,
ATTR_MOUNTS: [
mount.to_dict() | {ATTR_STATE: mount.state}
mount.to_dict()
| {
ATTR_STATE: mount.state,
ATTR_USER_PATH: mount.container_where.as_posix()
if mount.container_where
else None,
}
for mount in self.sys_mounts.mounts
],
}
@@ -53,7 +66,7 @@ class APIMounts(CoreSysAttributes):
else:
self.sys_mounts.default_backup_mount = mount
self.sys_mounts.save_data()
await self.sys_mounts.save_data()
@api_process
async def create_mount(self, request: web.Request) -> None:
@@ -74,20 +87,18 @@ class APIMounts(CoreSysAttributes):
if not self.sys_mounts.default_backup_mount:
self.sys_mounts.default_backup_mount = mount
self.sys_mounts.save_data()
await self.sys_mounts.save_data()
@api_process
async def update_mount(self, request: web.Request) -> None:
"""Update an existing mount in supervisor."""
name = request.match_info.get("mount")
current = self._extract_mount(request)
name_schema = vol.Schema(
{vol.Optional(ATTR_NAME, default=name): name}, extra=vol.ALLOW_EXTRA
{vol.Optional(ATTR_NAME, default=current.name): current.name},
extra=vol.ALLOW_EXTRA,
)
body = await api_validate(vol.All(name_schema, SCHEMA_MOUNT_CONFIG), request)
if name not in self.sys_mounts:
raise APIError(f"No mount exists with name {name}")
mount = Mount.from_dict(self.coresys, body)
await self.sys_mounts.create_mount(mount)
@@ -99,26 +110,26 @@ class APIMounts(CoreSysAttributes):
elif self.sys_mounts.default_backup_mount == mount:
self.sys_mounts.default_backup_mount = None
self.sys_mounts.save_data()
await self.sys_mounts.save_data()
@api_process
async def delete_mount(self, request: web.Request) -> None:
"""Delete an existing mount in supervisor."""
name = request.match_info.get("mount")
mount = await self.sys_mounts.remove_mount(name)
current = self._extract_mount(request)
mount = await self.sys_mounts.remove_mount(current.name)
# If it was a backup mount, reload backups
if mount.usage == MountUsage.BACKUP:
self.sys_create_task(self.sys_backups.reload())
self.sys_mounts.save_data()
await self.sys_mounts.save_data()
@api_process
async def reload_mount(self, request: web.Request) -> None:
"""Reload an existing mount in supervisor."""
name = request.match_info.get("mount")
await self.sys_mounts.reload_mount(name)
mount = self._extract_mount(request)
await self.sys_mounts.reload_mount(mount.name)
# If it's a backup mount, reload backups
if self.sys_mounts.get(name).usage == MountUsage.BACKUP:
if mount.usage == MountUsage.BACKUP:
self.sys_create_task(self.sys_backups.reload())

View File

@@ -42,7 +42,7 @@ from ..const import (
DOCKER_NETWORK_MASK,
)
from ..coresys import CoreSysAttributes
from ..exceptions import APIError, HostNetworkNotFound
from ..exceptions import APIError, APINotFound, HostNetworkNotFound
from ..host.configuration import (
AccessPoint,
Interface,
@@ -167,7 +167,7 @@ class APINetwork(CoreSysAttributes):
except HostNetworkNotFound:
pass
raise APIError(f"Interface {name} does not exist") from None
raise APINotFound(f"Interface {name} does not exist") from None
@api_process
async def info(self, request: web.Request) -> dict[str, Any]:

View File

@@ -169,7 +169,7 @@ class APIOS(CoreSysAttributes):
body[ATTR_SYSTEM_HEALTH_LED]
)
self.sys_dbus.agent.board.green.save_data()
await self.sys_dbus.agent.board.green.save_data()
@api_process
async def boards_yellow_info(self, request: web.Request) -> dict[str, Any]:
@@ -196,7 +196,7 @@ class APIOS(CoreSysAttributes):
if ATTR_POWER_LED in body:
await self.sys_dbus.agent.board.yellow.set_power_led(body[ATTR_POWER_LED])
self.sys_dbus.agent.board.yellow.save_data()
await self.sys_dbus.agent.board.yellow.save_data()
self.sys_resolution.create_issue(
IssueType.REBOOT_REQUIRED,
ContextType.SYSTEM,

View File

@@ -1 +1 @@
!function(){function d(d){var e=document.createElement("script");e.src=d,document.body.appendChild(e)}if(/Edge?\/(12\d|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Firefox\/(1{2}[5-9]|1[2-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Chrom(ium|e)\/(109|1[1-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|(Maci|X1{2}).+ Version\/(17\.([2-9]|\d{2,})|(1[89]|[2-9]\d|\d{3,})\.\d+)([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/(10[4-9]|1[1-9]\d|[2-9]\d{2}|\d{4,})\.\d+\.\d+|(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(15[._]([6-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})[._]\d+)([._]\d+|)|Android:?[ /-](12\d|1[3-9]\d|[2-9]\d{2}|\d{4,})(\.\d+|)(\.\d+|)|Mobile Safari.+OPR\/([89]\d|\d{3,})\.\d+\.\d+|Android.+Firefox\/(12\d|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Android.+Chrom(ium|e)\/(12\d|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|SamsungBrowser\/(2[4-9]|[3-9]\d|\d{3,})\.\d+|Home As{2}istant\/[\d.]+ \(.+; macOS (1[2-9]|[2-9]\d|\d{3,})\.\d+(\.\d+)?\)/.test(navigator.userAgent))try{new Function("import('/api/hassio/app/frontend_latest/entrypoint.eaJ87jjaoYA.js')")()}catch(e){d("/api/hassio/app/frontend_es5/entrypoint.t1yF5Ic8wyI.js")}else d("/api/hassio/app/frontend_es5/entrypoint.t1yF5Ic8wyI.js")}()
!function(){function d(d){var e=document.createElement("script");e.src=d,document.body.appendChild(e)}if(/Edge?\/(12[2-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Firefox\/(12[4-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Chrom(ium|e)\/(109|1[1-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|(Maci|X1{2}).+ Version\/(17\.([4-9]|\d{2,})|(1[89]|[2-9]\d|\d{3,})\.\d+)([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/(10[89]|1[1-9]\d|[2-9]\d{2}|\d{4,})\.\d+\.\d+|(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(15[._]([6-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})[._]\d+)([._]\d+|)|Android:?[ /-](12[3-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})(\.\d+|)(\.\d+|)|Mobile Safari.+OPR\/([89]\d|\d{3,})\.\d+\.\d+|Android.+Firefox\/(12[4-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Android.+Chrom(ium|e)\/(12[3-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|SamsungBrowser\/(2[4-9]|[3-9]\d|\d{3,})\.\d+|Home As{2}istant\/[\d.]+ \(.+; macOS (1[2-9]|[2-9]\d|\d{3,})\.\d+(\.\d+)?\)/.test(navigator.userAgent))try{new Function("import('/api/hassio/app/frontend_latest/entrypoint.9ac99222ee42fbb3.js')")()}catch(e){d("/api/hassio/app/frontend_es5/entrypoint.85ccafe1fda9d9a5.js")}else d("/api/hassio/app/frontend_es5/entrypoint.85ccafe1fda9d9a5.js")}()

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"1081.e647cbe586ff9dd0.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250221.0/src/components/ha-button-toggle-group.ts","https://raw.githubusercontent.com/home-assistant/frontend/20250221.0/src/components/ha-selector/ha-selector-button-toggle.ts"],"names":["_decorate","customElement","_initialize","_LitElement","F","constructor","args","d","kind","decorators","property","attribute","key","value","type","Boolean","queryAll","html","_t","_","this","buttons","map","button","iconPath","_t2","label","active","_handleClick","_t3","styleMap","width","fullWidth","length","dense","_this$_buttons","_buttons","forEach","async","updateComplete","shadowRoot","querySelector","style","margin","ev","currentTarget","fireEvent","static","css","_t4","LitElement","HaButtonToggleSelector","_this$selector$button","_this$selector$button2","_this$selector$button3","options","selector","button_toggle","option","translationKey","translation_key","localizeValue","localizedLabel","sort","a","b","caseInsensitiveStringCompare","hass","locale","language","toggleButtons","item","_valueChanged","_ev$detail","_this$value","stopPropagation","detail","target","disabled","undefined"],"mappings":"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"}

View File

@@ -1,2 +0,0 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([[110],{46875:function(e,n,t){t.d(n,{a:function(){return c}});t(82386);var r=t(9883),a=t(213);function c(e,n){var t=(0,a.m)(e.entity_id),c=void 0!==n?n:null==e?void 0:e.state;if(["button","event","input_button","scene"].includes(t))return c!==r.Hh;if((0,r.g0)(c))return!1;if(c===r.KF&&"alert"!==t)return!1;switch(t){case"alarm_control_panel":return"disarmed"!==c;case"alert":return"idle"!==c;case"cover":case"valve":return"closed"!==c;case"device_tracker":case"person":return"not_home"!==c;case"lawn_mower":return["mowing","error"].includes(c);case"lock":return"locked"!==c;case"media_player":return"standby"!==c;case"vacuum":return!["idle","docked","paused"].includes(c);case"plant":return"problem"===c;case"group":return["on","home","open","locked","problem"].includes(c);case"timer":return"active"===c;case"camera":return"streaming"===c}return!0}},94526:function(e,n,t){t.d(n,{Hg:function(){return r},e0:function(){return a}});t(33994),t(22858),t(88871),t(81027),t(82386),t(97741),t(50693),t(72735),t(26098),t(39790),t(66457),t(55228),t(36604),t(16891),"".concat(location.protocol,"//").concat(location.host);var r=function(e){return e.map((function(e){if("string"!==e.type)return e;switch(e.name){case"username":return Object.assign(Object.assign({},e),{},{autocomplete:"username"});case"password":return Object.assign(Object.assign({},e),{},{autocomplete:"current-password"});case"code":return Object.assign(Object.assign({},e),{},{autocomplete:"one-time-code"});default:return e}}))},a=function(e,n){return e.callWS({type:"auth/sign_path",path:n})}},9883:function(e,n,t){t.d(n,{HV:function(){return c},Hh:function(){return a},KF:function(){return u},ON:function(){return o},g0:function(){return l},s7:function(){return s}});var r=t(99890),a="unavailable",c="unknown",o="on",u="off",s=[a,c],i=[a,c,u],l=(0,r.g)(s);(0,r.g)(i)},54630:function(e,n,t){var r=t(72148);e.exports=/Version\/10(?:\.\d+){1,2}(?: [\w./]+)?(?: Mobile\/\w+)? Safari\//.test(r)},36686:function(e,n,t){var r=t(13113),a=t(93187),c=t(53138),o=t(90924),u=t(22669),s=r(o),i=r("".slice),l=Math.ceil,d=function(e){return function(n,t,r){var o,d,f=c(u(n)),p=a(t),m=f.length,g=void 0===r?" ":c(r);return p<=m||""===g?f:((d=s(g,l((o=p-m)/g.length))).length>o&&(d=i(d,0,o)),e?f+d:d+f)}};e.exports={start:d(!1),end:d(!0)}},79977:function(e,n,t){var r=t(41765),a=t(36686).start;r({target:"String",proto:!0,forced:t(54630)},{padStart:function(e){return a(this,e,arguments.length>1?arguments[1]:void 0)}})}}]);
//# sourceMappingURL=110.N3mhm3V6b1k.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"110.N3mhm3V6b1k.js","mappings":"wMAIO,SAASA,EAAYC,EAAsBC,GAChD,IAAMC,GAASC,EAAAA,EAAAA,GAAcH,EAASI,WAChCC,OAAyBC,IAAVL,EAAsBA,EAAQD,aAAQ,EAARA,EAAUC,MAE7D,GAAI,CAAC,SAAU,QAAS,eAAgB,SAASM,SAASL,GACxD,OAAOG,IAAiBG,EAAAA,GAG1B,IAAIC,EAAAA,EAAAA,IAAmBJ,GACrB,OAAO,EAOT,GAAIA,IAAiBK,EAAAA,IAAkB,UAAXR,EAC1B,OAAO,EAIT,OAAQA,GACN,IAAK,sBACH,MAAwB,aAAjBG,EACT,IAAK,QAEH,MAAwB,SAAjBA,EACT,IAAK,QAaL,IAAK,QACH,MAAwB,WAAjBA,EAZT,IAAK,iBACL,IAAK,SACH,MAAwB,aAAjBA,EACT,IAAK,aACH,MAAO,CAAC,SAAU,SAASE,SAASF,GACtC,IAAK,OACH,MAAwB,WAAjBA,EACT,IAAK,eACH,MAAwB,YAAjBA,EACT,IAAK,SACH,OAAQ,CAAC,OAAQ,SAAU,UAAUE,SAASF,GAGhD,IAAK,QACH,MAAwB,YAAjBA,EACT,IAAK,QACH,MAAO,CAAC,KAAM,OAAQ,OAAQ,SAAU,WAAWE,SAASF,GAC9D,IAAK,QACH,MAAwB,WAAjBA,EACT,IAAK,SACH,MAAwB,cAAjBA,EAGX,OAAO,CACT,C,+MChCuB,GAAHM,OAAMC,SAASC,SAAQ,MAAAF,OAAKC,SAASE,M,IAE5CC,EAA0B,SAACC,GAAsB,OAC5DA,EAAOC,KAAI,SAACC,GACV,GAAmB,WAAfA,EAAMC,KAAmB,OAAOD,EACpC,OAAQA,EAAME,MACZ,IAAK,WACH,OAAAC,OAAAC,OAAAD,OAAAC,OAAA,GAAYJ,GAAK,IAAEK,aAAc,aACnC,IAAK,WACH,OAAAF,OAAAC,OAAAD,OAAAC,OAAA,GAAYJ,GAAK,IAAEK,aAAc,qBACnC,IAAK,OACH,OAAAF,OAAAC,OAAAD,OAAAC,OAAA,GAAYJ,GAAK,IAAEK,aAAc,kBACnC,QACE,OAAOL,EAEb,GAAE,EAESM,EAAgB,SAC3BC,EACAC,GAAY,OACYD,EAAKE,OAAO,CAAER,KAAM,iBAAkBO,KAAAA,GAAO,C,+LC3C1DlB,EAAc,cACdoB,EAAU,UACVC,EAAK,KACLnB,EAAM,MAENoB,EAAqB,CAACtB,EAAaoB,GACnCG,EAAa,CAACvB,EAAaoB,EAASlB,GAEpCD,GAAqBuB,EAAAA,EAAAA,GAAqBF,IAC7BE,EAAAA,EAAAA,GAAqBD,E,wBCR/C,IAAIE,EAAY,EAAQ,OACxBC,EAAOC,QAAU,mEAAmEC,KAAKH,E,wBCDzF,IAAII,EAAc,EAAQ,OACtBC,EAAW,EAAQ,OACnBC,EAAW,EAAQ,OACnBC,EAAU,EAAQ,OAClBC,EAAyB,EAAQ,OACjCC,EAASL,EAAYG,GACrBG,EAAcN,EAAY,GAAGO,OAC7BC,EAAOC,KAAKD,KAGZE,EAAe,SAAUC,GAC3B,OAAO,SAAUC,EAAOC,EAAWC,GACjC,IAIIC,EAASC,EAJTC,EAAIf,EAASE,EAAuBQ,IACpCM,EAAejB,EAASY,GACxBM,EAAeF,EAAEG,OACjBC,OAAyBpD,IAAf6C,EAA2B,IAAMZ,EAASY,GAExD,OAAII,GAAgBC,GAA4B,KAAZE,EAAuBJ,IAE3DD,EAAeX,EAAOgB,EAASb,GAD/BO,EAAUG,EAAeC,GACqBE,EAAQD,UACrCA,OAASL,IAASC,EAAeV,EAAYU,EAAc,EAAGD,IACxEJ,EAASM,EAAID,EAAeA,EAAeC,EACpD,CACF,EACApB,EAAOC,QAAU,CAGfwB,MAAOZ,GAAa,GAGpBa,IAAKb,GAAa,G,wBC/BpB,IAAIc,EAAI,EAAQ,OACZC,EAAY,eAKhBD,EAAE,CACAE,OAAQ,SACRC,OAAO,EACPC,OAPe,EAAQ,QAQtB,CACDC,SAAU,SAAkBhB,GAC1B,OAAOY,EAAUK,KAAMjB,EAAWkB,UAAUX,OAAS,EAAIW,UAAU,QAAK9D,EAC1E,G","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20241106.0/src/common/entity/state_active.ts","https://raw.githubusercontent.com/home-assistant/frontend/20241106.0/src/data/auth.ts","https://raw.githubusercontent.com/home-assistant/frontend/20241106.0/src/data/entity.ts","/unknown/node_modules/core-js/internals/string-pad-webkit-bug.js","/unknown/node_modules/core-js/internals/string-pad.js","/unknown/node_modules/core-js/modules/es.string.pad-start.js"],"names":["stateActive","stateObj","state","domain","computeDomain","entity_id","compareState","undefined","includes","UNAVAILABLE","isUnavailableState","OFF","concat","location","protocol","host","autocompleteLoginFields","schema","map","field","type","name","Object","assign","autocomplete","getSignedPath","hass","path","callWS","UNKNOWN","ON","UNAVAILABLE_STATES","OFF_STATES","arrayLiteralIncludes","userAgent","module","exports","test","uncurryThis","toLength","toString","$repeat","requireObjectCoercible","repeat","stringSlice","slice","ceil","Math","createMethod","IS_END","$this","maxLength","fillString","fillLen","stringFiller","S","intMaxLength","stringLength","length","fillStr","start","end","$","$padStart","target","proto","forced","padStart","this","arguments"],"sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["12"],{5739:function(e,a,t){t.a(e,(async function(e,i){try{t.r(a),t.d(a,{HaNavigationSelector:()=>c});var d=t(73577),r=(t(71695),t(47021),t(57243)),n=t(50778),l=t(36522),o=t(63297),s=e([o]);o=(s.then?(await s)():s)[0];let u,h=e=>e,c=(0,d.Z)([(0,n.Mo)("ha-selector-navigation")],(function(e,a){return{F:class extends a{constructor(...a){super(...a),e(this)}},d:[{kind:"field",decorators:[(0,n.Cb)({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[(0,n.Cb)({attribute:!1})],key:"selector",value:void 0},{kind:"field",decorators:[(0,n.Cb)()],key:"value",value:void 0},{kind:"field",decorators:[(0,n.Cb)()],key:"label",value:void 0},{kind:"field",decorators:[(0,n.Cb)()],key:"helper",value:void 0},{kind:"field",decorators:[(0,n.Cb)({type:Boolean,reflect:!0})],key:"disabled",value(){return!1}},{kind:"field",decorators:[(0,n.Cb)({type:Boolean})],key:"required",value(){return!0}},{kind:"method",key:"render",value:function(){return(0,r.dy)(u||(u=h` <ha-navigation-picker .hass="${0}" .label="${0}" .value="${0}" .required="${0}" .disabled="${0}" .helper="${0}" @value-changed="${0}"></ha-navigation-picker> `),this.hass,this.label,this.value,this.required,this.disabled,this.helper,this._valueChanged)}},{kind:"method",key:"_valueChanged",value:function(e){(0,l.B)(this,"value-changed",{value:e.detail.value})}}]}}),r.oi);i()}catch(u){i(u)}}))}}]);
//# sourceMappingURL=12.ffa1bdc0a98802fa.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"12.ffa1bdc0a98802fa.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250221.0/src/components/ha-selector/ha-selector-navigation.ts"],"names":["HaNavigationSelector","_decorate","customElement","_initialize","_LitElement","F","constructor","args","d","kind","decorators","property","attribute","key","value","type","Boolean","reflect","html","_t","_","this","hass","label","required","disabled","helper","_valueChanged","ev","fireEvent","detail","LitElement"],"mappings":"mVAQaA,GAAoBC,EAAAA,EAAAA,GAAA,EADhCC,EAAAA,EAAAA,IAAc,4BAAyB,SAAAC,EAAAC,GAiCvC,OAAAC,EAjCD,cACiCD,EAAoBE,WAAAA,IAAAC,GAAA,SAAAA,GAAAJ,EAAA,QAApBK,EAAA,EAAAC,KAAA,QAAAC,WAAA,EAC9BC,EAAAA,EAAAA,IAAS,CAAEC,WAAW,KAAQC,IAAA,OAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAE9BC,EAAAA,EAAAA,IAAS,CAAEC,WAAW,KAAQC,IAAA,WAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAE9BC,EAAAA,EAAAA,OAAUE,IAAA,QAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAEVC,EAAAA,EAAAA,OAAUE,IAAA,QAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAEVC,EAAAA,EAAAA,OAAUE,IAAA,SAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAEVC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,QAASC,SAAS,KAAOJ,IAAA,WAAAC,KAAAA,GAAA,OAAmB,CAAK,IAAAL,KAAA,QAAAC,WAAA,EAElEC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,WAAUH,IAAA,WAAAC,KAAAA,GAAA,OAAmB,CAAI,IAAAL,KAAA,SAAAI,IAAA,SAAAC,MAEnD,WACE,OAAOI,EAAAA,EAAAA,IAAIC,IAAAA,EAAAC,CAAA,mKAECC,KAAKC,KACJD,KAAKE,MACLF,KAAKP,MACFO,KAAKG,SACLH,KAAKI,SACPJ,KAAKK,OACEL,KAAKM,cAG5B,GAAC,CAAAlB,KAAA,SAAAI,IAAA,gBAAAC,MAED,SAAsBc,IACpBC,EAAAA,EAAAA,GAAUR,KAAM,gBAAiB,CAAEP,MAAOc,EAAGE,OAAOhB,OACtD,IAAC,GA/BuCiB,EAAAA,I"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1236"],{4121:function(){Intl.PluralRules&&"function"==typeof Intl.PluralRules.__addLocaleData&&Intl.PluralRules.__addLocaleData({data:{categories:{cardinal:["one","other"],ordinal:["one","two","few","other"]},fn:function(e,n){var t=String(e).split("."),a=!t[1],l=Number(t[0])==e,o=l&&t[0].slice(-1),r=l&&t[0].slice(-2);return n?1==o&&11!=r?"one":2==o&&12!=r?"two":3==o&&13!=r?"few":"other":1==e&&a?"one":"other"}},locale:"en"})}}]);
//# sourceMappingURL=1236.64ca65d0ea4d76d4.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"1236.64ca65d0ea4d76d4.js","sources":["/unknown/node_modules/@formatjs/intl-pluralrules/locale-data/en.js"],"names":["Intl","PluralRules","__addLocaleData","n","ord","s","String","split","v0","t0","Number","n10","slice","n100"],"mappings":"wHAEIA,KAAKC,aAA2D,mBAArCD,KAAKC,YAAYC,iBAC9CF,KAAKC,YAAYC,gBAAgB,CAAC,KAAO,CAAC,WAAa,CAAC,SAAW,CAAC,MAAM,SAAS,QAAU,CAAC,MAAM,MAAM,MAAM,UAAU,GAAK,SAASC,EAAGC,GAC3I,IAAIC,EAAIC,OAAOH,GAAGI,MAAM,KAAMC,GAAMH,EAAE,GAAII,EAAKC,OAAOL,EAAE,KAAOF,EAAGQ,EAAMF,GAAMJ,EAAE,GAAGO,OAAO,GAAIC,EAAOJ,GAAMJ,EAAE,GAAGO,OAAO,GACvH,OAAIR,EAAmB,GAAPO,GAAoB,IAARE,EAAa,MAC9B,GAAPF,GAAoB,IAARE,EAAa,MAClB,GAAPF,GAAoB,IAARE,EAAa,MACzB,QACQ,GAALV,GAAUK,EAAK,MAAQ,OAChC,GAAG,OAAS,M"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1295"],{21393:function(s,n,e){e.r(n)}}]);

View File

@@ -1,2 +0,0 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([[1306],{21306:function(e,t,r){r.r(t),r.d(t,{HaFormGrid:function(){return v}});var a,i,o,n=r(64599),d=r(33994),u=r(22858),s=r(35806),c=r(71008),l=r(62193),h=r(2816),m=r(27927),f=r(35890),p=(r(81027),r(97741),r(16891),r(36185),r(50289)),k=r(29818),v=(0,m.A)([(0,k.EM)("ha-form-grid")],(function(e,t){var r,m=function(t){function r(){var t;(0,c.A)(this,r);for(var a=arguments.length,i=new Array(a),o=0;o<a;o++)i[o]=arguments[o];return t=(0,l.A)(this,r,[].concat(i)),e(t),t}return(0,h.A)(r,t),(0,s.A)(r)}(t);return{F:m,d:[{kind:"field",decorators:[(0,k.MZ)({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[(0,k.MZ)({attribute:!1})],key:"data",value:void 0},{kind:"field",decorators:[(0,k.MZ)({attribute:!1})],key:"schema",value:void 0},{kind:"field",decorators:[(0,k.MZ)({type:Boolean})],key:"disabled",value:function(){return!1}},{kind:"field",decorators:[(0,k.MZ)({attribute:!1})],key:"computeLabel",value:void 0},{kind:"field",decorators:[(0,k.MZ)({attribute:!1})],key:"computeHelper",value:void 0},{kind:"field",decorators:[(0,k.MZ)({attribute:!1})],key:"localizeValue",value:void 0},{kind:"method",key:"focus",value:(r=(0,u.A)((0,d.A)().mark((function e(){var t;return(0,d.A)().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,this.updateComplete;case 2:null===(t=this.renderRoot.querySelector("ha-form"))||void 0===t||t.focus();case 3:case"end":return e.stop()}}),e,this)}))),function(){return r.apply(this,arguments)})},{kind:"method",key:"updated",value:function(e){(0,f.A)(m,"updated",this,3)([e]),e.has("schema")&&(this.schema.column_min_width?this.style.setProperty("--form-grid-min-width",this.schema.column_min_width):this.style.setProperty("--form-grid-min-width",""))}},{kind:"method",key:"render",value:function(){var e=this;return(0,p.qy)(a||(a=(0,n.A)([" "," "])),this.schema.schema.map((function(t){return(0,p.qy)(i||(i=(0,n.A)([' <ha-form .hass="','" .data="','" .schema="','" .disabled="','" .computeLabel="','" .computeHelper="','" .localizeValue="','"></ha-form> '])),e.hass,e.data,[t],e.disabled,e.computeLabel,e.computeHelper,e.localizeValue)})))}},{kind:"get",static:!0,key:"styles",value:function(){return(0,p.AH)(o||(o=(0,n.A)([":host{display:grid!important;grid-template-columns:repeat(var(--form-grid-column-count,auto-fit),minmax(var(--form-grid-min-width,200px),1fr));grid-column-gap:8px;grid-row-gap:24px}:host>ha-form{display:block}"])))}}]}}),p.WF)}}]);
//# sourceMappingURL=1306.EM6Ds30Fu3A.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"1306.EM6Ds30Fu3A.js","mappings":"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","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20241106.0/src/components/ha-form/ha-form-grid.ts"],"names":["HaFormGrid","_decorate","customElement","_initialize","_LitElement","_focus","_LitElement2","_this","_classCallCheck","_len","arguments","length","args","Array","_key","_callSuper","concat","_inherits","_createClass","F","d","kind","decorators","property","attribute","key","value","type","Boolean","_asyncToGenerator","_regeneratorRuntime","mark","_callee","_this$renderRoot$quer","wrap","_context","prev","next","this","updateComplete","renderRoot","querySelector","focus","stop","apply","changedProps","_superPropGet","has","schema","column_min_width","style","setProperty","_this2","html","_templateObject","_taggedTemplateLiteral","map","item","_templateObject2","hass","data","disabled","computeLabel","computeHelper","localizeValue","static","css","_templateObject3","LitElement"],"sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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