lxdev 0.1.8 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/lxdev +2 -5
- data/lib/lxdev/main.rb +68 -80
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 188423201d5b4fe029a2384edd1bba6384c35d9054f72db0ea02b02c7aec1c37
|
4
|
+
data.tar.gz: 6cac980901cb2ff633067fb600c1064c3136a48ccce9d50f708c54104efa5d4e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4f77338f7f084b345df2538fdd555c8581d68ebb7ab2a80c4a37dece6aabf196094d1c7310cbc5e19d0ce6d8aadec44ee0150160961e71fb634eda30b3a98d2
|
7
|
+
data.tar.gz: 27ba6483e613dffdbaa2386661ae2533edcff4de5c296d4b223edc068103408dc042414c9fba1f92086fcb1f99a27b992ee0ce105c9c370163a71101dcd5c67a
|
data/bin/lxdev
CHANGED
@@ -32,16 +32,15 @@ def option_parser
|
|
32
32
|
Commands:
|
33
33
|
up Bring container up
|
34
34
|
status Show status of the container
|
35
|
-
ssh Log into the container
|
35
|
+
ssh Log into the container
|
36
36
|
halt Stop the container
|
37
|
-
destroy Destroy the container
|
37
|
+
destroy Destroy the container
|
38
38
|
provision Run provisioning commands from YAML file
|
39
39
|
exec Run command as root in container
|
40
40
|
snapshot Snapshots the container. Takes a snapshot name as parameter.
|
41
41
|
rmsnapshot Deletes a snapshot. Takes a snapshot name as parameter.
|
42
42
|
restore Restore container to a snapshot with a previous state. Takes a snapshot name as parameter.
|
43
43
|
revert Restore container to the last snapshot taken.
|
44
|
-
sudoers Create a sudoers file, allowing you to use lxdev without entering the sudo password
|
45
44
|
EOS
|
46
45
|
opt_parser.parse!()
|
47
46
|
|
@@ -74,8 +73,6 @@ def execute_main_command(lxdev)
|
|
74
73
|
else
|
75
74
|
lxdev.execute(command)
|
76
75
|
end
|
77
|
-
when 'sudoers'
|
78
|
-
LxDev::Main.create_sudoers_file()
|
79
76
|
when 'snapshot'
|
80
77
|
snapshot_name = ARGV[1]
|
81
78
|
if snapshot_name.nil?
|
data/lib/lxdev/main.rb
CHANGED
@@ -3,15 +3,16 @@ require 'json'
|
|
3
3
|
require 'terminal-table'
|
4
4
|
require 'lxdev/system'
|
5
5
|
require 'tempfile'
|
6
|
+
require 'etc'
|
6
7
|
|
7
8
|
module LxDev
|
8
9
|
class Main
|
9
|
-
|
10
|
+
REQUIRED_COMMANDS = ["lxc", "redir", "kill"]
|
10
11
|
SHELLS = ["bash", "zsh", "sh", "csh", "tcsh", "ash"]
|
11
12
|
BOOT_TIMEOUT = 30
|
12
|
-
VERSION = '0.
|
13
|
+
VERSION = '0.2.0'
|
13
14
|
|
14
|
-
def initialize(config_file, state_file)
|
15
|
+
def initialize(config_file, state_file, lxc_command)
|
15
16
|
@state_file = format(".lxdev/%s", state_file)
|
16
17
|
@uid = System.exec("id -u").output.chomp
|
17
18
|
@gid = System.exec("id -g").output.chomp
|
@@ -20,6 +21,7 @@ module LxDev
|
|
20
21
|
@image = @config['box']['image']
|
21
22
|
@user = @config['box']['user']
|
22
23
|
@ports = @config['box']['ports'] || {}
|
24
|
+
@lxc_command = lxc_command
|
23
25
|
Dir.mkdir('.lxdev') unless File.directory?('.lxdev')
|
24
26
|
begin
|
25
27
|
@state = YAML.load_file(@state_file)
|
@@ -37,7 +39,14 @@ module LxDev
|
|
37
39
|
puts "Please run 'lxd init' and configure LXD first"
|
38
40
|
return false
|
39
41
|
end
|
40
|
-
|
42
|
+
user_in_group = Main.user_in_lxd_group?
|
43
|
+
lxc_command = (user_in_group ? "lxc" : "sudo lxc")
|
44
|
+
unless(user_in_group)
|
45
|
+
puts "Add yourself to the 'lxd' group to avoid entering the sudo password :"
|
46
|
+
puts "(You need to login again afterwards, or start a new login shell)"
|
47
|
+
puts " sudo usermod -a -G lxd #{Etc.getlogin}"
|
48
|
+
end
|
49
|
+
lxdev = Main.new(config_file, state_file, lxc_command)
|
41
50
|
unless lxdev.set_ssh_keys
|
42
51
|
puts "No ssh keys detected. Make sure you have an ssh key, a running agent, and the key added to the agent, e.g. with ssh-add."
|
43
52
|
return false
|
@@ -45,6 +54,15 @@ module LxDev
|
|
45
54
|
return lxdev
|
46
55
|
end
|
47
56
|
|
57
|
+
def self.user_in_lxd_group?
|
58
|
+
begin
|
59
|
+
Etc.getgrnam("lxd").mem.include?(Etc.getlogin)
|
60
|
+
rescue ArgumentError
|
61
|
+
puts "There seems to be no 'lxd' group. Is LXD installed?"
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
48
66
|
def save_state
|
49
67
|
File.open(@state_file, 'w') {|f| f.write @state.to_yaml} unless @state.empty?
|
50
68
|
end
|
@@ -114,14 +132,14 @@ module LxDev
|
|
114
132
|
|
115
133
|
def halt
|
116
134
|
ensure_container_created
|
117
|
-
System.exec("
|
135
|
+
System.exec("#{@lxc_command} stop #{@name}")
|
118
136
|
cleanup_forwarded_ports
|
119
137
|
remove_state
|
120
138
|
end
|
121
139
|
|
122
140
|
def destroy
|
123
141
|
ensure_container_created
|
124
|
-
System.exec("
|
142
|
+
System.exec("#{@lxc_command} delete #{@name}")
|
125
143
|
end
|
126
144
|
|
127
145
|
def ssh(args)
|
@@ -137,9 +155,9 @@ module LxDev
|
|
137
155
|
|
138
156
|
def execute(command, interactive: false)
|
139
157
|
if interactive
|
140
|
-
exec("
|
158
|
+
exec("#{@lxc_command} exec #{@name} #{command}") # execution stops here and gives control to exec
|
141
159
|
end
|
142
|
-
IO.popen("
|
160
|
+
IO.popen("#{@lxc_command} exec #{@name} -- /bin/sh -c '#{command}'", err: [:child, :out]) do |cmd_output|
|
143
161
|
cmd_output.each do |line|
|
144
162
|
puts line
|
145
163
|
end
|
@@ -171,18 +189,18 @@ module LxDev
|
|
171
189
|
|
172
190
|
def snapshot(snapshot_name)
|
173
191
|
puts "Creating snapshot #{snapshot_name}"
|
174
|
-
System.exec("
|
192
|
+
System.exec("#{@lxc_command} snapshot #{@name} #{snapshot_name}")
|
175
193
|
end
|
176
194
|
|
177
195
|
def restore(snapshot_name)
|
178
196
|
puts "Restoring snapshot #{snapshot_name}"
|
179
|
-
exitstatus = System.exec("
|
197
|
+
exitstatus = System.exec("#{@lxc_command} restore #{@name} #{snapshot_name}").exitstatus
|
180
198
|
exitstatus == 0
|
181
199
|
end
|
182
200
|
|
183
201
|
def rmsnapshot(snapshot_name)
|
184
202
|
puts "Deleting snapshot #{snapshot_name}"
|
185
|
-
exitstatus = System.exec("
|
203
|
+
exitstatus = System.exec("#{@lxc_command} delete #{@name}/#{snapshot_name}").exitstatus
|
186
204
|
exitstatus == 0
|
187
205
|
end
|
188
206
|
|
@@ -199,7 +217,7 @@ module LxDev
|
|
199
217
|
private
|
200
218
|
|
201
219
|
def self.lxd_initialized?
|
202
|
-
exitstatus = System.exec("
|
220
|
+
exitstatus = System.exec("#{@lxc_command} info | grep 'lxd init'").exitstatus
|
203
221
|
exitstatus != 0
|
204
222
|
end
|
205
223
|
|
@@ -219,10 +237,10 @@ module LxDev
|
|
219
237
|
def create_container
|
220
238
|
add_subuid_and_subgid
|
221
239
|
puts "Launching #{@name}..."
|
222
|
-
System.exec("
|
223
|
-
System.exec(%{printf "uid #{@uid} 1001\ngid #{@gid} 1001"|
|
224
|
-
System.exec(%{
|
225
|
-
System.exec("
|
240
|
+
System.exec("#{@lxc_command} init #{@image} #{@name}")
|
241
|
+
System.exec(%{printf "uid #{@uid} 1001\ngid #{@gid} 1001"| #{@lxc_command} config set #{@name} raw.idmap -})
|
242
|
+
System.exec(%{#{@lxc_command} config set #{@name} boot.autostart false})
|
243
|
+
System.exec("#{@lxc_command} start #{@name}")
|
226
244
|
puts "Creating user #{@user}..."
|
227
245
|
create_container_user(@user)
|
228
246
|
puts "Mapping folders.."
|
@@ -231,12 +249,12 @@ module LxDev
|
|
231
249
|
|
232
250
|
def start_container
|
233
251
|
puts "Starting #{@name}..."
|
234
|
-
System.exec("
|
252
|
+
System.exec("#{@lxc_command} start #{@name}")
|
235
253
|
end
|
236
254
|
|
237
255
|
def get_container_status
|
238
256
|
return @status unless @status.nil?
|
239
|
-
command_result = System.exec("
|
257
|
+
command_result = System.exec("#{@lxc_command} list ^#{@name}$ --format=json")
|
240
258
|
@status = JSON.parse(command_result.output)
|
241
259
|
end
|
242
260
|
|
@@ -266,16 +284,16 @@ module LxDev
|
|
266
284
|
end
|
267
285
|
|
268
286
|
def create_container_user(user)
|
269
|
-
System.exec("
|
270
|
-
System.exec("
|
271
|
-
System.exec("
|
272
|
-
System.exec("
|
273
|
-
System.exec("printf '#{@ssh_keys}' |
|
274
|
-
System.exec("
|
275
|
-
System.exec("
|
276
|
-
System.exec("
|
277
|
-
System.exec(%{printf "#{user} ALL=(ALL) NOPASSWD: ALL\n" |
|
278
|
-
System.exec("
|
287
|
+
System.exec("#{@lxc_command} exec #{@name} -- groupadd --gid 1001 #{user}")
|
288
|
+
System.exec("#{@lxc_command} exec #{@name} -- useradd --uid 1001 --gid 1001 -s /bin/bash -m #{user}")
|
289
|
+
System.exec("#{@lxc_command} exec #{@name} -- mkdir /home/#{user}/.ssh")
|
290
|
+
System.exec("#{@lxc_command} exec #{@name} -- chmod 0700 /home/#{user}/.ssh")
|
291
|
+
System.exec("printf '#{@ssh_keys}' | #{@lxc_command} exec #{@name} tee /home/#{user}/.ssh/authorized_keys")
|
292
|
+
System.exec("#{@lxc_command} exec #{@name} -- chown -R #{user} /home/#{user}/.ssh")
|
293
|
+
System.exec("#{@lxc_command} exec #{@name} -- touch /home/#{@user}/.hushlogin")
|
294
|
+
System.exec("#{@lxc_command} exec #{@name} -- chown #{user} /home/#{user}/.hushlogin")
|
295
|
+
System.exec(%{printf "#{user} ALL=(ALL) NOPASSWD: ALL\n" | #{@lxc_command} exec #{@name} -- tee -a /etc/sudoers})
|
296
|
+
System.exec("#{@lxc_command} exec #{@name} -- chmod 0440 /etc/sudoers")
|
279
297
|
end
|
280
298
|
|
281
299
|
def wait_for_boot
|
@@ -289,20 +307,33 @@ module LxDev
|
|
289
307
|
|
290
308
|
def forward_ports(ports)
|
291
309
|
redir_pids = []
|
310
|
+
sudo_redir_pids = []
|
292
311
|
ports.each do |guest, host|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
312
|
+
if (host > 1024)
|
313
|
+
puts "Forwarding #{get_container_ip}:#{guest} to local port #{host}"
|
314
|
+
pid = System.spawn_exec("redir --caddr=#{get_container_ip} --cport=#{guest} --lport=#{host}", silent: true)
|
315
|
+
redir_pids << pid
|
316
|
+
Process.detach(pid)
|
317
|
+
else
|
318
|
+
puts "Forwarding #{get_container_ip}:#{guest} to local port #{host} (root privileges needed)"
|
319
|
+
pid = System.spawn_exec("sudo redir --caddr=#{get_container_ip} --cport=#{guest} --lport=#{host}", silent: true)
|
320
|
+
sudo_redir_pids << pid
|
321
|
+
Process.detach(pid)
|
322
|
+
end
|
297
323
|
end
|
298
324
|
@state['redir_pids'] = redir_pids
|
325
|
+
@state['sudo_redir_pids'] = sudo_redir_pids
|
299
326
|
end
|
300
327
|
|
301
328
|
def cleanup_forwarded_ports
|
302
329
|
if @state.empty?
|
303
330
|
return
|
304
331
|
end
|
305
|
-
@state['redir_pids']
|
332
|
+
@state['redir_pids']&.each do |pid|
|
333
|
+
System.exec("kill #{pid}")
|
334
|
+
end
|
335
|
+
@state['sudo_redir_pids']&.each do |pid|
|
336
|
+
puts "Killing pid #{pid} started with sudo privileges"
|
306
337
|
System.exec("sudo kill #{pid}")
|
307
338
|
end
|
308
339
|
end
|
@@ -313,7 +344,7 @@ module LxDev
|
|
313
344
|
counter = counter + 1
|
314
345
|
puts "Mounting #{host} in #{guest}"
|
315
346
|
absolute_path = System.exec("readlink -f #{host}").output.chomp
|
316
|
-
System.exec("
|
347
|
+
System.exec("#{@lxc_command} config device add #{@name} shared_folder_#{counter} disk source=#{absolute_path} path=#{guest}")
|
317
348
|
end
|
318
349
|
end
|
319
350
|
|
@@ -334,9 +365,9 @@ module LxDev
|
|
334
365
|
end
|
335
366
|
|
336
367
|
def lxd_service_name
|
337
|
-
if System.exec("
|
368
|
+
if System.exec("systemctl status lxd.service").exitstatus == 0
|
338
369
|
'lxd.service'
|
339
|
-
elsif System.exec("
|
370
|
+
elsif System.exec("systemctl status snap.lxd.daemon.service").exitstatus == 0
|
340
371
|
'snap.lxd.daemon.service'
|
341
372
|
else
|
342
373
|
raise 'There seems to be no LXD service on the system!'
|
@@ -344,7 +375,7 @@ module LxDev
|
|
344
375
|
end
|
345
376
|
|
346
377
|
def self.check_requirements
|
347
|
-
|
378
|
+
REQUIRED_COMMANDS.each do |cmd|
|
348
379
|
unless System.exec("which #{cmd}").exitstatus == 0
|
349
380
|
puts "The command '#{cmd}' is not installed or not available."
|
350
381
|
puts "Please install it before continuing."
|
@@ -352,48 +383,5 @@ module LxDev
|
|
352
383
|
end
|
353
384
|
end
|
354
385
|
end
|
355
|
-
|
356
|
-
def self.create_sudoers_file
|
357
|
-
self.check_requirements
|
358
|
-
user = System.exec("whoami").output.chomp
|
359
|
-
content = []
|
360
|
-
content << "# Created by lxdev #{Time.now}"
|
361
|
-
WHITELISTED_SUDO_COMMANDS.each do |cmd|
|
362
|
-
cmd_with_path = System.exec("which #{cmd}").output.chomp
|
363
|
-
content << "#{user} ALL=(root) NOPASSWD: #{cmd_with_path}"
|
364
|
-
end
|
365
|
-
content << "\n"
|
366
|
-
puts <<-EOS
|
367
|
-
!! WARNING !!
|
368
|
-
This will create a file, /etc/sudoers.d/lxdev,
|
369
|
-
which will give your user #{user} access to running
|
370
|
-
the following commands :
|
371
|
-
#{WHITELISTED_SUDO_COMMANDS.join(" ")}
|
372
|
-
with superuser privileges. If you do not know what you're
|
373
|
-
doing, this can be dangerous and insecure.
|
374
|
-
|
375
|
-
EOS
|
376
|
-
puts "The following content will be created in /etc/sudoers.d/lxdev :"
|
377
|
-
puts
|
378
|
-
puts content
|
379
|
-
puts "\nIf you want to do this, type 'yesplease'"
|
380
|
-
action = STDIN.gets.chomp
|
381
|
-
unless action == 'yesplease'
|
382
|
-
puts "Not creating sudoers file"
|
383
|
-
return
|
384
|
-
end
|
385
|
-
temp_file = Tempfile.create('lxdev-sudoers')
|
386
|
-
temp_file.write(content.join("\n"))
|
387
|
-
temp_file_name = temp_file.to_path
|
388
|
-
temp_file.close
|
389
|
-
unless System.exec("visudo -c -f #{temp_file_name}").exitstatus == 0
|
390
|
-
puts "Generated sudoers file contains errors, aborting."
|
391
|
-
exit 1
|
392
|
-
end
|
393
|
-
System.exec("sudo chown root:root #{temp_file_name}")
|
394
|
-
System.exec("sudo chmod 0440 #{temp_file_name}")
|
395
|
-
System.exec("sudo mv #{temp_file_name} /etc/sudoers.d/lxdev")
|
396
|
-
puts "Created sudoers file."
|
397
|
-
end
|
398
386
|
end
|
399
387
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lxdev
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christian Lønaas
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-03-
|
12
|
+
date: 2020-03-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|