Using the MEAN stack on Windows with PuPHPet

Photo by Liliane Limpens on Unsplash.

Lately I've been playing with Vagrant and Puppet to create virtual machines for local MEAN stack development. I quickly learned that hosting a Linux VM on a Windows host is tricky. Let's look at the challenges and workarounds for them.

Virtual Machines with Puppet and Vagrant

But before we dive in, let's define some terms.

  • A virtual machine (VM) is an operating system (OS) that you host within your primary OS. For example, you might run a Linux VM within your Windows OS. To work with VMs I like VirtualBox.
  • Vagrant is a tool that automates the setup of virtual machines to the specifications you define in your Vagrantfile. Just like you version your application code, you can version the code for creating the application environment.
  • Puppet adds Ruby script support to Vagrant, allowing for much more powerful setup routines.

In the past couple years I've worked with PHP, C#.NET, and Node.js, and gradually my computer grew messy with all of the runtimes and dependencies. Using the tools above I can create clean, controlled development VMs and throw them away when I'm done. Or if my project takes off, I can deploy my Vagrant configuration to providers like Amazon, Digital Ocean, Rackspace.

PuPHPet to the Rescue

This all sounds great, but as a Linux-novice and a Ruby-nothing I was soon overwhelmed by the demands of creating and managing these environments. I eventually found PuPHPet (think Puppet + PHP) which handles most of the hard work. PuPHPet is a web GUI for generating the Puppet scripts to meet your needs. It's described nicely in this article by its creator and this Sitepoint article. PuPHPet is named for its original PHP focus, but is evolving to support more stacks like MEAN.

Hosting Linux VMs on Windows

Now that PuPHPet has created our Linux VM, there will be some problems running it within Windows. Vagrant will map our Windows project directory (containing the Vagrantfile) to /vagrant on the Linux VM, so the two OSes need to play nicely together.

In order for VirtualBox to support Linux symbolic links in the shared folder, we need to enable the VBoxInternal2/SharedFoldersEnableSymlinksCreate property. Conveniently this can be done through our PuPHPet configuration file.

provider:
  virtualbox:
    modifyvm:
      natdnshostresolver1: on
    setextradata:
      VBoxInternal2/SharedFoldersEnableSymlinksCreate/vagranthome: '1'
  vmware:
    numvcpus: 1
  parallels:
    cpus: 1

The vagranthome part of this setting is the name of the Vagrant shared folder. It seems to vary depending on which version of Vagrant you use. To verify the name, open the Shared Folders list in the VirtualBox VM properties and find the name of the Machine Folder that maps to the folder containing your Vagrantfile.

Windows pathname length limit

Windows file paths are limited to 260 characters, but tools like npm may easily create paths longer than this. The workaround I use is a mix of ideas from this GitHub issue because no single suggestion worked for me.

See Updated – Vagrant on Windows path limit fix for an updated version of these scripts.

#!/bin/bash

BASE_FOLDER="/tmp/windows-path-limit-fix/"

create()
{
    # Generate a unique, random directory
    name="$BASE_FOLDER$$"
    while [ -d "$name" ]
    do
        name="$BASE_FOLDER$$"
    done

    eval "mkdir -p $name"
    eval "chown -R www-data:www-data $name"
    eval "chmod 775 $name"
    eval "mkdir -p $1"

    eval "mount -o bind $name $1"
}

if [ ! "$1" -o -z "$1" ]; then
    echo "You must specify a folder mount point (recommend an absolute path)"
else
    create $1
fi

This script will create a temporary directory inside the VM and mount it at the specified path. Because the temporary directory is solely within Linux, it can fully support long paths. When either OS tries to access the mounted path, the operation will be transparently rerouted to the temporary directory.

Update your Vagrantfile to call this script for each directory you want to remap.

config.vm.provision :shell, :path => 'puphpet/shell/install-ruby.sh'
config.vm.provision :shell, :path => 'puphpet/shell/install-puppet.sh'
config.vm.provision :shell, :path => 'puphpet/shell/install-puppet-modules.sh'
config.vm.provision :shell, :path => 'puphpet/shell/windows-path-limit-fix.sh', :args => '/vagrant/node_modules'config.vm.provision :shell, :path => 'puphpet/shell/windows-path-limit-fix.sh', :args => '/vagrant/.bower-tmp'
config.vm.provision :puppet do |puppet|
    puppet.facter = {
        'ssh_username'     => "#{ssh_username}",
        'provisioner_type' => ENV['VAGRANT_DEFAULT_PROVIDER'],
        'vm_target_key'    => 'vagrantfile-local',
    }

With these problems wrapped up, we should have a fully-functioning Linux VM running within Windows.

Drew

Drew

Hi! I'm Drew, the Wimpy Programmer. I'm a software developer and formerly a Windows server administrator. I use this blog to share my mistakes and ideas.