1 Commits
Author SHA1 Message Date
Codex b54693be0c Fix raw USB live boot and verify both installation media paths 2026-10-14 07:37:24 +03:00
11 changed files with 339 additions and 8 deletions
+6
View File
@@ -11,6 +11,12 @@ offline Btrfs base recovery and read-only laptop diagnostics. The complete sourc
suite passed **431 tests**. The final ISO passed **13 whole-disk** and **23
preserved-partition/recovery** UEFI/KVM checks, with no skips. See the
[release and acceptance record](docs/releases/2026-10-13-laptop-ready/README.md).
**USB correction (2026-10-14):** those installation tests used virtual CD-ROM
media. Raw USB boot exposed a duplicate HFS+ live-volume label in that ISO.
Use `hydra-os-usb-fixed.iso` for USB installation; its unchanged system payload
now passes both USB and CD-ROM live boot, graphical-session and shutdown checks.
The updated source suite passes **439 tests**. See the
[USB correction record](docs/releases/2026-10-14-usb-fix/README.md).
The [previous terminal UX preview](docs/releases/2026-10-13-terminal-ux/README.md)
passed 13 disposable UEFI/KVM checks against its earlier image. The first physical
target is ASUS TUF Dash F15 FX517ZC; hardware checks remain pending. No physical
+44 -3
View File
@@ -635,7 +635,12 @@ def qemu_path(path):
return str(path)
def qemu_argv(qemu, scratch, disk, code, variables, iso, memory, *, keep_cdrom_drive=False):
def qemu_argv(qemu, scratch, disk, code, variables, iso, memory, *, keep_cdrom_drive=False,
live_media='cdrom'):
if live_media not in {'cdrom', 'usb'}:
raise SmokeError('Unknown live media; choose cdrom or usb')
if keep_cdrom_drive and live_media == 'usb':
raise SmokeError('keep_cdrom_drive cannot be combined with usb live media')
scratch = Path(scratch)
owned_file(disk, scratch)
owned_file(variables, scratch)
@@ -658,10 +663,14 @@ def qemu_argv(qemu, scratch, disk, code, variables, iso, memory, *, keep_cdrom_d
if iso is not None:
optical += ',format=raw,file=' + qemu_path(iso)
argv += ['-drive', optical]
elif iso is not None and live_media == 'usb':
argv += ['-drive', f'if=none,id=acceptance-usb,format=raw,readonly=on,file={qemu_path(iso)}',
'-device', 'qemu-xhci,id=acceptance-xhci',
'-device', 'usb-storage,bus=acceptance-xhci.0,drive=acceptance-usb,bootindex=1']
elif iso is not None:
argv += ['-drive', f'file={qemu_path(iso)},media=cdrom,format=raw,readonly=on']
if iso is not None:
argv += ['-boot', 'order=d,strict=on']
argv += ['-boot', 'strict=on' if live_media == 'usb' else 'order=d,strict=on']
else:
argv += ['-boot', 'order=c,strict=on']
return argv
@@ -827,7 +836,8 @@ class VM:
self.stderr = open(scratch / f'{phase}-qemu.log', 'ab', buffering=0)
self.process = subprocess.Popen(qemu_argv(args.qemu, scratch, disk,
args.ovmf_code, scratch / 'OVMF_VARS.fd', iso, args.memory,
keep_cdrom_drive=getattr(args, 'keep_cdrom_drive', False)),
keep_cdrom_drive=getattr(args, 'keep_cdrom_drive', False),
live_media=getattr(args, 'live_media', 'cdrom')),
stdin=subprocess.DEVNULL, stdout=self.stderr, stderr=self.stderr)
self.console = None
self.qmp = None
@@ -910,6 +920,25 @@ for tool in hydra hydra-install qemu-system-x86_64 qemu-img remote-viewer foot l
for service in NetworkManager hydra-network hydra-dnsmasq; do systemctl is-active "$service"; done
'''
LIVE_GRAPHICAL_CHECK = r'''set -eu
systemctl is-active greetd
pgrep -u hydra-live -x labwc
pgrep -u hydra-live -x foot
'''
def live_graphical_probe(console, timeout=30):
# Serial login can become ready before greetd has launched the live shell.
deadline = time.monotonic() + timeout
while True:
try:
return console.command(LIVE_GRAPHICAL_CHECK, min(10, max(0, deadline - time.monotonic())))
except SmokeError:
if time.monotonic() >= deadline:
raise
time.sleep(min(1, max(0, deadline - time.monotonic())))
INSTALLED_CHECK = r'''set -eu
assert_no_match() {
if grep "$@"; then
@@ -1069,6 +1098,10 @@ def digest(path):
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--iso', required=True, type=Path)
parser.add_argument('--live-media', choices=('cdrom', 'usb'), default='cdrom',
help='Attach ISO as optical media or a read-only raw USB disk')
parser.add_argument('--live-only', action='store_true',
help='Check live boot and graphical session, then power off without installing')
parser.add_argument('--ovmf-code', required=True, type=Path)
parser.add_argument('--ovmf-vars', required=True, type=Path)
parser.add_argument('--qemu', default='qemu-system-x86_64')
@@ -1092,6 +1125,7 @@ def main(argv=None):
print(f'Acceptance artifacts: {scratch}', flush=True)
report = {'schema': 1, 'status': 'running', 'scratch': str(scratch),
'iso': str(args.iso), 'iso_sha256': digest(args.iso),
'live_media': args.live_media, 'scope': 'live-only' if args.live_only else 'install-reboot',
'checks': {}, 'physical_hardware': 'pending',
'nested_scope': 'Normal-user KVM VM launch, bridge helper and QMP; no nested guest OS boot',
'ovmf_code_sha256': digest(args.ovmf_code),
@@ -1115,6 +1149,13 @@ def main(argv=None):
console = vm.console
console.expect(rb'hydra-live[^\r\n]*[#$] ', args.boot_timeout)
checkpoint('live_boot', console.command(LIVE_CHECK, 90))
if args.live_only:
checkpoint('live_graphical_session', live_graphical_probe(console))
console.send('sudo -n poweroff\n')
vm.wait_poweroff(90)
checkpoint('live_poweroff', b'Live-only check completed without invoking the installer')
report['status'] = 'passed'
return 0
console.buffer = b''
installer_token = secrets.token_hex(12)
console.send(installer_command(installer_token) + '\n')
+5
View File
@@ -1,6 +1,11 @@
# Install Hydra OS
Hydra OS targets x86_64 UEFI systems. Disable Secure Boot for this first image.
For a USB flash drive, use the [2026-10-14 corrected ISO](releases/2026-10-14-usb-fix/README.md),
`hydra-os-usb-fixed.iso`. The original 0.2.0 ISO passed optical-media tests but has
a raw USB live-boot bug. Write the corrected image to the entire flash drive
using a host-native image writer; this erases that flash drive. Mounting it before
reboot is unnecessary. Physical firmware compatibility still requires testing.
Boot the ISO and enter `:shell` in the terminal VM menu. The live session uses a locked
`hydra-live` account with local sudo access; changes in the live filesystem are
volatile. The image provides no default SSH login or universal installed password.
@@ -0,0 +1,49 @@
# Hydra OS USB media correction — 2026-10-14
The original 0.2.0 ISO (`451e058228d7cbea87b5301ae6502b6be609cf7f52cc7c9e6fb477822501389b`)
boots as a virtual CD-ROM but fails when attached as raw USB storage. GRUB's HFS+
hybrid uses the same `HYDRA_OS` label as ISO9660; dracut then selects the HFS+
partition and fails to mount the live payload. This was reproduced under OVMF
with a read-only USB image and no physical disk access.
The corrected image adds native xorriso `-hfsplus off` to `grub-mkrescue`. It keeps
GPT, the EFI partition and El Torito boot information, and removes HFS+/APM.
The builder now rejects HFS+/APM in its inspection report.
- Local file: `build/artifacts/hydra-os-usb-fixed.iso`
- Size: 2,380,120,064 bytes
- SHA256: `3a126e1a1004ba24ead66971013ba4a8468508f6fac4dcaf04aa9770cb8df7e1`
- Frozen payload source: `ebeebc7fb1aed07a5f50bc62374365d614953dad`
- The ISO is local; this Git directory contains evidence, not an ISO download.
Kernel, live initramfs, squashfs and embedded build manifest match the original
ISO byte-for-byte. [Payload hashes](payload-comparison.json) were computed from
the actual original and corrected ISO extents. This is a media-layout repair,
not a new system payload or signed runtime update. The original artifact and
release evidence remain unchanged.
## Verification
`python3 -m unittest discover -s tests -q` passed **439 tests** inside Ubuntu with
Python 3.12 and OpenSSL available. Initial host runs failed in pre-existing
OpenSSL-dependent updater/signature tests because OpenSSL was absent or lost
from the test PATH; these were not reported as successful runs.
The same corrected ISO passed the real `dev/iso-smoke.py --live-only` checks in
both [USB](usb-live-acceptance.json) and [CD-ROM](cdrom-live-acceptance.json) modes:
KVM, UEFI live user and overlay root, readable squashfs, sudo, required tools,
NetworkManager and Hydra network services, active greetd/labwc/foot, and normal
shutdown. Each run used fresh OVMF variables and a disposable test disk. Neither
invoked the installer or accessed host disks. The earlier full installation and
recovery checks apply to the byte-identical system payload, not a newly repeated
full installation test of this repacked ISO.
The remaining FAT12 EFI partition is unchanged. OVMF successfully starts it via
USB; this does not certify detection by every physical firmware. ASUS boot-menu
visibility, USB port behavior and actual laptop live boot still need physical
confirmation. Secure Boot must remain disabled.
Reflash the corrected ISO using a host-native writer. For the existing NixOS
setup, GNOME Disks can be run temporarily; an Etcher process inside rootless
Distrobox could not elevate or write the raw USB device. Do not change SSD
partitions to repair an installation USB.
@@ -0,0 +1,32 @@
{
"schema": 1,
"status": "passed",
"scratch": "/tmp/hydra-iso-smoke-l7g8944d",
"iso": "/home/workspaces/ubuntu-dev/home/Projects/hydra-os/build/artifacts/usb-fix-nohfs/hydra-os.iso",
"iso_sha256": "3a126e1a1004ba24ead66971013ba4a8468508f6fac4dcaf04aa9770cb8df7e1",
"live_media": "cdrom",
"scope": "live-only",
"checks": {
"live_qmp_kvm": {
"status": "passed",
"output": "{\"enabled\": true, \"present\": true}"
},
"live_boot": {
"status": "passed",
"output": "/usr/bin/hydra\n/usr/bin/hydra-install\n/usr/bin/qemu-system-x86_64\n/usr/bin/qemu-img\n/usr/bin/remote-viewer\n/usr/bin/foot\n/usr/bin/labwc\nactive\nactive\nactive\n"
},
"live_graphical_session": {
"status": "passed",
"output": "active\n832\n854\n"
},
"live_poweroff": {
"status": "passed",
"output": "Live-only check completed without invoking the installer"
}
},
"physical_hardware": "pending",
"nested_scope": "Normal-user KVM VM launch, bridge helper and QMP; no nested guest OS boot",
"ovmf_code_sha256": "f3ff7e73448ed2845ee15356f394882f5618eb5dab92c9a30ec6ee0e1468553a",
"ovmf_vars_input_sha256": "6ed987af3a3c155be71665f510eae3e007eda9b8b94afd59d45e91c4a11565cc",
"completed_utc": "2026-10-14T04:35:22Z"
}
@@ -0,0 +1,26 @@
{
"vmlinuz": {
"size": 22666224,
"original": "bb77e0f760f1ce97d2f6e23acad1fb2bb3dccab6c1381bec0868327e110ec3a5",
"candidate": "bb77e0f760f1ce97d2f6e23acad1fb2bb3dccab6c1381bec0868327e110ec3a5",
"matches": true
},
"initramfs-live.img": {
"size": 83130912,
"original": "694778902d95bfc94da1d7566d371379fb4a60de95e49babb6500cf907509d8e",
"candidate": "694778902d95bfc94da1d7566d371379fb4a60de95e49babb6500cf907509d8e",
"matches": true
},
"squashfs.img": {
"size": 2254630912,
"original": "ca550eb444e05505f815f4820b28527c3f8e8aee425005b73b7f02e4e74f7fc8",
"candidate": "ca550eb444e05505f815f4820b28527c3f8e8aee425005b73b7f02e4e74f7fc8",
"matches": true
},
"build-manifest.json": {
"size": 142178,
"original": "126c66d84b7c2160a517cbd0c3649630982a4d734e113ecdd6ee48e0859f1630",
"candidate": "126c66d84b7c2160a517cbd0c3649630982a4d734e113ecdd6ee48e0859f1630",
"matches": true
}
}
@@ -0,0 +1,32 @@
{
"schema": 1,
"status": "passed",
"scratch": "/tmp/hydra-iso-smoke-arp78mfm",
"iso": "/home/workspaces/ubuntu-dev/home/Projects/hydra-os/build/artifacts/usb-fix-nohfs/hydra-os.iso",
"iso_sha256": "3a126e1a1004ba24ead66971013ba4a8468508f6fac4dcaf04aa9770cb8df7e1",
"live_media": "usb",
"scope": "live-only",
"checks": {
"live_qmp_kvm": {
"status": "passed",
"output": "{\"enabled\": true, \"present\": true}"
},
"live_boot": {
"status": "passed",
"output": "/usr/bin/hydra\n/usr/bin/hydra-install\n/usr/bin/qemu-system-x86_64\n/usr/bin/qemu-img\n/usr/bin/remote-viewer\n/usr/bin/foot\n/usr/bin/labwc\nactive\nactive\nactive\n"
},
"live_graphical_session": {
"status": "passed",
"output": "active\n855\n869\n"
},
"live_poweroff": {
"status": "passed",
"output": "Live-only check completed without invoking the installer"
}
},
"physical_hardware": "pending",
"nested_scope": "Normal-user KVM VM launch, bridge helper and QMP; no nested guest OS boot",
"ovmf_code_sha256": "f3ff7e73448ed2845ee15356f394882f5618eb5dab92c9a30ec6ee0e1468553a",
"ovmf_vars_input_sha256": "6ed987af3a3c155be71665f510eae3e007eda9b8b94afd59d45e91c4a11565cc",
"completed_utc": "2026-10-14T04:35:22Z"
}
+6 -2
View File
@@ -500,14 +500,18 @@ cp /hydra-output/artifacts/squashfs.img "$tree/LiveOS/squashfs.img"
cp /hydra-source/image/grub.cfg "$tree/boot/grub/grub.cfg"
cp /hydra-output/artifacts/rootfs-manifest.json "$tree/build-manifest.json"
cp /hydra-output/artifacts/packages.json "$tree/packages.json"
grub-mkrescue --directory=/usr/lib/grub/x86_64-efi --output=/hydra-output/artifacts/hydra-os.iso "$tree" -- -volid HYDRA_OS
xorriso -indev /hydra-output/artifacts/hydra-os.iso -report_el_torito plain -pvd_info > /hydra-output/artifacts/iso-inspection.txt 2>&1
# GRUB's default HFS+ hybrid duplicates HYDRA_OS on a second filesystem.
# On USB, dracut can select that label instead of the ISO9660 live payload.
grub-mkrescue --directory=/usr/lib/grub/x86_64-efi --output=/hydra-output/artifacts/hydra-os.iso "$tree" -- -volid HYDRA_OS -hfsplus off
xorriso -indev /hydra-output/artifacts/hydra-os.iso -report_el_torito plain -report_system_area plain -pvd_info > /hydra-output/artifacts/iso-inspection.txt 2>&1
chmod 0644 /hydra-output/artifacts/hydra-os.iso /hydra-output/artifacts/iso-inspection.txt
"""
self.pod('exec', assembler, '/bin/bash', '-euc', script)
inspection = (self.work / 'artifacts/iso-inspection.txt').read_text()
if 'HYDRA_OS' not in inspection or not re.search(r'El Torito boot img.*UEFI', inspection):
raise BuildError('ISO does not report the expected volume label and UEFI boot image')
if re.search(r'\b(?:APM|HFSPLUS)\b', inspection):
raise BuildError('ISO contains an HFS+/APM hybrid that can shadow the live volume on USB')
if image.source_identity(self.source_dir) != source_inputs:
raise BuildError('Source inputs changed during ISO assembly; completion not recorded')
checksum = image.digest(self.work / 'artifacts/hydra-os.iso')
+7
View File
@@ -45,3 +45,10 @@ metamanifest verification; the builder continues to use its pinned snapshot.
QEMU and remote-viewer retain local patches and are excluded from upstream
binary packages on installed updates. Disposable boot/install validation is
required before treating an emitted ISO as release ready.
The x86_64 ISO disables GRUB's optional HFS+/APM hybrid. Otherwise that second
filesystem duplicates the `HYDRA_OS` label and can make dracut mount the wrong
live medium after raw USB writing. ISO inspection includes the system area and
rejects HFS+/APM. Test both optical and USB attachment with `dev/iso-smoke.py
--live-only --live-media cdrom` and `--live-only --live-media usb` (plus the normal
ISO and OVMF arguments); optical boot alone is insufficient.
+21
View File
@@ -501,6 +501,27 @@ class ImageBuildTests(unittest.TestCase):
path.write_text('[{"package":"app-emulation/qemu-8.0", "use": [], "build_time": null}]')
self.assertFalse(self.builder.complete('packages'))
def test_iso_refuses_hfs_hybrid_that_shadows_live_volume_on_usb(self):
from hydra.build import BuildError
self.inventory()
self.builder.complete = lambda step: step == 'rootfs'
self.builder.assembly = lambda: 'isolated-assembler'
artifacts = self.builder.work / 'artifacts'
def pod(*args, **kwargs):
self.assertEqual(args[:2], ('exec', 'isolated-assembler'))
(artifacts / 'hydra-os.iso').write_bytes(b'ISO fixture')
(artifacts / 'iso-inspection.txt').write_text(
'Volume id : HYDRA_OS\n'
'El Torito boot img : 1 UEFI y none\n'
'System area summary: MBR protective-msdos-label GPT APM\n'
'GPT partname local : 3 HFSPLUS\n')
self.builder.pod = pod
with self.assertRaisesRegex(BuildError, 'HFS|APM'):
self.builder.iso()
self.assertFalse(self.builder.manifest()['iso']['complete'])
def test_malformed_inventory_with_matching_digest_is_refused(self):
for data in ['{', '{}', '[]', '[{"package":"wrong"}]',
'[{"package":"app-emulation/qemu-9.2.4", "use":"spice", "build_time":"123"}]',
+111 -3
View File
@@ -63,6 +63,103 @@ class SmokeTests(unittest.TestCase):
self.assertNotIn('media=cdrom', ' '.join(argv))
self.assertNotIn('-cdrom', argv)
def test_usb_live_boot_uses_readonly_raw_storage_and_explicit_bootindex(self):
disk = self.root / 'disk.qcow2'
self.mod.reserve_disk(disk)
iso = self.root / 'trusted.iso'
iso.write_bytes(b'ISO')
argv = self.mod.qemu_argv('qemu', self.root, disk, self.code,
self.variables, iso, 2048, live_media='usb')
drives = [argv[i + 1] for i, value in enumerate(argv[:-1]) if value == '-drive']
devices = [argv[i + 1] for i, value in enumerate(argv[:-1]) if value == '-device']
backend = next(dict(field.split('=', 1) for field in drive.split(','))
for drive in drives if str(iso) in drive)
self.assertEqual(backend['file'], str(iso))
self.assertEqual(backend['if'], 'none')
self.assertEqual(backend['format'], 'raw')
self.assertEqual(backend['readonly'], 'on')
self.assertNotIn('media', backend, 'USB must exercise a disk rather than a CD-ROM')
self.assertTrue(any(device.startswith('qemu-xhci,') for device in devices))
storage = next(device for device in devices if device.startswith('usb-storage,'))
self.assertIn('drive=' + backend['id'], storage)
self.assertIn('bootindex=1', storage)
self.assertNotIn('order=d', ' '.join(argv))
self.assertNotIn('media=cdrom', ' '.join(argv))
def test_usb_installed_reboot_removes_live_medium_and_keeps_disk_boot(self):
disk = self.root / 'disk.qcow2'
self.mod.reserve_disk(disk)
argv = self.mod.qemu_argv('qemu', self.root, disk, self.code,
self.variables, None, 2048, live_media='usb')
self.assertNotIn('usb-storage', ' '.join(argv))
self.assertNotIn('media=cdrom', ' '.join(argv))
self.assertIn('order=c,strict=on', argv)
def test_usb_cannot_be_combined_with_recovery_optical_topology(self):
disk = self.root / 'disk.qcow2'
self.mod.reserve_disk(disk)
for medium in (self.code, None):
with self.subTest(medium=medium):
with self.assertRaisesRegex(self.mod.SmokeError, 'keep_cdrom_drive.*usb'):
self.mod.qemu_argv('qemu', self.root, disk, self.code,
self.variables, medium, 2048, live_media='usb', keep_cdrom_drive=True)
def test_usb_rejects_devices_and_ambiguous_paths_as_live_media(self):
disk = self.root / 'disk.qcow2'
self.mod.reserve_disk(disk)
ambiguous = self.root / 'image,readonly=off.iso'
ambiguous.write_bytes(b'ISO')
for medium in (Path('/dev/null'), ambiguous):
with self.subTest(medium=medium):
with self.assertRaises(self.mod.SmokeError):
self.mod.qemu_argv('qemu', self.root, disk, self.code,
self.variables, medium, 2048, live_media='usb')
def test_unknown_live_media_is_refused_instead_of_silently_booting_cd(self):
disk = self.root / 'disk.qcow2'
self.mod.reserve_disk(disk)
with self.assertRaisesRegex(self.mod.SmokeError, 'live media'):
self.mod.qemu_argv('qemu', self.root, disk, self.code,
self.variables, self.code, 2048, live_media='typo')
def test_live_only_checks_boot_and_graphics_without_installing_or_rebooting(self):
import json
from unittest.mock import Mock, patch
iso = self.root / 'trusted.iso'
iso.write_bytes(b'ISO')
vm = Mock()
vm.kvm = {'enabled': True}
vm.console.command.return_value = b'live probe passed'
with patch.object(self.mod, 'VM', return_value=vm) as create_vm, \
patch.object(self.mod.subprocess, 'run') as create_disk:
try:
status = self.mod.main(['--iso', str(iso), '--ovmf-code', str(self.code),
'--ovmf-vars', str(self.variables), '--scratch-parent', str(self.root),
'--live-media', 'usb', '--live-only'])
except SystemExit as exc:
self.fail(f'Live-only USB acceptance must be supported: {exc}')
self.assertEqual(status, 0)
create_vm.assert_called_once()
args, scratch, disk, medium, _password, phase = create_vm.call_args.args
self.assertEqual(args.live_media, 'usb')
self.assertEqual(medium, iso)
self.assertEqual(phase, 'live')
self.assertEqual(disk.parent, scratch)
self.assertEqual(create_disk.call_args.args[0][-2], str(disk))
self.assertTrue(disk.is_file(), 'Only a newly reserved disposable disk is used')
vm.console.send.assert_called_once_with('sudo -n poweroff\n')
vm.console.expect_installer.assert_not_called()
self.assertEqual([call.args[0] for call in vm.console.command.call_args_list],
[self.mod.LIVE_CHECK, self.mod.LIVE_GRAPHICAL_CHECK])
vm.wait_poweroff.assert_called_once_with(90)
vm.close.assert_called_once()
report = json.loads((scratch / 'report.json').read_text())
self.assertEqual(report['status'], 'passed')
self.assertEqual(report['live_media'], 'usb')
self.assertEqual(report['scope'], 'live-only')
self.assertEqual(set(report['checks']),
{'live_qmp_kvm', 'live_boot', 'live_graphical_session', 'live_poweroff'})
def test_persistent_optical_drive_keeps_topology_with_no_installed_medium(self):
disk = self.root / 'disk.qcow2'
self.mod.reserve_disk(disk)
@@ -289,7 +386,7 @@ class SmokeTests(unittest.TestCase):
self.mod.qemu_argv('qemu', self.root, comma, self.code,
self.variables, None, 2048)
def probe_fixture(self, case, live=False):
def probe_fixture(self, case, live=False, graphical=False):
# Run the production shell against ordinary scratch files. Commands that
# query guest state are boundary fixtures; grep/find/test are real tools.
import os
@@ -349,16 +446,19 @@ elif name == 'getent':
else: print('127.0.0.1 STREAM example')
elif name == 'ip': print('10.203.0.1/24' if 'addr' in args else 'default via 10.0.2.2')
elif name == 'nmcli': print('connected')
elif name == 'pgrep':
sys.exit(1 if args[-1] == case else 0)
else: print('fixture tool')
''')
dispatcher.chmod(0o755)
for command in ('id', 'findmnt', 'systemctl', 'getent', 'ip', 'nft',
'nmcli', 'sudo', 'hydra', 'hydra-install',
'nmcli', 'pgrep', 'sudo', 'hydra', 'hydra-install',
'qemu-system-x86_64', 'qemu-img', 'remote-viewer', 'foot', 'labwc'):
path = commands / command
if not path.exists():
path.symlink_to(dispatcher)
probe = self.mod.LIVE_CHECK if live else self.mod.INSTALLED_CHECK
probe = (self.mod.LIVE_GRAPHICAL_CHECK if graphical else
self.mod.LIVE_CHECK if live else self.mod.INSTALLED_CHECK)
for path in ('/etc/', '/usr/share', '/proc/cmdline', '/sys/firmware/efi', '/run/initramfs/live/LiveOS/squashfs.img'):
probe = probe.replace(path, str(root) + path)
import shutil
@@ -405,6 +505,14 @@ else: print('fixture tool')
with self.subTest(service=service):
self.assertNotEqual(self.probe_fixture(service, live=True), 0)
def test_live_graphical_probe_requires_greetd_compositor_and_terminal(self):
self.assertTrue(hasattr(self.mod, 'LIVE_GRAPHICAL_CHECK'),
'Live-only acceptance needs graphical session evidence')
self.assertEqual(self.probe_fixture('clean', live=True, graphical=True), 0)
for case in ('greetd', 'labwc', 'foot'):
with self.subTest(case=case):
self.assertNotEqual(self.probe_fixture(case, live=True, graphical=True), 0)
def tcp_probe(self, fragments):
# Execute the exact Python client embedded in the guest network probe,
# redirecting only its address to a fresh loopback socket.