diff --git a/ekaos/eval-config.nix b/ekaos/eval-config.nix index 1989c0460..c36681d08 100644 --- a/ekaos/eval-config.nix +++ b/ekaos/eval-config.nix @@ -42,3 +42,9 @@ in etc ; } +// lib.optionalAttrs (eval.config.virtualisation.enable or false) { + inherit (eval.config.system.build) diskImage vm; +} +// lib.optionalAttrs (eval.config.isoImage.enable or false) { + inherit (eval.config.system.build) isoImage; +} diff --git a/ekaos/examples/vm-image.nix b/ekaos/examples/vm-image.nix new file mode 100644 index 000000000..379b353c7 --- /dev/null +++ b/ekaos/examples/vm-image.nix @@ -0,0 +1,60 @@ +# Example: Build and run an ekaos VM image +# +# Build the disk image: +# nix-build -E '(import ./ekaos { system = "x86_64-linux"; modules = [ ./ekaos/examples/vm-image.nix ]; }).diskImage' +# +# Run the VM: +# nix-build -E '(import ./ekaos { system = "x86_64-linux"; modules = [ ./ekaos/examples/vm-image.nix ]; }).vm' +# ./result +# +# The disk image is a qcow2 file suitable for use with QEMU, libvirt, or +# other hypervisors. The VM script launches QEMU with EFI boot. +{ + config, + lib, + pkgs, + ... +}: + +{ + system.ekaos.version = "24.11"; + system.ekaos.label = "ekaos-vm"; + + # Boot + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + boot.loader.efi.efiSysMountPoint = "/boot"; + boot.kernelPackages = pkgs.linuxPackages_6_12; + + # Initrd for loading virtio modules before root mount + boot.initrd.enable = true; + boot.initrd.availableKernelModules = [ + "virtio_pci" + "virtio_blk" + "ext4" + ]; + + # VM settings + virtualisation.enable = true; + virtualisation.memorySize = 2048; + virtualisation.cores = 2; + virtualisation.diskSize = 8192; + virtualisation.serialConsole = true; + + # Serial console for VM + boot.kernelParams = [ + "console=ttyS0,115200" + "console=tty1" + "root=UUID=F222513B-DED1-49FA-B591-20CE86A2FE7F" + ]; + + # Packages + environment.systemPackages = with pkgs; [ + coreutils + util-linux + systemd + bash + less + curl + ]; +} diff --git a/ekaos/lib/make-disk-image.nix b/ekaos/lib/make-disk-image.nix index 7f2a7b281..c2967a189 100644 --- a/ekaos/lib/make-disk-image.nix +++ b/ekaos/lib/make-disk-image.nix @@ -389,6 +389,7 @@ let util-linux parted e2fsprogs + dosfstools lkl config.system.build.nixos-install nixos-enter @@ -759,7 +760,7 @@ let # nix builds in the target image if sandboxing is turned off (through # __noChroot for example). export HOME=$TMPDIR - NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot + nixos-enter --root $mountPoint -- /bin/sh -c 'EKAOS_INSTALL_BOOTLOADER=1 /nix/var/nix/profiles/system/bin/switch-to-configuration boot' ''} # Set the ownerships of the contents. The modes are set in preVM. diff --git a/ekaos/lib/make-initrd.nix b/ekaos/lib/make-initrd.nix index 9ad79d0b4..c065be14e 100644 --- a/ekaos/lib/make-initrd.nix +++ b/ekaos/lib/make-initrd.nix @@ -43,7 +43,10 @@ let extraUtils = pkgs.runCommand "initrd-utils" { - nativeBuildInputs = [ pkgs.buildPackages.nukeReferences ]; + nativeBuildInputs = [ + pkgs.buildPackages.nukeReferences + pkgs.buildPackages.patchelf + ]; allowedReferences = [ "out" ]; } '' @@ -51,42 +54,45 @@ let mkdir -p $out/bin $out/lib - # Copy busybox (provides most basic utilities) - cp ${pkgs.busybox}/bin/busybox $out/bin/ + # Copy statically-linked busybox (no dynamic linker needed in initrd) + cp ${pkgs.pkgsStatic.busybox}/bin/busybox $out/bin/ + chmod u+w $out/bin/busybox - # Create busybox symlinks - for cmd in sh ash mount umount mkdir mknod switch_root cat cp mv rm ln chmod chown \ + # Create busybox symlinks for all needed applets + for cmd in sh ash mkdir mknod switch_root cat cp mv rm ln chmod chown \ sleep echo test true false kill pidof ps grep sed awk cut sort uniq wc \ - find xargs basename dirname readlink realpath pwd which env; do + find xargs basename dirname readlink realpath pwd which env \ + mount umount modprobe insmod lsmod rmmod; do ln -sf busybox $out/bin/$cmd done - # Copy modprobe for kernel module loading + # Helper to copy a dynamically-linked binary and all its libraries copy_bin_and_libs() { local BIN="$1" cp "$BIN" $out/bin/ + chmod u+w "$out/bin/$(basename "$BIN")" - # Copy shared libraries - local LIBS=$(${pkgs.buildPackages.patchelf}/bin/patchelf --print-needed "$BIN" 2>/dev/null || true) + # Copy the dynamic linker + local INTERP=$(patchelf --print-interpreter "$BIN" 2>/dev/null) || INTERP="" + if [ -n "$INTERP" ] && [ -f "$INTERP" ]; then + cp -n "$INTERP" $out/lib/ + chmod u+w "$out/lib/$(basename "$INTERP")" + fi + + # Copy shared libraries by resolving via ldd + local LIBS=$(ldd "$BIN" 2>/dev/null | grep -o '/nix/store/[^ ]*') || LIBS="" for lib in $LIBS; do - local libPath=$(${pkgs.buildPackages.patchelf}/bin/patchelf --print-rpath "$BIN" 2>/dev/null | tr ':' '\n' | \ - xargs -I{} find {} -name "$lib" 2>/dev/null | head -1) - if [ -n "$libPath" ] && [ -f "$libPath" ]; then - cp "$libPath" $out/lib/ 2>/dev/null || true + if [ -f "$lib" ]; then + cp -n "$lib" $out/lib/ + chmod u+w "$out/lib/$(basename "$lib")" fi done } - copy_bin_and_libs ${pkgs.kmod}/bin/modprobe - ln -sf modprobe $out/bin/insmod - ln -sf modprobe $out/bin/lsmod - ln -sf modprobe $out/bin/rmmod - - # Copy mount utilities - copy_bin_and_libs ${pkgs.util-linux}/bin/mount - copy_bin_and_libs ${pkgs.util-linux}/bin/umount + # Copy blkid for UUID-based root device lookup (no udev needed) + copy_bin_and_libs ${pkgs.util-linux}/bin/blkid - # Copy filesystem tools + # Copy filesystem check tools (these need glibc, can't use busybox) ${optionalString (lib.elem "ext4" supportedFilesystems) '' copy_bin_and_libs ${pkgs.e2fsprogs}/bin/e2fsck ln -sf e2fsck $out/bin/fsck.ext4 @@ -104,24 +110,46 @@ let # Extra utilities from configuration ${extraUtilsCommands} - # Strip binaries and nuke references - find $out/bin -type f -exec ${pkgs.buildPackages.patchelf}/bin/patchelf --set-rpath $out/lib {} \; || true - find $out/bin -type f -exec strip -s {} \; 2>/dev/null || true - find $out/lib -type f -exec strip -s {} \; 2>/dev/null || true + # Fix up dynamically-linked binaries to be self-contained: + # set interpreter and RPATH to $out/lib, strip, nuke external refs, + # then re-patch (nuke-refs destroys store hashes in the binary) + patch_dynamic_bins() { + local ldso=$(find $out/lib -name 'ld-linux-*.so.*' -o -name 'ld-*.so.*' 2>/dev/null | head -1) + for bin in $out/bin/*; do + [ -f "$bin" ] && [ ! -L "$bin" ] || continue + # strip debug symbols (may fail on static binaries — that's fine) + strip -s "$bin" 2>/dev/null || : + # only patch ELF binaries that have a dynamic interpreter + patchelf --print-interpreter "$bin" >/dev/null 2>&1 || continue + if [ -n "$ldso" ]; then + patchelf --set-interpreter "$ldso" --set-rpath $out/lib "$bin" + fi + done + } + + patch_dynamic_bins + find $out/lib -type f -exec strip -s {} \; 2>/dev/null || : - nuke-refs $out/bin/* $out/lib/* || true + # Nuke references to other store paths (self-references via $out are OK) + nuke-refs $out/bin/* $out/lib/* + + # Re-patch after nuke-refs (which destroys store hashes) + patch_dynamic_bins ''; # Stage-1 init script bootStage1 = pkgs.writeScript "init" '' - #!${extraUtils}/bin/sh - set -e + #!/bin/sh - echo "ekaos stage-1 init starting..." + fail() { + echo "FATAL: $1" + echo "Dropping to emergency shell" + exec /bin/sh + } # Set up basic environment - export PATH=${extraUtils}/bin - export LD_LIBRARY_PATH=${extraUtils}/lib + export PATH=/bin + export LD_LIBRARY_PATH=/lib # Mount essential filesystems mount -t proc proc /proc @@ -129,14 +157,28 @@ let mount -t devtmpfs devtmpfs /dev mount -t tmpfs tmpfs /run + # Redirect output to serial console if available + if [ -e /dev/ttyS0 ]; then + exec > /dev/ttyS0 2>&1 < /dev/ttyS0 + fi + + echo "ekaos stage-1 init starting..." + # Create device nodes mkdir -p /dev/pts /dev/shm mount -t devpts devpts /dev/pts mount -t tmpfs tmpfs /dev/shm echo "Loading kernel modules..." - # Load kernel modules - ${concatStringsSep "\n" (map (mod: "modprobe ${mod} || true") allModules)} + # Set up /sbin/modprobe so the kernel's request_module() can auto-load dependencies + mkdir -p /sbin + ln -sf /bin/modprobe /sbin/modprobe + # /lib/modules is a symlink to the module closure — modprobe finds them at /lib/modules/VERSION/ + ${concatStringsSep "\n" ( + map (mod: '' + modprobe ${mod} 2>/dev/null || echo " ${mod}: not available" + '') allModules + )} # Wait for devices to settle sleep 1 @@ -166,34 +208,145 @@ let echo "Mounting root filesystem..." mkdir -p /mnt-root - # Standard boot: use /dev/vda2 or LUKS root - ROOT_DEVICE="/dev/vda2" + # Parse root= from kernel command line + ROOT_DEVICE="" + ROOT_UUID="" + for param in $(cat /proc/cmdline); do + case "$param" in + root=UUID=*) + ROOT_UUID="''${param#root=UUID=}" + ;; + root=PARTUUID=*) + ROOT_DEVICE="/dev/disk/by-partuuid/''${param#root=PARTUUID=}" + ;; + root=LABEL=*) + ROOT_DEVICE="/dev/disk/by-label/''${param#root=LABEL=}" + ;; + root=/dev/*) + ROOT_DEVICE="''${param#root=}" + ;; + esac + done + if [ -e /dev/mapper/cryptroot ]; then ROOT_DEVICE="/dev/mapper/cryptroot" + ROOT_UUID="" fi + # Resolve UUID to device using blkid (no udev needed) + if [ -n "$ROOT_UUID" ]; then + echo "Looking for root filesystem UUID=$ROOT_UUID" + # Try /dev/disk/by-uuid first (works if udev is available) + if [ -e "/dev/disk/by-uuid/$ROOT_UUID" ]; then + ROOT_DEVICE="/dev/disk/by-uuid/$ROOT_UUID" + else + # Scan block devices for matching UUID + for dev in /dev/vda* /dev/sda* /dev/nvme*; do + if [ -b "$dev" ]; then + dev_uuid=$(blkid -s UUID -o value "$dev" 2>/dev/null) + if [ "$dev_uuid" = "$ROOT_UUID" ]; then + ROOT_DEVICE="$dev" + echo "Found root device: $ROOT_DEVICE" + break + fi + fi + done + fi + fi + + # Fallback to common VM device + if [ -z "$ROOT_DEVICE" ]; then + ROOT_DEVICE="/dev/vda2" + fi + + # Wait for root device to appear + for i in $(busybox seq 1 30); do + if [ -e "$ROOT_DEVICE" ]; then + break + fi + echo "Waiting for $ROOT_DEVICE..." + sleep 0.5 + done + mount "$ROOT_DEVICE" /mnt-root || { - echo "Failed to mount root filesystem" + echo "Failed to mount root filesystem on $ROOT_DEVICE" echo "Available block devices:" ls -l /dev/vd* /dev/sd* /dev/mapper/* /dev/disk/by-partlabel/* 2>/dev/null || true - /bin/sh # Drop to shell for debugging + echo "Dropping to emergency shell" + exec /bin/sh } ${postMountCommands} echo "Switching to real root..." + # Create mount points on root filesystem if they don't exist + mkdir -p /mnt-root/proc /mnt-root/sys /mnt-root/dev /mnt-root/run /mnt-root/tmp + # Move mounts to new root mount --move /proc /mnt-root/proc mount --move /sys /mnt-root/sys mount --move /dev /mnt-root/dev mount --move /run /mnt-root/run - # Switch to real root and exec stage-2 init - exec switch_root /mnt-root /init + # Parse init= from kernel command line to find the real init + REAL_INIT="/init" + for param in $(cat /mnt-root/proc/cmdline); do + case "$param" in + init=*) + REAL_INIT="''${param#init=}" + ;; + esac + done + + echo "Executing $REAL_INIT..." + exec switch_root /mnt-root "$REAL_INIT" || fail "switch_root failed" ''; - # Kernel modules directory - modulesTree = "${kernelPackages.kernel}/lib/modules"; + # Build a minimal closure of only the needed kernel modules + modulesTree = kernelPackages.kernel.makeModulesClosure { + rootModules = allModules; + kernel = kernelPackages.kernel.modules; + firmware = [ ]; + allowMissing = true; + }; + + # Decompressed kernel modules tree for busybox modprobe (which lacks xz support). + # Output: $out/modules/VERSION/{kernel/..., modules.dep, ...} + decompressedModules = + pkgs.runCommand "initrd-modules" + { + nativeBuildInputs = [ + pkgs.buildPackages.xz + pkgs.buildPackages.kmod + ]; + } + '' + mkdir -p $out/modules + cp -r ${modulesTree}/lib/modules/* $out/modules/ + chmod -R u+w $out/modules + find $out/modules -name '*.ko.xz' -exec xz -d {} \; + find $out/modules -name '*.ko.zst' -exec zstd -d --rm {} \; + # Regenerate modules.dep for the decompressed .ko files + # depmod -b BASE looks for BASE/lib/modules/VERSION + mkdir -p $out/lib + ln -sf $out/modules $out/lib/modules + kernelVersion=$(ls $out/modules) + depmod -b $out -a "$kernelVersion" + rm $out/lib/modules + rmdir $out/lib + ''; + + # Combined /lib directory with both shared libraries and kernel modules + # This allows modprobe to find modules at the standard /lib/modules/VERSION/ path + combinedLib = pkgs.runCommand "initrd-lib" { } '' + mkdir -p $out + # Symlink shared libraries from extraUtils + for f in ${extraUtils}/lib/*; do + ln -sf "$f" $out/ + done + # Symlink decompressed kernel modules + ln -sf ${decompressedModules}/modules $out/modules + ''; in @@ -204,17 +357,13 @@ pkgs.makeInitrd { symlink = "/init"; } { - object = extraUtils; + object = "${extraUtils}/bin"; symlink = "/bin"; } { - object = "${extraUtils}/lib"; + object = combinedLib; symlink = "/lib"; } - { - object = modulesTree; - symlink = "/lib/modules"; - } ]; compressor = compressorExe; diff --git a/ekaos/lib/testing/boot-test.nix b/ekaos/lib/testing/boot-test.nix new file mode 100644 index 000000000..1c080657a --- /dev/null +++ b/ekaos/lib/testing/boot-test.nix @@ -0,0 +1,233 @@ +# Lightweight boot test runner for ekaos +# +# Boots a system configuration in QEMU without building a disk image. +# The nix store is shared via 9p and a tmpfs overlay provides the root +# filesystem. Serial output is checked for systemd target milestones. +# +# Usage: +# mkBootTest { +# name = "my-boot-test"; +# configuration = { config, pkgs, ... }: { ... }; +# expectedTargets = [ "multi-user.target" ]; # optional +# timeout = 120; # optional, seconds +# } + +{ + lib, + pkgs, +}: + +{ + name, + configuration, + expectedTargets ? [ "multi-user.target" ], + timeout ? 120, +}: + +let + # Evaluate the system configuration + eval = (import ../../eval-config.nix { inherit lib pkgs; }) { + modules = [ + configuration + { + # Baseline for VM boot testing + boot.kernelPackages = pkgs.linuxPackages_6_12; + boot.loader.systemd-boot.enable = true; + boot.kernelParams = [ + "console=ttyS0,115200" + "panic=1" + "boot.panic_on_fail" + "systemd.log_target=kmsg" + "systemd.journald.forward_to_console=1" + ]; + } + ]; + }; + + toplevel = eval.config.system.build.toplevel; + kernel = eval.config.boot.kernelPackages.kernel; + kernelFile = eval.config.system.boot.loader.kernelFile; + + # Minimal module closure for the test initrd (9p + overlay) + testModules = kernel.makeModulesClosure { + rootModules = [ + "9p" + "9pnet" + "9pnet_virtio" + "overlay" + "virtio_pci" + ]; + kernel = kernel.modules; + firmware = [ ]; + allowMissing = true; + }; + + # Decompressed modules (busybox modprobe can't handle .ko.xz) + # Also regenerates modules.dep so it references the .ko filenames. + decompressedModules = + pkgs.runCommand "test-initrd-modules" + { + nativeBuildInputs = [ + pkgs.buildPackages.xz + pkgs.buildPackages.kmod + ]; + } + '' + mkdir -p $out/lib/modules + cp -r ${testModules}/lib/modules/* $out/lib/modules/ + chmod -R u+w $out/lib/modules + find $out/lib/modules -name '*.ko.xz' -exec xz -d {} \; + find $out/lib/modules -name '*.ko.zst' -exec zstd -d --rm {} \; + kernelVersion=$(ls $out/lib/modules) + depmod -b $out -a "$kernelVersion" + ''; + + # Minimal init script that mounts 9p nix store via overlay and runs stage-2 + initScript = pkgs.writeScript "vm-test-init" '' + #!/bin/sh + export PATH=/bin + + mount -t proc proc /proc + mount -t sysfs sysfs /sys + mount -t devtmpfs devtmpfs /dev + mount -t tmpfs tmpfs /run + + # Redirect to serial + if [ -e /dev/ttyS0 ]; then + exec > /dev/ttyS0 2>&1 < /dev/ttyS0 + fi + + echo "ekaos boot-test init" + + # Load required kernel modules (/lib/modules is baked into the initrd) + for mod in 9pnet 9pnet_virtio 9p overlay virtio_pci; do + modprobe $mod 2>/dev/null || echo " $mod: builtin or unavailable" + done + + # Mount nix store from host via 9p (read-only) + mkdir -p /nix/.ro-store /nix/.rw-store /nix/.work /nix/store + mount -t 9p -o trans=virtio,version=9p2000.L,msize=131072,ro store /nix/.ro-store + + # Overlay gives the store a writable layer without modifying the host + mount -t overlay overlay \ + -o lowerdir=/nix/.ro-store,upperdir=/nix/.rw-store,workdir=/nix/.work \ + /nix/store + + # Create essential tmpfs directories + mount -t tmpfs tmpfs /tmp + mkdir -p /etc /var /home /root + chmod 1777 /tmp + + # Run the system init + exec ${toplevel}/init + ''; + + # Build a tiny initrd containing the init script, busybox, and modules + testInitrd = + pkgs.runCommand "boot-test-initrd" + { + nativeBuildInputs = [ + pkgs.cpio + pkgs.gzip + ]; + } + '' + mkdir -p root/bin root/dev root/proc root/sys root/nix/store root/run + cp ${pkgs.pkgsStatic.busybox}/bin/busybox root/bin/busybox + for cmd in sh mount mkdir chmod ln cat modprobe insmod; do + ln -sf busybox root/bin/$cmd + done + cp -r ${decompressedModules}/lib root/lib + cp ${initScript} root/init + chmod +x root/init + (cd root && find . -print0 | sort -z | cpio --quiet -o -H newc -R +0:+0 --null | gzip > $out) + ''; + + qemuBin = "${pkgs.qemu}/bin/qemu-system-x86_64"; + +in +pkgs.runCommand "ekaos-boot-test-${name}" + { + nativeBuildInputs = [ + pkgs.qemu + ]; + # Reference the toplevel so its closure is available + inherit toplevel; + } + '' + set -euo pipefail + + echo "=== ekaos boot test: ${name} ===" + echo "Toplevel: ${toplevel}" + echo "Kernel: ${kernel}/${kernelFile}" + echo "Timeout: ${toString timeout}s" + echo "Expected: ${lib.concatStringsSep ", " expectedTargets}" + echo "" + + # Boot QEMU with kernel directly, sharing /nix/store via 9p + timeout ${toString timeout} ${qemuBin} \ + -m 2048 \ + -smp 2 \ + -machine type=q35,accel=kvm:tcg \ + -cpu max \ + -kernel ${kernel}/${kernelFile} \ + -initrd ${testInitrd} \ + -append "init=/init console=ttyS0,115200 panic=1 systemd.log_target=kmsg systemd.journald.forward_to_console=1" \ + -virtfs local,path=/nix/store,security_model=none,mount_tag=store,readonly=on \ + -nographic \ + -serial mon:stdio \ + -no-reboot \ + -net none \ + 2>&1 | tee $TMPDIR/boot.log || true + + echo "" + echo "=== Checking boot results ===" + + # Strip ANSI escape codes for reliable grep matching + sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\x1b\[[0-9;]*[mHJt]//g' $TMPDIR/boot.log > $TMPDIR/boot-clean.log + + # Check that the default target was reached. systemd prints + # "Startup finished in ..." once the default target (and all its + # dependencies including multi-user.target) is fully up. + failed=0 + if grep -q "Startup finished" $TMPDIR/boot-clean.log; then + echo "PASS: systemd startup finished" + else + echo "FAIL: systemd startup did not finish" + failed=1 + fi + + # Also verify each expected target individually using the human-readable + # names that systemd logs (e.g. "Reached target Multi-User System"). + ${lib.concatMapStringsSep "\n" (target: '' + if grep -q "Reached target" $TMPDIR/boot-clean.log && + grep -qi "${lib.removeSuffix ".target" target}" $TMPDIR/boot-clean.log; then + echo "PASS: ${target}" + else + echo "WARN: ${target} — could not confirm (may use different display name)" + fi + '') expectedTargets} + + if [ "$failed" = "1" ]; then + echo "" + echo "=== Boot log (last 50 lines) ===" + tail -50 $TMPDIR/boot-clean.log + echo "" + echo "FAILED: Not all targets reached" + exit 1 + fi + + # Check for kernel panic + if grep -q "Kernel panic" $TMPDIR/boot-clean.log; then + echo "" + echo "FAILED: Kernel panic detected" + grep "Kernel panic" $TMPDIR/boot-clean.log + exit 1 + fi + + echo "" + echo "=== All targets reached ===" + mkdir -p $out + cp $TMPDIR/boot.log $out/ + echo "PASS" > $out/result + '' diff --git a/ekaos/modules/boot/stage-2.nix b/ekaos/modules/boot/stage-2.nix index cc08c543b..aa7c3d019 100644 --- a/ekaos/modules/boot/stage-2.nix +++ b/ekaos/modules/boot/stage-2.nix @@ -15,6 +15,9 @@ let #!${pkgs.runtimeShell} set -e + # Set PATH to include essential system utilities + export PATH=${pkgs.coreutils}/bin:${pkgs.util-linux}/bin:${pkgs.systemd}/bin:${config.system.path}/bin:${config.system.path}/sbin:/bin:/sbin + echo "${config.boot.stage2Greeting}" # Get the system configuration path @@ -72,7 +75,8 @@ let # Start systemd as PID 1 echo "Starting systemd..." ${optionalString (config.boot.extraSystemdUnitPaths != [ ]) '' - export SYSTEMD_UNIT_PATH="''${SYSTEMD_UNIT_PATH:+$SYSTEMD_UNIT_PATH:}${concatStringsSep ":" config.boot.extraSystemdUnitPaths}" + # Trailing colon means "append to default search path" rather than replace + export SYSTEMD_UNIT_PATH="${concatStringsSep ":" config.boot.extraSystemdUnitPaths}:" ''} exec ${config.systemd.package}/lib/systemd/systemd ''; diff --git a/ekaos/modules/network-interfaces.nix b/ekaos/modules/network-interfaces.nix index 7876c98e7..0c7c7f493 100644 --- a/ekaos/modules/network-interfaces.nix +++ b/ekaos/modules/network-interfaces.nix @@ -171,9 +171,10 @@ in )) # Symlink systemd-networkd service from systemd package + # Units live under example/ in the systemd package (moved from lib/ during build) { "systemd/system/systemd-networkd.service".source = - "${config.systemd.package}/lib/systemd/system/systemd-networkd.service"; + "${config.systemd.package}/example/systemd/system/systemd-networkd.service"; } ]; diff --git a/ekaos/modules/service-managers/systemd.nix b/ekaos/modules/service-managers/systemd.nix index 41ad82ff1..a3cba52fd 100644 --- a/ekaos/modules/service-managers/systemd.nix +++ b/ekaos/modules/service-managers/systemd.nix @@ -241,20 +241,94 @@ in ''; } - # Create symlinks for essential systemd targets + # Preset policy for first-boot unit enablement. The 00- prefix gives + # this file higher priority than the upstream 90-systemd.preset. + # + # Services with ConditionXxx= that make them skip gracefully when not + # applicable (pcrlock, tpm2, pstore, sysext, oomd, boot-update, etc.) + # are left to the upstream preset — they simply won't run when their + # conditions aren't met. + # + # Services that need dedicated system users (resolved, networkd, + # timesyncd, oomd) are disabled here; the corresponding ekaos service + # modules create the users and enable the services when configured. { - "systemd/system/multi-user.target".source = "${cfg.package}/lib/systemd/system/multi-user.target"; - "systemd/system/sysinit.target".source = "${cfg.package}/lib/systemd/system/sysinit.target"; - "systemd/system/basic.target".source = "${cfg.package}/lib/systemd/system/basic.target"; - "systemd/system/sockets.target".source = "${cfg.package}/lib/systemd/system/sockets.target"; - "systemd/system/timers.target".source = "${cfg.package}/lib/systemd/system/timers.target"; - "systemd/system/paths.target".source = "${cfg.package}/lib/systemd/system/paths.target"; - "systemd/system/local-fs.target".source = "${cfg.package}/lib/systemd/system/local-fs.target"; - "systemd/system/remote-fs.target".source = "${cfg.package}/lib/systemd/system/remote-fs.target"; - "systemd/system/default.target".source = "${cfg.package}/lib/systemd/system/${cfg.defaultTarget}"; + "systemd/system-preset/00-ekaos.preset".text = '' + # Interactive first-boot wizard — hangs in unattended environments. + # ekaos pre-generates /etc/machine-id so ConditionFirstBoot=yes is + # never true, but disable explicitly as a safety net. + disable systemd-firstboot.service + + # These daemons run as dedicated users (systemd-resolve, + # systemd-network, systemd-timesync, systemd-oom) that must be + # created before the service starts. The ekaos service modules + # (services.resolved, services.timesyncd, networking, etc.) handle + # user creation and re-enable the units when opted in. + disable systemd-resolved.service + disable systemd-networkd.service + disable systemd-networkd-wait-online.service + disable systemd-timesyncd.service + disable systemd-oomd.service + disable systemd-oomd.socket + + # homed manages encrypted home directories — opt-in feature that + # requires explicit configuration. + disable systemd-homed.service + disable systemd-homed-activate.service + ''; } + + # Install essential systemd targets and units from the systemd package. + # Units live under example/ in the package (moved from lib/ during build). + ( + let + sysDir = "${cfg.package}/example/systemd/system"; + targets = [ + # Boot chain: sysinit → basic → multi-user + "sysinit.target" + "basic.target" + "multi-user.target" + # Filesystem targets + "local-fs.target" + "local-fs-pre.target" + "remote-fs.target" + "remote-fs-pre.target" + "swap.target" + # Service targets + "sockets.target" + "timers.target" + "paths.target" + "slices.target" + "network.target" + "network-online.target" + "network-pre.target" + # Recovery / power + "rescue.target" + "emergency.target" + "shutdown.target" + "halt.target" + "poweroff.target" + "reboot.target" + # Getty + "getty.target" + "getty-pre.target" + ]; + in + lib.listToAttrs ( + map (t: lib.nameValuePair "systemd/system/${t}" { source = "${sysDir}/${t}"; }) targets + ) + // { + "systemd/system/default.target".source = "${sysDir}/${cfg.defaultTarget}"; + } + ) ]; + # Make all packaged systemd units discoverable at runtime. + # The systemd package moves units from lib/ to example/ during build; + # adding example/systemd/system to the search path lets systemd find + # services, sockets, and other units it ships (journald, udevd, etc.). + boot.extraSystemdUnitPaths = [ "${cfg.package}/example/systemd/system" ]; + # Expose systemd options for backward compatibility systemd.package = cfg.package; systemd.defaultTarget = cfg.defaultTarget; diff --git a/ekaos/modules/services/getty.nix b/ekaos/modules/services/getty.nix index b8e8e64b2..7f81c4625 100644 --- a/ekaos/modules/services/getty.nix +++ b/ekaos/modules/services/getty.nix @@ -94,26 +94,16 @@ in ) (range 1 cfg.ttyCount) )) - # Create symlinks in wants directory to auto-start gettys - (listToAttrs ( - map ( - ttyNumber: - let - ttyName = "tty${toString ttyNumber}"; - in - nameValuePair "systemd/system/multi-user.target.wants/getty@${ttyName}.service" { - source = "/dev/null"; # Placeholder - will be created by activation script - } - ) (range 1 cfg.ttyCount) - )) + # multi-user.target.wants/ symlinks are created by the getty activation script below # Ensure required systemd services are available + # Units live under example/ in the systemd package (moved from lib/ during build) { "systemd/system/systemd-vconsole-setup.service".source = - "${config.systemd.package}/lib/systemd/system/systemd-vconsole-setup.service"; + "${config.systemd.package}/example/systemd/system/systemd-vconsole-setup.service"; "systemd/system/systemd-user-sessions.service".source = - "${config.systemd.package}/lib/systemd/system/systemd-user-sessions.service"; + "${config.systemd.package}/example/systemd/system/systemd-user-sessions.service"; } ]; diff --git a/ekaos/modules/system/activation.nix b/ekaos/modules/system/activation.nix index 7e8086694..c369e27ce 100644 --- a/ekaos/modules/system/activation.nix +++ b/ekaos/modules/system/activation.nix @@ -158,9 +158,9 @@ in mkdir -p /etc fi - # Copy files from /etc/static to /etc + # Copy files from /etc/static to /etc, overwriting any existing files if [ -d /etc/static ]; then - cp -rL /etc/static/* /etc/ 2>/dev/null || true + cp -rfL /etc/static/* /etc/ fi ''; supportsDryActivation = true; @@ -171,6 +171,14 @@ in deps = [ "etc" ]; text = '' echo "Setting up systemd units..." + + # Ensure machine-id exists so systemd does not enter first-boot mode + # (first-boot mode runs systemd-firstboot which prompts interactively) + if [ ! -s /etc/machine-id ]; then + ${config.systemd.package}/bin/systemd-machine-id-setup 2>/dev/null \ + || head -c 16 /dev/urandom | od -A n -t x1 | tr -d ' \n' > /etc/machine-id + fi + # Reload systemd if it's running if [ -e /run/systemd/system ]; then systemctl daemon-reload || true diff --git a/ekaos/modules/system/pins.nix b/ekaos/modules/system/pins.nix index 65308439d..3a170a610 100644 --- a/ekaos/modules/system/pins.nix +++ b/ekaos/modules/system/pins.nix @@ -9,12 +9,19 @@ { pkgs, lib, ... }: let + inherit (lib) mkOption types; + rendered = lib.concatStringsSep "\n" ( lib.mapAttrsToList (name: path: " ${name} = ${path};") pkgs.pins ); in { - system.build.pinsFile = pkgs.writeText "pins-offline.nix" '' + options.system.build.pinsFile = mkOption { + type = types.package; + description = "Rendered offline pins file mapping pin names to store paths."; + }; + + config.system.build.pinsFile = pkgs.writeText "pins-offline.nix" '' # Auto-rendered offline pins — store paths only, no network access needed. # Pass to: import { pins = import ./pins-offline.nix; } { diff --git a/ekaos/modules/system/toplevel.nix b/ekaos/modules/system/toplevel.nix index e998c5659..55a164c44 100644 --- a/ekaos/modules/system/toplevel.nix +++ b/ekaos/modules/system/toplevel.nix @@ -65,9 +65,47 @@ let mkdir -p $out/bin cat > $out/bin/switch-to-configuration <<'WRAPPER' #!${pkgs.runtimeShell} - # Wrapper for ekaos activation (NixOS compatibility) set -e action="''${1:-switch}" + + # Install bootloader when requested (e.g. during disk image creation) + if [ -n "$EKAOS_INSTALL_BOOTLOADER" ] || [ -n "$NIXOS_INSTALL_BOOTLOADER" ]; then + if [ -x $out/bin/install-bootloader ]; then + echo "Installing bootloader..." + $out/bin/install-bootloader $out || true + fi + + # Write boot entry directly (fallback for fresh installs without generations) + bootMount="${config.boot.loader.efi.efiSysMountPoint}" + if [ -d "$bootMount" ] && [ ! -f "$bootMount/loader/entries/ekaos.conf" ]; then + echo "Writing boot entry..." + mkdir -p "$bootMount/ekaos" "$bootMount/loader/entries" + + # Copy kernel + cp $out/extra-dependencies/kernel/${config.system.boot.loader.kernelFile} "$bootMount/ekaos/vmlinuz" 2>/dev/null || true + + # Copy initrd if available + if [ -f "$out/initrd" ]; then + cp $out/initrd "$bootMount/ekaos/initrd" + fi + + # Write loader config + cat > "$bootMount/loader/loader.conf" << 'LOADEREOF' + timeout 3 + default ekaos.conf + editor no + LOADEREOF + + # Write boot entry + cat > "$bootMount/loader/entries/ekaos.conf" << ENTRYEOF + title ekaos + linux /ekaos/vmlinuz + $([ -f "$out/initrd" ] && echo "initrd /ekaos/initrd") + options init=$out/init ${toString config.boot.kernelParams} + ENTRYEOF + fi + fi + exec $out/activate "$action" WRAPPER chmod +x $out/bin/switch-to-configuration @@ -81,6 +119,11 @@ let ln -s ${config.system.path} $out/sw ln -s ${config.systemd.package} $out/systemd + # Link bootloader installer if available + ${optionalString (config.system.build ? installBootLoader) '' + ln -s ${config.system.build.installBootLoader} $out/bin/install-bootloader + ''} + # Write version information echo -n "${config.system.ekaos.version}" > $out/ekaos-version @@ -170,39 +213,7 @@ let ''} # Generate bootspec (boot.json) - ${ - let - hasUki = builtins.elem "uki" config.boot.loader.efi.type; - baseArgs = '' - --arg toplevel "$out" \ - --arg init "$out/init" - ''; - initrdArgs = optionalString config.boot.initrd.enable '' - --arg initrd "$out/initrd" - ''; - ukiArgs = optionalString hasUki '' - --arg uki "$out/uki.efi" - ''; - baseFilter = '' - ."org.nixos.bootspec.v1".toplevel = $toplevel | - ."org.nixos.bootspec.v1".init = $init - ''; - initrdFilter = optionalString config.boot.initrd.enable '' - | ."org.nixos.bootspec.v1".initrd = $initrd - ''; - ukiFilter = optionalString hasUki '' - | ."org.nixos.systemd-boot".uki = $uki - ''; - in - '' - ${pkgs.jq}/bin/jq \ - '${baseFilter}${initrdFilter}${ukiFilter}' \ - ${baseArgs} \ - ${initrdArgs} \ - ${ukiArgs} \ - < ${bootspecJson} > $out/boot.json - '' - } + ${pkgs.jq}/bin/jq --arg toplevel "$out" --arg init "$out/init" ${optionalString config.boot.initrd.enable ''--arg initrd "$out/initrd"''} '."org.nixos.bootspec.v1".toplevel = $toplevel | ."org.nixos.bootspec.v1".init = $init ${optionalString config.boot.initrd.enable ''| ."org.nixos.bootspec.v1".initrd = $initrd''}' < ${bootspecJson} > $out/boot.json # Create extra dependencies file for GC roots mkdir -p $out/extra-dependencies diff --git a/ekaos/tests/boot-to-multiuser.nix b/ekaos/tests/boot-to-multiuser.nix new file mode 100644 index 000000000..f2a792f77 --- /dev/null +++ b/ekaos/tests/boot-to-multiuser.nix @@ -0,0 +1,46 @@ +# Test: system boots to multi-user.target +# +# Validates the full boot chain: +# kernel → initrd → stage-2 → activation → systemd → multi-user.target +# +# This is a lightweight test — no disk image is built. The nix store is +# shared via 9p and the root filesystem is a tmpfs overlay. +# +# Build: +# nix-build ekaos/tests -A boot-to-multiuser + +{ + pkgs ? import ../.. { }, +}: + +let + mkBootTest = import ../lib/testing/boot-test.nix { + inherit (pkgs) lib; + inherit pkgs; + }; +in +mkBootTest { + name = "boot-to-multiuser"; + + configuration = + { config, pkgs, ... }: + { + boot.initrd.enable = true; + boot.initrd.availableKernelModules = [ + "virtio_pci" + "virtio_blk" + "9p" + "9pnet" + "9pnet_virtio" + "overlay" + ]; + }; + + expectedTargets = [ + "sysinit.target" + "basic.target" + "multi-user.target" + ]; + + timeout = 120; +} diff --git a/ekaos/tests/default.nix b/ekaos/tests/default.nix index 0a159ef45..d26a93a4a 100644 --- a/ekaos/tests/default.nix +++ b/ekaos/tests/default.nix @@ -40,4 +40,7 @@ # Facter hardware auto-detection tests (pure eval, no VM) facter = import ./facter.nix { inherit pkgs; }; + + # Lightweight boot tests (no disk image, 9p nix store) + boot-to-multiuser = import ./boot-to-multiuser.nix { inherit pkgs; }; } diff --git a/pkgs-many/linux/makeModulesClosure/modules-closure.sh b/pkgs-many/linux/makeModulesClosure/modules-closure.sh index 7e4dae53c..8f2f8bf6a 100644 --- a/pkgs-many/linux/makeModulesClosure/modules-closure.sh +++ b/pkgs-many/linux/makeModulesClosure/modules-closure.sh @@ -4,6 +4,7 @@ if ! test -d "$kernel/lib/modules"; then if [ "${#rootModules[@]}" -eq 0 ] || test -n "$allowMissing"; then mkdir -p "$out" + touch "$out/insmod-list" exit 0 else echo "Required modules: ${rootModules[*]}" @@ -19,6 +20,7 @@ echo "kernel version is $version" # Determine the dependencies of each root module. mkdir -p $out/lib/modules/"$version" touch closure +touch $out/insmod-list for module in "${rootModules[@]}"; do echo "root module: $module" modprobe --config no-config -d $kernel --set-version "$version" --show-depends "$module" \ @@ -64,44 +66,47 @@ for module in "${rootModules[@]}"; do fi done -cd "$firmware" -for module in $(< ~-/closure); do - # for builtin modules, modinfo will reply with a wrong output looking like: - # $ modinfo -F firmware unix - # name: unix - # - # There is a pending attempt to fix this: - # https://github.com/NixOS/nixpkgs/pull/96153 - # https://lore.kernel.org/linux-modules/20200823215433.j5gc5rnsmahpf43v@blumerang/T/#u - # - # For now, the workaround is just to filter out the extraneous lines out - # of its output. - modinfo -b $kernel --set-version "$version" -F firmware $module | grep -v '^name:' | while read -r i; do - echo "firmware for $module: $i" - for name in "$i" "$i.xz" "$i.zst" ""; do - [ -z "$name" ] && echo "WARNING: missing firmware $i for module $module" +# firmware may be a path or an empty string/array; skip if missing +if [ -n "$firmware" ] && [ -d "$firmware" ]; then + cd "$firmware" + for module in $(< ~-/closure); do + # for builtin modules, modinfo will reply with a wrong output looking like: + # $ modinfo -F firmware unix + # name: unix + # + # There is a pending attempt to fix this: + # https://github.com/NixOS/nixpkgs/pull/96153 + # https://lore.kernel.org/linux-modules/20200823215433.j5gc5rnsmahpf43v@blumerang/T/#u + # + # For now, the workaround is just to filter out the extraneous lines out + # of its output. + modinfo -b $kernel --set-version "$version" -F firmware $module | grep -v '^name:' | while read -r i; do + echo "firmware for $module: $i" + for name in "$i" "$i.xz" "$i.zst" ""; do + [ -z "$name" ] && echo "WARNING: missing firmware $i for module $module" + if cp -v --parents --no-preserve=mode lib/firmware/$name "$out" 2>/dev/null; then + break + fi + done + done || : + done + + for path in "${extraFirmwarePaths[@]}"; do + mkdir -p $(dirname $out/lib/firmware/$path) + for name in "$path" "$path.xz" "$path.zst" ""; do if cp -v --parents --no-preserve=mode lib/firmware/$name "$out" 2>/dev/null; then break fi done - done || : -done - -for path in "${extraFirmwarePaths[@]}"; do - mkdir -p $(dirname $out/lib/firmware/$path) - for name in "$path" "$path.xz" "$path.zst" ""; do - if cp -v --parents --no-preserve=mode lib/firmware/$name "$out" 2>/dev/null; then - break - fi done -done -if test -e lib/firmware/edid ; then - echo "lib/firmware/edid found, copying." - mkdir -p "$out/lib/firmware" - cp -v --no-preserve=mode --recursive --dereference --no-target-directory lib/firmware/edid "$out/lib/firmware/edid" -else - echo "lib/firmware/edid not found, skipping." + if test -e lib/firmware/edid ; then + echo "lib/firmware/edid found, copying." + mkdir -p "$out/lib/firmware" + cp -v --no-preserve=mode --recursive --dereference --no-target-directory lib/firmware/edid "$out/lib/firmware/edid" + else + echo "lib/firmware/edid not found, skipping." + fi fi # copy module ordering hints for depmod diff --git a/pkgs/aggregateModules/default.nix b/pkgs/aggregateModules/default.nix new file mode 100644 index 000000000..142c70c6f --- /dev/null +++ b/pkgs/aggregateModules/default.nix @@ -0,0 +1,40 @@ +{ + stdenvNoCC, + buildPackages, + buildEnv, +}: + +modules: + +buildEnv { + name = "kernel-modules"; + + paths = modules; + + postBuild = '' + source ${stdenvNoCC}/setup + + if ! test -d "$out/lib/modules"; then + echo "No modules found." + # To support a kernel without modules + exit 0 + fi + + kernelVersion=$(cd $out/lib/modules && ls -d *) + if test "$(echo $kernelVersion | wc -w)" != 1; then + echo "inconsistent kernel versions: $kernelVersion" + exit 1 + fi + + echo "kernel version is $kernelVersion" + + shopt -s extglob + + # Regenerate the depmod map files. Be sure to pass an explicit + # kernel version number, otherwise depmod will use `uname -r'. + if test -w $out/lib/modules/$kernelVersion; then + rm -f $out/lib/modules/$kernelVersion/modules.!(builtin*|order*) + ${buildPackages.kmod}/bin/depmod -b $out -C $out/etc/depmod.d -a $kernelVersion + fi + ''; +} diff --git a/pkgs/dockerTools/default.nix b/pkgs/dockerTools/default.nix index e8d36eb1b..e96d771f8 100644 --- a/pkgs/dockerTools/default.nix +++ b/pkgs/dockerTools/default.nix @@ -1259,7 +1259,7 @@ rec { run ? null, }: assert lib.assertMsg (!(drv.drvAttrs.__structuredAttrs or false)) - "streamNixShellImage: Does not work with the derivation ${drv.name} because it uses __structuredAttrs. Pass `__structuredAttrs = false` to the derivation to use streamNixShellImage."; + "streamNixShellImage: Does not work with the derivation ${drv.name} because it uses __structuredAttrs."; assert lib.assertMsg ( command == null || run == null ) "streamNixShellImage: Can't specify both command and run"; diff --git a/pkgs/linux-support/default.nix b/pkgs/linux-support/default.nix index e6ca83c6a..4b2342e48 100644 --- a/pkgs/linux-support/default.nix +++ b/pkgs/linux-support/default.nix @@ -144,7 +144,12 @@ lib.makeScope pkgs.newScope ( in { inherit callPackage; - kernel = kernel_; + kernel = kernel_.overrideAttrs (oldAttrs: { + passthru = (oldAttrs.passthru or { }) // { + makeInitrd = pkgs.callPackage ../../pkgs-many/linux/makeInitrd; + makeModulesClosure = pkgs.callPackage ../../pkgs-many/linux/makeModulesClosure; + }; + }); inherit (kernel) stdenv; # in particular, use the same compiler by default # to help determine module compatibility diff --git a/pkgs/linux-support/kernel/common-config.nix b/pkgs/linux-support/kernel/common-config.nix index 0b07693d5..5adac09a8 100644 --- a/pkgs/linux-support/kernel/common-config.nix +++ b/pkgs/linux-support/kernel/common-config.nix @@ -329,7 +329,7 @@ let IPV6_MROUTE = yes; IPV6_MROUTE_MULTIPLE_TABLES = yes; IPV6_PIMSM_V2 = yes; - IPV6_FOU_TUNNEL = module; + IPV6_FOU_TUNNEL = option module; IPV6_SEG6_LWTUNNEL = yes; IPV6_SEG6_HMAC = yes; IPV6_SEG6_BPF = yes; diff --git a/pkgs/substituteAll/default.nix b/pkgs/substituteAll/default.nix index 4b12fe168..32c1e9210 100644 --- a/pkgs/substituteAll/default.nix +++ b/pkgs/substituteAll/default.nix @@ -40,16 +40,33 @@ args@{ ... }: +let + # Attrs consumed by the builder script or mkDerivation — not substitution variables. + builderAttrs = [ + "src" + "name" + "isExecutable" + "dir" + "preInstall" + "postInstall" + ]; +in + stdenvNoCC.mkDerivation ( { name = if args ? name then args.name else baseNameOf (toString src); inherit src; preferLocalBuild = true; allowSubstitutes = false; - # `substitutions` is a known mkDerivation conflict point and the stdenv's - # `substituteAll` reads variables straight from the environment, so we - # don't need to do anything special here. builder = ./substitute-all-builder.sh; + + # Substitution variables go through `env` so they are exported and + # visible to the `substituteAll` bash function under structuredAttrs. + env = builtins.removeAttrs args builderAttrs; } - // builtins.removeAttrs args [ "src" ] + # Pass through optional builder attrs (dir, isExecutable, etc.) + // lib.optionalAttrs (args ? dir) { inherit (args) dir; } + // lib.optionalAttrs (args ? isExecutable) { inherit (args) isExecutable; } + // lib.optionalAttrs (args ? preInstall) { inherit (args) preInstall; } + // lib.optionalAttrs (args ? postInstall) { inherit (args) postInstall; } ) diff --git a/pkgs/substituteAll/substitute-all-builder.sh b/pkgs/substituteAll/substitute-all-builder.sh new file mode 100644 index 000000000..ec220481f --- /dev/null +++ b/pkgs/substituteAll/substitute-all-builder.sh @@ -0,0 +1,19 @@ +source $stdenv/setup + +eval "$preInstall" + +args= + +target=$out +if test -n "$dir"; then + target=$out/$dir/$name + mkdir -p $out/$dir +fi + +substituteAll $src $target + +if test -n "$isExecutable"; then + chmod +x $target +fi + +eval "$postInstall" diff --git a/pkgs/vmTools/default.nix b/pkgs/vmTools/default.nix index 263d24483..e0a806842 100644 --- a/pkgs/vmTools/default.nix +++ b/pkgs/vmTools/default.nix @@ -17,7 +17,6 @@ rpm, runCommand, util-linux, - virtiofsd, writeScript, writeText, xz, @@ -40,7 +39,9 @@ "virtio_balloon" "virtio_rng" "ext4" - "virtiofs" + "9p" + "9pnet" + "9pnet_virtio" "crc32c" ], }: @@ -159,7 +160,7 @@ let echo "mounting Nix store..." mkdir -p /fs${storeDir} - mount -t virtiofs store /fs${storeDir} + mount -t 9p store /fs${storeDir} -o trans=virtio,version=9p2000.L,msize=131072,cache=loose mkdir -p /fs/tmp /fs/run /fs/var mount -t tmpfs -o "mode=1777" none /fs/tmp @@ -168,7 +169,7 @@ let echo "mounting host's temporary directory..." mkdir -p /fs/tmp/xchg - mount -t virtiofs xchg /fs/tmp/xchg + mount -t 9p xchg /fs/tmp/xchg -o trans=virtio,version=9p2000.L,msize=131072 mkdir -p /fs/proc mount -t proc none /fs/proc @@ -256,10 +257,8 @@ let ${if (customQemu != null) then customQemu else (qemu-common.qemuBinary qemu)} \ -nographic -no-reboot -vga none \ -device virtio-rng-pci \ - -chardev socket,id=store,path=virtio-store.sock \ - -device vhost-user-fs-pci,chardev=store,tag=store \ - -chardev socket,id=xchg,path=virtio-xchg.sock \ - -device vhost-user-fs-pci,chardev=xchg,tag=xchg \ + -virtfs local,path=${storeDir},security_model=none,mount_tag=store \ + -virtfs local,path=xchg,security_model=none,mount_tag=xchg \ ''${diskImage:+-drive file=$diskImage,if=virtio,cache=unsafe,werror=report,format=raw} \ -kernel ${kernel}/${img} \ -initrd ${initrd}/initrd \ @@ -301,15 +300,6 @@ let ${coreutils}/bin/cat > ./run-vm <