susi-qemu 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcd6b80ed80898c41c3ca6365ff62561c8dd6f6959e2bf0082ff2d30b30c0c49
4
- data.tar.gz: 69b8b5eea4153430e9af7540fafd5a4b6a46ffb60bb2ec39e54aeae5a1bb5d19
3
+ metadata.gz: dca16e441c61254d6f6bb88e4ecd0dc3567d15af01d542ea0905e22eead6fdaf
4
+ data.tar.gz: 682d45fac603f15a70a175420c8f13c1764c9040f1986d2244ab5bc6867248fd
5
5
  SHA512:
6
- metadata.gz: bcc80bdf535557fbc66257725635857521084b4574f2a58938c647bdfd3cdd79966235ce98373e163424b70120ba5c812651b007dabfbf94351b27bdb4fee9a4
7
- data.tar.gz: e63022c2205e0405749e9c5339098d2818c270a0d783de626f076a57e4ab1e47f286cef7519c91b1e10240b016e3b307544b83b40bec21b2d88b968c09914fcf
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,11 +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}")
18
- ssh.exec!("sudo sed -i 's/susi/#{name}/' /etc/hosts")
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
19
57
  end
20
58
  end
21
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.8'
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)
@@ -143,6 +148,14 @@ module Susi
143
148
  cmd << "-daemonize"
144
149
  cmd << "-enable-kvm"
145
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
+
146
159
  # control interfaces
147
160
  cmd << "-qmp tcp:localhost:#{qmp_port},server,nowait"
148
161
  cmd << "-vnc :#{vnc_port-5900},websocket=on"
@@ -182,6 +195,9 @@ module Susi
182
195
  # get local device name or IP
183
196
  ip = vm.ip
184
197
 
198
+ shared_dir = line.match(/-fsdev.*path=([^\s,]+)/)
199
+ shared_dir_info = shared_dir ? " - Shared Directory: #{shared_dir[1]}" : ""
200
+
185
201
  puts <<-EOF
186
202
  VM: #{n}
187
203
  - Disk: #{vm.disk}
@@ -190,6 +206,7 @@ VM: #{n}
190
206
  - Screen: http://#{ip}:#{vm.vnc_www_port}/
191
207
  - Websocket: ws://#{ip}:#{vm.vnc_websocket_port}/
192
208
  - SSH: ssh -p #{vm.ssh_port} dabo@#{ip}
209
+ #{shared_dir_info}
193
210
  EOF
194
211
  end
195
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.8
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