Auto-detect root password from IOM hostname on serial login
The BMC serial number (e.g. MXE3000048LHA03C) is embedded in the IOM hostname shown in the serial login prompt. It also doubles as the root password, so no manual entry is needed. - If at login prompt: extract serial from 'hostname login:' line - If already logged in: run 'hostname' on the shell and extract serial - Falls back to prompt_password() if extraction fails either way _login_serial_console() now returns (success, password) instead of bool. Callers in workflow_serial.py and workflow_check.py updated accordingly. CLAUDE.md updated to document the behaviour and remove it from planned features. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -90,9 +90,8 @@ def _check_via_serial():
|
||||
return
|
||||
|
||||
print()
|
||||
password = prompt_password()
|
||||
|
||||
if not _login_serial_console(ser, password):
|
||||
logged_in, password = _login_serial_console(ser)
|
||||
if not logged_in:
|
||||
error("Could not log in to IOM console. Returning to main menu.")
|
||||
close_serial_connection(ser, device)
|
||||
time.sleep(2)
|
||||
|
||||
@@ -46,14 +46,48 @@ def _at_shell_prompt(text: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _extract_serial_from_hostname(hostname: str) -> Optional[str]:
|
||||
"""
|
||||
Extract the BMC serial number from an IOM hostname.
|
||||
The serial number (which doubles as the root password) is a
|
||||
dash-separated segment matching MXE followed by alphanumerics.
|
||||
e.g. 'ves-ves-vds2249r-MXE3000048LHA03C-mgr1' → 'MXE3000048LHA03C'
|
||||
Returns None if no matching segment is found.
|
||||
"""
|
||||
for segment in hostname.split("-"):
|
||||
if re.match(r"^MXE[A-Z0-9]+$", segment, re.IGNORECASE):
|
||||
return segment
|
||||
return None
|
||||
|
||||
|
||||
def _detect_password_from_shell(ser: SerialPort) -> Optional[str]:
|
||||
"""
|
||||
When already logged in, run 'hostname' on the IOM shell and extract
|
||||
the BMC serial number from the output.
|
||||
"""
|
||||
ser.send_line("hostname", delay=0.3)
|
||||
out = _ANSI_RE.sub("", ser.read_until_quiet(quiet_period=0.5))
|
||||
for line in out.splitlines():
|
||||
pw = _extract_serial_from_hostname(line.strip())
|
||||
if pw:
|
||||
return pw
|
||||
return None
|
||||
|
||||
|
||||
# ── Serial Redfish transport ───────────────────────────────────────────────────
|
||||
def _login_serial_console(ser: SerialPort, password: str) -> bool:
|
||||
def _login_serial_console(ser: SerialPort) -> tuple:
|
||||
"""
|
||||
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 (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.
|
||||
|
||||
Auto-detects the root password from the IOM hostname — the BMC serial
|
||||
number (e.g. MXE3000048LHA03C) is embedded in both the login prompt
|
||||
hostname and the output of 'hostname', so no manual entry is needed.
|
||||
|
||||
- Login prompt present: extracts serial from 'hostname login:' line.
|
||||
- Already logged in: runs 'hostname' on the shell to extract the serial.
|
||||
- Falls back to prompting the user if auto-detection fails either way.
|
||||
|
||||
Returns (success: bool, password: str).
|
||||
"""
|
||||
info("Logging in to IOM serial console...")
|
||||
|
||||
@@ -61,20 +95,37 @@ def _login_serial_console(ser: SerialPort, password: str) -> bool:
|
||||
ser.send_line("", delay=0.5)
|
||||
response = _ANSI_RE.sub("", ser.read_until_quiet(quiet_period=0.5))
|
||||
|
||||
# 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.
|
||||
# Already at a shell prompt — extract password via 'hostname' command
|
||||
if _at_shell_prompt(response):
|
||||
ok("Already at shell prompt.")
|
||||
return True
|
||||
password = _detect_password_from_shell(ser)
|
||||
if password:
|
||||
ok(f"Password auto-detected: {_c(C.BOLD, password)}")
|
||||
return True, password
|
||||
warn("Could not auto-detect password from hostname.")
|
||||
return True, prompt_password()
|
||||
|
||||
low = response.lower()
|
||||
|
||||
# Try to extract the password from the login prompt hostname before
|
||||
# sending credentials — format: 'ves-ves-model-SERIAL-mgr1 login:'
|
||||
password = None
|
||||
login_match = re.search(r"(\S+)\s+login:", response, re.IGNORECASE)
|
||||
if login_match:
|
||||
password = _extract_serial_from_hostname(login_match.group(1))
|
||||
if password:
|
||||
ok(f"Password auto-detected from login prompt: {_c(C.BOLD, password)}")
|
||||
|
||||
# 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)
|
||||
response = _ANSI_RE.sub("", ser.read_until_quiet(quiet_period=0.5))
|
||||
|
||||
# Fall back to asking the user if auto-detection failed
|
||||
if not password:
|
||||
warn("Could not auto-detect password from login prompt.")
|
||||
password = prompt_password()
|
||||
|
||||
# Send password
|
||||
if "password" in response.lower():
|
||||
ser.send_line(password, delay=0.5)
|
||||
@@ -82,10 +133,10 @@ def _login_serial_console(ser: SerialPort, password: str) -> bool:
|
||||
|
||||
if _at_shell_prompt(response):
|
||||
ok("Logged in to IOM console.")
|
||||
return True
|
||||
return True, password
|
||||
|
||||
error(f"Login failed. Console response: {response.strip()[:120]}")
|
||||
return False
|
||||
return False, ""
|
||||
|
||||
|
||||
def _serial_redfish_request(ser: SerialPort, password: str, method: str,
|
||||
@@ -549,12 +600,10 @@ def configure_shelf() -> bool:
|
||||
time.sleep(2)
|
||||
return True
|
||||
|
||||
# Password needed before login
|
||||
# Log in to the IOM console (auto-detects password from hostname)
|
||||
print()
|
||||
cfg.password = prompt_password()
|
||||
|
||||
# Log in to the IOM console (required before any Redfish curl calls)
|
||||
if not _login_serial_console(ser, cfg.password):
|
||||
logged_in, cfg.password = _login_serial_console(ser)
|
||||
if not logged_in:
|
||||
error("Could not log in to IOM console. Returning to main menu.")
|
||||
close_serial_connection(ser, device)
|
||||
time.sleep(2)
|
||||
|
||||
Reference in New Issue
Block a user