feat(tools): Enhance guest tools installation and management

- Added new API endpoints for tool catalog and installation management.
- Updated usage documentation to include new tool installation features.
- Implemented asynchronous tool installation with job tracking.
- Introduced integration tests for tool installation in disposable VMs.
- Enhanced dashboard UI to reflect tool installation status and usage instructions.
- Improved error handling and logging for tool installation processes.
- Added support for multiple Python versions and Conda environments.
- Refactored tool management logic to ensure better state handling and job persistence.
This commit is contained in:
2026-09-25 23:52:21 +05:30
parent a41aafce83
commit 3e17e3e72f
16 changed files with 729 additions and 34 deletions
+9 -3
View File
@@ -281,7 +281,7 @@ class Installer:
raise ToolError(f"installation directory is already in use: {prefix}")
regular_file(marker)
previous = json.loads(marker.read_bytes())
if any(previous.get(key) != value for key, value in identity.items()):
if not isinstance(previous, dict) or any(previous.get(key) != value for key, value in identity.items()):
raise ToolError(f"installation directory belongs to a different release: {prefix}")
if tool["kind"] != "conda" or previous.get("complete"):
return prefix
@@ -348,6 +348,10 @@ class Installer:
if manifest.exists():
regular_file(manifest)
self.managed = json.loads(manifest.read_bytes())
if (not isinstance(self.managed, dict) or any(
not isinstance(values, list) or len(values) > 2 or any(not isinstance(value, str) for value in values)
for values in self.managed.values())):
raise ToolError("invalid managed tool file manifest")
with tempfile.TemporaryDirectory(prefix=".install-", dir=ROOT) as name:
temporary = Path(name)
if tool["kind"] == "php":
@@ -369,11 +373,13 @@ class Installer:
def main(payload):
installer = Installer(payload["username"])
info = {}
try:
if os.geteuid() != 0:
raise ToolError("tool installation and inspection require guest root")
info = platform_info()
if not info["supported"] or info["architecture"] != payload["architecture"]:
info["supported"] = info["supported"] and info["architecture"] == payload["architecture"]
if not info["supported"]:
raise ToolError("one-click tools require an Ubuntu 22.04 guest on the selected x86_64/ARM64 architecture")
if payload.get("boot_id") and payload["boot_id"] != info["boot_id"]:
raise ToolError("the guest rebooted during installation; retry on the current boot")
@@ -385,7 +391,7 @@ def main(payload):
raise ToolError("unsupported tool operation")
result["ok"] = True
except (ToolError, OSError, ValueError, KeyError, tarfile.TarError, subprocess.SubprocessError) as error:
result = {"ok": False, "error": str(error)[:2048]}
result = {"ok": False, "error": str(error)[:2048], "supported": info.get("supported")}
result.update(output=installer.log.decode("utf-8", errors="replace"), truncated=installer.truncated)
print(json.dumps(result, ensure_ascii=True))