feat: support installed guests and distribution display defaults

This commit is contained in:
Codex
2026-10-13 04:26:49 +03:00
parent 0e86fffd54
commit 6f7344fca5
8 changed files with 299 additions and 24 deletions
+29 -5
View File
@@ -16,24 +16,32 @@ def validate_name(name: str) -> str:
@dataclass(frozen=True)
class Profile:
name: str
iso: Path
iso: Path | None = None
memory: int = 4096
cpus: int = 2
disk_size: str = '40G'
graphics: str = 'auto'
network: str = 'user'
bridge: str = 'hydrabr0'
width: int = 1280
height: int = 800
refresh_millihz: int = 0
def __post_init__(self):
validate_name(self.name)
iso = Path(self.iso).expanduser().resolve()
if not iso.is_file():
raise ValueError(f'ISO does not exist or is not a file: {iso}')
object.__setattr__(self, 'iso', iso)
if self.iso is not None:
if not isinstance(self.iso, (str, Path)) or not str(self.iso):
raise ValueError('ISO must be a nonempty path or omitted')
object.__setattr__(self, 'iso', Path(self.iso).expanduser().resolve())
for key, upper in (('memory', 262144), ('cpus', 256)):
value = getattr(self, key)
if type(value) is not int or not 1 <= value <= upper:
raise ValueError(f'{key} must be an integer between 1 and {upper}')
for key in ('width', 'height'):
if type(getattr(self, key)) is not int or not 1 <= getattr(self, key) <= 16384:
raise ValueError(f'{key} must be an integer between 1 and 16384')
if type(self.refresh_millihz) is not int or not 0 <= self.refresh_millihz <= 1000000:
raise ValueError('refresh_millihz must be an integer between 0 and 1000000')
if not isinstance(self.disk_size, str) or not re.fullmatch(r'[1-9][0-9]{0,5}[MGT]', self.disk_size):
raise ValueError('disk_size must be a positive size with M, G or T suffix')
if self.graphics not in ('auto', 'software'):
@@ -67,6 +75,22 @@ def save_profile(path: Path, profile: Profile) -> None:
lines = []
for field in Profile.__dataclass_fields__:
value = getattr(profile, field)
if value is None:
continue
lines.append(f'{field} = {value if type(value) is int else json.dumps(str(value), ensure_ascii=False)}\n')
with path.open('x', encoding='utf-8') as stream:
stream.write(''.join(lines))
def load_defaults(path: Path) -> dict:
"""Distribution preferences apply only when creating a new profile."""
try:
with Path(path).open('rb') as stream:
data = tomllib.load(stream)
except FileNotFoundError:
return {}
unknown = data.keys() - {'network', 'bridge', 'width', 'height', 'refresh_millihz'}
if unknown:
raise ValueError(f'Unknown defaults fields: {", ".join(sorted(unknown))}')
Profile('defaults-check', **data)
return data