Files
wehub-resource-sync 91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:19 +08:00

92 lines
2.7 KiB
Python

"""QEMU qcow2 overlay (backing file) management.
Provides the 3-layer chain:
Layer 0 (base): windows-11-base.qcow2 — unattend + computer-server
Layer 1 (user): {hash}.qcow2 — user's .winget_install()/.run() etc
Layer 2 (session): session-{uuid}.qcow2 — ephemeral sandbox runtime
"""
from __future__ import annotations
import hashlib
import json
import logging
import subprocess
from pathlib import Path
logger = logging.getLogger(__name__)
IMAGES_DIR = Path.home() / ".cua" / "cua-sandbox" / "images"
def _qemu_img() -> str:
"""Resolve qemu-img binary."""
import shutil
found = shutil.which("qemu-img")
if found:
return found
from cua_sandbox.runtime.qemu_installer import qemu_bin
parent = Path(qemu_bin("x86_64")).parent
for name in ["qemu-img", "qemu-img.exe"]:
p = parent / name
if p.exists():
return str(p)
raise RuntimeError("qemu-img not found")
def create_overlay(backing: Path, overlay: Path) -> Path:
"""Create a qcow2 overlay with the given backing file."""
overlay.parent.mkdir(parents=True, exist_ok=True)
cmd = [
_qemu_img(),
"create",
"-q",
"-f",
"qcow2",
"-b",
str(backing),
"-F",
"qcow2",
str(overlay),
]
subprocess.run(cmd, check=True, capture_output=True)
logger.info(f"Created overlay: {overlay} (backing: {backing})")
return overlay
def commit_overlay(overlay: Path) -> None:
"""Commit an overlay's changes into its backing file (flattens one level)."""
cmd = [_qemu_img(), "commit", "-q", str(overlay)]
subprocess.run(cmd, check=True, capture_output=True)
logger.info(f"Committed overlay: {overlay}")
def rebase_standalone(disk: Path) -> None:
"""Remove backing file reference, making the disk standalone."""
cmd = [_qemu_img(), "rebase", "-u", "-b", "", str(disk)]
subprocess.run(cmd, check=True, capture_output=True)
logger.info(f"Rebased standalone: {disk}")
def layers_hash(layers: list[dict]) -> str:
"""Compute a stable hash for a list of Image layers."""
raw = json.dumps(layers, sort_keys=True).encode()
return hashlib.sha256(raw).hexdigest()[:16]
def base_image_path(os_type: str, version: str) -> Path:
"""Path to the cached base image (OS + computer-server installed)."""
return IMAGES_DIR / f"{os_type}-{version}-base" / "disk.qcow2"
def user_image_path(os_type: str, version: str, layer_hash: str) -> Path:
"""Path to a cached user layer image."""
return IMAGES_DIR / f"{os_type}-{version}-{layer_hash}" / "disk.qcow2"
def session_overlay_path(name: str) -> Path:
"""Path to an ephemeral session overlay."""
return IMAGES_DIR / "sessions" / f"{name}.qcow2"