lxdev 0.1.8 → 0.2.1
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 +4 -4
- data/bin/lxdev +2 -5
- data/lib/lxdev/main.rb +69 -81
- metadata +6 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb972fcc30452bfe0545f20313f64c1d81b0f55ee13355d4db2bc38feb9254ee
|
4
|
+
data.tar.gz: 27aeb08fc416c2eca7f34c773edb4204ff545103886034f9de01194666aa26b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c02623cee7ee35815dc6846bbc3a9b313610612b0f5600620ca6d1d9fc6b0a4da7f02b5005ed6c180fe1b9283a7111a20c226ececd332d262213b16a43ff2433
|
7
|
+
data.tar.gz: 59d924897d54fe732b40c68f92c4c0fd03044ede08d0b672ef3c1f340b2ba64f42c7bb452563f332937b35e8923dfff4a77503ddc3763791159e9eb25d4233a7
|
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.1
|
13
|
+
VERSION = '0.2.1'
|
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
|
|
@@ -212,17 +230,17 @@ module LxDev
|
|
212
230
|
end
|
213
231
|
|
214
232
|
def remove_state
|
215
|
-
File.delete(@state_file) if File.
|
233
|
+
File.delete(@state_file) if File.exist?(@state_file)
|
216
234
|
@state = {}
|
217
235
|
end
|
218
236
|
|
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,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lxdev
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christian Lønaas
|
8
8
|
- Eivind Mork
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-05-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|
@@ -53,7 +53,7 @@ homepage: https://github.com/GyldendalDigital/lxdev
|
|
53
53
|
licenses:
|
54
54
|
- MIT
|
55
55
|
metadata: {}
|
56
|
-
post_install_message:
|
56
|
+
post_install_message:
|
57
57
|
rdoc_options: []
|
58
58
|
require_paths:
|
59
59
|
- lib
|
@@ -68,9 +68,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: '0'
|
70
70
|
requirements: []
|
71
|
-
|
72
|
-
|
73
|
-
signing_key:
|
71
|
+
rubygems_version: 3.4.20
|
72
|
+
signing_key:
|
74
73
|
specification_version: 4
|
75
74
|
summary: Automagic development environment with LXD
|
76
75
|
test_files: []
|