mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 03:00:18 +02:00
Windows: fix ssh config ACL, link-local IPv6, and setup under python -I
- ~/.ssh/config writes swapped in a temp file that inherited the .ssh folder's ACL; Windows' OpenSSH refuses one granting another account (even a deleted one) more than read: "Bad owner or permissions". Writes now give the file an owner-only ACL (frame_host.make_private), and the server repairs a refused config once per run and retries. - frame_link.probe named a link-local IPv6 zone with if_indextoname, which on Windows is "ethernet_32769"; Windows' ssh can't resolve that, so a headset found at fe80:: showed as "can't find the Frame". Use the zone number there. - frame_connect.py imports frame_host (since #60), but the app runs it with python -I, which leaves its folder off sys.path: Set Up Connection exited with ModuleNotFoundError. Add the folder, as server.py does. Verified on a Windows 11 VM against OpenSSH_for_Windows 9.5p2: the old write reproduces the reported error with an orphan SID's Modify ACE; the new write, repair and server retry all leave a config ssh accepts; ssh to %ethernet_32769 fails to resolve while %5 connects. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a4031db052
commit
07f44f9082
6 files changed
+264
-6
No files matched your search
+32
-2
@@ -241,8 +241,7 @@ def _write_config(path, text, expected):
|
||||
try:
|
||||
with os.fdopen(fd_, "w", encoding="utf-8") as fh:
|
||||
fh.write(text)
|
||||
if not frame_host.WINDOWS:
|
||||
tmp.chmod(0o600)
|
||||
frame_host.make_private(tmp) # best effort: an edit still beats none (repair_permissions insists)
|
||||
for attempt in range(20): # Windows: a running ssh.exe can hold the file for a moment
|
||||
if read_config(path) != expected:
|
||||
return False
|
||||
@@ -271,6 +270,37 @@ def _edit_config(path, change):
|
||||
raise OSError(f"{path} kept changing while Frame Control tried to update it")
|
||||
|
||||
|
||||
def repair_permissions(path=None):
|
||||
"""Give ~/.ssh/config make_private's ACL by swapping in a byte-for-byte copy: for a
|
||||
file Windows' OpenSSH refuses ("Bad owner or permissions"). -> True only if the copy
|
||||
got that ACL and replaced the file."""
|
||||
path = Path(path or ssh_config())
|
||||
with _config_lock, file_lock(path.with_name(LOCK_NAME)):
|
||||
try:
|
||||
data = path.read_bytes()
|
||||
except OSError:
|
||||
return False
|
||||
fd_, tmp = tempfile.mkstemp(prefix="config.frame-control.", dir=str(path.parent))
|
||||
tmp = Path(tmp)
|
||||
try:
|
||||
with os.fdopen(fd_, "wb") as fh:
|
||||
fh.write(data)
|
||||
if not frame_host.make_private(tmp):
|
||||
return False
|
||||
for attempt in range(20): # a running ssh.exe can hold the file for a moment
|
||||
if path.read_bytes() != data:
|
||||
return False
|
||||
try:
|
||||
os.replace(tmp, path)
|
||||
return True
|
||||
except PermissionError:
|
||||
time.sleep(0.25)
|
||||
return False
|
||||
finally:
|
||||
if tmp.exists():
|
||||
tmp.unlink()
|
||||
|
||||
|
||||
def rewrite_block(alias, path=None, hostname=None, user=None, port=None, expect=None):
|
||||
"""Change HostName, User or Port inside ALIAS's managed block, leaving the rest of the
|
||||
file alone. -> True if the file changed. Does nothing if there's no such block, or
|
||||
|
||||
Reference in new issue
Block a user