fix UI client

This commit is contained in:
2026-06-08 09:27:55 +07:00
parent 08b94337ad
commit f9bec78c82
4 changed files with 313 additions and 149 deletions

View File

@@ -1,14 +1,40 @@
from __future__ import annotations
import os
import subprocess
from collections.abc import Mapping
from app.config import settings
from app.storage.repository import Repository
def _tail_output(label: str, output: str, line_limit: int = 6) -> str:
lines = [line.strip() for line in output.splitlines() if line.strip()]
if not lines:
return ""
return f"{label}: {' | '.join(lines[-line_limit:])}"
def _command_output_summary(stdout: str, stderr: str) -> str:
parts = [
part
for part in (
_tail_output("stderr", stderr),
_tail_output("stdout", stdout),
)
if part
]
summary = " ; ".join(parts)
return summary[:1600]
class CommandError(RuntimeError):
def __init__(self, command: list[str], returncode: int, stdout: str, stderr: str) -> None:
super().__init__(f"Command failed with exit code {returncode}: {' '.join(command)}")
message = f"Command failed with exit code {returncode}: {' '.join(command)}"
output_summary = _command_output_summary(stdout, stderr)
if output_summary:
message = f"{message}. Last output: {output_summary}"
super().__init__(message)
self.command = command
self.returncode = returncode
self.stdout = stdout
@@ -20,15 +46,26 @@ class CommandRunner:
self.repository = repository
self.task_id = task_id
def run(self, command: list[str], timeout: int | None = None) -> subprocess.CompletedProcess[str]:
def run(
self,
command: list[str],
timeout: int | None = None,
env: Mapping[str, str] | None = None,
) -> subprocess.CompletedProcess[str]:
if self.task_id:
self.repository.add_log(self.task_id, "debug", f"Running command: {' '.join(command)}")
command_env = os.environ.copy()
if env:
command_env.update(env)
try:
result = subprocess.run(
command,
check=False,
capture_output=True,
env=command_env,
stdin=subprocess.DEVNULL,
text=True,
timeout=timeout or settings.command_timeout_seconds,
)
@@ -47,4 +84,3 @@ class CommandRunner:
raise CommandError(command, result.returncode, result.stdout, result.stderr)
return result

View File

@@ -5,6 +5,22 @@ from pathlib import Path
from app.core.command_runner import CommandRunner
APT_DPKG_OPTIONS = [
"-o",
"Dpkg::Use-Pty=0",
"-o",
"Dpkg::Options::=--force-confdef",
"-o",
"Dpkg::Options::=--force-confold",
]
APT_NONINTERACTIVE_ENV = {
"DEBIAN_FRONTEND": "noninteractive",
"DEBCONF_NONINTERACTIVE_SEEN": "true",
"APT_LISTCHANGES_FRONTEND": "none",
}
def _parse_deb_control_output(output: str) -> dict[str, str]:
metadata: dict[str, str] = {}
@@ -49,11 +65,23 @@ class DebInstaller:
return metadata
def install_deb(self, file_path: Path) -> None:
self.command_runner.run(["apt", "install", "-y", str(file_path)])
self.command_runner.run(
[
"apt-get",
*APT_DPKG_OPTIONS,
"install",
"--yes",
str(file_path),
],
env=APT_NONINTERACTIVE_ENV,
)
def remove_package(self, package_name: str, purge: bool = False) -> None:
action = "purge" if purge else "remove"
self.command_runner.run(["apt", action, "-y", package_name])
self.command_runner.run(
["apt-get", *APT_DPKG_OPTIONS, action, "--yes", package_name],
env=APT_NONINTERACTIVE_ENV,
)
def get_package_version(self, package_name: str) -> str | None:
result = self.command_runner.run(["dpkg-query", "-W", "-f=${Version}", package_name])