344 lines
No EOL
11 KiB
Ruby
344 lines
No EOL
11 KiB
Ruby
require "fileutils"
|
||
require "ostruct"
|
||
|
||
module DisplayMode
|
||
def self.none
|
||
0
|
||
end
|
||
|
||
def self.fullscreen
|
||
1
|
||
end
|
||
|
||
def self.window
|
||
2
|
||
end
|
||
|
||
def self.vnc
|
||
3
|
||
end
|
||
end
|
||
|
||
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,
|
||
shell: false,
|
||
ram: 2048 * 8,
|
||
cpus: 1,
|
||
display: DisplayMode::none,
|
||
mount: {
|
||
wd: nil,
|
||
home: nil
|
||
}
|
||
}
|
||
|
||
# for testing only
|
||
defaults[:detach] = false
|
||
|
||
defaults[:display] = DisplayMode.fullscreen
|
||
defaults[:display] = DisplayMode.window
|
||
# defaults[:display] = DisplayMode.none
|
||
#defaults[:display] = DisplayMode.vnc
|
||
|
||
opts = defaults.merge(options)
|
||
|
||
puts 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"]
|
||
# cp /opt/homebrew/share/qemu/edk2-arm-vars.fd ~/edk2-arm-vars.fd
|
||
|
||
|
||
unless File.exist?(opts[:vars_fd])
|
||
#System.qemu_paths
|
||
FileUtils.cp(System.qemu_vars_fd_path, opts[:vars_fd])
|
||
|
||
end
|
||
|
||
unless File.exist?(opts[:code_fd])
|
||
FileUtils.cp(System.qemu_code_fd_path, opts[:code_fd])
|
||
end
|
||
|
||
args += ["-drive", "if=pflash,format=raw,unit=0,readonly=on,file=#{opts[:code_fd]}"]
|
||
args += ["-drive", "if=pflash,format=raw,unit=1,file=#{opts[:vars_fd]}"]
|
||
|
||
ssh_port = nil
|
||
|
||
if opts[:shell]
|
||
ssh_port = rand(4000..9999)
|
||
# args += ['-netdev', "user,id=net0,hostfwd=tcp:127.0.0.1:#{ssh_port}-:22"]
|
||
# args += ['-device', 'virtio-net-device,netdev=net0']
|
||
|
||
# args += ['-netdev', "user,id=n0,hostfwd=tcp:127.0.0.1:#{ssh_port}-10.0.2.15:22"]
|
||
# args += ['-device', 'virtio-net,netdev=n0']
|
||
|
||
# args += ['-netdev', "user,id=net0,hostfwd=tcp:127.0.0.1:#{ssh_port}-10.0.2.15:22"]
|
||
# args += ['-device', 'virtio-net-device,netdev=net0"']
|
||
|
||
args += ['-nic', "user,model=virtio-net-pci,hostfwd=tcp:127.0.0.1:#{ssh_port}-:22"]
|
||
|
||
puts "ssh -p #{ssh_port} user@localhost"
|
||
|
||
# conf that works
|
||
#args += ["-device", "virtio-net,netdev=n0", "-netdev", "user,id=n0"]
|
||
end
|
||
|
||
# -virtfs local,path=.,mount_tag=hostfs,security_model=passthrough,id=hostfs
|
||
# mount -t 9p -o trans=virtio,version=9p2000.L hostfs /mnt
|
||
# sudo mount -t 9p hostfs /home/user/Share -o trans=virtio,version=9p2000.L,uid=1000,gid=1000,msize=262144,cache=mmap
|
||
|
||
# hostfs /home 9p trans=virtio,version=9p2000.L,uid=1000,gid=1000,msize=262144,cache=mmap,nofail 0 0
|
||
# hostfs /share 9p trans=virtio,version=9p2000.L,uid=1000,gid=1000,msize=262144,cache=mmap,nofail 0 0
|
||
|
||
if opts[:mount][:wd]
|
||
args += ['-virtfs', 'local,path=.,mount_tag=hostfs,security_model=passthrough,id=hostfs']
|
||
end
|
||
|
||
if opts[:mount][:home]
|
||
#args += ['-homefs', "local,path=#{VMDATA},mount_tag=hostfs,security_model=passthrough,id=hostfs"]
|
||
end
|
||
|
||
if opts[:display] == DisplayMode::none
|
||
port = 2222
|
||
args += ['-nographic']
|
||
args += ['-netdev', "user,id=net0,hostfwd=tcp:127.0.0.1:#{port}-:22,udp:127.0.0.1:6544-:6544=on"]
|
||
#args += ['-device', 'e1000,netdev=net0']
|
||
args += ['-device', 'virtio-net-pci,netdev=net0']
|
||
puts "ssh -p #{port} user@localhost"
|
||
|
||
|
||
|
||
elsif opts[:display] == DisplayMode::vnc
|
||
# Note: this outputs serial on the console
|
||
#args += ['-nographic']
|
||
|
||
args += ["-display", "none"]
|
||
args += ["-device", "virtio-gpu-pci"]
|
||
args += ["-device", "virtio-keyboard-pci"]
|
||
args += ["-device", "virtio-mouse-pci"]
|
||
|
||
args += ['-vnc', '127.0.0.1:0,password=on']
|
||
|
||
# tunnel ssh -L 5900:127.0.0.1:5900 user@your-host
|
||
# -monitor unix:/tmp/qemu-mon,server,nowait
|
||
# -vnc 127.0.0.1:0,password=on
|
||
# printf 'change vnc password\nMySecret\n' | socat - UNIX-CONNECT:/tmp/qemu-mon
|
||
|
||
|
||
# SASL auth (username + password)
|
||
# -vnc :0,sasl
|
||
|
||
# TLS (certificates, optional password)
|
||
# -object tls-creds-x509,id=tls0,...
|
||
# -vnc :0,tls-creds=tls0
|
||
|
||
# qemu-system-x86_64 \
|
||
# -object tls-creds-x509,id=tls0,dir=/etc/pki/qemu,endpoint=server \
|
||
# -vnc :0,tls-creds=tls0,sasl
|
||
else
|
||
#args += ["-device", "virtio-gpu-device"]
|
||
if opts[:display] == DisplayMode::fullscreen
|
||
# #args += ["-display", "cocoa,full-screen=on"]
|
||
# # attempts:
|
||
# #args += ["-display", "cocoa,full-screen=on,retina=on"]
|
||
# # brew install gtk+3 sdl2
|
||
# # args += ["-display", "sdl,gl=on,full-screen=on"]
|
||
# #args += ["-display", "gtk,gl=on,full-screen=on"]
|
||
# #args += ["-display", "cocoa,full-screen=on"]
|
||
# #args += ["-display", "cocoa,gl=es,full-screen=on"]
|
||
|
||
#### TODO: try make it work with custom build
|
||
# args += ["-device", "virtio-gpu-gl-pci"]
|
||
# args += ["-display", "sdl,gl=on,full-screen=on"]
|
||
args += ["-display", "cocoa,full-screen=on"]
|
||
else
|
||
args += ["-display", "cocoa"]
|
||
end
|
||
|
||
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"]
|
||
|
||
#args += ['-nic', 'user,model=virtio-net-pci']
|
||
unless opts[:shell]
|
||
args += ["-device", "virtio-net,netdev=n0", "-netdev", "user,id=n0"]
|
||
end
|
||
|
||
|
||
### TODO: remove
|
||
port = 2222
|
||
args += ['-device', 'virtio-net-pci,netdev=net0']
|
||
args += ['-netdev', "user,id=net0,hostfwd=tcp:127.0.0.1:#{port}-:22,hostfwd=udp:127.0.0.1:6544-:6544"]
|
||
args += ['-virtfs', 'local,path=.,mount_tag=hostfs,security_model=passthrough,id=hostfs']
|
||
### TODO END
|
||
|
||
|
||
# macOS vmnet (shares Mac’s LAN)
|
||
# -netdev vmnet-shared,id=n1 \
|
||
# -device virtio-net-pci,netdev=n1
|
||
end
|
||
|
||
# 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,id=nvme0"]
|
||
#args += ["-device", "nvme,serial=nvme0,drive=nvme0,bootindex=2"]
|
||
|
||
if opts[:cdrom] != nil
|
||
#args += ["-cdrom", opts[:cdrom]]
|
||
|
||
# args += ["-device", "virtio-scsi-pci,id=scsi"]
|
||
# args += ["-drive", "if=none,id=cd,format=raw,file=#{opts[:cdrom]},media=cdrom"]
|
||
# args += ["-device", "scsi-cd,drive=cd,bootindex=1"]
|
||
|
||
|
||
args += ["-drive", "id=cd,format=raw,file=#{opts[:cdrom]},media=cdrom"]
|
||
args += ["-device", "usb-storage,drive=cd,bootindex=1"]
|
||
|
||
args += ["-device", "ramfb"]
|
||
# args += ["-device", "virtio-gpu-pci"]
|
||
# args += ["-display", "default,show-cursor=on"]
|
||
end
|
||
|
||
if opts[:tpm]
|
||
# brew install swtpm
|
||
# swtpm socket --tpm2 --ctrl type=unixio,path=./tpm/tpm.sock --tpmstate dir=./tpm --daemon
|
||
|
||
|
||
["swtpm", "socket", "--tpm2", "--ctrl", "type=unixio,path=./tpm/tpm.sock", "--tpmstate", "dir=./tpm"]
|
||
|
||
# args += ["-chardev", "socket,id=chrtpm,path=/Users/agurgul/Downloads/tpm/tpm.sock"]
|
||
# args += ["-tpmdev", "emulator,id=tpm0,chardev=chrtpm"]
|
||
# args += ["-device", "tpm-crb-device,tpmdev=tpm0"]
|
||
|
||
args += ["-chardev", "socket,id=chrtpm,path=/Users/agurgul/Downloads/tpm/tpm.sock"]
|
||
args += ["-tpmdev", "emulator,id=tpm0,chardev=chrtpm"]
|
||
#args += ["-device", "tpm-tis,tpmdev=tpm0"]
|
||
args += ["-device", "tpm-tis-device,tpmdev=tpm0"]
|
||
# nic user,ipv6=off,model=rtl8139,mac=84:1b:77:c9:03:a6
|
||
|
||
# TODO: Shared network on macOS
|
||
# -netdev vmnet-shared,id=net0
|
||
end
|
||
|
||
args += ['-monitor', 'stdio']
|
||
|
||
# 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(' ')}"
|
||
|
||
if opts[:detach]
|
||
log = File.open("log.txt", "w")
|
||
pid = Process.spawn(*cmd, pgroup: false, out: log, err: log)
|
||
Process.detach(pid)
|
||
puts "QEMU pid=#{pid}"
|
||
|
||
if opts[:shell]
|
||
System.exec_ssh(ssh_port)
|
||
end
|
||
|
||
else
|
||
pid = Process.spawn(*cmd, pgroup: true, out: $stdout, err: $stderr)
|
||
Process.wait(pid)
|
||
status = $?
|
||
puts "Exit status: #{status.exitstatus}"
|
||
end
|
||
end
|
||
end
|
||
|
||
|
||
|
||
|
||
## Works on MacOS=
|
||
# -monitor unix:/tmp/qemu-monitor.sock,server,nowait
|
||
# nc -U /tmp/qemu-monitor.sock
|
||
# instead of args += ['-monitor', 'stdio']
|
||
|
||
|
||
|
||
|
||
# 9P
|
||
# sudo mount -t 9p hostfs /home/user/Share \
|
||
# -o trans=virtio,version=9p2000.L,msize=262144,cache=mmap,access=any,dfltuid=1000,dfltgid=1000
|
||
# sudo chown -hR 1000:1000 /home/user/Share |