├── .gitignore ├── .ruby-version ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── example_box └── Vagrantfile ├── lib ├── vagrant-nfs_guest.rb └── vagrant-nfs_guest │ ├── action │ ├── mount_nfs.rb │ └── unmount_nfs.rb │ ├── config.rb │ ├── errors.rb │ ├── guests │ ├── debian │ │ ├── cap │ │ │ └── nfs_server.rb │ │ └── plugin.rb │ ├── linux │ │ ├── cap │ │ │ ├── nfs_server.rb │ │ │ └── read_user_ids.rb │ │ └── plugin.rb │ ├── redhat │ │ ├── cap │ │ │ └── nfs_server.rb │ │ └── plugin.rb │ └── ubuntu │ │ ├── cap │ │ └── nfs_server.rb │ │ └── plugin.rb │ ├── hosts │ ├── bsd │ │ ├── cap │ │ │ ├── mount_nfs.rb │ │ │ └── unmount_nfs.rb │ │ └── plugin.rb │ └── linux │ │ ├── cap │ │ ├── mount_nfs.rb │ │ └── unmount_nfs.rb │ │ └── plugin.rb │ ├── plugin.rb │ ├── providers │ ├── docker │ │ ├── cap │ │ │ └── nfs_settings.rb │ │ └── plugin.rb │ ├── lxc │ │ ├── cap │ │ │ └── nfs_settings.rb │ │ └── plugin.rb │ ├── parallels │ │ ├── cap │ │ │ └── nfs_settings.rb │ │ └── plugin.rb │ ├── virtualbox │ │ ├── cap │ │ │ └── nfs_settings.rb │ │ └── plugin.rb │ └── vmware_fusion │ │ ├── cap │ │ └── nfs_settings.rb │ │ └── plugin.rb │ ├── synced_folder.rb │ └── version.rb ├── templates ├── locales │ └── en.yml └── nfs_guest │ └── guest_export_linux.erb └── vagrant-nfs_guest.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | InstalledFiles 7 | _yardoc 8 | coverage 9 | doc/ 10 | lib/bundler/man 11 | pkg 12 | rdoc 13 | spec/reports 14 | test/tmp 15 | test/version_tmp 16 | tmp 17 | .vagrant 18 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.3 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opensource@learnosity.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How can I contribute? 2 | 3 | #### I think I've found a bug 4 | 5 | * Firstly cool! We can hopefully make the project better by your discovery. 6 | 7 | * Before you submit an **Issue** please have a scan through any existing or closed Issues to make sure you've found something unique and unreported. 8 | 9 | * If your issue seems unique and unreported, you should open a GitHub **Issue**. 10 | 11 | * In your Issue report, please include as much information as you can. 12 | * A detailed summary of the error you are seeing, and any information you think is relevant. 13 | * A snippet of your Vagrantfile showing your `vagrant-nfs_guest` share, and the network settings used. 14 | * The version of Vagrant, the host and guest operating system, which VM provider and it's version. 15 | * A simple example Vagrantfile that demonstrates the error (this would be super useful if possible). 16 | 17 | * [You should then create a new issue](https://github.com/Learnosity/vagrant-nfs_guest/issues/new) 18 | 19 | #### I've found a bug and I've fixed it! 20 | 21 | * Wow, awesome! This will help greatly! 22 | 23 | * Open a new GitHub pull request with the patch. 24 | 25 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 26 | 27 | * **Please send us a PR, all input and suggestions welcome. All assistance greatfully accepted and appreciated.** 28 | 29 | #### I have an idea for a feature, can you please add it? 30 | 31 | `vagrant-nfs_guest` is used daily by our in-house team and is well used and tested. So any new feature that doesn't impact our daily usage or is very specific to your use-case probably won't get the investment in time you might require. 32 | 33 | We don't want to disappoint you, but the facts of life are sometimes _"if you want it done, you have to do it yourself"_. We will happily help you where we can, and we will joyfully accept any PRs you come up with, but we will not just add any feature that's requested. 34 | 35 | #### But it's a really really good idea! 36 | 37 | Again, if it's a world beating idea, put something together (even if it's pseudo-code) and a good document on the problem you're trying to fix and the value of your feature. Then submit a PR. 38 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :development do 4 | # We depend on Vagrant for development, but we don't add it as a 5 | # gem dependency because we expect to be installed within the 6 | # Vagrant environment itself using `vagrant plugin`. 7 | gem "vagrant", :git => "https://github.com/mitchellh/vagrant.git", :tag => "v2.2.18" 8 | gem "rake" 9 | end 10 | 11 | group :plugins do 12 | gem "vagrant-nfs_guest", path: "." 13 | end 14 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/mitchellh/vagrant.git 3 | revision: 52857ccac9d315a6e1897c7885f463983ac67957 4 | tag: v2.2.18 5 | specs: 6 | vagrant (2.2.18) 7 | bcrypt_pbkdf (~> 1.1) 8 | childprocess (~> 4.1.0) 9 | ed25519 (~> 1.2.4) 10 | erubi 11 | hashicorp-checkpoint (~> 0.1.5) 12 | i18n (~> 1.8) 13 | listen (~> 3.5) 14 | log4r (~> 1.1.9, < 1.1.11) 15 | mime-types (~> 3.3) 16 | net-scp (~> 3.0.0) 17 | net-sftp (~> 3.0) 18 | net-ssh (>= 6.1.0, < 6.2) 19 | rb-kqueue (~> 0.2.0) 20 | rexml (~> 3.2) 21 | rubyzip (~> 2.0) 22 | vagrant_cloud (~> 3.0.5) 23 | wdm (~> 0.1.0) 24 | winrm (>= 2.3.4, < 3.0) 25 | winrm-elevated (>= 1.2.1, < 2.0) 26 | winrm-fs (>= 1.3.4, < 2.0) 27 | 28 | PATH 29 | remote: . 30 | specs: 31 | vagrant-nfs_guest (1.0.5) 32 | bundler (>= 2.2.10) 33 | 34 | GEM 35 | remote: https://rubygems.org/ 36 | specs: 37 | bcrypt_pbkdf (1.1.0) 38 | builder (3.2.4) 39 | childprocess (4.1.0) 40 | concurrent-ruby (1.1.9) 41 | ed25519 (1.2.4) 42 | erubi (1.10.0) 43 | excon (0.85.0) 44 | ffi (1.15.3) 45 | gssapi (1.3.1) 46 | ffi (>= 1.0.1) 47 | gyoku (1.3.1) 48 | builder (>= 2.1.2) 49 | hashicorp-checkpoint (0.1.5) 50 | httpclient (2.8.3) 51 | i18n (1.8.10) 52 | concurrent-ruby (~> 1.0) 53 | listen (3.7.0) 54 | rb-fsevent (~> 0.10, >= 0.10.3) 55 | rb-inotify (~> 0.9, >= 0.9.10) 56 | little-plugger (1.1.4) 57 | log4r (1.1.10) 58 | logging (2.3.0) 59 | little-plugger (~> 1.1) 60 | multi_json (~> 1.14) 61 | mime-types (3.3.1) 62 | mime-types-data (~> 3.2015) 63 | mime-types-data (3.2021.0704) 64 | multi_json (1.15.0) 65 | net-scp (3.0.0) 66 | net-ssh (>= 2.6.5, < 7.0.0) 67 | net-sftp (3.0.0) 68 | net-ssh (>= 5.0.0, < 7.0.0) 69 | net-ssh (6.1.0) 70 | nori (2.6.0) 71 | rake (13.0.6) 72 | rb-fsevent (0.11.0) 73 | rb-inotify (0.10.1) 74 | ffi (~> 1.0) 75 | rb-kqueue (0.2.6) 76 | ffi (>= 0.5.0) 77 | rexml (3.2.5) 78 | rubyntlm (0.6.3) 79 | rubyzip (2.3.2) 80 | vagrant_cloud (3.0.5) 81 | excon (~> 0.73) 82 | log4r (~> 1.1.10) 83 | rexml (~> 3.2.5) 84 | wdm (0.1.1) 85 | winrm (2.3.6) 86 | builder (>= 2.1.2) 87 | erubi (~> 1.8) 88 | gssapi (~> 1.2) 89 | gyoku (~> 1.0) 90 | httpclient (~> 2.2, >= 2.2.0.2) 91 | logging (>= 1.6.1, < 3.0) 92 | nori (~> 2.0) 93 | rubyntlm (~> 0.6.0, >= 0.6.3) 94 | winrm-elevated (1.2.3) 95 | erubi (~> 1.8) 96 | winrm (~> 2.0) 97 | winrm-fs (~> 1.0) 98 | winrm-fs (1.3.5) 99 | erubi (~> 1.8) 100 | logging (>= 1.6.1, < 3.0) 101 | rubyzip (~> 2.0) 102 | winrm (~> 2.0) 103 | 104 | PLATFORMS 105 | x86_64-darwin-20 106 | 107 | DEPENDENCIES 108 | rake 109 | vagrant! 110 | vagrant-nfs_guest! 111 | 112 | BUNDLED WITH 113 | 2.2.26 114 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Alan Garfield 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vagrant-NFS_Guest 2 | 3 | ## What's New? 4 | 5 | - NEW: moved to version v1.0.0 as it's no longer a "beta" plugin, it's been well used and tested. So figured now is a good time. 6 | - NEW: `disabled` flag on shares now are properly respected 7 | - UPDATED: share directories on guest are no longer recursively `chown`. 8 | - NEW: Added VMware Fusion provider support. 9 | - Added untested support for Docker providers (Please raise any issues if they don't work!). 10 | - Added Parallels provider support 11 | - Redhat/CentOS guest support added. 12 | - Now properly handles force halts. 13 | - FIXED: suspend and resume with 'up' instead of 'resume' fixed. 14 | - Supports Vagrant > 1.6. 15 | - Handles actions ```up```, ```halt```, ```destroy```, ```suspend```, ```resume``` and ```package``` properly. 16 | - Uses retryable() for host to guest communications allow more fault tolerance. 17 | - Better error messages and handling. 18 | - Re-organisation of modules and class to better match Vagrant proper. 19 | - Simplified the plugin events binding. 20 | - Will install the NFS daemon on the guest if the guest capability is supported (Ubuntu and Debian only at this stage). 21 | - Supports BSD/OSX, and Linux based Hosts that support NFS mounting. 22 | 23 | ## Overview 24 | 25 | Allows a guest VM to export synced folders via NFS and the host to mount them. 26 | 27 | Basically it's just the usual NFS synced folders in Vagrant but the roles are reversed. 28 | 29 | Guest VMs we've tested include: 30 | - Ubuntu (precise, trusty) 31 | - CentOS (6.5, 6.6, 7) 32 | - Debian (wheezy) 33 | - other Linux based guests may work fine with the generic Linux support. But no guarantee 34 | 35 | Hosts we've tested include: 36 | - OSX (Mavericks, Yosemite, El Capitan, Siera) 37 | - Ubuntu (precise, trusty) 38 | - CentOS (6, 7) 39 | - other Linux based OSs should work fine, but will need testing, again no guarantee 40 | 41 | We're happy to receive pull-request to support alternatives hosts and guests. To implement this support it's relatively trivial if you look in ./lib/hosts and ./lib/guests. 42 | 43 | ## Installation 44 | 45 | vagrant plugin install vagrant-nfs_guest 46 | 47 | ## Install from sources 48 | 49 | git clone https://github.com/Learnosity/vagrant-nfs_guest.git 50 | cd vagrant-nfs_guest 51 | bundle install 52 | bundle exec rake build 53 | vagrant plugin install pkg/vagrant-nfs_guest-VERSION.gem 54 | 55 | ## Usage 56 | 57 | To enable for example put similar in the Vagrantfile: 58 | 59 | config.vm.synced_folder 'srv', '/srv', type: 'nfs_guest' 60 | 61 | ## Building 62 | 63 | We use 'chruby' to allow a virtual ruby environment for developement. The 'bundle' gem is needed to build and run 64 | 65 | git clone https://github.com/Learnosity/vagrant-nfs_guest.git 66 | cd vagrant-nfs_guest 67 | bundle install 68 | bundle exec vagrant 69 | 70 | You can test your handy work using the ```example_box``` by doing the following: 71 | 72 | cd ./example_box/ 73 | bundle exec vagrant up 74 | 75 | You can ssh into the test VM using: 76 | 77 | bundle exec vagrant ssh 78 | 79 | ... and you can clean up with: 80 | 81 | bundle exec vagrant destroy 82 | 83 | 84 | ## Contributing 85 | 86 | 1. Fork it 87 | 2. Create your feature branch (`git checkout -b my-new-feature`) 88 | 3. Commit your changes (`git commit -am 'Add some feature'`) 89 | 4. Push to the branch (`git push origin my-new-feature`) 90 | 5. Create new Pull Request 91 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | Bundler::GemHelper.install_tasks 4 | -------------------------------------------------------------------------------- /example_box/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = "2" 6 | 7 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 8 | 9 | # Every Vagrant virtual environment requires a box to build off of. 10 | #config.vm.box = "ubuntu/trusty64" # ubuntu 11 | config.vm.box = "centos/7" # centos/7 12 | #config.vm.box = "nrel/CentOS-6.5-x86_64" # centos/6.5 13 | 14 | # Create a private network, which allows host-only access to the machine 15 | # using a specific IP. 16 | config.vm.network "private_network", ip: "192.168.55.10" 17 | #config.vm.network "public_network" 18 | 19 | # Share an additional folder to the guest VM. The first argument is 20 | # the path on the host to the actual folder. The second argument is 21 | # the path on the guest to mount the folder. And the optional third 22 | # argument is a set of non-required options. 23 | config.vm.synced_folder '.', '/vagrant', disabled: true 24 | config.vm.synced_folder './vagrant_share', '/vagrant', create: true, type: 'nfs_guest', disabled: false 25 | #config.vm.synced_folder './vagrant_share', '/vagrant', type: 'nfs' 26 | 27 | end 28 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | 3 | require "vagrant-nfs_guest/plugin" 4 | 5 | module VagrantPlugins 6 | module SyncedFolderNFSGuest 7 | lib_path = Pathname.new(File.expand_path("../vagrant-nfs_guest", __FILE__)) 8 | autoload :Errors, lib_path.join("errors") 9 | 10 | # This returns the path to the source of this plugin. 11 | # 12 | # @return [Pathname] 13 | def self.source_root 14 | @source_root ||= Pathname.new(File.expand_path("../../", __FILE__)) 15 | end 16 | 17 | I18n.load_path << File.expand_path("templates/locales/en.yml", source_root) 18 | I18n.reload! 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/action/mount_nfs.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module Action 4 | class MountNFS 5 | 6 | def initialize(app,env) 7 | @app = app 8 | @logger = Log4r::Logger.new("vagrant-nfs_guest::action::unmount_nfs") 9 | end 10 | 11 | def call(env) 12 | @machine = env[:machine] 13 | 14 | # because we're hooked into Vagrant::Action::Builtin::SyncedFolders and 15 | # Vagrant::Action::Builtin::WaitForCommunicator we need to figure out 16 | # if we're 'up'ing from 'poweroff' or 'up'ing from 'suspend' 17 | # If 'poweroff' we get called twice once with state.id = 'poweroff' and 18 | # again with state.id = 'running'. This toggle around @app.call() allows 19 | # the second call to actually do the mount after the SyncFolder setup 20 | # has completed properly. If 'running' well then we just do the mount 21 | # as we're obvioulsy coming from a 'suspended' instance. Stupid bug. 22 | if !env[:nfs_guest_first_state] 23 | env[:nfs_guest_first_state] = @machine.state.id 24 | end 25 | 26 | @app.call(env) 27 | 28 | # ... and also deal with 'reload' :weary: 29 | if env[:nfs_guest_first_state] == :poweroff and env[:action_name] != :machine_action_reload 30 | env[:nfs_guest_first_state] = @machine.state.id 31 | return 32 | end 33 | 34 | if @machine.state.id == :running 35 | if !@machine.provider.capability?(:nfs_settings) 36 | raise SyncedFolderNFSGuest::Errors::ProviderNFSSettingsCapMissing 37 | end 38 | 39 | # grab the folders to check if any use nfs_guest and require host networking 40 | folders = @machine.config.vm.synced_folders 41 | 42 | nfs_guest = false 43 | nfs_guest_folders = {} 44 | 45 | folders.each do |name, opts| 46 | if opts[:type] == :nfs_guest && opts[:disabled] == false 47 | nfs_guest = true 48 | opts[:hostpath] = File.expand_path(opts[:hostpath], env[:root_path]) 49 | nfs_guest_folders[name] = opts.dup 50 | end 51 | end 52 | 53 | if not nfs_guest 54 | return 55 | end 56 | 57 | # grab the ips from the provider 58 | host_ip, machine_ip = @machine.provider.capability(:nfs_settings) 59 | 60 | raise Vagrant::Errors::NFSNoHostIP if !host_ip 61 | raise Vagrant::Errors::NFSNoGuestIP if !machine_ip 62 | 63 | machine_ip = [machine_ip] if !machine_ip.is_a?(Array) 64 | 65 | if @machine.config.nfs_guest.verify_installed 66 | if @machine.guest.capability?(:nfs_server_installed) 67 | installed = @machine.guest.capability(:nfs_server_installed) 68 | if installed 69 | # Mount guest NFS folders 70 | @machine.ui.info(I18n.t("vagrant_nfs_guest.actions.vm.nfs.mounting")) 71 | @machine.env.host.capability(:nfs_mount, @machine.ui, @machine.id, machine_ip, nfs_guest_folders) 72 | end 73 | end 74 | end 75 | end 76 | end 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/action/unmount_nfs.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module Action 4 | class UnmountNFS 5 | 6 | def initialize(app,env) 7 | @app = app 8 | @logger = Log4r::Logger.new("vagrant-nfs_guest::action::unmount_nfs") 9 | end 10 | 11 | def call(env) 12 | @machine = env[:machine] 13 | 14 | if @machine.state.id == :running 15 | # unmount any host mounted NFS folders 16 | @machine.ui.info(I18n.t("vagrant_nfs_guest.actions.vm.nfs.unmounting")) 17 | folders = @machine.config.vm.synced_folders 18 | 19 | nfs_guest = false 20 | nfs_guest_folders = {} 21 | 22 | folders.each do |name, opts| 23 | if opts[:type] == :nfs_guest && opts[:disabled] == false 24 | nfs_guest = true 25 | opts[:hostpath] = File.expand_path(opts[:hostpath], env[:root_path]) 26 | 27 | if env[:force_halt] 28 | # If this is a force halt, force unmount the nfs shares. 29 | opts[:unmount_options] = opts.fetch(:unmount_options, []) << '-f' 30 | 31 | # We have to change the working dir if inside a hostmount to 32 | # prevent "No such file or directory - getcwd" errors. 33 | if Dir.pwd.start_with?(opts[:hostpath]) 34 | Dir.chdir("#{opts[:hostpath]}/..") 35 | end 36 | end 37 | 38 | nfs_guest_folders[name] = opts.dup 39 | end 40 | end 41 | 42 | if not nfs_guest 43 | return 44 | end 45 | 46 | @machine.env.host.capability(:nfs_unmount, @machine.ui, nfs_guest_folders) 47 | end 48 | 49 | @app.call(env) 50 | 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/config.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Config < Vagrant.plugin("2", :config) 6 | attr_accessor :functional 7 | attr_accessor :map_uid 8 | attr_accessor :map_gid 9 | attr_accessor :verify_installed 10 | 11 | def initialize 12 | super 13 | 14 | @functional = UNSET_VALUE 15 | @map_uid = UNSET_VALUE 16 | @map_gid = UNSET_VALUE 17 | @verify_installed = UNSET_VALUE 18 | end 19 | 20 | def finalize! 21 | @functional = true if @functional == UNSET_VALUE 22 | @map_uid = nil if @map_uid == UNSET_VALUE 23 | @map_gid = nil if @map_gid == UNSET_VALUE 24 | @verify_installed = true if @verify_installed == UNSET_VALUE 25 | end 26 | 27 | def to_s 28 | "NFS_Guest" 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/errors.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | module Errors 6 | 7 | class Error < Vagrant::Errors::VagrantError 8 | error_namespace("vagrant_nfs_guest.errors") 9 | end 10 | 11 | class GuestNFSError < Error 12 | error_key(:nfs_server_missing) 13 | error_key(:nfs_start_failed) 14 | error_key(:nfs_apply_failed) 15 | error_key(:nfs_update_exports_failed) 16 | error_key(:nfs_guest_clean) 17 | error_key(:nfs_create_mounts_failed) 18 | end 19 | 20 | class NFSServerMissing < Error 21 | error_key(:nfs_server_missing) 22 | end 23 | 24 | class NFSServerNotInstalledInGuest < Error 25 | error_key(:nfs_server_not_installed) 26 | end 27 | 28 | class ProviderNFSSettingsCapMissing < Error 29 | error_key(:provider_missing_nfs_setting_cap) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/guests/debian/cap/nfs_server.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module GuestDebian 4 | module Cap 5 | class NFSServer 6 | def self.nfs_server_install(machine) 7 | machine.communicate.sudo("apt-get update") 8 | machine.communicate.sudo("apt-get -y install nfs-kernel-server") 9 | end 10 | 11 | def self.nfs_server_installed(machine) 12 | machine.communicate.test("test -e /etc/init.d/nfs-kernel-server") 13 | end 14 | 15 | def self.nfs_test_command(machine) 16 | machine.communicate.test( 17 | "test -x /usr/sbin/exportfs" 18 | ) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/guests/debian/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Plugin < Vagrant.plugin("2") 6 | name "Debian guest" 7 | description "Debian guest support." 8 | 9 | guest_capability(:debian, :nfs_test_command) do 10 | require_relative "cap/nfs_server" 11 | GuestDebian::Cap::NFSServer 12 | end 13 | 14 | guest_capability(:debian, :nfs_server_installed) do 15 | require_relative "cap/nfs_server" 16 | GuestDebian::Cap::NFSServer 17 | end 18 | 19 | guest_capability(:debian, :nfs_server_install) do 20 | require_relative "cap/nfs_server" 21 | GuestDebian::Cap::NFSServer 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/guests/linux/cap/nfs_server.rb: -------------------------------------------------------------------------------- 1 | require "vagrant/util/retryable" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | module GuestLinux 6 | module Cap 7 | class NFSServer 8 | extend Vagrant::Util::Retryable 9 | 10 | def self.nfs_apply_command(machine) 11 | machine.communicate.sudo( 12 | "exportfs -r", 13 | error_class: Errors::GuestNFSError, 14 | error_key: :nfs_apply_failed 15 | ) 16 | end 17 | 18 | def self.nfs_start_command(machine) 19 | machine.communicate.sudo( 20 | "/etc/init.d/nfs-kernel-server start", 21 | error_class: Errors::GuestNFSError, 22 | error_key: :nfs_start_failed 23 | ) 24 | end 25 | 26 | def self.nfs_check_command(machine) 27 | machine.communicate.test( 28 | "/etc/init.d/nfs-kernel-server status" 29 | ) 30 | end 31 | 32 | def self.nfs_test_command(machine) 33 | machine.communicate.test( 34 | "which exportfs" 35 | ) 36 | end 37 | 38 | def self.nfs_exports_template(machine) 39 | VagrantPlugins::SyncedFolderNFSGuest.source_root.join( 40 | "templates/nfs_guest/guest_export_linux") 41 | end 42 | 43 | def self.nfs_capable?(machine) 44 | machine.guest.capability(:nfs_test_command) 45 | end 46 | 47 | def self.nfs_apply_changes!(machine) 48 | machine.guest.capability(:nfs_apply_command) 49 | end 50 | 51 | def self.nfs_start!(machine) 52 | machine.guest.capability(:nfs_start_command) 53 | end 54 | 55 | def self.nfs_running?(machine) 56 | machine.guest.capability(:nfs_check_command) 57 | end 58 | 59 | def self.nfs_export(machine, ips, folders) 60 | if !nfs_capable?(machine) 61 | raise Errors::NFSServerMissing 62 | end 63 | 64 | if machine.guest.capability?(:nfs_setup_firewall) 65 | machine.ui.info I18n.t("vagrant_nfs_guest.guests.linux.nfs_setup_firewall") 66 | ips.each do |ip| 67 | machine.guest.capability(:nfs_setup_firewall, ip) 68 | end 69 | end 70 | 71 | nfs_exports_template = machine.guest.capability(:nfs_exports_template) 72 | 73 | nfs_opts_setup(machine, folders) 74 | 75 | output = Vagrant::Util::TemplateRenderer.render( 76 | nfs_exports_template, 77 | uuid: machine.id, 78 | ips: ips, 79 | folders: folders, 80 | user: Process.uid) 81 | 82 | machine.ui.info I18n.t("vagrant_nfs_guest.guests.linux.nfs_export") 83 | sleep 0.5 84 | 85 | nfs_cleanup(machine) 86 | 87 | retryable(on: Errors::GuestNFSError, tries: 8, sleep: 3) do 88 | output.split("\n").each do |line| 89 | machine.communicate.sudo( 90 | %Q[echo '#{line}' >> /etc/exports], 91 | error_class: Errors::GuestNFSError, 92 | error_key: :nfs_update_exports_failed 93 | ) 94 | end 95 | 96 | if nfs_running?(machine) 97 | nfs_apply_changes!(machine) 98 | else 99 | nfs_start!(machine) 100 | end 101 | end 102 | end 103 | 104 | def self.nfs_cleanup(machine) 105 | return if !nfs_capable?(machine) 106 | 107 | id = machine.id 108 | user = Process.uid 109 | 110 | # Use sed to just strip out the block of code which was inserted 111 | # by Vagrant 112 | # 113 | machine.communicate.sudo( 114 | "sed -r -e '/^# VAGRANT(-NFS_GUEST)?-BEGIN/,/^# VAGRANT(-NFS_GUEST)?-END/ d' -ibak /etc/exports", 115 | error_class: Errors::GuestNFSError, 116 | error_key: :nfs_guest_clean 117 | ) 118 | end 119 | 120 | def self.nfs_opts_setup(machine, folders) 121 | folders.each do |k, opts| 122 | if !opts[:linux__nfs_options] 123 | opts[:linux__nfs_options] ||= ["rw", "no_subtree_check", "all_squash", "insecure"] 124 | end 125 | 126 | # Only automatically set anonuid/anongid if they weren't 127 | # explicitly set by the user. 128 | hasgid = false 129 | hasuid = false 130 | opts[:linux__nfs_options].each do |opt| 131 | hasgid = !!(opt =~ /^anongid=/) if !hasgid 132 | hasuid = !!(opt =~ /^anonuid=/) if !hasuid 133 | end 134 | 135 | opts[:linux__nfs_options] << "anonuid=#{opts[:map_uid]}" if !hasuid 136 | opts[:linux__nfs_options] << "anongid=#{opts[:map_gid]}" if !hasgid 137 | opts[:linux__nfs_options] << "fsid=#{opts[:uuid]}" 138 | 139 | # Expand the guest path so we can handle things like "~/vagrant" 140 | expanded_guest_path = machine.guest.capability( 141 | :shell_expand_guest_path, opts[:guestpath]) 142 | 143 | retryable(on: Errors::GuestNFSError, tries: 8, sleep: 3) do 144 | # Do the actual creating and mounting 145 | machine.communicate.sudo( 146 | "mkdir -p #{expanded_guest_path}", 147 | error_class: Errors::GuestNFSError, 148 | error_key: :nfs_create_mounts_failed 149 | ) 150 | 151 | # Folder options 152 | opts[:owner] ||= machine.ssh_info[:username] 153 | opts[:group] ||= machine.ssh_info[:username] 154 | 155 | machine.communicate.sudo( 156 | "chown #{opts[:owner]}:#{opts[:group]} #{expanded_guest_path}", 157 | error_class: Errors::GuestNFSError, 158 | error_key: :nfs_create_mounts_failed 159 | ) 160 | machine.communicate.sudo( 161 | "chmod 2775 #{expanded_guest_path}", 162 | error_class: Errors::GuestNFSError, 163 | error_key: :nfs_create_mounts_failed 164 | ) 165 | end 166 | end 167 | end 168 | end 169 | end 170 | end 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/guests/linux/cap/read_user_ids.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module GuestLinux 4 | module Cap 5 | class ReadUserIDs 6 | def self.read_uid(machine) 7 | command = "id -u" 8 | result = "" 9 | machine.communicate.execute(command) do |type, data| 10 | result << data if type == :stdout 11 | end 12 | 13 | result.chomp.split("\n").first 14 | end 15 | 16 | def self.read_gid(machine) 17 | command = "id -g" 18 | result = "" 19 | machine.communicate.execute(command) do |type, data| 20 | result << data if type == :stdout 21 | end 22 | 23 | result.chomp.split("\n").first 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/guests/linux/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Plugin < Vagrant.plugin("2") 6 | name "Linux guest." 7 | description "Linux guest support." 8 | 9 | guest_capability(:linux, :nfs_export) do 10 | require_relative "cap/nfs_server" 11 | GuestLinux::Cap::NFSServer 12 | end 13 | 14 | guest_capability(:linux, :nfs_apply_command) do 15 | require_relative "cap/nfs_server" 16 | GuestLinux::Cap::NFSServer 17 | end 18 | 19 | guest_capability(:linux, :nfs_check_command) do 20 | require_relative "cap/nfs_server" 21 | GuestLinux::Cap::NFSServer 22 | end 23 | 24 | guest_capability(:linux, :nfs_start_command) do 25 | require_relative "cap/nfs_server" 26 | GuestLinux::Cap::NFSServer 27 | end 28 | 29 | guest_capability(:linux, :nfs_test_command) do 30 | require_relative "cap/nfs_server" 31 | GuestLinux::Cap::NFSServer 32 | end 33 | 34 | guest_capability(:linux, :nfs_exports_template) do 35 | require_relative "cap/nfs_server" 36 | GuestLinux::Cap::NFSServer 37 | end 38 | 39 | guest_capability(:linux, :read_uid) do 40 | require_relative "cap/read_user_ids" 41 | GuestLinux::Cap::ReadUserIDs 42 | end 43 | 44 | guest_capability(:linux, :read_gid) do 45 | require_relative "cap/read_user_ids" 46 | GuestLinux::Cap::ReadUserIDs 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/guests/redhat/cap/nfs_server.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module GuestRedHat 4 | module Cap 5 | class NFSServer 6 | 7 | def self.systemd?(machine) 8 | machine.communicate.test("test $(ps -o comm= 1) == 'systemd'") 9 | end 10 | 11 | def self.firewalld?(machine) 12 | machine.communicate.test("test $(command -v firewall-cmd)") 13 | end 14 | 15 | def self.firewalld_enabled?(machine) 16 | machine.communicate.test("test $(firewall-cmd --state) == 'running'") 17 | end 18 | 19 | def self.dnf?(machine) 20 | machine.communicate.test("test $(command -v dnf)") 21 | end 22 | 23 | def self.nfs_server_install(machine) 24 | if dnf?(machine) 25 | machine.communicate.sudo("dnf -y install nfs-utils") 26 | else 27 | machine.communicate.sudo("yum -y install nfs-utils") 28 | end 29 | 30 | if systemd?(machine) 31 | machine.communicate.sudo("systemctl enable rpcbind nfs-server") 32 | else 33 | machine.communicate.sudo("chkconfig nfs on") 34 | 35 | end 36 | end 37 | 38 | def self.nfs_setup_firewall(machine, ip) 39 | if not systemd?(machine) 40 | # centos 6.5 has iptables on by default is would seem 41 | machine.communicate.sudo( 42 | "sed -i \"s/#LOCKD_/LOCKD_/\" /etc/sysconfig/nfs" 43 | ) 44 | machine.communicate.sudo( 45 | "sed -i \"s/#MOUNTD_PORT/MOUNTD_PORT/\" /etc/sysconfig/nfs" 46 | ) 47 | machine.communicate.sudo( 48 | "iptables -I INPUT -m state --state NEW -p udp -m multiport " + 49 | "--dport 111,892,2049,32803 -s #{ip}/32 -j ACCEPT" 50 | ) 51 | machine.communicate.sudo( 52 | "iptables -I INPUT -m state --state NEW -p tcp -m multiport " + 53 | "--dport 111,892,2049,32803 -s #{ip}/32 -j ACCEPT" 54 | ) 55 | 56 | nfs_start_command(machine) 57 | end 58 | 59 | if firewalld?(machine) and firewalld_enabled?(machine) 60 | # add nfs rules if we have firewalld and it's enabled 61 | machine.communicate.sudo("firewall-cmd --permanent --add-service=nfs") 62 | machine.communicate.sudo("firewall-cmd --permanent --add-service=mountd") 63 | machine.communicate.sudo("firewall-cmd --permanent --add-service=rpc-bind") 64 | machine.communicate.sudo("firewall-cmd --reload") 65 | end 66 | end 67 | 68 | def self.nfs_server_installed(machine) 69 | if systemd?(machine) 70 | machine.communicate.test("systemctl status nfs-server.service") 71 | else 72 | machine.communicate.test("service nfs status") 73 | end 74 | end 75 | 76 | def self.nfs_check_command(machine) 77 | if systemd?(machine) 78 | machine.communicate.test( 79 | "systemctl status rpcbind nfs-server" 80 | ) 81 | else 82 | machine.communicate.test( 83 | "service nfs status" 84 | ) 85 | end 86 | end 87 | 88 | def self.nfs_start_command(machine) 89 | if systemd?(machine) 90 | machine.communicate.sudo( 91 | "systemctl start rpcbind nfs-server", 92 | error_class: Errors::GuestNFSError, 93 | error_key: :nfs_start_failed 94 | ) 95 | else 96 | ['rpcbind', 'nfs'].each do |i| 97 | machine.communicate.sudo( 98 | "service #{i} restart", 99 | error_class: Errors::GuestNFSError, 100 | error_key: :nfs_start_failed 101 | ) 102 | end 103 | end 104 | end 105 | end 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/guests/redhat/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Plugin < Vagrant.plugin("2") 6 | name "Red Hat Enterprise Linux guest" 7 | description "Red Hat Enterprise Linux guest support." 8 | 9 | guest_capability(:redhat, :nfs_server_installed) do 10 | require_relative "cap/nfs_server" 11 | GuestRedHat::Cap::NFSServer 12 | end 13 | 14 | guest_capability(:redhat, :nfs_server_install) do 15 | require_relative "cap/nfs_server" 16 | GuestRedHat::Cap::NFSServer 17 | end 18 | 19 | guest_capability(:redhat, :nfs_check_command) do 20 | require_relative "cap/nfs_server" 21 | GuestRedHat::Cap::NFSServer 22 | end 23 | 24 | guest_capability(:redhat, :nfs_start_command) do 25 | require_relative "cap/nfs_server" 26 | GuestRedHat::Cap::NFSServer 27 | end 28 | 29 | guest_capability(:redhat, :nfs_setup_firewall) do 30 | require_relative "cap/nfs_server" 31 | GuestRedHat::Cap::NFSServer 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/guests/ubuntu/cap/nfs_server.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module GuestUbuntu 4 | module Cap 5 | class NFSServer 6 | def self.nfs_server_install(machine) 7 | machine.communicate.sudo("apt-get update") 8 | machine.communicate.sudo("apt-get -y install nfs-kernel-server") 9 | end 10 | 11 | def self.nfs_server_installed(machine) 12 | machine.communicate.test("test -e /etc/init.d/nfs-kernel-server") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/guests/ubuntu/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Plugin < Vagrant.plugin("2") 6 | name "Ubuntu guest" 7 | description "Ubuntu guest support." 8 | 9 | guest_capability(:ubuntu, :nfs_server_installed) do 10 | require_relative "cap/nfs_server" 11 | GuestUbuntu::Cap::NFSServer 12 | end 13 | 14 | guest_capability(:ubuntu, :nfs_server_install) do 15 | require_relative "cap/nfs_server" 16 | GuestUbuntu::Cap::NFSServer 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/hosts/bsd/cap/mount_nfs.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module HostBSD 4 | module Cap 5 | class MountNFS 6 | 7 | def self.nfs_mount(environment, ui, id, ips, folders) 8 | folders.each do |name, opts| 9 | ips.each do |ip| 10 | ui.detail(I18n.t("vagrant.actions.vm.share_folders.mounting_entry", 11 | guestpath: opts[:guestpath], 12 | hostpath: opts[:hostpath])) 13 | 14 | mount_options = opts.fetch(:mount_options, ["noatime"]) 15 | nfs_options = mount_options.empty? ? "" : "-o #{mount_options.join(',')}" 16 | 17 | mount_command = "mount -t nfs #{nfs_options} '#{ip}:#{opts[:guestpath]}' '#{opts[:hostpath]}'" 18 | if system(mount_command) 19 | break 20 | end 21 | end 22 | end 23 | end 24 | 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/hosts/bsd/cap/unmount_nfs.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module HostBSD 4 | module Cap 5 | class UnmountNFS 6 | 7 | def self.nfs_unmount(environment, ui, folders) 8 | folders.each do |name, opts| 9 | ui.detail(I18n.t("vagrant.actions.vm.share_folders.mounting_entry", 10 | guestpath: opts[:guestpath], 11 | hostpath: opts[:hostpath])) 12 | 13 | unmount_options = opts.fetch(:unmount_options, []).join(" ") 14 | 15 | umount_msg = `umount #{unmount_options} '#{opts[:hostpath]}' 2>&1` 16 | 17 | if $?.exitstatus != 0 18 | unless /not (currently )?mounted/ =~ umount_msg 19 | ui.info "NFS mounts still in use!" 20 | exit(1) 21 | end 22 | end 23 | end 24 | end 25 | 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/hosts/bsd/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Plugin < Vagrant.plugin("2") 6 | name "BSD host" 7 | description "BSD host support." 8 | 9 | host_capability("bsd", "nfs_mount") do 10 | require_relative "cap/mount_nfs" 11 | HostBSD::Cap::MountNFS 12 | end 13 | 14 | host_capability("bsd", "nfs_unmount") do 15 | require_relative "cap/unmount_nfs" 16 | HostBSD::Cap::UnmountNFS 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/hosts/linux/cap/mount_nfs.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module HostLinux 4 | module Cap 5 | class MountNFS 6 | 7 | def self.nfs_mount(environment, ui, id, ips, folders) 8 | folders.each do |name, opts| 9 | ips.each do |ip| 10 | ui.detail(I18n.t("vagrant.actions.vm.share_folders.mounting_entry", 11 | guestpath: opts[:guestpath], 12 | hostpath: opts[:hostpath])) 13 | 14 | mount_options = opts.fetch(:mount_options, ["noatime"]) 15 | nfs_options = mount_options.empty? ? "" : "-o #{mount_options.join(',')}" 16 | 17 | mount_command = "sudo mount -t nfs #{nfs_options} '#{ip}:#{opts[:guestpath]}' '#{opts[:hostpath]}'" 18 | if system(mount_command) 19 | break 20 | end 21 | end 22 | end 23 | end 24 | 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/hosts/linux/cap/unmount_nfs.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module HostLinux 4 | module Cap 5 | class UnmountNFS 6 | 7 | def self.nfs_unmount(environment, ui, folders) 8 | folders.each do |name, opts| 9 | ui.detail(I18n.t("vagrant.actions.vm.share_folders.mounting_entry", 10 | guestpath: opts[:guestpath], 11 | hostpath: opts[:hostpath])) 12 | 13 | unmount_options = opts.fetch(:unmount_options, []).join(" ") 14 | 15 | umount_msg = `sudo umount #{unmount_options} '#{opts[:hostpath]}' 2>&1` 16 | 17 | if $?.exitstatus != 0 18 | unless /not (currently )?mounted/ =~ umount_msg 19 | ui.info umount_msg 20 | ui.info "Maybe NFS mounts still in use!" 21 | exit(1) 22 | end 23 | end 24 | end 25 | end 26 | 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/hosts/linux/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Plugin < Vagrant.plugin("2") 6 | name "Linux host" 7 | description "Linux host support." 8 | 9 | host_capability(:linux, "nfs_mount") do 10 | require_relative "cap/mount_nfs" 11 | HostLinux::Cap::MountNFS 12 | end 13 | 14 | host_capability(:linux, "nfs_unmount") do 15 | require_relative "cap/unmount_nfs" 16 | HostLinux::Cap::UnmountNFS 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/plugin.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require "vagrant" 3 | rescue LoadError 4 | raise "The Vagrant NFS Guest plugin must be run within Vagrant." 5 | end 6 | 7 | # This is a sanity check to make sure no one is attempting to install 8 | # this into an early Vagrant version. 9 | if Vagrant::VERSION < "1.2.0" 10 | raise "The Vagrant NFS Guest plugin is only compatible with Vagrant 1.2+" 11 | end 12 | 13 | require_relative "guests/debian/plugin" 14 | require_relative "guests/linux/plugin" 15 | require_relative "guests/redhat/plugin" 16 | require_relative "guests/ubuntu/plugin" 17 | require_relative "hosts/bsd/plugin" 18 | require_relative "hosts/linux/plugin" 19 | require_relative "providers/virtualbox/plugin" 20 | require_relative "providers/parallels/plugin" 21 | require_relative "providers/docker/plugin" 22 | require_relative "providers/lxc/plugin" 23 | require_relative "providers/vmware_fusion/plugin" 24 | 25 | module VagrantPlugins 26 | module SyncedFolderNFSGuest 27 | # This plugin implements Guest Exported NFS folders. 28 | # 29 | class Plugin < Vagrant.plugin("2") 30 | name "vagrant-nfs_guest" 31 | description <<-DESC 32 | The Vagrant NFS Guest synced folders plugin enables you to use NFS exports from 33 | the Guest as a synced folder implementation. This allows the guest to utilise 34 | inotify (eg. file watchers) and other filesystem related functions that don't 35 | work across NFS from the host. 36 | DESC 37 | 38 | config(:nfs_guest) do 39 | require_relative "config" 40 | Config 41 | end 42 | 43 | synced_folder(:nfs_guest, 5) do 44 | require_relative "synced_folder" 45 | SyncedFolder 46 | end 47 | 48 | action_hook(:nfs_guest, :machine_action_up) do |hook| 49 | require_relative "action/mount_nfs" 50 | hook.before( 51 | Vagrant::Action::Builtin::WaitForCommunicator, 52 | Action::MountNFS 53 | ) 54 | hook.before( 55 | Vagrant::Action::Builtin::SyncedFolders, 56 | Action::MountNFS 57 | ) 58 | end 59 | 60 | action_hook(:nfs_guest, :machine_action_suspend) do |hook| 61 | require_relative "action/unmount_nfs" 62 | hook.prepend(Action::UnmountNFS) 63 | end 64 | 65 | action_hook(:nfs_guest, :machine_action_resume) do |hook| 66 | require_relative "action/mount_nfs" 67 | hook.after( 68 | Vagrant::Action::Builtin::WaitForCommunicator, 69 | Action::MountNFS 70 | ) 71 | end 72 | 73 | action_hook(:nfs_guest, :machine_action_halt) do |hook| 74 | require_relative "action/unmount_nfs" 75 | hook.before( 76 | Vagrant::Action::Builtin::GracefulHalt, 77 | Action::UnmountNFS 78 | ) 79 | end 80 | 81 | action_hook(:nfs_guest, :machine_action_reload) do |hook| 82 | require_relative "action/unmount_nfs" 83 | require_relative "action/mount_nfs" 84 | hook.before( 85 | Vagrant::Action::Builtin::GracefulHalt, 86 | Action::UnmountNFS 87 | ) 88 | hook.before( 89 | Vagrant::Action::Builtin::SyncedFolders, 90 | Action::MountNFS 91 | ) 92 | end 93 | 94 | action_hook(:nfs_guest, :machine_action_destroy) do |hook| 95 | require_relative "action/unmount_nfs" 96 | hook.after( 97 | Vagrant::Action::Builtin::DestroyConfirm, 98 | Action::UnmountNFS 99 | ) 100 | end 101 | 102 | action_hook(:nfs_guest, :machine_action_package) do |hook| 103 | require_relative "action/unmount_nfs" 104 | hook.before( 105 | Vagrant::Action::Builtin::GracefulHalt, 106 | Action::UnmountNFS 107 | ) 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/providers/docker/cap/nfs_settings.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module ProviderDocker 4 | module Cap 5 | 6 | def self.nfs_settings(machine) 7 | provider = machine.provider 8 | 9 | host_ip = provider.driver.docker_bridge_ip 10 | machine_ip = provider.ssh_info[:host] 11 | 12 | raise Vagrant::Errors::NFSNoHostonlyNetwork if !host_ip || !machine_ip 13 | 14 | return host_ip, machine_ip 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/providers/docker/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Plugin < Vagrant.plugin("2") 6 | name "docker-provider" 7 | description "Docker provider" 8 | 9 | provider_capability(:docker, :nfs_settings) do 10 | require_relative "cap/nfs_settings" 11 | ProviderDocker::Cap 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/providers/lxc/cap/nfs_settings.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module ProviderLxc 4 | module Cap 5 | 6 | def self.read_host_ip(machine) 7 | machine.communicate.execute 'echo $SSH_CLIENT' do |buffer, output| 8 | return output.chomp.split(' ')[0] if buffer == :stdout 9 | end 10 | end 11 | 12 | def self.nfs_settings(machine) 13 | host_ip = self.read_host_ip(machine) 14 | machine_ip = machine.provider.ssh_info[:host] 15 | 16 | raise Vagrant::Errors::NFSNoHostonlyNetwork if !host_ip || !machine_ip 17 | 18 | return host_ip, machine_ip 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/providers/lxc/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Plugin < Vagrant.plugin("2") 6 | name "lxc-provider" 7 | description "vagrant-lxc provider" 8 | 9 | provider_capability(:lxc, :nfs_settings) do 10 | require_relative "cap/nfs_settings" 11 | ProviderLxc::Cap 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/providers/parallels/cap/nfs_settings.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module ProviderParallels 4 | module Cap 5 | 6 | def self.nfs_settings(machine) 7 | host_ip = machine.provider.driver.read_shared_interface[:ip] 8 | machine_ip = machine.provider.driver.read_guest_ip 9 | 10 | raise Vagrant::Errors::NFSNoHostonlyNetwork if !host_ip || !machine_ip 11 | 12 | return host_ip, machine_ip 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/providers/parallels/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Plugin < Vagrant.plugin("2") 6 | name "vagrant-parallels" 7 | description "Parallels provider" 8 | 9 | provider_capability(:parallels, :nfs_settings) do 10 | require_relative "cap/nfs_settings" 11 | ProviderParallels::Cap 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/providers/virtualbox/cap/nfs_settings.rb: -------------------------------------------------------------------------------- 1 | require "vagrant/util/retryable" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | module ProviderVirtualBox 6 | module Cap 7 | extend Vagrant::Util::Retryable 8 | 9 | def self.nfs_settings(machine) 10 | adapter, host_ip = self.find_host_only_adapter(machine) 11 | machine_ip = self.read_static_machine_ips(machine) || self.read_dynamic_machine_ip(machine, adapter) 12 | return host_ip, machine_ip 13 | end 14 | 15 | # Finds first host only network adapter and returns its adapter number 16 | # and IP address 17 | # 18 | # @return [Integer, String] adapter number, ip address of found host-only adapter 19 | def self.find_host_only_adapter(machine) 20 | machine.provider.driver.read_network_interfaces.each do |adapter, opts| 21 | if opts[:type] == :hostonly 22 | machine.provider.driver.read_host_only_interfaces.each do |interface| 23 | if interface[:name] == opts[:hostonly] 24 | return adapter, interface[:ip] 25 | end 26 | end 27 | end 28 | end 29 | 30 | nil 31 | end 32 | 33 | # Returns the IP address(es) of the guest by looking for static IPs 34 | # given to host only adapters in the Vagrantfile 35 | # 36 | # @return [Array] Configured static IPs 37 | def self.read_static_machine_ips(machine) 38 | ips = [] 39 | machine.config.vm.networks.each do |type, options| 40 | if type == :private_network && options[:type] != :dhcp && options[:ip].is_a?(String) 41 | ips << options[:ip] 42 | end 43 | end 44 | 45 | if ips.empty? 46 | return nil 47 | end 48 | 49 | ips 50 | end 51 | 52 | # Returns the IP address of the guest by looking at vbox guest property 53 | # for the appropriate guest adapter. 54 | # 55 | # For DHCP interfaces, the guest property will not be present until the 56 | # guest completes 57 | # 58 | # @param [Integer] adapter number to read IP for 59 | # @return [String] ip address of adapter 60 | def self.read_dynamic_machine_ip(machine, adapter) 61 | return nil unless adapter 62 | 63 | # vbox guest properties are 0-indexed, while showvminfo network 64 | # interfaces are 1-indexed. go figure. 65 | guestproperty_adapter = adapter - 1 66 | 67 | # we need to wait for the guest's IP to show up as a guest property. 68 | # retry thresholds are relatively high since we might need to wait 69 | # for DHCP, but even static IPs can take a second or two to appear. 70 | retryable(retry_options.merge(on: Vagrant::Errors::VirtualBoxGuestPropertyNotFound)) do 71 | machine.provider.driver.read_guest_ip(guestproperty_adapter) 72 | end 73 | rescue Vagrant::Errors::VirtualBoxGuestPropertyNotFound 74 | # this error is more specific with a better error message directing 75 | # the user towards the fact that it's probably a reportable bug 76 | raise Vagrant::Errors::NFSNoGuestIP 77 | end 78 | 79 | # Separating these out so we can stub out the sleep in tests 80 | def self.retry_options 81 | {tries: 15, sleep: 1} 82 | end 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/providers/virtualbox/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Plugin < Vagrant.plugin("2") 6 | name "VirtualBox provider" 7 | description "VirtualBox provider" 8 | 9 | provider_capability(:virtualbox, :nfs_settings) do 10 | require_relative "cap/nfs_settings" 11 | ProviderVirtualBox::Cap 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/providers/vmware_fusion/cap/nfs_settings.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | module ProviderVMwareFusion 4 | module Cap 5 | def self.read_host_ip 6 | # In practice, we need the host's IP on the vmnet device this VM uses. 7 | # It seems a bit tricky to get the right one, so let's allow all. 8 | return `for N in $(ifconfig | awk -F: '/vmnet/ { print $1; }'); do ifconfig $N | awk '/inet / { print $2; }'; done`.split("\n") 9 | end 10 | 11 | def self.nfs_settings(machine) 12 | host_ip = self.read_host_ip 13 | machine_ip = machine.provider.driver.read_ip 14 | return host_ip, machine_ip 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/providers/vmware_fusion/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class Plugin < Vagrant.plugin("2") 6 | name "vmware_fusion-provider" 7 | description "vagrant-vmware_fusion provider" 8 | 9 | provider_capability(:vmware_fusion, :nfs_settings) do 10 | require_relative "cap/nfs_settings" 11 | ProviderVMwareFusion::Cap 12 | end 13 | provider_capability(:vmware_desktop, :nfs_settings) do 14 | require_relative "cap/nfs_settings" 15 | ProviderVMwareFusion::Cap 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/synced_folder.rb: -------------------------------------------------------------------------------- 1 | require 'zlib' 2 | 3 | module VagrantPlugins 4 | module SyncedFolderNFSGuest 5 | class SyncedFolder < Vagrant.plugin("2", :synced_folder) 6 | 7 | def initialize(*args) 8 | super 9 | 10 | @logger = Log4r::Logger.new("vagrant::synced_folders::nfs_guest") 11 | end 12 | 13 | def usable?(machine, raise_error=false) 14 | # If the machine explicitly said NFS is not supported, then 15 | # it isn't supported. 16 | if !machine.config.nfs_guest.functional 17 | return false 18 | end 19 | return true if machine.env.host.capability(:nfs_installed) 20 | return false if !raise_error 21 | raise Vagrant::Errors::NFSNotSupported 22 | end 23 | 24 | def enable(machine, folders, nfsopts) 25 | if !machine.provider.capability?(:nfs_settings) 26 | raise Errors::ProviderNFSSettingsCapMissing 27 | end 28 | 29 | # I've abstracted this out to a plugin provided capability per 30 | # provider as it's impossible to resume a VM because the 31 | # PrepareNFSSettings action NEVER is trigger on a resume because 32 | # the host is exporting so therefore it's assumed to always be there. 33 | # Easier to maintain and add new providers this way. 34 | host_ip, machine_ip = machine.provider.capability(:nfs_settings) 35 | machine_ip = [machine_ip] if !machine_ip.is_a?(Array) 36 | 37 | raise Vagrant::Errors::NFSNoHostonlyNetwork if !host_ip || !machine_ip 38 | 39 | if machine.config.nfs_guest.verify_installed 40 | if machine.guest.capability?(:nfs_server_installed) 41 | installed = machine.guest.capability(:nfs_server_installed) 42 | if !installed 43 | can_install = machine.guest.capability?(:nfs_server_install) 44 | raise Errors::NFSServerNotInstalledInGuest if !can_install 45 | machine.ui.info I18n.t("vagrant_nfs_guest.guests.linux.nfs_server_installing") 46 | machine.guest.capability(:nfs_server_install) 47 | end 48 | end 49 | end 50 | 51 | # Prepare the folder, this means setting up various options 52 | # and such on the folder itself. 53 | folders.each { |id, opts| prepare_folder(machine, opts) } 54 | 55 | # Only mount folders that have a guest path specified. 56 | mount_folders = {} 57 | folders.each do |id, opts| 58 | mount_folders[id] = opts.dup if opts[:guestpath] 59 | end 60 | 61 | machine.ui.info I18n.t("vagrant_nfs_guest.actions.vm.nfs.exporting") 62 | machine.guest.capability(:nfs_export, Array(host_ip), mount_folders) 63 | end 64 | 65 | protected 66 | 67 | def prepare_folder(machine, opts) 68 | opts[:map_uid] = prepare_permission(machine, :uid, opts) 69 | opts[:map_gid] = prepare_permission(machine, :gid, opts) 70 | opts[:nfs_udp] = true if !opts.has_key?(:nfs_udp) 71 | opts[:nfs_version] ||= 3 72 | 73 | # We use a CRC32 to generate a 32-bit checksum so that the 74 | # fsid is compatible with both old and new kernels. 75 | opts[:uuid] = Zlib.crc32(opts[:hostpath]).to_s 76 | end 77 | 78 | # Prepares the UID/GID settings for a single folder. 79 | def prepare_permission(machine, perm, opts) 80 | key = "map_#{perm}".to_sym 81 | return nil if opts.has_key?(key) && opts[key].nil? 82 | 83 | # The options on the hash get priority, then the default 84 | # values 85 | value = opts.has_key?(key) ? opts[key] : machine.config.nfs.send(key) 86 | return value if value != :auto 87 | 88 | # Get UID/GID from guests user if we've made it this far 89 | # (value == :auto) 90 | return machine.guest.capability("read_#{perm}".to_sym) 91 | end 92 | end 93 | end 94 | end 95 | 96 | -------------------------------------------------------------------------------- /lib/vagrant-nfs_guest/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module SyncedFolderNFSGuest 3 | VERSION = "1.0.5" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /templates/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vagrant_nfs_guest: 3 | actions: 4 | vm: 5 | nfs: 6 | exporting: "Exporting NFS shared folders from guest..." 7 | mounting: "Mounting NFS shared folders from guest..." 8 | unmounting: "Unmounting NFS shared folders from guest..." 9 | guests: 10 | linux: 11 | nfs_export: "Preparing to edit /etc/exports on the guest..." 12 | nfs_server_installing: "Installing nfs server on the guest..." 13 | nfs_setup_firewall: "Setup firewall on the guest to allow NFS exports..." 14 | 15 | errors: 16 | nfs_update_exports_failed: |- 17 | There was an error updating the guests /etc/exports file. 18 | 19 | %{command} 20 | 21 | Stdout from the command: 22 | %{stdout} 23 | 24 | Stderr from the command: 25 | %{stderr} 26 | nfs_start_failed: |- 27 | Something failed while starting the NFS service on the guest. 28 | 29 | %{command} 30 | 31 | Stdout from the command: 32 | %{stdout} 33 | 34 | Stderr from the command: 35 | %{stderr} 36 | nfs_apply_failed: |- 37 | Something failed while applying changes to the NFS service on the guest. 38 | 39 | %{command} 40 | 41 | Stdout from the command: 42 | %{stdout} 43 | 44 | Stderr from the command: 45 | %{stderr} 46 | nfs_create_mounts_failed: |- 47 | Something failed while creating the NFS mounts on the guest. 48 | 49 | %{command} 50 | 51 | Stdout from the command: 52 | %{stdout} 53 | 54 | Stderr from the command: 55 | %{stderr} 56 | 57 | nfs_server_missing: |- 58 | Guest is missing the required NFS server daemon. 59 | 60 | nfs_server_not_installed: |- 61 | Guest cannot install the required NFS server daemon. 62 | 63 | nfs_guest_clean: |- 64 | Something failed while cleaning up NFS shared folders on the guest. 65 | 66 | provider_missing_nfs_setting_cap: |- 67 | Missing provider support in vagrant-nfs_guest plugin for retrieving host and guest IPs. 68 | 69 | Currently only Virtualbox provider is supported. 70 | -------------------------------------------------------------------------------- /templates/nfs_guest/guest_export_linux.erb: -------------------------------------------------------------------------------- 1 | # VAGRANT-NFS_GUEST-BEGIN 2 | <% ips.each do |ip| %> 3 | <% folders.each do |dirs, opts| %> 4 | "<%= opts[:guestpath] %>" <%= ip %>(<%= opts[:linux__nfs_options].join(",") %>) 5 | <% end %> 6 | <% end %> 7 | # VAGRANT-NFS_GUEST-END 8 | -------------------------------------------------------------------------------- /vagrant-nfs_guest.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'vagrant-nfs_guest/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "vagrant-nfs_guest" 8 | spec.version = VagrantPlugins::SyncedFolderNFSGuest::VERSION 9 | spec.authors = ["Alan Garfield"] 10 | spec.email = ["alan.garfield@learnosity.com"] 11 | spec.description = %q{Adds support for guest nfs exporting of synced folders} 12 | spec.summary = %q{Adds support for guest nfs exporting of synced folders} 13 | spec.homepage = "" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_dependency "bundler", ">= 2.2.10" 22 | spec.add_development_dependency "rake" 23 | end 24 | --------------------------------------------------------------------------------