mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 00:03:01 +03:00
fix(cli): canonicalize default gateway identity
This commit is contained in:
+13
-4
@@ -36,6 +36,15 @@ GatewayServiceFactory = Callable[[], Any]
|
|||||||
WebUIBundlePreparer = Callable[[Config, BuildMode], None]
|
WebUIBundlePreparer = Callable[[Config, BuildMode], None]
|
||||||
|
|
||||||
|
|
||||||
|
def _resolved_config_selector(config: str | None) -> Path:
|
||||||
|
"""Return the one canonical config identity used by every local client."""
|
||||||
|
if config:
|
||||||
|
return Path(config).expanduser().resolve(strict=False)
|
||||||
|
from nanobot.config.loader import get_config_path
|
||||||
|
|
||||||
|
return get_config_path().resolve(strict=False)
|
||||||
|
|
||||||
|
|
||||||
def create_gateway_app(
|
def create_gateway_app(
|
||||||
*,
|
*,
|
||||||
console: Console,
|
console: Console,
|
||||||
@@ -73,12 +82,12 @@ def create_gateway_app(
|
|||||||
def runtime_for_instance(*, workspace: str | None = None, config: str | None = None):
|
def runtime_for_instance(*, workspace: str | None = None, config: str | None = None):
|
||||||
if runtime_factory is not None:
|
if runtime_factory is not None:
|
||||||
return runtime_factory(workspace=workspace, config=config)
|
return runtime_factory(workspace=workspace, config=config)
|
||||||
config_path = str(Path(config).expanduser().resolve(strict=False)) if config else None
|
resolved_config = _resolved_config_selector(config)
|
||||||
|
config_path = str(resolved_config)
|
||||||
workspace_path = str(Path(workspace).expanduser().resolve(strict=False)) if workspace else None
|
workspace_path = str(Path(workspace).expanduser().resolve(strict=False)) if workspace else None
|
||||||
data_dir = Path(config_path).parent if config_path else None
|
|
||||||
return GatewayRuntime(
|
return GatewayRuntime(
|
||||||
paths=GatewayRuntimePaths.for_instance(
|
paths=GatewayRuntimePaths.for_instance(
|
||||||
data_dir=data_dir,
|
data_dir=resolved_config.parent,
|
||||||
workspace=workspace_path,
|
workspace=workspace_path,
|
||||||
config_path=config_path,
|
config_path=config_path,
|
||||||
)
|
)
|
||||||
@@ -101,7 +110,7 @@ def create_gateway_app(
|
|||||||
loaded_config: Config | None = None,
|
loaded_config: Config | None = None,
|
||||||
) -> GatewayStartOptions:
|
) -> GatewayStartOptions:
|
||||||
cfg = loaded_config or load_runtime_config(config, workspace)
|
cfg = loaded_config or load_runtime_config(config, workspace)
|
||||||
resolved_config = str(Path(config).expanduser().resolve()) if config else None
|
resolved_config = str(_resolved_config_selector(config)) if config else None
|
||||||
resolved_workspace = str(Path(workspace).expanduser().resolve(strict=False)) if workspace else None
|
resolved_workspace = str(Path(workspace).expanduser().resolve(strict=False)) if workspace else None
|
||||||
return GatewayStartOptions(
|
return GatewayStartOptions(
|
||||||
port=port if port is not None else cfg.gateway.port,
|
port=port if port is not None else cfg.gateway.port,
|
||||||
|
|||||||
@@ -384,16 +384,26 @@ def _print_foreground_port_conflict(
|
|||||||
gateway_host: str,
|
gateway_host: str,
|
||||||
gateway_port: int,
|
gateway_port: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
console.print(
|
gateway_running = _gateway_health_ready(gateway_host, gateway_port)
|
||||||
"[red]Error: nanobot cannot start because one of its local ports is already in use.[/red]"
|
if gateway_running:
|
||||||
)
|
console.print(
|
||||||
console.print(f" WebUI: [cyan]{webui_url}[/cyan]")
|
"[yellow]A nanobot gateway is already running for this local instance.[/yellow]"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
console.print(
|
||||||
|
"[red]Error: nanobot cannot start because one of its local ports "
|
||||||
|
"is already in use.[/red]"
|
||||||
|
)
|
||||||
|
console.print(f" WebUI: [cyan]{_webui_display_url(webui_url)}[/cyan]")
|
||||||
console.print(
|
console.print(
|
||||||
f" Gateway health: "
|
f" Gateway health: "
|
||||||
f"[cyan]http://{_host_for_local_browser(gateway_host)}:{gateway_port}/health[/cyan]"
|
f"[cyan]http://{_host_for_local_browser(gateway_host)}:{gateway_port}/health[/cyan]"
|
||||||
)
|
)
|
||||||
console.print()
|
console.print()
|
||||||
console.print("If this is an existing nanobot instance, use it or stop it first:")
|
if gateway_running:
|
||||||
|
console.print("Use the existing instance, or stop it first:")
|
||||||
|
else:
|
||||||
|
console.print("If this is an existing nanobot instance, use it or stop it first:")
|
||||||
console.print(" [cyan]nanobot gateway status[/cyan]")
|
console.print(" [cyan]nanobot gateway status[/cyan]")
|
||||||
console.print(" [cyan]nanobot gateway stop[/cyan]")
|
console.print(" [cyan]nanobot gateway stop[/cyan]")
|
||||||
console.print(
|
console.print(
|
||||||
|
|||||||
@@ -2504,6 +2504,10 @@ def test_webui_foreground_attaches_to_existing_managed_gateway(monkeypatch, tmp_
|
|||||||
_patch_webui_provider_ready(monkeypatch)
|
_patch_webui_provider_ready(monkeypatch)
|
||||||
monkeypatch.setattr("nanobot.cli.webui.sync_workspace_templates", lambda _path: None)
|
monkeypatch.setattr("nanobot.cli.webui.sync_workspace_templates", lambda _path: None)
|
||||||
monkeypatch.setattr("nanobot.cli.webui._gateway_health_ready", lambda *_args, **_kwargs: True)
|
monkeypatch.setattr("nanobot.cli.webui._gateway_health_ready", lambda *_args, **_kwargs: True)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"nanobot.cli.webui_support._gateway_health_ready",
|
||||||
|
lambda *_args, **_kwargs: True,
|
||||||
|
)
|
||||||
monkeypatch.setattr("nanobot.cli.webui._webui_endpoint_reachable", lambda *_args, **_kwargs: True)
|
monkeypatch.setattr("nanobot.cli.webui._webui_endpoint_reachable", lambda *_args, **_kwargs: True)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"nanobot.cli.webui._open_webui_browser",
|
"nanobot.cli.webui._open_webui_browser",
|
||||||
@@ -2628,6 +2632,10 @@ def test_webui_foreground_refuses_occupied_webui_port(monkeypatch, tmp_path: Pat
|
|||||||
_patch_webui_provider_ready(monkeypatch)
|
_patch_webui_provider_ready(monkeypatch)
|
||||||
monkeypatch.setattr("nanobot.cli.webui.sync_workspace_templates", lambda _path: None)
|
monkeypatch.setattr("nanobot.cli.webui.sync_workspace_templates", lambda _path: None)
|
||||||
monkeypatch.setattr("nanobot.cli.webui._gateway_health_ready", lambda *_args, **_kwargs: False)
|
monkeypatch.setattr("nanobot.cli.webui._gateway_health_ready", lambda *_args, **_kwargs: False)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"nanobot.cli.webui_support._gateway_health_ready",
|
||||||
|
lambda *_args, **_kwargs: False,
|
||||||
|
)
|
||||||
monkeypatch.setattr("nanobot.cli.webui._webui_endpoint_reachable", lambda *_args, **_kwargs: True)
|
monkeypatch.setattr("nanobot.cli.webui._webui_endpoint_reachable", lambda *_args, **_kwargs: True)
|
||||||
monkeypatch.setattr("nanobot.cli.webui._tcp_endpoint_reachable", lambda *_args, **_kwargs: False)
|
monkeypatch.setattr("nanobot.cli.webui._tcp_endpoint_reachable", lambda *_args, **_kwargs: False)
|
||||||
result = runner.invoke(app, ["webui", "--config", str(config_file), "--yes"])
|
result = runner.invoke(app, ["webui", "--config", str(config_file), "--yes"])
|
||||||
@@ -2638,6 +2646,28 @@ def test_webui_foreground_refuses_occupied_webui_port(monkeypatch, tmp_path: Pat
|
|||||||
assert "--gateway-port" in result.stdout
|
assert "--gateway-port" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_webui_foreground_reports_an_existing_gateway_without_leaking_secret(
|
||||||
|
monkeypatch,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
config_file = tmp_path / "config.json"
|
||||||
|
config_file.write_text(
|
||||||
|
'{"channels":{"websocket":{"tokenIssueSecret":"do-not-leak"}}}',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
_patch_webui_provider_ready(monkeypatch)
|
||||||
|
monkeypatch.setattr("nanobot.cli.webui.sync_workspace_templates", lambda _path: None)
|
||||||
|
monkeypatch.setattr("nanobot.cli.webui._gateway_health_ready", lambda *_args, **_kwargs: True)
|
||||||
|
monkeypatch.setattr("nanobot.cli.webui._webui_endpoint_reachable", lambda *_args, **_kwargs: False)
|
||||||
|
|
||||||
|
result = runner.invoke(app, ["webui", "--config", str(config_file), "--yes"])
|
||||||
|
|
||||||
|
assert result.exit_code == 1
|
||||||
|
assert "gateway is already running for this local instance" in result.stdout
|
||||||
|
assert "bootstrapSecret=<redacted>" in result.stdout
|
||||||
|
assert "do-not-leak" not in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def _patch_serve_runtime(monkeypatch, config: Config, seen: dict[str, object]) -> None:
|
def _patch_serve_runtime(monkeypatch, config: Config, seen: dict[str, object]) -> None:
|
||||||
pytest.importorskip("aiohttp")
|
pytest.importorskip("aiohttp")
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import typer
|
|||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from typer.testing import CliRunner
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
from nanobot.cli.gateway import create_gateway_app
|
from nanobot.cli.gateway import _resolved_config_selector, create_gateway_app
|
||||||
from nanobot.config.schema import Config
|
from nanobot.config.schema import Config
|
||||||
from nanobot.gateway import GatewayRuntimePaths, GatewayStartOptions, GatewayStatus, RuntimeResult
|
from nanobot.gateway import GatewayRuntimePaths, GatewayStartOptions, GatewayStatus, RuntimeResult
|
||||||
from nanobot.gateway.service import GatewayServiceOptions, GatewayServiceResult
|
from nanobot.gateway.service import GatewayServiceOptions, GatewayServiceResult
|
||||||
@@ -14,6 +14,17 @@ from nanobot.gateway.service import GatewayServiceOptions, GatewayServiceResult
|
|||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_config_has_the_same_gateway_identity_when_explicit(
|
||||||
|
monkeypatch,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
config_path = tmp_path / "config.json"
|
||||||
|
monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path)
|
||||||
|
|
||||||
|
assert _resolved_config_selector(None) == config_path
|
||||||
|
assert _resolved_config_selector(str(config_path)) == config_path
|
||||||
|
|
||||||
|
|
||||||
class FakeRuntime:
|
class FakeRuntime:
|
||||||
def __init__(self, tmp_path: Path):
|
def __init__(self, tmp_path: Path):
|
||||||
self.paths = GatewayRuntimePaths.for_instance(data_dir=tmp_path)
|
self.paths = GatewayRuntimePaths.for_instance(data_dir=tmp_path)
|
||||||
|
|||||||
Reference in New Issue
Block a user