diff --git a/hydra/image.py b/hydra/image.py index 5df4562..e09b468 100644 --- a/hydra/image.py +++ b/hydra/image.py @@ -251,6 +251,21 @@ def enable_user_audio(root): raise ImageError('Cannot enable user audio sockets: ' + result.stderr.strip()) +def brand_os_release(root): + """Override derivative identity while retaining packaged Gentoo release fields.""" + release = root_path(root, 'etc/os-release') + if not release.is_file(): + release = root_path(root, 'usr/lib/os-release') + original = release.read_text() if release.is_file() else '' + retained = [line for line in original.splitlines() + if not re.match(r'^(NAME|PRETTY_NAME|ID|ID_LIKE)=', line)] + retained += ['NAME="Hydra OS"', 'PRETTY_NAME="Hydra OS"', + 'ID=hydra-os', 'ID_LIKE=gentoo'] + # Gentoo links /etc/os-release to its packaged file; keep that file intact. + remove(root, 'etc/os-release') + write(root, 'etc/os-release', '\n'.join(retained) + '\n') + + def configure_rootfs(root, source): root, source = Path(root), Path(source) # Refuse incomplete integrations before changing the exported copy. @@ -264,6 +279,7 @@ def configure_rootfs(root, source): alias.symlink_to(kernel.name) sanitize_rootfs(root) copy_overlay(root, source / 'image/rootfs') + brand_os_release(root) write(root, 'etc/hydra/live-mode/dracut.conf', (source / 'image/dracut.conf').read_text()) root_path(root, 'etc/hydra/live-mode/dracut.conf.d').mkdir() package = root_path(root, 'usr/lib/hydra/hydra') diff --git a/image/grub.cfg b/image/grub.cfg index 22e87ff..75b056b 100644 --- a/image/grub.cfg +++ b/image/grub.cfg @@ -5,7 +5,7 @@ insmod serial serial --unit=0 --speed=115200 terminal_input console serial terminal_output console serial -menuentry "HydraOS live (volatile host root)" { +menuentry "Hydra OS live (volatile host root)" { search --no-floppy --label HYDRA_OS --set=root linux /boot/vmlinuz root=live:CDLABEL=HYDRA_OS rd.live.image rd.live.overlay.overlayfs=1 ro console=tty0 console=ttyS0,115200n8 initrd /boot/initramfs-live.img diff --git a/image/packages b/image/packages index b5772a2..633afdc 100644 --- a/image/packages +++ b/image/packages @@ -12,6 +12,7 @@ net-firewall/nftables media-video/pipewire media-video/wireplumber app-admin/sudo +app-misc/fastfetch sys-kernel/gentoo-kernel-bin sys-kernel/linux-firmware sys-firmware/sof-firmware diff --git a/image/rootfs/etc/default/grub b/image/rootfs/etc/default/grub index 0cc5428..c49aca5 100644 --- a/image/rootfs/etc/default/grub +++ b/image/rootfs/etc/default/grub @@ -1,3 +1,3 @@ -GRUB_DISTRIBUTOR="HydraOS" +GRUB_DISTRIBUTOR="Hydra OS" GRUB_TIMEOUT=5 GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=ttyS0,115200n8" diff --git a/image/rootfs/etc/fastfetch/config.jsonc b/image/rootfs/etc/fastfetch/config.jsonc new file mode 100644 index 0000000..e16dbfd --- /dev/null +++ b/image/rootfs/etc/fastfetch/config.jsonc @@ -0,0 +1,24 @@ +{ + "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", + "logo": { + "type": "file", + "source": "/usr/share/hydra/fastfetch.txt", + "color": { "1": "cyan", "2": "white" }, + "padding": { "right": 3 } + }, + "display": { + "color": { "keys": "cyan", "title": "white" }, + "separator": ": ", + "percent": { "type": 1 } + }, + "modules": [ + "title", + { "type": "os", "format": "{pretty-name} {arch}" }, + "kernel", + "uptime", + "cpu", + "memory", + { "type": "disk", "folders": "/" }, + "localip" + ] +} diff --git a/image/rootfs/etc/hydra/live-mode/active b/image/rootfs/etc/hydra/live-mode/active index f257c8f..7d19712 100644 --- a/image/rootfs/etc/hydra/live-mode/active +++ b/image/rootfs/etc/hydra/live-mode/active @@ -1 +1 @@ -HydraOS live medium: changes to the host root are not persistent. +Hydra OS live medium: changes to the host root are not persistent. diff --git a/image/rootfs/etc/motd b/image/rootfs/etc/motd index b194864..7857efe 100644 --- a/image/rootfs/etc/motd +++ b/image/rootfs/etc/motd @@ -1,5 +1,6 @@ -HydraOS: fullscreen VMs with an ordinary Gentoo host underneath. +Hydra OS: fullscreen VMs with an ordinary Gentoo host underneath. Live host changes are volatile. Use sudo hydra-install for the guarded installer. -Use hydra --help, nmcli, or nmtui to configure VMs and network connections. +Use hydra for the VM menu; hydra --help lists commands. fastfetch shows this host. +Use sudo nmtui to configure network connections. Ctrl+Alt+F2 opens local recovery; return to the graphical session on Ctrl+Alt+F1. Guest bridge: 10.203.0.0/24. Routed LAN access requires a route on your LAN router. diff --git a/image/rootfs/etc/systemd/system/hydra-bridge-permissions.service b/image/rootfs/etc/systemd/system/hydra-bridge-permissions.service index 93084f6..e290309 100644 --- a/image/rootfs/etc/systemd/system/hydra-bridge-permissions.service +++ b/image/rootfs/etc/systemd/system/hydra-bridge-permissions.service @@ -1,5 +1,5 @@ [Unit] -Description=Restrict QEMU bridge helper to HydraOS VM users +Description=Restrict QEMU bridge helper to Hydra OS VM users Before=greetd.service [Service] Type=oneshot diff --git a/image/rootfs/etc/systemd/system/hydra-dnsmasq.service b/image/rootfs/etc/systemd/system/hydra-dnsmasq.service index 1a774fd..d88b854 100644 --- a/image/rootfs/etc/systemd/system/hydra-dnsmasq.service +++ b/image/rootfs/etc/systemd/system/hydra-dnsmasq.service @@ -1,5 +1,5 @@ [Unit] -Description=DHCP and DNS for HydraOS guests +Description=DHCP and DNS for Hydra OS guests Requires=hydra-network.service PartOf=hydra-network.service After=hydra-network.service NetworkManager.service diff --git a/image/rootfs/etc/systemd/system/hydra-network.service b/image/rootfs/etc/systemd/system/hydra-network.service index 9023361..2c49248 100644 --- a/image/rootfs/etc/systemd/system/hydra-network.service +++ b/image/rootfs/etc/systemd/system/hydra-network.service @@ -1,5 +1,5 @@ [Unit] -Description=HydraOS guest bridge and Internet NAT +Description=Hydra OS guest bridge and Internet NAT Requires=NetworkManager.service PartOf=NetworkManager.service After=NetworkManager.service diff --git a/image/rootfs/usr/share/hydra/fastfetch.txt b/image/rootfs/usr/share/hydra/fastfetch.txt new file mode 100644 index 0000000..74534f5 --- /dev/null +++ b/image/rootfs/usr/share/hydra/fastfetch.txt @@ -0,0 +1,10 @@ +$1 /\ /\ /\ + .-(o > .-(o > .-(o > +/ / / / / / +\ \______\ \______\ \ + \ \ / + '---. .---' + \ /\/\ / + ___\/ \/___ + /______________\ +$2 H Y D R A O S diff --git a/tests/test_branding.py b/tests/test_branding.py new file mode 100644 index 0000000..f51c756 --- /dev/null +++ b/tests/test_branding.py @@ -0,0 +1,55 @@ +import shlex +from pathlib import Path +import tempfile +import unittest + +from hydra import image + + +class BrandingTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) / 'rootfs' + self.root.mkdir() + version = '6.18.54-gentoo-dist-bin' + (self.root / 'lib/modules' / version).mkdir(parents=True) + (self.root / 'boot').mkdir() + (self.root / 'boot' / ('vmlinuz-' + version)).write_text('kernel') + + def release(self): + return dict(line.split('=', 1)[0:1] + shlex.split(line.split('=', 1)[1]) + for line in (self.root / 'etc/os-release').read_text().splitlines() + if line and not line.startswith('#')) + + def test_export_brands_derivative_without_rewriting_packaged_gentoo_metadata(self): + vendor = self.root / 'usr/lib/os-release' + vendor.parent.mkdir(parents=True) + original = "NAME='Gentoo'\nID=gentoo\nPRETTY_NAME='Gentoo Linux'\nVERSION_ID=2.18\nHOME_URL='https://www.gentoo.org/'\n" + vendor.write_text(original) + (self.root / 'etc').mkdir() + (self.root / 'etc/os-release').symlink_to('../usr/lib/os-release') + + image.configure_rootfs(self.root, Path(__file__).resolve().parents[1]) + + self.assertEqual(self.release(), { + 'NAME': 'Hydra OS', 'PRETTY_NAME': 'Hydra OS', 'ID': 'hydra-os', + 'ID_LIKE': 'gentoo', 'VERSION_ID': '2.18', + 'HOME_URL': 'https://www.gentoo.org/', + }) + self.assertFalse((self.root / 'etc/os-release').is_symlink()) + self.assertEqual(vendor.read_text(), original) + self.assertEqual((self.root / 'etc/hostname').read_text(), 'hydra-os\n') + + def test_export_can_brand_minimal_payload_without_os_release(self): + image.configure_rootfs(self.root, Path(__file__).resolve().parents[1]) + + self.assertTrue((self.root / 'etc/os-release').is_file()) + self.assertEqual(self.release(), { + 'NAME': 'Hydra OS', 'PRETTY_NAME': 'Hydra OS', + 'ID': 'hydra-os', 'ID_LIKE': 'gentoo', + }) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_image.py b/tests/test_image.py index 464e30c..ca7abf6 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -256,8 +256,8 @@ sys.exit(result.returncode) result = subprocess.run([sys.executable, '-I', '-c', script, str(self.root / 'usr/lib/hydra'), str(manifest)], cwd=self.tmp.name, text=True, capture_output=True, timeout=20) self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn('HydraOS local recovery', result.stdout) - self.assertIn('add', result.stdout) + self.assertIn('Hydra OS', result.stdout) + self.assertIn('run', result.stdout) def test_installed_updates_exclude_unadapted_qemu_and_viewer_binaries(self): self.kernel()