From dcc40767887821cd864a4e9506a941dfa232e053 Mon Sep 17 00:00:00 2001 From: Michael MacLeod <1720142+MacLeodMike@users.noreply.github.com> Date: Thu, 7 May 2026 08:55:16 -0400 Subject: [PATCH] fix: support IPv6 bind address in QuietHTTPServer Detect IPv6 addresses (containing ':') in QuietHTTPServer.__init__ and set address_family to AF_INET6 before socket creation, fixing EAFNOSUPPORT when binding to :: or ::1. Also updates the loopback check to recognize ::1 and the container warning to mention :: as the IPv6 equivalent of 0.0.0.0. Documents IPv6 usage in HERMES_WEBUI_HOST env var description. --- README.md | 2 +- server.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 810b9eaf..5fa41870 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,7 @@ Full list of environment variables: |---|---|---| | `HERMES_WEBUI_AGENT_DIR` | auto-discovered | Path to the hermes-agent checkout | | `HERMES_WEBUI_PYTHON` | auto-discovered | Python executable | -| `HERMES_WEBUI_HOST` | `127.0.0.1` | Bind address | +| `HERMES_WEBUI_HOST` | `127.0.0.1` | Bind address (`0.0.0.0` for all IPv4, `::` for all IPv6, `::1` for IPv6 loopback) | | `HERMES_WEBUI_PORT` | `8787` | Port | | `HERMES_WEBUI_STATE_DIR` | `~/.hermes/webui-mvp` | Where sessions and state are stored | | `HERMES_WEBUI_DEFAULT_WORKSPACE` | `~/workspace` | Default workspace | diff --git a/server.py b/server.py index a8bc75b6..7e1563ac 100644 --- a/server.py +++ b/server.py @@ -33,6 +33,9 @@ class QuietHTTPServer(ThreadingHTTPServer): request_queue_size = 64 def __init__(self, *args, **kwargs): + server_address = args[0] if args else kwargs.get('server_address', None) + if server_address and ':' in server_address[0]: + self.address_family = socket.AF_INET6 super().__init__(*args, **kwargs) self.accept_loop_requests_total = 0 self.accept_loop_last_request_at = 0.0 @@ -244,7 +247,7 @@ def main() -> None: print(f' Set a password via Settings or HERMES_WEBUI_PASSWORD env var.', flush=True) print(f' To suppress: bind to 127.0.0.1 or set a password.', flush=True) if within_container: - print(f' Note: You are running within a container, must bind to 0.0.0.0 to publish the port.', flush=True) + print(f' Note: You are running within a container, must bind to 0.0.0.0 (IPv4) or :: (IPv6) to publish the port.', flush=True) elif not is_auth_enabled(): print(f' [tip] No password set. Any process on this machine can read sessions', flush=True) print(f' and memory via the local API. Set HERMES_WEBUI_PASSWORD to', flush=True) @@ -295,7 +298,7 @@ def main() -> None: scheme = 'http' print(f' Hermes Web UI listening on {scheme}://{HOST}:{PORT}', flush=True) - if HOST == '127.0.0.1' or within_container: + if HOST in ('127.0.0.1', '::1') or within_container: print(f' Remote access: ssh -N -L {PORT}:127.0.0.1:{PORT} @', flush=True) print(f' Then open: {scheme}://localhost:{PORT}', flush=True) print('', flush=True)