From 8bd8d635463d2bf73c3f460df68d7df837159787 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Tue, 29 Sep 2026 00:44:18 +1000 Subject: [PATCH] Devices: fixes from review round 21 - Behind a jump host, a forward it couldn't open moves on to the next address; only a refused key stops (judged by ssh's words, not the step). - Add a headset suggests an alias no Host in ~/.ssh/config already uses. Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/test_link.py | 11 +++++++++++ ui/frame_link.py | 17 ++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/tests/test_link.py b/tests/test_link.py index 81e2e4d..b064f0c 100644 --- a/tests/test_link.py +++ b/tests/test_link.py @@ -317,6 +317,15 @@ class Connecting(unittest.TestCase): self.assertFalse(fl.Link.is_target('Authenticated to bastion ([1.2.3.4]:22) using "publickey".', opts, "frame")) self.assertTrue(fl.Link.is_target('Authenticated to 10.0.0.5 ([10.0.0.5]:22) using "publickey".', opts, "frame")) + def test_a_forward_the_jump_host_couldnt_open_tries_the_next_address(self): + said = ["Authenticated to bastion ([1.2.3.4]:22) using \"publickey\".", + "channel 0: open failed: connect failed: Connection refused", "stdio forwarding failed"] + self.link.state["stages"] = [{"id": i, "state": "pending", "started": None, "ended": None, "detail": ""} + for i, _ in fl.STAGES] + self.assertEqual(self.link.failed("login", said, False, "frame"), "next") + self.assertEqual(self.link.failed("login", ["steamos@frame: Permission denied (publickey)."], False, "frame"), + "stop") + def test_probes_from_an_earlier_attempt_leave_the_new_rows_alone(self): self.link.state.update(attempt=2, probes=[{"host": "b", "state": "waiting"}]) self.link.probe_update(0, 1, state="answered", ip="10.0.0.2") @@ -528,6 +537,8 @@ class Connecting(unittest.TestCase): self.assertIn("frame-9", out["message"]) self.assertEqual(out["active"], d["id"]) self.assertEqual(fl.next_alias(self.link), "frame") + (self.dir / "ssh" / "config").write_text("Host frame lab-*\n HostName 10.0.0.7\n") # someone's own `frame` + self.assertEqual(fl.next_alias(self.link), "frame-2") @unittest.skipIf(os.name == "nt", "the stand-in ssh is a POSIX script") diff --git a/ui/frame_link.py b/ui/frame_link.py index bf5b85c..1867355 100644 --- a/ui/frame_link.py +++ b/ui/frame_link.py @@ -914,7 +914,11 @@ class Link: self.fail("identity", "This address answered as a different headset (its SSH identity doesn't match). " "If SteamOS was reinstalled, use Forget Identity on the Devices tab.", text) return "next" - if step == "login" or re.search(r"Permission denied", text): + # A refused key is the same at every address: stop. (Judged by ssh's own words, not + # the step: a jump host's progress lines look like the headset's.) A forward that + # a jump host couldn't open is about this address only: try the next. + forward = re.search(r"open failed|forwarding failed|Connection refused|Connection closed|timed out", text) + if re.search(r"Permission denied", text) and not forward: self.fail("login", self.explain(text) or "The headset didn't accept this computer's key.", text) return "stop" if step == "connected": @@ -1141,8 +1145,15 @@ def devices_action(link, body, open_setup, busy=lambda: 0): def next_alias(link): - taken = {d["alias"] for d in link.reg.devices()} | {b["alias"] for b in frame_devices.parse_blocks( - frame_devices.read_config())} + """A free alias for a new headset: not one Frame Control knows, nor any `Host` name + already in ~/.ssh/config (Set Up Connection's block would shadow it).""" + text = frame_devices.read_config() + taken = {d["alias"] for d in link.reg.devices()} | {b["alias"] for b in frame_devices.parse_blocks(text)} + taken |= {a for a in (link.session_alias, link.override, link.active_device().get("alias")) if a} + for line in text.splitlines(): + f = line.split() + if f and f[0].lower() == "host": + taken |= {name for name in f[1:] if not any(c in name for c in "*?!")} if "frame" not in taken: return "frame" n = 2