Changed TUI implementation to support new docker compose override

This commit is contained in:
Lucas Oliveira 2025-11-25 18:10:02 -03:00
parent 608fc798b0
commit 28cb9fc26b
3 changed files with 91 additions and 55 deletions

View file

@ -485,7 +485,7 @@ def copy_compose_files(*, force: bool = False) -> None:
logger.debug(f"Could not access compose assets: {e}")
return
for filename in ("docker-compose.yml", "docker-compose-cpu.yml"):
for filename in ("docker-compose.yml", "docker-compose.gpu.yml"):
destination = Path(filename)
if destination.exists() and not force:
continue

View file

@ -56,15 +56,15 @@ class ContainerManager:
self.platform_detector = PlatformDetector()
self.runtime_info = self.platform_detector.detect_runtime()
self.compose_file = compose_file or self._find_compose_file("docker-compose.yml")
self.cpu_compose_file = self._find_compose_file("docker-compose-cpu.yml")
self.gpu_compose_file = self._find_compose_file("docker-compose.gpu.yml")
self.services_cache: Dict[str, ServiceInfo] = {}
self.last_status_update = 0
# Auto-select CPU compose if no GPU available
# Auto-select GPU override if GPU is available
try:
has_gpu, _ = detect_gpu_devices()
self.use_cpu_compose = not has_gpu
self.use_gpu_compose = has_gpu
except Exception:
self.use_cpu_compose = True
self.use_gpu_compose = False
# Expected services based on compose files
self.expected_services = [
@ -143,9 +143,15 @@ class ContainerManager:
return False, "", "No container runtime available"
if cpu_mode is None:
cpu_mode = self.use_cpu_compose
compose_file = self.cpu_compose_file if cpu_mode else self.compose_file
cmd = self.runtime_info.compose_command + ["-f", str(compose_file)] + args
use_gpu = self.use_gpu_compose
else:
use_gpu = not cpu_mode
# Build compose command with override pattern
cmd = self.runtime_info.compose_command + ["-f", str(self.compose_file)]
if use_gpu and self.gpu_compose_file.exists():
cmd.extend(["-f", str(self.gpu_compose_file)])
cmd.extend(args)
try:
process = await asyncio.create_subprocess_exec(
@ -179,9 +185,15 @@ class ContainerManager:
return
if cpu_mode is None:
cpu_mode = self.use_cpu_compose
compose_file = self.cpu_compose_file if cpu_mode else self.compose_file
cmd = self.runtime_info.compose_command + ["-f", str(compose_file)] + args
use_gpu = self.use_gpu_compose
else:
use_gpu = not cpu_mode
# Build compose command with override pattern
cmd = self.runtime_info.compose_command + ["-f", str(self.compose_file)]
if use_gpu and self.gpu_compose_file.exists():
cmd.extend(["-f", str(self.gpu_compose_file)])
cmd.extend(args)
try:
process = await asyncio.create_subprocess_exec(
@ -242,9 +254,15 @@ class ContainerManager:
return
if cpu_mode is None:
cpu_mode = self.use_cpu_compose
compose_file = self.cpu_compose_file if cpu_mode else self.compose_file
cmd = self.runtime_info.compose_command + ["-f", str(compose_file)] + args
use_gpu = self.use_gpu_compose
else:
use_gpu = not cpu_mode
# Build compose command with override pattern
cmd = self.runtime_info.compose_command + ["-f", str(self.compose_file)]
if use_gpu and self.gpu_compose_file.exists():
cmd.extend(["-f", str(self.gpu_compose_file)])
cmd.extend(args)
try:
process = await asyncio.create_subprocess_exec(
@ -551,44 +569,61 @@ class ContainerManager:
"""Get resolved image names from compose files using docker/podman compose, with robust fallbacks."""
images: set[str] = set()
compose_files = [self.compose_file, self.cpu_compose_file]
for compose_file in compose_files:
# Try both GPU and CPU modes to get all images
for use_gpu in [True, False]:
try:
if not compose_file or not compose_file.exists():
continue
# Build compose command with override pattern
cmd = self.runtime_info.compose_command + ["-f", str(self.compose_file)]
if use_gpu and self.gpu_compose_file.exists():
cmd.extend(["-f", str(self.gpu_compose_file)])
cmd.extend(["config", "--format", "json"])
cpu_mode = (compose_file == self.cpu_compose_file)
# Try JSON format first
success, stdout, _ = await self._run_compose_command(
["config", "--format", "json"],
cpu_mode=cpu_mode
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=Path.cwd(),
)
stdout, stderr = await process.communicate()
stdout_text = stdout.decode() if stdout else ""
if success and stdout.strip():
from_cfg = self._extract_images_from_compose_config(stdout, tried_json=True)
if process.returncode == 0 and stdout_text.strip():
from_cfg = self._extract_images_from_compose_config(stdout_text, tried_json=True)
if from_cfg:
images.update(from_cfg)
continue # this compose file succeeded; move to next file
continue
# Fallback to YAML output (for older compose versions)
success, stdout, _ = await self._run_compose_command(
["config"],
cpu_mode=cpu_mode
)
cmd = self.runtime_info.compose_command + ["-f", str(self.compose_file)]
if use_gpu and self.gpu_compose_file.exists():
cmd.extend(["-f", str(self.gpu_compose_file)])
cmd.append("config")
if success and stdout.strip():
from_cfg = self._extract_images_from_compose_config(stdout, tried_json=False)
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=Path.cwd(),
)
stdout, stderr = await process.communicate()
stdout_text = stdout.decode() if stdout else ""
if process.returncode == 0 and stdout_text.strip():
from_cfg = self._extract_images_from_compose_config(stdout_text, tried_json=False)
if from_cfg:
images.update(from_cfg)
continue
except Exception:
# Keep behavior resilient—just continue to next file
# Keep behavior resilient—just continue to next mode
continue
# Fallback: manual parsing if compose config didn't work
if not images:
compose_files = [self.compose_file]
if self.gpu_compose_file.exists():
compose_files.append(self.gpu_compose_file)
for compose in compose_files:
try:
if not compose.exists():
@ -638,8 +673,11 @@ class ContainerManager:
yield False, "No container runtime available"
return
# Diagnostic info about compose files
compose_file = self.cpu_compose_file if (cpu_mode if cpu_mode is not None else self.use_cpu_compose) else self.compose_file
# Determine GPU mode
if cpu_mode is None:
use_gpu = self.use_gpu_compose
else:
use_gpu = not cpu_mode
# Show the search process for debugging
if hasattr(self, '_compose_search_log'):
@ -650,9 +688,12 @@ class ContainerManager:
# Show runtime detection info
runtime_cmd_str = " ".join(self.runtime_info.compose_command)
yield False, f"Using compose command: {runtime_cmd_str}", False
yield False, f"Final compose file: {compose_file.absolute()}", False
if not compose_file.exists():
yield False, f"ERROR: Compose file not found at {compose_file.absolute()}", False
compose_files_str = str(self.compose_file.absolute())
if use_gpu and self.gpu_compose_file.exists():
compose_files_str += f" + {self.gpu_compose_file.absolute()}"
yield False, f"Compose files: {compose_files_str}", False
if not self.compose_file.exists():
yield False, f"ERROR: Base compose file not found at {self.compose_file.absolute()}", False
return
yield False, "Starting OpenRAG services...", False
@ -786,16 +827,11 @@ class ContainerManager:
yield "No container runtime available"
return
compose_file = (
self.cpu_compose_file if self.use_cpu_compose else self.compose_file
)
cmd = self.runtime_info.compose_command + [
"-f",
str(compose_file),
"logs",
"-f",
service_name,
]
# Build compose command with override pattern
cmd = self.runtime_info.compose_command + ["-f", str(self.compose_file)]
if self.use_gpu_compose and self.gpu_compose_file.exists():
cmd.extend(["-f", str(self.gpu_compose_file)])
cmd.extend(["logs", "-f", service_name])
try:
process = await asyncio.create_subprocess_exec(

View file

@ -581,22 +581,22 @@ class MonitorScreen(Screen):
def _update_mode_row(self) -> None:
"""Update the mode indicator and toggle button label."""
try:
use_cpu = getattr(self.container_manager, "use_cpu_compose", True)
use_gpu = getattr(self.container_manager, "use_gpu_compose", False)
indicator = self.query_one("#mode-indicator", Static)
mode_text = "Mode: CPU (no GPU detected)" if use_cpu else "Mode: GPU"
mode_text = "Mode: GPU" if use_gpu else "Mode: CPU (no GPU detected)"
indicator.update(mode_text)
toggle_btn = self.query_one("#toggle-mode-btn", Button)
toggle_btn.label = "Switch to GPU Mode" if use_cpu else "Switch to CPU Mode"
toggle_btn.label = "Switch to CPU Mode" if use_gpu else "Switch to GPU Mode"
except Exception:
pass
def action_toggle_mode(self) -> None:
"""Toggle between CPU/GPU compose files and refresh view."""
try:
current = getattr(self.container_manager, "use_cpu_compose", True)
self.container_manager.use_cpu_compose = not current
current = getattr(self.container_manager, "use_gpu_compose", False)
self.container_manager.use_gpu_compose = not current
self.notify(
"Switched to GPU compose" if not current else "Switched to CPU compose",
"Switched to GPU mode" if not current else "Switched to CPU mode",
severity="information",
)
self._update_mode_row()