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?
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
Kernel
module, documented here. The Kernel
module is included in the Object
class, so its methods are available in all scopes.
(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
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
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
{inline: "..."}
for inline scripts.
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.
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.
if [[ $(vagrant plugin list | grep -c vagrant-host-shell) == "0" ]] then vagrant plugin install vagrant-host-shell fi
vagrant status
before vagrant up
...