Add read-only Debug API support and update documentation

- Introduced a local read-only Debug API accessible at `http://127.0.0.1:17821` for diagnostics and agent interactions.
- Updated CLI commands to include `gpu-rent debug` for launching the Debug API.
- Enhanced documentation to reflect the new Debug API features and usage.
- Modified configuration to include `DEBUG_LOCAL_PORT` for easier customization.
- Added tests to ensure Debug API links are correctly generated in access card outputs.
This commit is contained in:
Leonid Pershin
2026-08-23 06:11:20 +03:00
parent ac0797530d
commit 5847eaab3f
10 changed files with 1558 additions and 11 deletions
+69 -8
View File
@@ -529,9 +529,12 @@ def up(
"""Create/unshelve GPU; SwarmUI и/или LLM; по умолчанию туннель."""
cfg = None
up_ok = False
debug_srv = None
active_log = log
try:
from dataclasses import replace
from gpu_rent.debug_api import start_debug_server, stop_debug_server
from gpu_rent.llm_runtime import (
decide_runtime,
ensure_ollama_manifest_from_example,
@@ -543,13 +546,23 @@ def up(
from gpu_rent.varsfile import upsert_vars
clock_reset()
log("запускаю проверку…")
# Sidecar before doctor so an agent can watch installer hang from second 0.
early_cfg = load_config(require_auth=False)
debug_srv = start_debug_server(early_cfg, log=log, print_urls=True)
if debug_srv is not None:
active_log = debug_srv.wrap_log(log)
debug_srv.set_step("doctor")
active_log("запускаю проверку…")
checks = run_doctor()
log(f"проверка заняла {format_duration(clock_elapsed())}")
active_log(f"проверка заняла {format_duration(clock_elapsed())}")
code = _print_checks(checks, quiet=not verbose)
if code != 0:
raise typer.Exit(1)
cfg = load_config(require_auth=True)
if debug_srv is not None:
debug_srv.hub.cfg = cfg
debug_srv.set_step("up")
try:
runtime = decide_runtime(
@@ -618,7 +631,7 @@ def up(
ollama_preset_menu(include_keep=False),
default="recommended",
ask=_ask,
show=log,
show=active_log,
)
except ValueError as exc:
raise GpuRentError(str(exc)) from exc
@@ -640,7 +653,7 @@ def up(
ollama_preset_menu(include_keep=True),
default="keep",
ask=_ask2,
show=log,
show=active_log,
)
if key not in {"keep", "example", ""}:
write_ollama_models_preset(cfg.ollama_models_manifest, key)
@@ -654,6 +667,8 @@ def up(
)
cfg = replace(cfg, llm_runtime=runtime, enable_swarmui=enable_swarm)
if debug_srv is not None:
debug_srv.hub.cfg = cfg
if enable_swarm:
ok("стек: SwarmUI" + (f" + {runtime}" if runtime != "none" else ""))
else:
@@ -665,6 +680,8 @@ def up(
def ask(msg: str, default: str = "") -> str:
return typer.prompt(msg, default=default)
if debug_srv is not None:
debug_srv.set_step("provisioning")
state = cmd_up(
cfg,
no_spot=no_spot,
@@ -674,13 +691,17 @@ def up(
update=(False if no_update else True if force_update else None),
confirm=confirm,
ask=None if yes else ask,
log=log,
log=active_log,
)
up_ok = True
if no_tunnel:
from gpu_rent.access_card import print_access_card
print_access_card(cfg, tunneled=False, host=state.floating_ip)
console.print(
f"[bold]готово[/bold] (без туннеля). "
f"Доступы: gpu-rent tunnel --open | stop: gpu-rent stop"
f"Доступы: gpu-rent tunnel --open | stop: gpu-rent stop | "
f"debug: gpu-rent debug"
)
if state.floating_ip:
console.print(f"FIP {state.floating_ip}")
@@ -691,11 +712,13 @@ def up(
if not state.floating_ip:
raise GpuRentError("нет floating IP после up — туннель не открыть")
if debug_srv is not None:
debug_srv.set_step("tunnel")
run_tunnel(
cfg,
state.floating_ip,
open_browser=open_browser,
log=log,
log=active_log,
)
except KeyboardInterrupt as exc:
if (
@@ -716,6 +739,11 @@ def up(
):
_stop_after_failed_up(cfg, exc)
_die(exc)
finally:
if debug_srv is not None:
from gpu_rent.debug_api import stop_debug_server
stop_debug_server(debug_srv)
@app.command()
@@ -816,19 +844,52 @@ def tunnel(
open_browser: bool = typer.Option(False, "--open", help="Открыть браузер на 17801"),
) -> None:
"""SSH localhost:17801 -> VM :7801. Ctrl+C / Ctrl+D — stop GPU (диски остаются)."""
debug_srv = None
try:
from gpu_rent.debug_api import start_debug_server, stop_debug_server
cfg = load_config(require_auth=True)
state = load_state()
if not state.floating_ip:
raise GpuRentError("нет floating IP — сначала gpu-rent up")
debug_srv = start_debug_server(cfg, log=log, print_urls=True)
active_log = debug_srv.wrap_log(log) if debug_srv is not None else log
if debug_srv is not None:
debug_srv.set_step("tunnel")
run_tunnel(
cfg,
state.floating_ip,
open_browser=open_browser,
log=log,
log=active_log,
)
except GpuRentError as exc:
_die(exc)
finally:
if debug_srv is not None:
from gpu_rent.debug_api import stop_debug_server
stop_debug_server(debug_srv)
@app.command("debug")
def debug_cmd(
port: Optional[int] = typer.Option(
None,
"--port",
"-p",
help="Локальный порт (по умолчанию DEBUG_LOCAL_PORT / 17821)",
),
) -> None:
"""Локальный read-only Debug API (для агента), пока VM уже есть / после --no-tunnel."""
try:
from gpu_rent.debug_api import run_debug_blocking
cfg = load_config(require_auth=False)
run_debug_blocking(cfg, port=port, log=log)
except RuntimeError as exc:
_die(GpuRentError(str(exc)))
except GpuRentError as exc:
_die(exc)
@app.command()