pax_global_header00006660000000000000000000000064132513467620014523gustar00rootroot0000000000000052 comment=5cb3943c6806c42551f676c468136e1555b8e654 .gitignore000066400000000000000000000002221325134676200130530ustar00rootroot00000000000000# Build artifacts pkg build # Ruby / Bundler Gemfile.lock .ruby-gemset .ruby-version # .vagrant dirs .vagrant # IDE config files .idea *.iml Gemfile000066400000000000000000000010721325134676200123620ustar00rootroot00000000000000source "https://rubygems.org" gemspec group :development do # We depend on Vagrant for development, but we don't add it as a # gem dependency because we expect to be installed within the # Vagrant environment itself using `vagrant plugin`. gem "vagrant", :git => "https://github.com/mitchellh/vagrant.git", :ref => 'v2.0.2' end group :plugins do gem "vagrant-sshfs" , path: "." # Add vagrant-libvirt plugin here, otherwise you won't be able to # use libvirt as a provider when you execute `bundle exec vagrant up` gem "vagrant-libvirt" , '0.0.43' end LICENSE000066400000000000000000000431771325134676200121100ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {description} Copyright (C) {year} {fullname} This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. README.adoc000066400000000000000000000244721325134676200126650ustar00rootroot00000000000000= vagrant-sshfs :toc: :toc-placement!: This Vagrant plugin adds synced folder support for mounting folders from the Vagrant host into the Vagrant guest via https://github.com/libfuse/sshfs[SSHFS]. In the default mode it does this by executing the `SSHFS` client software within the guest, which creates an SSH connection from the Vagrant guest back to the Vagrant host. ''' toc::[] ''' [[considerations]] == Considerations The benefits of this approach: * Works on any host platform and hypervisor type ** Windows, Linux, Mac OS X ** Virtualbox, Libvirt, Hyper-V, VMWare * Seamlessly works on remote Vagrant solutions ** Works with vagrant aws/openstack/etc.. plugins The drawbacks with this approach: * Performance is worse than an implementation like NFS * There must be `sftp-server` software on the Vagrant host `sftp-server` is usually provided by SSH server software so it already exists on Linux/Mac. On windows you only need to install https://cygwin.com/cgi-bin2/package-grep.cgi?grep=openssh&arch=x86_64[openssh] via https://cygwin.com/[cygwin] and you will get `sftp-server`. [[history]] == History The inspiration for this plugin came from https://github.com/fabiokr[Fabio Kreusch] and his https://github.com/fabiokr/vagrant-sshfs[code] for the original vagrant-sshfs Vagrant plugin. The goal of this plugin (as opposed to the old implementation) is to implement SSHFS as a synced folder plugin just like the other synced folder plugins (NFS/RSYNC/SMB/VirtualBox). This plugin was developed mainly by copying the code from the NFS synced folder plugin from the Vagrant core code and molding it to fit SSHFS. [[modes-of-operation]] == Modes of Operation [[sharing-vagrant-host-directory-to-vagrant-guest---94-of-users]] === Sharing Vagrant Host Directory to Vagrant Guest - 94% of users This plugin uses SSHFS slave mounts (see https://github.com/dustymabe/vagrant-sshfs/issues/11[link]) to mount a directory from the Vagrant Host into the Vagrant Guest. It uses the `sftp-server` software that exists on the host and `sshfs` running in _slave mode_ within the guest to create a connection using the existing authentication over SSH that vagrant sets up for you. [[sharing-arbitrary-host-directory-to-vagrant-guest---1-of-users]] === Sharing Arbitrary Host Directory to Vagrant Guest - 1% of users This plugin allows you to share a folder from an arbitrary host to the Vagrant Guest. This would allow you to do a folder mount to some other host that may have files that you need. To do this the plugin will run an SSHFS command from the Guest and connect to the arbitrary host that must have an SSH daemon running. You must provide the `ssh_host` option in the Vagrantfile to get this to work. You can use ssh key forwarding or username/password for authentication for this. See link:#options-specific-to-arbitrary-host-mounting[Options] and link:#appendix-a-using-keys-and-forwarding-ssh-agent[Appendix A] for more information. [[sharing-vagrant-guest-directory-to-vagrant-host---5-of-users]] === Sharing Vagrant Guest Directory to Vagrant Host - 5% of users _NOTE:_ This option is dangerous as data will be destroyed upon `vagrant destroy` This plugin allows you to share a folder from a Vagrant guest into the host. If you have workloads where there are a lot of disk intensive operations (such as compilation) it may be ideal to have the files live in the guest where the disk intensive operations would occur. For discussion see https://github.com/dustymabe/vagrant-sshfs/issues/7[Issue #7]. See link:#options-specific-to-reverse-mounting-guest-host-mount[Options] for more information on how to enable this type of mount. [[getting-started]] == Getting Started In order to use this synced folder implementation perform the following steps: [[install-plugin]] === Install Plugin In order to install the plugin simply run the following command: .... # vagrant plugin install vagrant-sshfs .... [[add-sshfs-synced-folder-in-vagrantfile]] === Add SSHFS Synced Folder in Vagrantfile Edit your Vagrantfile to specify a folder to mount from the host into the guest: .... config.vm.synced_folder "/path/on/host", "/path/on/guest", type: "sshfs" .... Now you can simply `vagrant up` and your folder should be mounted in the guest. For more options that you can add see the link:#options[Options] section. [[executing-the-vagrant-sshfs-command]] == Executing the `vagrant sshfs` Command The Vagrant SSHFS plugin also supports execution of the `vagrant sshfs` command from the command line. Executing this command with the `--mount` option will iterate through the Vagrant file and attempt to mount (via SSHFS) any folders that aren't already mounted in the Vagrant guest. Executing with the `--unmount` option will unmount any mounted folders. .... vagrant sshfs [--mount|--unmount] [vm-name] .... [[options]] == Options The SSHFS synced folder plugin supports a few options that can be provided in the `Vagrantfile`. The following sections describe the options in more detail. [[generic-options]] === Generic Options The SSHFS synced folder plugin supports a few options that can be provided in the `Vagrantfile`. They are described below: * `disabled` ** If set to 'true', ignore this folder and don't mount it. * `ssh_opts_append` ** Add some options for the ssh connection that will be established. ** See the ssh man page for more details on possible options. * `sshfs_opts_append` ** Add some options for the sshfs fuse mount that will made ** See the sshfs man page for more details on possible options. An example snippet from a `Vagrantfile`: .... config.vm.synced_folder "/path/on/host", "/path/on/guest", ssh_opts_append: "-o Compression=yes -o CompressionLevel=5", sshfs_opts_append: "-o auto_cache -o cache_timeout=115200", disabled: false, type: "sshfs" .... [[options-specific-to-arbitrary-host-mounting]] === Options Specific to Arbitrary Host Mounting The following options are only to be used when link:#sharing-arbitrary-host-directory-to-vagrant-guest---1-of-users[sharing an arbitrary host directory] with the guest. They will be ignored otherwise: * `ssh_host` ** The host to connect to via SSH. If not provided this will be detected as the Vagrant host that is running the Vagrant guest. * `ssh_port` ** The port to use when connecting. Defaults to port 22. * `ssh_username` ** The username to use when connecting. If not provided it is detected as the current user who is interacting with Vagrant. * `ssh_password` ** The password to use when connecting. If not provided and the user is not using SSH keys, then the user will be prompted for the password. Please use SSH keys and don't use this option! * `prompt_for_password` ** The user can force Vagrant to interactively prompt the user for a password by setting this to 'true'. Alternatively the user can deny Vagrant from ever prompting for the password by setting this to 'false'. An example snippet from a `Vagrantfile`: .... config.vm.synced_folder "/path/on/host", "/path/on/guest", ssh_host: "somehost.com", ssh_username: "fedora", ssh_opts_append: "-o Compression=yes -o CompressionLevel=5", sshfs_opts_append: "-o auto_cache -o cache_timeout=115200", disabled: false, type: "sshfs" .... [[options-specific-to-reverse-mounting-guest-host-mount]] === Options Specific to Reverse Mounting (Guest->Host Mount) If your host has the `sshfs` software installed then the following options enable mounting a folder from a Vagrant Guest into the Vagrant Host: * `reverse` ** This can be set to 'true' to enable reverse mounting a guest folder into the Vagrant host. An example snippet from a `Vagrantfile` where we want to mount `/data` on the guest into `/guest/data` on the host: .... config.vm.synced_folder "/guest/data", "/data", type: 'sshfs', reverse: true .... [[faq]] == FAQ Here are some answers to some frequently asked questions: [[why-do-new-files-take-time-to-appear-inside-the-guest]] === Why do new files take time to appear inside the guest? Sometimes it can take time for files to appear on the other end of the sshfs mount. An example would be I create a file on my host system and then it doesn't show up inside the guest mount for 10 to 20 seconds. This is because of caching that SSHFS does to improve performance. Performance vs accuracy is always going to be a trade-off. If you'd like to disable caching completely you can disable caching completely by appending the `cache=no` SSHFS option to the synced folder definition in the Vagrantfile like so: .... config.vm.synced_folder "/path/on/host", "/path/on/guest", type: "sshfs", sshfs_opts_append: "-o cache=no" .... All caching options that are available to sshfs can be added/modified in this same manner. [[appendix-a-using-keys-and-forwarding-ssh-agent]] == Appendix A: Using Keys and Forwarding SSH Agent When link:#sharing-arbitrary-host-directory-to-vagrant-guest---1-of-users[sharing an arbitrary host directory] you may want a completely non-interactive experience. You can either hard code your password in the Vagrantfile or you can use SSH keys. A few guides for setting up ssh keys and key forwarding are on Github: * https://help.github.com/articles/generating-ssh-keys[Key Generation] * https://developer.github.com/guides/using-ssh-agent-forwarding/[Key Forwarding] The idea is that if `key1` is a key that is authorized to log in to the Vagrant host ,meaning there is an entry for `key1` in the `~/.ssh/authorized_keys` file, then you should be able to do the following to have a non-interactive experience with SSH keys and agent forwarding: Modify the Vagrantfile to forward your SSH agent: .... config.ssh.forward_agent = 'true' .... Now set up your agent and add your key to the agent: .... # eval $(ssh-agent) # ssh-add /path/to/key1 .... And finally bring up your Vagrant guest: .... # vagrant up .... [[appendix-b-development]] == Appendix B: Development For local development of this plugin here is an example of how to build, test and install this plugin on your local machine: .... # Install development dependencies $ gem install bundler && bundle install # List available Rake tasks $ bundle exec rake -T # Run Cucumber tests $ bundle exec rake featuretests # Build the gem (gets generated in the 'pkg' directory $ bundle exec rake build # Run Vagrant in the context of the plugin $ bundle exec vagrant # Install built gem into global Vagrant installation (run outside of git checkout!) $ vagrant plugin install .... RELEASE.txt000066400000000000000000000027461325134676200127210ustar00rootroot00000000000000 # Run viv tests cd /guests/sharedfolder/code/github.com/dustymabe/vagrant-sshfs/test/libvirt vagrant up follow README for running tests # Make sure to bump version in lib/vagrant-sshfs/version.rb and commit # DO NOT TAG YET # Craft a commit message for the tag. View the commit message for a previous tag by running: git tag -l -n100 v1.2.1 -> In vim add thanks to contributors - grab info with git log --no-merges --pretty=format:"%h - %an - %s" and git log --no-merges --pretty=format:"%h - %ae - %s" -> In vim add commit log - grab with git log --no-merges --pretty=format:"%h %s" -> In vim add release message - see previous tag for example # After crafting message then install git-evtag and sign git-evtag sign vX.X.X close and type in password for signing verify with git-evtag verify v1.2.0 verify with git verify-tag v1.2.0 git push git push --tags # Build with bundle exec rake build (inside viv VM) bundle exec rake build # Sign the output gpg --armor --detach-sign pkg/vagrant-sshfs-1.2.0.gem $ ls pkg/vagrant-sshfs-1.2.0.gem* pkg/vagrant-sshfs-1.2.0.gem pkg/vagrant-sshfs-1.2.0.gem.asc # make tar.gz and zip files git archive --format=tar.gz v1.3.0 > vagrant-sshfs-1.3.0.tar.gz gpg --armor --detach-sign vagrant-sshfs-1.3.0.tar.gz git archive --format=zip v1.3.0 > vagrant-sshfs-1.3.0.zip gpg --armor --detach-sign vagrant-sshfs-1.3.0.zip # Update release notes and upload files on github # push to rubygems with: gem push pkg/vagrant-sshfs-1.2.0.gem Rakefile000066400000000000000000000021301325134676200125300ustar00rootroot00000000000000# A Rakefile is like a Makefile for ruby # bundler/gem_tasks provides functionality like: # bundle exec rake build # bundle exec rake install # bundle exec rake release # require 'bundler/gem_tasks' # cucumber/rake/task provides us with an easy way to call cucumber require 'cucumber/rake/task' # rake/clean provides CLEAN/CLOBBER # http://www.virtuouscode.com/2014/04/28/rake-part-6-clean-and-clobber/ # CLEAN - list to let rake know what files can be cleaned up after build # CLOBBER - list to let rake know what files are final products of the build # require 'rake/clean' # Add the build dir to the list of items to clean up CLEAN.include('build') # We want to keep the build artifacts in the pkg dir CLOBBER.include('pkg') # Define a Rake::Task that will do initialization for us # See http://www.ultrasaurus.com/2009/12/creating-a-custom-rake-task/ task :init do FileUtils.mkdir_p 'build' end # Create new Cucumber::Rake::Task that will run Cucumber tests Cucumber::Rake::Task.new(:featuretests) # Define Rake::Task dependency - run :init before :featuretests task :featuretests => :init features/000077500000000000000000000000001325134676200127055ustar00rootroot00000000000000features/README.md000066400000000000000000000014051325134676200141640ustar00rootroot00000000000000 We are using Cucumber for automated testing. Read more at the following two links: - [link1](https://en.wikipedia.org/wiki/Cucumber_(software)) - [link2](http://www.methodsandtools.com/tools/cucumber.php) features/ This is the features directory. The features directory Contains feature files, which all have a .feature extension. May contain subdirectories to organize feature files. features/step_definitions This directory contains step definition files, which are Ruby code and have a .rb extension. features/support This directory contains supporting Ruby code. Files in support load before those in step_definitions, which makes it useful for such things as environment configuration (commonly done in a file called env.rb). features/sshfs_cwd_mount.feature000066400000000000000000000036511325134676200174740ustar00rootroot00000000000000# The language in this file is Gherkin. It is the language Cucumber # uses to define test cases and is designed to be non-technical and # human readable. All Gherkin files have a .feature extension # # See more here: https://en.wikipedia.org/wiki/Cucumber_(software) # # Additionally in the setup/env.rb file we set up Aruba. Aruba is used # to define most of the basic step definitions that we use as part of # the Gherkin syntax in this file. # # For more information on the step definitions provided see: # https://github.com/cucumber/aruba/tree/bb5d7ff71809b5461e29153ded793d2b9a3a0624/features/testing_frameworks/cucumber/steps # Feature: SSHFS mount of vagrant current working directory Scenario Outline: SSHFS mounting of vagrant cwd Given a file named "Vagrantfile" with: """ Vagrant.configure('2') do |config| config.vm.box = '' # Disable the default rsync config.vm.synced_folder '.', '/vagrant', disabled: true # If using libvirt and nested virt (vagrant in vagrant) then # we need to use a different network than 192.168.121.0 config.vm.provider :libvirt do |libvirt| libvirt.management_network_name = 'vagrant-libvirt-test' libvirt.management_network_address = '192.168.129.0/24' end # Mount up the current dir. It will have the Vagrantfile in there. config.vm.synced_folder './', '/testdir', type: 'sshfs' end """ When I successfully run `bundle exec vagrant up` Then stdout from "bundle exec vagrant up" should contain "Installing SSHFS client..." And stdout from "bundle exec vagrant up" should contain "Mounting SSHFS shared folder..." And stdout from "bundle exec vagrant up" should contain "Folder Successfully Mounted!" # The code for the following test is in ./step_definitions/sshfs_cwd_mount_steps.rb And vagrant current working directory should be mounted Examples: | box | | centos/7 | features/step_definitions/000077500000000000000000000000001325134676200162535ustar00rootroot00000000000000features/step_definitions/sshfs_cwd_mount_steps.rb000066400000000000000000000011011325134676200232140ustar00rootroot00000000000000# This is a cucumber step definition. Cucumber scenarios become automated # tests with the addition of what are called step definitions. A step # definition is a block of code associated with one or more steps by a # regular expression (or, in simple cases, a string). # # This is the step definition for the `And vagrant current working # directory should be mounted` step from sshfs_cwd_mount.feature # And(/^vagrant current working directory should be mounted$/) do run("vagrant ssh -c 'ls /testdir/Vagrantfile'") expect(last_command_started).to have_exit_status(0) end features/support/000077500000000000000000000000001325134676200144215ustar00rootroot00000000000000features/support/env.rb000066400000000000000000000024031325134676200155350ustar00rootroot00000000000000# This is a support file for the cucumber tests. This file sets up the # environment for the tests to run. At this point mainly that means # configuring Aruba. Aruba is used to define most of the basic step # definitions that we use as part of the Gherkin syntax in our .feature files. # # For more information on the step definitions provided see: # https://github.com/cucumber/aruba/tree/bb5d7ff71809b5461e29153ded793d2b9a3a0624/features/testing_frameworks/cucumber/steps require 'aruba/cucumber' require 'komenda' # use komenda for easily executing a command # Configure aruba. The options can be inferred from here: # https://github.com/cucumber/aruba/tree/bb5d7ff71809b5461e29153ded793d2b9a3a0624/features/configuration Aruba.configure do |config| # Wait up to 300 seconds for the test to run config.exit_timeout = 300 # Output stdout and stderr on test failure config.activate_announcer_on_command_failure = [:stdout, :stderr] # The directory where the tests are to be run config.working_directory = 'build/aruba' end # After running tests, clean up After do |_scenario| if File.exist?(File.join(aruba.config.working_directory, 'Vagrantfile')) Komenda.run('bundle exec vagrant destroy -f', cwd: aruba.config.working_directory, fail_on_fail: true) end end lib/000077500000000000000000000000001325134676200116355ustar00rootroot00000000000000lib/vagrant-sshfs.rb000066400000000000000000000015471325134676200147570ustar00rootroot00000000000000begin require "vagrant" rescue LoadError raise "The Vagrant sshfs plugin must be run within Vagrant" end # Only load the gem on Windows since it replaces some methods in Ruby's # Process class. Also load it here before Process.uid is called the first # time by Vagrant. The Process.create() function actually gets used in # lib/vagrant-sshfs/cap/guest/linux/sshfs_forward_mount.rb if Vagrant::Util::Platform.windows? require 'win32/process' end require "vagrant-sshfs/errors" require "vagrant-sshfs/version" require "vagrant-sshfs/plugin" module VagrantPlugins module SyncedFolderSSHFS # Returns the path to the source of this plugin def self.source_root @source_root ||= Pathname.new(File.expand_path('../../', __FILE__)) end I18n.load_path << File.expand_path('locales/synced_folder_sshfs.yml', source_root) I18n.reload! end end lib/vagrant-sshfs/000077500000000000000000000000001325134676200144235ustar00rootroot00000000000000lib/vagrant-sshfs/action_hostpath_fixup.rb000066400000000000000000000102351325134676200213530ustar00rootroot00000000000000require "log4r" require "vagrant/action/builtin/mixin_synced_folders" module VagrantPlugins module SyncedFolderSSHFS # Class that contains common function that are called by both # HostPathFix and HostPathUnfix classes. class HostPathFixCommon include Vagrant::Action::Builtin::MixinSyncedFolders def initialize() @logger = Log4r::Logger.new("vagrant::synced_folders::sshfs") end def fix(data) # If this is an arbitrary host mount we need to set the hostpath # to something that will pass the config checks that assume the # hostpath is coming from the vagrant host and not from an arbitrary # host. Save off the original hostpath and then set the hostpath to # "." to pass the checks. if data[:ssh_host] data[:hostpath_orig] = data[:hostpath] data[:hostpath] = "." end end def unfix(data) # If this is a reverse mounted folder or an arbitrary host mount # then we'll set "hostpath_exact" so they don't try to create a # folder on the host in Vagrant::Action::Builtin::SyncedFolders. if data[:ssh_host] data[:hostpath_exact] = true data[:hostpath] = data[:hostpath_orig] data.delete(:hostpath_orig) end end # Loop through synced folder entries and either fix or unfix # based on the fix arg def loop_and_fix_unfix(env, fix) opts = { cached: !!env[:synced_folders_cached], config: env[:synced_folders_config], } @logger.debug("SyncedFolders loading from cache: #{opts[:cached]}") folders = synced_folders(env[:machine], **opts) folders.each do |impl_name, fs| next if impl_name != :sshfs @logger.debug("Synced Folder Implementation: #{impl_name}") fs.each do |id, data| # replace data with a copy since we may delete/add new data to the config data = data.dup if fix @logger.debug("fixup host path before: - #{id}: #{data[:hostpath]} => #{data[:guestpath]}") fix(data) @logger.debug("fixup host path after: - #{id}: #{data[:hostpath]} => #{data[:guestpath]}") else @logger.debug("unfixup host path before: - #{id}: #{data[:hostpath]} => #{data[:guestpath]}") unfix(data) @logger.debug("fixup host path after: - #{id}: #{data[:hostpath]} => #{data[:guestpath]}") end # Replace the entry in the config with the updated one env[:machine].config.vm.synced_folders.delete(id) env[:machine].config.vm.synced_folder( data[:hostpath], data[:guestpath], data) end end end end # Class that will massage the data for synced folders that are # arbitrary host mounts (contain ssh_host in the options) to make # it so that "host path checking" isn't performed on the vagrant # host machine class HostPathFix def initialize(app, env) @app = app @logger = Log4r::Logger.new("vagrant::synced_folders::sshfs") end def call(env) classname = "VagrantPlugins::SyncedFolderSSHFS::HostPathFix" @logger.debug("Executing hook within #{classname}") # This part is for the IN action call HostPathFixCommon.new().loop_and_fix_unfix(env, fix=true) # Now continue until the OUT call @app.call(env) end end # Class that will undo the data manipulation that was done in # HostPathFix and also set hostpath_exact=true if necessary class HostPathUnfix def initialize(app, env) @app = app @logger = Log4r::Logger.new("vagrant::synced_folders::sshfs") end def call(env) classname = "VagrantPlugins::SyncedFolderSSHFS::HostPathUnfix" @logger.debug("Executing hook within #{classname}") # This part is for the IN action call HostPathFixCommon.new().loop_and_fix_unfix(env, fix=false) # Now continue until the OUT call @app.call(env) end end end end lib/vagrant-sshfs/cap/000077500000000000000000000000001325134676200151665ustar00rootroot00000000000000lib/vagrant-sshfs/cap/guest/000077500000000000000000000000001325134676200163155ustar00rootroot00000000000000lib/vagrant-sshfs/cap/guest/arch/000077500000000000000000000000001325134676200172325ustar00rootroot00000000000000lib/vagrant-sshfs/cap/guest/arch/sshfs_client.rb000066400000000000000000000022311325134676200222410ustar00rootroot00000000000000module VagrantPlugins module GuestArch module Cap class SSHFSClient def self.sshfs_install(machine) # Attempt to install sshfs but note that it may likely fail # because the package file list is out of date (see [1]). A # logical answer to this problem would be to update the # package list and then install the package, but since arch # doesn't support partial upgrades [2] that would require # updating all packages in the system first. Not ideal # # [1] https://wiki.archlinux.org/index.php/pacman#Packages_cannot_be_retrieved_on_installation # [2] https://wiki.archlinux.org/index.php/System_maintenance#Partial_upgrades_are_unsupported error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSInstallFailed error_key = :install_failed_arch cmd = "pacman --noconfirm -S sshfs" machine.communicate.sudo( cmd, error_class: error_class, error_key: error_key) end def self.sshfs_installed(machine) machine.communicate.test("pacman -Q sshfs") end end end end end lib/vagrant-sshfs/cap/guest/debian/000077500000000000000000000000001325134676200175375ustar00rootroot00000000000000lib/vagrant-sshfs/cap/guest/debian/sshfs_client.rb000066400000000000000000000006011325134676200225450ustar00rootroot00000000000000module VagrantPlugins module GuestDebian module Cap class SSHFSClient def self.sshfs_install(machine) machine.communicate.sudo("apt-get update") machine.communicate.sudo("apt-get install -y sshfs") end def self.sshfs_installed(machine) machine.communicate.test("dpkg -l sshfs") end end end end end lib/vagrant-sshfs/cap/guest/fedora/000077500000000000000000000000001325134676200175555ustar00rootroot00000000000000lib/vagrant-sshfs/cap/guest/fedora/sshfs_client.rb000066400000000000000000000005211325134676200225640ustar00rootroot00000000000000module VagrantPlugins module GuestFedora module Cap class SSHFSClient def self.sshfs_install(machine) machine.communicate.sudo("dnf -y install fuse-sshfs") end def self.sshfs_installed(machine) machine.communicate.test("rpm -q fuse-sshfs") end end end end end lib/vagrant-sshfs/cap/guest/freebsd/000077500000000000000000000000001325134676200177275ustar00rootroot00000000000000lib/vagrant-sshfs/cap/guest/freebsd/sshfs_client.rb000066400000000000000000000011741325134676200227430ustar00rootroot00000000000000module VagrantPlugins module GuestFreeBSD module Cap class SSHFSClient def self.sshfs_install(machine) machine.communicate.sudo("pkg install -y fusefs-sshfs") machine.communicate.sudo("kldload fuse") end def self.sshfs_installed(machine) installed = machine.communicate.test("pkg info fusefs-sshfs") if installed # fuse may not get loaded at boot, so check if it's loaded otherwise force load it machine.communicate.sudo("kldstat -m fuse || kldload fuse") end installed end end end end end lib/vagrant-sshfs/cap/guest/freebsd/sshfs_forward_mount.rb000066400000000000000000000004231325134676200243470ustar00rootroot00000000000000require_relative "../linux/sshfs_forward_mount" module VagrantPlugins module GuestFreeBSD module Cap class MountSSHFS < VagrantPlugins::GuestLinux::Cap::MountSSHFS def self.list_mounts_command "mount -p" end end end end end lib/vagrant-sshfs/cap/guest/linux/000077500000000000000000000000001325134676200174545ustar00rootroot00000000000000lib/vagrant-sshfs/cap/guest/linux/sshfs_client.rb000066400000000000000000000003401325134676200224620ustar00rootroot00000000000000module VagrantPlugins module GuestLinux module Cap class SSHFSClient def self.sshfs_installed(machine) machine.communicate.test("test -x /usr/bin/sshfs") end end end end end lib/vagrant-sshfs/cap/guest/linux/sshfs_forward_mount.rb000066400000000000000000000377131325134676200241100ustar00rootroot00000000000000require "log4r" require "vagrant/util/retryable" require "vagrant/util/platform" require "tempfile" # This is already done for us in lib/vagrant-sshfs.rb. We needed to # do it there before Process.uid is called the first time by Vagrant # This provides a new Process.create() that works on Windows. if Vagrant::Util::Platform.windows? require 'win32/process' end module VagrantPlugins module GuestLinux module Cap class MountSSHFS extend Vagrant::Util::Retryable @@logger = Log4r::Logger.new("vagrant::synced_folders::sshfs_mount") def self.list_mounts_command "cat /proc/mounts" end def self.sshfs_forward_is_folder_mounted(machine, opts) mounted = false guest_path = opts[:guestpath] # If the path doesn't exist at all in the machine then we # can safely say it is not mounted exists = machine.communicate.test("test -e #{guest_path}", sudo: true) return false unless exists # find the absolute path so that we can properly check if it is mounted # https://github.com/dustymabe/vagrant-sshfs/issues/44 absolute_guest_path = machine.guest.capability( :sshfs_get_absolute_path, guest_path) # consult /proc/mounts to see if it is mounted or not machine.communicate.execute(self.list_mounts_command) do |type, data| if type == :stdout data.each_line do |line| if line.split()[1] == absolute_guest_path mounted = true break end end end end return mounted end def self.sshfs_forward_mount_folder(machine, opts) # opts contains something like: # { :type=>:sshfs, # :guestpath=>"/sharedfolder", # :hostpath=>"/guests/sharedfolder", # :disabled=>false # :ssh_host=>"192.168.1.1" # :ssh_port=>"22" # :ssh_username=>"username" # :ssh_password=>"password" # } # expand the guest path so we can handle things like "~/vagrant" expanded_guest_path = machine.guest.capability( :shell_expand_guest_path, opts[:guestpath]) # Create the mountpoint inside the guest machine.communicate.tap do |comm| comm.sudo("mkdir -p #{expanded_guest_path}") comm.sudo("chmod 777 #{expanded_guest_path}") end # Mount path information: if arbitrary host mounting then # take as is. If not, then expand the hostpath to the real # path. if opts[:ssh_host] hostpath = opts[:hostpath].dup else hostpath = File.expand_path(opts[:hostpath], machine.env.root_path) hostpath = Vagrant::Util::Platform.fs_real_path(hostpath).to_s end # Add in some sshfs/fuse options that are common to both mount methods opts[:sshfs_opts] = ' -o allow_other ' # allow non-root users to access opts[:sshfs_opts]+= ' -o noauto_cache '# disable caching based on mtime # Add in some ssh options that are common to both mount methods opts[:ssh_opts] = ' -o StrictHostKeyChecking=no '# prevent yes/no question opts[:ssh_opts]+= ' -o ServerAliveInterval=30 ' # send keepalives # Do a normal mount only if the user provided host information if opts.has_key?(:ssh_host) and opts[:ssh_host] self.sshfs_normal_mount(machine, opts, hostpath, expanded_guest_path) else self.sshfs_slave_mount(machine, opts, hostpath, expanded_guest_path) end end def self.sshfs_forward_unmount_folder(machine, opts) # opts contains something like: # { :type=>:sshfs, # :guestpath=>"/sharedfolder", # :hostpath=>"/guests/sharedfolder", # :disabled=>false # :ssh_host=>"192.168.1.1" # :ssh_port=>"22" # :ssh_username=>"username" # :ssh_password=>"password" # } # expand the guest path so we can handle things like "~/vagrant" expanded_guest_path = machine.guest.capability( :shell_expand_guest_path, opts[:guestpath]) # Log some information machine.ui.info(I18n.t("vagrant.sshfs.actions.unmounting_folder", guestpath: expanded_guest_path)) # Build up the command and connect error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSUnmountFailed cmd = "umount #{expanded_guest_path}" machine.communicate.sudo( cmd, error_class: error_class, error_key: :unmount_failed) end protected def self.windows_uninherit_handles(machine) # For win32-process Process.create, if we pass any file handles to the # underlying process for stdin/stdout/stderr then all file handles are # inherited by default. We'll explicitly go through and set all Handles # to not be inheritable by default. See following links for more info # # https://github.com/djberg96/win32-process/blob/6b380f450aebb69d44bb7accd958ecb6b9e1d246/lib/win32/process.rb#L445-L447 # bInheritHandles from https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx # # In 6f285cd we made it so that we would uninherit all filehandles by # default on windows. Some users have reported that this operation # is erroring because `The parameter is incorrect.`. See #52 # We will make the uninheriting operation best effort. The rationale # is that if a file handle was not able to be set to uninheritable # then it probably wasn't one that would get inherited in the first place. # # For each open IO object ObjectSpace.each_object(IO) do |io| if !io.closed? fileno = io.fileno @@logger.debug("Setting file handle #{fileno} to not be inherited") begin self.windows_uninherit_handle(fileno) rescue SystemCallError => error msg = "Warning: couldn't set file handle #{fileno} to not be inherited\n" msg+= "Message: " + error.message + "\n" msg+= "Continuing in best effort...." machine.ui.warn(msg) @@logger.warn(msg) end end end end def self.windows_uninherit_handle(fileno) # Right now we'll be doing this using private methods from the win32-process # module by calling For each open IO object. Much of this code was copied from # that module. We access the private methods by using the object.send(:method, args) # technique. In the future we want to get a patch upstream so we don't need to # access private methods. Upstream request is here: # https://github.com/djberg96/win32-process/pulls # Get the windows IO handle and make sure we were successful getting it handle = Process.send(:get_osfhandle, fileno) if handle == Process::Constants::INVALID_HANDLE_VALUE ptr = FFI::MemoryPointer.new(:int) if Process.send(:windows_version) >= 6 && Process.get_errno(ptr) == 0 errno = ptr.read_int else errno = FFI.errno end raise SystemCallError.new("get_osfhandle", errno) end # Now clear the HANDLE_FLAG_INHERIT from the HANDLE so that the handle # won't get shared by default. See: # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724935(v=vs.85).aspx # bool = Process.send(:SetHandleInformation, handle, Process::Constants::HANDLE_FLAG_INHERIT, 0) raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool end # Perform a mount by running an sftp-server on the vagrant host # and piping stdin/stdout to sshfs running inside the guest def self.sshfs_slave_mount(machine, opts, hostpath, expanded_guest_path) sftp_server_path = opts[:sftp_server_exe_path] ssh_path = opts[:ssh_exe_path] # SSH connection options ssh_opts = opts[:ssh_opts] ssh_opts_append = opts[:ssh_opts_append].to_s # provided by user # SSHFS executable options sshfs_opts = opts[:sshfs_opts] sshfs_opts_append = opts[:sshfs_opts_append].to_s # provided by user # The sftp-server command sftp_server_cmd = sftp_server_path # The remote sshfs command that will run (in slave mode) sshfs_opts+= ' -o slave ' sshfs_cmd = "sudo -E sshfs :#{hostpath} #{expanded_guest_path}" sshfs_cmd+= sshfs_opts + ' ' + sshfs_opts_append + ' ' # The ssh command to connect to guest and then launch sshfs ssh_opts = opts[:ssh_opts] ssh_opts+= ' -o User=' + machine.ssh_info[:username] ssh_opts+= ' -o Port=' + machine.ssh_info[:port].to_s ssh_opts+= ' -o UserKnownHostsFile=/dev/null ' ssh_opts+= ' -F /dev/null ' # Don't pick up options from user's config if machine.ssh_info.key?(:private_key_path) and machine.ssh_info[:private_key_path] and machine.ssh_info[:private_key_path][0] # Add IdentityFile since there is one # Note the backslash escapes for IdentityFile - handles spaces in key path ssh_opts+= ' -o "IdentityFile=\"' + machine.ssh_info[:private_key_path][0] + '\""' end # Use an SSH ProxyCommand when corresponding Vagrant setting is defined if machine.ssh_info[:proxy_command] ssh_opts+= " -o ProxyCommand=\"" + machine.ssh_info[:proxy_command] + "\"" end ssh_cmd = ssh_path + ssh_opts + ' ' + ssh_opts_append + ' ' + machine.ssh_info[:host] ssh_cmd+= ' "' + sshfs_cmd + '"' # Log some information @@logger.debug("sftp-server cmd: #{sftp_server_cmd}") @@logger.debug("ssh cmd: #{ssh_cmd}") machine.ui.info(I18n.t("vagrant.sshfs.actions.slave_mounting_folder", hostpath: hostpath, guestpath: expanded_guest_path)) # Create two named pipes for communication between sftp-server and # sshfs running in slave mode r1, w1 = IO.pipe # reader/writer from pipe1 r2, w2 = IO.pipe # reader/writer from pipe2 # Log STDERR to predictable files so that we can inspect them # later in case things go wrong. We'll use the machines data # directory (i.e. .vagrant/machines/default/virtualbox/) for this f1path = machine.data_dir.join('vagrant_sshfs_sftp_server_stderr.txt') f2path = machine.data_dir.join('vagrant_sshfs_ssh_stderr.txt') f1 = File.new(f1path, 'w+') f2 = File.new(f2path, 'w+') # The way this works is by hooking up the stdin+stdout of the # sftp-server process to the stdin+stdout of the sshfs process # running inside the guest in slave mode. An illustration is below: # # stdout => w1 pipe1 r1 => stdin # />------------->==============>----------->\ # / \ # | | # sftp-server (on vm host) sshfs (inside guest) # | | # \ / # \<-------------<==============<----------- sftp_server_cmd, :creation_flags => Process::DETACHED_PROCESS, :process_inherit => false, :thread_inherit => true, :startup_info => {:stdin => w2, :stdout => r1, :stderr => f1}) Process.create(:command_line => ssh_cmd, :creation_flags => Process::DETACHED_PROCESS, :process_inherit => false, :thread_inherit => true, :startup_info => {:stdin => w1, :stdout => r2, :stderr => f2}) else p1 = spawn(sftp_server_cmd, :out => w2, :in => r1, :err => f1, :pgroup => true) p2 = spawn(ssh_cmd, :out => w1, :in => r2, :err => f2, :pgroup => true) # Detach from the processes so they will keep running Process.detach(p1) Process.detach(p2) end # Check that the mount made it mounted = false for i in 0..6 machine.ui.info("Checking Mount..") if self.sshfs_forward_is_folder_mounted(machine, opts) mounted = true break end sleep(2) end if !mounted f1.rewind # Seek to beginning of the file f2.rewind # Seek to beginning of the file error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSSlaveMountFailed raise error_class, sftp_stderr: f1.read, ssh_stderr: f2.read end machine.ui.info("Folder Successfully Mounted!") end # Do a normal sshfs mount in which we will ssh into the guest # and then execute the sshfs command to connect the the opts[:ssh_host] # and mount a folder from opts[:ssh_host] into the guest. def self.sshfs_normal_mount(machine, opts, hostpath, expanded_guest_path) # SSH connection options ssh_opts = opts[:ssh_opts] ssh_opts_append = opts[:ssh_opts_append].to_s # provided by user # SSHFS executable options sshfs_opts = opts[:sshfs_opts] sshfs_opts_append = opts[:sshfs_opts_append].to_s # provided by user # Host/Port and Auth Information username = opts[:ssh_username] password = opts[:ssh_password] host = opts[:ssh_host] port = opts[:ssh_port] # Add echo of password if password is being used echopipe = "" if password echopipe = "echo '#{password}' | " sshfs_opts+= '-o password_stdin ' end # Log some information machine.ui.info(I18n.t("vagrant.sshfs.actions.normal_mounting_folder", user: username, host: host, hostpath: hostpath, guestpath: expanded_guest_path)) # Build up the command and connect error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSNormalMountFailed cmd = echopipe cmd+= "sshfs -p #{port} " cmd+= ssh_opts + ' ' + ssh_opts_append + ' ' cmd+= sshfs_opts + ' ' + sshfs_opts_append + ' ' cmd+= "#{username}@#{host}:'#{hostpath}' #{expanded_guest_path}" retryable(on: error_class, tries: 3, sleep: 3) do machine.communicate.sudo( cmd, error_class: error_class, error_key: :normal_mount_failed) end end end end end end lib/vagrant-sshfs/cap/guest/linux/sshfs_get_absolute_path.rb000066400000000000000000000013411325134676200246770ustar00rootroot00000000000000module VagrantPlugins module GuestLinux module Cap class SSHFSGetAbsolutePath def self.sshfs_get_absolute_path(machine, path) abs_path = "" machine.communicate.execute("readlink -f #{path}", sudo: true) do |type, data| if type == :stdout abs_path = data end end if ! abs_path # If no real absolute path was detected then error out error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSGetAbsolutePathFailed raise error_class, path: path end # Chomp the string so that any trailing newlines are killed return abs_path.chomp end end end end end lib/vagrant-sshfs/cap/guest/redhat/000077500000000000000000000000001325134676200175645ustar00rootroot00000000000000lib/vagrant-sshfs/cap/guest/redhat/sshfs_client.rb000066400000000000000000000020531325134676200225750ustar00rootroot00000000000000module VagrantPlugins module GuestRedHat module Cap class SSHFSClient def self.sshfs_install(machine) # Install epel rpm if not installed if !epel_installed(machine) epel_install(machine) end # Install sshfs (comes from epel repos) machine.communicate.sudo("yum -y install fuse-sshfs") end def self.sshfs_installed(machine) machine.communicate.test("rpm -q fuse-sshfs") end protected def self.epel_installed(machine) machine.communicate.test("rpm -q epel-release") end def self.epel_install(machine) case machine.guest.capability("flavor") when :rhel_7 machine.communicate.sudo("rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm") when :rhel # rhel6 machine.communicate.sudo("rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm") end end end end end end lib/vagrant-sshfs/cap/guest/suse/000077500000000000000000000000001325134676200172745ustar00rootroot00000000000000lib/vagrant-sshfs/cap/guest/suse/sshfs_client.rb000066400000000000000000000005101325134676200223010ustar00rootroot00000000000000module VagrantPlugins module GuestSUSE module Cap class SSHFSClient def self.sshfs_install(machine) machine.communicate.sudo("zypper -n install sshfs") end def self.sshfs_installed(machine) machine.communicate.test("rpm -q sshfs") end end end end end lib/vagrant-sshfs/cap/host/000077500000000000000000000000001325134676200161435ustar00rootroot00000000000000lib/vagrant-sshfs/cap/host/darwin/000077500000000000000000000000001325134676200174275ustar00rootroot00000000000000lib/vagrant-sshfs/cap/host/darwin/sshfs_reverse_mount.rb000066400000000000000000000161311325134676200240610ustar00rootroot00000000000000require "log4r" require "vagrant/util/retryable" require "tempfile" module VagrantPlugins module HostDarwin module Cap class MountSSHFS extend Vagrant::Util::Retryable @@logger = Log4r::Logger.new("vagrant::synced_folders::sshfs_reverse_mount") def self.sshfs_reverse_is_folder_mounted(env, opts) mounted = false hostpath = opts[:hostpath].dup hostpath.gsub!("'", "'\\\\''") hostpath = hostpath.chomp('/') # remove trailing / if exists hostpath = File.expand_path(hostpath) # get the absolute path of the file mount_cmd = Vagrant::Util::Which.which('mount') result = Vagrant::Util::Subprocess.execute(mount_cmd) result.stdout.each_line do |line| if line.split()[2] == hostpath mounted = true break end end return mounted end def self.sshfs_reverse_mount_folder(env, machine, opts) # opts contains something like: # { :type=>:sshfs, # :guestpath=>"/sharedfolder", # :hostpath=>"/guests/sharedfolder", # :disabled=>false # :ssh_host=>"192.168.1.1" # :ssh_port=>"22" # :ssh_username=>"username" # :ssh_password=>"password" # } self.sshfs_mount(machine, opts) end def self.sshfs_reverse_unmount_folder(env, machine, opts) self.sshfs_unmount(machine, opts) end protected # Perform a mount by running an sftp-server on the vagrant host # and piping stdin/stdout to sshfs running inside the guest def self.sshfs_mount(machine, opts) sshfs_path = Vagrant::Util::Which.which('sshfs') # expand the guest path so we can handle things like "~/vagrant" expanded_guest_path = machine.guest.capability( :shell_expand_guest_path, opts[:guestpath]) # Mount path information hostpath = opts[:hostpath].dup hostpath.gsub!("'", "'\\\\''") # Add in some sshfs/fuse options that are common to both mount methods opts[:sshfs_opts] = ' -o noauto_cache '# disable caching based on mtime # Add in some ssh options that are common to both mount methods opts[:ssh_opts] = ' -o StrictHostKeyChecking=no '# prevent yes/no question opts[:ssh_opts]+= ' -o ServerAliveInterval=30 ' # send keepalives # SSH connection options # Note the backslash escapes for IdentityFile - handles spaces in key path ssh_opts = opts[:ssh_opts] ssh_opts+= ' -o Port=' + machine.ssh_info[:port].to_s ssh_opts+= ' -o "IdentityFile=\"' + machine.ssh_info[:private_key_path][0] + '\""' ssh_opts+= ' -o UserKnownHostsFile=/dev/null ' ssh_opts+= ' -F /dev/null ' # Don't pick up options from user's config if machine.ssh_info.key?(:private_key_path) and machine.ssh_info[:private_key_path] and machine.ssh_info[:private_key_path][0] # Add IdentityFile since there is one # Note the backslash escapes for IdentityFile - handles spaces in key path ssh_opts+= ' -o "IdentityFile=\"' + machine.ssh_info[:private_key_path][0] + '\""' end ssh_opts_append = opts[:ssh_opts_append].to_s # provided by user # SSHFS executable options sshfs_opts = opts[:sshfs_opts] sshfs_opts_append = opts[:sshfs_opts_append].to_s # provided by user username = machine.ssh_info[:username] host = machine.ssh_info[:host] # The sshfs command to mount the guest directory on the host sshfs_cmd = "#{sshfs_path} #{ssh_opts} #{ssh_opts_append} " sshfs_cmd+= "#{sshfs_opts} #{sshfs_opts_append} " sshfs_cmd+= "#{username}@#{host}:#{expanded_guest_path} #{hostpath}" # Log some information @@logger.debug("sshfs cmd: #{sshfs_cmd}") machine.ui.info(I18n.t("vagrant.sshfs.actions.reverse_mounting_folder", hostpath: hostpath, guestpath: expanded_guest_path)) # Log STDERR to predictable files so that we can inspect them # later in case things go wrong. We'll use the machines data # directory (i.e. .vagrant/machines/default/virtualbox/) for this f1path = machine.data_dir.join('vagrant_sshfs_sshfs_stderr.txt') f1 = File.new(f1path, 'w+') # Launch sshfs command to mount guest dir into the host if Vagrant::Util::Platform.windows? # Need to handle Windows differently. Kernel.spawn fails to work, # if the shell creating the process is closed. # See https://github.com/dustymabe/vagrant-sshfs/issues/31 Process.create(:command_line => ssh_cmd, :creation_flags => Process::DETACHED_PROCESS, :process_inherit => false, :thread_inherit => true, :startup_info => {:stdin => w2, :stdout => r1, :stderr => f1}) else p1 = spawn(sshfs_cmd, :out => f1, :err => f1, :pgroup => true) Process.detach(p1) # Detach so process will keep running end # Check that the mount made it mounted = false for i in 0..6 machine.ui.info("Checking Mount..") if self.sshfs_reverse_is_folder_mounted(machine, opts) mounted = true break end sleep(2) end if !mounted f1.rewind # Seek to beginning of the file error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSReverseMountFailed raise error_class, sshfs_output: f1.read end machine.ui.info("Folder Successfully Mounted!") end def self.sshfs_unmount(machine, opts) # opts contains something like: # { :type=>:sshfs, # :guestpath=>"/sharedfolder", # :hostpath=>"/guests/sharedfolder", # :disabled=>false # :ssh_host=>"192.168.1.1" # :ssh_port=>"22" # :ssh_username=>"username" # :ssh_password=>"password" # } # Mount path information hostpath = opts[:hostpath].dup hostpath.gsub!("'", "'\\\\''") # Log some information machine.ui.info(I18n.t("vagrant.sshfs.actions.reverse_unmounting_folder", hostpath: hostpath)) # Build up the command and connect # on linux it is fusermount -u, on mac it is just umount error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSUnmountFailed umount_cmd = Vagrant::Util::Which.which('umount') cmd = "#{umount_cmd} #{hostpath}" result = Vagrant::Util::Subprocess.execute(*cmd.split()) if result.exit_code != 0 raise error_class, command: cmd, stdout: result.stdout, stderr: result.stderr end end end end end end lib/vagrant-sshfs/cap/host/linux/000077500000000000000000000000001325134676200173025ustar00rootroot00000000000000lib/vagrant-sshfs/cap/host/linux/sshfs_reverse_mount.rb000066400000000000000000000154431325134676200237410ustar00rootroot00000000000000require "log4r" require "vagrant/util/retryable" require "tempfile" module VagrantPlugins module HostLinux module Cap class MountSSHFS extend Vagrant::Util::Retryable @@logger = Log4r::Logger.new("vagrant::synced_folders::sshfs_reverse_mount") def self.sshfs_reverse_is_folder_mounted(env, opts) mounted = false hostpath = opts[:hostpath].dup hostpath.gsub!("'", "'\\\\''") hostpath = hostpath.chomp('/') # remove trailing / if exists hostpath = File.expand_path(hostpath) # get the absolute path of the file mounts = File.open('/proc/mounts', 'r') mounts.each_line do |line| if line.split()[1] == hostpath mounted = true break end end return mounted end def self.sshfs_reverse_mount_folder(env, machine, opts) # opts contains something like: # { :type=>:sshfs, # :guestpath=>"/sharedfolder", # :hostpath=>"/guests/sharedfolder", # :disabled=>false # :ssh_host=>"192.168.1.1" # :ssh_port=>"22" # :ssh_username=>"username" # :ssh_password=>"password" # } self.sshfs_mount(machine, opts) end def self.sshfs_reverse_unmount_folder(env, machine, opts) self.sshfs_unmount(machine, opts) end protected # Perform a mount by running an sftp-server on the vagrant host # and piping stdin/stdout to sshfs running inside the guest def self.sshfs_mount(machine, opts) sshfs_path = Vagrant::Util::Which.which('sshfs') # expand the guest path so we can handle things like "~/vagrant" expanded_guest_path = machine.guest.capability( :shell_expand_guest_path, opts[:guestpath]) # Mount path information hostpath = opts[:hostpath].dup hostpath.gsub!("'", "'\\\\''") # Add in some sshfs/fuse options that are common to both mount methods opts[:sshfs_opts] = ' -o noauto_cache '# disable caching based on mtime # Add in some ssh options that are common to both mount methods opts[:ssh_opts] = ' -o StrictHostKeyChecking=no '# prevent yes/no question opts[:ssh_opts]+= ' -o ServerAliveInterval=30 ' # send keepalives # SSH connection options ssh_opts = opts[:ssh_opts] ssh_opts+= ' -o Port=' + machine.ssh_info[:port].to_s ssh_opts+= ' -o UserKnownHostsFile=/dev/null ' ssh_opts+= ' -F /dev/null ' # Don't pick up options from user's config if machine.ssh_info.key?(:private_key_path) and machine.ssh_info[:private_key_path] and machine.ssh_info[:private_key_path][0] # Add IdentityFile since there is one # Note the backslash escapes for IdentityFile - handles spaces in key path ssh_opts+= ' -o "IdentityFile=\"' + machine.ssh_info[:private_key_path][0] + '\""' end ssh_opts_append = opts[:ssh_opts_append].to_s # provided by user # SSHFS executable options sshfs_opts = opts[:sshfs_opts] sshfs_opts_append = opts[:sshfs_opts_append].to_s # provided by user username = machine.ssh_info[:username] host = machine.ssh_info[:host] # The sshfs command to mount the guest directory on the host sshfs_cmd = "#{sshfs_path} #{ssh_opts} #{ssh_opts_append} " sshfs_cmd+= "#{sshfs_opts} #{sshfs_opts_append} " sshfs_cmd+= "#{username}@#{host}:#{expanded_guest_path} #{hostpath}" # Log some information @@logger.debug("sshfs cmd: #{sshfs_cmd}") machine.ui.info(I18n.t("vagrant.sshfs.actions.reverse_mounting_folder", hostpath: hostpath, guestpath: expanded_guest_path)) # Log STDERR to predictable files so that we can inspect them # later in case things go wrong. We'll use the machines data # directory (i.e. .vagrant/machines/default/virtualbox/) for this f1path = machine.data_dir.join('vagrant_sshfs_sshfs_stderr.txt') f1 = File.new(f1path, 'w+') # Launch sshfs command to mount guest dir into the host if Vagrant::Util::Platform.windows? # Need to handle Windows differently. Kernel.spawn fails to work, # if the shell creating the process is closed. # See https://github.com/dustymabe/vagrant-sshfs/issues/31 Process.create(:command_line => ssh_cmd, :creation_flags => Process::DETACHED_PROCESS, :process_inherit => false, :thread_inherit => true, :startup_info => {:stdin => w2, :stdout => r1, :stderr => f1}) else p1 = spawn(sshfs_cmd, :out => f1, :err => f1, :pgroup => true) Process.detach(p1) # Detach so process will keep running end # Check that the mount made it mounted = false for i in 0..6 machine.ui.info("Checking Mount..") if self.sshfs_reverse_is_folder_mounted(machine, opts) mounted = true break end sleep(2) end if !mounted f1.rewind # Seek to beginning of the file error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSReverseMountFailed raise error_class, sshfs_output: f1.read end machine.ui.info("Folder Successfully Mounted!") end def self.sshfs_unmount(machine, opts) # opts contains something like: # { :type=>:sshfs, # :guestpath=>"/sharedfolder", # :hostpath=>"/guests/sharedfolder", # :disabled=>false # :ssh_host=>"192.168.1.1" # :ssh_port=>"22" # :ssh_username=>"username" # :ssh_password=>"password" # } # Mount path information hostpath = opts[:hostpath].dup hostpath.gsub!("'", "'\\\\''") # Log some information machine.ui.info(I18n.t("vagrant.sshfs.actions.reverse_unmounting_folder", hostpath: hostpath)) # Build up the command and connect error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSUnmountFailed fusermount_cmd = Vagrant::Util::Which.which('fusermount') cmd = "#{fusermount_cmd} -u #{hostpath}" result = Vagrant::Util::Subprocess.execute(*cmd.split()) if result.exit_code != 0 raise error_class, command: cmd, stdout: result.stdout, stderr: result.stderr end end end end end end lib/vagrant-sshfs/command.rb000066400000000000000000000035351325134676200163740ustar00rootroot00000000000000module VagrantPlugins module SyncedFolderSSHFS module Command class SSHFS < Vagrant.plugin("2", :command) include Vagrant::Action::Builtin::MixinSyncedFolders def self.synopsis "mounts SSHFS shared folder mounts into the remote machine" end def execute options = {:unmount => false} # Default to mounting shares opts = OptionParser.new do |o| o.banner = "Usage: vagrant sshfs [--mount|--unmount] [vm-name]" o.separator "" o.separator "Mount or unmount sshfs synced folders into the vagrant box" o.separator "" o.on("--mount", "Mount folders - the default") do options[:unmount] = false end o.on("--unmount", "Unmount folders") do options[:unmount] = true end end # Parse the options and return if we don't have any target. argv = parse_options(opts) return if !argv # Go through each machine and perform the rsync error = false with_target_vms(argv) do |machine| # Is the machine up yet? if !machine.communicate.ready? machine.ui.error(I18n.t("vagrant.sshfs.errors.communicator_not_ready")) error = true next end # Determine the sshfs synced folders for this machine folders = synced_folders(machine, cached: false)[:sshfs] next if !folders || folders.empty? # Mount or Unmount depending on the user's request if options[:unmount] SyncedFolder.new.disable(machine, folders, {}) else SyncedFolder.new.enable(machine, folders, {}) end end return error ? 1 : 0 end end end end end lib/vagrant-sshfs/errors.rb000066400000000000000000000020041325134676200162600ustar00rootroot00000000000000module VagrantPlugins module SyncedFolderSSHFS module Errors # A convenient superclass for all our errors. class SSHFSError < Vagrant::Errors::VagrantError error_namespace("vagrant.sshfs.errors") end class SSHFSNormalMountFailed < SSHFSError error_key(:normal_mount_failed) end class SSHFSSlaveMountFailed < SSHFSError error_key(:slave_mount_failed) end class SSHFSReverseMountFailed < SSHFSError error_key(:reverse_mount_failed) end class SSHFSUnmountFailed < SSHFSError error_key(:unmount_failed) end class SSHFSInstallFailed < SSHFSError error_key(:install_failed) end class SSHFSNotInstalledInGuest < SSHFSError error_key(:sshfs_not_in_guest) end class SSHFSExeNotAvailable < SSHFSError error_key(:exe_not_in_host) end class SSHFSGetAbsolutePathFailed < SSHFSError error_key(:get_absolute_path_failed) end end end end lib/vagrant-sshfs/plugin.rb000066400000000000000000000136061325134676200162540ustar00rootroot00000000000000require "vagrant" module VagrantPlugins module SyncedFolderSSHFS # This plugin implements SSHFS synced folders. class Plugin < Vagrant.plugin("2") name "SSHFS synced folders" description <<-EOF The SSHFS synced folders plugin enables you to use SSHFS as a synced folder implementation. EOF synced_folder("sshfs", 5) do require_relative "synced_folder" SyncedFolder end command("sshfs", primary: true) do require_relative "command" Command::SSHFS end # The following two hooks allow us to workaround # the config validations that assume the hostpaths # are coming from our host machine. This is not the # case for arbitrary host mounts. action_hook("sshfs_hostpath_fixup") do |hook| require_relative "action_hostpath_fixup" hook.before( Vagrant::Action::Builtin::ConfigValidate, HostPathFix) end action_hook("sshfs_hostpath_unfix") do |hook| require_relative "action_hostpath_fixup" hook.after( Vagrant::Action::Builtin::ConfigValidate, HostPathUnfix) end host_capability("linux", "sshfs_reverse_mount_folder") do require_relative "cap/host/linux/sshfs_reverse_mount" VagrantPlugins::HostLinux::Cap::MountSSHFS end host_capability("linux", "sshfs_reverse_unmount_folder") do require_relative "cap/host/linux/sshfs_reverse_mount" VagrantPlugins::HostLinux::Cap::MountSSHFS end host_capability("linux", "sshfs_reverse_is_folder_mounted") do require_relative "cap/host/linux/sshfs_reverse_mount" VagrantPlugins::HostLinux::Cap::MountSSHFS end host_capability("darwin", "sshfs_reverse_mount_folder") do require_relative "cap/host/darwin/sshfs_reverse_mount" VagrantPlugins::HostDarwin::Cap::MountSSHFS end host_capability("darwin", "sshfs_reverse_unmount_folder") do require_relative "cap/host/darwin/sshfs_reverse_mount" VagrantPlugins::HostDarwin::Cap::MountSSHFS end host_capability("darwin", "sshfs_reverse_is_folder_mounted") do require_relative "cap/host/darwin/sshfs_reverse_mount" VagrantPlugins::HostDarwin::Cap::MountSSHFS end guest_capability("linux", "sshfs_forward_mount_folder") do require_relative "cap/guest/linux/sshfs_forward_mount" VagrantPlugins::GuestLinux::Cap::MountSSHFS end guest_capability("linux", "sshfs_forward_unmount_folder") do require_relative "cap/guest/linux/sshfs_forward_mount" VagrantPlugins::GuestLinux::Cap::MountSSHFS end guest_capability("linux", "sshfs_forward_is_folder_mounted") do require_relative "cap/guest/linux/sshfs_forward_mount" VagrantPlugins::GuestLinux::Cap::MountSSHFS end guest_capability("linux", "sshfs_get_absolute_path") do require_relative "cap/guest/linux/sshfs_get_absolute_path" VagrantPlugins::GuestLinux::Cap::SSHFSGetAbsolutePath end guest_capability("redhat", "sshfs_installed") do require_relative "cap/guest/redhat/sshfs_client" VagrantPlugins::GuestRedHat::Cap::SSHFSClient end guest_capability("redhat", "sshfs_install") do require_relative "cap/guest/redhat/sshfs_client" VagrantPlugins::GuestRedHat::Cap::SSHFSClient end guest_capability("fedora", "sshfs_installed") do require_relative "cap/guest/fedora/sshfs_client" VagrantPlugins::GuestFedora::Cap::SSHFSClient end guest_capability("fedora", "sshfs_install") do require_relative "cap/guest/fedora/sshfs_client" VagrantPlugins::GuestFedora::Cap::SSHFSClient end guest_capability("debian", "sshfs_installed") do require_relative "cap/guest/debian/sshfs_client" VagrantPlugins::GuestDebian::Cap::SSHFSClient end guest_capability("debian", "sshfs_install") do require_relative "cap/guest/debian/sshfs_client" VagrantPlugins::GuestDebian::Cap::SSHFSClient end guest_capability("arch", "sshfs_installed") do require_relative "cap/guest/arch/sshfs_client" VagrantPlugins::GuestArch::Cap::SSHFSClient end guest_capability("arch", "sshfs_install") do require_relative "cap/guest/arch/sshfs_client" VagrantPlugins::GuestArch::Cap::SSHFSClient end guest_capability("suse", "sshfs_installed") do require_relative "cap/guest/suse/sshfs_client" VagrantPlugins::GuestSUSE::Cap::SSHFSClient end guest_capability("suse", "sshfs_install") do require_relative "cap/guest/suse/sshfs_client" VagrantPlugins::GuestSUSE::Cap::SSHFSClient end guest_capability("freebsd", "sshfs_forward_mount_folder") do require_relative "cap/guest/freebsd/sshfs_forward_mount" VagrantPlugins::GuestFreeBSD::Cap::MountSSHFS end guest_capability("freebsd", "sshfs_forward_unmount_folder") do require_relative "cap/guest/freebsd/sshfs_forward_mount" VagrantPlugins::GuestFreeBSD::Cap::MountSSHFS end guest_capability("freebsd", "sshfs_forward_is_folder_mounted") do require_relative "cap/guest/freebsd/sshfs_forward_mount" VagrantPlugins::GuestFreeBSD::Cap::MountSSHFS end guest_capability("freebsd", "sshfs_get_absolute_path") do require_relative "cap/guest/linux/sshfs_get_absolute_path" VagrantPlugins::GuestLinux::Cap::SSHFSGetAbsolutePath end guest_capability("freebsd", "sshfs_install") do require_relative "cap/guest/freebsd/sshfs_client" VagrantPlugins::GuestFreeBSD::Cap::SSHFSClient end guest_capability("freebsd", "sshfs_installed") do require_relative "cap/guest/freebsd/sshfs_client" VagrantPlugins::GuestFreeBSD::Cap::SSHFSClient end end end end lib/vagrant-sshfs/synced_folder.rb000066400000000000000000000105311325134676200175700ustar00rootroot00000000000000require "log4r" require "vagrant/util/platform" require "vagrant/util/which" require_relative "synced_folder/sshfs_forward_mount" require_relative "synced_folder/sshfs_reverse_mount" module VagrantPlugins module SyncedFolderSSHFS class SyncedFolder < Vagrant.plugin("2", :synced_folder) def initialize(*args) super @logger = Log4r::Logger.new("vagrant::synced_folders::sshfs") end # This is called early when the synced folder is set to determine # if this implementation can be used for this machine. This should # return true or false. # # @param [Machine] machine # @param [Boolean] raise_error If true, should raise an exception # if it isn't usable. # @return [Boolean] def usable?(machine, raise_error=false) return true #for now end # This is called after the machine is booted and after networks # are setup. # # This might be called with new folders while the machine is running. # If so, then this should add only those folders without removing # any existing ones. # # No return value. def enable(machine, folders, pluginopts) # Iterate through the folders and mount if needed folders.each do |id, opts| if opts.has_key?(:reverse) and opts[:reverse] do_reverse_mount(machine, opts) else do_forward_mount(machine, opts) end end end # This is called to remove the synced folders from a running # machine. # # This is not guaranteed to be called, but this should be implemented # by every synced folder implementation. # # @param [Machine] machine The machine to modify. # @param [Hash] folders The folders to remove. This will not contain # any folders that should remain. # @param [Hash] opts Any options for the synced folders. def disable(machine, folders, opts) # Iterate through the folders and mount if needed folders.each do |id, opts| if opts.has_key?(:reverse) and opts[:reverse] do_reverse_unmount(machine, opts) else do_forward_unmount(machine, opts) end end end # This is called after destroying the machine during a # `vagrant destroy` and also prior to syncing folders during # a `vagrant up`. # # No return value. # # @param [Machine] machine # @param [Hash] opts def cleanup(machine, opts) end protected # Function to find the path to an executable with name "name" def find_executable(name) error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSExeNotAvailable # Save off PATH env var before we modify it oldpath = ENV['PATH'] # Try to include paths where sftp-server may live so # That we have a good chance of finding it if Vagrant::Util::Platform.windows? if Vagrant::Util::Platform.cygwin? # If in a cygwin terminal then we can programmatically # determine where sftp-server would be. ssh should already # be in path. cygwin_root = Vagrant::Util::Platform.cygwin_windows_path('/') ENV['PATH'] += ';' + cygwin_root + '\usr\sbin' else # If not in a cygwin terminal then we'll have to guess # where cygwin is installed and add the /bin/ (for ssh) and # /usr/sbin (for sftp-server) to the PATH. ENV['PATH'] += ';C:\cygwin\bin' ENV['PATH'] += ';C:\cygwin\usr\sbin' ENV['PATH'] += ';C:\cygwin64\bin' ENV['PATH'] += ';C:\cygwin64\usr\sbin' end else ENV['PATH'] += ':/usr/libexec/openssh' # Linux (Red Hat Family) ENV['PATH'] += ':/usr/lib/openssh' # Linux (Debian Family) ENV['PATH'] += ':/usr/lib/ssh' # Linux (Arch Linux Family) ENV['PATH'] += ':/usr/lib/misc' # Linux (Gentoo Family) ENV['PATH'] += ':/usr/libexec/' # Mac OS X end # Try to find the executable exepath = Vagrant::Util::Which.which(name) raise error_class, executable: name if !exepath # Restore the PATH variable and return ENV['PATH'] = oldpath return exepath end end end end lib/vagrant-sshfs/synced_folder/000077500000000000000000000000001325134676200172435ustar00rootroot00000000000000lib/vagrant-sshfs/synced_folder/sshfs_forward_mount.rb000066400000000000000000000103031325134676200236610ustar00rootroot00000000000000require "log4r" require "vagrant/util/platform" require "vagrant/util/which" module VagrantPlugins module SyncedFolderSSHFS class SyncedFolder < Vagrant.plugin("2", :synced_folder) protected # Do a forward mount: mounting host folder into the guest def do_forward_mount(machine, opts) # Check to see if sshfs software is in the guest if machine.guest.capability?(:sshfs_installed) if !machine.guest.capability(:sshfs_installed) can_install = machine.guest.capability?(:sshfs_install) if !can_install raise VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSNotInstalledInGuest end machine.ui.info(I18n.t("vagrant.sshfs.actions.installing")) machine.guest.capability(:sshfs_install) end end # If already mounted then there is nothing to do if machine.guest.capability(:sshfs_forward_is_folder_mounted, opts) machine.ui.info( I18n.t("vagrant.sshfs.info.already_mounted", location: 'guest', folder: opts[:guestpath])) return end # If the synced folder entry has host information in it then # assume we are doing a normal sshfs mount to a host that isn't # the machine running vagrant. Rely on password/ssh keys. # # If not, then we are doing a slave mount and we need to # make sure we can find the sftp-server and ssh execuatable # files on the host. if opts.has_key?(:ssh_host) and opts[:ssh_host] # Check port information and find out auth info check_host_port(machine, opts) get_auth_info(machine, opts) else opts[:ssh_exe_path] = find_executable('ssh') opts[:sftp_server_exe_path] = find_executable('sftp-server') end # Do the mount machine.ui.info(I18n.t("vagrant.sshfs.actions.mounting")) machine.guest.capability(:sshfs_forward_mount_folder, opts) end def do_forward_unmount(machine, opts) # If not mounted then there is nothing to do if ! machine.guest.capability(:sshfs_forward_is_folder_mounted, opts) machine.ui.info( I18n.t("vagrant.sshfs.info.not_mounted", location: 'guest', folder: opts[:guestpath])) return end # Do the Unmount machine.ui.info(I18n.t("vagrant.sshfs.actions.unmounting")) machine.guest.capability(:sshfs_forward_unmount_folder, opts) end # Check if port information was provided in the options. If not, # then default to port 22 for ssh def check_host_port(machine, opts) if not opts.has_key?(:ssh_port) or not opts[:ssh_port] opts[:ssh_port] = '22' end end # Function to gather authentication information (username/password) # for doing a normal sshfs mount def get_auth_info(machine, opts) prompt_for_password = false ssh_info = machine.ssh_info # Detect the username of the current user username = `whoami`.strip # If no username provided then default to the current # user that is executing vagrant if not opts.has_key?(:ssh_username) or not opts[:ssh_username] opts[:ssh_username] = username end # Check to see if we need to prompt the user for a password. # We will prompt if: # - User asked us to via prompt_for_password option # - User did not provide a password in options and is not fwding ssh agent # if opts.has_key?(:prompt_for_password) and opts[:prompt_for_password] prompt_for_password = opts[:prompt_for_password] end if not opts.has_key?(:ssh_password) or not opts[:ssh_password] if not ssh_info.has_key?(:forward_agent) or not ssh_info[:forward_agent] prompt_for_password = true end end # Now do the prompt if prompt_for_password opts[:ssh_password] = machine.ui.ask( I18n.t("vagrant.sshfs.ask.prompt_for_password", username: opts[:ssh_username]), echo: false) end end end end end lib/vagrant-sshfs/synced_folder/sshfs_reverse_mount.rb000066400000000000000000000031601325134676200236730ustar00rootroot00000000000000require "log4r" require "vagrant/util/platform" require "vagrant/util/which" module VagrantPlugins module SyncedFolderSSHFS class SyncedFolder < Vagrant.plugin("2", :synced_folder) protected # Do a reverse mount: mounting guest folder onto the host def do_reverse_mount(machine, opts) # Check to see if sshfs software is in the host if machine.env.host.capability?(:sshfs_installed) if !machine.env.host.capability(:sshfs_installed) raise VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSNotInstalledInHost end end # If already mounted then there is nothing to do if machine.env.host.capability(:sshfs_reverse_is_folder_mounted, opts) machine.ui.info( I18n.t("vagrant.sshfs.info.already_mounted", location: 'host', folder: opts[:hostpath])) return end # Do the mount machine.ui.info(I18n.t("vagrant.sshfs.actions.mounting")) machine.env.host.capability(:sshfs_reverse_mount_folder, machine, opts) end def do_reverse_unmount(machine, opts) # If not mounted then there is nothing to do if ! machine.env.host.capability(:sshfs_reverse_is_folder_mounted, opts) machine.ui.info( I18n.t("vagrant.sshfs.info.not_mounted", location: 'host', folder: opts[:hostpath])) return end # Do the Unmount machine.ui.info(I18n.t("vagrant.sshfs.actions.unmounting")) machine.env.host.capability(:sshfs_reverse_unmount_folder, machine, opts) end end end end lib/vagrant-sshfs/version.rb000066400000000000000000000001211325134676200164270ustar00rootroot00000000000000module VagrantPlugins module SyncedFolderSSHFS VERSION = "1.3.1" end end locales/000077500000000000000000000000001325134676200125115ustar00rootroot00000000000000locales/synced_folder_sshfs.yml000066400000000000000000000066711325134676200172740ustar00rootroot00000000000000en: vagrant: sshfs: actions: installing: Installing SSHFS client... mounting: Mounting SSHFS shared folder... unmounting: Unmounting SSHFS shared folder... unmounting_folder: |- Unmounting SSHFS shared folder mounted at %{guestpath} reverse_unmounting_folder: |- Unmounting SSHFS shared folder mounted at host's %{hostpath} reverse_mounting_folder: |- mounting folder via SSHFS: guestpath:%{guestpath} => hostpath:%{hostpath} slave_mounting_folder: |- Mounting folder via SSHFS: %{hostpath} => %{guestpath} normal_mounting_folder: |- Mounting folder via SSHFS: %{user}@%{host}:%{hostpath} => %{guestpath} ask: prompt_for_password: |- SSHFS password for '%{username}': info: detected_host_ip: |- Detected host IP address is '%{ip}' already_mounted: |- The folder %{folder} in the %{location} is already mounted. not_mounted: |- The folder %{folder} in the %{location} is not mounted. errors: communicator_not_ready: |- The machine is reporting that it is not ready to communicate via ssh. Verify this machine is properly running. exe_not_in_host: |- The '%{executable}' executable file can't be found on the host machine but is required for sshfs mounting to work. Please install the software and try again. sshfs_not_in_guest: |- The necessary SSHFS software is not installed in the guest. get_absolute_path_failed: |- Could not get the absolute path of the folder within the guest '%{path}' install_failed_arch: |- The install of the sshfs client software failed. On Arch this is most likely because the package lists are not up to date [1] and partial upgrades are not supported [2]. Please update your Arch system or install SSHFS manually. [1] https://wiki.archlinux.org/index.php/pacman#Packages_cannot_be_retrieved_on_installation [2] https://wiki.archlinux.org/index.php/System_maintenance#Partial_upgrades_are_unsupported normal_mount_failed: |- Mounting SSHFS shared folders failed. This is most often caused by either an SSH Daemon not running on the target host or invalid credentials being provided. Please make sure an SSH daemon is running on the host and proper credentials were provided to be able to authenticate via SSH. The command and output are: %{command} Stdout from the command: %{stdout} Stderr from the command: %{stderr} reverse_mount_failed: |- Mounting SSHFS shared folder via reverse SSHFS mount failed. Please look at the below output from from the processes that were run. SSHFS command output: %{sshfs_output} slave_mount_failed: |- Mounting SSHFS shared folder via slave SSHFS mount failed. Please look at the below STDERR output from the processes that were run. SSH command: %{ssh_stderr} SFTP command: %{sftp_stderr} unmount_failed: |- Unmount the SSHFS mount failed. The command and output are: %{command} Stdout from the command: %{stdout} Stderr from the command: %{stderr} test/000077500000000000000000000000001325134676200120465ustar00rootroot00000000000000test/libvirt/000077500000000000000000000000001325134676200135215ustar00rootroot00000000000000test/libvirt/README.txt000066400000000000000000000010321325134676200152130ustar00rootroot00000000000000 To bring up vagrant host: vagrant up To run tests: vagrant ssh and then: cd /sharedfolder/code/github.com/dustymabe/vagrant-sshfs/ gem install bundler # see [2] bundle install --with plugins # see [1] bundle exec rake featuretests [1] when running with bundler 1.13.2 I had to comment out the vagrant-sshfs line in Gemfile because it errored out complaining about it being defined twice. Running with 1.12.5 works fine. [2] because of [1] need to use this instead: gem install bundler --version 1.12.5 test/libvirt/Vagrantfile000066400000000000000000000023471325134676200157140ustar00rootroot00000000000000# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure(2) do |config| config.ssh.insert_key = 'true' config.vm.synced_folder "/guests/sharedfolder", "/sharedfolder", type: "sshfs" config.vm.provider :libvirt do |domain| domain.memory = 4096 domain.cpus = 4 domain.nested = true end host = 'viv-libvirt' # vagrant in vagrant - to test libvirt box = 'fedora/25-cloud-base' config.vm.define host do | tmp | tmp.vm.hostname = host tmp.vm.box = box end config.vm.provision "shell", inline: <<-SHELL rpms=( libvirt-daemon-kvm # for vagrant libvirt support make gcc ruby ruby-devel redhat-rpm-config # for building gems gcc-c++ # for building unf_ext libvirt-devel # for building ruby-libvirt gem zlib-devel # for building nokogiri gem git # for the git ls-files in gemspec file bsdtar # used by vagrant to unpack box files ) dnf install -y ${rpms[@]} usermod -a -G libvirt vagrant systemctl start libvirtd virtlogd SHELL end test/misc/000077500000000000000000000000001325134676200130015ustar00rootroot00000000000000test/misc/README.txt000066400000000000000000000026331325134676200145030ustar00rootroot00000000000000 # This directory is for testing the three different mount modes # that are supported by vagrant-sshfs # To test we will first create the directory on the machien where # we will mount the guest /etc/ into the host (the reverse mount). mkdir /tmp/reverse_mount_etc # Next we will define where our 3rd party host is (the normal mount). # This can be another vagrant box or whatever machine you want. export THIRD_PARTY_HOST='192.168.121.73' export THIRD_PARTY_HOST_USER='vagrant' export THIRD_PARTY_HOST_PASS='vagrant' # Next vagrant up - will do 4 mounts # - slave # - slave with sym link # - normal # - reverse vagrant up # Next run the script to test the mounts: $ bash dotests.sh Testing slave forward mount! d635332fe7aa4d4fb48e5cb9357bdedf Testing slave forward mount with a symlink! d635332fe7aa4d4fb48e5cb9357bdedf Testing normal forward mount! 6ccc3034df924bd289dd16205bf3d629 Testing reverse mount! 508619e7e68e446c84d1fcdf7e0dc577 # We are printing out the machine-id under each mount. The first two should be the same, because they are from the same machine. The last two should be different. test/misc/Vagrantfile000066400000000000000000000021761325134676200151740ustar00rootroot00000000000000Vagrant.configure(2) do |config| config.ssh.insert_key = 'true' # Test a forward slave mount: # mounting /etc/ from the vagrant host into the guest config.vm.synced_folder "/etc/", "/tmp/forward_slave_mount_etc/", type: "sshfs" # Test a forward mount to a location that is a symbolic link # https://github.com/dustymabe/vagrant-sshfs/issues/44 config.vm.synced_folder "/etc/", "/sbin/forward_slave_mount_sym_link_test/", type: "sshfs" # Test a forward normal mount: # mounting a folder from a 3rd party host into guest config.vm.synced_folder "/etc/", "/tmp/forward_normal_mount_etc/", type: "sshfs", ssh_host: ENV['THIRD_PARTY_HOST'], ssh_username: ENV['THIRD_PARTY_HOST_USER'], ssh_password: ENV['THIRD_PARTY_HOST_PASS'] # Test a reverse mount: # mounting /etc/ from vagrant guest into vagrant host config.vm.synced_folder "/tmp/reverse_mount_etc/", "/etc", type: "sshfs", reverse: true host = 'vagrant-sshfs-tests' box = 'fedora/25-cloud-base' config.vm.define host do | tmp | tmp.vm.hostname = host tmp.vm.box = box end end test/misc/dotests.sh000066400000000000000000000010251325134676200150200ustar00rootroot00000000000000#!/bin/bash set -eu # Test the four mounts we have done echo -en "Testing slave forward mount!\n\t" vagrant ssh -- cat /tmp/forward_slave_mount_etc/machine-id # https://github.com/dustymabe/vagrant-sshfs/issues/44 echo -en "Testing slave forward mount with a symlink!\n\t" vagrant ssh -- cat /usr/sbin/forward_slave_mount_sym_link_test/machine-id echo -en "Testing normal forward mount!\n\t" vagrant ssh -- cat /tmp/forward_normal_mount_etc/machine-id echo -en "Testing reverse mount!\n\t" cat /tmp/reverse_mount_etc/machine-id test/virtualbox/000077500000000000000000000000001325134676200142455ustar00rootroot00000000000000test/virtualbox/README.txt000066400000000000000000000012041325134676200157400ustar00rootroot00000000000000# XXX Note this is not working right now as nested virt. I keep # getting kernel tracebacks on Fedora 24. To bring up vagrant host: vagrant up To run tests: vagrant ssh and then: cd /sharedfolder/code/github.com/dustymabe/vagrant-sshfs/ gem install bundler # see [2] bundle install --with plugins # see [1] bundle exec rake featuretests [1] when running with bundler 1.13.2 I had to comment out the vagrant-sshfs line in Gemfile because it errored out complaining about it being defined twice. Running with 1.12.5 works fine. [2] because of [1] need to use this instead: gem install bundler --version 1.12.5 test/virtualbox/Vagrantfile000066400000000000000000000030371325134676200164350ustar00rootroot00000000000000# -*- mode: ruby -*- # vi: set ft=ruby : # # XXX Note this is not working right now as nested virt. I keep # getting kernel tracebacks on Fedora 24. # Vagrant.configure(2) do |config| config.ssh.insert_key = 'true' config.vm.synced_folder "/guests/sharedfolder", "/sharedfolder", type: "sshfs" config.vm.provider :libvirt do |domain| domain.memory = 4096 domain.cpus = 4 domain.nested = true end host = 'viv-virtbox' # vagrant in vagrant - to test virtbox box = 'fedora/25-cloud-base' config.vm.define host do | tmp | tmp.vm.hostname = host tmp.vm.box = box end # Must use VirtualBox-5.0 - 5.1 not supported by Vagrant yet config.vm.provision "shell", inline: <<-SHELL dnf config-manager --add-repo http://download.virtualbox.org/virtualbox/rpm/fedora/virtualbox.repo rpms=( kernel-devel-$(uname -r) kernel-headers-$(uname -r) gcc make VirtualBox-5.0 # all for virtualbox support make gcc ruby ruby-devel redhat-rpm-config # for building gems gcc-c++ # for building unf_ext libvirt-devel # for building ruby-libvirt gem zlib-devel # for building nokogiri gem git # for the git ls-files in gemspec file bsdtar # used by vagrant to unpack box files ) dnf install -y ${rpms[@]} /usr/lib/virtualbox/vboxdrv.sh setup usermod -a -G vboxusers vagrant SHELL end vagrant-sshfs.gemspec000066400000000000000000000022631325134676200152250ustar00rootroot00000000000000# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'vagrant-sshfs/version' Gem::Specification.new do |spec| spec.name = "vagrant-sshfs" spec.version = VagrantPlugins::SyncedFolderSSHFS::VERSION spec.authors = ["Dusty Mabe"] spec.email = ["dusty@dustymabe.com"] spec.description = """ A Vagrant synced folder plugin that mounts folders via SSHFS. This is the successor to Fabio Kreusch's implementation: https://github.com/fabiokr/vagrant-sshfs""" spec.summary = spec.description spec.homepage = "https://github.com/dustymabe/vagrant-sshfs" spec.license = "GPL-2.0" spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.add_dependency 'win32-process' spec.add_development_dependency 'bundler' spec.add_development_dependency 'rake' spec.add_development_dependency 'cucumber' spec.add_development_dependency 'aruba' spec.add_development_dependency 'komenda' end