Fix package detection using dpkg-query, add apt-get update before install, add libxcb-cursor0 dep

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
roberts 2026-03-14 23:51:53 -05:00
parent 983857bd5b
commit 076ca348d7
2 changed files with 46 additions and 12 deletions

View file

@ -147,26 +147,45 @@ def _parse_apt_packages(install_sh_content: str) -> set[str]:
async def _get_installed_packages(packages: set[str]) -> set[str]: async def _get_installed_packages(packages: set[str]) -> set[str]:
"""Check which packages from the set are already installed via dpkg.""" """Check which packages from the set are already installed via dpkg-query."""
if not packages: if not packages:
return set() return set()
installed = set() installed = set()
for pkg in packages: # Use dpkg-query for batch check — more reliable than dpkg -s
# Note: dpkg-query returns non-zero if ANY package is unknown, but still
# outputs status for known packages on stdout.
try:
proc = await asyncio.create_subprocess_exec( proc = await asyncio.create_subprocess_exec(
"dpkg", "-s", pkg, "dpkg-query", "-W", "-f", "${Package} ${Status}\n", *sorted(packages),
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
env=_safe_env(), env=_safe_env(),
) )
try: stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=10)
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=5) # returncode may be non-zero if some packages are unknown — that's fine
except asyncio.TimeoutError: for line in stdout.decode().strip().splitlines():
continue # Format: "packagename install ok installed"
if proc.returncode == 0: if "install ok installed" in line:
# Verify it's actually installed (Status: install ok installed) pkg_name = line.split()[0]
output = stdout.decode() installed.add(pkg_name)
if "Status:" in output and "installed" in output: except (asyncio.TimeoutError, Exception) as e:
installed.add(pkg) log.debug("dpkg-query failed, falling back to individual checks: %s", e)
# Fallback: check each package individually
for pkg in packages:
proc = await asyncio.create_subprocess_exec(
"dpkg", "-s", pkg,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
env=_safe_env(),
)
try:
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=5)
except asyncio.TimeoutError:
continue
if proc.returncode == 0:
output = stdout.decode()
if "install ok installed" in output:
installed.add(pkg)
return installed return installed
@ -262,6 +281,20 @@ async def install_deps(req: InstallDepsRequest, _user: str = Depends(get_current
raise HTTPException(400, f"Invalid package name: {pkg}") raise HTTPException(400, f"Invalid package name: {pkg}")
apt_bin = shutil.which("apt-get") or "/usr/bin/apt-get" apt_bin = shutil.which("apt-get") or "/usr/bin/apt-get"
# Update package lists first
log.info("Running apt-get update before install")
update_proc = await asyncio.create_subprocess_exec(
apt_bin, "update", "-qq",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
env=_safe_env(),
)
try:
await asyncio.wait_for(update_proc.communicate(), timeout=120)
except asyncio.TimeoutError:
pass # Non-fatal, proceed with install anyway
cmd = [apt_bin, "install", "-y"] + req.packages cmd = [apt_bin, "install", "-y"] + req.packages
log.info("Installing system packages: %s", " ".join(req.packages)) log.info("Installing system packages: %s", " ".join(req.packages))

View file

@ -52,6 +52,7 @@ install_deps() {
xdotool \ xdotool \
imagemagick \ imagemagick \
x11-utils \ x11-utils \
libxcb-cursor0 \
> /dev/null 2>&1 > /dev/null 2>&1
ok "System dependencies installed." ok "System dependencies installed."
} }