diff --git a/modules/workflow_serial.py b/modules/workflow_serial.py index 534fd82..86a6991 100644 --- a/modules/workflow_serial.py +++ b/modules/workflow_serial.py @@ -26,8 +26,24 @@ from ui import ( prompt, prompt_ip, prompt_yn, prompt_password, ) -# Strip ANSI escape sequences from serial terminal output -_ANSI_RE = re.compile(r'\x1b\[[0-9;]*[mGKHF]') +# Strip ANSI escape sequences from serial terminal output. +# Catches CSI sequences (ESC[...X) and other two-character ESC sequences. +_ANSI_RE = re.compile(r'\x1b(?:\[[0-9;]*[A-Za-z]|[^[])') + + +def _at_shell_prompt(text: str) -> bool: + """ + Return True if any line in text looks like a root shell prompt. + Checks whether a non-empty line ends with '#' or '# ' — the pattern + produced by prompts like 'root@hostname:~#' — without being tripped + up by the word 'login' appearing elsewhere in the output (e.g. in + 'Last login: ...' messages shown after a session is resumed). + """ + for line in text.splitlines(): + stripped = line.rstrip() + if stripped.endswith("#") or stripped.endswith("# "): + return True + return False # ── Serial Redfish transport ─────────────────────────────────────────────────── @@ -35,7 +51,8 @@ def _login_serial_console(ser: SerialPort, password: str) -> bool: """ Perform the root login sequence on the IOM serial console. Handles both the case where a login prompt is showing and where - a shell session is already active. + a shell session is already active (i.e. was never logged out after + a prior run — the IOM stays logged in until power-cycled). Returns True if a shell prompt is reached, False on failure. """ info("Logging in to IOM serial console...") @@ -43,13 +60,16 @@ def _login_serial_console(ser: SerialPort, password: str) -> bool: # Send a newline to get the current state of the console ser.send_line("", delay=0.5) response = _ANSI_RE.sub("", ser.read_until_quiet(quiet_period=0.5)) - low = response.lower() - # Already at a shell prompt — no login needed - if ("#" in response or "$" in response) and "login" not in low: + # Already at a shell prompt — no login needed. + # Use _at_shell_prompt() rather than a bare '#' check so that + # 'Last login: ...' text in the response does not cause a false negative. + if _at_shell_prompt(response): ok("Already at shell prompt.") return True + low = response.lower() + # Send username if we see a login prompt (or an empty/unknown response) if "login" in low or not response.strip(): ser.send_line("root", delay=0.5) @@ -60,7 +80,7 @@ def _login_serial_console(ser: SerialPort, password: str) -> bool: ser.send_line(password, delay=0.5) response = _ANSI_RE.sub("", ser.read_until_quiet(quiet_period=2.0)) - if "#" in response or "$" in response: + if _at_shell_prompt(response): ok("Logged in to IOM console.") return True