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
92 lines
2.7 KiB
Python
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"
|