v1.8.5: restore sudo auto-password + fix sudo SFTP operations
Terminal: - Auto-detect [sudo] password prompts in interactive shell output - Auto-send server password when sudo prompt detected - Reset detection flag on new command (Enter key) SSH client (SFTPSession): - Fix exec_command() sudo password timing (0.1s delay for prompt) - Fix listdir_attr_sudo() ls output parsing with proper maxsplit - Handle filenames with spaces, symlinks, and varied ls formats Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -465,6 +465,8 @@ class SFTPSession:
|
|||||||
full_cmd = cmd
|
full_cmd = cmd
|
||||||
stdin, stdout, stderr = self._client.exec_command(full_cmd, timeout=30)
|
stdin, stdout, stderr = self._client.exec_command(full_cmd, timeout=30)
|
||||||
if self.sudo_mode and user != "root" and password:
|
if self.sudo_mode and user != "root" and password:
|
||||||
|
# Wait briefly for sudo prompt to appear before sending password
|
||||||
|
time.sleep(0.1)
|
||||||
stdin.write(password + "\n")
|
stdin.write(password + "\n")
|
||||||
stdin.flush()
|
stdin.flush()
|
||||||
return stdout.read().decode("utf-8", errors="replace")
|
return stdout.read().decode("utf-8", errors="replace")
|
||||||
@@ -476,29 +478,35 @@ class SFTPSession:
|
|||||||
for line in output.strip().splitlines():
|
for line in output.strip().splitlines():
|
||||||
if line.startswith("total "):
|
if line.startswith("total "):
|
||||||
continue
|
continue
|
||||||
parts = line.split(None, 7)
|
# With --time-style=+%s the columns are:
|
||||||
|
# perms links owner group size mtime name [-> target if symlink]
|
||||||
|
# Use maxsplit=8 to preserve spaces in filenames, giving us:
|
||||||
|
# [0=perms, 1=links, 2=owner, 3=group, 4=size, 5=mtime, 6=name...]
|
||||||
|
parts = line.split(None, 8)
|
||||||
if len(parts) < 7:
|
if len(parts) < 7:
|
||||||
continue
|
continue
|
||||||
perms = parts[0]
|
perms = parts[0]
|
||||||
if perms.startswith("d") or perms.startswith("l") or perms.startswith("-"):
|
if not (perms.startswith("d") or perms.startswith("l") or perms.startswith("-")):
|
||||||
pass
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
size_str = parts[4]
|
|
||||||
mtime_str = parts[5]
|
|
||||||
# parts[6] may be time or name depending on format
|
|
||||||
# With --time-style=+%s: perms links owner group size epoch name
|
|
||||||
name = parts[6] if len(parts) == 7 else parts[7]
|
|
||||||
if name in (".", ".."):
|
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
size = int(size_str)
|
size = int(parts[4])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
size = 0
|
size = 0
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
mtime = int(mtime_str)
|
mtime = int(parts[5])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
mtime = 0
|
mtime = 0
|
||||||
|
continue
|
||||||
|
name = parts[6]
|
||||||
|
# Handle cases where name contains " -> " (symlinks) or has spaces
|
||||||
|
if len(parts) > 7:
|
||||||
|
# This means the filename itself contained spaces and was split
|
||||||
|
name = parts[6] + " " + parts[7]
|
||||||
|
# Strip symlink target (e.g. "name -> target")
|
||||||
|
name = name.split(" -> ")[0].strip()
|
||||||
|
if name in (".", ".."):
|
||||||
|
continue
|
||||||
mode = _parse_ls_perms(perms)
|
mode = _parse_ls_perms(perms)
|
||||||
entry = _SudoFileAttr(name, size, mtime, mode)
|
entry = _SudoFileAttr(name, size, mtime, mode)
|
||||||
results.append(entry)
|
results.append(entry)
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ class TerminalTab(ctk.CTkFrame):
|
|||||||
# Thread-safe data queue
|
# Thread-safe data queue
|
||||||
self._data_queue: queue.Queue[bytes] = queue.Queue()
|
self._data_queue: queue.Queue[bytes] = queue.Queue()
|
||||||
|
|
||||||
|
# Sudo auto-password detection
|
||||||
|
self._sudo_buffer = b"" # Buffer for detecting sudo prompts
|
||||||
|
self._sudo_sent = False # Prevent sending password twice for same prompt
|
||||||
|
|
||||||
def set_server(self, alias: str | None):
|
def set_server(self, alias: str | None):
|
||||||
if alias == self._current_alias:
|
if alias == self._current_alias:
|
||||||
return
|
return
|
||||||
@@ -151,7 +155,20 @@ class TerminalTab(ctk.CTkFrame):
|
|||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
if chunks:
|
if chunks:
|
||||||
self._terminal.feed(b"".join(chunks))
|
combined = b"".join(chunks)
|
||||||
|
self._sudo_buffer += combined
|
||||||
|
# Keep only last 200 bytes for pattern matching
|
||||||
|
self._sudo_buffer = self._sudo_buffer[-200:]
|
||||||
|
|
||||||
|
if not self._sudo_sent:
|
||||||
|
buf_str = self._sudo_buffer.decode("utf-8", errors="replace").lower()
|
||||||
|
if "[sudo] password for" in buf_str or buf_str.rstrip().endswith("password:"):
|
||||||
|
server = self.store.get_server(self._current_alias)
|
||||||
|
if server and server.get("password"):
|
||||||
|
self._session.send(server["password"].encode() + b"\n")
|
||||||
|
self._sudo_sent = True
|
||||||
|
|
||||||
|
self._terminal.feed(combined)
|
||||||
|
|
||||||
def _on_disconnected(self):
|
def _on_disconnected(self):
|
||||||
"""Called from SSH read thread."""
|
"""Called from SSH read thread."""
|
||||||
@@ -189,6 +206,9 @@ class TerminalTab(ctk.CTkFrame):
|
|||||||
session = self._session # local ref for thread safety
|
session = self._session # local ref for thread safety
|
||||||
if session and session.connected:
|
if session and session.connected:
|
||||||
session.send(data)
|
session.send(data)
|
||||||
|
# Reset sudo sent flag when user sends a new command (detects \r or \n)
|
||||||
|
if b'\r' in data or b'\n' in data:
|
||||||
|
self._sudo_sent = False
|
||||||
elif self._current_alias and not self._intentional_disconnect and self._reconnect_count == 0:
|
elif self._current_alias and not self._intentional_disconnect and self._reconnect_count == 0:
|
||||||
# Session dead, no reconnect in progress — trigger one attempt
|
# Session dead, no reconnect in progress — trigger one attempt
|
||||||
self._on_disconnected()
|
self._on_disconnected()
|
||||||
|
|||||||
BIN
releases/ServerManager-v1.8.5-win-x64.exe
Normal file
BIN
releases/ServerManager-v1.8.5-win-x64.exe
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
"""Version info for ServerManager."""
|
"""Version info for ServerManager."""
|
||||||
|
|
||||||
__version__ = "1.8.4"
|
__version__ = "1.8.5"
|
||||||
__app_name__ = "ServerManager"
|
__app_name__ = "ServerManager"
|
||||||
__author__ = "aibot777"
|
__author__ = "aibot777"
|
||||||
__description__ = "Desktop GUI for managing remote servers"
|
__description__ = "Desktop GUI for managing remote servers"
|
||||||
|
|||||||
Reference in New Issue
Block a user