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}") logger.debug(f"Could not access compose assets: {e}")
return return
for filename in ("docker-compose.yml", "docker-compose-cpu.yml"): for filename in ("docker-compose.yml", "docker-compose.gpu.yml"):
destination = Path(filename) destination = Path(filename)
if destination.exists() and not force: if destination.exists() and not force:
continue continue

View file

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

View file

@ -581,22 +581,22 @@ class MonitorScreen(Screen):
def _update_mode_row(self) -> None: def _update_mode_row(self) -> None:
"""Update the mode indicator and toggle button label.""" """Update the mode indicator and toggle button label."""
try: 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) 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) indicator.update(mode_text)
toggle_btn = self.query_one("#toggle-mode-btn", Button) 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: except Exception:
pass pass
def action_toggle_mode(self) -> None: def action_toggle_mode(self) -> None:
"""Toggle between CPU/GPU compose files and refresh view.""" """Toggle between CPU/GPU compose files and refresh view."""
try: try:
current = getattr(self.container_manager, "use_cpu_compose", True) current = getattr(self.container_manager, "use_gpu_compose", False)
self.container_manager.use_cpu_compose = not current self.container_manager.use_gpu_compose = not current
self.notify( 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", severity="information",
) )
self._update_mode_row() self._update_mode_row()