From 7306c9844b8cce9d7a4e7d2cddbaa5ccaff5bc27 Mon Sep 17 00:00:00 2001 From: AlexTemirov Date: Mon, 10 Aug 2026 22:12:37 -0700 Subject: [PATCH] Allow Hardware reinstall before robot attachment --- editor-server/server.py | 51 +++++++++++++++++++++++++++++- tests/test_editor_devices.py | 61 ++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/editor-server/server.py b/editor-server/server.py index d21ac16..133db66 100644 --- a/editor-server/server.py +++ b/editor-server/server.py @@ -6254,7 +6254,56 @@ def report(percent: int, message: str) -> None: hardware_ports if scope in {"all", "hardware"} else [] ) if scope == "hardware" and not selected_hardware_ports: - raise HTTPException(409, "This device has no attached Hardware services to update.") + operation = str(req.operation or "auto").strip().lower() + if operation not in {"auto", "update", "reinstall"}: + raise HTTPException( + 400, + "Hardware package operation must be update or reinstall.", + ) + report(10, "Reinstalling Robot Hardware before robot discovery") + installed = _install_device_host_hardware_payload( + host_id, + DiscoverHostRobotsReq(password=req.password), + progress=lambda value: report( + 10 + int(int(value.get("progress") or 0) * 0.85), + str(value.get("message") or "Installing Robot Hardware"), + ), + ) + hardware_commit = str( + (installed.get("install") or {}).get("hardware_commit") or "" + ) + summary = ( + "Robot Hardware package reinstalled. Use Find and attach robots " + "to configure the detected hardware provider." + ) + report(100, summary) + return { + "ok": True, + "scope": "hardware", + "device": installed["device"], + "update": { + "ok": True, + "components": [{ + "kind": "hardware", + "service_name": "blacknode-hardware-awaiting-device", + "port": 0, + "before": {"version": "unknown", "commit": ""}, + "after": { + "version": "unknown", + "commit": hardware_commit[:12], + }, + "changed": True, + "state": "configured", + "source_mode": "snapshot", + }], + }, + "runtime": {}, + "robots": [], + "stopped_deployments": [], + "controlled_robots": [], + "warnings": [], + "summary": summary, + } stopped_deployments: list[str] = [] controlled_robots: list[str] = [] diff --git a/tests/test_editor_devices.py b/tests/test_editor_devices.py index 499fe9d..cfffa24 100644 --- a/tests/test_editor_devices.py +++ b/tests/test_editor_devices.py @@ -2494,6 +2494,67 @@ def test_robot_discovery_configures_connected_robots_when_no_services_exist(self self.assertNotIn(hardware_token, response.text) self.assertNotIn("ssh-password", response.text) + def test_hardware_reinstall_before_robot_attachment_uses_package_installer(self): + host = server._device_registry.pair_host( + name="Jetson", + runtime_url="http://192.168.1.171:8766", + runtime_token="runtime-pairing-token-1234567890", + manifest={ + "service": "blacknode-runtime", + "protocol_version": 1, + "device_id": "ubuntu", + }, + managed_runtime={ + "ssh_host": "192.168.1.171", + "ssh_port": 22, + "ssh_username": "ubuntu", + "host_fingerprint": "SHA256:trusted-device-key", + "instance_id": "default", + "runtime_port": 8766, + "service_name": "blacknode-runtime.service", + "install_root": "~/Blacknode/devices/default", + "runtime_dir": "~/Blacknode/devices/default/runtime", + "packages_dir": "~/Blacknode/devices/default/runtime/packages", + "delivery_mode": "pc_assisted", + "stack_mode": "isolated", + "hardware_dir": "~/Blacknode/devices/default/hardware", + }, + ) + installed_device = server._device_registry.get_host_public(host["id"]) + progress = [] + + with patch.object( + server, + "_install_device_host_hardware_payload", + return_value={ + "ok": True, + "device": installed_device, + "install": { + "hardware_dir": "~/Blacknode/devices/default/hardware", + "hardware_commit": "a" * 40, + }, + }, + ) as install: + result = server._update_device_host_payload( + host["id"], + server.UpdateManagedDeviceReq( + password="ssh-password", + scope="hardware", + operation="reinstall", + ), + progress.append, + ) + + install.assert_called_once() + self.assertTrue(result["ok"]) + self.assertEqual(result["update"]["components"][0]["port"], 0) + self.assertEqual( + result["update"]["components"][0]["after"]["commit"], + "a" * 12, + ) + self.assertIn("Find and attach robots", result["summary"]) + self.assertEqual(progress[-1]["progress"], 100) + def test_runtime_only_device_can_install_managed_hardware_package(self): managed = { "ssh_host": "192.168.1.87",