From 78d7233ff3190e1a11ce1c2a05cd4b4096367f00 Mon Sep 17 00:00:00 2001 From: Artur Gurgul Date: Mon, 11 Aug 2025 17:36:55 +0200 Subject: [PATCH] Working version of QEMU configuration --- lib/downloader.rb | 2 +- lib/virtual-machine.rb | 12 +++- lib/vm/qemu.rb | 126 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 lib/vm/qemu.rb diff --git a/lib/downloader.rb b/lib/downloader.rb index 462be6e..3ca652a 100644 --- a/lib/downloader.rb +++ b/lib/downloader.rb @@ -23,7 +23,7 @@ module Downloader puts "File does not exist, downloading..." URI.open(uri) do |input| File.open(path, 'wb') do |output| - IO.copy_stream(input, output) + IO.copy_stream(input, output) end end end diff --git a/lib/virtual-machine.rb b/lib/virtual-machine.rb index 44dae19..2f5476d 100644 --- a/lib/virtual-machine.rb +++ b/lib/virtual-machine.rb @@ -1,6 +1,7 @@ require 'downloader' require 'system' require_relative 'data/resources/iso-images' +require 'vm/qemu' module VirtualMachine def self.distro(name, arch, type = :install) @@ -27,14 +28,23 @@ module VirtualMachine Downloader.get(url) do |path| disk_img_path = File.join(User.cache_path, "vm", distro.to_s, arch.to_s, options[:name], "root.img") - create_disk_image(disk_img_path, 500) + create_disk_image(disk_img_path, 5000) puts path puts disk_img_path + + Qemu.launch( + arch, + disk_img_path, + cpus: [1, System.cpus - 2].max, + cdrom: path, + detach: false + ) end end # size in MB + # lsof /Users/artur/.cache/dat/vm/debian/arm64/debian/root.img def self.create_disk_image(path, size) size_in_bytes = 1024 * 1024 * size diff --git a/lib/vm/qemu.rb b/lib/vm/qemu.rb new file mode 100644 index 0000000..905e56a --- /dev/null +++ b/lib/vm/qemu.rb @@ -0,0 +1,126 @@ +require "fileutils" +require "ostruct" + +module Qemu + def self.qemu_bin_for(arch) + { + x86_64: "qemu-system-x86_64", + x86_32: "qemu-system-i386", + arm64: "qemu-system-aarch64", + armv7: "qemu-system-arm", + armv6: "qemu-system-arm", + armv5: "qemu-system-arm", + riscv64: "qemu-system-riscv64", + ppc64le: "qemu-system-ppc64", + ppc64: "qemu-system-ppc64", + ppc32: "qemu-system-ppc", + s390x: "qemu-system-s390x", + mips64el: "qemu-system-mips64el", + mips64: "qemu-system-mips64", + mipsel: "qemu-system-mipsel", + mips: "qemu-system-mips", + loongarch64: "qemu-system-loongarch64", + sparc64: "qemu-system-sparc64", + alpha: "qemu-system-alpha", + hppa: "qemu-system-hppa", + ia64: "qemu-system-ia64", + }.fetch(arch) { raise "Unsupported arch: #{arch.inspect}" } + end + + def self.accel_args + host = RbConfig::CONFIG["host_os"] + if host =~ /linux/i && File.exist?("/dev/kvm") + ["-accel", "kvm"] + elsif host =~ /darwin/i + # hvf exists on Apple Silicon + Intel macOS; QEMU falls back if not available + ["-accel", "hvf"] + elsif host =~ /freebsd/i + # if QEMU was built with it; otherwise it will ignore + ["-accel", "bhyve"] + else + ["-accel", "tcg"] + end + end + + def self.machine_args_for(arch) + case arch + when :x86_64, :x86_32 + [] + when :arm64 + # -machine type=virt + # -cpu cortex-a72 + ["-machine", "virt", "-cpu", "max"] + when :armv7, :armv6, :armv5 + ["-machine", "virt", "-cpu", "cortex-a15"] + when :riscv64 + ["-machine", "virt"] + when :loongarch64 + ["-machine", "virt"] + else + [] + end + end + + #def self.launch(arch, disk_path, cdrom = nil, detach = true) + def self.launch(arch, disk_path, **options) + defaults = { + arch: System::ARCH, + cdrom: nil, + detach: true, + ram: 2048, + cpus: 1 + } + opts = defaults.merge(options) + + puts opts + + qemu = qemu_bin_for(arch) + args = [] + + if System::OS == :macos && arch == :arm64 + # args += ["-bios", "/opt/homebrew/share/qemu/edk2-aarch64-code.fd"] + args += ["-drive", "if=pflash,format=raw,unit=0,readonly=on,file=/opt/homebrew/share/qemu/edk2-aarch64-code.fd"] + + #args += ["-device", "virtio-gpu-device"] + args += ["-display", "cocoa"] + args += ["-device", "qemu-xhci,id=xhci"] + args += ["-device", "usb-kbd"] + args += ["-device", "usb-tablet"] + + args += ["-device", "virtio-keyboard-device"] + args += ["-device", "virtio-mouse-device"] + args += ["-device", "virtio-gpu"] + + + # copy to /Users/artur/.cache/dat/vm/debian/arm64/debian/vars.fd + # /opt/homebrew/share/qemu/edk2-aarch64-vars.fd + # args += ["-drive", "if=pflash,format=raw,unit=1,file=/Users/artur/.cache/dat/vm/debian/arm64/debian/vars.fd"] + end + + args += accel_args + args += machine_args_for(arch) + args += ["-m", opts[:ram].to_s, "-smp", opts[:cpus].to_s] + args += ["-name", "FirstVM", "-boot", "order=d"] # boot from CD first + args += ["-drive", "file=#{disk_path},if=virtio,cache=writeback,format=raw"] + if opts[:cdrom] != nil + args += ["-cdrom", opts[:cdrom]] + end + args += ["-device", "virtio-net,netdev=n0", "-netdev", "user,id=n0"] # user-mode NAT + # optional: uncomment to run headless with VNC on :5901 + # args += ["-display", "none", "-vnc", "127.0.0.1:1"] + + cmd = [qemu, *args] + + puts "Launching: #{cmd.join(' ')}" + pid = Process.spawn(*cmd, pgroup: true, out: $stdout, err: $stderr) + + if opts[:detach] + Process.detach(pid) + puts "QEMU pid=#{pid}" + else + Process.wait(pid) + status = $? + puts "Exit status: #{status.exitstatus}" + end + end +end \ No newline at end of file