susi-qemu 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0fad5758256c0e74da6f5b06cc494653f4dace51cdaf0c358b658d7255f539c
4
- data.tar.gz: 743619250706519bc7d4bd5d93953a8c2a08d1acaf9923d0100fc65c26e3630a
3
+ metadata.gz: dca16e441c61254d6f6bb88e4ecd0dc3567d15af01d542ea0905e22eead6fdaf
4
+ data.tar.gz: 682d45fac603f15a70a175420c8f13c1764c9040f1986d2244ab5bc6867248fd
5
5
  SHA512:
6
- metadata.gz: 23502b5662ca90b84c15e42456689f80cb9b216b8f1e6d507fd99c19957ba47296ab38d3e910d399559ecb5f3f2a9b9e5b34f8e8d864d4560b4f4ccb8f0422fb
7
- data.tar.gz: 719ca17aade8cc9bc3603041907aee96021030bea45401179b76f9e3f640041cd4bdba101b65fd98acf57a8ee67ffc4dd5b9723fbf1065beb8b2c78830cefacc
6
+ metadata.gz: cdc1c3db00ed5e608d0e4f8b810b8be21273102d9e835b7f4ad5b091bb0a170e22e07cca22e92c4e280a5a137d6441bb7545b684b64d0b6df672836d416953a7
7
+ data.tar.gz: 6440884ac55b0a16cabdbaa38b74e70e2db6738a9a0dff87b8fb04d9895e031cfcabe789eec9bfab2017e267bae88eed2c15e4914adc2deb030f85f07fb7ecfb
data/bin/susi CHANGED
@@ -1,87 +1,19 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'susi'
4
- require 'version'
5
-
6
- if ARGV[0] == 'init'
7
- Susi::init
8
-
9
- elsif ARGV[0] == 'start'
10
- Susi::start
11
-
12
- elsif ARGV[0] == 'rm'
13
- # quit vm and remove disk
14
- Susi::VM.quit(Susi::current_vm_name)
15
- Susi::rm
16
-
17
- # create a disk
18
- elsif ARGV[0] == 'disk' && ARGV[1] == 'create' &&
19
- ARGV[2].is_a?(String) && ARGV[3].to_i > 0
20
- disk_name = ARGV[2]
21
- disk_size = ARGV[3].to_i
22
- Susi::Disk.create(disk_name, disk_size)
23
-
24
- # clone a disk
25
- elsif ARGV[0] == 'disk' && ARGV[1] == 'clone' &&
26
- ARGV[2].is_a?(String) && ARGV[3].is_a?(String) &&
27
- File.exist?("#{ARGV[2]}.qcow2") &&
28
- !File.exist?("#{ARGV[3]}.qcow2")
29
- disk_name = ARGV[2]
30
- new_disk_name = ARGV[3]
31
- Susi::Disk.clone(disk_name, new_disk_name)
32
-
33
- # start VM
34
- elsif ARGV[0] == 'vm' && ARGV[1] == 'start' &&
35
- ARGV[2].is_a?(String) && File.exist?("#{ARGV[2]}.qcow2")
36
- vm_name = ARGV[2]
37
- Susi::VM.start(vm_name, vm_name)
38
-
39
- # install VM
40
- elsif ARGV[0] == 'vm' && ARGV[1] == 'install' &&
41
- ARGV[2].is_a?(String) && File.exist?("#{ARGV[2]}.qcow2") &&
42
- ARGV[3].is_a?(String) && File.exist?(ARGV[3])
43
- vm_name = ARGV[2]
44
- iso = ARGV[3]
45
- Susi::VM.install(vm_name, vm_name, iso)
46
-
47
- # quit VM
48
- elsif ARGV[0] == 'vm' && ARGV[1] == 'quit' &&
49
- ARGV[2].is_a?(String)
50
- vm_name = ARGV[2]
51
- Susi::VM.quit(vm_name)
52
-
53
- # list VMs
54
- elsif ARGV[0] == 'vm' && ARGV[1] == 'ls'
55
- Susi::VM.ls
56
- elsif ARGV[0] == 'ls'
57
- Susi::VM.ls
58
-
59
- # open VNC
60
- elsif ARGV[0] == 'vnc'
61
- if ARGV[1].is_a?(String)
62
- vm_name = ARGV[1]
63
- Susi::VNC.open(vm_name)
64
- else
65
- Susi::VNC.open(Susi::current_vm_name)
3
+ if File.exist?(File.expand_path('../susi.gemspec', __dir__))
4
+ puts "Running in development mode"
5
+ module Susi
6
+ DEBUG = true
66
7
  end
8
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
9
+ end
67
10
 
68
- # open SSH
69
- elsif ARGV[0] == 'ssh'
70
- if ARGV[1].is_a?(String)
71
- vm_name = ARGV[1]
72
- Susi::SSH.open(vm_name)
73
- else
74
- Susi::SSH.open(Susi::current_vm_name)
75
- end
76
-
77
- # download Debian netinstall ISO
78
- elsif ARGV[0] == 'iso' && ARGV[1] == 'download'
79
- Susi::Disk.download_debian_netinstall
80
-
81
- else
82
- puts <<-EOF
83
- Invalid command
11
+ require 'susi'
12
+ require 'version'
13
+ require 'optparse'
84
14
 
15
+ def print_usage
16
+ Susi::info <<-EOF
85
17
  Usage:
86
18
  Init susi setting
87
19
  susi init
@@ -127,8 +59,121 @@ susi (v#{Susi::VERSION}) - Simple User System Interface
127
59
  author: Daniel Bovensiepen
128
60
  contact: oss@bovi.li
129
61
  www: https://github.com/bovi/susi
130
- EOF
62
+ EOF
63
+ exit 1
64
+ end
65
+
66
+ options = {}
67
+ subcommands = %w[init start rm disk vm ls vnc ssh iso]
131
68
 
132
- exit 1
69
+ global = OptionParser.new do |opts|
70
+ opts.banner = "Usage: susi [options] [subcommand [options]]"
71
+ opts.on("-h", "--help", "Prints this help") do
72
+ Susi::info opts
73
+ print_usage
74
+ end
75
+ end
133
76
 
77
+ subcommand = ARGV.shift
78
+ global.order!
79
+ print_usage if subcommand.nil?
80
+
81
+ case subcommand
82
+ when "init"
83
+ Susi::init
84
+ when "start"
85
+ Susi::start
86
+ when "stop", "shutdown"
87
+ vm_name = ARGV.shift || Susi::current_vm_name
88
+ Susi::VM.new(vm_name) do |vm|
89
+ vm.shutdown
90
+ end
91
+ when "rm"
92
+ Susi::VM.quit(Susi::current_vm_name)
93
+ Susi::rm
94
+ when "disk"
95
+ disk_command = ARGV.shift
96
+ case disk_command
97
+ when "create"
98
+ disk_name, disk_size = ARGV
99
+ if disk_name && disk_size.to_i > 0
100
+ Susi::Disk.create(disk_name, disk_size.to_i)
101
+ else
102
+ Susi::info "Invalid disk create command"
103
+ print_usage
104
+ end
105
+ when "clone"
106
+ disk_name, new_disk_name = ARGV
107
+ if disk_name && new_disk_name &&
108
+ File.exist?("#{disk_name}.qcow2") &&
109
+ !File.exist?("#{new_disk_name}.qcow2")
110
+ Susi::Disk.clone(disk_name, new_disk_name)
111
+ else
112
+ Susi::info "Invalid disk clone command"
113
+ print_usage
114
+ end
115
+ else
116
+ Susi::info "Invalid disk command"
117
+ print_usage
118
+ end
119
+ when "vm"
120
+ vm_command = ARGV.shift
121
+ case vm_command
122
+ when "start"
123
+ vm_name = ARGV.shift
124
+ if vm_name && File.exist?("#{vm_name}.qcow2")
125
+ Susi::VM.start(vm_name, vm_name)
126
+ else
127
+ Susi::info "Invalid vm start command"
128
+ print_usage
129
+ end
130
+ when "install"
131
+ vm_name, iso = ARGV
132
+ if vm_name && File.exist?("#{vm_name}.qcow2") && iso && File.exist?(iso)
133
+ Susi::VM.install(vm_name, vm_name, iso)
134
+ else
135
+ Susi::info "Invalid vm install command"
136
+ print_usage
137
+ end
138
+ when "quit"
139
+ vm_name = ARGV.shift
140
+ if vm_name
141
+ Susi::VM.quit(vm_name)
142
+ else
143
+ Susi::info "Invalid vm quit command"
144
+ print_usage
145
+ end
146
+ when "ls"
147
+ Susi::VM.ls
148
+ else
149
+ Susi::info "Invalid vm command"
150
+ print_usage
151
+ end
152
+ when "ls"
153
+ Susi::VM.ls
154
+ when "vnc"
155
+ vm_name = ARGV.shift
156
+ if vm_name
157
+ Susi::VNC.open(vm_name)
158
+ else
159
+ Susi::VNC.open(Susi::current_vm_name)
160
+ end
161
+ when "ssh"
162
+ vm_name = ARGV.shift
163
+ if vm_name
164
+ Susi::SSH.open(vm_name)
165
+ else
166
+ Susi::SSH.open(Susi::current_vm_name)
167
+ end
168
+ when "iso"
169
+ iso_command = ARGV.shift
170
+ if iso_command == "download"
171
+ Susi::Disk.download_debian_netinstall
172
+ else
173
+ Susi::info "Invalid iso command"
174
+ print_usage
175
+ end
176
+ else
177
+ Susi::info "Invalid command"
178
+ print_usage
134
179
  end
data/lib/output.rb CHANGED
@@ -1,6 +1,4 @@
1
1
  module Susi
2
- DEBUG = true
3
-
4
2
  def self.debug(msg)
5
3
  puts msg if DEBUG
6
4
  end
data/lib/qmp.rb CHANGED
@@ -11,6 +11,8 @@ module Susi
11
11
  @port = port
12
12
  @server = TCPSocket.new('localhost', @port)
13
13
 
14
+ Susi::debug "QMP connected"
15
+
14
16
  resp = JSON.parse(@server.gets)
15
17
  unless resp["QMP"]["version"]["qemu"]["major"] == 9
16
18
  server.close
data/lib/ssh.rb CHANGED
@@ -11,10 +11,49 @@ module Susi
11
11
  end
12
12
 
13
13
  def self.set_hostname(name)
14
- vm = VM.new(name)
15
- Net::SSH.start(vm.ip, 'dabo', port: vm.ssh_port, keys: [File.expand_path('~/.ssh/id_ed25519')]) do |ssh|
16
- Susi::debug "setting up VM..."
17
- ssh.exec!("sudo hostnamectl set-hostname #{name}")
14
+ VM.new(name) do |vm|
15
+ Net::SSH.start(vm.ip, 'dabo', port: vm.ssh_port, keys: [File.expand_path('~/.ssh/id_ed25519')]) do |ssh|
16
+ Susi::debug "Setting up VM hostname..."
17
+
18
+ output = ssh.exec!("sudo hostnamectl set-hostname #{name}")
19
+ Susi::debug "Setting hostname: #{output}"
20
+
21
+ output = ssh.exec!("sudo sed -i 's/susi/#{name}/' /etc/hosts")
22
+ Susi::debug "Updating /etc/hosts: #{output}"
23
+
24
+ Susi::debug "Hostname setup complete."
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.setup_virtio_filesystem(name)
30
+ VM.new(name) do |vm|
31
+ Net::SSH.start(vm.ip, 'dabo', port: vm.ssh_port, keys: [File.expand_path('~/.ssh/id_ed25519')]) do |ssh|
32
+ Susi::debug "Setting up virtio filesystem..."
33
+
34
+ output = ssh.exec!("echo 'susi_virtio_share /mnt/susi 9p trans=virtio,version=9p2000.L,rw 0 0' | sudo tee -a /etc/fstab")
35
+ Susi::debug "Adding virtio share to /etc/fstab: #{output}"
36
+
37
+ output = ssh.exec!("sudo mkdir -p /mnt/susi")
38
+ Susi::debug "Creating mount point: #{output}"
39
+
40
+ output = ssh.exec!("sudo systemctl daemon-reload")
41
+ Susi::debug "Reloading systemd: #{output}"
42
+
43
+ output = ssh.exec!("sudo mount -a")
44
+ Susi::debug "Mounting all filesystems: #{output}"
45
+
46
+ # Check if the mount was successful
47
+ output = ssh.exec!("mount | grep susi_virtio_share")
48
+ if output.empty?
49
+ Susi::debug "Warning: virtio share mount not found. Checking dmesg for errors..."
50
+ dmesg_output = ssh.exec!("dmesg | tail -n 20")
51
+ Susi::debug "Recent dmesg output: #{dmesg_output}"
52
+ else
53
+ Susi::debug "Virtio share mounted successfully: #{output}"
54
+ end
55
+ Susi::debug "Virtio filesystem setup complete."
56
+ end
18
57
  end
19
58
  end
20
59
  end
data/lib/susi.rb CHANGED
@@ -51,6 +51,7 @@ YAML
51
51
  id = config['id']
52
52
  name = config['name'] || Dir.pwd.split('/').last
53
53
  usb = config['usb']
54
+ shared_dir = config['shared_dir']
54
55
  disk_template = "#{TEMPLATE_DIR}/#{config['template']}"
55
56
 
56
57
  # check if vm is already running
@@ -64,12 +65,15 @@ YAML
64
65
  Susi::debug "Disk not found, cloning..."
65
66
  Disk.clone(disk_template, disk_name)
66
67
 
67
- VM.start(name, disk_name, usb: usb)
68
+ VM.start(name, disk_name, usb: usb, shared_dir: shared_dir)
68
69
 
69
70
  # SSH into VM and set hostname
70
71
  SSH.set_hostname(name)
72
+ if shared_dir
73
+ SSH.setup_virtio_filesystem(name)
74
+ end
71
75
  else
72
- VM.start(name, disk_name, usb: usb)
76
+ VM.start(name, disk_name, usb: usb, shared_dir: shared_dir)
73
77
  end
74
78
  end
75
79
 
@@ -101,7 +105,7 @@ if __FILE__ == $0
101
105
  end
102
106
 
103
107
  def test_cli_empty
104
- assert_equal(false, run_command("ruby ./susi.rb"))
108
+ assert_equal(false, run_command("ruby ./bin/susi"))
105
109
  end
106
110
 
107
111
  def test_create_img
@@ -117,15 +121,15 @@ if __FILE__ == $0
117
121
  end
118
122
 
119
123
  def test_cli_create_img
120
- assert_equal(true, run_command("ruby ./susi.rb disk create test 1"))
124
+ assert_equal(true, run_command("ruby ./bin/susi disk create test 1"))
121
125
  assert(File.exist?("test.qcow2"))
122
126
  run_command("rm test.qcow2")
123
127
  end
124
128
 
125
129
  def test_cli_create_img_fail
126
- assert_equal(false, run_command("ruby ./susi.rb disk create test"))
127
- assert_equal(false, run_command("ruby ./susi.rb disk create test pest"))
128
- assert_equal(false, run_command("ruby ./susi.rb disk create 1"))
130
+ assert_equal(false, run_command("ruby ./bin/susi disk create test"))
131
+ assert_equal(false, run_command("ruby ./bin/susi disk create test pest"))
132
+ assert_equal(false, run_command("ruby ./bin/susi disk create 1"))
129
133
  end
130
134
 
131
135
  def test_clone_img
@@ -144,19 +148,19 @@ if __FILE__ == $0
144
148
  end
145
149
 
146
150
  def test_cli_clone_img
147
- assert_equal(true, run_command("ruby ./susi.rb disk create test 1"))
148
- assert_equal(true, run_command("ruby ./susi.rb disk clone test clone"))
151
+ assert_equal(true, run_command("ruby ./bin/susi disk create test 1"))
152
+ assert_equal(true, run_command("ruby ./bin/susi disk clone test clone"))
149
153
  assert(File.exist?("clone.qcow2"))
150
154
  run_command("rm clone.qcow2")
151
155
  run_command("rm test.qcow2")
152
156
  end
153
157
 
154
158
  def test_cli_clone_img_fail
155
- assert_equal(false, run_command("ruby ./susi.rb disk clone test clone"))
156
- assert_equal(true, run_command("ruby ./susi.rb disk create test 1"))
157
- assert_equal(false, run_command("ruby ./susi.rb disk clone test"))
158
- assert_equal(true, run_command("ruby ./susi.rb disk clone test clone"))
159
- assert_equal(false, run_command("ruby ./susi.rb disk clone test clone"))
159
+ assert_equal(false, run_command("ruby ./bin/susi disk clone test clone"))
160
+ assert_equal(true, run_command("ruby ./bin/susi disk create test 1"))
161
+ assert_equal(false, run_command("ruby ./bin/susi disk clone test"))
162
+ assert_equal(true, run_command("ruby ./bin/susi disk clone test clone"))
163
+ assert_equal(false, run_command("ruby ./bin/susi disk clone test clone"))
160
164
 
161
165
  run_command("rm clone.qcow2")
162
166
  run_command("rm test.qcow2")
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Susi
2
- VERSION = '0.0.7'
2
+ VERSION = '0.0.9'
3
3
  end
data/lib/vm.rb CHANGED
@@ -45,8 +45,13 @@ module Susi
45
45
  end
46
46
  end
47
47
 
48
+ def shutdown
49
+ Susi::debug "Initiating shutdown for VM #{@name}"
50
+ @qmp.execute("system_powerdown")
51
+ end
52
+
48
53
  def quit
49
- #QMP.new(@qmp_port) { |q| q.quit }
54
+ Susi::debug "Quitting QMP for VM #{@name}"
50
55
  @qmp.quit
51
56
  end
52
57
 
@@ -122,7 +127,7 @@ module Susi
122
127
  raise "No free port found in range #{start_port}..#{end_port}"
123
128
  end
124
129
 
125
- def self.start(name, disk, cdrom: nil, usb: nil)
130
+ def self.start(name, disk, cdrom: nil, usb: nil, shared_dir: nil)
126
131
  qmp_port = self.get_free_port(4000, 4099)
127
132
  ssh_port = self.get_free_port(2000, 2099)
128
133
  vnc_port = self.get_free_port(5900, 5999)
@@ -133,15 +138,24 @@ module Susi
133
138
 
134
139
  cmd = []
135
140
 
136
- cmd << "qemu-system-x86_64"
141
+ cmd << "sudo"
142
+ cmd << `which qemu-system-x86_64`.strip
137
143
 
138
144
  # general setup
139
145
  cmd << "-name #{name}"
140
146
  cmd << "-m 2048"
141
- cmd << "-hda #{disk}.qcow2"
147
+ cmd << "-hda #{File.expand_path(disk)}.qcow2"
142
148
  cmd << "-daemonize"
143
149
  cmd << "-enable-kvm"
144
150
 
151
+ # Add shared directory passthrough if specified
152
+ if shared_dir
153
+ Susi::debug "Adding shared directory #{shared_dir}"
154
+ shared_dir_path = File.expand_path(shared_dir)
155
+ cmd << "-fsdev local,security_model=passthrough,id=fsdev0,path=#{shared_dir_path}"
156
+ cmd << "-device virtio-9p-pci,fsdev=fsdev0,mount_tag=susi_virtio_share"
157
+ end
158
+
145
159
  # control interfaces
146
160
  cmd << "-qmp tcp:localhost:#{qmp_port},server,nowait"
147
161
  cmd << "-vnc :#{vnc_port-5900},websocket=on"
@@ -181,6 +195,9 @@ module Susi
181
195
  # get local device name or IP
182
196
  ip = vm.ip
183
197
 
198
+ shared_dir = line.match(/-fsdev.*path=([^\s,]+)/)
199
+ shared_dir_info = shared_dir ? " - Shared Directory: #{shared_dir[1]}" : ""
200
+
184
201
  puts <<-EOF
185
202
  VM: #{n}
186
203
  - Disk: #{vm.disk}
@@ -189,6 +206,7 @@ VM: #{n}
189
206
  - Screen: http://#{ip}:#{vm.vnc_www_port}/
190
207
  - Websocket: ws://#{ip}:#{vm.vnc_websocket_port}/
191
208
  - SSH: ssh -p #{vm.ssh_port} dabo@#{ip}
209
+ #{shared_dir_info}
192
210
  EOF
193
211
  end
194
212
  end
data/lib/vnc.rb CHANGED
@@ -6,6 +6,12 @@ require_relative 'output'
6
6
  module Susi
7
7
  class VNC
8
8
  def self.open(name)
9
+ port = nil
10
+ token = nil
11
+ html = nil
12
+ novnc_path = nil
13
+ url = nil
14
+
9
15
  VM.new(name) do |vm|
10
16
  port = vm.vnc_www_port
11
17
  token = (0...8).map { (65 + rand(26)).chr }.join
@@ -19,31 +25,42 @@ module Susi
19
25
  html.gsub!("###TOKEN###", token)
20
26
  html.gsub!("###HOST###", vm.ip)
21
27
  html.gsub!("###PORT###", vm.vnc_websocket_port.to_s)
28
+ end
22
29
 
23
- server = WEBrick::HTTPServer.new(:Port => port,
24
- :Logger => WEBrick::Log.new("/dev/null"),
25
- :AccessLog => [])
26
- server.mount_proc "/#{token}.html" do |req, res|
27
- # print access by someone on command line
28
- Susi.info "[#{Time.now}] Access by: #{req.peeraddr[3]}"
29
- res.body = html
30
- end
31
- server.mount_proc "/#{token}.close.html" do |req, res|
32
- Susi.info "[#{Time.now}] Closing by #{req.peeraddr[3]}"
33
- res.body = "Closing VNC..."
34
- server.shutdown
35
- end
36
- server.mount("/core",
37
- WEBrick::HTTPServlet::FileHandler,
38
- File.join(novnc_path, "core"))
39
- server.mount("/vendor",
40
- WEBrick::HTTPServlet::FileHandler,
41
- File.join(novnc_path, "vendor"))
30
+ server = WEBrick::HTTPServer.new(:Port => port,
31
+ :Logger => WEBrick::Log.new("/dev/null"),
32
+ :AccessLog => [])
33
+ server.mount_proc "/#{token}.html" do |req, res|
34
+ # print access by someone on command line
35
+ Susi.info "[#{Time.now}] Access by: #{req.peeraddr[3]}"
36
+ res.body = html
37
+ end
38
+ server.mount_proc "/#{token}.close.html" do |req, res|
39
+ Susi.info "[#{Time.now}] Closing by #{req.peeraddr[3]}"
40
+ res.body = "Closing VNC..."
41
+ server.shutdown
42
+ end
43
+ server.mount("/core",
44
+ WEBrick::HTTPServlet::FileHandler,
45
+ File.join(novnc_path, "core"))
46
+ server.mount("/vendor",
47
+ WEBrick::HTTPServlet::FileHandler,
48
+ File.join(novnc_path, "vendor"))
42
49
 
43
- Susi::debug "[#{Time.now}] VNC screen: #{url}"
50
+ Susi::debug "[#{Time.now}] VNC screen: #{url}"
44
51
 
45
- server.start
52
+ Thread.new do
53
+ loop do
54
+ unless VM.running?(name)
55
+ Susi::info "VM #{name} is no longer running. Shutting down VNC server."
56
+ server.shutdown
57
+ break
58
+ end
59
+ sleep 10
60
+ end
46
61
  end
62
+
63
+ server.start
47
64
  end
48
65
  end
49
66
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: susi-qemu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Bovensiepen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-29 00:00:00.000000000 Z
11
+ date: 2024-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-ssh