fix: keep serve preflight errors visible (#398)
This commit is contained in:
@@ -214,6 +214,17 @@ def _validate_serve_cmd(v: str | None) -> str | None:
|
||||
return v
|
||||
|
||||
|
||||
def _append_serve_preflight_exit_lines(runner_lines: list[str], *, keep_shell_open: bool) -> None:
|
||||
"""Append serve-runner lines that surface preflight failures before exit."""
|
||||
runner_lines.append('if [ -n "$ODYSSEUS_PREFLIGHT_EXIT" ]; then')
|
||||
runner_lines.append(' echo ""; echo "=== Process exited with code $ODYSSEUS_PREFLIGHT_EXIT ==="')
|
||||
if keep_shell_open:
|
||||
runner_lines.append(' exec "${SHELL:-/bin/bash}"')
|
||||
else:
|
||||
runner_lines.append(' exit "$ODYSSEUS_PREFLIGHT_EXIT"')
|
||||
runner_lines.append('fi')
|
||||
|
||||
|
||||
class ModelDownloadRequest(BaseModel):
|
||||
repo_id: str
|
||||
include: str | None = None # glob pattern e.g. "*Q4_K_M*"
|
||||
|
||||
@@ -36,7 +36,7 @@ from routes.cookbook_helpers import (
|
||||
_validate_repo_id, _validate_include, _validate_remote_host, _validate_token,
|
||||
_validate_local_dir, _validate_ssh_port, _validate_gpus, _shell_path,
|
||||
_ps_squote, _bash_squote, _validate_serve_cmd, _parse_serve_phase,
|
||||
_safe_env_prefix, _local_tooling_path_export,
|
||||
_safe_env_prefix, _local_tooling_path_export, _append_serve_preflight_exit_lines,
|
||||
ModelDownloadRequest, ServeRequest,
|
||||
)
|
||||
|
||||
@@ -950,6 +950,7 @@ def setup_cookbook_routes() -> APIRouter:
|
||||
# ── Linux/Termux: bash + tmux (existing flow) ──
|
||||
runner_lines = ["#!/bin/bash"]
|
||||
runner_lines.extend(_user_shell_path_bootstrap())
|
||||
runner_lines.append('ODYSSEUS_PREFLIGHT_EXIT=""')
|
||||
# Put Odysseus's own venv bin on PATH (local runs only) so the serve
|
||||
# shell resolves the bundled python3/hf, mirroring the download flow.
|
||||
if not remote:
|
||||
@@ -1044,7 +1045,7 @@ def setup_cookbook_routes() -> APIRouter:
|
||||
# command (the natural serving engine on Apple Silicon / Metal).
|
||||
runner_lines.append('if ! command -v ollama &>/dev/null; then')
|
||||
runner_lines.append(' echo "ERROR: Ollama not found. Install it (macOS: brew install ollama, or https://ollama.com/download), then launch again."')
|
||||
runner_lines.append(' exit 127')
|
||||
runner_lines.append(' ODYSSEUS_PREFLIGHT_EXIT=127')
|
||||
runner_lines.append('fi')
|
||||
runner_lines.append('if ! curl -sf http://localhost:11434/api/tags >/dev/null 2>&1; then')
|
||||
runner_lines.append(' echo "Starting ollama server..."; (ollama serve >/dev/null 2>&1 &)')
|
||||
@@ -1054,7 +1055,7 @@ def setup_cookbook_routes() -> APIRouter:
|
||||
# vLLM is CUDA/ROCm-only and does not run on macOS at all.
|
||||
runner_lines.append('if [ "$(uname -s)" = "Darwin" ]; then')
|
||||
runner_lines.append(' echo "ERROR: vLLM does not run on macOS. Use Ollama or llama.cpp (Metal) instead."')
|
||||
runner_lines.append(' exit 1')
|
||||
runner_lines.append(' ODYSSEUS_PREFLIGHT_EXIT=1')
|
||||
runner_lines.append('fi')
|
||||
# Put ~/.local/bin on PATH first — without a venv, vllm installs
|
||||
# there via --user and the non-login serve shell otherwise can't
|
||||
@@ -1062,21 +1063,25 @@ def setup_cookbook_routes() -> APIRouter:
|
||||
runner_lines.append('export PATH="$HOME/.local/bin:$PATH"')
|
||||
runner_lines.append('if ! command -v vllm &>/dev/null; then')
|
||||
runner_lines.append(' echo "ERROR: vLLM is not installed. Open Cookbook -> Dependencies and install vllm on this server, then launch again."')
|
||||
runner_lines.append(' exit 127')
|
||||
runner_lines.append(' ODYSSEUS_PREFLIGHT_EXIT=127')
|
||||
runner_lines.append('fi')
|
||||
elif "sglang.launch_server" in req.cmd:
|
||||
runner_lines.append('export PATH="$HOME/.local/bin:$PATH"')
|
||||
runner_lines.append('if ! python3 -c "import sglang" 2>/dev/null; then')
|
||||
runner_lines.append(' echo "ERROR: SGLang is not installed. Open Cookbook -> Dependencies and install sglang on this server, then launch again."')
|
||||
runner_lines.append(' exit 127')
|
||||
runner_lines.append(' ODYSSEUS_PREFLIGHT_EXIT=127')
|
||||
runner_lines.append('fi')
|
||||
elif "scripts/diffusion_server.py" in req.cmd or ".diffusion_server.py" in req.cmd:
|
||||
runner_lines.append('export PATH="$HOME/.local/bin:$PATH"')
|
||||
runner_lines.append('if ! python3 -c "import torch, diffusers" 2>/dev/null; then')
|
||||
runner_lines.append(' echo "ERROR: Diffusion serving requires PyTorch + diffusers. Open Cookbook -> Dependencies and install diffusers on this server, then launch again."')
|
||||
runner_lines.append(' exit 127')
|
||||
runner_lines.append(' ODYSSEUS_PREFLIGHT_EXIT=127')
|
||||
runner_lines.append('fi')
|
||||
|
||||
_append_serve_preflight_exit_lines(
|
||||
runner_lines,
|
||||
keep_shell_open=not local_windows,
|
||||
)
|
||||
runner_lines.append(req.cmd)
|
||||
if local_windows:
|
||||
# Detached background process — no interactive shell to keep open.
|
||||
|
||||
@@ -2,6 +2,7 @@ import pytest
|
||||
from fastapi import HTTPException
|
||||
|
||||
from routes.cookbook_helpers import (
|
||||
_append_serve_preflight_exit_lines,
|
||||
_local_tooling_path_export,
|
||||
_safe_env_prefix,
|
||||
_validate_gpus,
|
||||
@@ -58,3 +59,24 @@ def test_local_tooling_path_export_preserves_spaces_and_expands_path():
|
||||
line = _local_tooling_path_export("/Users/John Smith/.venv/bin/python3")
|
||||
assert line == 'export PATH="/Users/John Smith/.venv/bin:$PATH"'
|
||||
assert line.endswith(':$PATH"') # $PATH stays expandable in double quotes
|
||||
|
||||
|
||||
def test_serve_preflight_failure_keeps_tmux_pane_visible():
|
||||
"""Dependency preflight failures should remain visible in tmux output.
|
||||
|
||||
A bare `exit 127` kills the tmux pane before the browser/status poller can
|
||||
capture the helpful error, leaving users with a blank "crashed" card.
|
||||
"""
|
||||
runner_lines = [
|
||||
'ODYSSEUS_PREFLIGHT_EXIT=""',
|
||||
'echo "ERROR: vLLM is not installed. Open Cookbook -> Dependencies and install vllm on this server, then launch again."',
|
||||
'ODYSSEUS_PREFLIGHT_EXIT=127',
|
||||
]
|
||||
_append_serve_preflight_exit_lines(runner_lines, keep_shell_open=True)
|
||||
script = "\n".join(runner_lines)
|
||||
|
||||
assert "ERROR: vLLM is not installed" in script
|
||||
assert 'ODYSSEUS_PREFLIGHT_EXIT=127' in script
|
||||
assert 'echo "=== Process exited with code $ODYSSEUS_PREFLIGHT_EXIT ==="' in script
|
||||
assert 'exec "${SHELL:-/bin/bash}"' in script
|
||||
assert "exit 127" not in script
|
||||
|
||||
Reference in New Issue
Block a user