mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-16 13:08:02 +00:00
Merge remote-tracking branch 'origin/dev'
# Conflicts: # routes/document_routes.py
This commit is contained in:
+211
-54
@@ -32,6 +32,13 @@ from core.platform_compat import (
|
||||
which_tool,
|
||||
)
|
||||
from routes.shell_routes import TMUX_LOG_DIR
|
||||
from src.host_docker_access import (
|
||||
HOST_DOCKER_ACCESS_HINT,
|
||||
HOST_DOCKER_SOCKET_PATH,
|
||||
host_docker_access_enabled,
|
||||
local_docker_available,
|
||||
running_in_container,
|
||||
)
|
||||
from routes.cookbook_output import (
|
||||
error_aware_output_tail, classify_dead_download,
|
||||
HF_CACHE_COMPLETE_PROBE, HF_CACHE_INCOMPLETE_PROBE,
|
||||
@@ -64,6 +71,182 @@ _HF_TOKEN_STATUS_SNIPPET = (
|
||||
'fi'
|
||||
)
|
||||
|
||||
_OLLAMA_SIDECAR_CONTAINERS = {"ollama-test", "ollama-rocm"}
|
||||
_UNSAFE_DOCKER_EXEC_CHARS = frozenset(";&|<>$`\r\n")
|
||||
_SAFE_OLLAMA_MODEL_TOKEN_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/-]*$")
|
||||
_SAFE_OLLAMA_FILE_TOKEN_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
||||
|
||||
|
||||
def _is_generated_ollama_docker_exec_cmd(cmd: str | None) -> bool:
|
||||
"""Match only the fixed Docker exec shapes generated by Cookbook."""
|
||||
if not cmd or any(char in cmd for char in _UNSAFE_DOCKER_EXEC_CHARS):
|
||||
return False
|
||||
try:
|
||||
parts = shlex.split(cmd)
|
||||
except ValueError:
|
||||
return False
|
||||
if len(parts) < 4 or parts[:2] != ["docker", "exec"]:
|
||||
return False
|
||||
container, executable = parts[2:4]
|
||||
if container not in _OLLAMA_SIDECAR_CONTAINERS:
|
||||
return False
|
||||
if container == "ollama-rocm" and executable == "ollama":
|
||||
return (
|
||||
len(parts) == 6
|
||||
and parts[4] == "show"
|
||||
and _SAFE_OLLAMA_MODEL_TOKEN_RE.fullmatch(parts[5]) is not None
|
||||
)
|
||||
if container != "ollama-test" or executable != "ollama-import":
|
||||
return False
|
||||
if len(parts) not in {7, 8}:
|
||||
return False
|
||||
model, name, context_size = parts[4:7]
|
||||
return (
|
||||
_SAFE_OLLAMA_MODEL_TOKEN_RE.fullmatch(model) is not None
|
||||
and _SAFE_OLLAMA_FILE_TOKEN_RE.fullmatch(name) is not None
|
||||
and re.fullmatch(r"[0-9]+", context_size) is not None
|
||||
and (
|
||||
len(parts) == 7
|
||||
or _SAFE_OLLAMA_FILE_TOKEN_RE.fullmatch(parts[7]) is not None
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _missing_binary_message(
|
||||
binary: str,
|
||||
target: str,
|
||||
*,
|
||||
local_host_docker_blocked: bool = False,
|
||||
) -> str:
|
||||
if binary == "tmux":
|
||||
return (
|
||||
f"tmux is required for Cookbook background downloads/serves on {target}. "
|
||||
"Install it with your OS package manager, or run Cookbook server setup for that server."
|
||||
)
|
||||
if binary == "docker":
|
||||
if local_host_docker_blocked:
|
||||
return HOST_DOCKER_ACCESS_HINT
|
||||
return (
|
||||
f"Docker is required by this Cookbook launch command on {target}, but the docker CLI was not found. "
|
||||
"Install Docker and make sure this user can run `docker`, then retry."
|
||||
)
|
||||
return f"{binary} is required on {target}, but it was not found."
|
||||
|
||||
|
||||
async def _remote_binary_available(
|
||||
remote: str,
|
||||
ssh_port: str | None,
|
||||
binary: str,
|
||||
*,
|
||||
windows: bool = False,
|
||||
) -> bool:
|
||||
port = ssh_port or ""
|
||||
port_args = ["-p", port] if port and port != "22" else []
|
||||
if windows:
|
||||
check = f'powershell -NoProfile -Command "if (Get-Command {binary} -ErrorAction SilentlyContinue) {{ exit 0 }} else {{ exit 127 }}"'
|
||||
else:
|
||||
check = f"command -v {shlex.quote(binary)} >/dev/null 2>&1"
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"ssh",
|
||||
"-o",
|
||||
"ConnectTimeout=6",
|
||||
"-o",
|
||||
"StrictHostKeyChecking=no",
|
||||
*port_args,
|
||||
remote,
|
||||
check,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
await asyncio.wait_for(proc.communicate(), timeout=10)
|
||||
return proc.returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
async def _binary_available(
|
||||
binary: str,
|
||||
remote: str | None,
|
||||
ssh_port: str | None,
|
||||
*,
|
||||
windows: bool = False,
|
||||
in_container: bool | None = None,
|
||||
environ=None,
|
||||
socket_path: str = HOST_DOCKER_SOCKET_PATH,
|
||||
) -> bool:
|
||||
if remote:
|
||||
return await _remote_binary_available(
|
||||
remote,
|
||||
ssh_port,
|
||||
binary,
|
||||
windows=windows,
|
||||
)
|
||||
cli_available = shutil.which(binary) is not None
|
||||
if binary != "docker":
|
||||
return cli_available
|
||||
return local_docker_available(
|
||||
cli_available=cli_available,
|
||||
in_container=in_container,
|
||||
environ=environ,
|
||||
socket_path=socket_path,
|
||||
)
|
||||
|
||||
|
||||
|
||||
def _local_ollama_docker_fallback_available(
|
||||
*,
|
||||
in_container: bool | None = None,
|
||||
environ: dict[str, str] | None = None,
|
||||
socket_path: str = HOST_DOCKER_SOCKET_PATH,
|
||||
) -> bool:
|
||||
return local_docker_available(
|
||||
cli_available=shutil.which("docker") is not None,
|
||||
in_container=in_container,
|
||||
environ=environ,
|
||||
socket_path=socket_path,
|
||||
)
|
||||
|
||||
|
||||
def _local_ollama_docker_access_blocked(
|
||||
*,
|
||||
in_container: bool | None = None,
|
||||
environ: dict[str, str] | None = None,
|
||||
socket_path: str = HOST_DOCKER_SOCKET_PATH,
|
||||
) -> bool:
|
||||
containerized = running_in_container() if in_container is None else in_container
|
||||
if not containerized or shutil.which("docker") is None:
|
||||
return False
|
||||
return not _local_ollama_docker_fallback_available(
|
||||
in_container=containerized,
|
||||
environ=environ,
|
||||
socket_path=socket_path,
|
||||
)
|
||||
|
||||
|
||||
def _append_local_ollama_download_command_lines(
|
||||
lines: list[str],
|
||||
ollama_cmd: str,
|
||||
*,
|
||||
docker_fallback_available: bool,
|
||||
docker_fallback_blocked: bool,
|
||||
) -> None:
|
||||
lines.append('if command -v ollama >/dev/null 2>&1; then')
|
||||
lines.append(f' ODYSSEUS_OLLAMA_PULL_CMD={shlex.quote(ollama_cmd)}')
|
||||
if docker_fallback_available:
|
||||
lines.append('elif command -v docker >/dev/null 2>&1; then')
|
||||
lines.append(" ODYSSEUS_OLLAMA_CONTAINER=\"$(docker ps --format '{{.Names}}' 2>/dev/null | grep -E '^(ollama-rocm|ollama-test)$' | head -1)\"")
|
||||
lines.append(' if [ -n "$ODYSSEUS_OLLAMA_CONTAINER" ]; then')
|
||||
lines.append(f' ODYSSEUS_OLLAMA_PULL_CMD={shlex.quote("docker exec ${ODYSSEUS_OLLAMA_CONTAINER} " + ollama_cmd)}')
|
||||
lines.append(' fi')
|
||||
elif docker_fallback_blocked:
|
||||
hint = shlex.quote("ERROR: " + HOST_DOCKER_ACCESS_HINT)
|
||||
lines.append('else')
|
||||
lines.append(f" printf '%s\\n' {hint}; exit 127")
|
||||
lines.append('fi')
|
||||
lines.append('if [ -z "$ODYSSEUS_OLLAMA_PULL_CMD" ]; then echo "ERROR: Ollama not found on this server. Install Ollama or start an ollama-rocm/ollama-test container."; exit 127; fi')
|
||||
|
||||
|
||||
def setup_cookbook_routes() -> APIRouter:
|
||||
router = APIRouter(tags=["cookbook"])
|
||||
_cookbook_state_path = Path(COOKBOOK_STATE_FILE)
|
||||
@@ -445,43 +628,6 @@ def setup_cookbook_routes() -> APIRouter:
|
||||
def _needs_binary(cmd: str, binary: str) -> bool:
|
||||
return bool(re.search(rf"(^|[\s;&|()]){re.escape(binary)}($|[\s;&|()])", cmd or ""))
|
||||
|
||||
def _missing_binary_message(binary: str, target: str) -> str:
|
||||
if binary == "tmux":
|
||||
return (
|
||||
f"tmux is required for Cookbook background downloads/serves on {target}. "
|
||||
"Install it with your OS package manager, or run Cookbook server setup for that server."
|
||||
)
|
||||
if binary == "docker":
|
||||
return (
|
||||
f"Docker is required by this Cookbook launch command on {target}, but the docker CLI was not found. "
|
||||
"Install Docker and make sure this user can run `docker`, then retry."
|
||||
)
|
||||
return f"{binary} is required on {target}, but it was not found."
|
||||
|
||||
async def _remote_binary_available(remote: str, ssh_port: str | None, binary: str, *, windows: bool = False) -> bool:
|
||||
_port = ssh_port or ""
|
||||
_pf = ["-p", _port] if _port and _port != "22" else []
|
||||
if windows:
|
||||
check = f"powershell -NoProfile -Command \"if (Get-Command {binary} -ErrorAction SilentlyContinue) {{ exit 0 }} else {{ exit 127 }}\""
|
||||
else:
|
||||
check = f"command -v {shlex.quote(binary)} >/dev/null 2>&1"
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"ssh", "-o", "ConnectTimeout=6", "-o", "StrictHostKeyChecking=no",
|
||||
*_pf, remote, check,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
await asyncio.wait_for(proc.communicate(), timeout=10)
|
||||
return proc.returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def _binary_available(binary: str, remote: str | None, ssh_port: str | None, *, windows: bool = False) -> bool:
|
||||
if remote:
|
||||
return await _remote_binary_available(remote, ssh_port, binary, windows=windows)
|
||||
return shutil.which(binary) is not None
|
||||
|
||||
def _launch_local_detached(session_id: str, bash_lines: list[str]) -> dict:
|
||||
"""Windows-native stand-in for a LOCAL tmux session (tmux doesn't exist
|
||||
on Windows). Mirrors shell_routes._generate_win_detached / bg_jobs.launch:
|
||||
@@ -610,15 +756,12 @@ def setup_cookbook_routes() -> APIRouter:
|
||||
# slower-but-reliable downloader (resumes cleanly from the .incomplete files).
|
||||
# Use `python3 -m pip` not `pip` — macOS has no bare `pip` command.
|
||||
if is_ollama_download:
|
||||
lines.append('if command -v ollama >/dev/null 2>&1; then')
|
||||
lines.append(f' ODYSSEUS_OLLAMA_PULL_CMD={shlex.quote(ollama_cmd)}')
|
||||
lines.append('elif command -v docker >/dev/null 2>&1; then')
|
||||
lines.append(' ODYSSEUS_OLLAMA_CONTAINER="$(docker ps --format \'{{.Names}}\' 2>/dev/null | grep -E \'^(ollama-rocm|ollama-test)$\' | head -1)"')
|
||||
lines.append(' if [ -n "$ODYSSEUS_OLLAMA_CONTAINER" ]; then')
|
||||
lines.append(f' ODYSSEUS_OLLAMA_PULL_CMD={shlex.quote("docker exec ${ODYSSEUS_OLLAMA_CONTAINER} " + ollama_cmd)}')
|
||||
lines.append(' fi')
|
||||
lines.append('fi')
|
||||
lines.append('if [ -z "$ODYSSEUS_OLLAMA_PULL_CMD" ]; then echo "ERROR: Ollama not found on this server. Install Ollama or start an ollama-rocm/ollama-test container."; exit 127; fi')
|
||||
_append_local_ollama_download_command_lines(
|
||||
lines,
|
||||
ollama_cmd,
|
||||
docker_fallback_available=_local_ollama_docker_fallback_available(),
|
||||
docker_fallback_blocked=_local_ollama_docker_access_blocked(),
|
||||
)
|
||||
else:
|
||||
lines.append(f"command -v hf >/dev/null 2>&1 || {_pip_install_fallback_chain('huggingface_hub', upgrade=True)}")
|
||||
if req.disable_hf_transfer:
|
||||
@@ -1384,13 +1527,18 @@ def setup_cookbook_routes() -> APIRouter:
|
||||
req.gpus = _validate_gpus(req.gpus)
|
||||
req.hf_token = req.hf_token or _load_stored_hf_token()
|
||||
_validate_token(req.hf_token)
|
||||
# Normalize away backslash-newline continuations (multi-line pasted
|
||||
# serve commands) so the cleaned single-line command is what gets
|
||||
# written into the runner script and used for engine auto-detection.
|
||||
# `_validate_serve_cmd` returns None for empty input; coerce to "" so the
|
||||
# many downstream `"engine" in req.cmd` membership checks can't hit
|
||||
# `TypeError: argument of type 'NoneType'` (a 500 instead of a clean 400).
|
||||
req.cmd = _validate_serve_cmd(req.cmd) or ""
|
||||
# Cookbook emits two fixed Docker exec forms for its Ollama sidecars.
|
||||
# Keep Docker out of the general allowlist: only these parsed shapes may
|
||||
# proceed to the target-aware Docker availability/opt-in preflight.
|
||||
if _is_generated_ollama_docker_exec_cmd(req.cmd):
|
||||
req.cmd = req.cmd.strip()
|
||||
else:
|
||||
# Normalize away backslash-newline continuations (multi-line pasted
|
||||
# serve commands) so the cleaned single-line command is what gets
|
||||
# written into the runner script and used for engine auto-detection.
|
||||
# `_validate_serve_cmd` returns None for empty input; coerce to "" so
|
||||
# downstream `"engine" in req.cmd` checks cannot raise TypeError.
|
||||
req.cmd = _validate_serve_cmd(req.cmd) or ""
|
||||
req.cmd = _normalize_llama_cpp_python_cache_types(req.cmd) or ""
|
||||
req.cmd = _normalize_minimax_m3_vllm_cmd(req.cmd)
|
||||
req.cmd = _venv_safe_local_pip_install_cmd(
|
||||
@@ -1468,9 +1616,18 @@ def setup_cookbook_routes() -> APIRouter:
|
||||
"session_id": session_id,
|
||||
}
|
||||
if _needs_binary(req.cmd, "docker") and not await _binary_available("docker", remote, req.ssh_port, windows=is_windows):
|
||||
local_host_docker_blocked = (
|
||||
not remote
|
||||
and running_in_container()
|
||||
and not host_docker_access_enabled()
|
||||
)
|
||||
return {
|
||||
"ok": False,
|
||||
"error": _missing_binary_message("docker", remote or "local server"),
|
||||
"error": _missing_binary_message(
|
||||
"docker",
|
||||
remote or "local server",
|
||||
local_host_docker_blocked=local_host_docker_blocked,
|
||||
),
|
||||
"session_id": session_id,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user