From 2a8aad446fd5a1050d5c8e935d17e672e0775af4 Mon Sep 17 00:00:00 2001 From: Luke Date: Sun, 13 Sep 2026 15:15:20 -0400 Subject: [PATCH] Prepare v1.1.0-rc1 with an opt-in Home Assistant Beta add-on --- .github/workflows/docker-release.yml | 6 +- docs/home_assistant.md | 14 +++++ pyproject.toml | 2 +- roborock_local_server_beta_addon/CHANGELOG.md | 8 +++ roborock_local_server_beta_addon/DOCS.md | 59 ++++++++++++++++++ roborock_local_server_beta_addon/config.yaml | 62 +++++++++++++++++++ src/roborock_local_server/__init__.py | 2 +- tests/test_version_sync.py | 34 ++++++++-- uv.lock | 2 +- 9 files changed, 178 insertions(+), 11 deletions(-) create mode 100644 roborock_local_server_beta_addon/CHANGELOG.md create mode 100644 roborock_local_server_beta_addon/DOCS.md create mode 100644 roborock_local_server_beta_addon/config.yaml diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 97e20cf..9afdda9 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -126,9 +126,9 @@ jobs: flavor: latest=false tags: | type=semver,pattern={{version}},value=${{ github.event.release.tag_name }} - type=semver,pattern={{major}}.{{minor}},value=${{ github.event.release.tag_name }} - type=semver,pattern={{major}},value=${{ github.event.release.tag_name }} - type=raw,value=latest,enable=${{ startsWith(github.event.release.tag_name, 'v') && !contains(github.event.release.tag_name, '-') }} + type=semver,pattern={{major}}.{{minor}},value=${{ github.event.release.tag_name }},enable=${{ !github.event.release.prerelease && !contains(github.event.release.tag_name, '-') }} + type=semver,pattern={{major}},value=${{ github.event.release.tag_name }},enable=${{ !github.event.release.prerelease && !contains(github.event.release.tag_name, '-') }} + type=raw,value=latest,enable=${{ !github.event.release.prerelease && startsWith(github.event.release.tag_name, 'v') && !contains(github.event.release.tag_name, '-') }} type=sha,prefix=sha- labels: | org.opencontainers.image.title=roborock-local-server diff --git a/docs/home_assistant.md b/docs/home_assistant.md index aaa5d0f..6072b27 100644 --- a/docs/home_assistant.md +++ b/docs/home_assistant.md @@ -60,6 +60,20 @@ After the dashboard opens, complete the same post-start steps as the Docker inst If you need the MITM protocol sync secret for the Roborock app flow, sign in to the admin page and open **Protocol Auth**. The dashboard shows the active `admin.session_secret`, so you do not need to inspect `/data/config.toml` manually. +### Opt-in Beta testing + +The repository also provides **Roborock Local Server Beta** for prerelease +testing. Install it explicitly from the add-on store after its prerelease image +has been published. Stable installations remain on their stable image version. + +Beta has a separate add-on slug and persistent data, so the first installation +requires its own configuration and cloud import. If reusing the same hostname +and ports, stop the stable add-on and disable its start-on-boot setting while +testing. Later Beta versions use the normal Home Assistant update flow. + +See the [Beta setup and testing instructions](https://github.com/Python-roborock/local_roborock_server/blob/main/roborock_local_server_beta_addon/DOCS.md) +for installation, V2 checkpoints, and returning to stable. + ### Add-on Behavior - The add-on always runs the embedded MQTT broker and keeps the topic bridge enabled. diff --git a/pyproject.toml b/pyproject.toml index 8c7ef0c..b129c12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "roborock-local-server" -version = "1.0.2" +version = "1.1.0-rc1" description = "private local Roborock server stack." requires-python = ">=3.11,<3.14" readme = "README.md" diff --git a/roborock_local_server_beta_addon/CHANGELOG.md b/roborock_local_server_beta_addon/CHANGELOG.md new file mode 100644 index 0000000..669761a --- /dev/null +++ b/roborock_local_server_beta_addon/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## 1.1.0-rc1 + +- Added an opt-in Beta add-on with its own configuration and persistent data. The existing stable add-on stays on 1.0.2. +- Added experimental RSA-4096 / SHA-384 public-key recovery for V2 `GET /region` onboarding requests observed on Saros a279. +- Preserve request version metadata, resume pending recovery after a server restart, and verify all captured samples before accepting a recovered V2 public key. +- Full V2 onboarding and MQTT connectivity still require hardware validation. The V2 unsupported onboarding status remains in place. diff --git a/roborock_local_server_beta_addon/DOCS.md b/roborock_local_server_beta_addon/DOCS.md new file mode 100644 index 0000000..095f07a --- /dev/null +++ b/roborock_local_server_beta_addon/DOCS.md @@ -0,0 +1,59 @@ +# Roborock Local Server Beta + +This is an opt-in prerelease for testing experimental changes. Version +`1.1.0-rc1` includes V2 public-key recovery verified against saved Saros a279 +requests. Complete V2 onboarding and MQTT connectivity still need hardware tests. + +The stable **Roborock Local Server** add-on remains on `1.0.2`. Installing or +updating that add-on does not select this prerelease. Beta is a separate add-on, +with its own settings, cloud import, keys, and persistent `/data` directory. + +## First installation + +1. Wait until the `v1.1.0-rc1` GitHub prerelease has published its container image. +2. Add the repository `https://github.com/Python-roborock/local_roborock_server` + in the Home Assistant add-on store if it is not already present, then refresh + the store and install **Roborock Local Server Beta**. +3. Back up the stable add-on if installed. If reusing its hostname and ports, + stop the stable add-on and turn off its **Start on boot** setting while testing. + The two add-ons use the same default ports (`555` and `8881`) and cannot run + together on those ports. Beta defaults to manual startup. +4. Enter the server hostname, ports, admin password, protocol email/PIN, and TLS + settings in Beta's configuration. Settings and private certificate files are + not automatically copied from the stable add-on; use certificate paths that + Beta can access, or configure certificate issuance for Beta. +5. Start Beta, open `https://YOUR_API_HOST:555/admin` using the configured port, + and perform cloud import so the test vacuum appears. Enable **Allow new app + logins, onboarding, and first-time vacuum connections**. +6. On a second computer with Wi-Fi, use the onboarding scripts from the + `v1.1.0-rc1` checkout and run: + + ```bash + uv run start_onboarding.py --server YOUR_API_HOST:555 + ``` + + Follow the model's Wi-Fi reset and hotspot prompts. The terminal tool allows + retrying while the server still marks V2 unsupported. + +## What to check + +The V2 query sample counter may remain zero because V2 uses header signatures. +Wait for **Public Key determined** in the admin dashboard, then perform another +pairing cycle and check for NC registration, authenticated MQTT traffic, status +updates, and a command response. A recovered key or HTTP 200 alone does not +establish successful onboarding. The unsupported banner remains in this build. + +Keep the saved Beta data between attempts. Report the model, firmware, Beta +version, recovery state, and furthest connection stage reached. Keep original +logs locally and redact credentials before sharing excerpts. + +## Later updates and returning to stable + +Subsequent prereleases appear as updates to **Roborock Local Server Beta** after +the store refreshes. Update that entry normally to keep its settings and samples. + +To return to stable, stop Beta and start the stable add-on. Restore its original +start-on-boot setting if desired. Stable's saved data is retained, but changes +made while testing Beta are not copied back. A vacuum paired to Beta may need +another onboarding cycle, and clients reconfigured for Beta may need to be +reconfigured for stable again. diff --git a/roborock_local_server_beta_addon/config.yaml b/roborock_local_server_beta_addon/config.yaml new file mode 100644 index 0000000..605054c --- /dev/null +++ b/roborock_local_server_beta_addon/config.yaml @@ -0,0 +1,62 @@ +name: Roborock Local Server Beta +version: "1.1.0-rc1" +slug: roborock_local_server_beta +description: Opt-in prerelease testing of the Roborock local server, including experimental V2 key recovery. +stage: experimental +url: "https://github.com/Python-roborock/local_roborock_server" +image: "ghcr.io/python-roborock/local_roborock_server" +startup: services +boot: manual +init: false +arch: + - amd64 + - aarch64 +ports: + 555/tcp: 555 + 8881/tcp: 8881 +ports_description: + 555/tcp: Roborock HTTPS API + 8881/tcp: Roborock MQTT TLS proxy +map: + - ssl:ro + - addon_config:rw + - all_addon_configs:ro +webui: "https://[HOST]:[PORT:555]/admin" +options: + stack_fqdn: "api-roborock.example.com" + https_port: 555 + mqtt_tls_port: 8881 + advertised_https_port: 0 + advertised_mqtt_tls_port: 0 + region: "us" + tls_mode: "provided" + tls_base_domain: "" + tls_email: "" + acme_server: "zerossl" + acme_eab_kid: "" + acme_eab_hmac_key: "" + cloudflare_token: "" + cert_file: "" + key_file: "" + admin_password: "" + protocol_login_email: "" + protocol_login_pin: "" +schema: + stack_fqdn: str + https_port: port + mqtt_tls_port: port + advertised_https_port: int + advertised_mqtt_tls_port: int + region: list(us|eu|cn|ru) + tls_mode: list(provided|cloudflare_acme) + tls_base_domain: str + tls_email: str + acme_server: list(zerossl|actalis) + acme_eab_kid: str + acme_eab_hmac_key: password + cloudflare_token: str + cert_file: str + key_file: str + admin_password: password + protocol_login_email: email + protocol_login_pin: password diff --git a/src/roborock_local_server/__init__.py b/src/roborock_local_server/__init__.py index 041f97c..8a03ec7 100644 --- a/src/roborock_local_server/__init__.py +++ b/src/roborock_local_server/__init__.py @@ -2,4 +2,4 @@ __all__ = ["__version__"] -__version__ = "1.0.2" +__version__ = "1.1.0-rc1" diff --git a/tests/test_version_sync.py b/tests/test_version_sync.py index 92a602e..e93daf9 100644 --- a/tests/test_version_sync.py +++ b/tests/test_version_sync.py @@ -2,6 +2,9 @@ import tomllib from pathlib import Path +from packaging.version import Version +import yaml + from roborock_local_server import __version__ @@ -17,8 +20,29 @@ def test_init_module_exports_single_version_literal() -> None: def test_home_assistant_addon_version_matches_package_version() -> None: - addon_config = Path("roborock_local_server_addon/config.yaml").read_text(encoding="utf-8") - match = re.search(r'^version:\s*"([^"]+)"\s*$', addon_config, re.MULTILINE) - assert match is not None - assert match.group(1) == __version__ - + # Prereleases update only the opt-in Beta add-on. Stable keeps its last release. + directory = ( + "roborock_local_server_beta_addon" + if Version(__version__).is_prerelease + else "roborock_local_server_addon" + ) + addon = yaml.safe_load(Path(directory, "config.yaml").read_text(encoding="utf-8")) + assert addon["version"] == __version__ + + +def test_stable_addon_never_selects_a_prerelease_image() -> None: + addon = yaml.safe_load(Path("roborock_local_server_addon/config.yaml").read_text(encoding="utf-8")) + version = Version(addon["version"]) + assert not version.is_prerelease and not version.is_devrelease + assert addon["slug"] == "roborock_local_server" + assert addon["image"] == "ghcr.io/python-roborock/local_roborock_server" + + +def test_beta_addon_requires_a_separate_opt_in_installation() -> None: + stable = yaml.safe_load(Path("roborock_local_server_addon/config.yaml").read_text(encoding="utf-8")) + beta = yaml.safe_load(Path("roborock_local_server_beta_addon/config.yaml").read_text(encoding="utf-8")) + assert beta["slug"] != stable["slug"] + assert beta["name"] != stable["name"] + assert beta["stage"] == "experimental" + assert beta["boot"] == "manual" + assert Version(beta["version"]).is_prerelease diff --git a/uv.lock b/uv.lock index fc0aa59..081a409 100644 --- a/uv.lock +++ b/uv.lock @@ -1266,7 +1266,7 @@ wheels = [ [[package]] name = "roborock-local-server" -version = "1.0.2" +version = "1.1.0rc1" source = { editable = "." } dependencies = [ { name = "aiohttp" },