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
95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
"""CLI for building QEMU VM images.
|
|
|
|
Usage:
|
|
python -m cua_sandbox.builder build-base --os windows --version 11
|
|
python -m cua_sandbox.builder build-base --os windows --version 11 --iso-path /path/to/win11.iso
|
|
python -m cua_sandbox.builder build --spec '{"os_type":"windows","version":"11","layers":[{"type":"winget_install","packages":["Git.Git"]}]}'
|
|
python -m cua_sandbox.builder info
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from cua_sandbox.builder.overlay import IMAGES_DIR
|
|
|
|
|
|
def main():
|
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
|
|
parser = argparse.ArgumentParser(description="CUA Sandbox image builder")
|
|
sub = parser.add_subparsers(dest="command")
|
|
|
|
# build-base
|
|
bb = sub.add_parser("build-base", help="Build a base image (OS + computer-server)")
|
|
bb.add_argument("--os", default="windows", help="OS type (windows, linux)")
|
|
bb.add_argument("--version", default="11", help="OS version")
|
|
bb.add_argument("--iso-path", help="Path to OS ISO (skip download)")
|
|
bb.add_argument("--product-key", help="Windows product key")
|
|
bb.add_argument("--force", action="store_true", help="Rebuild even if cached")
|
|
|
|
# build (from Image spec)
|
|
b = sub.add_parser("build", help="Build a user image from an Image spec JSON")
|
|
b.add_argument("--spec", required=True, help="Image spec as JSON string or @file.json")
|
|
b.add_argument("--force", action="store_true")
|
|
|
|
# info
|
|
sub.add_parser("info", help="Show cached images")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.command == "build-base":
|
|
from cua_sandbox.builder.build import ensure_base_image
|
|
|
|
disk = asyncio.run(
|
|
ensure_base_image(
|
|
args.os,
|
|
args.version,
|
|
windows_iso=args.iso_path,
|
|
product_key=args.product_key,
|
|
force=args.force,
|
|
)
|
|
)
|
|
print(f"Base image: {disk}")
|
|
|
|
elif args.command == "build":
|
|
spec_str = args.spec
|
|
if spec_str.startswith("@"):
|
|
spec_str = Path(spec_str[1:]).read_text()
|
|
spec = json.loads(spec_str)
|
|
|
|
from cua_sandbox.builder.build import build_user_image, ensure_base_image
|
|
from cua_sandbox.image import Image
|
|
|
|
image = Image.from_dict(spec)
|
|
base = asyncio.run(ensure_base_image(image.os_type, image.version))
|
|
user_disk = asyncio.run(build_user_image(image, base, force=args.force))
|
|
print(f"User image: {user_disk}")
|
|
|
|
elif args.command == "info":
|
|
if not IMAGES_DIR.exists():
|
|
print(f"No images directory: {IMAGES_DIR}")
|
|
return
|
|
print(f"Images directory: {IMAGES_DIR}\n")
|
|
for d in sorted(IMAGES_DIR.iterdir()):
|
|
if d.is_dir():
|
|
disk = d / "disk.qcow2"
|
|
meta = d / "disk.json"
|
|
size = f"{disk.stat().st_size / 1024 / 1024:.0f} MB" if disk.exists() else "no disk"
|
|
layers = ""
|
|
if meta.exists():
|
|
m = json.loads(meta.read_text())
|
|
layers = f" ({len(m.get('layers', []))} layers)"
|
|
print(f" {d.name}: {size}{layers}")
|
|
|
|
else:
|
|
parser.print_help()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|