mirror of
https://github.com/usestrix/strix.git
synced 2026-08-16 17:27:26 +02:00
218 lines
7.8 KiB
Python
218 lines
7.8 KiB
Python
"""Startup environment validation and Docker image management."""
|
|
|
|
import logging
|
|
import shutil
|
|
import sys
|
|
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.text import Text
|
|
|
|
from strix.config import codex, load_settings
|
|
from strix.interface.utils import (
|
|
check_docker_connection,
|
|
image_exists,
|
|
process_pull_line,
|
|
)
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def validate_environment() -> None:
|
|
logger.info("Validating environment")
|
|
console = Console()
|
|
missing_required_vars = []
|
|
missing_optional_vars = []
|
|
|
|
settings = load_settings()
|
|
|
|
if codex.subscription_model(settings.llm.model):
|
|
if not codex.is_authenticated():
|
|
console.print(
|
|
f"[red]STRIX_LLM={settings.llm.model} uses your ChatGPT subscription, "
|
|
"but you're not signed in.[/] Run [cyan]strix auth login chatgpt[/] first."
|
|
)
|
|
sys.exit(1)
|
|
logger.info("Environment OK (ChatGPT subscription)")
|
|
return
|
|
|
|
if not settings.llm.model:
|
|
missing_required_vars.append("STRIX_LLM")
|
|
|
|
if not settings.llm.api_key:
|
|
missing_optional_vars.append("LLM_API_KEY")
|
|
|
|
if not settings.llm.api_base:
|
|
missing_optional_vars.append("LLM_API_BASE")
|
|
|
|
if not settings.integrations.perplexity_api_key:
|
|
missing_optional_vars.append("PERPLEXITY_API_KEY")
|
|
|
|
if missing_required_vars:
|
|
error_text = Text()
|
|
error_text.append("MISSING REQUIRED ENVIRONMENT VARIABLES", style="bold red")
|
|
error_text.append("\n\n", style="white")
|
|
|
|
for var in missing_required_vars:
|
|
error_text.append(f"• {var}", style="bold yellow")
|
|
error_text.append(" is not set\n", style="white")
|
|
|
|
if missing_optional_vars:
|
|
error_text.append("\nOptional environment variables:\n", style="dim white")
|
|
for var in missing_optional_vars:
|
|
error_text.append(f"• {var}", style="dim yellow")
|
|
error_text.append(" is not set\n", style="dim white")
|
|
|
|
error_text.append("\nRequired environment variables:\n", style="white")
|
|
for var in missing_required_vars:
|
|
if var == "STRIX_LLM":
|
|
error_text.append("• ", style="white")
|
|
error_text.append("STRIX_LLM", style="bold cyan")
|
|
error_text.append(
|
|
" - Model name to use (e.g., 'openai/gpt-5.4' or "
|
|
"'anthropic/claude-opus-4-7')\n",
|
|
style="white",
|
|
)
|
|
|
|
if missing_optional_vars:
|
|
error_text.append("\nOptional environment variables:\n", style="white")
|
|
for var in missing_optional_vars:
|
|
if var == "LLM_API_BASE":
|
|
error_text.append("• ", style="white")
|
|
error_text.append("LLM_API_BASE", style="bold cyan")
|
|
error_text.append(
|
|
" - Custom API base URL if using local models (e.g., Ollama, LMStudio)\n",
|
|
style="white",
|
|
)
|
|
elif var == "PERPLEXITY_API_KEY":
|
|
error_text.append("• ", style="white")
|
|
error_text.append("PERPLEXITY_API_KEY", style="bold cyan")
|
|
error_text.append(
|
|
" - API key for Perplexity AI web search (enables real-time research)\n",
|
|
style="white",
|
|
)
|
|
elif var == "STRIX_REASONING_EFFORT":
|
|
error_text.append("• ", style="white")
|
|
error_text.append("STRIX_REASONING_EFFORT", style="bold cyan")
|
|
error_text.append(
|
|
" - Reasoning effort level: none, minimal, low, medium, high, xhigh, "
|
|
"max (default: high)\n",
|
|
style="white",
|
|
)
|
|
|
|
error_text.append("\nExample setup:\n", style="white")
|
|
error_text.append("export STRIX_LLM='openai/gpt-5.4'\n", style="dim white")
|
|
|
|
if missing_optional_vars:
|
|
for var in missing_optional_vars:
|
|
if var == "LLM_API_BASE":
|
|
error_text.append(
|
|
"export LLM_API_BASE='http://localhost:11434' "
|
|
"# needed for local models only\n",
|
|
style="dim white",
|
|
)
|
|
elif var == "PERPLEXITY_API_KEY":
|
|
error_text.append(
|
|
"export PERPLEXITY_API_KEY='your-perplexity-key-here'\n", style="dim white"
|
|
)
|
|
elif var == "STRIX_REASONING_EFFORT":
|
|
error_text.append(
|
|
"export STRIX_REASONING_EFFORT='high'\n",
|
|
style="dim white",
|
|
)
|
|
|
|
panel = Panel(
|
|
error_text,
|
|
title="[bold white]STRIX",
|
|
title_align="left",
|
|
border_style="red",
|
|
padding=(1, 2),
|
|
)
|
|
|
|
logger.debug("Missing required env vars: %s", missing_required_vars)
|
|
console.print("\n")
|
|
console.print(panel)
|
|
console.print()
|
|
sys.exit(1)
|
|
logger.info(
|
|
"Environment OK (optional missing: %s)",
|
|
missing_optional_vars or "none",
|
|
)
|
|
|
|
|
|
def check_docker_installed() -> None:
|
|
if shutil.which("docker") is None:
|
|
logger.debug("Docker CLI not found in PATH")
|
|
console = Console()
|
|
error_text = Text()
|
|
error_text.append("DOCKER NOT INSTALLED", style="bold red")
|
|
error_text.append("\n\n", style="white")
|
|
error_text.append("The 'docker' CLI was not found in your PATH.\n", style="white")
|
|
error_text.append(
|
|
"Please install Docker and ensure the 'docker' command is available.\n\n", style="white"
|
|
)
|
|
|
|
panel = Panel(
|
|
error_text,
|
|
title="[bold white]STRIX",
|
|
title_align="left",
|
|
border_style="red",
|
|
padding=(1, 2),
|
|
)
|
|
console.print("\n", panel, "\n")
|
|
sys.exit(1)
|
|
logger.debug("Docker CLI present")
|
|
|
|
|
|
def pull_docker_image() -> None:
|
|
from docker.errors import DockerException
|
|
|
|
console = Console()
|
|
client = check_docker_connection()
|
|
|
|
image = load_settings().runtime.image
|
|
|
|
if image_exists(client, image):
|
|
logger.debug("Docker image already present locally: %s", image)
|
|
return
|
|
|
|
logger.info("Pulling docker image: %s", image)
|
|
console.print()
|
|
console.print(f"[dim]Pulling image[/] {image}")
|
|
console.print("[dim yellow]This only happens on first run and may take a few minutes...[/]")
|
|
console.print()
|
|
|
|
with console.status("[bold cyan]Downloading image layers...", spinner="dots") as status:
|
|
try:
|
|
layers_info: dict[str, str] = {}
|
|
last_update = ""
|
|
|
|
for line in client.api.pull(image, stream=True, decode=True):
|
|
last_update = process_pull_line(line, layers_info, status, last_update)
|
|
|
|
except DockerException as e:
|
|
logger.debug("Failed to pull docker image %s", image, exc_info=True)
|
|
console.print()
|
|
error_text = Text()
|
|
error_text.append("FAILED TO PULL IMAGE", style="bold red")
|
|
error_text.append("\n\n", style="white")
|
|
error_text.append(f"Could not download: {image}\n", style="white")
|
|
error_text.append(str(e), style="dim red")
|
|
|
|
panel = Panel(
|
|
error_text,
|
|
title="[bold white]STRIX",
|
|
title_align="left",
|
|
border_style="red",
|
|
padding=(1, 2),
|
|
)
|
|
console.print(panel, "\n")
|
|
sys.exit(1)
|
|
|
|
logger.info("Docker image %s ready", image)
|
|
success_text = Text()
|
|
success_text.append("Docker image ready", style="#22c55e")
|
|
console.print(success_text)
|
|
console.print()
|