├── lib ├── vagrant-vbguest │ ├── version.rb │ ├── vagrant_compat │ │ ├── vagrant_1_1 │ │ │ ├── download.rb │ │ │ ├── vm_compatible.rb │ │ │ ├── command.rb │ │ │ └── rebootable.rb │ │ ├── vagrant_1_2 │ │ │ ├── command.rb │ │ │ ├── rebootable.rb │ │ │ ├── vm_compatible.rb │ │ │ └── download.rb │ │ ├── vagrant_1_3 │ │ │ ├── command.rb │ │ │ ├── download.rb │ │ │ ├── rebootable.rb │ │ │ └── vm_compatible.rb │ │ └── vagrant_1_0 │ │ │ ├── command.rb │ │ │ ├── rebootable.rb │ │ │ ├── vm_compatible.rb │ │ │ └── download.rb │ ├── errors.rb │ ├── vagrant_compat.rb │ ├── download.rb │ ├── installers │ │ ├── redhat.rb │ │ ├── ubuntu.rb │ │ ├── debian.rb │ │ ├── linux.rb │ │ └── base.rb │ ├── rebootable.rb │ ├── middleware.rb │ ├── config.rb │ ├── command.rb │ ├── machine.rb │ ├── hosts │ │ ├── virtualbox.rb │ │ └── base.rb │ ├── installer.rb │ └── core_ext │ │ └── string │ │ └── interpolate.rb ├── vagrant_init.rb └── vagrant-vbguest.rb ├── .gitignore ├── Rakefile ├── Gemfile ├── LICENSE ├── vagrant-vbguest.gemspec ├── locales └── en.yml ├── CHANGELOG.md └── Readme.md /lib/vagrant-vbguest/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | VERSION = "0.10.1.dev" 3 | end 4 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_1/download.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../../vagrant_1_0/download", __FILE__) 2 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_2/command.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../../vagrant_1_1/command", __FILE__) 2 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_3/command.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../../vagrant_1_2/command", __FILE__) 2 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_3/download.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../../vagrant_1_2/download", __FILE__) 2 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_2/rebootable.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../../vagrant_1_1/rebootable", __FILE__) 2 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_3/rebootable.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../../vagrant_1_2/rebootable", __FILE__) 2 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_3/vm_compatible.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../../vagrant_1_2/vm_compatible", __FILE__) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | 4 | # Immediately sync all stdout so that tools like buildbot can 5 | # immediately load in the output. 6 | $stdout.sync = true 7 | $stderr.sync = true 8 | 9 | # This installs the tasks that help with gem creation and 10 | # publishing. 11 | Bundler::GemHelper.install_tasks 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in vagrant-vbguest.gemspec 4 | gemspec 5 | 6 | group :development do 7 | # We depend on Vagrant for development, but we don't add it as a 8 | # gem dependency because we expect to be installed within the 9 | # Vagrant environment itself using `vagrant plugin`. 10 | gem "vagrant", :git => "git://github.com/mitchellh/vagrant.git" 11 | end 12 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_2/vm_compatible.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../../vagrant_1_1/vm_compatible", __FILE__) 2 | module VagrantVbguest 3 | module Helpers 4 | module VmCompatible 5 | def self.included(base) 6 | base.extend(ClassMethods) 7 | end 8 | module ClassMethods 9 | def distro_name(vm) 10 | vm.guest.name 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/errors.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | 3 | class VbguestError < Vagrant::Errors::VagrantError 4 | def error_namespace; "vagrant_vbguest.errors"; end 5 | end 6 | 7 | class IsoPathAutodetectionError < VagrantVbguest::VbguestError 8 | error_key :autodetect_iso_path 9 | end 10 | 11 | class DownloadingDisabledError < VagrantVbguest::VbguestError 12 | error_key :downloading_disabled 13 | end 14 | 15 | class NoVirtualBoxMachineError < VagrantVbguest::VbguestError 16 | error_key :no_virtualbox_machine 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_2/download.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-vbguest/download' 2 | require "vagrant/util/downloader" 3 | 4 | module VagrantVbguest 5 | 6 | class Download < DownloadBase 7 | 8 | def download! 9 | downloader_options = {} 10 | downloader_options[:ui] = @ui 11 | @ui.info(I18n.t("vagrant_vbguest.download.started", :source => @source)) 12 | @downloader = Vagrant::Util::Downloader.new(@source, @destination, downloader_options) 13 | @downloader.download! 14 | end 15 | 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_0/command.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-vbguest/command' 2 | require 'vagrant/command/start_mixins' 3 | 4 | module VagrantVbguest 5 | 6 | class Command < Vagrant::Command::Base 7 | include CommandCommons 8 | include Vagrant::Command::StartMixins 9 | 10 | def check_runable_on(vm) 11 | raise Vagrant::Errors::VMNotCreatedError if !vm.created? 12 | raise Vagrant::Errors::VMInaccessible if !vm.state == :inaccessible 13 | raise Vagrant::Errors::VMNotRunningError if vm.state != :running 14 | end 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_0/rebootable.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-vbguest/rebootable' 2 | 3 | module VagrantVbguest 4 | module Helpers 5 | 6 | module Rebootable 7 | def reboot(vm, options) 8 | if reboot? vm, options 9 | @env[:action_runner].run(Vagrant::Action::VM::Halt, @env) 10 | @env[:action_runner].run(Vagrant::Action::VM::Boot, @env) 11 | end 12 | end 13 | 14 | # executes the whole reboot process 15 | def reboot!(vm, options) 16 | if reboot? vm, options 17 | vm.reload(options) 18 | end 19 | end 20 | end 21 | 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_0/vm_compatible.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | module Helpers 3 | module VmCompatible 4 | def communicate 5 | vm.channel 6 | end 7 | 8 | def driver 9 | vm.driver 10 | end 11 | 12 | def self.included(base) 13 | base.extend(ClassMethods) 14 | end 15 | 16 | module ClassMethods 17 | def vm_id(vm) 18 | vm.uuid 19 | end 20 | 21 | def communicate_to(vm) 22 | vm.channel 23 | end 24 | 25 | def distro_name(vm) 26 | vm.guest.distro_dispatch 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_1/vm_compatible.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | module Helpers 3 | module VmCompatible 4 | def communicate 5 | vm.communicate 6 | end 7 | 8 | def driver 9 | vm.provider.driver 10 | end 11 | 12 | def self.included(base) 13 | base.extend(ClassMethods) 14 | end 15 | 16 | module ClassMethods 17 | def vm_id(vm) 18 | vm.id 19 | end 20 | 21 | def communicate_to(vm) 22 | vm.communicate 23 | end 24 | 25 | def distro_name(vm) 26 | vm.guest.distro_dispatch 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat.rb: -------------------------------------------------------------------------------- 1 | vagrant_version = Gem::Version.new(Vagrant::VERSION) 2 | supported_version = { 3 | "< 1.1.0" => "1_0", 4 | "~> 1.1.0" => "1_1", 5 | "~> 1.2.0" => "1_2", 6 | "~> 1.3.0" => "1_3", 7 | } 8 | compat_version = supported_version.find { |requirement, version| 9 | Gem::Requirement.new(requirement).satisfied_by?(vagrant_version) 10 | } 11 | 12 | if compat_version 13 | compat_version = compat_version[1] 14 | else 15 | # @TODO: yield warning 16 | compat_version = supported_version.to_a.last[1] 17 | end 18 | 19 | %w{vm_compatible rebootable command download}.each do |r| 20 | require File.expand_path("../vagrant_compat/vagrant_#{compat_version}/#{r}", __FILE__) 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_1/command.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-vbguest/command' 2 | require Vagrant.source_root.join("plugins/commands/up/start_mixins") 3 | 4 | module VagrantVbguest 5 | 6 | class Command < Vagrant.plugin("2", :command) 7 | include CommandCommons 8 | include VagrantPlugins::CommandUp::StartMixins 9 | 10 | def check_runable_on(vm) 11 | raise Vagrant::Errors::VMNotCreatedError if vm.state.id == :not_created 12 | raise Vagrant::Errors::VMInaccessible if vm.state.id == :inaccessible 13 | raise Vagrant::Errors::VMNotRunningError if vm.state.id != :running 14 | raise VagrantVbguest::NoVirtualBoxMachineError if vm.provider.class != VagrantPlugins::ProviderVirtualBox::Provider 15 | end 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/download.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | 3 | class DownloadBase 4 | attr_reader :source, :destination, :downloader 5 | 6 | def initialize(source, destination, options=nil) 7 | @downloader = nil 8 | @source = source 9 | @destination = destination 10 | if File.directory?(destination) 11 | @destination = File.join(destination, "vbguest_download_#{Time.now.to_i.to_s}") 12 | end 13 | @ui = options[:ui] 14 | end 15 | 16 | def download! 17 | raise NotImplementedError 18 | end 19 | 20 | def cleanup 21 | if destination && File.exist?(destination) 22 | @ui.info I18n.t("vagrant_vbguest.download.cleaning") 23 | File.unlink(destination) 24 | end 25 | end 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/installers/redhat.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | module Installers 3 | class RedHat < Linux 4 | # Scientific Linux (and probably CentOS) both show up as :redhat 5 | # fortunately they're probably both similar enough to RHEL 6 | # (RedHat Enterprise Linux) not to matter. 7 | def self.match?(vm) 8 | :redhat == self.distro(vm) 9 | end 10 | 11 | # Install missing deps and yield up to regular linux installation 12 | def install(opts=nil, &block) 13 | communicate.sudo(install_dependencies_cmd, opts, &block) 14 | super 15 | end 16 | 17 | protected 18 | def install_dependencies_cmd 19 | "yum install -y #{dependencies}" 20 | end 21 | 22 | def dependencies 23 | packages = ['kernel-devel-`uname -r`', 'gcc', 'make', 'perl'] 24 | packages.join ' ' 25 | end 26 | end 27 | end 28 | end 29 | VagrantVbguest::Installer.register(VagrantVbguest::Installers::RedHat, 5) 30 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/rebootable.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | module Helpers 3 | 4 | module Rebootable 5 | include VmCompatible 6 | def self.included(base) 7 | base.extend(ClassMethods) 8 | end 9 | 10 | @@rebooted = {} 11 | 12 | def rebooted?(vm) 13 | !!@@rebooted[ self.class.vm_id(vm) ] 14 | end 15 | 16 | def reboot?(vm, options) 17 | if rebooted?(vm) 18 | vm.env.ui.error(I18n.t("vagrant_vbguest.restart_loop_guard_activated")) 19 | false 20 | elsif options[:auto_reboot] 21 | vm.env.ui.warn(I18n.t("vagrant_vbguest.restart_vm")) 22 | @@rebooted[ self.class.vm_id(vm) ] = true 23 | else 24 | vm.env.ui.warn(I18n.t("vagrant_vbguest.suggest_restart", :name => vm.name)) 25 | false 26 | end 27 | end 28 | 29 | def reboot(vm, options) 30 | raise NotImplementedError 31 | end 32 | 33 | def reboot!(vm, options) 34 | raise NotImplementedError 35 | end 36 | end 37 | 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2011 Robert Schulze 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_1/rebootable.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-vbguest/rebootable' 2 | 3 | module VagrantVbguest 4 | module Helpers 5 | 6 | module Rebootable 7 | def reboot(vm, options) 8 | if reboot? vm, options 9 | simle_reboot = Vagrant::Action::Builder.new.tap do |b| 10 | b.use Vagrant::Action::Builtin::Call, Vagrant::Action::Builtin::GracefulHalt, :poweroff, :running do |env2, b2| 11 | if !env2[:result] 12 | b2.use VagrantPlugins::ProviderVirtualBox::Action::ForcedHalt 13 | end 14 | end 15 | b.use VagrantPlugins::ProviderVirtualBox::Action::Boot 16 | if defined?(Vagrant::Action::Builtin::WaitForCommunicator) 17 | b.use Vagrant::Action::Builtin::WaitForCommunicator, [:starting, :running] 18 | end 19 | end 20 | @env[:action_runner].run(simle_reboot, @env) 21 | end 22 | end 23 | 24 | # executes the whole reboot process 25 | def reboot!(vm, options) 26 | if reboot? vm, options 27 | vm.action(:reload, options) 28 | end 29 | end 30 | end 31 | 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /vagrant-vbguest.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/vagrant-vbguest/version', __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "vagrant-vbguest" 6 | s.version = VagrantVbguest::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.authors = ["Robert Schulze"] 9 | s.email = ["robert@dotless.de"] 10 | s.license = 'MIT' 11 | s.homepage = "https://github.com/dotless-de/vagrant-vbguest" 12 | s.summary = %q{A Vagrant plugin to install the VirtualBoxAdditions into the guest VM} 13 | s.description = %q{A Vagrant plugin which automatically installs the host's VirtualBox Guest Additions on the guest system.} 14 | 15 | s.required_rubygems_version = ">= 1.3.6" 16 | 17 | s.add_dependency "micromachine", "~> 1.1.0" 18 | s.add_development_dependency "bundler", ">= 1.2.0" 19 | 20 | # those should be satisfied by vagrant 21 | s.add_dependency "i18n" 22 | s.add_dependency "log4r" 23 | s.add_development_dependency "rake" 24 | 25 | s.files = `git ls-files`.split("\n") 26 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 27 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 28 | s.require_paths = ["lib"] 29 | 30 | end 31 | -------------------------------------------------------------------------------- /lib/vagrant_init.rb: -------------------------------------------------------------------------------- 1 | # This file is automatically loaded by Vagrant < 1.1 2 | # to load any plugins. This file kicks off this plugin. 3 | begin 4 | require "vagrant" 5 | rescue LoadError 6 | raise "The Vagrant VBGuest plugin must be run within Vagrant." 7 | end 8 | 9 | require 'vagrant-vbguest/core_ext/string/interpolate' 10 | 11 | require "vagrant-vbguest/errors" 12 | require 'vagrant-vbguest/vagrant_compat' 13 | 14 | require 'vagrant-vbguest/machine' 15 | 16 | require 'vagrant-vbguest/hosts/base' 17 | require 'vagrant-vbguest/hosts/virtualbox' 18 | 19 | require 'vagrant-vbguest/installer' 20 | require 'vagrant-vbguest/installers/base' 21 | require 'vagrant-vbguest/installers/linux' 22 | require 'vagrant-vbguest/installers/debian' 23 | require 'vagrant-vbguest/installers/ubuntu' 24 | require 'vagrant-vbguest/installers/redhat' 25 | 26 | require 'vagrant-vbguest/config' 27 | require 'vagrant-vbguest/command' 28 | require 'vagrant-vbguest/middleware' 29 | 30 | Vagrant.config_keys.register(:vbguest) { VagrantVbguest::Config } 31 | 32 | Vagrant.commands.register(:vbguest) { VagrantVbguest::Command } 33 | 34 | Vagrant.actions[:start].use VagrantVbguest::Middleware 35 | 36 | # Add our custom translations to the load path 37 | I18n.load_path << File.expand_path("../../locales/en.yml", __FILE__) 38 | 39 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/middleware.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | 3 | # A Vagrant middleware which checks the installed VirtualBox Guest 4 | # Additions to match the installed VirtualBox installation on the 5 | # host system. 6 | 7 | class Middleware 8 | include VagrantVbguest::Helpers::Rebootable 9 | 10 | def initialize(app, env) 11 | @app = app 12 | @env = env 13 | end 14 | 15 | def call(env) 16 | @env = env 17 | vm = env[:vm] || env[:machine] 18 | 19 | options = override_config(vm.config.vbguest.to_hash).freeze 20 | 21 | if options[:auto_update] 22 | machine = VagrantVbguest::Machine.new vm, options 23 | status = machine.state 24 | vm.env.ui.send((:ok == status ? :success : :warn), I18n.t("vagrant_vbguest.status.#{status}", machine.info)) 25 | machine.run 26 | reboot(vm, options) if machine.reboot? 27 | end 28 | rescue VagrantVbguest::Installer::NoInstallerFoundError => e 29 | vm.env.ui.error e.message 30 | ensure 31 | @app.call(env) 32 | end 33 | 34 | def override_config(opts) 35 | if opts[:auto_reboot] && Vagrant::VERSION.between?("1.1.0", "1.1.5") && Vagrant::VERSION != "1.1.4" 36 | @env[:ui].warn I18n.t("vagrant_vbguest.vagrant_11_reload_issues") 37 | opts.merge!({:auto_reboot => false}) 38 | end 39 | opts 40 | end 41 | 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/installers/ubuntu.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | module Installers 3 | class Ubuntu < Debian 4 | 5 | def self.match?(vm) 6 | :ubuntu == self.distro(vm) 7 | end 8 | 9 | def install(opts=nil, &block) 10 | if packaged_additions? 11 | unload_packaged_additions(opts, &block) 12 | remove_packaged_additions(opts, &block) 13 | end 14 | super 15 | end 16 | 17 | protected 18 | 19 | def packaged_additions? 20 | communicate.test("dpkg --list | grep virtualbox-guest") 21 | end 22 | 23 | def remove_packaged_additions(opts=nil, &block) 24 | options = (opts || {}).merge(:error_check => false) 25 | command = "apt-get -y -q purge virtualbox-guest-dkms virtualbox-guest-utils virtualbox-guest-x11" 26 | communicate.sudo(command, options, &block) 27 | end 28 | 29 | def unload_packaged_additions(opts=nil, &block) 30 | commands = [ 31 | "service virtualbox-guest-utils stop", 32 | "umount -a -t vboxsf", 33 | "rmmod vboxsf", 34 | "rmmod vboxguest" 35 | ] 36 | command = "(" + commands.join("; sleep 1; ") + ")" 37 | options = (opts || {}).merge(:error_check => false) 38 | communicate.sudo(command, options, &block) 39 | end 40 | 41 | end 42 | end 43 | end 44 | VagrantVbguest::Installer.register(VagrantVbguest::Installers::Ubuntu, 5) 45 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/installers/debian.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | module Installers 3 | class Debian < Linux 4 | 5 | def self.match?(vm) 6 | :debian == self.distro(vm) 7 | end 8 | 9 | # installes the correct linux-headers package 10 | # installes `dkms` package for dynamic kernel module loading 11 | # @param [Hash] opts Optional options Hash wich meight get passed to {Vagrant::Communication::SSH#execute} and firends 12 | # @yield [type, data] Takes a Block like {Vagrant::Communication::Base#execute} for realtime output of the command being executed 13 | # @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc. 14 | # @yieldparam [String] data Data for the given output. 15 | def install(opts=nil, &block) 16 | begin 17 | communicate.sudo(install_dependencies_cmd, opts, &block) 18 | rescue 19 | communicate.sudo('apt-get update', opts, &block) 20 | communicate.sudo(install_dependencies_cmd, opts, &block) 21 | end 22 | super 23 | end 24 | 25 | protected 26 | def install_dependencies_cmd 27 | "apt-get install -y #{dependencies}" 28 | end 29 | 30 | def dependencies 31 | packages = ['linux-headers-`uname -r`'] 32 | # some Debian system (lenny) dont come with a dkms packe so we neet to skip that. 33 | # apt-cache search will exit with 0 even if nothing was found, so we need to grep. 34 | packages << 'dkms' if communicate.test('apt-cache search --names-only \'^dkms$\' | grep dkms') 35 | packages.join ' ' 36 | end 37 | end 38 | end 39 | end 40 | VagrantVbguest::Installer.register(VagrantVbguest::Installers::Debian, 5) 41 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/vagrant_compat/vagrant_1_0/download.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-vbguest/download' 2 | module VagrantVbguest 3 | # This implementation is based on Action::Box::Download by vagrant 4 | # 5 | # This adoption does not run as a action/middleware, but is called manually 6 | # 7 | # MIT License - Mitchell Hashimoto and John Bender - https://github.com/mitchellh/vagrant 8 | # 9 | # 10 | # 11 | class Download < DownloadBase 12 | 13 | include Vagrant::Util 14 | 15 | def download! 16 | if instantiate_downloader 17 | File.open(@destination, Platform.tar_file_options) do |destination_file| 18 | @downloader.download!(@source, destination_file) 19 | end 20 | end 21 | @destination 22 | end 23 | 24 | def instantiate_downloader 25 | # Assign to a temporary variable since this is easier to type out, 26 | # since it is used so many times. 27 | classes = [Vagrant::Downloaders::HTTP, Vagrant::Downloaders::File] 28 | 29 | # Find the class to use. 30 | classes.each_index do |i| 31 | klass = classes[i] 32 | 33 | # Use the class if it matches the given URI or if this 34 | # is the last class... 35 | if classes.length == (i + 1) || klass.match?(@source) 36 | @ui.info I18n.t("vagrant_vbguest.download.with", :class => klass.to_s) 37 | @downloader = klass.new(@ui) 38 | break 39 | end 40 | end 41 | 42 | # This line should never be reached, but we'll keep this here 43 | # just in case for now. 44 | raise Errors::BoxDownloadUnknownType if !@downloader 45 | 46 | @downloader.prepare(@source) if @downloader.respond_to?(:prepare) 47 | true 48 | end 49 | 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require "vagrant" 3 | rescue LoadError 4 | raise "The Vagrant VBGuest plugin must be run within Vagrant." 5 | end 6 | 7 | # Add our custom translations to the load path 8 | I18n.load_path << File.expand_path("../../locales/en.yml", __FILE__) 9 | I18n.reload! 10 | 11 | require "vagrant-vbguest/errors" 12 | require 'vagrant-vbguest/vagrant_compat' 13 | 14 | require 'vagrant-vbguest/machine' 15 | 16 | require 'vagrant-vbguest/hosts/base' 17 | require 'vagrant-vbguest/hosts/virtualbox' 18 | 19 | require 'vagrant-vbguest/installer' 20 | require 'vagrant-vbguest/installers/base' 21 | require 'vagrant-vbguest/installers/linux' 22 | require 'vagrant-vbguest/installers/debian' 23 | require 'vagrant-vbguest/installers/ubuntu' 24 | require 'vagrant-vbguest/installers/redhat' 25 | 26 | require 'vagrant-vbguest/middleware' 27 | 28 | module VagrantVbguest 29 | 30 | class Plugin < Vagrant.plugin("2") 31 | 32 | name "vagrant-vbguest" 33 | description <<-DESC 34 | Provides automatic and/or manual management of the 35 | VirtualBox Guest Additions inside the Vagrant environment. 36 | DESC 37 | 38 | config('vbguest') do 39 | require File.expand_path("../vagrant-vbguest/config", __FILE__) 40 | Config 41 | end 42 | 43 | command('vbguest') do 44 | Command 45 | end 46 | 47 | # hook after anything that boots: 48 | # that's all middlewares which will run the buildin "VM::Boot" action 49 | action_hook('vbguest') do |hook| 50 | if defined?(VagrantPlugins::ProviderVirtualBox::Action::CheckGuestAdditions) 51 | hook.before(VagrantPlugins::ProviderVirtualBox::Action::CheckGuestAdditions, VagrantVbguest::Middleware) 52 | else 53 | hook.after(VagrantPlugins::ProviderVirtualBox::Action::Boot, VagrantVbguest::Middleware) 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/config.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | 3 | class Config <( Vagrant::VERSION < "1.1.0" ? Vagrant::Config::Base : Vagrant.plugin("2", :config) ) 4 | 5 | module Attributes 6 | attr_accessor :iso_path, :auto_update, :auto_reboot, :no_install, :no_remote, :installer, :installer_arguments 7 | end 8 | 9 | class << self 10 | include Attributes 11 | 12 | def auto_update; @auto_update.nil? ? true : @auto_update end 13 | def auto_reboot; @auto_reboot.nil? ? true : @auto_reboot end 14 | def no_install; @no_install.nil? ? false : @no_install end 15 | def no_remote; @no_remote.nil? ? false : @no_remote end 16 | def installer_arguments; @installer_arguments.nil? ? '--nox11' : @installer_arguments end 17 | 18 | def iso_path 19 | return nil if !@iso_path || @iso_path == :auto 20 | @iso_path 21 | end 22 | end 23 | 24 | include Attributes 25 | 26 | def auto_update; @auto_update.nil? ? self.class.auto_update : @auto_update end 27 | def auto_reboot; @auto_reboot.nil? ? self.class.auto_reboot : @auto_reboot end 28 | def no_install; @no_install.nil? ? self.class.no_install : @no_install end 29 | def no_remote; @no_remote.nil? ? self.class.no_remote : @no_remote end 30 | def installer_arguments; @installer_arguments.nil? ? self.class.installer_arguments : @installer_arguments end 31 | 32 | def iso_path 33 | return self.class.iso_path if !@iso_path || @iso_path == :auto 34 | @iso_path 35 | end 36 | 37 | # explicit hash, to get symbols in hash keys 38 | def to_hash 39 | { 40 | :installer => installer, 41 | :installer_arguments => installer_arguments, 42 | :iso_path => iso_path, 43 | :auto_update => auto_update, 44 | :auto_reboot => auto_reboot, 45 | :no_install => no_install, 46 | :no_remote => no_remote 47 | } 48 | end 49 | 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/command.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | module VagrantVbguest 4 | 5 | module CommandCommons 6 | include VagrantVbguest::Helpers::Rebootable 7 | def self.included(base) 8 | base.extend(ClassMethods) 9 | end 10 | 11 | # Runs the vbguest installer on the VMs that are represented 12 | # by this environment. 13 | def execute 14 | options = { 15 | :_method => :run, 16 | :_rebootable => true, 17 | :auto_reboot => false 18 | } 19 | opts = OptionParser.new do |opts| 20 | opts.banner = "Usage: vagrant vbguest [vm-name] [--do start|rebuild|install] [--status] [-f|--force] [-b|--auto-reboot] [-R|--no-remote] [--iso VBoxGuestAdditions.iso]" 21 | opts.separator "" 22 | 23 | opts.on("--do COMMAND", [:start, :rebuild, :install], "Manually `start`, `rebuild` or `install` GuestAdditions.") do |command| 24 | options[:_method] = command 25 | options[:force] = true 26 | end 27 | 28 | opts.on("--status", "Print current GuestAdditions status and exit.") do 29 | options[:_method] = :status 30 | options[:_rebootable] = false 31 | end 32 | 33 | opts.on("-f", "--force", "Whether to force the installation. (Implied by --do start|rebuild|install)") do 34 | options[:force] = true 35 | end 36 | 37 | opts.on("--auto-reboot", "-b", "Allow rebooting the VM after installation. (when GuestAdditions won't start)") do 38 | options[:auto_reboot] = true 39 | end 40 | 41 | opts.on("--no-remote", "-R", "Do not attempt do download the iso file from a webserver") do 42 | options[:no_remote] = true 43 | end 44 | 45 | opts.on("--iso file_or_uri", "Full path or URI to the VBoxGuestAdditions.iso") do |file_or_uri| 46 | options[:iso_path] = file_or_uri 47 | end 48 | 49 | build_start_options(opts, options) 50 | end 51 | 52 | 53 | argv = parse_options(opts) 54 | return if !argv 55 | 56 | if argv.empty? 57 | with_target_vms(nil) { |vm| execute_on_vm(vm, options) } 58 | else 59 | argv.each do |vm_name| 60 | with_target_vms(vm_name) { |vm| execute_on_vm(vm, options) } 61 | end 62 | end 63 | 64 | end 65 | 66 | protected 67 | 68 | # Executes a command on a specific VM. 69 | def execute_on_vm(vm, options) 70 | check_runable_on(vm) 71 | 72 | options = options.clone 73 | _method = options.delete(:_method) 74 | _rebootable = options.delete(:_rebootable) 75 | 76 | options = vm.config.vbguest.to_hash.merge(options) 77 | machine = VagrantVbguest::Machine.new(vm, options) 78 | status = machine.state 79 | vm.env.ui.send((:ok == status ? :success : :warn), I18n.t("vagrant_vbguest.status.#{status}", machine.info)) 80 | 81 | if _method != :status 82 | machine.send(_method) 83 | end 84 | 85 | reboot!(vm, options) if _rebootable && machine.reboot? 86 | rescue VagrantVbguest::Installer::NoInstallerFoundError => e 87 | vm.env.ui.error e.message 88 | end 89 | 90 | def check_runable_on(vm) 91 | raise NotImplementedError 92 | end 93 | end 94 | 95 | end 96 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/machine.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | class Machine 3 | 4 | require 'micromachine' 5 | 6 | attr_reader :installer, :env, :vm, :options 7 | 8 | def initialize vm, options 9 | @vm = vm 10 | @env = vm.env 11 | @options = options 12 | 13 | @logger = Log4r::Logger.new("vagrant::plugins::vbguest-machine") 14 | @logger.debug("initialize vbguest machine for VM '#{vm.name}' (#{vm.to_s})") 15 | 16 | @installer = Installer.new vm, options 17 | end 18 | 19 | def run 20 | current_state = state 21 | runlist = steps(current_state) 22 | @logger.debug("Runlist for state #{current_state} is: #{runlist}") 23 | while (command = runlist.shift) 24 | @logger.debug("Running command #{command} from runlist") 25 | if !self.send(command) 26 | env.ui.error('vagrant_vbguest.machine_loop_guard', :command => command, :state => current_state) 27 | return false 28 | end 29 | return run if current_state != state 30 | end 31 | true 32 | end 33 | 34 | def install 35 | return env.ui.warn(I18n.t("vagrant_vbguest.skipped_installation")) if options[:no_install] && !options[:force] 36 | guest_additions_state.trigger :install 37 | end 38 | 39 | def rebuild 40 | return env.ui.warn(I18n.t("vagrant_vbguest.skipped_rebuild")) if options[:no_install] && !options[:force] 41 | guest_additions_state.trigger :rebuild 42 | end 43 | 44 | def start 45 | guest_additions_state.trigger :start 46 | end 47 | 48 | def installation_ran?; guest_additions_state == :installed end 49 | def started?; guest_additions_state == :started end 50 | def rebuilt?; guest_additions_state == :rebuilt end 51 | 52 | def reboot; box_state.trigger :reboot end 53 | def reboot?; box_state.state == :rebooted end 54 | 55 | def steps(state) 56 | case state 57 | when :clean, :unmatched 58 | [:install] 59 | when :not_running 60 | installation_ran? ? [:reboot] : [:start, :rebuild, :reboot] 61 | else 62 | [] 63 | end 64 | end 65 | 66 | def state 67 | guest_version = installer.guest_version(true) 68 | host_version = installer.host_version 69 | running = installer.running? 70 | @logger.debug("Current states for VM '#{vm.name}' are : guest_version=#{guest_version} : host_version=#{host_version} : running=#{running}") 71 | 72 | return :clean if !guest_version 73 | return :unmatched if host_version != guest_version 74 | return :not_running if !running 75 | return :ok 76 | end 77 | 78 | def info 79 | { 80 | :host_version => installer.host_version, 81 | :guest_version => installer.guest_version 82 | } 83 | end 84 | 85 | protected 86 | 87 | def guest_additions_state 88 | @guest_additions_state ||= MicroMachine.new(:pending).tap { |m| 89 | m.when :install, :pending => :installed 90 | m.when :start, :pending => :started 91 | m.when :rebuild, :pending => :rebuilt, :started => :rebuilt 92 | 93 | m.on(:installed) { installer.install } 94 | m.on(:started) { installer.start } 95 | m.on(:rebuilt) { installer.rebuild } 96 | } 97 | end 98 | 99 | def box_state 100 | @box_state ||= MicroMachine.new(:first_boot).tap { |m| 101 | m.when :reboot, :first_boot => :rebooted 102 | } 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vagrant_vbguest: 3 | skipped_installation: "Updating GuestAdditions skipped." 4 | skipped_rebuild: "Rebuilding GuestAdditions skipped." 5 | installing: "Installing Virtualbox Guest Additions %{installer_version} - guest version is %{guest_version}" 6 | installing_forced: "Forcing installation of Virtualbox Guest Additions %{installer_version} - guest version is %{guest_version}" 7 | rebuild: "Rebuilding Virtualbox Guest Additions %{guest_version} (Your host version is %{host_version})" 8 | rebuild_forced: "Forcing rebuilding of Virtualbox Guest Additions %{guest_version} (Your host version is %{host_version})" 9 | install_error: |- 10 | An error occurred during installation of VirtualBox Guest Additions %{installer_version}. Some functionality may not work as intended. 11 | In most cases it is OK that the "Window System drivers" installation failed. 12 | start_copy_iso: "Copy iso file %{from} into the box %{to}" 13 | restart_vm: "Restarting VM to apply changes..." 14 | suggest_restart: "Guest Additions got installed. However, they seem not be loaded correctly. Please restart the box running `vagrant reload %{name}`" 15 | restart_loop_guard_activated: "Guest Additions will not load, even after reboot." 16 | machine_loop_guard: "Could not execute %{command} from current state %{state}. To prevent running in circles, we'll stop." 17 | guest_version_reports_differ: |- 18 | Got different reports about installed GuestAdditions version: 19 | Virtualbox on your host claims: %{driver} 20 | VBoxService inside the vm claims: %{service} 21 | Going on, assuming VBoxService is correct... 22 | vagrant_11_reload_issues: |- 23 | This version of vagrant has issues with reloading boxes. `auto_reboot` got disabled as a precaution. 24 | Expect errors and run `vagrant halt` and `vagrant up` instead of `vagrant reload`! 25 | unknown: unknown 26 | 27 | status: 28 | clean: "No installation found." 29 | unmatched: "GuestAdditions versions on your host (%{host_version}) and guest (%{guest_version}) do not match." 30 | not_running: "GuestAdditions seems to be installed (%{guest_version}) correctly, but not running." 31 | ok: "GuestAdditions %{guest_version} running --- OK." 32 | 33 | errors: 34 | autodetect_iso_path: |- 35 | Could not locate a local Virtualbox Guest Additions iso file. 36 | Please configure a local path to the iso using `config.vbguest.iso_path` 37 | in your Vagrantfile or the `--iso` command line option. 38 | If you think this is a bug and vbguest could have guessed the iso_path, 39 | please file an issue at: https://github.com/dotless-de/vagrant-vbguest/issues 40 | 41 | downloading_disabled: |- 42 | Could not locate a local Virtualbox Guest Additions iso file. 43 | However, the no_remote option was set and thus I will not download it from 44 | %{from} 45 | Please configure a local path to the iso using `config.vbguest.iso_path` 46 | in your Vagrantfile or the `--iso` command line option. 47 | 48 | no_virtualbox_machine: |- 49 | The VBGuest plugin must be used with VirtualBox Machines only. 50 | 51 | installer: 52 | no_installer_for_platform: |- 53 | Sorry, don't know how to %{method} Virtualbox Guest Additions on this platform. Stopping installation. 54 | generic_linux_installer: |- 55 | The guest's platform is currently not supported, will try generic Linux method... 56 | do_not_inherit_match_method: |- 57 | Installer classes must provide their own `match?` method. 58 | 59 | download: 60 | started: "Downloading VirtualBox Guest Additions ISO from %{source}" 61 | with: "Downloading VirtualBox Guest Additions ISO with %{class}..." 62 | cleaning: "Cleaning up downloaded VirtualBox Guest Additions ISO..." 63 | unknown_type: "Unknown or unsupported URI type given for VirtualBox Guest Additions ISO download." 64 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/hosts/virtualbox.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | module Hosts 3 | class VirtualBox < Base 4 | 5 | protected 6 | 7 | # Default web URI, where GuestAdditions iso file can be downloaded. 8 | # 9 | # @return [String] A URI template containing the versions placeholder. 10 | def web_path 11 | "http://download.virtualbox.org/virtualbox/%{version}/VBoxGuestAdditions_%{version}.iso" 12 | end 13 | 14 | 15 | # Finds GuestAdditions iso file on the host system. 16 | # Returns +nil+ if none found. 17 | # 18 | # @return [String] Absolute path to the local GuestAdditions iso file, or +nil+ if not found. 19 | def local_path 20 | media_manager_iso || guess_local_iso 21 | end 22 | 23 | # Kicks off +VagrantVbguest::Download+ to download the additions file 24 | # into a temp file. 25 | # 26 | # To remove the created tempfile call +cleanup+ 27 | # 28 | # @param [String] The path or URI to download 29 | # 30 | # @return [String] The path to the downloaded file 31 | def download(path) 32 | temp_path = File.join(@env.tmp_path, "VBoxGuestAdditions_#{version}.iso") 33 | @download = VagrantVbguest::Download.new(path, temp_path, :ui => @env.ui) 34 | @download.download! 35 | @download.destination 36 | end 37 | 38 | private 39 | 40 | # Helper method which queries the VirtualBox media manager 41 | # for the first existing path that looks like a 42 | # +VBoxGuestAdditions.iso+ file. 43 | # 44 | # @return [String] Absolute path to the local GuestAdditions iso file, or +nil+ if not found. 45 | def media_manager_iso 46 | driver.execute('list', 'dvds').scan(/^.+:\s+(.*VBoxGuestAdditions(?:_#{version})?\.iso)$/i).map { |path, _| 47 | path if File.exist?(path) 48 | }.compact.first 49 | end 50 | 51 | # Find the first GuestAdditions iso file which exists on the host system 52 | # 53 | # @return [String] Absolute path to the local GuestAdditions iso file, or +nil+ if not found. 54 | def guess_local_iso 55 | Array(platform_path).find do |path| 56 | path && File.exists?(path) 57 | end 58 | end 59 | 60 | # Makes an educated guess where the GuestAdditions iso file 61 | # could be found on the host system depending on the OS. 62 | # Returns +nil+ if no the file is not in it's place. 63 | def platform_path 64 | [:linux, :darwin, :cygwin, :windows].each do |sys| 65 | return self.send("#{sys}_path") if Vagrant::Util::Platform.respond_to?("#{sys}?") && Vagrant::Util::Platform.send("#{sys}?") 66 | end 67 | nil 68 | end 69 | 70 | # Makes an educated guess where the GuestAdditions iso file 71 | # on linux based systems 72 | def linux_path 73 | paths = ["/usr/share/virtualbox/VBoxGuestAdditions.iso"] 74 | paths.unshift(File.join(ENV['HOME'], '.VirtualBox', "VBoxGuestAdditions_#{version}.iso")) if ENV['HOME'] 75 | paths 76 | end 77 | 78 | # Makes an educated guess where the GuestAdditions iso file 79 | # on Macs 80 | def darwin_path 81 | "/Applications/VirtualBox.app/Contents/MacOS/VBoxGuestAdditions.iso" 82 | end 83 | 84 | # Makes an educated guess where the GuestAdditions iso file 85 | # on windows systems 86 | def windows_path 87 | if (p = ENV["VBOX_INSTALL_PATH"]) && !p.empty? 88 | File.join(p, "VBoxGuestAdditions.iso") 89 | elsif (p = ENV["PROGRAM_FILES"] || ENV["ProgramW6432"] || ENV["PROGRAMFILES"]) && !p.empty? 90 | File.join(p, "/Oracle/VirtualBox/VBoxGuestAdditions.iso") 91 | end 92 | end 93 | alias_method :cygwin_path, :windows_path 94 | 95 | # overwrite the default version string to allow lagacy 96 | # '$VBOX_VERSION' as a placerholder 97 | def versionize(path) 98 | super(path.gsub('$VBOX_VERSION', version)) 99 | end 100 | 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/installer.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | 3 | ## 4 | # Dispatches the installation process to a rigistered 5 | # Installer implementation. 6 | class Installer 7 | 8 | class NoInstallerFoundError < Vagrant::Errors::VagrantError 9 | error_namespace "vagrant_vbguest.errors.installer" 10 | error_key "no_installer_for_platform" 11 | end 12 | 13 | class << self 14 | 15 | ## 16 | # Register an Installer implementation. 17 | # All Installer classes which wish to get picked automaticly 18 | # using their `#match?` method have to register. 19 | # Ad-hoc or small custom Installer meight not need to get 20 | # registered, but need to get passed as an config option (`installer`) 21 | # 22 | # Registration takes a priority which defines how specific 23 | # the Installer matches a system. Low level installers, like 24 | # "linux" or "bsd" use a small priority (2), while distribution 25 | # installers use higher priority (5). Installers matching a 26 | # specific version of a distribution should use heigher 27 | # priority numbers. 28 | # 29 | # @param [Class] installer_class A reference to the Installer class. 30 | # @param [Fixnum] prio Priority describing how specific the Installer matches. (default: `5`) 31 | def register(installer_class, prio = 5) 32 | @installers ||= {} 33 | @installers[prio] ||= [] 34 | @installers[prio] << installer_class 35 | end 36 | 37 | ## 38 | # Returns the class of the registrated Installer class which 39 | # matches first (according to it's priority) or `nil` if none matches. 40 | def detect(vm, options) 41 | @installers.keys.sort.reverse.each do |prio| 42 | klass = @installers[prio].detect { |k| k.match?(vm) } 43 | return klass if klass 44 | end 45 | return nil 46 | end 47 | end 48 | 49 | def initialize(vm, options = {}) 50 | @vm = vm 51 | @env = vm.env 52 | @options = options 53 | @iso_path = nil 54 | end 55 | 56 | def install 57 | installer = guest_installer 58 | raise NoInstallerFoundError, :method => 'install' if !installer 59 | 60 | installer.install do |type, data| 61 | @env.ui.info(data, :prefix => false, :new_line => false) 62 | end 63 | ensure 64 | cleanup 65 | end 66 | 67 | def rebuild 68 | installer = guest_installer 69 | raise NoInstallerFoundError, :method => 'rebuild' if !installer 70 | 71 | installer.rebuild do |type, data| 72 | @env.ui.info(data, :prefix => false, :new_line => false) 73 | end 74 | end 75 | 76 | def start 77 | installer = guest_installer 78 | raise NoInstallerFoundError, :method => 'manual start' if !installer 79 | 80 | installer.start do |type, data| 81 | @env.ui.info(data, :prefix => false, :new_line => false) 82 | end 83 | end 84 | 85 | def guest_version(reload=false) 86 | installer = guest_installer 87 | raise NoInstallerFoundError, :method => 'check guest version of' if !installer 88 | installer.guest_version(reload) 89 | end 90 | 91 | def host_version 92 | installer = guest_installer 93 | raise NoInstallerFoundError, :method => 'check host version of' if !installer 94 | installer.host.version 95 | end 96 | 97 | def running? 98 | installer = guest_installer 99 | raise NoInstallerFoundError, :method => 'check current state of' if !installer 100 | installer.running? 101 | end 102 | 103 | # Returns an installer instance for the current vm 104 | # This is either the one configured via `installer` option or 105 | # detected from all registered installers (see {Installer.detect}) 106 | # 107 | # @return [Installers::Base] 108 | def guest_installer 109 | @guest_installer ||= if @options[:installer].is_a? Class 110 | @options[:installer].new(@vm, @options) 111 | else 112 | installer_klass = Installer.detect(@vm, @options) and installer_klass.new(@vm, @options) 113 | end 114 | end 115 | 116 | def cleanup 117 | @guest_installer.cleanup if @guest_installer 118 | end 119 | 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/hosts/base.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | 3 | module VagrantVbguest 4 | module Hosts 5 | class Base 6 | include VagrantVbguest::Helpers::VmCompatible 7 | 8 | attr_reader :env, :vm, :options 9 | 10 | def initialize(vm, options=nil) 11 | @vm = vm 12 | @env = vm.env 13 | @options = options 14 | end 15 | 16 | # Determinates the host's version 17 | # 18 | # @return [String] The version code of the *host*'s virtualisation 19 | def version 20 | @version ||= driver.version 21 | end 22 | 23 | 24 | # Additions-file-detection-magig. 25 | # 26 | # Detection runs in those stages: 27 | # 1. Uses the +iso_path+ config option, if present and not set to +:auto+ 28 | # 2. Look out for a local additions file 29 | # 3. Use the default web URI 30 | # 31 | # If the detected or configured path is not a local file and remote downloads 32 | # are allowed (the config option +:no_remote+ is NOT set) it will try to 33 | # download that file into a temp file using Vagrants Downloaders. 34 | # If remote downloads are prohibited (the config option +:no_remote+ IS set) 35 | # a +VagrantVbguest::IsoPathAutodetectionError+ will be thrown 36 | # 37 | # @return [String] A absolute path to the GuestAdditions iso file. 38 | # This might be a temp-file, e.g. when downloaded from web. 39 | def additions_file 40 | return @additions_file if @additions_file 41 | 42 | path = options[:iso_path] 43 | if !path || path.empty? || path == :auto 44 | path = local_path 45 | path = web_path if !options[:no_remote] && !path 46 | end 47 | raise VagrantVbguest::IsoPathAutodetectionError if !path || path.empty? 48 | 49 | path = versionize(path) 50 | 51 | if file_match? path 52 | @additions_file = path 53 | else 54 | # :TODO: This will also raise, if the iso_url points to an invalid local path 55 | raise VagrantVbguest::DownloadingDisabledError.new(:from => path) if options[:no_remote] 56 | @additions_file = download path 57 | end 58 | end 59 | 60 | # If needed, remove downloaded temp file 61 | def cleanup 62 | @download.cleanup if @download 63 | end 64 | 65 | protected 66 | 67 | # fix bug when the vagrant version is higher than 1.2, by moving method Vagrant::Vagrant::File.match? here 68 | def file_match?(uri) 69 | extracted = ::URI.extract(uri, "file") 70 | 71 | return true if extracted && extracted.include?(uri) 72 | 73 | return ::File.file?(::File.expand_path(uri)) 74 | end 75 | 76 | # Default web URI, where "additions file" can be downloaded. 77 | # 78 | # @return [String] A URI template containing the versions placeholder. 79 | def web_path 80 | raise NotImplementedError 81 | end 82 | 83 | # Finds the "additions file" on the host system. 84 | # Returns +nil+ if none found. 85 | # 86 | # @return [String] Absolute path to the local "additions file", or +nil+ if not found. 87 | def local_path 88 | raise NotImplementedError 89 | end 90 | 91 | # replaces the veriosn placeholder with the additions 92 | # version string 93 | # 94 | # @param [String] A path or URL (or any other String) 95 | # 96 | # @retrun [String] A copy of the passed string, with verision 97 | # placeholder replaced 98 | def versionize(path) 99 | path % {:version => version} 100 | end 101 | 102 | # Kicks off +VagrantVbguest::Download+ to download the additions file 103 | # into a temp file. 104 | # 105 | # To remove the created tempfile call +cleanup+ 106 | # 107 | # @param [String] The path or URI to download 108 | # 109 | # @return [String] The path to the downloaded file 110 | def download(path) 111 | @download = VagrantVbguest::Download.new(path, @env.tmp_path, :ui => @env.ui) 112 | @download.download! 113 | @download.destination 114 | end 115 | 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/core_ext/string/interpolate.rb: -------------------------------------------------------------------------------- 1 | # This backports the Ruby 1.9 String interpolation syntax to Ruby 1.8. 2 | # 3 | # This file is a copy from I18n 4 | # https://github.com/mattetti/i18n/blob/master/lib/i18n/core_ext/string/interpolate.rb 5 | # Copyright (c) 2008 The Ruby I18n team 6 | # It's copied here for the resons given below: 7 | # 8 | # This backport has been shipped with I18n for a number of versions. Meanwhile 9 | # Rails has started to rely on it and we are going to move it to ActiveSupport. 10 | # See https://rails.lighthouseapp.com/projects/8994/tickets/6013-move-19-string-interpolation-syntax-backport-from-i18n-to-activesupport 11 | # 12 | # Once the above patch has been applied to Rails the following code will be 13 | # removed from I18n. 14 | 15 | =begin 16 | heavily based on Masao Mutoh's gettext String interpolation extension 17 | http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb 18 | Copyright (C) 2005-2009 Masao Mutoh 19 | You may redistribute it and/or modify it under the same license terms as Ruby. 20 | =end 21 | 22 | begin 23 | raise ArgumentError if ("a %{x}" % {:x=>'b'}) != 'a b' 24 | rescue ArgumentError 25 | # KeyError is raised by String#% when the string contains a named placeholder 26 | # that is not contained in the given arguments hash. Ruby 1.9 includes and 27 | # raises this exception natively. We define it to mimic Ruby 1.9's behaviour 28 | # in Ruby 1.8.x 29 | class KeyError < IndexError 30 | def initialize(message = nil) 31 | super(message || "key not found") 32 | end 33 | end unless defined?(KeyError) 34 | 35 | # Extension for String class. This feature is included in Ruby 1.9 or later but not occur TypeError. 36 | # 37 | # String#% method which accept "named argument". The translator can know 38 | # the meaning of the msgids using "named argument" instead of %s/%d style. 39 | class String 40 | # For older ruby versions, such as ruby-1.8.5 41 | alias :bytesize :size unless instance_methods.find {|m| m.to_s == 'bytesize'} 42 | alias :interpolate_without_ruby_19_syntax :% # :nodoc: 43 | 44 | INTERPOLATION_PATTERN = Regexp.union( 45 | /%\{(\w+)\}/, # matches placeholders like "%{foo}" 46 | /%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%.d" 47 | ) 48 | 49 | INTERPOLATION_PATTERN_WITH_ESCAPE = Regexp.union( 50 | /%%/, 51 | INTERPOLATION_PATTERN 52 | ) 53 | 54 | # % uses self (i.e. the String) as a format specification and returns the 55 | # result of applying it to the given arguments. In other words it interpolates 56 | # the given arguments to the string according to the formats the string 57 | # defines. 58 | # 59 | # There are three ways to use it: 60 | # 61 | # * Using a single argument or Array of arguments. 62 | # 63 | # This is the default behaviour of the String class. See Kernel#sprintf for 64 | # more details about the format string. 65 | # 66 | # Example: 67 | # 68 | # "%d %s" % [1, "message"] 69 | # # => "1 message" 70 | # 71 | # * Using a Hash as an argument and unformatted, named placeholders. 72 | # 73 | # When you pass a Hash as an argument and specify placeholders with %{foo} 74 | # it will interpret the hash values as named arguments. 75 | # 76 | # Example: 77 | # 78 | # "%{firstname}, %{lastname}" % {:firstname => "Masao", :lastname => "Mutoh"} 79 | # # => "Masao Mutoh" 80 | # 81 | # * Using a Hash as an argument and formatted, named placeholders. 82 | # 83 | # When you pass a Hash as an argument and specify placeholders with %d 84 | # it will interpret the hash values as named arguments and format the value 85 | # according to the formatting instruction appended to the closing >. 86 | # 87 | # Example: 88 | # 89 | # "%d, %.1f" % { :integer => 10, :float => 43.4 } 90 | # # => "10, 43.3" 91 | def %(args) 92 | if args.kind_of?(Hash) 93 | dup.gsub(INTERPOLATION_PATTERN_WITH_ESCAPE) do |match| 94 | if match == '%%' 95 | '%' 96 | else 97 | key = ($1 || $2).to_sym 98 | raise KeyError unless args.has_key?(key) 99 | $3 ? sprintf("%#{$3}", args[key]) : args[key] 100 | end 101 | end 102 | elsif self =~ INTERPOLATION_PATTERN 103 | raise ArgumentError.new('one hash required') 104 | else 105 | result = gsub(/%([{<])/, '%%\1') 106 | result.send :'interpolate_without_ruby_19_syntax', args 107 | end 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.10.1 (Not released yet) 2 | 3 | - Make sure our log message strings are loaded [GH-107] 4 | 5 | ## 0.10.0 (2013-12-09) 6 | 7 | - Adds new config option `installer_arguments` 8 | Customize arguments passed to the VirtualBox GuestAdditions shell script 9 | installer. Defaults to "--no-x11". [GH-98] 10 | - Cope with UbuntuCloudImage by default [GH-86], [GH-64], [GH-43] 11 | On Ubuntu, always try to remove conflicting installations of 12 | GuestAdditions by removing those packages: 13 | virtualbox-guest-dkms virtualbox-guest-utils virtualbox-guest-x11 14 | - Unload kernel modules when UbuntuCloudImage packages are installed. 15 | (Thanks @eric1234 for commenting on ec9a7b1f0a) 16 | - Wait for SSH connection to be ready. Fixes timing issues with vagrant 17 | v1.3.0 and later. [GH-80], [GH-90] 18 | - Fix a typo in command description [GH-84] 19 | - Tweak gem dependencies [GH-82] 20 | - add rake as development dependency 21 | - remove version locks on gems provided by vagrant 22 | - Pass plugin name when registration the action hook for vagrant ≥1.1 [GH-80] 23 | - Fix crash on Vagrant 1.4 [GH-100] 24 | 25 | ### heads-up 26 | 27 | - With [GH-94] the **name**, `vagrant-vbguest` registers itself to vagrant's 28 | (≥1.1) plugin-system changed from 'vbguest management' to 29 | 'vagrant-vbguest' 30 | 31 | ## 0.9.0 32 | 33 | - Adds support for vagrant 1.3 [GH-71], [GH-72] 34 | - Fix crash when using as a command [GH-68]. 35 | - Don't trust VirtualBox Media Manager informations when 36 | looking for a iso file. [GH-70] 37 | 38 | ### heads-up 39 | 40 | - Be lax about missing installer for guest OS. 41 | No longer throws an error when no Installer class 42 | for the guest os was found. Keep the error message, 43 | stop vbguest workflow, but keep vagrant running. 44 | [GH-65] 45 | 46 | 47 | ## 0.8.0 48 | 49 | - Adds Vagrant 1.2 compatibility [GH-59], [GH-60], [GH-62] / 50 | (thanks @Andrew8xx8 for pointing directions) 51 | - Fix basic/fallback linux installer [GH-56] 52 | - Guard auto-reload on broken vagrant builds. 53 | Some varant 1.1.x versions have a bug regarding ssh and cleaning 54 | up old connections, which results in varant crashing when a box 55 | is reloaded. 56 | 57 | ## 0.7.1 58 | 59 | - Fix auto-reloading for vagrant 1.1 [GH-52] 60 | Also changes the reload method for vagrant 1.0 when ran 61 | as middleware (to not run buildin actions manually). 62 | 63 | ## 0.7.0 64 | 65 | - When looking for a GuestAdditions iso file in media manager 66 | allow version number in filename. [GH-48], [GH-49] / 67 | (thanks @neerolyte) 68 | - Support multiple locations to be searched while "guessing" 69 | GuestAdditions iso file 70 | - On Linux guests also search "$HOME/.VirtualBox" for 71 | GuestAdditions iso file. [GH-48] 72 | - Add support for redhat-based distributions (Scientific Linux and 73 | presumably CentOS) [GH-47], [GH-46] / (thanks @neerolyte) 74 | - Fix an issue with VirtualBox GuestAdditions 4.2.8 [GH-44] / 75 | (thanks @jimmycuadra) 76 | - Reworked bunch of internals, particularly how vagrants's 77 | environemnt is passed arround. Also decoupled GuestAdditions 78 | finder into a seperate class. 79 | - Intodruce a vagrant 1.0 compatibility layer for Installers and 80 | other vbguest internals 81 | 82 | ### heads-up 83 | 84 | - [GH-44] changes the behavior of vbguest to that effect, that it 85 | will no longer halt vagrant workflow if running the VirtualBox 86 | GuestAdditions Installer returns an error-code. 87 | Instead it will print a human readable waring message. 88 | - The environment (`env`) in custom installers is no longer the 89 | actions environment `Hash`, but the `Environment` instance. 90 | Which has some implications on how you can access e.g. the `ui`: 91 | instead of `env[:ui]` use `env.ui` 92 | - To achieve compatibility to both vagrant 1.0 and 1.1, custom 93 | Installers, when executing shell commands on the guest system, 94 | should use vbguests `communicate` wrapper. e.g.: 95 | A call like `vm.channel.sudo 'apt-get update'` should be 96 | changed to `communicate.sudo 'apt-get update'` 97 | The old `vm.channel` syntax will continue to work on vagrant 1.0.x 98 | but will fail on vagrant 1.1.x. 99 | 100 | ## 0.6.4 (2013-01-24) 101 | 102 | - Fix passing a installer class as an config option [GH-40] 103 | 104 | ## 0.6.3 (2013-01-19) 105 | 106 | - Fix generic linux installer for not explicitly supported 107 | distributions [GH-39] 108 | 109 | ## 0.6.2 (2013-01-18) 110 | 111 | - Fix typos and wording in error messages and I18n keys 112 | et al. [GH-38] 113 | 114 | ## 0.6.1 (2013-01-13) 115 | 116 | - Fix missing command output block and parameters for 117 | installation process [GH-37] 118 | - Update README to reflect new wording for status informations 119 | 120 | ## 0.6.0 (2013-01-13) 121 | 122 | - Debian installer now cope with missing `dkms` package [GH-30] 123 | - Fixed some issues when runnig on just creating boxes [GH-31] 124 | - Fixed workding (thx @scalp42) [GH-32] 125 | - Add debug logging 126 | - Installers no longer are shell scripts, but ruby classes 127 | - Users may pass in their own installer classes 128 | (yes! plugins in plugins) 129 | - New `sprintf` style `%{version}` placeholder for iso download path. 130 | (old `$VBOX_VERSION` placeholder still working) 131 | - Revisited command arguments to not just mirror config values: 132 | - New `--do` argument: force-run one of those commands: 133 | * `start` : Try to start the GuestAdditions Service 134 | * `rebuild` : Rebuild the currently installed Guest Additions 135 | * `install` : Run the installation process from "iso file" 136 | - New `--status` argument 137 | - Removed `-I|--no-install` argument (instead use `--status`) 138 | 139 | ## 0.5.1 (2012-11-30) 140 | 141 | - Fix: Provisioning will not run twice when rebooted due 142 | to incomplete GuestAdditions installation [GH-27] / 143 | (thanks @gregsymons for pointing) 144 | 145 | ## 0.5.0 (2012-11-19) 146 | 147 | - Box will be rebooted if the GuestAdditions installation 148 | process does not load the kernel module [GH-25], [GH-24] 149 | - Add `--auto-reboot` argument to allow rebooting when running as a 150 | command (which is disabled by default when runnind as command) 151 | - Adds this Changelog 152 | 153 | ## 0.4.0 (2012-10-21) 154 | 155 | - Add global configuration options [GH-22] 156 | - Add `iso_path` option `:auto` to rest a previously 157 | configured path [GH-22] 158 | 159 | ## 0.3.5 160 | 161 | - Fix iso detection for windows hosts [GH-20] 162 | 163 | ## 0.3.4 164 | 165 | - Fix installer shell script invocation [GH-18] 166 | 167 | ## 0.3.3 168 | 169 | - Add Fedora to the list of supported plattforms [GH-17] 170 | - Add system package update (`apt-get update`) to the 171 | debian installer if package installation fails [GH-16] 172 | - Drop dependency on `vagrant` gem [GH-15] 173 | 174 | ## 0.3.2 175 | 176 | - Stop GuestAdditions installation and fail with an error 177 | when installation of dependency packes fails [GH-13] 178 | 179 | ## 0.3.1 180 | 181 | - Ruby 1.8.7 compatibility [GH-12] 182 | 183 | ## 0.3.0 184 | 185 | - Removed dependency to the `virtualbox` gem by using 186 | `vagrant`s vm driver [GH-8] 187 | 188 | ## 0.2.1 189 | 190 | - Typo fixes in readme and internal renamings. [GH-9], [GH-7] 191 | 192 | ## 0.2.0 193 | 194 | - Makes a guess on where to look for a `VBoxGuestAdditions.iso` file 195 | based on the host system (when VirtualBox does not tell). [GH-6] 196 | - Adds command line options `--no-install`, `--no-remote`, `--iso` 197 | - Requires vagrant v0.9.4 or later 198 | 199 | ## 0.1.1 200 | 201 | - Fix vagrant 0.9.4 compatibility [GH-4] 202 | 203 | ## 0.1.0 204 | 205 | - Vagrant 0.9 compatibility (drops 0.8 support) [GH-3] 206 | 207 | ## Previous (≤ 0.0.3) 208 | 209 | - Vagrant 0.8 support 210 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/installers/linux.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | module Installers 3 | # A basic Installer implementation for vanilla or 4 | # unknown Linux based systems. 5 | class Linux < Base 6 | 7 | # A helper method to cache the result of {Vagrant::Guest::Base#distro_dispatch} 8 | # which speeds up Installer detection runs a lot, 9 | # when having lots of Linux based Installer classes 10 | # to check. 11 | # 12 | # @see {Vagrant::Guest::Linux#distro_dispatch} 13 | # @return [Symbol] One of `:debian`, `:ubuntu`, `:gentoo`, `:fedora`, `:redhat`, `:suse`, `:arch` 14 | def self.distro(vm) 15 | @@ditro ||= {} 16 | @@ditro[ vm_id(vm) ] ||= distro_name vm 17 | end 18 | 19 | # Matches if the operating system name prints "Linux" 20 | # Raises an Error if this class is beeing subclassed but 21 | # this method was not overridden. This is considered an 22 | # error because, subclassed Installers usually indicate 23 | # a more specific distributen like 'ubuntu' or 'arch' and 24 | # therefore should do a more specific check. 25 | def self.match?(vm) 26 | raise Error, :_key => :do_not_inherit_match_method if self != Linux 27 | communicate_to(vm).test("uname | grep 'Linux'") 28 | end 29 | 30 | # defaults the temp path to "/tmp/VBoxGuestAdditions.iso" for all Linux based systems 31 | def tmp_path 32 | '/tmp/VBoxGuestAdditions.iso' 33 | end 34 | 35 | # defaults the mount point to "/mnt" for all Linux based systems 36 | def mount_point 37 | '/mnt' 38 | end 39 | 40 | # a generic way of installing GuestAdditions assuming all 41 | # dependencies on the guest are installed 42 | # @param [Hash] opts Optional options Hash wich meight get passed to {Vagrant::Communication::SSH#execute} and firends 43 | # @yield [type, data] Takes a Block like {Vagrant::Communication::Base#execute} for realtime output of the command being executed 44 | # @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc. 45 | # @yieldparam [String] data Data for the given output. 46 | def install(opts=nil, &block) 47 | env.ui.warn I18n.t("vagrant_vbguest.errors.installer.generic_linux_installer") if self.class == Linux 48 | upload(iso_file) 49 | mount_iso(opts, &block) 50 | execute_installer(opts, &block) 51 | unmount_iso(opts, &block) 52 | end 53 | 54 | # @param [Hash] opts Optional options Hash wich meight get passed to {Vagrant::Communication::SSH#execute} and firends 55 | # @yield [type, data] Takes a Block like {Vagrant::Communication::Base#execute} for realtime output of the command being executed 56 | # @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc. 57 | # @yieldparam [String] data Data for the given output. 58 | def running?(opts=nil, &block) 59 | opts = { 60 | :sudo => true 61 | }.merge(opts || {}) 62 | communicate.test('lsmod | grep vboxsf', opts, &block) 63 | end 64 | 65 | # This overrides {VagrantVbguest::Installers::Base#guest_version} 66 | # to also query the `VBoxService` on the host system (if available) 67 | # for it's version. 68 | # In some scenarios the results of the VirtualBox driver and the 69 | # additions installed on the host may differ. If this happens, we 70 | # assume, that the host binaries are right and yield a warning message. 71 | # 72 | # @return [String] The version code of the VirtualBox Guest Additions 73 | # available on the guest, or `nil` if none installed. 74 | def guest_version(reload = false) 75 | return @guest_version if @guest_version && !reload 76 | driver_version = super 77 | 78 | communicate.sudo('VBoxService --version', :error_check => false) do |type, data| 79 | if (v = data.to_s.match(/^(\d+\.\d+.\d+)/)) && driver_version != v[1] 80 | @env.ui.warn(I18n.t("vagrant_vbguest.guest_version_reports_differ", :driver => driver_version, :service => v[1])) 81 | @guest_version = v[1] 82 | end 83 | end 84 | @guest_version 85 | end 86 | 87 | # @param [Hash] opts Optional options Hash wich meight get passed to {Vagrant::Communication::SSH#execute} and firends 88 | # @yield [type, data] Takes a Block like {Vagrant::Communication::Base#execute} for realtime output of the command being executed 89 | # @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc. 90 | # @yieldparam [String] data Data for the given output. 91 | def rebuild(opts=nil, &block) 92 | communicate.sudo('/etc/init.d/vboxadd setup', opts, &block) 93 | end 94 | 95 | # @param [Hash] opts Optional options Hash wich meight get passed to {Vagrant::Communication::SSH#execute} and firends 96 | # @yield [type, data] Takes a Block like {Vagrant::Communication::Base#execute} for realtime output of the command being executed 97 | # @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc. 98 | # @yieldparam [String] data Data for the given output. 99 | def start(opts=nil, &block) 100 | opts = {:error_check => false}.merge(opts || {}) 101 | communicate.sudo('/etc/init.d/vboxadd start', opts, &block) 102 | end 103 | 104 | 105 | # A generic helper method to execute the installer. 106 | # This also yields a installation warning to the user, and an error 107 | # warning in the event that the installer returns a non-zero exit status. 108 | # 109 | # @param [Hash] opts Optional options Hash wich meight get passed to {Vagrant::Communication::SSH#execute} and firends 110 | # @yield [type, data] Takes a Block like {Vagrant::Communication::Base#execute} for realtime output of the command being executed 111 | # @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc. 112 | # @yieldparam [String] data Data for the given output. 113 | def execute_installer(opts=nil, &block) 114 | yield_installation_waring(installer) 115 | opts = {:error_check => false}.merge(opts || {}) 116 | exit_status = communicate.sudo("#{installer} #{installer_arguments}", opts, &block) 117 | yield_installation_error_warning(installer) unless exit_status == 0 118 | exit_status 119 | end 120 | 121 | # The absolute path to the GuestAdditions installer script. 122 | # The iso file has to be mounted on +mount_point+. 123 | def installer 124 | @installer ||= File.join(mount_point, 'VBoxLinuxAdditions.run') 125 | end 126 | 127 | # The arguments string, which gets passed to the installer script 128 | def installer_arguments 129 | @installer_arguments ||= Array(options[:installer_arguments]).join " " 130 | end 131 | 132 | # A generic helper method for mounting the GuestAdditions iso file 133 | # on most linux system. 134 | # Mounts the given uploaded file from +tmp_path+ on +mount_point+. 135 | # 136 | # @param [Hash] opts Optional options Hash wich meight get passed to {Vagrant::Communication::SSH#execute} and firends 137 | # @yield [type, data] Takes a Block like {Vagrant::Communication::Base#execute} for realtime output of the command being executed 138 | # @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc. 139 | # @yieldparam [String] data Data for the given output. 140 | def mount_iso(opts=nil, &block) 141 | communicate.sudo("mount #{tmp_path} -o loop #{mount_point}", opts, &block) 142 | end 143 | 144 | # A generic helper method for un-mounting the GuestAdditions iso file 145 | # on most linux system 146 | # Unmounts the +mount_point+. 147 | # 148 | # @param [Hash] opts Optional options Hash wich meight get passed to {Vagrant::Communication::SSH#execute} and firends 149 | # @yield [type, data] Takes a Block like {Vagrant::Communication::Base#execute} for realtime output of the command being executed 150 | # @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc. 151 | # @yieldparam [String] data Data for the given output. 152 | def unmount_iso(opts=nil, &block) 153 | communicate.sudo("umount #{mount_point}", opts, &block) 154 | end 155 | end 156 | end 157 | end 158 | VagrantVbguest::Installer.register(VagrantVbguest::Installers::Linux, 2) 159 | -------------------------------------------------------------------------------- /lib/vagrant-vbguest/installers/base.rb: -------------------------------------------------------------------------------- 1 | module VagrantVbguest 2 | module Installers 3 | class Error < Vagrant::Errors::VagrantError 4 | error_namespace "vagrant_vbguest.errors.installer" 5 | end 6 | 7 | # This is the base class all installers must inherit from 8 | # It defines the basic structure of an Installer and should 9 | # never be used directly 10 | class Base 11 | include VagrantVbguest::Helpers::VmCompatible 12 | 13 | # Tests whether this installer class is applicable to the 14 | # current environment. Usually, testing for a specific OS. 15 | # Subclasses must override this method and return `true` if 16 | # they wish to handle. 17 | # 18 | # This method will be called only when an Installer detection 19 | # is run. It is ignored, when passing an Installer class 20 | # directly as an config (`installer`) option. 21 | # 22 | # @param [Vagrant::VM] 23 | # @return [Boolean] 24 | def self.match?(vm) 25 | false 26 | end 27 | 28 | attr_reader :env, :vm, :options, :host 29 | 30 | def initialize(vm, options=nil) 31 | @vm = vm 32 | @env = vm.env 33 | @options = options 34 | 35 | @host = VagrantVbguest::Hosts::VirtualBox.new(vm, options) 36 | end 37 | 38 | # The absolute file path of the GuestAdditions iso file should 39 | # be uploaded into the guest. 40 | # Subclasses must override this method! 41 | # 42 | # @return [String] 43 | def tmp_path 44 | end 45 | 46 | # The mountpoint path 47 | # Subclasses shall override this method, if they need to mount the uploaded file! 48 | # 49 | # @return [String] 50 | def mount_point 51 | end 52 | 53 | # Handles the installation process. 54 | # All necessary steps for an installation must be defined here. 55 | # This includes uploading the iso into the box, mounting, 56 | # installing and cleaning up. 57 | # The path to the local iso file should be obtained by calling +iso_file+ 58 | # Subclasses must override this method! 59 | # 60 | # @param [Hash] opts Optional options Hash wich meight get passed to {Vagrant::Communication::SSH#execute} and firends 61 | # @yield [type, data] Takes a Block like {Vagrant::Communication::Base#execute} for realtime output of the command being executed 62 | # @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc. 63 | # @yieldparam [String] data Data for the given output. 64 | def install(opts=nil, &block) 65 | end 66 | 67 | # Handels the rebuild of allready running GuestAdditions 68 | # It may happen, that the guest has the correct GuestAdditions 69 | # version running, but not the kernel module is not running. 70 | # This method should perform a rebuild or try to reload the 71 | # kernel module _without_ the GuestAdditions iso file. 72 | # If there is no way of rebuidling or reloading the 73 | # GuestAdditions on a specific system, this method should left 74 | # empty. 75 | # Subclasses should override this method. 76 | # 77 | # @param [Hash] opts Optional options Hash wich meight get passed to {Vagrant::Communication::SSH#execute} and firends 78 | # @yield [type, data] Takes a Block like {Vagrant::Communication::Base#execute} for realtime output of the command being executed 79 | # @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc. 80 | # @yieldparam [String] data Data for the given output. 81 | def rebuild(opts=nil, &block) 82 | end 83 | 84 | # Restarts the allready installed GuestAdditions 85 | # It may happen, that the guest has the correct GuestAdditions 86 | # version installed, but for some reason are not (yet) runnig. 87 | # This method should execute the GuestAdditions system specific 88 | # init script in order to start it manually. 89 | # If there is no way of doing this on a specific system, 90 | # this method should left empty. 91 | # Subclasses should override this method. 92 | # 93 | # @param [Hash] opts Optional options Hash wich meight get passed to {Vagrant::Communication::SSH#execute} and firends 94 | # @yield [type, data] Takes a Block like {Vagrant::Communication::Base#execute} for realtime output of the command being executed 95 | # @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc. 96 | # @yieldparam [String] data Data for the given output. 97 | def start(opts=nil, &block) 98 | end 99 | 100 | # Determinates if the GuestAdditions kernel module is loaded. 101 | # This method tests if there is a working GuestAdditions 102 | # kernel module. If there is none, {#rebuild} is beeing called. 103 | # If there is no way of telling if there is a working 104 | # GuestAddition for a specific system, this method should 105 | # return `true`. 106 | # Subclasses should override this method. 107 | # 108 | # @return [Boolean] `true` if the kernel module is loaded (and thus seems to work), `false` otherwise. 109 | def running?(opts=nil, &block) 110 | true 111 | end 112 | 113 | # Determinates the GuestAdditions version installed on the 114 | # guest system. 115 | # 116 | # @param [Boolean] reload Whether to read the value again or use 117 | # the cached value form an erlier call. 118 | # @return [String] The version code of the VirtualBox Guest Additions 119 | # available on the guest, or `nil` if none installed. 120 | def guest_version(reload=false) 121 | return @guest_version if @guest_version && !reload 122 | 123 | guest_version = driver.read_guest_additions_version 124 | guest_version = !guest_version ? nil : guest_version.gsub(/[-_]ose/i, '') 125 | 126 | @guest_version = guest_version 127 | end 128 | 129 | 130 | # Determinates the version of the GuestAdditions installer in use 131 | # 132 | # @return [String] The version code of the GuestAdditions installer 133 | def installer_version(path_to_installer) 134 | version = nil 135 | communicate.sudo("#{path_to_installer} --info", :error_check => false) do |type, data| 136 | if (v = data.to_s.match(/\AIdentification.*\s(\d+\.\d+.\d+)/i)) 137 | version = v[1] 138 | end 139 | end 140 | version 141 | end 142 | 143 | # Helper to yield a warning message to the user, that the installation 144 | # will start _now_. 145 | # The message includes the host and installer version strings. 146 | def yield_installation_waring(path_to_installer) 147 | @env.ui.warn I18n.t("vagrant_vbguest.installing#{@options[:force] ? '_forced' : ''}", 148 | :guest_version => guest_version, 149 | :installer_version => installer_version(path_to_installer) || I18n.t("vagrant_vbguest.unknown")) 150 | end 151 | 152 | # Helper to yield a warning message to the user, that the installation 153 | # will be rebuild using the installed GuestAdditions. 154 | # The message includes the host and installer version strings. 155 | def yield_rebuild_warning 156 | @env.ui.warn I18n.t("vagrant_vbguest.rebuild#{@options[:force] ? '_forced' : ''}", 157 | :guest_version => guest_version(true), 158 | :host_version => @host.version) 159 | end 160 | 161 | # Helper to yield a warning message to the user in the event that the 162 | # installer returned a non-zero exit status. Because lack of a window 163 | # system will cause this result in VirtualBox 4.2.8+, we don't want to 164 | # kill the entire boot process, but we do want to make sure the user 165 | # knows there could be a problem. The message includles the installer 166 | # version. 167 | def yield_installation_error_warning(path_to_installer) 168 | @env.ui.warn I18n.t("vagrant_vbguest.install_error", 169 | :installer_version => installer_version(path_to_installer) || I18n.t("vagrant_vbguest.unknown")) 170 | end 171 | 172 | def iso_file 173 | @host.additions_file 174 | end 175 | alias_method :additions_file, :iso_file 176 | 177 | # A helper method to handle the GuestAdditions iso file upload 178 | # into the guest box. 179 | # The file will uploaded to the location given by the +temp_path+ method. 180 | # 181 | # @example Default upload 182 | # upload(file) 183 | # 184 | # @param [String] Path of the file to upload to the +tmp_path* 185 | def upload(file) 186 | env.ui.info(I18n.t("vagrant_vbguest.start_copy_iso", :from => file, :to => tmp_path)) 187 | communicate.upload(file, tmp_path) 188 | end 189 | 190 | # A helper method to delete the uploaded GuestAdditions iso file 191 | # from the guest box 192 | def cleanup 193 | @host.cleanup 194 | communicate.execute("test -f #{tmp_path} && rm #{tmp_path}", :error_check => false) do |type, data| 195 | env.ui.error(data.chomp, :prefix => false) 196 | end 197 | end 198 | 199 | end 200 | end 201 | end 202 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # vagrant-vbguest 2 | 3 | *vagrant-vbguest* is a [Vagrant](http://vagrantup.com) plugin which automatically installs the host's VirtualBox Guest Additions on the guest system. 4 | 5 | [![Code Climate](https://codeclimate.com/github/dotless-de/vagrant-vbguest.png)](https://codeclimate.com/github/dotless-de/vagrant-vbguest) [![Dependency Status](https://gemnasium.com/dotless-de/vagrant-vbguest.png)](https://gemnasium.com/dotless-de/vagrant-vbguest) 6 | 7 | ## Installation 8 | 9 | Requires vagrant 0.9.4 or later (including 1.x) 10 | 11 | ### Vagrant ≥ 1.1 12 | 13 | ```bash 14 | $ vagrant plugin install vagrant-vbguest 15 | ``` 16 | 17 | ### Vagrant 1.0 and older 18 | 19 | Since vagrant v1.0.0 the preferred installation method for vagrant is using the provided packages or installers. 20 | If you installed vagrant that way, you need to use vagrant's gem wrapper: 21 | 22 | ```bash 23 | $ vagrant gem install vagrant-vbguest 24 | ``` 25 | 26 | If you installed vagrant using RubyGems, use: 27 | 28 | ```bash 29 | $ gem install vagrant-vbguest 30 | ``` 31 | 32 | Compatibly for vagrant 0.8 is provided by version 0.0.3 (which lacks a bunch of new options) 33 | 34 | ## Configuration / Usage 35 | 36 | If you're lucky, *vagrant-vbguest* does not require any configuration. 37 | However, here is an example of `Vagrantfile`: 38 | 39 | ```ruby 40 | Vagrant::Config.run do |config| 41 | # we will try to autodetect this path. 42 | # However, if we cannot or you have a special one you may pass it like: 43 | # config.vbguest.iso_path = "#{ENV['HOME']}/Downloads/VBoxGuestAdditions.iso" 44 | # or 45 | # config.vbguest.iso_path = "http://company.server/VirtualBox/%{version}/VBoxGuestAdditions.iso" 46 | 47 | # set auto_update to false, if you do NOT want to check the correct 48 | # additions version when booting this machine 49 | config.vbguest.auto_update = false 50 | 51 | # do NOT download the iso file from a webserver 52 | config.vbguest.no_remote = true 53 | end 54 | ``` 55 | 56 | ### Config options 57 | 58 | * `iso_path` : The full path or URL to the VBoxGuestAdditions.iso file.
59 | The `iso_path` may contain the optional placeholder `%{version}` replaced with detected VirtualBox version (e.g. `4.1.8`). 60 | The default URI for the actual iso download is: `http://download.virtualbox.org/virtualbox/%{version}/VBoxGuestAdditions_%{version}.iso`
61 | vbguest will try to autodetect the best option for your system. WTF? see below. 62 | * `auto_update` (Boolean, default: `true`) : Whether to check the correct additions version on each start (where start is _not_ resuming a box). 63 | * `auto_reboot` (Boolean, default: `true` when running as a middleware, `false` when running as a command) : Whether to reboot the box after GuestAdditions has been installed, but not loaded. 64 | * `no_install` (Boolean, default: `false`) : Whether to check the correct additions version only. This will warn you about version mis-matches, but will not try to install anything. 65 | * `no_remote` (Boolean, default: `false`) : Whether to _not_ download the iso file from a remote location. This includes any `http` location! 66 | * `installer` (`VagrantVbguest::Installers::Base`, optional) : Reference to a (custom) installer class 67 | 68 | #### Global Configuration 69 | 70 | Using [Vagrantfile Load Order](http://vagrantup.com/v1/docs/vagrantfile.html#vagrantfile_load_order) you may change default configuration values. 71 | Edit (create, if missing) your `~/.vagrant.d/Vagrantfile` like this: 72 | 73 | For Vagrant >= 1.1.0 use: 74 | 75 | ```ruby 76 | Vagrant.configure("2") do |config| 77 | config.vbguest.auto_update = false 78 | end 79 | ``` 80 | 81 | For older versions of Vagrant: 82 | 83 | ```ruby 84 | # vagrant's autoloading may not have kicked in 85 | require 'vagrant-vbguest' unless defined? VagrantVbguest::Config 86 | VagrantVbguest::Config.auto_update = false 87 | ``` 88 | 89 | Settings in a project's `Vagrantfile` will overwrite those setting. When executed as a command, command line arguments will overwrite all of the above. 90 | 91 | 92 | ### Running as a middleware 93 | 94 | Running as a middleware is the default way of using *vagrant-vbguest*. 95 | It will run automatically right after the box started. This is each time the box boots, i.e. `vagrant up` or `vagrant reload`. 96 | It won't run on `vagrant resume` (or `vagrant up` a suspended box) to save you some time resuming a box. 97 | 98 | You may switch off the middleware by setting the vm's config `vbguest.auto_update` to `false`. 99 | This is a per box setting. On multi vm environments you need to set that for each vm. 100 | 101 | When *vagrant-vbguest* is running it will provide you some logs: 102 | 103 | [...] 104 | [default] Booting VM... 105 | [default] Booting VM... 106 | [default] Waiting for VM to boot. This can take a few minutes. 107 | [default] VM booted and ready for use! 108 | [default] GuestAdditions versions on your host (4.2.6) and guest (4.1.0) do not match. 109 | stdin: is not a tty 110 | Reading package lists... 111 | Building dependency tree... 112 | Reading state information... 113 | The following extra packages will be installed: 114 | fakeroot linux-headers-2.6.32-33 patch 115 | 116 | [...] 117 | 118 | [default] Copy iso file /Applications/VirtualBox.app/Contents/MacOS/VBoxGuestAdditions.iso into the box /tmp/VBoxGuestAdditions.iso 119 | stdin: is not a tty 120 | [default] Installing Virtualbox Guest Additions 4.2.6 - guest version is 4.1.0 121 | stdin: is not a tty 122 | Verifying archive integrity... All good. 123 | Uncompressing VirtualBox 4.2.6 Guest Additions for Linux........... 124 | VirtualBox Guest Additions installer 125 | Removing installed version 4.1.0 of VirtualBox Guest Additions... 126 | tar: Record size = 8 blocks 127 | Removing existing VirtualBox DKMS kernel modules ...done. 128 | Removing existing VirtualBox non-DKMS kernel modules ...done. 129 | Building the VirtualBox Guest Additions kernel modules ...done. 130 | Doing non-kernel setup of the Guest Additions ...done. 131 | You should restart your guest to make sure the new modules are actually used 132 | 133 | Installing the Window System drivers ...fail! 134 | (Could not find the X.Org or XFree86 Window System.) 135 | stdin: is not a tty 136 | [default] Restarting VM to apply changes... 137 | [default] Attempting graceful shutdown of VM... 138 | [default] Booting VM... 139 | [default] Waiting for VM to boot. This can take a few minutes. 140 | [default] VM booted and ready for use! 141 | [default] Configuring and enabling network interfaces... 142 | [default] Setting host name... 143 | [default] Mounting shared folders... 144 | [default] -- v-root: /vagrant 145 | [default] -- v-csc-1: /tmp/vagrant-chef-1/chef-solo-1/cookbooks 146 | [default] Running provisioner: Vagrant::Provisioners::ChefSolo... 147 | [default] Generating chef JSON and uploading... 148 | [default] Running chef-solo... 149 | [...] 150 | 151 | 152 | The plugin's part starts at `[default] Installing Virtualbox Guest Additions 4.1.14 - guest's version is 4.1.1`, telling you that: 153 | 154 | * the guest addition of the box *default* is outdated (or mismatching) 155 | * which guest additions iso file will be used 156 | * which installer script will be used 157 | * all the VirtualBox Guest Additions installer output. 158 | 159 | No worries on the `Installing the Window System drivers ...fail!`. Most dev boxes you are using won't run a Window Server, thus it's absolutely safe to ignore that error. 160 | 161 | When everything is fine, and no update is needed, you see log like: 162 | 163 | ... 164 | [default] Booting VM... 165 | [default] Waiting for VM to boot. This can take a few minutes. 166 | [default] VM booted and ready for use! 167 | [default] GuestAdditions 4.2.6 running --- OK. 168 | ... 169 | 170 | 171 | ### Running as a Command 172 | 173 | When you switched off the middleware auto update, or you have a box up and running you may also run the installer manually. 174 | 175 | ```bash 176 | $ vagrant vbguest [vm-name] [--do start|rebuild|install] [--status] [-f|--force] [-b|--auto-reboot] [-R|--no-remote] [--iso VBoxGuestAdditions.iso] 177 | ``` 178 | 179 | For example, when you just updated VirtualBox on your host system, you should update the guest additions right away. However, you may need to reload the box to get the guest additions working. 180 | 181 | If you want to check the guest additions version, without installing, you may run: 182 | 183 | ```bash 184 | $ vagrant vbguest --status 185 | ``` 186 | 187 | Telling you either about a version mismatch: 188 | 189 | [default] GuestAdditions versions on your host (4.2.6) and guest (4.1.0) do not match. 190 | 191 | or a match: 192 | 193 | [default] GuestAdditions 4.2.6 running --- OK. 194 | 195 | 196 | The `auto-reboot` is turned off by default when running as a command. Vbguest will suggest you to reboot the box when needed. To turn it on simply pass the `--auto-reboot` parameter: 197 | 198 | ```bash 199 | $ vagrant vbguest --auto-reboot 200 | ``` 201 | 202 | You can also pass vagrant's `reload` options like: 203 | 204 | ```bash 205 | $ vagrant vbguest --auto-reboot --no-provision 206 | ``` 207 | 208 | 209 | 210 | ### ISO autodetection 211 | 212 | *vagrant-vbguest* will try to autodetect a VirtualBox GuestAdditions iso file on your system, which usually matches your installed version of VirtualBox. If it cannot find one, it downloads one from the web (virtualbox.org). 213 | Those places will be checked in order: 214 | 215 | 1. Checks your VirtualBox "Virtual Media Maganger" for a DVD called "VBoxGuestAdditions.iso" 216 | 2. Guess by your host operating system: 217 | * for linux : `/usr/share/virtualbox/VBoxGuestAdditions.iso` 218 | * for Mac : `/Applications/VirtualBox.app/Contents/MacOS/VBoxGuestAdditions.iso` 219 | * for Windows : `%PROGRAMFILES%/Oracle/VirtualBox/VBoxGuestAdditions.iso` 220 | 221 | 222 | ### Automatic reboot 223 | 224 | The VirtualBox GuestAdditions Installer will try to load the newly built kernel module. However the installer may fail to load, just as it is happening when updating GuestAdditions from version 4.1 to 4.2. 225 | 226 | Hence, vbguest will check for a loaded kernel module after the installation has finished and reboots the box, if it could not find one. 227 | 228 | 229 | ## Advanced Usage 230 | 231 | vagrant-vbguest provides installers for generic linux and debian/ubuntu. 232 | Installers take care of the whole installation process, that includes where to save the iso file inside the guest and where to mount it. 233 | 234 | ```ruby 235 | class MyInstaller < VagrantVbguest::Installers::Linux 236 | 237 | # use /temp instead of /tmp 238 | def tmp_path 239 | '/temp/VBoxGuestAdditions.iso' 240 | end 241 | 242 | # use /media instead of /mnt 243 | def mount_point 244 | '/media' 245 | end 246 | 247 | def install(opts=nil, &block) 248 | communicate.sudo('my_distos_way_of_preparing_guestadditions_installation', opts, &block) 249 | # calling `super` will run the installation 250 | # also it takes care of uploading the right iso file into the box 251 | # and cleaning up afterward 252 | super 253 | end 254 | end 255 | 256 | Vagrant::Config.run do |config| 257 | config.vbguest.installer = MyInstaller 258 | end 259 | ``` 260 | 261 | 262 | ## Known Issues 263 | 264 | * The installer script, which mounts and runs the GuestAdditions Installer Binary, works on Linux only. Most likely it will run on most Unix-like platforms. 265 | * The installer script requires a directory `/mnt` on the guest system 266 | * On multi vm boxes, the iso file will be downloaded for each vm 267 | * The plugin installation on Windows host systems my not work as expected (using `vagrant gem install vagrant-vbguest`). Try `C:\vagrant\vagrant\embedded\bin\gem.bat install vagrant-vbguest` instead. (See [issue #19](https://github.com/dotless-de/vagrant-vbguest/issues/19#issuecomment-7040304)) 268 | --------------------------------------------------------------------------------