Add-on repositories can pin a branch with the "url#branch" syntax. The
branch part of the repository regex only accepted word characters,
hyphens and dots, so a branch with a slash like "feature/hot-new-stuff"
failed to match and the repository was rejected as invalid.
Git branch names commonly use prefixes such as "feature/" or "fix/", so
allow slashes in the captured branch name.
* Fix typos in comments, docstrings and log messages
Correct 39 spelling mistakes across comments, docstrings and log/error
message strings throughout the package (e.g. "conection" -> "connection",
"Incomming" -> "Incoming", "Rasie" -> "Raise"). All changes are confined
to human-readable text; no identifiers, attributes or D-Bus contracts are
touched, so there is no behavior change. Found with codespell.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix typos in tests and CI workflow
Correct spelling mistakes in test comments, docstrings and data, plus one
in the builder workflow, so the whole tree is clean for the codespell hook
added next. The assertion in test_network_manager.py is updated to match
the corrected "Unknown error while processing" log message in the source.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add codespell pre-commit hook
Wire up codespell so spelling mistakes in comments, docstrings and strings
are caught automatically. The vendored frontend panel is excluded, and
"hass" and "astroid" are added to the ignore list as known false positives
(the Home Assistant abbreviation and the pylint dependency package).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address review feedback
Improve grammar in several of the touched comments and docstrings: use the
plural "ignore conditions" for the list-returning property, add the missing
auxiliary verb and fix agreement in the timezone-filter comment, fix
"backups ... use" agreement, and reword "underlay" to "underlying" in the
arch module docstring.
Also drop the "*.json" skip from the codespell hook. It was carried over
from another project but is unnecessary here (all tracked JSON is clean),
and skipping it would needlessly leave translation and data JSON unchecked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Reword onboarding comment
"overflight" was a literal calque of the German "überflogen"; use the
idiomatic "skimmed through" instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* apps: model client-state Apps* errors as APIError
Follow-up on #6739: with HassioError now logged and captured by Sentry
in api_process, a handful of Apps* exceptions raised from
AppManager.install/update/rebuild and AppModel._validate_availability
surfaced as "unexpected" 400s with a noisy log entry and a Sentry
event, even though they are all user/client-state errors (clicked
install on an already-installed add-on, "no update available", local
and store versions diverged, system architecture/machine/HA version
incompatible, etc.). The dominant offender is SUPERVISOR-1JVV
("No update available for app core_mosquitto", ~19k events / ~12k
users), but several siblings show the same shape.
Map these through properly so the API returns clean, structured 400s:
- Add modeled APIError subclasses in exceptions.py for the previously
raw raises in apps/manager.py: AppAlreadyInstalledError,
AppNotFoundError, AppNotInstalledError, AppNotInStoreError,
AppNoUpdateAvailableError, AppRebuildVersionChangedError,
AppRebuildImageBasedError. Each gets a stable error_key, a
message_template, and an "addon" extra_field.
- Add APIError to AppNotSupportedArchitectureError,
AppNotSupportedMachineTypeError and
AppNotSupportedHomeAssistantVersionError so they behave the same as
the other Apps* APIErrors instead of being treated as unexpected.
- Pass the app's display name (app.name from the add-on config)
instead of the slug to extra_fields wherever an App or AppModel is
available at the raise site, so users see "Mosquitto broker" rather
than "core_mosquitto" in error messages. Slug is only used as a
fallback when no app object exists (install of an unknown slug,
update/rebuild of a slug that is not installed).
- Update raise sites in apps/manager.py and apps/model.py to use the
new typed exceptions and the addon= keyword.
These are all runtime states users hit during normal interaction with
the apps UI, not Supervisor bugs worth paging on.
* apps: include slug alongside name in Apps* APIError extra_fields
Address review feedback from #6856: clients still need the slug to look
up additional add-on information (the name is for display only), and we
should be consistent about it across the Apps* errors touched by this
PR.
Every Apps* APIError raised with an App/AppModel available now carries
both `addon` (display name, used by the message_template) and `slug` in
extra_fields. Raise sites in apps/manager.py and apps/model.py pass
both. The two errors raised before an app object exists keep slug-only
extra_fields and use {slug} in their message:
- AppNotFoundError (install of an unknown slug)
- AppNotInstalledError (update/rebuild of a slug not in self.local)
Pre-existing Apps* APIErrors outside the scope of this PR
(AppUnknownError, AppConfigurationInvalidError, AppBootConfigCannot
ChangeError, AppNotRunningError, AppPortConflict, AppNotSupportedWrite
StdinError, AppBuild*) will be migrated in a follow-up.
* apps: introduce AppAPIError base for uniform addon/slug extra_fields
Address review follow-up on #6856: the addon/slug convention was
enforced only by hand-rolled __init__s, easy to drift on (forget slug,
use a different key, etc.). Promote it to a base class that owns the
shape of extra_fields for all App-related API errors.
- Add AppAPIError(AppsError, APIError). Its __init__ takes
`app: AppModel | App | AppStore | str` and uniformly populates
extra_fields with `addon` (display name) and `slug`. Pass a string
when no app object exists; only `slug` is set in that case. Extra
per-error fields flow through **extra_fields and merge with the
defaults.
- Convert the new exceptions added in this PR
(AppAlreadyInstalledError, AppNotFoundError, AppNotInstalledError,
AppNotInStoreError, AppNoUpdateAvailableError,
AppRebuildVersionChangedError) into thin subclasses that only declare
error_key and message_template -- the __init__ is inherited.
- Migrate the AppNotSupported* errors (architecture, machine type, HA
version) and AppRebuildImageBasedError to use AppAPIError too;
their bespoke per-error fields go through **extra_fields. They keep
inheriting AppNotSupportedError so `except AppNotSupportedError`
callers (e.g., AppModel._available) still work; MRO routes __init__
through AppAPIError.
- Update raise sites in apps/manager.py and apps/model.py to pass
`app=<obj-or-slug>` instead of repeating `addon=...` and `slug=...`.
Pre-existing App* APIErrors outside this PR's scope
(AppUnknownError, AppConfigurationInvalidError,
AppBootConfigCannotChangeError, AppNotRunningError, AppPortConflict,
AppNotSupportedWriteStdinError, AppBuild*) will be migrated to
AppAPIError in a follow-up; the base class is in place for them.
* apps: tighten AppAPIError model per review
Address mdegat01's two follow-ups on #6856 (review approved as-is,
this is the cleanup):
- AppNotFoundError and AppNotInstalledError are raised before any
App/AppModel object exists (unknown slug; not-installed slug). Pull
them out of AppAPIError and inherit (AppsError, APIError) directly
with a slug-only __init__ + {slug} message_template. Removes the
conceptually-wrong str branch from AppAPIError.__init__: it now
strictly requires an app-like object with .name and .slug.
- Restore per-class typed __init__ on AppNotSupportedArchitectureError,
AppNotSupportedMachineTypeError and
AppNotSupportedHomeAssistantVersionError so callers get an explicit
signature for the bespoke architectures/machine_types/version params
instead of dumping them through **extra_fields. Each override just
delegates to AppAPIError.__init__, which keeps ownership of the
addon/slug shape. The list-joining for architectures/machine_types
moves back into the override (raise sites pass the raw list again).
AppRebuildImageBasedError takes no bespoke fields and stays a plain
AppAPIError subclass.
Enable the PYI, RET and RSE ruff rule sets and fix the resulting
violations across the codebase. The pylint no-else-* checks
(RET505-508) are now disabled since ruff covers them.
The cleanups are mechanical:
- RSE102: drop empty parentheses from `raise Exception()` when no
arguments are passed.
- RET505-508: drop `else` branches that follow a `return`, `raise`,
`continue` or `break`, flattening control flow.
- RET502/504: add explicit return values and remove redundant
assign-then-return patterns.
- PYI030/032/041: tidy up type annotations (collapse literal unions,
use `object` for `__eq__`/`__ne__`, drop redundant numeric unions).
Turn on the PTH rule set in ruff and convert the remaining call sites
that triggered violations:
- `supervisor/utils/yaml.py` and `tests/homeassistant/test_module.py`
use `Path.open()` instead of the builtin `open()`.
- `tests/backups/test_backup.py` and
`tests/resolution/fixup/test_store_execute_reset.py` use
`Path.iterdir()` instead of `os.listdir()`.
- `tests/mounts/test_mount.py` uses `Path.unlink()` instead of
`os.remove()`.
- `tests/store/test_translation_load.py` uses `Path.mkdir(parents=True)`
instead of `os.makedirs()`.
- `tests/hardware/test_disk.py` captures the unpatched `Path.is_dir`,
`Path.is_symlink` and `Path.stat` before the `patch.object(Path, ...)`
block so the mocks can delegate to the real implementations without
reaching for `os.path.isdir`/`os.path.islink`/`os.stat`.
* tests: enable flake8-pytest-style (PT) ruff rules
Enable the `PT` ruff rule set and fix the resulting violations across the
test suite:
- PT006: pass parametrize argument names as tuples instead of a single
comma-separated string.
- PT022: switch fixtures that have no teardown from `yield` to `return`
so the lack of cleanup is obvious at a glance.
- PT011: add `match=` to broad `pytest.raises(ValueError)` blocks so the
expected error is anchored to a specific message.
- PT012: hoist setup (patches, branching) out of `pytest.raises()`
blocks so only the call that is expected to raise remains inside.
- PT013: replace `from pytest import X` with `import pytest` and access
attributes via the module.
- PT015: replace `try/except` + `assert False` patterns with
`pytest.raises(...)`.
- PT017: replace `assert` on exceptions inside `except` blocks with
`pytest.raises(...) as exc_info` and assert on `exc_info.value`.
No behavioral changes to the tests; the full suite still passes.
* tests: address review feedback on PT ruff rule enablement
- Fix fixture return-type annotations after switching `yield` to `return`
in tests/conftest.py: drop the `Generator[...]`/`AsyncGenerator[...]`
wrapper for `dns_manager_service`, `supervisor_internet`, `websession`,
and `mock_update_data` so the annotation matches what the fixture
actually returns.
- Correct the return-type annotation of `fixture_ip6config_service` from
`IP4ConfigService` to `IP6ConfigService`.
- Fix recurring "excepiton" typo in tests/utils/test_exception_helper.py.
* tests: verify backup cleanup on permission error
After `test_new_backup_permission_error` raises `BackupPermissionError`,
assert that no tarfile was left behind and `tmp_path` is empty. The
previous version only checked that the exception was raised, which
missed any regression where a partial tarfile would survive the failed
create.
* tests: rename DNS_GOOD_V6 to DNS_V6_UNSUPPORTED
The constant was named "good" but its tests assert that the URLs are
rejected by the DNS validator. The IPv6 URLs are well-formed but
currently rejected because IPv6 doesn't work with the Docker network
(see `dns_url` in supervisor/validate.py). Rename the constant and the
related test to make the intent obvious.
* Rename addon/addons to app/apps in filenames and imports
Continues the addon→app terminology migration (#6786).
Renames all source files, test files, fixture files, and
directories that contained 'addon'/'addons' in their names,
and updates all imports accordingly.
Resolution check files in supervisor/resolution/checks/ that were
renamed override the slug property to preserve the existing API
contract (slugs are exposed via the resolution info API and used
to run checks by name).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Rename add-on.json fixture
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The pytest config sets ``asyncio_mode = "auto"``, which already
auto-marks every ``async def test_*`` as a coroutine test. The 38
``@pytest.mark.asyncio`` decorators sprinkled across the suite were
no-ops kept around from before that flag was set. Remove them along
with the now-unused ``import pytest`` lines they were the only
consumer of.
Pure mechanical cleanup; no test behavior changes.
* Rename addon→app in docstrings and comments
Updates all docstrings and inline comments across supervisor/ and
tests/ to use the new app/apps terminology. No runtime behaviour
is changed by this commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Rename addon→app in code (variables, args, class names, functions)
Renames all internal Python identifiers from addon/addons to app/apps:
- Variable and argument names
- Function and method names
- Class names (Addon→App, AddonManager→AppManager, DockerAddon→DockerApp,
all exception, check, and fixup classes, etc.)
- String literals used as Python identifiers (pytest fixtures,
parametrize param names, patch.object attribute strings,
URL route match_info keys)
External API contracts are preserved: JSON keys, error codes,
discovery protocol fields, TypedDict/attr.s field names.
Import module paths (supervisor/addons/) are also unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix partial backup/restore API to remap addons key to apps
The external API accepts `addons` as the request body key (since
ATTR_APPS = "addons"), but do_backup_partial and do_restore_partial
now take an `apps` parameter after the rename. The **body expansion
in both endpoints would pass `addons=...` causing a TypeError.
Remap the key before expansion in both backup_partial and
restore_partial:
if ATTR_APPS in body:
body["apps"] = body.pop(ATTR_APPS)
Also adds test_restore_partial_with_addons_key to verify the restore
path correctly receives apps= when addons is passed in the request
body. This path had no existing test coverage.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix merge error
* Adjust AppLoggerAdapter to use app_name
Co-authored-by: Stefan Agner <stefan@agner.ch>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Stefan Agner <stefan@agner.ch>
* Change addons to apps in all user-facing strings
* Fix grammar in errors
* Apply suggestions from code review
Co-authored-by: Jan Čermák <sairon@users.noreply.github.com>
* Apply suggestions from code review
Co-authored-by: Stefan Agner <stefan@agner.ch>
---------
Co-authored-by: Jan Čermák <sairon@users.noreply.github.com>
Co-authored-by: Stefan Agner <stefan@agner.ch>
* Drop unsupported architectures and machines from Supervisor
Since #5620 Supervisor no longer updates the version information on
unsupported architectures and machines. This means users can no longer
update to newer version of Supervisor since that PR got released.
Furthermore since #6347 we also no longer build for these
architectures. With this, any code related to these architectures
becomes dead code and should be removed.
This commit removes all refrences to the deprecated architectures and
machines from Supervisor.
This affects the following architectures:
- armhf
- armv7
- i386
And the following machines:
- odroid-xu
- qemuarm
- qemux86
- raspberrypi
- raspberrypi2
- raspberrypi3
- raspberrypi4
- tinker
* Create issue if an app using a deprecated architecture is installed
This adds a check to the resolution system to detect if an app is
installed that uses a deprecated architecture. If so, it will show a
warning to the user and recommend them to uninstall the app.
* Formally deprecate machine add-on configs as well
Not only deprecate add-on configs for unsupported architectures, but
also for unsupported machines.
* For installed add-ons architecture must always exist
Fail hard in case of missing architecture, as this is a required field
for installed add-ons. This will prevent the Supervisor from running
with an unsupported configuration and causing further issues down the
line.
* Ensure uuid of dismissed suggestion/issue matches an existing one
* Fix lint, test and feedback issues
* Adjust existing tests and remove new ones for not found errors
* fix device access issue usage
Add AttributeError to the exception handler in the git pull operation.
This catches the case where a repository exists but has no 'origin'
remote configured, which can happen if the remote was renamed or
deleted by the user or due to repository corruption.
When this error occurs, it now creates a CORRUPT_REPOSITORY issue with
an EXECUTE_RESET suggestion, triggering the auto-fix mechanism to
re-clone the repository.
Fixes SUPERVISOR-69Z
Fixes SUPERVISOR-172C
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
* Add availability API for addons
* Add cast back and test for latest version of installed addon
* Make error responses more translation/client library friendly
* Add test cases for install/update APIs
* Drop ensure_builtin_repositories
With the new Repository classes we have the is_builtin property, so we
can easily make sure that built-ins are not removed. This allows us to
further cleanup the code by removing the ensure_builtin_repositories
function and the ALL_BUILTIN_REPOSITORIES constant.
* Make sure we add built-ins on load
* Reuse default set and avoid unnecessary copy
Reuse default set and avoid unnecessary copying during validation if
the default is not being used.
* Rename repository fixture to test_repository
Also don't remove the built-in repositories. The list was incomplete,
and tests don't seem to require that anymore.
* Get rid of StoreType
The type doesn't have much value, we have constant strings anyways.
* Introduce types.py
* Use slug to determine which repository urls to return
* Simplify BuiltinRepository enum
* Mock GitRepo load
* Improve URL handling and repository creation logic
* Refactor update_repositories
* Get rid of get_from_url
It is no longer used in production code.
* More refactoring
* Address pylint
* Introduce is_git_based property to Repository class
Return all git based URLs, including the Core repository.
* Revert "Introduce is_git_based property to Repository class"
This reverts commit dfd5ad79bf.
* Fold type.py into const.py
Align more with how Supervisor code is typically structured.
* Update supervisor/store/__init__.py
Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
* Apply repository remove suggestion
* Fix tests
---------
Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
* Use add-on config timestamp to determine add-on update age
Instead of using the current timestamp when loading the add-on config,
simply use the add-on config modification timestamp. This way, we can
get a timetsamp even when Supervisor got restarted. It also simplifies
the code a bit.
* Fix pytest
* Patch stat() instead of modifing fixture files
* Check local store repository for changes
Instead of simply assume that the local store repository got changed,
use mtime to check if there have been any changes to the local store.
This mimics a similar behavior to the git repository store updates.
Before this change, we end up in the updated repo code path, which
caused a re-read of all add-ons on every store reload, even though
nothing changed at all. Store reloads are triggered by Home Assistant
Core every 5 minutes.
* Fix pytest failure
Now that we actually only reload metadata if the local store changed
we have to fake the change as well to fix the store manager tests.
* Fix path cache update test for local store repository
* Take root directory into account/add pytest
* Rename utils/__init__.py tests to test_utils_init.py
* Finish out effort of adding and enabling blockbuster
* Skip getting addon file size until securetar fixed
* Fix test for devcontainer and blocking I/O
* Fix docker fixture and load_config to post_init
* Add blockbuster library and find I/O from unit tests
* Fix lint and test issue
* Fixes from feedback
* Avoid modifying webapp object in executor
* Split su options validation and only validate timezone on change
* 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
* 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>
* Test stub for keeping shared images after update
* Keep shared images on addon update
* ImageNotFound should only skip the one image not all
* Fix tests and nonetype error
* Normalize logic between two cleanup methods
* Cache existence of addon paths
* Always update submodules
* Switch to an always cached model
* Cache on store addon only
* Fix tests
* refresh_cache to refresh_path_cache
* Fix name change in test
* Move logic into StoreManager
* Migrate to Ruff for lint and format
* Fix pylint issues
* DBus property sets into normal awaitable methods
* Fix tests relying on separate tasks in connect
* Fixes from feedback
* Bad message error marks system as unhealthy
* Finish adding test cases for changes
* Rename test file for uniqueness
* bad_message to oserror_bad_message
* Omit some checks and check for network mounts
* Move repository urls to store settings file
* Remove default repos from supervisor config
* Fix clone at initial store load
* Mock git load in repository fixture