1#!/usr/bin/env python3
2"""Publish a clean, signed-in sandbox Windows template with the built guest agent."""
3import argparse
4import base64
5import json
6from pathlib import Path
7import time
8import vms
9
10parser = argparse.ArgumentParser(description=__doc__)
11parser.add_argument('name', help='Running disposable template with the sandbox administrator signed in')
12parser.add_argument('id', help='New immutable preset ID')
13parser.add_argument('--supersedes', required=True)
14args = parser.parse_args()
15for name in [args.name, args.id, args.supersedes]:
16 vms.ensure_name(name)
17if (vms.PRESETS / args.id).exists():
18 raise ValueError('A preset already has this name.')
19root = Path(__file__).resolve().parent.parent
20preset = vms.read_preset(args.supersedes)
21if preset['platform'] != 'windows' or preset['arch'] != 'x86_64':
22 raise ValueError('Select a Windows preset.')
23files = [(root / 'guest/install.ps1', 'install-guest.ps1'),
24 (root / 'guest/windows-provision.ps1', 'windows-provision.ps1'),
25 (root / 'guest/build/snowglobe-guest-x86_64.exe', 'snowglobe-guest.exe')]
26contents = [(path.read_bytes(), name) for path, name in files]
27
28def qga(operation, **arguments):
29 command = {'execute': operation}
30 if arguments:
31 command['arguments'] = arguments
32 return json.loads(vms.virsh('qemu-agent-command', args.name, json.dumps(command), '--timeout', '15'))['return']
33
34def wait(pid):
35 deadline = time.monotonic() + 180
36 while True:
37 result = qga('guest-exec-status', pid=pid)
38 if result.get('exited'):
39 if result.get('exitcode') != 0:
40 raise RuntimeError(base64.b64decode(result.get('err-data', '')).decode(errors='replace'))
41 return
42 if time.monotonic() > deadline:
43 raise TimeoutError('Template preparation did not finish; inspect the existing process before retrying.')
44 time.sleep(.25)
45
46pid = qga('guest-exec', path='powershell.exe', arg=['-NoProfile', '-NonInteractive', '-Command',
47 "New-Item C:\\Snowglobe -ItemType Directory -Force | Out-Null; Stop-ScheduledTask -TaskName SnowglobeProvision -ErrorAction SilentlyContinue"], **{'capture-output': True})['pid']
48wait(pid)
49for content, name in contents:
50 handle = qga('guest-file-open', path='C:\\Snowglobe\\' + name, mode='w')
51 try:
52 qga('guest-file-write', handle=handle, **{'buf-b64': base64.b64encode(content).decode()})
53 finally:
54 qga('guest-file-close', handle=handle)
55pid = qga('guest-exec', path='powershell.exe', arg=['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass',
56 '-File', 'C:\\Snowglobe\\windows-provision.ps1', '-Prepare'], **{'capture-output': True})['pid']
57wait(pid)
58vms.virsh('shutdown', args.name, '--mode', 'agent')
59deadline = time.monotonic() + 120
60while vms.virsh('domstate', args.name).strip() != 'shut off':
61 if time.monotonic() > deadline:
62 raise TimeoutError('The template did not shut down.')
63 time.sleep(.5)
64vms.save_preset({'name': args.name, 'id': args.id, 'os': preset['os'],
65 'description': 'Prepared desktop with cursor integration', 'setup': 'windows-v3', 'supersedes': args.supersedes})
66(vms.PRESETS / args.id / 'disk.qcow2').chmod(0o440)
67print(args.id)