remove app

This commit is contained in:
2026-06-08 10:54:52 +07:00
parent f9bec78c82
commit 47407cdbca
9 changed files with 112 additions and 13 deletions

View File

@@ -56,10 +56,15 @@ class TaskRunner:
try:
if not settings.allow_remove:
raise ValueError("Remove is disabled on this Agent")
if request.purge and not settings.allow_purge:
raise ValueError("Purge is disabled on this Agent")
self._mark_started(task_id, "starting remove")
self._require_root_if_available()
effective_purge = request.purge and settings.allow_purge
if request.purge and not effective_purge:
self.repository.add_log(
task_id,
"warning",
"Purge cleanup was requested but ALLOW_PURGE is disabled; falling back to remove cleanup",
)
components = self.repository.list_installed_components(request.app_id)
if not components and request.package_name:
@@ -81,6 +86,7 @@ class TaskRunner:
ordered = sorted(components, key=lambda item: item["install_order"], reverse=True)
total = len(ordered)
removed_deb_package = False
for index, component in enumerate(ordered, start=1):
progress = int((index - 1) / total * 80) + 10
component_id = component["component_id"]
@@ -93,22 +99,42 @@ class TaskRunner:
service_name = component.get("service_name")
if service_name:
self.repository.add_log(task_id, "info", f"Stopping service {service_name}")
services.stop_service(service_name)
services.disable_service(service_name)
self._best_effort(task_id, f"stop service {service_name}", lambda: services.stop_service(service_name))
self._best_effort(task_id, f"disable service {service_name}", lambda: services.disable_service(service_name))
package_name = component.get("package_name")
if component["type"] == "deb" and package_name:
self.repository.add_log(task_id, "info", f"Removing package {package_name}")
installer.remove_package(package_name, purge=request.purge)
installer.remove_package(package_name, purge=effective_purge)
self._clean_cached_package_files(task_id, package_name, component_id)
if service_name:
self._best_effort(task_id, f"reset failed state for {service_name}", lambda: services.reset_failed(service_name))
removed_deb_package = True
elif component["type"] == "docker":
container_name = component.get("container_name") or component_id
self.repository.add_log(task_id, "info", f"Removing Docker container {container_name}")
docker_installer = DockerInstaller(command_runner)
docker_installer.ensure_runtime(auto_install=settings.auto_install_docker)
docker_installer.remove_container(container_name)
docker_installer.remove_labeled_containers(
request.app_id,
component_id,
remove_volumes=effective_purge,
)
docker_installer.remove_container(container_name, remove_volumes=effective_purge)
image = component.get("docker_image") or component.get("image")
if effective_purge and image:
self._best_effort(task_id, f"remove Docker image {image}", lambda: docker_installer.remove_image(image))
else:
raise ValueError(f"Unsupported installed component type: {component['type']}")
if removed_deb_package:
self.repository.update_task(task_id, progress=92, current_step="cleaning package leftovers")
self._best_effort(
task_id,
"clean unused packages and apt cache",
lambda: installer.cleanup_after_remove(purge=effective_purge),
)
self.repository.delete_installed_app(request.app_id)
self.repository.update_task(
task_id,
@@ -323,3 +349,39 @@ class TaskRunner:
geteuid = getattr(os, "geteuid", None)
if callable(geteuid) and geteuid() != 0:
raise PermissionError("Agent must run as root to call apt and systemctl")
def _best_effort(self, task_id: str, action: str, callback: Any) -> None:
try:
callback()
except Exception as error:
self.repository.add_log(task_id, "warning", f"Could not {action}: {error}")
def _clean_cached_package_files(self, task_id: str, *identifiers: str | None) -> None:
cache_dir = settings.cache_dir
if not cache_dir.exists() or not cache_dir.is_dir():
return
patterns: list[str] = []
seen_patterns: set[str] = set()
for identifier in identifiers:
name = (identifier or "").strip()
if not name:
continue
for pattern in (f"{name}.deb", f"{name}_*.deb"):
if pattern not in seen_patterns:
patterns.append(pattern)
seen_patterns.add(pattern)
removed_files: set[str] = set()
for pattern in patterns:
for file_path in cache_dir.glob(pattern):
if not file_path.is_file():
continue
try:
file_path.unlink()
removed_files.add(str(file_path))
except Exception as error:
self.repository.add_log(task_id, "warning", f"Could not remove cached package {file_path}: {error}")
for file_path in sorted(removed_files):
self.repository.add_log(task_id, "info", f"Removed cached package {file_path}")