diff --git a/neondocker/neondocker.rb b/neondocker/neondocker.rb index 9cea407..2f89e98 100755 --- a/neondocker/neondocker.rb +++ b/neondocker/neondocker.rb @@ -1,232 +1,232 @@ #!/usr/bin/env ruby # Copyright 2017 Jonathan Riddell # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of # the License or (at your option) version 3 or any later version # accepted by the membership of KDE e.V. (or its successor approved # by the membership of KDE e.V.), which shall act as a proxy # defined in Section 14 of version 3 of the license. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . begin require 'docker' rescue LoadError puts 'Could not find docker-api library, run: sudo gem install docker-api' exit 1 end require 'optparse' require 'mkmf' # A wee command to simplify running KDE neon Docker images. # # KDE neon Docker images are the fastest and easiest way to test out KDE's # software. You can use them on top of any Linux distro. # # ## Pre-requisites # # Install Docker and ensure you add yourself into the necessary group. # Also install Xephyr which is the X-server-within-a-window to run # Plasma. With Ubuntu this is: # # ```apt install docker.io xserver-xephyr # usermod -G docker # newgrp docker # ``` # # # Run # # To run a full Plasma session of Neon Developer Unstable Edition: # `neondocker` # # To run a full Plasma session of Neon User Edition: # `neondocker --edition user` # # For more options see # `neondocker --help` class NeonDocker attr_accessor :options # settings attr_accessor :tag # docker image tag to use attr_accessor :container # my Docker::Container def command_options @options = { pull: false, all: false, edition: 'dev-unstable', kill: false } OptionParser.new do |opts| opts.banner = 'Usage: neondocker [options] [standalone-application]' opts.on('-p', '--pull', 'Always pull latest version') do |v| @options[:pull] = v end opts.on('-a', '--all', 'Use Neon All images (larger, contains all apps)') do |v| @options[:all] = v end opts.on('-e', '--edition EDITION', '[user-lts,user,dev-stable,dev-unstable]') do |v| @options[:edition] = v end opts.on('-k', '--keep-alive', 'keep-alive container on exit') do |v| @options[:keep_alive] = v end opts.on('-r', '--reattach', 'reuse an existing container [assumes -k]') do |v| @options[:reattach] = v end opts.on('-n', '--new', 'Always start a new container even if one is already running' \ 'from the requested image') { |v| @options[:new] = v } opts.on('-w', '--wayland', 'Run a Wayland session') do |v| @options[:wayland] = v end opts.on_tail('standalone-application: Run a standalone application ' \ 'rather than full Plasma shell. Assumes -n to always ' \ 'start a new container.') end.parse! edition_options = ['user-lts', 'user', 'dev-stable', 'dev-unstable'] unless edition_options.include?(@options[:edition]) puts "Unknown edition. Valid editions are: #{edition_options}" exit 1 end @options end def validate_docker Docker.validate_version! rescue puts 'Could not connect to Docker, check it is installed, running and ' \ 'your user is in the right group for access' exit 1 end # Has the image already been downloaded to the local Docker? def docker_has_image? !Docker::Image.all.find do |image| next false if image.info['RepoTags'].nil? image.info['RepoTags'].include?(@tag) end.nil? end def docker_image_tag image_type = @options[:all] ? 'all' : 'plasma' @tag = 'kdeneon/' + image_type + ':' + @options[:edition] end def docker_pull puts "Downloading image #{@tag}" Docker::Image.create('fromImage' => @tag) end # Is the command available to run? def installed?(command) MakeMakefile.find_executable(command) end def running_xhost unless installed?('xhost') puts 'xhost is not installed, run apt install xserver-xephyr or similar' exit 1 end system('xhost +') yield system('xhost -') end def xdisplay return @xdisplay if defined? @xdisplay @xdisplay = (0..1024).find { |i| !File.exist?("/tmp/.X11-unix/X#{i}") } end def running_xephyr installed = installed?('Xephyr') unless installed puts 'Xephyr is not installed, apt-get install xserver-xephyr or similar' exit 1 end xephyr = IO.popen("Xephyr -screen 1024x768 :#{xdisplay}") yield Process.kill('KILL', xephyr.pid) end # If this image already has a container then use that, else start a new one def container return @container if defined? @container if @options[:reattach] all_containers = Docker::Container.all(all: true) all_containers.each do |container| if container.info['Image'] == @tag @container = Docker::Container.get(container.info['id']) end end begin @container = Docker::Container.create('Image' => @tag) rescue Docker::Error::NotFoundError puts "Could not find an image with @tag #{@tag}" return nil end elsif !ARGV.empty? @container = Docker::Container.create('Image' => @tag, 'Cmd' => ARGV, 'Env' => ['DISPLAY=:0']) elsif @options[:wayland] @container = Docker::Container.create('Image' => @tag, 'Env' => ['DISPLAY=:0'], 'Cmd' => ['startplasmacompositor']) else @container = Docker::Container.create('Image' => @tag, 'Env' => ["DISPLAY=:#{xdisplay}"]) end @container end # runs the container and wait until Plasma or whatever has stopped running def run_container # find devices to bind for Wayland devices = Dir['/dev/dri/*'] + Dir['/dev/video*'] devices_list = [] devices.each do |dri| devices_list.push('PathOnHost' => dri, 'PathInContainer' => dri, 'CgroupPermissions' => 'mrw') end container.start('Binds' => ['/tmp/.X11-unix:/tmp/.X11-unix'], 'Devices' => devices_list, 'Privileged' => true) - container.refresh! if container.respond_to? :refresh! - status = container.info.fetch('State', [])['Status'] || container.json.fetch('State').fetch('Status') - while status == 'running' - sleep 1 + loop do container.refresh! if container.respond_to? :refresh! - status = container.info.fetch('State', [])['Status'] || container.json.fetch('State').fetch('Status') + status = container.info.fetch('State', [])['Status'] + status ||= container.json.fetch('State').fetch('Status') + break if status == 'running' + sleep 1 end container.delete if !@options[:keep_alive] || @options[:reattach] end end if $PROGRAM_NAME == __FILE__ neon_docker = NeonDocker.new options = neon_docker.command_options neon_docker.validate_docker neon_docker.docker_image_tag options[:pull] = true unless neon_docker.docker_has_image? neon_docker.docker_pull if options[:pull] if !ARGV.empty? || options[:wayland] neon_docker.running_xhost do neon_docker.run_container end else neon_docker.running_xephyr do neon_docker.run_container end end exit 0 end