fix(install): probe /dev/tty by opening it, not bare existence (#16746)
In Docker builds the `/dev/tty` device node is present in the mount
namespace, so `[ -e /dev/tty ]` returns true — but opening it fails
with `ENXIO: No such device or address`. Under the old gate the
"no terminal available" skip never triggered, the setup wizard ran,
and the build aborted a few lines later when bash tried `< /dev/tty`:
/tmp/install.sh: line 1347: /dev/tty: No such device or address
Replace the existence check with `(: </dev/tty) 2>/dev/null`, which
actually attempts to open /dev/tty in a subshell. The probe succeeds
when piped from `curl | bash` on a real terminal (the wizard's intended
use case) and fails cleanly in Docker build / CI contexts so the skip
kicks in before the redirect can crash.
Add a regression test that statically asserts run_setup_wizard does not
gate on the bare existence check and that the open-based probe is in
place.
Fixes #16746.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b2339c87e4
commit
20c9340c34
@ -1330,7 +1330,12 @@ run_setup_wizard() {
|
||||
# The setup wizard reads from /dev/tty, so it works even when the
|
||||
# install script itself is piped (curl | bash). Only skip if no
|
||||
# terminal is available at all (e.g. Docker build, CI).
|
||||
if ! [ -e /dev/tty ]; then
|
||||
#
|
||||
# Probe by actually opening /dev/tty: a bare existence test passes
|
||||
# in Docker builds where the device node is in the mount namespace
|
||||
# but opening fails with ENXIO, so the wizard would proceed and
|
||||
# then crash on `< /dev/tty` below.
|
||||
if ! (: </dev/tty) 2>/dev/null; then
|
||||
log_info "Setup wizard skipped (no terminal available). Run 'hermes setup' after install."
|
||||
return 0
|
||||
fi
|
||||
|
||||
41
tests/test_install_sh_setup_wizard_tty_probe.py
Normal file
41
tests/test_install_sh_setup_wizard_tty_probe.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""Regression for #16746: setup-wizard tty gate must actually open /dev/tty.
|
||||
|
||||
In a Docker build, ``/dev/tty`` exists as a device node (so ``[ -e /dev/tty ]``
|
||||
returns true) but opening it fails with ``ENXIO: No such device or address``.
|
||||
Under the old gate the wizard proceeded past the "no terminal available" skip
|
||||
and then crashed on the ``< /dev/tty`` redirect a few lines later, aborting
|
||||
the entire image build. The fix replaces the bare existence check with an
|
||||
open-based probe so the skip kicks in correctly.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
INSTALL_SH = REPO_ROOT / "scripts" / "install.sh"
|
||||
|
||||
|
||||
def _extract_run_setup_wizard() -> str:
|
||||
"""Return the body of run_setup_wizard() as a single string."""
|
||||
text = INSTALL_SH.read_text()
|
||||
start = text.index("run_setup_wizard()")
|
||||
# The next top-level function follows immediately; use it as the end marker.
|
||||
end = text.index("\nmaybe_start_gateway()", start)
|
||||
return text[start:end]
|
||||
|
||||
|
||||
def test_run_setup_wizard_does_not_use_bare_existence_check() -> None:
|
||||
body = _extract_run_setup_wizard()
|
||||
assert "[ -e /dev/tty ]" not in body, (
|
||||
"run_setup_wizard guards on `[ -e /dev/tty ]`, which is true in Docker "
|
||||
"builds where the device node exists but cannot be opened (ENXIO). "
|
||||
"Use an open-based probe such as `(: </dev/tty) 2>/dev/null` so the "
|
||||
"skip kicks in before the wizard tries to read from /dev/tty. See #16746."
|
||||
)
|
||||
|
||||
|
||||
def test_run_setup_wizard_uses_open_based_tty_probe() -> None:
|
||||
body = _extract_run_setup_wizard()
|
||||
assert "(: </dev/tty)" in body, (
|
||||
"run_setup_wizard must probe /dev/tty by actually opening it before "
|
||||
"running the wizard. See #16746."
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user