SilverStripe, Homestead & Forge 2/3

After installing and configuring SilverStripe in my first article we’ll move onto setting up our local development environment using Homestead, a Vagrant Virtual Machine set-up by Taylor Otwell the creator of Laravel and Forge.

The benefit of using this particular VM is that it is the same one that will be provisioned by Forge so we can ensure we have the same development environment as that of our production server.

Getting started with Forge is quite straightfoward and the php framework Laravel has great documentation that gives a thorough overview of using Homestead. Better yet, Laracasts has a fantastic screencast which will walk you through installing Vagrant, Virtual Box and Homestead. I highly recommend you start with these as I wont be going over their installation steps.

After you’ve gone through the installation process you should be able to run the command $ homestead from anywhere in the terminal to see a list of help commands. But before we run Homestead it will need to know a few things, like which sites we intend to develop and what domains they will use. To tell Homestead about these it has a helpful yaml configuration file. To open this run the following commands:

$ homestead init
$ homestead edit

The first command initialises Homestead if not already installed and the second opens the VM’s configuration files (specifically Homestead.yaml) in your predefined editor. If you’ve followed the recommended Laracast tutorial you’ll know what we’re looking at. Lets add the below configuration:

sites:
    map: forge-silverstripe.local
    to: home/vagrant/Sites/forge-sivlerstripe

databases:
    forgesilverstripe

variables:
    - key: APP_ENV
    value: local
    - key: DB_SERVER
    value: localhost
    - key: DB_USERNAME
    value: homestead
    - key: DB_PASSWORD
    value: secret
    - key: ADMIN_USERNAME
    value: admin
    - key: ADMIN_PASSWORD
    value: ohsosecret

Firstly we are mapping out the correct local development folder to the domain we intend to use for development purposes. In the Laravel Homestead documentation they use the extension .app but that’s now a valid extension so I’m using ‘.local’ to keep it clean and obvious.

Next we define a database named forgesilverstripe, simple enough.

Finally we set our environment variables. These will be available for every project that runs via Homestead. Here we map our required keys to use the default values that Homestead comes with. This is what _ss_environment.php will use when setting it’s configuration.

Before we continue we now need to let our local system know about the new host (forge-silverstripe.local) request we will be making otherwise we won’t see anything when we try hit our development domain. To add this to our hosts file we will use the Vim editor that comes with iTerm2. Run following command:

$ sudo vim /etc/hosts

This will ask for your system administrator password before opening up your hosts file in Vim. Once it’s opened add the following to the bottom of the file which points the VM IP address to our human readable domain.

192.168.10.10   forge-silverstripe.local

If you haven’t used Vim before you can navigate with the arrow keys, then press i to enter insert mode and begin editing the file. Once you’ve added in your text press esc to exit insert mode. Now type :w to write (save) your changes. Finally type :exit to close Vim. For more take a look at openvim.com.

We’re now ready to launch our Homestead Vagrant box. From anywhere in the terminal we can finally run the following command:

$ homestead up

Homestead wraps parts of the Vagrant api so it allows you to run the up command the same as you would directly from Vagrant. Once Vagrant has provisioned the environment you can navigate to the url you set up earlier, for us it’s http://forge-silverstripe.local. You should now see your fresh install of SilverStripe with the default theme.

Moving from Apache to Nginx

You may have noticed if you’ve hit some of the navigation links or tried to login to the admin panel that the url is not updating as you’d normally expect. That is the ‘mod_rewrite’ rules are not being applied and there is a prefix of /index.php/ before each subsequent link. This is because we are not using Apache but instead Nginx (pronounced ‘engine-x’) which does not implement the default .htaccess files that comes with SilverStripe. However, Nginx has it’s own configuration file that allows us to implement the same behaviour as well as other important security protocols which we’ll do in this article.

Admittedly I’m not an expert in either Apache or Nginx but I know enough to get around or perhaps just enough to be dangerous, so please ensure that you read through the appropriate documentation to ensure your implementation is robust and secure.

For us we will need to tweak the nginx.conf file that contains the default setup for the Laravel framework. To do this we need to get back into the terminal and ssh into our Virtual Machine. Homestead makes this really simple, just run the following command:

$ homestead ssh

If you’re having trouble with ssh ensure you’ve set up an appropriate access key and refer back to the documentation, the Laracast tutorial is good for this.

Once you’ve connected to the VM we need to edit the servers nginx.conf file. Run the following commands:

$ cd /etc/nginx/sites-available
$ ls

This will change directory to where Nginx stores it’s configuration files. The second command will list the files found in this directory which should match all the sites you have set-up to run via Homestead. We are going to open the configuration file specific to our project, for me it’s called forge-silverstripe.local, named the same as our domain. To open this in Vim run the following command.

$ sudo vim forge-silverstripe.local

With this open you will see the default Homestead Nginx configuration. There’s a lot in here which we won’t cover, for now all we’re interested in are the changes we need to make in order to secure our application and create pretty urls. Since this is our local version we will end up making the same changes when deploying to production via Forge so it’s a good idea to save these configuration settings somewhere to be used again later. For my part I’m going to simply commit a nginx.conf file in the root of my project.

At the top of the file you will notice a few properties that will be unique to your setup. The server_name and root will depend on what you’ve named your website and where files are located on your machine. Keep these as they are, the rest of the file is what we will be altering.

Going through this we firstly need to alter the index references, we don’t want to use SilverStripes default index.php but rather the framework/main.php file. So remove all the references to index here and replace the line with the following.

index framework/main.php;

We need to do the same for the default location. Replace the index reference here with framework/main.php again. It should look like the following.

location / {
    try_files $uri $uri/ /framework/main.php?$query_string;
}

You can then update the error pages but I’m going to skip that for now and move onto editing the assets which should look like something like the below.

location ^~ /assets/ {
    sendfile on;
    try_files $uri = 404;
}

Next we again need to replace the reference to the default index.php but this time allow a few additional options.

location ~ /framework/.*(main|rpc|tiny_mce_gzip)\.php$ {
    …
}

Ok, that will get the majority of our pretty urls behaving correctly but more importantly we need to address some security concerns, namely that we are leaving a lot of our executable files exposed for direct access. To protect these we are going to add a series of denials and in some cases a 404 so as to hide certain aspects of our setup from potential attack vectors.

However, if we lock this down too tightly the SilverStripe CMS will not be able to make the required ajax calls and you’ll quickly discover your administration panel breaking down.

So with that in mind we add the below rules.

location ~ /vendor/(.+)$ {
    deny all;
}

location ~ /framework/sake$ {
    deny all;
    return 404;
}

location ~ /(cms|framework|mysite)/(.+).(php|php3|php4|php5|phtml|inc|rb|config|json|md)$ {
    deny all;
}

location ^~ /silverstripe-cache {
    deny all;
}

location ~* /(cms|framework)/silverstripe_version$ {
    deny all;
}

location ~* \.(ya?ml)$ {
    deny all;
    return 404;
}

location ~* web\.config {
    deny all;
    return 404;
}

location ~* nginx\.conf {
    deny all;
    return 404;
}

location ~* composer\.(json|lock) {
    deny all;
    return 404;
}

location ~ /\. {
    deny all;
}

The complete configuration should now look something like this:

server {

    listen 80;
    server_name forge-silverstripe.local;
    root /home/vagrant/Sites/forge-silverstripe;

    index framework/main.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /framework/main.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/forge-silverstripe.local-error.log error;

    error_page 404 /framework/main.php;

    sendfile off;

    location ^~ /assets/ {
        sendfile on;
        try_files $uri = 404;
    }

    location ~ /framework/.*(main|rpc|tiny_mce_gzip)\.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_intercept_errors on;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
    }

    location ~ /\.ht {
        deny all;
    }

    location ~ /vendor/(.+)$ {
        deny all;
    }

    location ~ /framework/sake$ {
        deny all;
    }

    location ~ /(cms|framework|mysite)/(.+).(php|php3|php4|php5|phtml|inc|rb|config|json|md)$ {
        deny all;
    }

    location ^~ /silverstripe-cache {
        deny all;
    }

    location ~* \.(ya?ml)$ {
        deny all;
        return 404;
    }

    location ~* web\.config {
        deny all;
        return 404;
    }

    location ~* nginx\.conf {
        deny all;
        return 404;
    }

    location ~* composer\.(json|lock) {
        deny all;
        return 404;
    }

    location ~ /\. {
        deny all;
    }

}

Save the changes and exit out of Vim.

As a quick reminder to edit a file press i to enter insert mode. Pressing the esc key will exit insert mode. To save your changes exit insert mode and type :w and finally exit Vim by typing :exit.

Ok so we went through this the slightly longer way as I wanted to go over the changes required. What we can also do is create a file nginx.conf and commit this to the root of our project with all the above settings. Instead of manually typing these changes up we could copy and replace the default nginx setup for our site using the below commands.

$ sudo cp nginx.conf /etc/nginx/sites-available/forge-silverstripe.local

Make sure you’re in your project root directory otherwise ensure the path to your nginx.conf precedes the filename.

Reload & Restart Nginx

We now have an updated configuration for SilverStripe and Nginx that works but this wont change a thing until we reload and restart the server. From back in your terminal (still connected via ssh) run the following two commands.

$ sudo service nginx reload
$ sudo service nginx restart

You may find that if you if you don’t do this with sudo that the command does not execute. Also you might see a [ fail ] on the right side of terminal if there were errors in the configuration file. If this is the case open your configuration file and check for any syntax or spelling errors and reload/restart Nginx again.

If this has all worked without errors you should be able to refresh your site in the browser and see your pretty urls are back as expected. You should now also not be able to access any sensitive files, I’d recommend running some tests to ensure this is the case.

Using the Sake CLI

Working with SilverStripe you will find yourself using dev/build and flush=1 often either via the browser or command line. To do this with Homestead we will need to ensure we have ssh’d into the VM. Once inside we need to move to our project directory and set-up a few temp variables to enable the use of SilverStripes sake cli. Run the following commands:

$ cd Sites/forge-silverstripe

$ export DB_USERNAME=homestead
$ export DB_PASSWORD=secret
$ export APP_ENV=local

$ sudo ./framework/sake installsake

Firstly we change to the root directory of our project. From here we set up some temp variables in order to execute commands successfully from the terminal. Here we again configure the database username and password as well as the development environment key. Once these are set we can install the sake cli with the last command. If this has all worked correctly you can now re-build the database and flush the cache from the command line like so.

$ sake dev/build
$ sake / flush=all

Why not just use the browser? Well we are planning on automating a few things when we deploy to staging and production so we will eventually be running these commands via a post-install bash script which is essentially the same thing as what we’re doing here. Plus this also means we can now run our phpunit tests from the command line, a nice bonus.

End

Now that we have our local setup running we can confidently deploy our site to a DigitalOcean droplet/instance using Forge which we will do in my next article coming soon.