prograhammer
4/29/2015 - 4:05 PM

Laravel/Lumen 5.1 Homestead for Windows (includes fixes for shared-folder related slowness, npm install problems, caching, etc)

Laravel/Lumen 5.1 Homestead for Windows (includes fixes for shared-folder related slowness, npm install problems, caching, etc)

Laravel / Lumen Homestead for Windows w/fixes

###Initial installation and configuration Install Git for Windows which includes Git Bash.

Install VirtualBox and Vagrant.

Note: These fixes were not originally done for VirtualBox 5.0 and Vagrant 1.7.4. They are for the previous versions instead: VirtualBox 4.3.30 and Vagrant 1.7.3. Some of these fixes may not be needed now. For example, I recently upgraded to VirtualBox 5.0 and Vagrant 1.7.4 and I did not have to alter the Vagrant ruby (.rb) files as shown in part of this Gist below. I'll soon try from a fresh install (not upgrade) and update this Gist accordingly.

In Git Bash (always run as administrator), type cd ~ to get to your "home" directory, ie.C:\Users\David, and run these commands:

$ vagrant box add laravel/homestead

When prompted: choose option #1: virtualbox. Now clone (here I show clone command for php7 homestead box...although memcached isn't ready yet for php7):

$ git clone -b php-7 https://github.com/laravel/homestead.git Homestead

Now navigate to the Homestead directory and run the bash init.sh command to create the Homestead.yaml configuration file:

$ bash init.sh

Create your SSH keys using Git bash. I like to have the keys (.ssh folder) in the "home" directory as well. ie. C:\Users\David\.ssh:

$ ssh-keygen -t rsa -C "david@homestead"

(and then just press [enter] through each prompt)

Note: If you are using PhpStorm, you may already have an ssh key made. You could use that one if you wish. Just update the ssh section of your Homestead.yaml file to point to that key.

Now here is my C:\Users\David\.homestead\Homestead.yaml file with some adjustments I made, and with things pointing to the ~ home directory: (You may use the absolute Windows path instead of what I have done with ~/projects/sites here, just make sure you capitalize the drive letter)

---
ip: "192.168.10.10"
memory: 3072
cpus: 1
provider: virtualbox

authorize: ~/.ssh/id_rsa.pub

keys:
    - ~/.ssh/id_rsa

folders:
   - map: ~/projects/sites
     to: /home/vagrant/sites

sites:
   - map: my-homestead-blog.loc
     to: /home/vagrant/sites/blog/public
    
databases:
   - homestead
   
variables:
   - key: APP_ENV
     value: local

This example will look for a folder at C:\Users\David\projects\sites, so ensure you have created that folder there. (Note: Be careful you don't add Tabs when editing Homestead.yaml)

###Shared mounted folder configuration and Laravel install

Run the guest box additions plugin and the winnfsd plugin (in Git bash):

$ vagrant plugin install vagrant-vbguest  
$ vagrant plugin install vagrant-winnfsd

And add/update these parts of your C:\Users\David\Homestead\scripts\homestead.rb file like this:

  # Add this new section
  # Reorder folders for winnfsd plugin compatilibty
  # see https://github.com/GM-Alex/vagrant-winnfsd/issues/12#issuecomment-78195957
  settings["folders"].sort! { |a,b| a["map"].length <=> b["map"].length }

  # Configure A Few VirtualBox Settings
  config.vm.provider "virtualbox" do |vb|
    vb.name = 'homestead'
    vb.customize ["modifyvm", :id, "--memory", settings["memory"] ||= "2048"]
    vb.customize ["modifyvm", :id, "--cpus", settings["cpus"] ||= "1"]
    vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
    vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
    vb.customize ["modifyvm", :id, "--ostype", "Ubuntu_64"]
    # add this to turn on symlink ability (so we can do 'npm installs')
    vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/v-root", "1"]      
  end     
  
 # Register All Of The Configured Shared Folders
 if settings.include? 'folders'
   settings["folders"].each do |folder|
     mount_opts = []

     if (folder["type"] == "nfs")
         mount_opts = folder["mount_opts"] ? folder["mount_opts"] : ['actimeo=1']
     end

     # config.vm.synced_folder folder["map"], folder["to"], type: folder["type"] ||= nil, mount_options: mount_opts
     # add this manual mount option line:
     config.vm.synced_folder folder["map"], folder["to"], :nfs => { :mount_options => ["dmode=777","fmode=777"] }
   end
 end

To fix the issue with long path names in Windows:

  • Update C:\HashiCorp\Vagrant\embedded\gems\gems\vagrant-1.7.3\plugins\providers\virtualbox\driver\version_4_3.rb near line 499 by replacing the hostpath declaration to look like this now (and notice the if condition below is now commented out):
def share_folders(folders)
       folders.each do |folder|
         hostpath = '\\\\?\\' + folder[:hostpath].gsub(/[\/\\]/,'\\')
         #if Vagrant::Util::Platform.windows?
         #  hostpath = Vagrant::Util::Platform.windows_unc_path(hostpath)
         #end
  • Update C:\HashiCorp\Vagrant\embedded\gems\gems\vagrant-1.7.3\lib\vagrant\util\platform.rb and comment out a big part of this section starting near line 111, like this:
```
    # This expands the path and ensures proper casing of each part
    # of the path.
    
    def fs_real_path(path, **opts)
    
      #path = Pathname.new(File.expand_path(path))
      #
      # if path.exist? && !fs_case_sensitive?
      #  # Build up all the parts of the path
      #  original = []
      #  while !path.root?
      #    original.unshift(path.basename.to_s)
      #    path = path.parent
      #  end
      #
      #  # Traverse each part and join it into the resulting path
      #  original.each do |single|
      #    Dir.entries(path).each do |entry|
      #     if entry.downcase == single.encode('filesystem').downcase
      #       path = path.join(entry)
      #     end
      #    end
      #  end
      #end
      #
      # if windows?
      #  # Fix the drive letter to be uppercase.
      #  path = path.to_s
      #  if path[1] == ":"
      #    path[0] = path[0].upcase
      #  end
      #
      #  path = Pathname.new(path)
      #end

      path
    end
```

Now navigate to your ~/Homestead directory in Git bash and vagrant up. (if you get a strange error, run vagrant up once more and you should be good. I'll look into this strange behavior later.)

Note: you won't be able to manage the homestead box using the VirtualBox manager GUI since you'll need this 'vagrant up' configuration each time. Use vagrant halt to power-down and vagrant up to restart. If you need to destroy it and start all over, do vagrant destroy --force followed by another vagrant up.

Then vagrant ssh and go into your sites folder and install Laravel or Lumen (I'm installing Lumen here):

$ composer global require "laravel/lumen-installer=~1.0"
$ lumen new blog

Now navigate to: http://localhost:8000. You should update your C:\Windows\System32\drivers\etc\hosts file to use the site name like you specificed in your Homestead.yaml file:

192.168.10.10   my-homestead-blog.loc
192.168.10.10   my-second-blog.loc
192.168.10.10   my-third-blog.loc

Note: If you see "no input file specified", then nginx can't find any file for your site. Probably your Homestead.yaml sites "to" path is not matching were your actual public folder is for Laravel or Lumen.

###NPM Install problems

You will most surely have problems with sudo npm install on the ubuntu guest machine, in the shared folder. You may get problems like:

  • symlink input/output error
  • Cannot find module 'laravel-elixir'
  • and more

Here's what to do:

  • Before you do these steps, you need to ensure you have opened Git Bash with 'run as administrator'.
  • Now create a folder outside of the shared folder (just create some folder one level up from the shared folder), and manually create a package.json file there (just manually re-type the file, don't perform a cp command), and run sudo npm install there.
  • If you mess up for some reason and need to remove the node_modules folder with a sudo rm -rf node_modules, it may fail on first try, but try running the command again.
  • Then change directories to your Laravel/Lumen project folder (that has the composer.json file in it) and now run the symlink command (you must be inside the shared folder for symlinking command to work):
  sudo ln -s /home/vagrant/some-folder/node_modules  node_modules
  • Now you can run gulp (run it without sudo):
   gulp --production

You'll need to perform all npm installs in the folder you created above (ie. /home/vagrant/some-folder/). Here's some useful gulp plugins you might want to install:

     sudo npm install gulp-less
     sudo npm install gulp-minify-css
     sudo npm install gulp-uglify
     sudo npm install gulp-concat
     sudo npm install gulp-add-src
     sudo npm install gulp-sourcemaps

###How can I access my site from another PC/Device?

  • Just go to your command prompt (cmd.exe) in Windows and do: ipconfig to get your host's IP (ie. 192.168.x.x). Then, from any other pc on the network, enter the host IP address followed by a :8000 to access the VM/site (ie. 192.168.x.x:8000/foo/bar).
  • If you want to access the site using the domain then you will need to edit the hosts files of the other computers as well. However those host files will point to the 192.168.x.x:8000 ip address. If you have multiple sites (my-homestead-blog.loc, my-second-blog.loc, etc.) then you will need to do this. If you are trying to access your site form a rooted mobile device that doesn't let you modify the hosts file, then set one of your sites as the nginx default_server in your sites-available and sites-enabled files. This will allow you to just use the ip and map whichever site you want to view at a time:
    server {
        listen      80 default_server;
        listen      443 ssl;
        server_name my.homestead-blog.loc;
        root "/home/vagrant/sites/blog/public";
        ...
    }

Then don't forget to sudo service nginx restart.

###Caching issues

  • Update /etc/nginx/nginx.conf to have sendfile off;

  • Then find the location of opcache.so:

    $ sudo find / -name 'opcache.so'
    /usr/lib/php5/20131226/opcache.so`
    
  • Add the following to opcache.ini:

    $ sudo nano /etc/php5/mods-available/opcache.ini
    
    zend_extension=/usr/lib/php5/20131226/opcache.so
    opcache.memory_consumption=128
    opcache.interned_strings_buffer=8
    opcache.max_accelerated_files=4000
    opcache.revalidate_freq=0
    opcache.validate_timestamps=on
    opcache.fast_shutdown=1
    
  • Update the timezone: sudo dpkg-reconfigure tzdata

  • Update the time so it's older than the host OS. You will have to do this each time you vagrant up your box if your box's time loses sync (I'm currently not experiencing this issue on Windows 8.1.). If your Host OS is at 11:01:00, then set your VM to:

    $ sudo date --set 11:00:50
    $ sudo service nginx restart
    
    // You can set the full date with: 
    // $ sudo date --set "Thu, 4 Jun 2015 11:00:50" 
    
  • For local/developing environment, you can add/update your lumen .env file on your local machine to cache using array like so:

     APP_ENV=local
     APP_DEBUG=true
     ...
     CACHE_DRIVER=array
     ...
    
  • In Laravel, you can run php artisan clear-compiled (the opposite of optimize) to clear vendor/compiled.php and other things. You can also run php artisan cache:clear in Lumen and Laravel.

  • If you are using HHVM, I've not confirmed if HHVM caches anything that would be problematic for developer working local, but try without it (re-serve your site).

Note: For Lumen, in your bootstrap file, make sure you have Dotenv::load(__DIR__.'/../'); uncommented in the file so you can use your .env settings

###HHVM

####Changing a site to HHVM

 $ cd /etc/nginx/sites-enabled
 $ sudo rm my-homestead-blog.loc
 
 $ cd /etc/nginx/sites-available
 $ sudo rm my-homestead-blog.loc
 
 $ sudo service nginx restart
 
 $ cd /vagrant/scripts
 $ sudo sh serve-hhvm.sh my-homestead-blog.loc /home/vagrant/sites/blog/public 80
 $ sudo service nginx restart

You can confirm the site is using HHVM in php with:

die(defined('HHVM_VERSION'));

####Some errors not showing?

Make sure you have these settings in your php.ini:

/etc/hhvm/php.ini

 hhvm.server.implicit_flush = true
 hhvm.error_handling.call_user_handler_on_fatals = true

You can of course also tail errors at /var/log/hhvm/error.log

##PDO::FETCH_ASSOC

To change the 'fetch' => PDO::FETCH_CLASS, setting in vendor/laravel/lumen-framework/config/databse.php just create a folder config just under your main project folder (ie. same level as your boostrap folder) and copy the file database.php file into it. Then change the line to: 'fetch' => PDO::FETCH_ASSOC, and you'll get arrays returned from queries. Then do a php artisan cache:clear.

##Bower

Create bower.json in project folder (same folder where composer.json & package.json are):

 {
    "name": "laravel"
 }

Create .bowerrc in project folder:

 {
    "directory": "resources/assets/bower"
 }

Install dependencies:

 $ sudo bower install jquery --save --allow-root
 $ sudo bower install bootstrap --save --allow-root

Update Less

Change:

 @import "bootstrap/bootstrap";

to:

 @import "../bower/bootstrap/less/bootstrap";

Update gulpfile.js, update all references in the blade templates, remove old resources/assets/less/bootstrap folder, and then run gulp or gulp --production(to minify).

(note: you may need to clear bower cache from time to time: bower cache clean some-package --force --allow-root)

##Publishing Assets from Packages

You can use the artisan publish command to publish your assets from packages. For Lumen, you just need to add your own artisan command (look at Laravel's) or use the one found here: https://github.com/irazasyed/larasupport.

However, I prefer to avoid using artisan for this since I'm already maintaining a Less file and a Gulpfile. I simply update my gulpfile.js to have paths that pull directly from the vendor's package. See my gulpfile below.

Note: I don't use Laravel elixir.

In gulpfile.js

var gulp = require('gulp'),
    less = require('gulp-less'),
    minifyCSS = require('gulp-minify-css'),
    uglify = require('gulp-uglify'),
    concat = require('gulp-concat'),
    addsrc = require('gulp-add-src'),
    sourcemaps = require('gulp-sourcemaps');

var paths = {
    'assets': 'resources/assets/',
    'bower': 'resources/assets/bower/',
    'vendor': 'vendor/',
    'public': 'public/'
};

// CSS & Less
gulp.task('css', function(){
    gulp.src(paths.assets + 'less/core.less')
        .pipe(sourcemaps.init())
        .pipe(less())
        .pipe(minifyCSS())
        .pipe(sourcemaps.write('source-maps'))
        .pipe(gulp.dest('public/css'));
});

// Javascript
gulp.task('js', function() {
    gulp.src(paths.bower + 'selectize-extended/dist/js/standalone/selectize.js')
        .pipe(addsrc.append(paths.bower + 'smalot-bootstrap-datetimepicker/js/bootstrap-datetimepicker.js'))
        .pipe(addsrc.append(paths.bower + 'bootstrap-table/dist/bootstrap-table.js'))
        .pipe(addsrc.append(paths.bower + 'bootstrap-table/locale/bootstrap-table-en-US.min.js'))
        .pipe(addsrc.append(paths.assets + 'js/core.js'))
        .pipe(sourcemaps.init())
        .pipe(concat('core.js'))
        .pipe(uglify({mangle: false}))
        .pipe(sourcemaps.write('source-maps'))
        .pipe(gulp.dest('public/js'));
});

gulp.task('default',['css','js']);

You'll need to do a chmod -R 777 public so Gulp can write files to your public directory.