* Track installed OS update pending reboot activation
Since #6982 the OS update no longer reboots the host automatically, so the
system keeps running the old version until the user reboots. In that window
the update kept being offered: need_update compares the running OS version,
which only changes on reboot. Requesting the same update again re-downloaded
and reinstalled the full image. Worse, a Supervisor restart in that window
dropped the in-memory REBOOT_REQUIRED issue and canceled the pending update
altogether, because mark_healthy marks the booted slot active on startup,
reverting the primary boot slot set by the rauc install.
Track the installed version awaiting a reboot as version_pending and expose
it in /os/info. A successful install sets it, updating again to that version
is rejected with a hint to reboot, and need_update no longer reports true for
an update that is already installed. On load, the pending state is recovered
from rauc by comparing the primary boot slot (via a new GetPrimary D-Bus
wrapper) with the booted slot, re-creating the REBOOT_REQUIRED issue as well.
mark_healthy now skips marking the booted slot active while an update is
pending.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Split pending update detection condition
Fix pylint R0916 (too-many-boolean-expressions) by splitting the guard in
_detect_pending_update into a slot data validity check and the actual
pending update check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Report pending OS update as current version in /os/info
The Core update entity derives update availability by comparing version
with version_latest from /os/info, so it keeps offering an update that is
already installed until the system is rebooted. Report an installed update
pending activation as the current version so existing Core releases
reflect update availability correctly. Once Core consumes version_pending,
this can be limited to Core versions predating that support.
The hassos field of the root /info endpoint keeps reporting the running
version, Core only uses it as a HAOS presence check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Return 404 for raspberrypi endpoints on boards without firmware
Return "not found" when board doesn't have the firmware update
available. Any other error may indicate it's worth retrying later (as
discussed in [1]), so 404 is more appropriate here.
[1] https://github.com/home-assistant/core/pull/172929#discussion_r3363706261
* Consistently return APINotFound with debug log
* Add Raspberry Pi firmware update API
Expose `io.hass.os.Boards.RaspberryPi.Firmware` via a D-Bus proxy and
`GET/POST /os/boards/raspberrypi/firmware[/update]` REST endpoints for
Raspberry Pi 4 / 5 / CM4 (Yellow). Gated on OS Agent >= 1.9.0. The
update job raises a `REBOOT_REQUIRED` resolution issue on success and
rejects up front when the agent reports `update_blocked`.
The `blocked_reason` field currently returns only
`unsupported_boot_device` regardless of the underlying cause (CM4
without self-update, USB/NVMe boot, etc.), more reasons may be added
later if we need to make distinction.
Refs home-assistant/operating-system#4631
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix pylint issue in tests
* Return blocked_reason=None instead of empty string when update is not blocked
* Fix typo in update_raspberrypi_firmware docstring
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Reject API call for update early if blocked
* Flip API availablility check in _check_rpi_firmware_available
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Remove extra newline in docstring
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* 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.
* Refactor API registration to support v1/v2 via shared methods
- Add AppVersion StrEnum (V1, V2) to supervisor/api/const.py
- Replace self.v2_app with self._v2_app and expose a versions property
(dict[AppVersion, web.Application]) computed dynamically so that test
fixtures reassigning self.webapp are automatically reflected in V1
- All _register_* methods now accept a required app: web.Application
parameter; version-specific routes are gated with
"if app is self.versions[AppVersion.V1/V2]:"
- load() loops over enabled_versions (V1 always, V2 when feature-flagged)
and calls each registration method once per version, no duplication
- Static resources are registered before webapp.add_subapp() to avoid
registering into a frozen router
- add_subapp uses self.webapp directly for readability
- Fold _register_v2_apps/_register_v2_backups/_register_v2_store into
their respective unified methods; remove the now-defunct _register_v2_*
helpers and the _api_apps/_api_backups/_api_store instance vars
- _register_proxy and _register_ingress updated to accept app; legacy
/homeassistant/* proxy routes gated behind V1 conditional
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add dual v1/v2 parametrization to API tests
All 163 tests across 17 API modules that register identically on both
v1 and v2 now run against both versions via api_client_with_prefix.
- tests/api/conftest.py: advanced_logs_tester switched to
api_client_with_prefix so log-endpoint tests are auto-parametrized;
accepts optional v2_path_prefix kwarg for paths that differ by version
- tests/api/test_{auth,discovery,dns,docker,hardware,host,ingress,
jobs,mounts,network,os,resolution,security,services,supervisor}.py:
api_client -> api_client_with_prefix with path prefix unpacking
- supervisor/api/__init__.py: _register_panel() moved outside the
version loop -- frontend static assets are V1-only
- tests/api/test_panel.py: kept on plain api_client (V1-only)
Tests intentionally kept V1-only:
- auth/discovery: use indirect api_client parametrize for addon context
- homeassistant: all tests call legacy /homeassistant/* paths (V1-only)
- jobs (4 tests): inner @Job-decorated classes register names into a
module-level set; re-running the same test raises RuntimeError
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Extend dual v1/v2 parametrization to homeassistant and jobs tests
tests/api/conftest.py:
- Add core_api_client_with_root fixture parametrized over three paths:
v1-core: /core/... (canonical v1 path)
v1-legacy: /homeassistant/... (legacy v1 alias, same handlers)
v2-core: /v2/core/... (canonical v2 path)
tests/api/test_homeassistant.py:
- Switch all 17 api_client tests to core_api_client_with_root so each
test runs against all three access paths (v1 canonical, v1 legacy
alias, v2 canonical), exercising every registered route
tests/api/test_jobs.py:
- Promote four inner TestClass definitions to module-level helpers
(_JobsTreeTestHelper, _JobManualCleanupTestHelper,
_JobsSortedTestHelper, _JobWithErrorTestHelper) so that @Job name
registration into the global _JOB_NAMES set only happens once at
import time rather than on each parametrized test run
- Replace closure references to outer-scope coresys with self.coresys
- Use api_client_with_prefix for dual-version coverage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix typo
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-27 23:39:47 +02:00
Jan ČermákGitHubcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Add API for swap configuration
Add HTTP API for swap size and swappiness to /os/config/swap. Individual
options can be set in JSON and are calling the DBus API added in OS
Agent 1.7.x, available since OS 15.0. Check for presence of OS of the
required version and return 404 if the criteria are not met.
* Fix type hints and reboot_required logic
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Fix formatting after adding suggestions from GH
* Address @mdegat01 review comments
- Improve swap options validation
- Add swap to the 'all' property of dbus agent
- Use APINotFound with reason instead of HTTPNotFound
- Reorder API routes
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Allow client to change boot slot via API
* Wrap call to rauc in job that checks for OS
* Reboot after changing the active boot slot
* Add test cases and clean up
* BootName to BootSlot
* Fix test
* Rename boot_name to boot_slot
* Fix tests after field change
* 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
* Add mount to supported features
* Typo in enable
* Fix places mocking os available without version
* Increase resilence of problematic repeat task test
* Return list of possible data disk targets
* fix path
* fix tests
* Add test
* Fix tests
* Add tests
* Add more tests
* Remove debug
* Address comments
* more clear