mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 10:15:27 -04:00
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
|
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):
|
class ModelDownloadRequest(BaseModel):
|
||||||
repo_id: str
|
repo_id: str
|
||||||
include: str | None = None # glob pattern e.g. "*Q4_K_M*"
|
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_repo_id, _validate_include, _validate_remote_host, _validate_token,
|
||||||
_validate_local_dir, _validate_ssh_port, _validate_gpus, _shell_path,
|
_validate_local_dir, _validate_ssh_port, _validate_gpus, _shell_path,
|
||||||
_ps_squote, _bash_squote, _validate_serve_cmd, _parse_serve_phase,
|
_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,
|
ModelDownloadRequest, ServeRequest,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -950,6 +950,7 @@ def setup_cookbook_routes() -> APIRouter:
|
|||||||
# ── Linux/Termux: bash + tmux (existing flow) ──
|
# ── Linux/Termux: bash + tmux (existing flow) ──
|
||||||
runner_lines = ["#!/bin/bash"]
|
runner_lines = ["#!/bin/bash"]
|
||||||
runner_lines.extend(_user_shell_path_bootstrap())
|
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
|
# Put Odysseus's own venv bin on PATH (local runs only) so the serve
|
||||||
# shell resolves the bundled python3/hf, mirroring the download flow.
|
# shell resolves the bundled python3/hf, mirroring the download flow.
|
||||||
if not remote:
|
if not remote:
|
||||||
@@ -1044,7 +1045,7 @@ def setup_cookbook_routes() -> APIRouter:
|
|||||||
# command (the natural serving engine on Apple Silicon / Metal).
|
# command (the natural serving engine on Apple Silicon / Metal).
|
||||||
runner_lines.append('if ! command -v ollama &>/dev/null; then')
|
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(' 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('fi')
|
||||||
runner_lines.append('if ! curl -sf http://localhost:11434/api/tags >/dev/null 2>&1; then')
|
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 &)')
|
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.
|
# vLLM is CUDA/ROCm-only and does not run on macOS at all.
|
||||||
runner_lines.append('if [ "$(uname -s)" = "Darwin" ]; then')
|
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(' 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')
|
runner_lines.append('fi')
|
||||||
# Put ~/.local/bin on PATH first — without a venv, vllm installs
|
# Put ~/.local/bin on PATH first — without a venv, vllm installs
|
||||||
# there via --user and the non-login serve shell otherwise can't
|
# 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('export PATH="$HOME/.local/bin:$PATH"')
|
||||||
runner_lines.append('if ! command -v vllm &>/dev/null; then')
|
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(' 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')
|
runner_lines.append('fi')
|
||||||
elif "sglang.launch_server" in req.cmd:
|
elif "sglang.launch_server" in req.cmd:
|
||||||
runner_lines.append('export PATH="$HOME/.local/bin:$PATH"')
|
runner_lines.append('export PATH="$HOME/.local/bin:$PATH"')
|
||||||
runner_lines.append('if ! python3 -c "import sglang" 2>/dev/null; then')
|
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(' 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')
|
runner_lines.append('fi')
|
||||||
elif "scripts/diffusion_server.py" in req.cmd or ".diffusion_server.py" in req.cmd:
|
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('export PATH="$HOME/.local/bin:$PATH"')
|
||||||
runner_lines.append('if ! python3 -c "import torch, diffusers" 2>/dev/null; then')
|
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(' 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')
|
runner_lines.append('fi')
|
||||||
|
|
||||||
|
_append_serve_preflight_exit_lines(
|
||||||
|
runner_lines,
|
||||||
|
keep_shell_open=not local_windows,
|
||||||
|
)
|
||||||
runner_lines.append(req.cmd)
|
runner_lines.append(req.cmd)
|
||||||
if local_windows:
|
if local_windows:
|
||||||
# Detached background process — no interactive shell to keep open.
|
# Detached background process — no interactive shell to keep open.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import pytest
|
|||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|
||||||
from routes.cookbook_helpers import (
|
from routes.cookbook_helpers import (
|
||||||
|
_append_serve_preflight_exit_lines,
|
||||||
_local_tooling_path_export,
|
_local_tooling_path_export,
|
||||||
_safe_env_prefix,
|
_safe_env_prefix,
|
||||||
_validate_gpus,
|
_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")
|
line = _local_tooling_path_export("/Users/John Smith/.venv/bin/python3")
|
||||||
assert line == 'export PATH="/Users/John Smith/.venv/bin:$PATH"'
|
assert line == 'export PATH="/Users/John Smith/.venv/bin:$PATH"'
|
||||||
assert line.endswith(':$PATH"') # $PATH stays expandable in double quotes
|
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