class LxDev::Main
Constants
- BOOT_TIMEOUT
- REQUIRED_COMMANDS
- SHELLS
- VERSION
Public Class Methods
new(config_file, state_file, lxc_command)
click to toggle source
# File lib/lxdev/main.rb, line 15 def initialize(config_file, state_file, lxc_command) @state_file = format(".lxdev/%s", state_file) @uid = System.exec("id -u").output.chomp @gid = System.exec("id -g").output.chomp @config = YAML.load_file(config_file) @name = @config['box']['name'] @image = @config['box']['image'] @user = @config['box']['user'] @ports = @config['box']['ports'] || {} @lxc_command = lxc_command Dir.mkdir('.lxdev') unless File.directory?('.lxdev') begin @state = YAML.load_file(@state_file) rescue @state = Hash.new end rescue Errno::ENOENT puts "#{config_file} not found" exit 1 end
setup(config_file = 'lxdev.yml', state_file = 'state')
click to toggle source
# File lib/lxdev/main.rb, line 36 def self.setup(config_file = 'lxdev.yml', state_file = 'state') self.check_requirements unless lxd_initialized? puts "Please run 'lxd init' and configure LXD first" return false end user_in_group = Main.user_in_lxd_group? lxc_command = (user_in_group ? "lxc" : "sudo lxc") unless(user_in_group) puts "Add yourself to the 'lxd' group to avoid entering the sudo password :" puts "(You need to login again afterwards, or start a new login shell)" puts " sudo usermod -a -G lxd #{Etc.getlogin}" end lxdev = Main.new(config_file, state_file, lxc_command) unless lxdev.set_ssh_keys 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." return false end return lxdev end
user_in_lxd_group?()
click to toggle source
# File lib/lxdev/main.rb, line 57 def self.user_in_lxd_group? begin Etc.getgrnam("lxd").mem.include?(Etc.getlogin) rescue ArgumentError puts "There seems to be no 'lxd' group. Is LXD installed?" false end end
Private Class Methods
check_requirements()
click to toggle source
# File lib/lxdev/main.rb, line 377 def self.check_requirements REQUIRED_COMMANDS.each do |cmd| unless System.exec("which #{cmd}").exitstatus == 0 puts "The command '#{cmd}' is not installed or not available." puts "Please install it before continuing." exit 1 end end end
lxd_initialized?()
click to toggle source
# File lib/lxdev/main.rb, line 219 def self.lxd_initialized? exitstatus = System.exec("#{@lxc_command} info | grep 'lxd init'").exitstatus exitstatus != 0 end
Public Instance Methods
destroy()
click to toggle source
# File lib/lxdev/main.rb, line 140 def destroy ensure_container_created System.exec("#{@lxc_command} delete #{@name}") end
execute(command, interactive: false)
click to toggle source
# File lib/lxdev/main.rb, line 156 def execute(command, interactive: false) if interactive exec("#{@lxc_command} exec #{@name} #{command}") # execution stops here and gives control to exec end IO.popen("#{@lxc_command} exec #{@name} -- /bin/sh -c '#{command}'", err: [:child, :out]) do |cmd_output| cmd_output.each do |line| puts line end end end
halt()
click to toggle source
# File lib/lxdev/main.rb, line 133 def halt ensure_container_created System.exec("#{@lxc_command} stop #{@name}") cleanup_forwarded_ports remove_state end
provision()
click to toggle source
# File lib/lxdev/main.rb, line 167 def provision ensure_container_created if get_container_status.first['status'] != 'Running' puts "#{@name} is not running!" exit 1 end provisioning = @config['box']['provisioning'] if provisioning.nil? puts "Nothing to do" return end if @config['box']['auto_snapshots'] snapshot_name = "provision_#{Time.now.to_i}" snapshot(snapshot_name) end puts "Provisioning #{@name}..." STDOUT.sync = true provisioning.each do |cmd| execute cmd end STDOUT.sync = false end
restore(snapshot_name)
click to toggle source
# File lib/lxdev/main.rb, line 195 def restore(snapshot_name) puts "Restoring snapshot #{snapshot_name}" exitstatus = System.exec("#{@lxc_command} restore #{@name} #{snapshot_name}").exitstatus exitstatus == 0 end
revert()
click to toggle source
# File lib/lxdev/main.rb, line 207 def revert snapshot = get_container_status.first['snapshots'].last snapshot_name = snapshot['name'].partition('/').last if restore(snapshot_name) puts "Reverted to snapshot #{snapshot_name}" puts "Deleting snapshot" rmsnapshot(snapshot_name) end end
rmsnapshot(snapshot_name)
click to toggle source
# File lib/lxdev/main.rb, line 201 def rmsnapshot(snapshot_name) puts "Deleting snapshot #{snapshot_name}" exitstatus = System.exec("#{@lxc_command} delete #{@name}/#{snapshot_name}").exitstatus exitstatus == 0 end
save_state()
click to toggle source
# File lib/lxdev/main.rb, line 66 def save_state File.open(@state_file, 'w') {|f| f.write @state.to_yaml} unless @state.empty? end
set_ssh_keys()
click to toggle source
# File lib/lxdev/main.rb, line 70 def set_ssh_keys ssh_keys = System.exec("ssh-add -L").output if ssh_keys[0..3] == 'ssh-' @ssh_keys = ssh_keys else nil end end
snapshot(snapshot_name)
click to toggle source
# File lib/lxdev/main.rb, line 190 def snapshot(snapshot_name) puts "Creating snapshot #{snapshot_name}" System.exec("#{@lxc_command} snapshot #{@name} #{snapshot_name}") end
ssh(args)
click to toggle source
# File lib/lxdev/main.rb, line 145 def ssh(args) ensure_container_created host = get_container_ip if host.nil? puts "#{@name} doesn't seem to be running." exit 1 end ssh_command = "ssh -o StrictHostKeyChecking=no -t #{@user}@#{get_container_ip} #{args.empty? ? '' : "'#{args.join(' ')}'"}" exec ssh_command end
status()
click to toggle source
# File lib/lxdev/main.rb, line 79 def status ensure_container_created container_status = get_container_status folders = container_status.first['devices'].map {|name, folders| [name, "#{folders['source']} => #{folders['path']}"] if folders['source']}.compact table = Terminal::Table.new do |t| t.add_row ['Name', container_status.first['name']] t.add_row ['Status', container_status.first['status']] t.add_row ['IP', get_container_ip] t.add_row ['Image', @image] t.add_separator folders.each do |folder| t.add_row folder end t.add_separator @ports.each do |guest, host| t.add_row ['Forwarded port', "guest: #{guest} host: #{host}"] end if container_status.first['snapshots'].any? t.add_separator t.add_row ['Snapshots', ''] end container_status.first['snapshots'].each do |snapshot| t.add_row [snapshot['name'].partition('/').last, snapshot['created_at']] end end puts table end
up()
click to toggle source
# File lib/lxdev/main.rb, line 108 def up do_provision = false unless @state.empty? puts "Container state #{@state_file} exists, is it running? If not it might have stopped unexpectedly. Please remove the file before starting." exit 1 end if get_container_status.empty? create_container do_provision = true else if get_container_status.first['status'] == 'Running' puts "#{@name} is already running!" exit 1 else start_container end end puts "Waiting for boot..." wait_for_boot @state['status'] = 'running' puts "Forwarding ports..." forward_ports(@ports) provision if do_provision end
Private Instance Methods
abort_boot()
click to toggle source
# File lib/lxdev/main.rb, line 362 def abort_boot puts "Timeout waiting for container to boot" exit 1 end
add_subuid_and_subgid()
click to toggle source
# File lib/lxdev/main.rb, line 267 def add_subuid_and_subgid need_restart = false if System.exec("grep -q 'root:#{@uid}:1' /etc/subuid").exitstatus != 0 System.exec("echo 'root:#{@uid}:1' | sudo tee -a /etc/subuid") need_restart = true end if System.exec("grep -q 'root:#{@gid}:1' /etc/subgid").exitstatus != 0 System.exec("echo 'root:#{@gid}:1' | sudo tee -a /etc/subgid") need_restart = true end if need_restart begin System.exec("sudo systemctl restart #{lxd_service_name}") rescue puts "The LXD service needs to be restarted, but the service name cannot be detected. Please restart it manually." end end end
cleanup_forwarded_ports()
click to toggle source
# File lib/lxdev/main.rb, line 328 def cleanup_forwarded_ports if @state.empty? return end @state['redir_pids']&.each do |pid| System.exec("kill #{pid}") end @state['sudo_redir_pids']&.each do |pid| puts "Killing pid #{pid} started with sudo privileges" System.exec("sudo kill #{pid}") end end
create_container()
click to toggle source
# File lib/lxdev/main.rb, line 237 def create_container add_subuid_and_subgid puts "Launching #{@name}..." System.exec("#{@lxc_command} init #{@image} #{@name}") System.exec(%{printf "uid #{@uid} 1001\ngid #{@gid} 1001"| #{@lxc_command} config set #{@name} raw.idmap -}) System.exec(%{#{@lxc_command} config set #{@name} boot.autostart false}) System.exec("#{@lxc_command} start #{@name}") puts "Creating user #{@user}..." create_container_user(@user) puts "Mapping folders.." map_folders(@config['box']['folders']) end
create_container_user(user)
click to toggle source
# File lib/lxdev/main.rb, line 286 def create_container_user(user) System.exec("#{@lxc_command} exec #{@name} -- groupadd --gid 1001 #{user}") System.exec("#{@lxc_command} exec #{@name} -- useradd --uid 1001 --gid 1001 -s /bin/bash -m #{user}") System.exec("#{@lxc_command} exec #{@name} -- mkdir /home/#{user}/.ssh") System.exec("#{@lxc_command} exec #{@name} -- chmod 0700 /home/#{user}/.ssh") System.exec("printf '#{@ssh_keys}' | #{@lxc_command} exec #{@name} tee /home/#{user}/.ssh/authorized_keys") System.exec("#{@lxc_command} exec #{@name} -- chown -R #{user} /home/#{user}/.ssh") System.exec("#{@lxc_command} exec #{@name} -- touch /home/#{@user}/.hushlogin") System.exec("#{@lxc_command} exec #{@name} -- chown #{user} /home/#{user}/.hushlogin") System.exec(%{printf "#{user} ALL=(ALL) NOPASSWD: ALL\n" | #{@lxc_command} exec #{@name} -- tee -a /etc/sudoers}) System.exec("#{@lxc_command} exec #{@name} -- chmod 0440 /etc/sudoers") end
ensure_container_created()
click to toggle source
# File lib/lxdev/main.rb, line 224 def ensure_container_created container_status = get_container_status unless container_status.size > 0 puts "Container not created yet. Run lxdev up" exit(0) end end
forward_ports(ports)
click to toggle source
# File lib/lxdev/main.rb, line 308 def forward_ports(ports) redir_pids = [] sudo_redir_pids = [] ports.each do |guest, host| if (host > 1024) puts "Forwarding #{get_container_ip}:#{guest} to local port #{host}" pid = System.spawn_exec("redir --caddr=#{get_container_ip} --cport=#{guest} --lport=#{host}", silent: true) redir_pids << pid Process.detach(pid) else puts "Forwarding #{get_container_ip}:#{guest} to local port #{host} (root privileges needed)" pid = System.spawn_exec("sudo redir --caddr=#{get_container_ip} --cport=#{guest} --lport=#{host}", silent: true) sudo_redir_pids << pid Process.detach(pid) end end @state['redir_pids'] = redir_pids @state['sudo_redir_pids'] = sudo_redir_pids end
get_container_ip()
click to toggle source
# File lib/lxdev/main.rb, line 261 def get_container_ip get_container_status.first['state']['network']['eth0']['addresses'].select {|addr| addr['family'] == 'inet'}.first['address'] rescue nil end
get_container_status()
click to toggle source
# File lib/lxdev/main.rb, line 255 def get_container_status return @status unless @status.nil? command_result = System.exec("#{@lxc_command} list ^#{@name}$ --format=json") @status = JSON.parse(command_result.output) end
get_snapshots()
click to toggle source
# File lib/lxdev/main.rb, line 351 def get_snapshots snapshots = [] get_container_status.first['snapshots'].each do |snapshot| result = {} result['name'] = snapshot['name'] result['date'] = snapshot['created_at'] snapshots << result end snapshots end
lxd_service_name()
click to toggle source
# File lib/lxdev/main.rb, line 367 def lxd_service_name if System.exec("systemctl status lxd.service").exitstatus == 0 'lxd.service' elsif System.exec("systemctl status snap.lxd.daemon.service").exitstatus == 0 'snap.lxd.daemon.service' else raise 'There seems to be no LXD service on the system!' end end
map_folders(folders)
click to toggle source
# File lib/lxdev/main.rb, line 341 def map_folders(folders) counter = 0 folders.each do |host, guest| counter = counter + 1 puts "Mounting #{host} in #{guest}" absolute_path = System.exec("readlink -f #{host}").output.chomp System.exec("#{@lxc_command} config device add #{@name} shared_folder_#{counter} disk source=#{absolute_path} path=#{guest}") end end
remove_state()
click to toggle source
# File lib/lxdev/main.rb, line 232 def remove_state File.delete(@state_file) if File.exists?(@state_file) @state = {} end
start_container()
click to toggle source
# File lib/lxdev/main.rb, line 250 def start_container puts "Starting #{@name}..." System.exec("#{@lxc_command} start #{@name}") end
wait_for_boot()
click to toggle source
# File lib/lxdev/main.rb, line 299 def wait_for_boot BOOT_TIMEOUT.times do |t| @status = nil # reset status for each iteration to refresh IP break if get_container_ip abort_boot if t == (BOOT_TIMEOUT - 1) sleep 1 end end