51

I'd like to run a bash script on the host machine when vagrant provisions the server.

What would be the best method of achieving this?

5 Answers 5

34

At least two plugins which should help:

If you don't care that the script is run on (almost) all vagrant commands, you can also just shell out (or use what ever ruby magic) in Vagrantfile:

system('./myscript.sh')

Vagrant.configure('2') do |config|
  # ...
end
5
  • 2
    Vagrant triggers looks like exactly what I need.
    – digital
    Feb 3, 2014 at 15:40
  • Where did you found this system() function? I can't find any documentation about it anywhere... Jul 21, 2015 at 11:42
  • 1
    @CristianoFontes, it's in the Kernel module, documented here. The Kernel module is included in the Object class, so its methods are available in all scopes.
    – tmatilai
    Jul 21, 2015 at 16:23
  • 1
    Stupid me. I was looking in vagrant documentation. Thanks! Jul 21, 2015 at 16:45
  • 1
    Btw. vagrant-triggers has been merged into Vagrant.
    – AdamKalisz
    Mar 31, 2020 at 16:52
34

Simple (and complete) Solution

(I say complete because the accepted answer does not check if the user is using vagrant up. Therefore, the script is executed on each command, which is not what the OP wants.)

There is however a simple solution to this.

ARGV[0] is the first argument of the command entered and can be up, down, status, etc.. Simply check the value of ARGV[0] in your Vagrantfile.


Something like this will do:

system("
    if [ #{ARGV[0]} = 'up' ]; then
        echo 'You are doing vagrant up and can execute your script'
        ./myscript.sh
    fi
")

Vagrant.configure('2') do |config|
  # ...
end
7
  • 1
    Hi, Mick... Nice answer thanks for it. But I can't get if [ #{ARGV[0]} = 'up' ]; to work on windows. It never finds the arg Jul 16, 2015 at 13:58
  • 1
    This executes your script first thing before anything else runs regardless of it's position in Vagrantfile. It might be sufficient for what you're doing but vagrant-triggers plugin was what I needed... Jul 17, 2015 at 11:22
  • 3
    @CristianoFontes you can do the argv test in ruby outside of the system call and it will work in windows and *nix. I use this to set a global ruby variable indicating that provisioning is happening by looking for the up or provision command on the command line:if ARGV[0] =~ /^up|provision$/i and not ARGV.include?("--no-provision") $provisioning = true else $provisioning = false end
    – Rhubarb
    Jul 31, 2015 at 18:01
  • 1
    This is actually a bad practice as advised by Vagrant, you should write a plugin to hook on to the "up" command, you can specificy any of: before, after, and around execution.
    – SilentICE
    Dec 14, 2015 at 16:54
  • 2
    @Mick its just from the Vagrant docs (docs.vagrantup.com/v2/plugins/commands.html). It also makes for a fragile script since you can't be certain that in call cases argv[0] is 'up' rather than say a flag. Also if you're dropping in to raw ruby you're kinda breaking out the encapsulation the framework is supposed to be providing. There are mechanisms exposed to do this correctly so IMHO you should use those whenever possible
    – SilentICE
    Dec 21, 2015 at 12:54
18

Based on @tmatilai's answer but updated for 2019, vagrant-triggers has been merged into Vagrant. So you can now do something like so:

node.trigger.before [:up, :provision] do |trigger|
  trigger.info = "Running ./myscript.sh locally..."
  trigger.run = {path: "./myscript.sh"}
end

This block goes inside of config.vm.define. Further documentation: https://www.vagrantup.com/docs/triggers/

Or directly in the Vagrant.configure("2") block:

config.trigger.before [:up, :provision] do |trigger|
  ...
end
2
  • 2
    This is the most elegant Answer to date. I should add that placing this and similar snippets inside config.vm.define is not a requirement; they can be placed within Vagrant.configure("2") do |config| ... end as well. As a final point of note, on Windows hosts, Vagrant will gladly execute Powershell scripts that have the .ps1 extension, too. Oct 10, 2019 at 17:10
  • @BenJohnson thanks for the tip. And as per the docs, one can use {inline: "..."} for inline scripts.
    – Druckles
    Sep 17, 2021 at 11:54
13

Put this near the top of your Vagrantfile:

module LocalCommand
    class Config < Vagrant.plugin("2", :config)
        attr_accessor :command
    end

    class Plugin < Vagrant.plugin("2")
        name "local_shell"

        config(:local_shell, :provisioner) do
            Config
        end

        provisioner(:local_shell) do
            Provisioner
        end
    end

    class Provisioner < Vagrant.plugin("2", :provisioner)
        def provision
            result = system "#{config.command}"
        end
    end
end

Then simply invoke in your Vagrantfile like this:

config.vm.provision "list-files", type: "local_shell", command: "ls"

And via the command line like this:

vagrant provision --provision-with list-files

This is kind of a hack as it looks like plug-in, but really isn't (it won't show up when you do vagrant plugin list). I don't recommend doing it this way except that it has the advantage of not needing to install a plugin, so your Vagrantfile will work on any machine that supports the latest configuration version (version 2 as of writing this). Though that sounds promisingly portable, there's also the whole cross-platform issue of the actual command you're issuing. You'll need to take into consideration if you want your Vagrantfile to be portable, but this should get you started.

1
  • 1
    Good answer, I'm going to use this to setup low port forwarding.
    – poindexter
    Feb 5, 2016 at 16:47
5

In line with what @tmatilai said about using

system('./myscript.sh')

I found it quite helpful for one time commands like installing vagrant commands or some provisioner that might not be installed in the system. I just avoid it re-running every time I invoke the vagrant commands by adding a sed to auto-comment the Vagrantfile.

For example:

system('vagrant plugin install vagrant-fabric && (pip install fabric jinja2 || sudo pip install fabric jinja2) && sed -i -e "s/^system/#system/g" Vagrantfile')

And I make that the first line of my Vagrantfile. This way it will first install the vagrant-fabric plugin, fabric and jinja (will try first without sudo for virtualenvs and with sudo if that fails) and then the line comments itself.

3
  • 1
    It would be easier to simply grep vagrant plugins list rather than uncommenting the Vagrantfile, which could cause problems for other people on your team. if [[ $(vagrant plugin list | grep -c vagrant-host-shell) == "0" ]] then vagrant plugin install vagrant-host-shell fi
    – Jordan
    Mar 30, 2015 at 14:31
  • The problem with this is that it will be triggered on other commands, what if you run vagrant status before vagrant up...
    – Mick
    Apr 15, 2015 at 14:26
  • @Mick does it matter as long as this unconditionally installs the prerequisites? Dec 14, 2020 at 16:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .