<![CDATA[iyWare]]>https://iyware.com/Ghost 0.5Wed, 04 Mar 2015 07:32:25 GMT60<![CDATA[Proxy Ghost with Homebrew Apache]]>I don't know if you're like me, but I'm lazy. As such I find adding ports to URLs to be quite tedius, so the fact that Ghost runs on a non-standard web port (:2368 as opposed to :80) bugs me. I know it's so that it can run in userspace and not colide with any other services, but still. So in this post we'll outline how to fix that.

This outline should work for any version of Apache but I'm using homebrew Apache 2.4 using the --with-privileged-ports switch to run on port 80 but Apache can be running on any port. If you're looking for how to install Apache I've written about it before here.

Enable Apache Mods

We need to enable 2 modules to allow Ghost to be proxied by Apache, mod_proxy (which enables proxy in general) and mod_proxy_http (which enables HTTP proxy specifically).

Navigate to your httpd.conf file located at /usr/local/etc/apache2/2.4/ and open it with your favorite editor.

Locate the lines for proxy_module and proxy_http_module and uncomment them by removing the # at the beginning of the line so that:

#LoadModule proxy_module libexec/mod_proxy.so


LoadModule proxy_module libexec/mod_proxy.so  

The snippet of that whole section should look something like this:

#LoadModule remoteip_module libexec/mod_remoteip.so
LoadModule proxy_module libexec/mod_proxy.so  
#LoadModule proxy_connect_module libexec/mod_proxy_connect.so
#LoadModule proxy_ftp_module libexec/mod_proxy_ftp.so
LoadModule proxy_http_module libexec/mod_proxy_http.so  
#LoadModule proxy_fcgi_module libexec/mod_proxy_fcgi.so
#LoadModule proxy_scgi_module libexec/mod_proxy_scgi.so

Your settings may vary depending on other modules that you have previously enabled, the important thing is that both modules are uncommented.

Add an Apache VHost

The next step is to define an Apache VirtualHost declaration essentially stating how the proxy should work. In the same httpd.conf document scroll all the way to the bottom and add the following snippet:

<VirtualHost example.com:80>  
     ServerName example.com
     ProxyRequests off
     ProxyPass /
     ProxyPassReverse / http:/

You'll need to replace example.com with the domain that you'd like to access Ghost from. If it's a localhost installation I highly suggest that you consider leveraging DNSmasq and mod_vhost_alias as well as this works well with that solution as well. If you want more information about localhost development I've written previously about it here.

My VHost looks like this:

<VirtualHost ghost.localhost:80>  
     ServerName ghost.localhost
     ProxyRequests off
     ProxyPass /
     ProxyPassReverse / http:/

Restart Apache

After you've finished editing your config file save it and then restart Apache with this command:

sudo httpd -k restart  

This will immediately restart your Apache server, even if you're using a LaunchAgent to start it on load or log in.

Now you can visit the URL you declared in your Apache configuration and access your Ghost instance. Please note that you can still visit it from the old URL as well, this doesn't block access, so if Apache ever breaks you can still get to Ghost.

https://iyware.com/proxy-ghost-with-homebrew-apache/a45f48e9-c05f-418e-8d1f-b2aa4d59988fFri, 13 Feb 2015 04:55:19 GMT
<![CDATA[Secure Ghost With Cloudflare SSE]]>Cloudflare is a great company for so many reasons. They provide DNS, CDN, and security all in one ‐ even on their free tier. If you're not using Cloudflare yet, you should consider it. In this post we'll look at integrating Cloudflare SSE (Server Side Exclusion) to your Ghost theme.

Cloudflare SSE is a cool feature that will simply strip out any content that you want at the time it's being served to the user, but only for users (IP addresses/ranges) that Cloudflare knows to be a threat. So instead of completely blocking a user from your site in the event of a false-positive, which happens, you can prevent them from seeing only certain info like contact info, or comments.

Enable SSE

If you don't have your domain set up with Cloudflare yet you'll need to that, which is beyond the scope of this article, but fairly straight forward. After you've added your domain to Cloudflare click the 'settings' wheel and select "CloudFlare Settings" from the drop-down menu.

Depending on the security profile that you selected when configuring your domain you may or may not have different options enabled, but you'll need to check that SSE is enabled anyways. On the CloudFlare Settings page click the "Security Settings" tab.

Scroll down the settings page until until you see the "Server side exclude (SSE)" option and enable it.

That's it to enable you SSE on your site, but it won't do anything until you add the SSE tags to your blog template.

Modify Your Ghost Theme

Now that SSE is enabled you need to decide what exactly you want to exclude. Generally I exclude any calls to the author theme API with either {{author}} or {{#author}}{{/author}} as well as components of the @blog API. Again this is up to you.

Exclude Your Personal Info

In your theme find the post.hbs file and find the {{#author}}{{/author}} section. In order to exclude it you're simply going to wrap it in HTML comments <!--sse--><!--/sse--> like this:


The default Ghost theme "Casper" should look like this:

        {{! Everything inside the #author tags pulls data from the author }}

            {{#if image}}
            <figure class="author-image">
                <a class="img" href="{{url}}" style="background-image: url({{image}})"><span class="hidden">{{name}}'s Picture</span></a>

            <section class="author">
                <h4><a href="{{url}}">{{name}}</a></h4>

                {{#if bio}}
                    <p>Read <a href="{{url}}">more posts</a> by this author.</p>
                <div class="author-meta">
                    {{#if location}}<span class="author-location icon-location">{{location}}</span>{{/if}}
                    {{#if website}}<span class="author-link icon-link"><a href="{{website}}">{{website}}</a></span>{{/if}}


Now whenever a visitor that CloudFlare deems a "threat" accesses a post that whole section will be excluded.

In post.hbs right below the {{author}} section you can remove the "share" links:

            <section class="share">
                <h4>Share this post</h4>
                <a class="icon-twitter" href="https://twitter.com/share?text={{encode title}}&amp;url={{url absolute="true"}}"
                    onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
                    <span class="hidden">Twitter</span>
                <a class="icon-facebook" href="https://www.facebook.com/sharer/sharer.php?u={{url absolute="true"}}"
                    onclick="window.open(this.href, 'facebook-share','width=580,height=296');return false;">
                    <span class="hidden">Facebook</span>
                <a class="icon-google-plus" href="https://plus.google.com/share?url={{url absolute="true"}}"
                   onclick="window.open(this.href, 'google-plus-share', 'width=490,height=530');return false;">
                    <span class="hidden">Google+</span>

Exlude Comments

If you've integrated comments into your site, you're likely doing so via JavaScript loading as Ghost (as of this writing) doesn't support native comments. So you can simply exclude those too (in post.hbs):

            <div id="disqus_thread"></div>
            <script type="text/javascript">
                var disqus_shortname = 'yoursite';
                (function() {
                    var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
                    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
                    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
            <noscript><p style="text-align:center;"><small>Please enable JavaScript to view the <a href="https://disqus.com/home/forums/yoursite/">comments powered by Disqus.</a></small></p></noscript>

And now a "threat" won't be able to access your comments via your site, of course I can't comment on the security of Disqus itself.

Exclude Anything

You can exclude anything you want from your theme templates, if you've added say a phone number or an email address that can be excluded too.

<!--sse-->danny &#64; iyware.com<!--/sse-->  

Exclude Post Content

Due to the ethos behind Markdown HTML comments are not supported within the Ghost editor so you won't be able to add SSE to the content of a post.


Using CloudFlare SSE is just one small step toward securing your Ghost blog but it should be an effective step. If you trust CloudFlare's analytics there's quite a bit of malicious activity out there (8% on my site).

My guess would be that this is mostly bots looking for vulnerabilities, but still they're out there trying to crawl your personal info, spam your comments, and generally ruin your day. So take a few minutes and add another layer of security to your blog.

https://iyware.com/secure-ghost-with-cloudflare-sse/34dfcafe-bca4-4c0b-b662-9dc65eb110ecThu, 12 Feb 2015 11:55:47 GMT
<![CDATA[Enable Accessibility Zoom on iOS 8]]>When OS X 10.10 was released one of the coolest new features was the ability to use your iOS device as an input 'camera' in QuickTime capture. This feature was a great addition for teachers at Tianjin International School - which had just rolled out its 1:1 iPad program in grades 3-6. Recording the screen of the iPad allowed the teacher to make demonstrative tutorials for their students.

I was asked to create an example of a screen-captured video, so I figured that I would kill two birds with one stone as another teacher had asked about how to record only a certain part of a screen or to show only a part of a screen. Instead of telling him how, I decided to create a video demonstrating how to enable, and use Zoom under iOS Accessibility settings.

This video was completely unscripted, and actually what you just saw was the 4th or 5th take as I did things like accidentally show my lastpass password or already have zoom enabled in the other takes.

https://iyware.com/enable-accessibility-zoom-on-ios-8/41a8e1e6-4c9f-415a-95b5-80839313244cFri, 06 Feb 2015 01:53:36 GMT
<![CDATA[Ghost on GitHub Pages With HTTrack]]>I've written a little bit about this before but this site is currently created using a local copy of Ghost and then converted to a static HTML site and deployed to GitHub pages. This post will overview how I do that.

First I'd like to say that I am aware of, and initially used, the tool Buster (get it, Ghost Buster), it's a fine little utility that basically functions as a fancy wget wrapper, but it has some issues. Issues that were preventing this site from being fully functional. In the author's defense- some of it may have actually been caused by homebrew's wget. So I decided to investigate an alternative toolset.

This bring us to HTTrack. httrack is an alternative website downloader (like wget) and is available for just about every platform. HTTrack has a lot of options, and if you need something fancy I recommend that you read the guide. That said, here's a step-by-step to deploy your Ghost blog to GitHub Pages.

Initialize Your Git Repository

I'll go over how to deploy to a user (or organization) page on Github Pages. If you need to do it for a project, just replace the user steps with the steps necessary for your repo, but generally it's just using the gh-pages branch instead of master.

Create the directory that you want to init as a git repository and open a terminal prompt in that directory. HTTrack will create some meta files (a cache and a log) and clone your site to something that is ASCII safe. So for example if your Ghost blog is running on http://localhost:2368 which is the default, from your output directory HTTrack will create:

  • ./hts-cache/
  • ./hts-log.txt
  • ./localhost_2368/

with ./localhost_2368/ containing your static site. So you'll need to initialize your git repository there. I have mine in ~/Sites/htdocs/iyware.com/localhost_2368/

mkdir ~/Sites/htdocs/iyware.com/localhost_2368/  
cd ~/Sites/htdocs/iyware.com/localhost_2368/  
git init  
git checkout -b master  
git remote add origin git@github.com:[USER NAME]/[USER NAME].github.io.git  

A few notes here:

  1. Replace [USER NAME] with your user name, or organization name.
  2. Replace -b master with -b gh-pages if this is a project page.
  3. You don't have to use the SSH repository path, you can use HTTPS but you'll be prompted to authenticate every time you update your site. If you don't have SSH configured, I strongly suggest you do it.
  4. If you have things in your repository you might want to git fetch and git merge them- but you probably don't. Even if you blow them away locally, they'll still be in your commit history.

Initializing your Git repository is obviously a one time task.

Generate Your Initial Static Site

One of the dowsides of HTTrack to wget is that it seems to be a bit slower. One of the benefits that HTTrack has over wget is its cache. That means that the first crawl of your site is going to be considerably slower than using wget (or buster) but updates to your site should be relatively quick. Luckily we can begin the process with a single command. The directory from where you run this command doesn't matter:

httrack http://localhost:2368/ -O /PATH/TO/OUTPUT/FOLDER/ -c128 -I0 -#p "+sitemap*"  

Let's break this command down a little bit. The first parameter is the path to the site you want to crawl, e.g. your ghost blog. The second parameter -O is the path to the output folder. You'll obviously need to replace /PATH/TO/OUTPUT/FOLDER/ with your actual path (NOT the localhost_2368 folder but it's parent. Mine is /Users/dannywahl/Sites/htdocs/iyware.com/. -c128 means that we scrape with a rate of 128 simulatneous connections. -I0 tells httrack not to make a custom / file for the output and -#p is detailed output (verbose).

The "+sitemap*" tells httrack to explicitly to grab any links to a sitemap. Ghost automatically generates a sitemap, but it uses @blog.url to populate them so httrack will not scrape them by default- so we need add this command. This might be dangerous if you blog content contains a lot of links to other sites' sitemaps. You might consider changing it to your Ghost blog url, e.g. "+https://iyware.com/sitemap*" to only grab your site's sitemap.

If it all goes right you'll see the progress of your site scrape in the terminal output like this:

Mirror launched on Wed, 04 Feb 2015 12:06:11 by HTTrack Website Copier/3.48-19 [XR&CO'2014]  
mirroring http://localhost:2368/ +/sitemap* with the wizard help..  
Thanks for using HTTrack!  

Now you should have a fully viewable static site in /localhost_2368/ that is ready to deploy to GitHub Pages.

Deploy to GitHub Pages

In terminal navigate back to the /localhost_2368/ directory and commit at push your changes to GitHub:

git add .  
git commit -m "Blog updated"  
git push -u origin master  

Now you should be able to visit your GitHub page and see the static version of your site.

Update Your Static Site

The next time you update your Ghost blog and you need to update your static site navigate to the folder containing /localhost_2368/ and run this command:

httrack -iC2  

This command updates your site and reuses your cache, you don't need to do a full re-scrape. It will delete items that are remotely deleted, update changed assets, and it will add new files. Then push to GitHub again.

Wrap it in a Shell Script

After the initial static site is created and the git repository is created it's quite a simple and repetitive task to update and deploy, so you can stick it all in a simple shell script like this:

# working path
cd /Users/dannywahl/Sites/htdocs/iyware.com/

# update
httrack -iC2

# replace favicon
mv favicon.ico localhost_2368/favicon.ico

# deploy
cd /Users/dannywahl/Sites/htdocs/iyware.com/localhost_2368/

current_time=$(date -u +"%Y-%m-%d %T")  
git add .  
git commit -m "Blog update at $current_time"  
git push -u origin master  
echo "Deployed to github"  

I have that saved as a utility called bustit so now I can simply type bustit in terminal to deploy my updated blog.

It's not nearly as robust as buster but it very easily could be- it's the same wrapper for a site downloader and a git interface, I just didn't make it modular or extensible, maybe I will in the future.


If you're not happy with the look of your sitemaps it's because sitemap.xsl is missing- you'll need to visit http://localhost:2368/sitemap.xsl and save that to the static site root (next to sitemap.xml). I can't figure out how to get httrack (or buster) to download this- but you only need to do that the first time. This file is not necessary.

If you're used to using buster it automatically adds a README file to your site. You'll need to create that manually if you want it. Same for CNAME and robots.txt.

Well that's all there is to it. Create an initial static copy, update that copy when you update Ghost, and push to github. Maybe in the future I'll rewrite this as a robust application because I think it would be cool to integrate it with AppleScript Folder Actions so that whenever the Ghost sqlite database is updated this triggers, but for now this works just fine.

https://iyware.com/host-your-ghost-blog-on-github-pages/593db4b6-71ff-4e7b-8324-b7bc45332744Wed, 04 Feb 2015 05:41:23 GMT
<![CDATA[Start Your Ghost NPM Server on Mac Log In]]>If you follow the Ghost documentation to install and start Ghost then you probably just open terminal, cd to your install directory and type npm start, this leaves you with a terminal window always opened. There's a few simple steps you can take to get node, and therefore Ghost, running on log in on OS X.

The first is to install the NPM package forever:

npm install forever -g  

Next you should copy this .plist and save it to ~/Library/LaunchAgents/node.forever.ghost.plist.

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">  
<plist version="1.0">  
        <string>[PATH TO GHOST]/ghost/index.js</string>

replacing [PATH TO GHOST] with the path to your Ghost installation. Finally load the launchAgent:

launchctl load ~/Library/LaunchAgents/node.forever.ghost.plist  

Now you can head to your ghost URL (default http://localhost:2368) and start blogging- node forever will start ghost every time you log in to your Mac.

https://iyware.com/start-your-ghost-npm-server-on-mac-log-in/f79a2079-9d27-47ea-aaca-33a653703298Tue, 03 Feb 2015 00:39:00 GMT
<![CDATA[Add Search to Your Ghost Blog with Swiftype]]>If you're moving from WordPress to Ghost (or really any other blog platform) one of the things you've probably noticed is sorely missing is search. The Ghost devs know it too. Adding search is on the roadmap, unfortunately it's backlogged and they're currently looking for a Search Lead developer. So I wouldn't count on native search coming for a while- like maybe 0.7.

In the meantime, it's not that hard to integrate a third-party service into your Ghost site, and in this post I'll show you how to do it using Swiftype. What is Swiftype?

Swiftype is a hosted software service that eliminates the need to create your own search software from scratch, making it possible for any website owner or mobile app developer to add great search to their product.

Sounds exactly like what we need!

Sign up for Swiftype

Head on over to the Swiftype pricing page page and scout out the plan that you want. Personally I'm just using the free plan.

Another option available is to activate Smart Errors as an app in your Cloudflare dashboard if you're using Cloudflare. This will also give you the added benefit of having a better 404 page than this: ;> <

After you've enabled smart errors head to your swiftype dashboard and create what they call a "Search Engine" which is actually a search context (generally a domain- but you can have it search multiple domains).

Option 1: Add Swiftype Search with Cloudflare

After you've created your account you can very simply integrate Swiftype search into your site by heading back to Cloudflare and enabling the Swiftype Search app. This will provide the most basic integration- a simple 'search' button gets injected into your pages. It's crude, but it works.

Option 2: Add Swiftype to Your Ghost Theme

If you're looking for a richer integration then you will need to install the Swiftype code to your Ghost theme template. There are a variety of ways to do this- you could add a search icon to your footer, or to your default.hbs template file if you want every page to be searchable. However, I'll be showing you how to make a static search page with an embedded search form.

Go back to the Swiftype page and inside your search engine choose "Install" from the toolbar.

Download Swiftype Autocomplete & Search

Before you install the integration code you'll need to download the packages. You could do it with git so you can upgrade in the future (make sure you initialize as a submodule if ghost is a git module too!). Or you can simply download them:

Notice that these are jQuery plugins. It shouldn't be an issue as {{ghost_foot}} will insert jQuery into your theme- and that should be a part of your theme!

Add Autocomplete & Search to your Theme Folder

Navigate to /ghost/content/themes/[your theme]/assets/js/ where /ghost/ is your ghost installation directory and [your theme] is your theme name (default is casper). If your theme doesn't have a js folder you can create one. Move the JavaScript files from the Swiftype downloads into this folder. Note that you don't need all the files, only the .js files:

  • jquery.swiftype.autocomplete.js
  • jquery.ba-hashchange.min.js
  • jquery.swiftype.search.js

Now do the same thing for the CSS files but in the /assets/css/ folder:

  • autocomplete.css
  • search.css

Optionally you can concatenate the CSS to the end of the Ghost CSS file screen.css, which is probably a better idea because it will save you 2 HTTP requests and a little bit of bandwidth.

Add Autocomplete & Search to your Theme Template

Now that you have added the files to your Ghost /assets/ folder you can insert them into your theme template files. Navigate to /Ghost/content/themes/[your theme]/ and open up default.hbs. The reason that we need to edit the default template is because the function {{ghost_foot}} outputs the jQuery call. If you call Swiftype before jQuery is loaded (e.g. in page.hbs) you'll get an error.

In default.hbs find the line with {{ghost-foot}} and add the following:

        <script src="{{asset "js/jquery.swiftype.autocomplete.js"}}"></script>
        <script src="{{asset "js/jquery.ba-hashchange.min.js"}}"></script>
        <script src="{{asset "js/jquery.swiftype.search.js"}}"></script>

        <script type="text/javascript">
                engineKey: "XXXXXXXXXX"
                engineKey: "XXXXXXXXXX",
                resultContainingElement: "#st-results-container"

Notice that we're using the {{asset }} API so you don't have to worry about the full path, Ghost will build it for you. You'll need to replace engineKey: "XXX" with your engine key, found on your Swiftkey dashboard.

If you want search to be available on every page (you'll still have to add a search form to default.hbs then leave the code as is. If you intend to only have search available on a specific search page then you can wrap the above code in {{is}} brackets:

    {{#is 'page'}}
        <script src="{{asset "js/jquery.swiftype.autocomplete.js"}}"></script>
        <script src="{{asset "js/jquery.ba-hashchange.min.js"}}"></script>
        <script src="{{asset "js/jquery.swiftype.search.js"}}"></script>

        <script type="text/javascript">
                engineKey: "XXXXXX"
                engineKey: "XXXXXX",
                resultContainingElement: "#st-results-container"

This will tell Ghost to only include the javascript on 'page' types. Unfortunately at this point the API isn't robust enough to target a specific page, but this will save you a few HTTP requests on archives and posts.

Create a Search Page

If you are intending to create a static search page and not to integrate search into your theme template then head to your Ghost dashboard and create a new post. I titled mine "Search":

Click on the 'settings' wheel and change the post into a static page. You can also adjust the slug if you'd like.

If you want to provide information to your users you can add content to the page, for example I added all the tags I use on my blog. There's no easy way to do this I just don't have many tags, if you want something more dynamic, I suggest you add it to your template.

Finally it's time to add the search form to your page. Even though Ghost uses Markdown, remember that any HTML is valid Markdown, so you can directly create a form:

    <input type="text" id="st-search-input">
    <button type="submit">Search</button>

<div id="st-results-container"></div>  

Publish your page and you should be ready to go (assuming Swiftype has finished indexing your content). Just make sure that the form ID and the results ID match your jQuery selectors- they should if you just copied from here.

That's it for the integration. You can always go back and edit the CSS to better match your site. You can also add the renderFunction to your JavaScript to control how Swiftype outputs its results.

There you have it, you now have an indexed and searchable blog.

https://iyware.com/add-search-to-your-ghost-blog/761b70ab-c126-4d8c-9b4c-3794af4bc8c9Mon, 02 Feb 2015 05:39:55 GMT
<![CDATA[Don't Use .dev For Development]]>A while back I wrote about setting up DNSMasq for your local development using the .localhost TLD. Unfortunately during my research I came across a lot of people advocating setup using the hypothetical .dev domain under the guise of "DevOps". I hate to say it but this is a bad idea, so consider this post a public service announcement.

Generic TLDs

In the beginning there was .com and it was good. In 1984 the IETF released RFC-920 which specified a list of generic top level domains including:

  • .gov
  • .edu
  • .com
  • .mil
  • .org
  • .net (added later)

in addition to country code TLDs. We're probably all familiar with these as they have been the primary domains since then, and upto even a few years ago it would have been safe to mess around with other top-level domains on your local DNS or intranet. But the issue now is that the IANA has started registering more and more TLDs.

Do you remember how pissed you were (or your sysadmin was) when your intranet was set to the same domain as your company website and no one could reach the website because your AD server was serving an IIS error page? I do. Everyone that's advocating using .dev is potentially doing the same thing.

New hotness: .whatever

According to gTLD.com:

The release of more than 1,000 new gTLDs (generic Top Level Domains) is the biggest shake-up since the internet began.

Dot place, dot trend, dot industry, dot meme – dot allsorts. They’re up for grabs.

You read that right, we went from a list of 6 TLDs to over 1,000 [vegeta-its-over-1000.gif]! Why?

One of ICANN's key commitments is to promote competition in the domain name market

Money, duh.

Don't Do What Donny Dev Does

Ok, that's all well and good but what about .dev? Turns out it's already available as a TLD. Oops. Oh, look, it's administered by Google too- so you know it's super serious. Now, I want you go to back to a DNSmasq article where the author advocates routing all .dev traffic to and replace .dev with .com and then tell me how smart of an idea it is. Awesome DevOps d00d!

Reserved Top Level Domain Names

Now that I've dumped all over DNS hacking it's only fair that I suggest an alternative. The answer is simple: RFC-2606. You see after the IETF released the list of Generic TLDs they released a list of Reserved TLDs. Reserved meaning that they will never be valid TLDs in a global DNS sense. This is similar to how 192.xxx.xxx.xxx and 10.xxx.xxx.xxx are reserved IP addresses.

The list of Reserved TLDs contains 4 different domains specifically for "Testing & Documentation Examples":

  • .test
  • .example
  • .invalid
  • .localhost

A subsection of the document also declares reserved second-level addresses:

  • example.com
  • example.net
  • example.org

so feel free to continue to use nobody@example.com for instructions for users.

Using Reserved TLDs

RFC-2606 goes on to describe recommended usage cases for the four reserved TLDs but they're pretty vague, saying things like:

".invalid" is intended for use in online construction of domain names that are sure to be invalid and which it is obvious at a glance are invalid.

In order to clarify when and how these Reserved TLDs should be used another specification was released, RFC-6761: Special-Use Domain Names. This document clarifies how a Reserved TLD should be used and how it should be handled by DNS servers. This document specifies seven categories of use:

  1. users
  2. applications
  3. APIs & libraries
  4. DNS cache servers
  5. Authoratative DNS servers
  6. DNS server operators
  7. DNS registries/registrars

Probably as a developer you only care about the first 4, and maybe number 5, essentially, how does your local network handle these domains? Go read the specification and the outline for each Reserved TLD.

Recommended Setup

Now I'll outline my recommendation to you for using the Reserved TLDs. There's a bit of wiggle room in here, by design of the RFCs.

.localhost TLD

I recommend (as does RFC-6761) that you use .localhost to only look at your local loopback address: The specification explicitly states that APIs and libraries should point at the loopback address already, so if you were to try to point it anywhere else you might end up with unexpected results. In addition everyone knows that localhost === so from a people perspective, this just makes sense.

.invalid TLD

This is just my personal recommendation, but it is acceptable according to the specification. Your local DNS cache server should be configured to point .invalid domains back to development machines. This should NOT be configured on a client machine. If you're using static IPs or DHCP reservations (better, IMO) then you can easily set this up.

The reason for this is that you can serve the same site (hopefully) from .localhost or .invalid. If you have a cool new feature that you want to show someone or want someone to play with then they can view it- but they know that it requires your dev machine to be up and running. This is great for face-to-face development. Here's an example:

I spend a while working on dannywahlmbp.localhost/widget and I want some feedback. I send a coworker a link to dannywahlmbp.invalid/widget and we can sit down and check it out together. If I shut my laptop- it's gone. This isn't necessarily obvious in the naming but it can be documented.

.test & .example TLDs

You could decide to use either of these interchangably but I recommend using .test first. My recommendation is that .test is where you move code to be tested- this is the first step toward production. So my code base would be moved off of my development machine and onto a server that's accessible by all machines on our local network 24/7. Maybe you start running code coverage here- maybe it's even an upstream repository. Of course you'll need to configure your local DNS server.

The final step is to use .example. My recommendation is that you use this for code that's nearly ready to go live. You could also use this to tie together multiple locations and even use an internet-facing IP address, knowing that it's not going to be resolved by global DNS servers.

After that you're ready to go live with trusty ol' .com. Hacking on domains and DNS can be fun and a lot of people have built some really cool tools- but it's important that you use them appropriately too. Once .dev domains start becoming prevalent I imagine that there will be a few people kicking themselves for mapping it to their local machine, but hey, that's part of learning!

https://iyware.com/dont-use-dev-for-development/b3eb1e13-08a6-4a51-bd5e-ee5aae947505Tue, 27 Jan 2015 02:39:52 GMT
<![CDATA[Migrate from WordPress to Ghost]]>I've been using WordPress as the backend of my website for about 4 years now. I've been a fan of its simplicity, its usability, and its extensability. I didn't want to spend a lot of time developing a site- I just wanted to blog but as time went on my site was hacked, hacked again, botched an upgrade, received 20,000+ spam comments, and I realized I was spending more time as a WordPress administrator than as a WordPress author. In a nutshell I was tired of my WordPress backend looking like this:

So in my usual fashion I decided to pick something new that I knew nothing about and dive into that. I picked Ghost because at its core it's what I wanted; a blogging platform. Sure it's young, sure it's missing some features I want (or at least I thought I did until I actually started migrating) but that's OK with me. It's extremely functional as a blog.

In this post I will detail how I migrated, and how you can migrate too, from WordPress to Ghost. I'm going to assume that you have a copy of Ghost already up an running somewhere. I did mine on my MacBook but you could have a host somewhere too, however, if that's the case, you'll probably need FTP access at some point.

Export Your WordPress Content

There is quite a list of things that need to be exported from WordPress if you want to do a full site migration which may include:

  • Comments
  • Uploads (images, .zip, text files, etc...)
  • Users
  • Posts
  • Pages
  • Tags and Categories

At the time of this writing Ghost doesn't support categories, so we're not going to bother with attempting to add functionality. If you want categories, please read the Ghost documentation on the subject first and then try this hack or make sure that your WordPress post category is also a tag.

Export Your WordPress Comments

The first thing to do is export your WordPress comments. Unfortunately at the time of this writing Ghost does not support native comments, but there are many commenting solutions available that can be easily integrated with Ghost. For this tutorial we will be using Disqus to host our comments.

Install Disqus WP Plugin

There is a plugin available from the WordPress plugins page called "Disqus Comment System" that needs to be installed. This is the official Disqus plugin for WordPress and as such it works pretty well. Simply install and activate the plugin.

Add a Disqus Site

If you don't already have a Disqus account sign up for one, if you do have an account sign in and click on "Add Disqus to Your Site".

Fill out the 3 fields on the form with the information for your site and click "Finish Registration". For example my site name is just 'iyWare' and the URL is 'iyware.disqus.com'. However, if you have a marketing identity or branding guideline, follow that. Frankly I'm not sure how many people stumble up a blog by navigating the Disqus homepage so your conversion based on this information alone will probably be pretty low.

After you click "Finish Registration" you will be taken to the "Install" page under "Settings". Since we've installed the Disqus plugin on WordPress we don't need to do a separate installation. But we will need to come back to this page to get the "Universal Code" (a.k.a. JavaScript) when it's time to install it in Ghost.

Migrate Comments to Disqus

Head back to your WordPress Dashboard and navigate to Comments in the sidebar. If you have any spam comments you'll want to delete those so that they don't accidentally get imported into Disqus. This shouldn't happen, but I had a few get in there.

Now click on Comments → Disqus in the sidebar. If your site was has not been configured yet you will need to authenticate to Disqus and type in your site's identifier, which is the "Unique Disqus URL" that you chose earlier.

Once you've authenticated you can scroll down to the section titled "Import and Export" and find the label "Export Comments to Disqus". Click the "Export Comments" button.

Shortly you should get an email from Disqus saying that comment migration has begun.

The linked site will show you the progress and tell you that it might take up to 24 hours to complete. My site only too 3 or 4 hours, but I don't have too many comments (maybe 100?).

Delete comments from WP

After your comments are fully migrated to Disqus go back to WordPress → Comments → All Comments and delete all comments on your site. Please don't delete your comments before the export is done (this should be obvious). At this point I should mention that you might want to backup your WP DB first ;)

The reason we're going to delete the comments from the WordPress instance is because even though Ghost doesn't support comments yet the Ghost WordPress export tool DOES export your comments in its JSON file. So despite the fact that the documentation says

Ghost does not, and will not deal with comments.

the Ghost exporter DOES export the comments. Since I had over 1,800 spam comments they all got added to the JSON file and caused a real headache later- just to be ignored by Ghost. So do yourself a favor, and delete the comments.

After this you might want to temporarily disable commenting on your WordPress site or fully integrate Disqus because if people continue adding comments to WP after you've done the migration, they will be lost when you kick over to Ghost.

Finally you can head to your Disqus dashboard and view your site's comments there.

Export Your WordPress Media (Uploads)

According to the Ghost documentation

Ghost has very rudimentary media handling at the moment, and currently grabbing images from WordPress and plucking them into Ghost is tedious. For that reason it might be a good idea to sign up for a free Cloudinary account...

However, migrating your media (anything you've uploaded to WordPress) is not actualy that difficult of a task and there are many good reasons to not host your images on another cloud account.

The reason that they advocate this is because the cloudinary plugin will transfer all your files to their server and then rewrite the URLs in your wordpress database so, for example


would get rewritten as


which Ghost would have no trouble with when it is running as example.com instead of WordPress. But it's very very simple to continue to serve your images directly from Ghost.

Find Your WordPress Media Files

In WordPress head to Settings → Media and scroll down until you find the "Uploading Files" Section. Take note of where your files are uploaded to.

It does not matter if you have "Organize my uploads into month- and year-based folders" checked or not. However, IF you do have it enabled you will be able to get even richer Ghost integration. If you don't please don't change it- as this will only affect new media files.

Download Your WordPress Media Files

Once you know where the files are open up your FTP client and download the entire containing folder. So in the example above I would be downloading /assets/ folder. If yours is the default you will be downling the /uploads/ folder NOT /wp-content/. If you accidentally download /wp-content/ it's OK- we'll just pull /uploads/ from inside of it later.

If you don't know your FTP credentials you'll need to check with your hosting provider about how to access. If you don't have FTP you could try using the "downML" WordPress plugin to download your media library directly from WordPress. I haven't personally tried this plugin.

Optionally Optimize Your Files

This isn't a necessary step, but since Ghost doesn't have rich multimedia handling you could take this opportunity to properly minimize all of your media. I simply dragged the entire assets folder that I downloaded onto the ImageOptim application.

You might notice that for every file uploaded to WordPress, e.g. "1.png" there are multiple versions in the media folder. This is because the way WordPress works is to create multiple sizes of each image and store them on the server, which you can then insert into a post/page/whatever. Ghost will only use one version but it doesn't hurt to have them all there- it will only use more disk space, not more bandwidth.

After the image compression finishes you'll probably find it's quite worth it. I was able to shave 35.8MB off of 138.7MB for a total of 29.9%. Not too shabby.

If you want to further optimize your images you could batch resize them to the max-width that you theme will output. I don't necessarily recommend this as you might want a header image or change themes at some point in the future. Not too mention that a user could still want to see the image at full size which simply isn't possible if you've manually resized it.

Move Your WordPress Media to Ghost

WordPress will store your images by default in the relative path /wp-content/uploads/ and if you've enabled year and month sorting then it will add YYYY/MM/ to the path.

Ghost will store your images by default in /content/images/YYYY/MM/ which is a similar URL structure.

Now you can take your WordPress uploads folder and just drop it into the /ghost/content/images/ folder on your install. You can see that I renamed mine to "import" (which might not have been the best idea) but if an image failed I wanted to know it was from WordPress.

If you were using year and month folders in WordPress then you can merge the folder with the Ghost folder for a completely seamless migration. However, since I haven't tested this You might need to be careful for name collisions when adding media in the future. This should NOT be an issue because importing our WordPress post content (covered later) should add the media references to the sqlite database.

For my site any posts imported from WordPress will serve images from /content/images/import/image.png and any new posts created in Ghost will serve from /content/images/YYYY/MM/image.png with no ill effect.

We'll still have a little more work to do to point the post data to the new URL but that will be covered later on. For now your physical assets are located in Ghost. You can test this by creating a new post in Ghost, inserting an image, and pointing the path to an imported image:

Export Your WordPress Data

Now that we've taken care of comments and media it's time to move on to actual WordPress data. There are quite a few steps to this process but they are all fairly simple.

Publish Your WordPress Drafts

I recommend that if you have any WordPress drafts that you publish them. The Ghost Export tool should handle them, but it definitely handles published materials. You can always unpublish once your content is imported into Ghost.

Delete Your WordPress Trash

If you have any trash on WordPress, delete it. This includes Posts, Pages, Comments, etc... If you don't want it then you definitely don't want it to migrate. For now less is more. If You have a backup of your DB you can always go back and get it later.

Convert Your WordPress Post Types

One of the most popular features in WordPress is custom post types like "Portfolio", "Gallery", or whatever. However, Ghost only supports two types- which are the default WordPress types- "Post" and "Page". The Ghost Exporter might export other types but it won't know how to map them, so you'll need to do it manually.

The "Post Type Switcher" WordPress plugin is a great utility for switching individual posts. To change a post type after you have installed this plugin simply go to edit the post (page, or whatever) and click "Edit" under the "Post Type" section in the "Publish" widget, choose Post or Page, and click "Update".

If You have a lot of posts to convert you might consider using the "Convert Post Types" plugin instead. I have not personally used this plugin.

Add WordPress Categories as Tags

As we mentioned earlier, Ghost does not support Categories- only tags. As a result when we export WordPress data later categories will be lost. If you wish to preserve a category-like metadata in your post you must add the category as a tag in your WordPress posts.

In this example the category "Android" will not be preserved when exporting to Ghost. I need to manually add the category as a tag.

Now when I export my WordPress data the tag "Android" will still be associated with this post. At the time of this writing Ghost 0.5.8 has only rudimentary tag support, so you can't set "Android" as the first or primary tag or anything like that- tags are simply all listed alphabetically.

If you have many posts I suggest using the WordPress plugin "Categories to Tags Converter" which will automate this process for you. I have not personally used this plugin.

Delete Your WordPress Users

The Ghost export plugin will export all users and associated metadata, so if you have "Anyone can register" enabled under Settings → General in your WordPress instance you will need to check your users page to see how many users you have.

Navigate to Users → All Users and see how many users are registered on your site. My site had over 18,000 users registered with the default role "Subscriber". If you don't have too many users you can filter the role and delete them.

However, if you have more than a few hundred users it will be more effective to delete the users directly from the database. You can connect to phpMyAdmin or directly to your database and execute the following SQL commands.

FROM wp_users  
(SELECT post_author FROM wp_posts
UNION SELECT user_id FROM wp_comments);

FROM wp_usermeta  
WHERE user_id NOT IN  
(SELECT ID FROM wp_users);

commands courtsey of vtxyzzy at WordPress Support Forums

This will execute two commands, the first will delete all users from the wp_users table who are not post authors. Caveat: if you have an admin account that you use to change settings and a separate account to post with you WILL lose your admin account. Quickly publish something with your admin account or modify the SQL command.

The second command will delete the metadata for users from the wp_metadata table for users that aren't found in the wp_users table.

Again, please make sure you have a backup of your database first. After you execute the SQL commands you can refresh the All Users page on WordPress and only post authors should be left- in my case just my account.

Install WordPress Ghost Exporter

I've mentioned exporting your WordPress data to Ghost a few times so far and now we're going to get ready to do just that. The final WordPress plugin you'll need to install is the oddly named "Ghost" which takes all of WordPress data, and converts it to a format that can be imported to your Ghost instance. Once you've installed and activated the plugin head to Tools → Export to Ghost in WordPress.

Export Your Ghost JSON File

Now that the Ghost exporter is installed and activated it's time to actually export the data. You can safely ignore the "Things to Keep in Mind" section as it mostly covers things that we've gone over in this post. Scroll to the bottom of the page and click "Download Ghost File"

WordPress will think for a minute and you should eventually get a download prompt for a .json file

Save the file to your local machine and you're done with WordPress for now.

Fix Ghost JSON Export Errors

The potential for something going wrong is pretty high at this point. There's a lot of data manipulation and conversion going on and the amount of variables from system to system can be pretty extreme. Your best bet is to check the WordPress to Ghost Support Forum at WordPress and see if anyone else has had the same error as you. If they haven't, report it there and see if someone can help you. That said here are the errors I ran into.

Error: White Page of Death

After clicking "Download Ghost File" my WordPress just hung for a minute and turned to a white page. I enabled debugging on my WordPress instance by editing my wp-config.php and changing

define( 'WP_DEBUG', false );  


define( 'WP_DEBUG', true );  

After enabling debugging I ran the export again and the page output the error:

Fatal error: Maximum execution time of 30 seconds exceeded

Essentially, it was taking the exporter longer to build the download file than my server was set to allow. Fortunately this is something that can be easily changed in a variety of ways:

I ended up settling on the script level so that it only changed it for this export tool. To do this, in WordPress head to Plugins → Editor and choose "Ghost" from the menu on the top right then select the file ghost/class-ghost.php.

Search the file for the definition of the function populate_data() at the begging of the function add an ini_set(). Your code should now look like this:

     * Gets the raw data in an array that will be later turned into glorious escaped json format
     * so Ghost can gobble it up
     * @return array                        everything we need, but in an array
    public function populate_data() {
            ini_set('max_execution_time', 0);
        if ( $this->garray !== null ) {

        $this->_safe_url( 'http://google.com' );

        $this->garray = array();

        // preps the structure

        // attaches metadata

        // get the users

        // attaches tags

        // populates posts

setting the value of max_execution_time to 0 means 'unlimited'. Now you might run into a memory error, but similarly you can allocate extra memory.

Error: Invalid Content Encoding

After I was able to extend the execution time I started getting an "Invalid Content Encoding" error when trying to generate the file. It would gripe about being unable to set headers after they'd already been set. The specific code it was referring to was again in class-ghost.php

        header( 'Content-Description: File Transfer' );
        header( 'Content-Type: application/octet-stream' );
        header( 'Content-Disposition: attachment; filename='.$filename );
        header( 'Content-Transfer-Encoding: binary' );
        header( 'Expires: 0' );
        header( 'Cache-Control: must-revalidate' );
        header( 'Pragma: public' );
        header( 'Content-Length: ' . filesize( $filedir . '/' . $filename ) );

Here ghost is trying to build and serve the download file, not a webpage, but what I discovered was that with debugging on PHP would throw an error

Notice: Undefined index: description in /wp-content/plugins/ghost/class-ghost.php on line 394  

saying that it couldn't grab some data from WordPress. When PHP threw the error it would set the headers to a webpage to display the error, then Ghost Export would try to set them to be a file download, and we'd end up with a content encoding error.

There are a couple of ways to fix this. The first (and easiest) is to disable debugging. This will prevent PHP from throwing a notice, that value will silently fail (not a problem) and you'll get your export file. The second is to comment out the link looking for the description index so the failure doesn't occur. You still don't end up with the data but it's not a big deal. Again in class-ghost.php edit the user_meta section to comment out bio as shown below:

        foreach ( $users as $user ) {
            $user_meta = get_user_meta( $user->ID );

            $this->garray['data']['users'][] = array(
                'id' => $this->_safe_author_id( $user->ID ),
                'slug' => $user->user_login,
                //'bio' => substr( $user_meta['description'][0], 0, 199 ),
                'website' => $this->_safe_url( $user->user_url ),
                'created_at' => $this->_get_json_date( $user->user_registered ),
                'created_by' => 1,
                'email' => $user->user_email,
                'name' => $user->user_nicename,
Error: 404 Page Not Found

Occassionally you will end up at a page that was served by the WordPress front end instead of getting your JSON file or an error. This is, I believe (though I haven't investigated) related to the script execution time and memory limit. Increasing those as described above should resolve it.

Error: Corrupt JSON File

You won't necessarily know that you have a corrupt file until you go to import it and it fails. But a fast way to check if your download file is vaild is to either open it in a text editor and check, or pass it to a website like JSONLint. A corrupt file would look something like this:

If you look closesly you can see that where it falls apart is actually on a user account. When I first tried to export my WP data to JSON the file size was over 4MB. 4MB of text is quite a lot of data. After removing comments, users, drafts, etc... the final file size was 498Kb. So if you're getting a botched JSON file please make sure that you've followed this tutorial so far- otherwise investigate where it's failing and if you can't fix it submit a report at the WordPress Support Forums for Ghost.

Manipulate Your WordPress Data

If you follow the import instructions of the Ghost plugin you would simply import your JSON file into Ghost, but if you do that now you'll spend more time correcting data in Ghost than you need to. We're simply going to massage the data to be a better fit in Ghost.

Remove WordPress Shortcodes

One of the major drawbacks of WordPress is shortcodes. Years ago they were revolutionary, [gallery] was awesome! But once theme devs started adding their own shortcodes to create a type of "vendor lock-in" then they got really frustrating.

So Hopefully you didn't have any shortcodes in your text. I did. When you import them into ghost the raw code will show up. For example, if you had a shortcode called [button] that outputs a link element with the class button, in Ghost you'll just have:

[button href="#"]Text[/button]

Which probably you just don't want. Now, fortunately shortcodes are just a bit of code that become a different bit of code which means we can reverse engineer them pretty easily. Open your JSON file in your favorite text editor, I'm using Coda 2 but you can use sed or whatever you want.

You're going to have to do a little bit of personalization here but I'll give you an overview of what I did in my editor.

Note: be sure to escape your JSON strings!

Replace [button] Shortcode

In My text editor I simply searched for [button (not there's no closing bracket) to evaluate how I used it in my source and then built a replacement string:

[button link=\u201d
<a href=\"


Replace [tabgroup] Shortcode

The second shortcode I used was was one that took some inline code and formatted it for jQuery tabs. Mostly all I needed to do to replace them was a NULL string





The output was simply the inline markup.

Edit WordPress URLs

After the shortcodes we need to edit wordpress URLs to work with our Ghost paths. Remember that our media has been moved from /wp-content/uploads/ (default) or /assets/ to /content/images/. Since the paths have moved we need to reflect this in our posts. Again we simply use find and replace. This time we need 3 find and replaces starting with fully qualified path down to relative path. The reason being that if you replace the relative path first then the fully qualified path will fail.

My site was served from http://www.iyware.com so we'll start with http://www.iyware.com/assets/ again we need to escape the JSON strings.




In addition to images/videos this will work for ALL media, so if you have text, html, or zip files they will work too. This again, in my opinion, makes it a better option than migrating your images to cloudify.

Optionally Edit WordPress Media URLs

Once we've rewritten the paths for our media to be relative to our Ghost instance we need to edit the source for our images to always use the original image. Go back to your WordPress instance and note the thumbnail sizes for images under Settings → Media.

When you insert an image into a WordPress post you generally have to choose an image size and WordPress will insert the resized image with the dimensions appended to it inside of an anchor element linked to the original. So, for example an image called "post.jpg" that was inserted into a WordPress post at medium size (using the settings above) would generate the following HTML:

<a href="/wp-content/uploads/post.jpg">  
  <img src="/wp-content/uploads/post-500x500.jpg" />

Essentially all we need to do is strip off the suffix for each media size. Make sure that you use your WordPress settings, and if you've ever changed the settings that you use what your old settings were too.




Now the output HTML would look like this:

<a href="/wp-content/uploads/post.jpg">  
  <img src="/wp-content/uploads/post.jpg" />

Import Your WordPress Data to Ghost

Now that our JSON file has been edited to better work with it's ready to be imported to Ghost. Fortunately this is about the easiest part of the process. On your Ghost instance head to Settings → Labs → Import.

Click "Browse" and then select your edited JSON file and click "Import". When the import is finished you should be able to browse your posts on the back-end and the front-end with working images and links to other media resources (.zip, .html, etc...).

Integrate Disqus Comments with Ghost

Now that all the other data has been imported the only thing left to do is add comments back to the site. Since Ghost doesn't natively support comments we won't be importing them from Disqus, but rather integrating the Disqus JavaScript applet into our theme template.

Configure Disqus settings

Before we integrate the code there are a couple of settings worth checking at the Disqus website. In the Disqus Admin Panel click on the "settings" tab and scroll to "Community Settings".

It's worth reviewing some of the settings regarding moderation and guest posting. I personally enabled guest posting as I don't necessarily expect someone to sign up for an account at a third party website to comment on my site.

Insert Disqus JavaScript Code

Now you can integrate the Disqus code into your site. The Ghost documentation has a great overview of doing that: "How Do I Add Disqus to my Ghost Blog?"

I highly recommend that you simply follow that tutorial and then return here- no need to reinvent the wheel!

Once you have completed that I do have one change that I make to the integration to increase user experience. In your post.hbs where you inserted the Disqus JavaScript find this line:

 <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>

and replace it with a link to your site's Disqus forum instead of to the Disqus homepage.

<noscript>Please enable JavaScript to view the <a href="https://disqus.com/home/forums/iyware/">comments powered by Disqus.</a></noscript>  

Obviously you'll need to replace /iyware/ with your site's URL, but this will allow users with JavaScript disabled on your site to go directly to your Disqus forum- where they'll get another error if they hava JS disabled there too- but that's a Disqus problem, not yours.

Optionally Move WordPress to Another Domain

If you intend to run Ghost on the same domain as your WordPress instance was originally running you'll need to move your WordPress to a different domain. Personally I moved my WordPress instance to the old. subdomain. To do this you can simply head to Settings -> General in WordPress and change both "WordPress Address (URL) and "Site Address (URL)" to the new domain that you wish to use.

Without doing a massive find and replace in your database a lot of things will probably break- like image URLs but the site will be largely functional.

Please note that as soon as you change this setting your WordPress site will stop serving at the old URL and only serve from the new URL so if you need to configure the submain with your DNS provider make sure to do that too. I'm using CloudFlare for DNS so I simply added an "A Record" for "old" pointing to the IP address where WordPress is hosted.

If you've already published Ghost to your domain and lost access to WordPress you can change it in the database directly. Here's an example of the two fields to change in phpMyAdmin but changing directly in SQL will work too.

Field siteurl in table wp_options

Field home in table wp_options

Final Steps

Hopefully by this point all the heavy lifting is done. If you're intending to setup Ghost as your main domain you can do do that as WordPress is now out of the way.

Of course I strongly recommend you do a thorough content audit before you go live. It took me about a day and a half to audit my 60 or so posts and clean them up a little. The biggest issue I found was that the MarkDown conversion stripped anything that came after two less-than signs (<<) for whatever reason. Fortunately there were only two instances in my site where this happened and I was able to repace the text by copying it straight from WordPress to the Ghost editor.

The only other thing I did was remove superflous <span> tags in the post body which were used for element positioning in WordPress and no longer necessary in Ghost.

Next time I'll write about some of the other things I've done to customize my Ghost instance which I think are rather nice, but for now that's the end of this tutorial.

Happy blogging!

https://iyware.com/migrate-from-wordpress-to-ghost/8884244a-654c-4a95-b0f0-24fcae4a8472Mon, 19 Jan 2015 07:49:03 GMT
<![CDATA[Moving]]>Update: We're mostly back up now.

Crazy things happening right now. You may notice this website looks very broken. Well, that’s just because I’m in the process of moving from a hosted version of WordPress to something a lot more nerdy:

A local ghost exported to static HTML via buster (a lá jekyll) and then pushed to github pages.

Until then, this site’s gonna look a little broken, oh well :(

https://iyware.com/moving/d80a404b-304a-41ac-afc0-fcf64e978200Fri, 09 Jan 2015 13:46:30 GMT
<![CDATA[Nexus5 Stock Lollipop Root How-To]]>I just got my new Nexus 5, upgrading from my now almost 3 year old Galaxy Nexus. I got a good deal on the Galaxy Nexus because I bought it well after the Nexus 4 was released and the price had dropped significantly. Unfortunately because of Google’s marketing strategy with the Nexus 6 that didn’t happen this time; the prices for the Nexus 5 have remained pretty steady. But I needed (wanted) a new phone, so I bought it.

Unfortunately the Nexus 5 I received shipped with Android 4.4.x and I knew that 5.0 was available. I didn’t really want to do an upgrade immediately after setting up the phone, so I decided it would be best to flash the factory image since I have no data to lose. When I went to the Nexus 5 section of XDA-Developers I was a little disappointed. I just wanted a rooted, stock Lollipop ROM, but didn’t find one. In addition a lot of the toolkits etc… seemed a little dated. The last thing I wanted to do was download toolkits and then be manually updating components (roms, recoveries, root, etc…) so I decided to just do it myself. It was all pretty easy, so here’s the process I used, getting all the files directly from the sources, you can copy and paste these commands into your terminal and have your phone up and running pretty quickly- depending on your internet connection speed.

brew install wget  
brew install android-platform-tools  
cd ~/Desktop  
mkdir nexus5  
cd nexus5  
wget http://download.chainfire.eu/636/SuperSU/UPDATE-SuperSU-v2.37.zip -O supersu.zip  
wget http://techerrata.com/file/twrp2/hammerhead/openrecovery-twrp- -O recovery.img  
wget https://dl.google.com/dl/android/aosp/hammerhead-lrx21o-factory-01315e08.tgz -O lollipop.tgz  
tar zxvf lollipop.tgz  
cd hammerhead-lrx21o  
adb reboot-bootloader  
fastboot oem unlock  
sh flash-all.sh  
# if it reboots, go back to bootloader #
adb reboot-bootloader  
cd ..  
fastboot flash recovery recovery.img  
fastboot reboot  
adb push supersu.zip /sdcard/0/  
adb reboot-bootloader  
# now boot recovery and install superSU zip, done!

The process is pretty simple: download root, TWRP, and the stock lollipop ROM. Flash the ROM, flash recovery, reboot the device, push the root zip, and reboot to fastboot. You’ll still need to manually flash SuperSU zip in TWRP, but that’s it. No messing with broken rapidshare links, no messing with outdated toolkits, just a nice rooted ROM.

https://iyware.com/nexus5-stock-lollipop-root/43e6a11f-85a0-4114-8b8a-67c237bd26bbFri, 12 Dec 2014 08:41:50 GMT
<![CDATA[Yosemite MAMP Homebrew Dev Setup, Part 2]]>In my previous post OS X 10.10 Yosemite, Apache, MySQL, PHP 5.6, (MAMP) Homebrew Dev Setup I covered a lot. Like a lot a lot, but by the end of the post all we had really accomplished was installing the basic building blocks of a development server, namely: Apache, MySQL, and PHP. In this post we’ll take those components and turn them into a sweet development workflow.

Updating Brew

Everyone loves running brew update && brew upgrade just to see what’s been updated. It’s a seriously busy repo, but it’s easy to forget to do and then your versions languish. Fortunately for us Mark Kalmes wrote a LaunchAgent plist for us called, aptly, brewupdate. We’re going to grab the latest version and install it.

curl https://raw.githubusercontent.com/mkalmes/brewupdate/develop/net.mkalmes.brewupdate.plist > ~/Library/LaunchAgents/net.mkalmes.brewupdate.plist  
launchctl load ~/Library/LaunchAgents/net.mkalmes.brewupdate.plist  

The first line downloads the latest version straight from github to the user-space LaunchAgents folder. The second line loads it. You can edit the file and change when it updates, the default is 11 AM (local time zone). I changed mine to 8 AM because I like to start the day with a fresh brew (ha ha.)

Just don’t forget to run brew upgrade yourself occasionally. You could make another LaunchAgent by changing the parameter from update to upgrade, but I really don’t like unattended upgrades as much as updates.

Apache 2: DocumentRoot

That’s a clever title, you see we’re running Apache HTTPD 2, and at the same time this is part 2 of the Apache section… Anyways, at the end of Part 1 Apache was running on port 80 like a champ. The DocumentRoot is set to something ridiculous like /usr/local/var/www/htdocs which is pretty freaking inconvenient.

First we need to create our /Sites folder and we’ll still keep our web documents inside of that folder.

mkdir ~/Sites ~/Sites/htdocs  

Then edit your /usr/local/etc/apache2/2.4/httpd.conf file and change the DocumentRoot directive, as well as accompanying Directory declaration to match your new htdocs location:

DocumentRoot "/Users/USERNAME/Sites/htdocs"  

Obviously replace USERNAME with your username’s folder name. Then inside of that directory declaration we’re going to change some of the default Apache values to be more permissive:

Options All MultiViews  
AllowOverride All  

This is given with the usual caveat that OF COURSE you would never default everything like this on a production server- but on a development server there’s generally not much security implication (unless you’re playing with live data!). My full DocumentRoot section looks like this:

DocumentRoot "/Users/dannywahl/Sites/htdocs"  
  # Possible values for the Options directive are "None", "All", 
  # or any combination of: 
  # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews 
  # Note that "MultiViews" must be named *explicitly* --- "Options All" 
  # doesn't give it to you. # # The Options directive is both complicated and important. Please see 
  # http://httpd.apache.org/docs/2.4/mod/core.html#options 
  # for more information. 
  Options All MultiViews 
  # AllowOverride controls what directives may be placed in .htaccess files. 
  # It can be "All", "None", or any combination of the keywords: 
  # AllowOverride FileInfo AuthConfig Limit 
  AllowOverride All 
  # Controls who can get stuff from this server. 
  # Require all granted

Now, restart your httpd daemon:

sudo httpd -k restart  

If you didn’t use --with-privileged-ports when you installed Apache then you don’t need root. Now when you visit http://localhost you should just get a blank directory listing instead of the default “It Works!” page. Let’s put a test page there. First let’s create a sub-directory in our /Sites folder called phpinfo.localhost (more on the . later).

mkdir ~/Sites/phpinfo.localhost  

Now let’s create a page that calls the phpinfo(); function and place it in that folder. if you don’t know about phpinfo() it’s a simple function that outputs a page that shows you information about your PHP configuration.

printf ‘<?php phpinfo(); ??>‘ >> ~/Sites/htdocs/phpinfo.localhost/index.php  

Now you should be able to visit http://localhost/phpinfo.localhost/ and see the overview of your PHP configuration. That’s it for now for Apache, let’s move on to MySQL.


In the first post we simply installed MySQL and set a root password. It’s good to have a password because some authentication APIs require one, but we’re not necessarily using it for security (otherwise we wouldn’t be using root), so we’re going to save our authentication in a my.cnf options file. Homebrew provides us with a sample file, but it’s not symlinked by default so we’re going to copy it to an available configuration folder and since MySQL is running in user-space, we will override the configuration at the user-space level:

cp -v $(brew –prefix mysql)/support-files/my-default.cnf ~/.my.cnf  

Now we have our config override, let’s have it auto-save our credentials.

cat > ~/.my.cnf  

Replace “PASS” with your root MySQL password. If you have special characters in your password, which you shouldn’t, add double quotes around the password. Now you can connect to mysql without having to manually authenticate.

mysql -u root  


Finally we’re going to install everyone’s favorite database manager, phpmyadmin. Again, we’ll be using homebrew to install it.

brew install phpmyadmin  

After a successful installation you will get a caveat about adding a directory alias to your httpd configuration to enable phpmyadmin, but we’re not going to do that we’re going to symlink it to to our ~/Sites directory.

ln -s /usr/local/share/phpmyadmin ~/Sites/htdocs/phpmyadmin.localhost  

This will create a symbolic link in your webroot called phpmyadmin.localhost that takes you to /usr/local/share/phpmyadmin (which itself is a symlink to the Cellar). The benefit here is you don’t need to restart Apache, and again we’ll go over it later but the .localhost is important.

Now that phpmyadmin is installed, we need to configure it. First we’ll import the phpmyadmin linked tabled configuration storage via commandline:

mysql -u root -e 'CREATE DATABASE IF NOT EXISTS `phpmyadmin`;'  
mysql -u root phpmyadmin  

Once the tables are imported open your phpmyadmin config file located at /usr/local/etc/phpmyadmin.config.inc.php and you're going to do a few things:

  1. generate a blowfish secret
  2. Set your controluser and controlpass (I use root again)
  3. Uncomment the storage database and tables section

In the end your blowfish and servers configuration sections should look something like this:

$cfg['blowfish_secret'] = 'y=PUY@-GleE+~%OiGQVz#)N4I)@Q%Ug_Jr[DxF';
/* * Servers configuration */
$i = 0;
/* * First server */
/* Authentication type */
$cfg['Servers'][$i]['auth_type'] = 'cookie';
/* Server parameters */
$cfg['Servers'][$i]['host'] = '';
$cfg['Servers'][$i]['connect_type'] = 'tcp';
$cfg['Servers'][$i]['compress'] = false;
$cfg['Servers'][$i]['AllowNoPassword'] = false;
/* * phpMyAdmin configuration storage settings. */
/* User used to manipulate with storage */
// $cfg['Servers'][$i]['controlhost'] = '';
// $cfg['Servers'][$i]['controlport'] = '';
$cfg['Servers'][$i]['controluser'] = 'root';
$cfg['Servers'][$i]['controlpass'] = 'root';
/* Storage database and tables */
$cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';
$cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
$cfg['Servers'][$i]['relation'] = 'pma__relation';
$cfg['Servers'][$i]['table_info'] = 'pma__table_info';
$cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
$cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
$cfg['Servers'][$i]['column_info'] = 'pma__column_info';
$cfg['Servers'][$i]['history'] = 'pma__history';
$cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
$cfg['Servers'][$i]['tracking'] = 'pma__tracking';
$cfg['Servers'][$i]['designer_coords'] = 'pma__designer_coords'; 
$cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
$cfg['Servers'][$i]['recent'] = 'pma__recent';
$cfg['Servers'][$i]['favorite'] = 'pma__favorite';
$cfg['Servers'][$i]['users'] = 'pma__users';
$cfg['Servers'][$i]['usergroups'] = 'pma__usergroups';
$cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding';
$cfg['Servers'][$i]['savedsearches'] = 'pma__savedsearches';
/* Contrib / Swekey authentication */
// $cfg['Servers'][$i]['auth_swekey_config'] = '/etc/swekey-pma.conf';
/* * End of servers configuration */

You might notice that I changed my host to "" from "localhost". By default /etc/hosts should resolve localhost to so it shouldn't trigger a DNS lookup, but by changing it to an IP address you'll never have that happen because there are ways to inadvertently send lookups to your machine DNS for localhost. Again, this is not necessary. After saving your phpmyadmin configuration you should be able to visit the page at http://localhost/phpmyadmin.localhost


Dnsmasq is a lightweight DNS/DHCP server which can be installed and serve DNS for addresses that aren't in the global namespace. We'll be installing dnsmasq via homebrew and configuring it, along with Apache to serve sites on reserved TLDs.

brew install dnsmasq  

Once dnsmasq is installed we need to copy a configuration profile, edit it, and then load the daemon. First we'll copy a provided configuration:

cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf  
printf '\naddress=/localhost/' >> /usr/local/etc/dnsmasq.conf  
sudo cp -fv /usr/local/opt/dnsmasq/*.plist /Library/LaunchDaemons  
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist  

Effectively we are telling dnsmasq to resolve all .localhost domains to and setting it to run the daemon at the machine level (with Apache). So you can now try to ping any .localhost domain and it will resolve to

ping -c 1 thisdoesntexist.localhost  
PING thisdoesntexist.localhost ( 56 data bytes  
64 bytes from icmp_seq=0 ttl=64 time=0.029 ms

--- thisdoesntexist.localhost ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss  
round-trip min/avg/max/stddev = 0.029/0.029/0.029/0.000 ms  

Obviously trying to visit http://thisdoesntexit.localhost won't work because Apache isn't configured to handle requests to that address, yet. There are two different ways to configure this, the first is to create a vhost entry for every domain you want to use, the second is to enable mod_vhost_alias- which is what we'll do now.


Mod_vhost_alias is an Apache module that let's you do pattern matching from a request URL to a directory structure, there are lots of cool examples at the documentation site- but the one we want is pretty straight forward. Open your httpd.conf file (in /usr/local/etc/apache2/2.4/) and uncomment the mod_vhost_alias module

LoadModule vhost_alias_module libexec/mod_vhost_alias.so  

then in terminal we'll configure vhost_alias_module:

printf '\nUseCanonicalName Off\nVirtualDocumentRoot /Users/USERNAME/Sites/htdocs/%%0' >> /usr/local/etc/apache2/2.4/httpd.conf  

And finally restart the httpd daemon:

sudo httpd -k restart  

The end result is that now every folder that ends in .localhost in your ~/Sites/htdocs/ folder is accessible as a FQDN. Go back to your browser and now you can visit http://phpinfo.localhost and http://phpmyadmin.localhost If you create another folder inside of htdocs that ends in .localhost it will be available in your dev environment as a standalone domain. Here's a couple of examples of how that could be useful:

Say you want a standalone domain of wordpress:

git clone git@github.com:WordPress/WordPress.git ~/Sites/htdocs/wordpress.localhost  

What about multiple branches of drupal on standalone domains? Let's make http://drupal6.localhost and http://drupal7.localhost

git clone git@github.com:drupal/drupal.git --branch 6.x --single-branch ~/Sites/htdocs/drupal6.localhost  
git clone git@github.com:drupal/drupal.git --branch 7.x --single-branch ~/Sites/htdocs/drupal7.localhost  

As another example, how about a single domain with multiple moodle instances in sub-directories like http://moodle.localhost/26 and http://moodle.localhost/27

mkdir ~/Sites/htdocs/moodle  
git clone git@github.com:moodle/moodle.git --branch MOODLE_26_STABLE --single-branch ~/Sites/htdocs/moodle/26  
git clone git@github.com:moodle/moodle.git --branch MOODLE_27_STABLE --single-branch ~/Sites/htdocs/moodle/27  

Wrapping Up

So there you go! Mac OS X with brewed versions of important stuff like openssl and git, Apache 2.4, MySQL, and PHP 5.6 and a killer setup using modvhostalias and dnsmasq to provision .localhost domains on the fly. Of course there are a million ways to dev from here, but this is a pretty solid foundation.

If you need help with educational technology in your institution, from strategic planning, to implementation, to technical support contact us and we can help get you on the path to success.

https://iyware.com/yosemite-mamp-homebrew-dev-setup-part-2/9a641183-9430-493a-9294-b85129cefb68Tue, 28 Oct 2014 19:39:48 GMT
<![CDATA[OS X 10.10 Yosemite, Apache, MySQL, PHP 5.6, (MAMP) Homebrew Dev Setup]]>Update:

A quick update, this was originally written for Yosemite Beta 1, XCode 6 Beta, and PHP56 RC1. Now that the official versions of all those are out, a lot of this post is unnecessary because, well, stable software is much more… stable. Who knew? Anyways, here’s a tl;dr for the stable version:

# Install Homebrew
xcode-select --install  
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"  
brew doctor  
# Tap Repos
brew tap homebrew/dupes  
brew tap homebrew/versions  
brew tap homebrew/homebrew-php  
brew tap homebrew/apache  
# Verify 
brew update && brew upgrade  
# Macintosh 
brew install git  
brew install openssl  
ssh-keygen -t rsa -C "email@address.invalid"  
ssh-add ~/.ssh/id_rsa  
# Apache 
sudo apachectl stop  
sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist 2>/dev/null  
brew install httpd24 --with-privileged-ports --with-brewed-ssl  
sudo cp -v /usr/local/Cellar/httpd24/2.4.10/homebrew.mxcl.httpd24.plist /Library/LaunchDaemons  
sudo chown -v root:wheel /Library/LaunchDaemons/homebrew.mxcl.httpd24.plist  
sudo chmod -v 644 /Library/LaunchDaemons/homebrew.mxcl.httpd24.plist  
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.httpd24.plist  
sudo httpd -k start  
# MySQL 
brew install mysql  
ln -sfv /usr/local/opt/mysql/*.plist ~/Library/LaunchAgents  
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist  
mysqladmin -u root password "NEWPASSWORD"  
brew install php56 --homebrew-apxs --with-apache --with-homebrew-curl --with-homebrew-openssl --with-phpdbg --with-tidy --without-snmp  
chmod -R ug+w /usr/local/Cellar/php56/5.6.2/lib/php  
pear config-set php_ini /usr/local/etc/php/5.6/php.ini  
printf '\nAddHandler php5-script .php\nAddType text/html .php' >> /usr/local/etc/apache2/2.4/httpd.conf  
perl -p -i -e 's/DirectoryIndex //DirectoryIndex index.php //g' /usr/local/etc/apache2/2.4/httpd.conf  
printf '\nexport PATH="$(brew --prefix homebrew/php/php56)/bin:$PATH"' >> ~/.profile  
# Dev Stuff 
brew install composer  
brew install behat  
brew install node  
npm -g install grunt  
npm -g install shifter  
brew tap danpoltawski/homebrew-mdk  
brew install moodle-sdk  

Original Post:

It’s that time of the year again. Apple will soon release a "new" OS (10.10 Yosemite) and in celebration I’ll format my hard drive and reinstall from scratch and then slowly start reconfiguring my machine for development purposes. So I figured, if I’m going to do it- I might as well document it too. Because, to be honest, I kinda forget what I’ve done… Over a few posts we will look at every step of setting up a Moodle development machine from services configuration through installing Moodle and we’ll document all the tools and commands that are needed along the way. In part one we’ll go over installing Homebrew, git, Apache, MySQL, and PHP. In part two we’ll go over configuring Apache, MySQL, and git, as well as how to use the MDK, git, and JIRA to contribute your code. This post is good for any developer that’s using PHP- not just Moodle. So if you want to set up a Mac to develop in WordPress, Drupal, or whatever- this will get you started.

Starting Point

Sorry, we won’t be starting with the unboxing of a new Mac, because I didn’t have a $2,000 budget for this post. And in fact we won’t even be starting with the OS setup, which is OK because if you’re using 10.7 or newer, then you’re okay. If you’re still running 10.6 I’m going to assume that it’s because Apple locked you out of updates because your machine is too old, jerks. Do yourself a favor, buy a new PC.

Here’s what you’ll need: Internet access (how are you even reading this?), administrative privileges (the account doesn’t have to be admin), and accounts at a couple of websites (or the ability to sign up). I’ll also need your credit card number and the last four digits of your social security number, just leave them in the comments. Just kidding, don’t.


If you spend any amount of time in the Moodle forums you’ll run across all kinds of cool acronyms. Usually the glossary auto-linker will fill your head with some cool new knowledge, but for the sake of thoroughness we’ll go over a few of them here. You may see people talk about their WAMP or MAMP or LAMP server. Generally this describes their server configuration.

If somebody says they’re using WAMP that means that their server is running on "Windows". Lamp would be "Linux" and MAMP – Mac. Though technically it should be "OS X" but OAMP is hard to say. Generally most web softwares don’t need a specific platform to run on, a website can run on any operating system. Generally. Stupid ASP.

The second letter, "A", which in the examples above is consistent means that the web service running on the server is "Apache". There are a variety of web service applications available including IIS, Nginx, lighttpd. Generally the software (i.e. Moodle) doesn’t mandate a specific web server software either. Now it could be that the software (i.e. Moodle) depends on some other software (i.e. PHP) that is not compatible or available on that web server. But again, this isn’t usually a problem.

The Third letter defines the database type. Moodle supports 4 different databases out of the box: MySQL, PostgreSQL, Oracle, and MSSQL. Because Moodle has a sweet database driver ($DB) the database that you use doesn’t really matter- in terms of developing on your machine. If you’re trying to pick a database for a large, live server, it probably does matter. So if you see LAMP, MySQL or LAPP, PostgreSQL. Users of other DB types aren’t so clever so they’ll just say boring stuff like "I’m using Windows Server 2008 RC2 with IIS 7 and MSSQL." instead of WS08RC2IIS7MSSQLFTW, which in my opinion is cooler AND easier to understand.

Finally, the last "P" stands for "PHP" which stands for "PHP Hypertext Preprocessor" which stands for "PHP Hypertext Preprocessor Hypertext Preprocessor" which stands for "All work and no play makes Jack a dull boy." Anyways… You’ll need this. Moodle won’t run with out it, and without a specific version as mentioned in the release notes of the Moodle version you download.

With so many options, how do you choose? Like I said in the title, "Meh". I’m going to assume you’re developing on a Mac, so that rules out Windows and Linux. Apache is by far the most popular web server and is included with OS X. MSSQL and Oracle don’t run on OS X. You need PHP.

So your only real options are MySQL or PostgreSQL, and we’ll cover that in a bit, but first let’s start with "M".

Macintosh (OS X)

We need to do a bit of configuring of our Mac before we dive into setting up services. Mostly this will involve downloading some applications.

Get git

Moodle development is managed using git. git in a nutshell lets you track changes of code from lots of users. If you’re going to be doing Moodle development you MUST use git. Fortunately OS X comes with git included, but you can’t use it. Open up terminal and enter the following command:

which git; git -version  

and you’ll end up with a return something like this

which git  
git -version  
xcode-select: note: no developer tools were found at '/Applications/Xcode.app', requesting install. Choose an option in the dialog to download the command line developer tools.  

with a prompt to download XCode command line tools:

what git?

Go ahead and download it. It’s also worth it to install Xcode as well. For this post you will need both Xcode 5 which you can download from the Apple Developer site and the Xcode 6 beta. I recommend that you rename Xcode 5 from "xcode.app" to "xcode5.app" and Xcode 6 from "Xcode-beta.app" to "Xcode.app" because most of our toolchain will be using the utilities in Xcode 6. Once Xcode and the command line tools are installed we can now use the version of git included with OS X- however it’s a little dated, and we’re going to install a newer version of git from somewhere else.

When it comes to installing the git binary we have a few different choices. First, we can install the official git version from git-scm.com, second we can install a bundled version of git with a different application like github for Mac or git-tower (both of which we’ll go over later), or third you can use a third-party package manager to install a copy of git for you. This last option is the one we will be using.


Homebrew is self-styled "The missing package manager for OS X" and they really deliver. If you’ve spent any amount of time working on an actual Unix or Linux machine you probably both appreciate OS X’s "Unix roots" and really dislike what Apple has chosen to trim back. Without a package manager like pac-man, apt, or the like, OS X is really limited. Homebrew solves that in a really elegant way, so let’s install it. Open up terminal and copy and paste this single command:

ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"  

This command has three requirements, one you need to have administrator access or you’ll not be able to install the program as the /usr/ directory is protected. Two you’ll need internet access as the command downloads the script from github and then passes it to ruby to run. If you don’t have internet access you’ll see an error like this:

ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"  
curl: (6) Could not resolve host: raw.github.com  

Three, you will need to have the XCode command line utilities installed. You should have already install them. If you don’t have the command line tools installed then you will be prompted to install them:

==> Installing the Command Line Tools (expect a GUI popup): 
==> /usr/bin/sudo /usr/bin/xcode-select --install 
xcode-select: note: install requested for command line developer tools Press any key when the installation has completed.  

After that you should be good to go. Just run brew doctor as recommended to make sure everything’s okay.

ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"  
==> This script will install: 
/usr/local/bin/brew /usr/local/Library/... 
Press RETURN to continue or any other key to abort  
==> /usr/bin/sudo /bin/mkdir /usr/local 
WARNING: Improper use of the sudo command could lead to data loss or the deletion of important system files. Please double-check your typing when using sudo. Type "man sudo" for more information. To proceed, enter your password, or type Ctrl-C to abort.  
==> /usr/bin/sudo /bin/chmod g+rwx /usr/local 
==> /usr/bin/sudo /usr/bin/chgrp admin /usr/local 
==> /usr/bin/sudo /bin/mkdir /Library/Caches/Homebrew 
==> /usr/bin/sudo /bin/chmod g+rwx /Library/Caches/Homebrew 
==> Downloading and installing Homebrew... 
remote: Counting objects: 188543, done.  
remote: Compressing objects: 100% (51551/51551), done.  
remote: Total 188543 (delta 135844), reused 188510 (delta 135819)  
Receiving objects: 100% (188543/188543), 37.53 MiB | 395.00 KiB/s, done.  
Resolving deltas: 100% (135844/135844), done.  
From https://github.com/Homebrew/homebrew * [new branch] master -> origin/master HEAD is now at 881c231 Fix up dep directories before activating ENV extensions  
==> Installation successful! 
==> Next steps Run `brew doctor` before you install anything Run `brew help` to get started 
brew doctor  
Your system is ready to brew.  

If you see "Your system is ready to brew." then we can move on to the next step. At this point you do have OS X’s version of git installed. To see your specific version run git --version in terminal:

git --version git version 1.9.3 (Apple Git-50)  

That’s the version on my system, which is the bleeding-edge version of git that Apple is shipping on a beta operating system and according to github releases, it was released (at time of this writing) about two and a half months ago. Not to mention it’s a full major release behind. git-scm.com has the latest version of git available as 2.0.3. I like to be a little more up to date than that. If we search the homebrew repository we can see the latest version available is 2.0.2:

brew search git  
bagit           git-extras      git-number      giter8  
bash-git-prompt git-flow        git-open        gitfs  
easy-git        git-flow-avh    git-review      github-release  
geogit          git-flow-clone  git-sh          gitslave  
git             git-ftp         git-ssh         legit  
git-annex       git-gerrit      git-tf          libgit2  
git-archive-all git-imerge      git-tf-2.0.2    libgit2-glib  
git-cal         git-integration git-tig         magit  
git-cola        git-latexdiff   git-tracker     stgit  
git-crypt       git-multipush   git-url-sub     topgit  
git-encrypt     git-now         gitbucket  

At the time of this writing 2.0.2 is only 13 days old and 2.0.3 is only 5 days old. That’s a much better timeframe for updating. So let’s install the homebrew version of git using brew install git

brew install git  
==> Downloading https://www.kernel.org/pub/software/scm/git/git-2.0.3.tar.gz
######################################################################## 100.0% 
==> make prefix=/usr/local/Cellar/git/2.0.3 sysconfdir=/usr/local/etc CC=clang C 
==> make CC=clang CFLAGS= LDFLAGS= ==> make clean ==> make CC=clang CFLAGS= LDFLAGS= 
==> Downloading https://www.kernel.org/pub/software/scm/git/git-manpages-2.0.3.t
######################################################################## 100.0% 
==> Downloading https://www.kernel.org/pub/software/scm/git/git-htmldocs-2.0.3.t 
######################################################################## 100.0% 
==> Caveats The OS X keychain credential helper has been installed to: 
The 'contrib' directory has been installed to:  
Bash completion has been installed to:  
zsh completion has been installed to:  
==> Summary /usr/local/Cellar/git/2.0.3: 1328 files, 31M, built in 69 seconds

So you’ll see a couple of things- first we actually installed 2.0.3 and that’s because homebrew isn’t a repository of binaries, it simply manages them, the install actually comes from the main git repository at git-scm (kernel.org). In addition to the vanilla git binary we also get bash completion and man pages. Sweet! Go ahead and check the version again, you may have to quit your terminal session and start a new one.

dannywahlmbp:~ dannywahl$ git --version git version 2.0.3  

That’s pretty much it for git, now we can move on to github, where we’ll be managing our source code.


Github is where all the cool kids are storing their code these days. Well, everybody except Moodle and Mahara. But at least they provide copies of their work at github- Moodle at least, I didn’t check Mahara (too lazy to open a new tab…).

Basically Github is "the cloud" for source code versioning. You create a repository there, "clone" it to your machine, edit, and push your changes back to the server. If you’re new to git there are lots of resources available to you. We’ll cover a few basics in this post but mastering it is beyond the scope. I suggest you check out:

Sorry, that list isn’t nearly as fun as anything Buzzfeed has put together, and full disclosure- I learned git just by Moodle development so I can’t vouch for any of the above resources… (then why did I even recommend them? Stupid!) Anyways…

If you do not already have an account at github you will need to go sign up. Once you’ve cleared the hoops of verifying email, etc… log in. Github should provide you with a nice step-by-step tutorial of setting up you Mac. But we’ll do it again, just to be sure!

Log in and head over to your profile, fill in any info you want and then click on the SSH Keys tab on the left. SSH basically lets you securely communicate with the server where you are storing your code. Right now we’ll go through the process of generating an SSH key and storing it on github.

Check for existing SSH Keys

Open Terminal again, now if you’re on a fresh OS install you won’t have to do this but you can. Type the following command:

ls -lsa ~/.ssh  

ls is the command to ‘list’ everything in a directory. ~ is short code for your home folder, on a Mac that’s /Users/UserName (by default). / is a folder inside of your home folder. .ssh is the name of the folder. On Unix systems a folder that starts with a ‘.’ will be hidden- no need for chflags! So we’ve put together a command that says "show the contents of the hidden folder ‘.ssh’ inside of my home folder". And that is probably shockingly similar to the AppleScript that you would use to do that too.

If terminal gives you this return:

ls -lsa ~/.ssh ls: /Users/dannywahl/.ssh: No such file or directory  

then we need to create the directory with another command (just copy and paste):

mkdir ~/.ssh; chmod 700 ~/.ssh  

This will do two things, create the ssh directory, and then make sure only you can access it. You can learn more about Unix permissions here.

If terminal gives you this return:

ls -lsa ~/.ssh/  
total 16  
0 drwx------ 4 dannywahl staff 136 Jul 31 16:41 .  
0 drwxr-xr-x+ 18 dannywahl staff 612 Jul 31 16:36 ..  
8 -rw------- 1 dannywahl staff 1766 Jul 31 16:41 id_rsa  
8 -rw-r--r-- 1 dannywahl staff 398 Jul 31 16:41 id_rsa.pub  

Then you already have an SSH Key. IF you created this key and are sure of its security you can use it with github, skip to the "Adding your SSH Key to Github" section below. Otherwise you can create a new one.

Creating an SSH Key Pair

When you create an SSH key you actually create two keys, a private key and a public key. You keep the private key and you can share the public key with others. This allows them to receive messages and ensure it is from you, but they can’t pretend to be you and send out messages on your behalf. Back in terminal we’re going to create a new RSA SSH key set. Run the command:

ssh-keygen -t rsa -C "email@address.invalid"  

This will kick off a "Wizard" of sorts and walk you through creating your key pair. First you need to specify where to save the new keys, the default is your ~/.ssh folder, so just push enter.

Generating public/private rsa key pair.  
Enter file in which to save the key (/Users/dannywahl/.ssh/id_rsa):  

Note that you can specify a different name, as you can have multiple SSH key pairs. Though for most instances a single pair is all that is necessary.

Next it will ask you for a key passphrase, which is optional. I strongly suggest that you add a passphrase for both added security and the fact that other SSH applications (not github) require a passphrase to function properly and if you want to reuse your key pair you will need a passphrase. Type your passphrase press Enter and type it again to make sure it matches.

Enter passphrase (empty for no passphrase):  
Enter same passphrase again:  

After that your key pair will be save and you will be given some information about it. It will look something like this:

Your identification has been saved in /Users/dannywahl/.ssh/id_rsa.  
Your public key has been saved in /Users/dannywahl/.ssh/id_rsa.pub.  
The key fingerprint is:  
The key's randomart image is:  
+--[ RSA 2048]----+ 
| =A+ . | | *=* . |
| +.=oE . | | . o= 
= | | .S . | | . | 
| | | | | | 

The finger print and randomart allow you to verify that a public key copy is a real copy. Please note that these are NOT my real public key credentials. On that note you should NEVER share your private key with anyone.

Activating Your Key

Now that you have created your key you need to tell the operating system to use it. We will use the ssh-add command to load your key. Enter this command, and then type your key passphrase. If you didn’t enter a passphrase, simply press Enter.

ssh-add ~/.ssh/id_rsa  
Enter passphrase for /Users/dannywahl/.ssh/id_rsa:  
Identity added: /Users/dannywahl/.ssh/id_rsa (/Users/dannywahl/.ssh/id_rsa)  

Copy Your Public Key to Github

The final step is to give github a copy of your public key. If you don’t have the SSH Keys page open you will need to navigate there and click "Add SSH Keys". You can store more than one key with github, which is useful if you have multiple devices that you do development from.

Back in terminal you have one more command to run, simply copy and paste (unless you renamed your key):

pbcopy < ~/.ssh/id_rsa.pub  

This command copies the contents of your public key to your clipboard. You can also open your key in a text editor and copy the contents but this command prevents both omitting a character and accidentally editing the key. Switch back to github and paste in the "Key" field. If there’s a blank line at the end, that’s ok- don’t erase it. Remember we want the contents of the key exactly.

You’ll want to give you key a title so that if you ever have a security issue at github you can see exactly which machine accessed or changed which repositories. Your page should look something like this:

Add an SSH Public Key

Click the "Add key" button and you’ll be taken back to the SSH Keys page, where your new key will be listed. If you look at terminal you’ll notice that the fingerprints match.


For now we’re done with github. So far we’ve set up homebrew, installed git, created an SSH key pair, and added a copy of our public key to github. Next we’ll start setting up the "server" side of our development machine, starting with Apache.


The Apache HTTPD server is the most popular web server in the world. And in fact Apache is included with OS X by default. Just copy and paste this command into terminal:

apachectl -v  

Mine says:

Server version: Apache/2.4.9 (Unix) Server built: Jun 28 2014 12:11:10  

Which is actually not that bad. Apache is one of those softwares that updates rather slowly, 2.4.10 was released on July 10th. Since Apache is included in OS X we can just use that right? Sure, just visit this link: http://localhost. Oops, does your screen look something like this?

localhost: Unable to connect

That’s because even though Apache is included in OS X it’s off by default. We have a few options available that we’ll look at to enabling Apache.

The Apple Way

Even though Apple includes Apache as a part of OS X, and they used to include an option to enable it in the "Sharing" pane of System preferences, the preferred way to enable Apache on OS X since 10.7 is to buy the Server app from the App Store. That’s right, for only $19 Apple will sell you a button that starts Apache.

The Poor Man’s Way

The second option is to simply start Apache ourselves. Go to terminal and type this command:

sudo apachectl start  

After you type your password head back to http://localhost and it should look more like this:

it works!

$20 bucks for that. To be fair to Apple, Server does give you lots of other functionality but for our purposes they’re not really necessary. Kind of like installing XCode to use git. Now, I’m going to recommend that you actually stop the Apache service because we are not going to use the built in version that Apple ships with.

sudo apachectl stop  

The Homebrew Way

The reason we’re not going to use the version that ships with Apple is because they do a TON of customization to the Apache configurations and it makes it really difficult to simply add a vhost or edit a config. You even have to mess with groups to allow yourself to change files in your ~/Sites folder. Instead we’re going to install Apache via Homebrew and it will run entirely within your home folder.

First we need to unload the OS X version of Apache which is loaded on the machine at startup, but "stopped". Copy and paste this into terminal.

sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist 2>/dev/null  

Next we need to do is add the Apache repository to the list of repositories that Homebrew tracks, as it is not included by default.

brew tap homebrew/apache  

and you’ll see that the repository is downloaded and 9 new forumlae are available for install.

brew tap homebrew/apache  
Cloning into '/usr/local/Library/Taps/homebrew/homebrew-apache'...  
remote: Counting objects: 241, done.  
remote: Total 241 (delta 0), reused 0 (delta 0)  
Receiving objects: 100% (241/241), 45.55 KiB | 39.00 KiB/s, done.  
Resolving deltas: 100% (130/130), done.  
Checking connectivity... done.  
Tapped 9 formulae  

The new repository gives us two versions of Apache: 2.2 and 2.4. For our development we will be installing Apache 2.4, however when we try we end up with an error:

brew install httpd24  
Error: No available formula for apr (dependency of httpd24)  
Searching taps...  
homebrew/dupes/apr-util homebrew/dupes/apr homebrew/science/reapr  

it seems that Apache 2.4 requires another app called ‘apr’ that isn’t on our system. Fortunately Homebrew does us a favor and tells us where to find it and it’s available in another repository, so we need to add that to our list of available repositories like before:

brew tap homebrew/dupes  

According to the ‘dupes’ repository, "These formulae duplicate software provided by OS X, though may provide more recent or bugfix versions." Now what’s nice is that homebrew will resolve dependencies for us now that it knows where the files are, so let’s try again:

brew install httpd24 –with-privileged-ports  

httpd24 will get us the current version of Apache 2.4. The ‘with-privileged-ports’ parameter tells Apache to run on port 80/443 instead of 8080/8443. Essentially this means you can visit http://localhost instead of having to type http://localhost:8080. 80 and 443 are the default ports that all websites run on and for our development purposes are preferable.

If you get an error during compiling please follow the instructions of the output. If you have installed a beta version of Xcode on your machine please do NOT open an issue with the Homebrew people (like this one). Instead head over to the App Store and redownload the old version. If the app store won’t let you download you can go to the Apple Developer Downloads page and download the latest stable version of Xcode to your machine. You don’t have to remove the beta version, but the stable needs to be named ‘Xcode.app’.

Error: Homebrew doesn't know what compiler versions ship with your version of Xcode (6.0).  
Please `brew update` and if that doesn't help, file an issue with the output of `brew --config`:  
Note that we only track stable, released versions of Xcode. Thanks!  
If reporting this issue please do so at (not Homebrew/homebrew):  

Another issue I ran into was a configure failure because zlib couldn’t be found:

brew install httpd24 --with-privileged-ports  
==> Downloading https://archive.apache.org/dist/httpd/httpd-2.4.10.tar.bz2 
Already downloaded: /Library/Caches/Homebrew/httpd24-2.4.10.tar.bz2  
==> ./configure --enable-layout=Homebrew --enable-mods-shared=all --with-mpm=pre 
checking whether to enable mod_sed... shared (all) checking whether to enable mod_charset_lite... shared (all)  
checking whether to enable mod_deflate...  
checking dependencies  
checking for zlib location... not found  
checking whether to enable mod_deflate...  
configure: error: mod_deflate has been requested but can not be built due to prerequisite failures  
https://github.com/Homebrew/homebrew/wiki/troubleshooting If reporting this issue please do so at (not Homebrew/homebrew):  

This is probably a result of using a beta version of the Xcode command line tools, but there’s a simple fix that’s been reported. First you need to install zlib in Homebrew from the dupes repository.

brew install zlib  

Homebrew will give you this caveat:

==> Caveats 
This formula is keg-only, so it was not symlinked into /usr/local.  
Mac OS X already provides this software and installing another version in parallel can cause all kinds of trouble.  
Generally there are no consequences of this for you.  
If you build your own software and it requires this formula, you'll need to add to your build variables:  
LDFLAGS: -L/usr/local/opt/zlib/lib  
CPPFLAGS: -I/usr/local/opt/zlib/include  

Which basically means that you need to manually use that version of zlib, which is what we’ll tell our Apache build script to do now. Go back to terminal and enter the following command:

brew edit httpd24  

This will open the config in your default text editor. Enter this line:

args << "--with-z=/usr/local/Cellar/zlib/1.2.8"  

After this section:

if build.with? "privileged-ports"  
  args << "--with-port=80"
  args << "--with-sslport=443"
  args << "--with-port=8080"
  args << "--with-sslport=8443"

That will add a new argument that forces it to use our Homebrew version of zlib. Save the file and run the build again:

brew install httpd24 --with-privileged-ports  

If you don't have any errors then your output should look something like this:

brew install httpd24 --with-privileged-ports  
==> Downloading https://archive.apache.org/dist/httpd/httpd-2.4.10.tar.bz2 
Already downloaded: /Library/Caches/Homebrew/httpd24-2.4.10.tar.bz2  
==> ./configure --enable-layout=Homebrew --enable-mods-shared=all --with-mpm=pre 
==> make 
==> make install 
==> Caveats 
To have launchd start httpd24 at login:  
ln -sfv /usr/local/opt/httpd24/*.plist ~/Library/LaunchAgents  
Then to load httpd24 now:  
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.httpd24.plist  
==> Summary /usr/local/Cellar/httpd24/2.4.10: 199 files, 4.4M, built in 86 seconds

Finally it's important that we look at the caveats as they will tell us how to make sure that Apache starts when we log in to the system. Since we used the --with-privileged-ports parameter we will have to make some adjustments to the recommendations that Homebrew provides. Their recommendation is to create a symlink to your user LaunchAgents folder. That folder contains plists that do certain things when you log in. However, on a Unix system lower ports like 80 require root to run. Since this launch agent is launching Apache under your username it will quietly fail. Instead we will have the system (root) load it for us. The biggest difference is that Apache will be loaded at system start instead of when you log in. So go ahead and copy and paste this modified command into terminal.

sudo ln -sfv /usr/local/opt/httpd24/*.plist /Library/LaunchDaemons; sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.httpd24.plist  

Now the next time you reboot Apache will load. But the beautiful thing about a service is that you can start, stop, or reload it at any time- we don't need to log out and log in again to start. Now, since we're running Apache as root we will need to use sudo. So it's not necessary now, because we just loaded it, but for the future you can use:

sudo httpd -k start  
sudo httpd -k restart  
sudo httpd -k stop  

for more info you can check the manual man entry that's included with our Apache install.

man httpd  

Now Apache is installed (by you) and running (as root). Go ahead and go back to http://localhost and you should see this:

Apache is Installed

By default the Apache configuration files are stored in /usr/local/etc/apache2/2.4 and the web root is at /usr/local/var/www/htdocs. We'll revisit further Apache configuration when we get to Moodle setup, but for now enjoy your shiny new site!

MySQL, uhh.. PostgreSQL

The next step on setting up our MA*P server is the database. In the old, olden days of 3 years ago OS X included MySQL in OS X server. Then Apple switched to Postgres. Then Apple bundled Postgres with the OS and added an 'enable' button in the Server app (a la Apache). And now starting with OS 10.9 it's back to being bundled exclusively with the Server App. tl;dr: you need to install a database, and I figure if it's so trivial for Apple to switch databases then it doesn't really matter which one we're using right ;)

Ok, here's the exception, if you're planning on developing some sort of Moodle plugin, or fixing bugs that are database oriented, then you'll probably want to install multiple database drivers. But for simpler development where Moodle $DB handled the transactions, pick your poison. I'm going to use MySQL. So let's head back to the Homebrew app in terminal and get started:

brew install mysql  

Ok, caveat. If you happen to be on an as-of-yet-unreleased OS and have both Xcode 5 and Xcode 6 beta installed on your system you will need to use Xcode 6 to install MySQL otherwise this will happen:

brew install mysql  
==> Installing dependencies for mysql: cmake, pkg-config, makedepend, ope 
==> Installing mysql dependency: cmake 
==> Downloading http://www.cmake.org/files/v3.0/cmake-3.0.0.tar.gz 
######################################################################## 100.0% 
==> ./bootstrap --prefix=/usr/local/Cellar/cmake/3.0.0 --system-libs --no-system 
See also "/tmp/cmake-Lgaj/cmake-3.0.0/CMakeFiles/CMakeError.log".  
Error when bootstrapping CMake: Problem while running initial CMake  
READ THIS: https://github.com/Homebrew/homebrew/wiki/troubleshooting 

These open issues may also help:  
Qt5 Cmake Files Incorrect (https://github.com/Homebrew/homebrew/issues/29938)  
Fix LLVM CMake modules not being preprocessed (https://github.com/Homebrew/homebrew/pull/29976)  
glm: Does not install FindGLM.cmake (https://github.com/Homebrew/homebrew/issues/28403)  
cmake builds fail on CLT-only with --env=std (https://github.com/Homebrew/homebrew/issues/29101)  
cmake find_package(PythonLibs)  
broken with brewed python on 10.9 (https://github.com/Homebrew/homebrew/issues/25118)  

It's nice that they give you a log to inspect, but it's also kind of annoying that the /tmp dir gets cleaned up one it's done failing... Basically if you can capture the log in Console while it's failing you'll see that something in the Xcode tool is causing the failure. I guessed that it was because of Xcode 5 - and hey, lucky guess! So if you have that error drop into terminal and use this command:

mv /Applications/Xcode.app /Applications/Xcode5.app; mv /Applications/Xcode6-beta4.app /Applications/Xcode.app  

Or just go to the /Applications folder and rename them, your choice. Then run the install command again. When the build is done you can move your Xcode apps back to their original names. If all goes well you should end up with some terminal output like this:

brew install mysql  
==> Installing dependencies for mysql: cmake, pkg-config, makedepend, ope 
==> Installing mysql dependency: cmake 
==> Downloading http://www.cmake.org/files/v3.0/cmake-3.0.0.tar.gz 
Already downloaded: /Library/Caches/Homebrew/cmake-3.0.0.tar.gz  
==> ./bootstrap --prefix=/usr/local/Cellar/cmake/3.0.0 --system-libs --no-system 
==> make 
==> make install /usr/local/Cellar/cmake/3.0.0: 1622 files, 25M, built in 4.1 minutes 
==> Installing mysql dependency: pkg-config 
==> Downloading http://pkgconfig.freedesktop.org/releases/pkg-config-0.28.tar.gz
######################################################################## 100.0% 
==> ./configure --prefix=/usr/local/Cellar/pkg-config/0.28 --disable-host-tool - 
==> make 
==> make check 
==> make install /usr/local/Cellar/pkg-config/0.28: 10 files, 608K, built in 89 seconds 
==> Installing mysql dependency: makedepend 
==> Downloading http://xorg.freedesktop.org/releases/individual/util/makedepend- 
######################################################################## 100.0% 
==> Downloading http://xorg.freedesktop.org/releases/individual/proto/xproto-7.0 
######################################################################## 100.0% 
==> ./configure --disable-silent-rules --prefix=/private/tmp/makedepend-xfpn/mak 
==> make install 
==> Downloading http://xorg.freedesktop.org/releases/individual/util/util-macros
######################################################################## 100.0% 
==> ./configure --prefix=/private/tmp/makedepend-xfpn/makedepend-1.0.5/xorg-macr 
==> make install 
==> ./configure --disable-silent-rules --prefix=/usr/local/Cellar/makedepend/1.0 
==> make install /usr/local/Cellar/makedepend/1.0.5: 7 files, 92K, built in 27 seconds 
==> Installing mysql dependency: openssl 
==> Downloading https://www.openssl.org/source/openssl-1.0.1h.tar.gz curl: (28) Operation timed out after 6190 milliseconds with 0 out of 0 bytes received Trying a mirror... 
==> Downloading http://mirrors.ibiblio.org/openssl/source/openssl-1.0.1h.tar.gz
######################################################################## 100.0% 
==> perl ./Configure --prefix=/usr/local/Cellar/openssl/1.0.1h --openssldir=/usr 
==> make depend 
==> make 
==> make test 
==> make install MANDIR=/usr/local/Cellar/openssl/1.0.1h/share/man MANSUFFIX=ssl 
==> Caveats 
A CA file has been bootstrapped using certificates from the system keychain.  
To add additional certificates, place .pem files in  
and run  
This formula is keg-only, so it was not symlinked into /usr/local.  
Mac OS X already provides this software and installing another version in parallel can cause all kinds of trouble.  
The OpenSSL provided by OS X is too old for some software.  
Generally there are no consequences of this for you.  
If you build your own software and it requires this formula, you'll need to add to your build variables:  
LDFLAGS: -L/usr/local/opt/openssl/lib  
CPPFLAGS: -I/usr/local/opt/openssl/include  
==> Summary /usr/local/Cellar/openssl/1.0.1h: 429 files, 15M, built in 4.1 minutes 
==> Installing mysql 
==> Downloading http://cdn.mysql.com/Downloads/MySQL-5.6/mysql-5.6.20.tar.gz
######################################################################## 100.0% 
==> cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/Cellar/mysql/5.6.20 -DCMAKE_FIND_F 
==> make 
==> make install 
==> Caveats 
A "/etc/my.cnf" from another install may interfere with a Homebrew-built server starting up correctly.  
To connect:  
mysql -uroot  
To have launchd start mysql at login:  
ln -sfv /usr/local/opt/mysql/*.plist ~/Library/LaunchAgents  
Then to load mysql now:  
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist  
Or, if you don't want/need launchctl, you can just run:  
mysql.server start  
==> /usr/local/Cellar/mysql/5.6.20/bin/mysql_install_db --verbose --user=dannywa 
==> Summary /usr/local/Cellar/mysql/5.6.20: 9578 files, 339M, built in 7.2 minutes

Now that's pretty amazing to me, and where homebrew is really worth its salt. All of those dependencies managed, auto downloaded, configured, and installed: cmake, pkg-config, makedepend, openssl, and finally mysql. There's no way I could have done that in 7.2 minutes manually.

Generally MySQL runs at a high enough port that it can run in user space, so you don't need to sudo it into the /Library/LaunchAgents folder, it's ok to run it from your home folder, so go ahead and follow the instruction on screen:

ln -sfv /usr/local/opt/mysql/*.plist ~/Library/LaunchAgents;  
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist  

That will load MySQL. Now we'll test it and make some configuration changes. In shell we will connect to the mysql server:

mysql -u root  

You will now drop into a MySQL prompt that should look something like this:

Welcome to the MySQL monitor. Commands end with ; or \g.  
Your MySQL connection id is 3  
Server version: 5.6.20 Homebrew 

Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. 

Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. 

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 


Notice that we have version 5.6.20 Homebrew - helpful in identifying that we are connected to the correct database. Now at the prompt (mysql >) we will set a password for the root account. It's not only good practice, it's required by some software. Obviously in a production server you would worry about security- but for a dev server using root is A-OK in my book. To set the root password enter this command:


No, that didn't really change the root password. That simply disconnected from the MySQL server and dropped us back into our standard shell. You see, there's a simple command called mysqladmin that's been installed for us as well which makes it much easier to change the password. Now we will use this command:

mysqladmin -u root password "newpwd"  

Please substitute "newpwd" for the password you want to use for the root account. I use "root" (no quotes). Again- it's a dev. server. Remember that the MySQL docs recommend only alphanumeric characters for MySQL passwords. If you run into log in issues later it might be because of special characters. Now, let's try to connect to mysql again as above and you should get an error:

mysql -u root ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)  

If you want to connect via terminal in the future you will have to use the -p flag like so:

mysql -u root -p  

After which you will be prompted for the password which you just set.

For now that's the basic configuration of MySQL, we'll cover creating databases later when we go into Moodle setup.


PHP is one of the most popular dynamic languages out there. It's robust, it's solid, and it's what Moodle uses. So let's install it. Once again, OS X ships with PHP installed, but we're going to disable it and replace with the Homebrew version. My shipped version is 5.5.9 (cli) which was released on Feb 4, 2014. The latest stable version is 5.5.15:

php -v  
PHP 5.5.9 (cli) (built: Jun 28 2014 15:59:38)  
Copyright (c) 1997-2014 The PHP Group  
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies  

Now, before we install PHP we need to remember that Moodle requires and recommends a lot of submodules:

  • php
  • pcreunicode
  • php_extension iconv
  • php_extension mbstring
  • php_extension curl
  • php_extension openssl
  • php_extension tokenizer
  • php_extension xmlrpc
  • php_extension soap
  • php_extension ctype
  • php_extension zip
  • php_extension zlib
  • php_extension gd
  • php_extension simplexml
  • php_extension spl
  • php_extension pcre
  • php_extension dom
  • php_extension xml
  • php_extension intl
  • php_extension json
  • php_extension hash

Man that's a lot of requirements/recommendations, fortunately Homebrew is going to provide us a lot of flexibility in getting everything installed. First we're going to need to tap two new repositories 'versions' and 'homebrew-php':

brew tap homebrew/versions; brew tap homebrew/homebrew-php  

If everything goes well your output should look something like this:

brew tap homebrew/versions  
Cloning into '/usr/local/Library/Taps/homebrew/homebrew-versions'...  
remote: Counting objects: 2274, done.  
remote: Compressing objects: 100% (42/42), done.  
remote: Total 2274 (delta 18), reused 4 (delta 2) Receiving objects: 100% (2274/2274), 729.39 KiB | 338.00 KiB/s, done.  
Resolving deltas: 100% (1278/1278), done.  
Checking connectivity... done.  
Tapped 160 formulae  
brew tap homebrew/homebrew-php  
Cloning into '/usr/local/Library/Taps/homebrew/homebrew-php'...  
remote: Counting objects: 6685, done.  
remote: Compressing objects: 100% (5/5), done.  
remote: Total 6685 (delta 0), reused 1 (delta 0)  
Receiving objects: 100% (6685/6685), 1.32 MiB | 691.00 KiB/s, done.  
Resolving deltas: 100% (4322/4322), done.  
Checking connectivity... done.  
Tapped 408 formulae  

Next you'll need to decide which version of PHP you want. I'm going to use PHP 5.6 even though it's only at RC1 as of this writing because I really want to try phpdbg. If nobody tests release versions of software then how will the bugs be found? Let's take a look at what options are available to us by running this command:

brew options php56  

and we get quite a few options:

brew options php56  
  Build without Opcache extension 
  Build against apxs in Homebrew prefix
  Enable building of shared Apache 2.0 Handler module, overriding any options which disable apache 
  Enable building of the CGI executable (implies --without-apache) 
  Compile with debugging symbols 
  Enable building of the fpm SAPI executable (implies --without-apache) 
  Build with gmp support 
  Include Curl support via Homebrew 
  Include LibXSLT support via Homebrew 
  Include OpenSSL support via Homebrew 
--with-imap Include 
  IMAP extension 
  Include internationalization support 
  Include (old-style) libmysql support instead of mysqlnd 
  Include MSSQL-DB support 
  Include Oracle databases (requries ORACLE_HOME be set) 
  Include PostgreSQL support 
  Enable building of the phpdbg SAPI executable (PHP 5.4 and above) 
  Build with thread safety 
  Include Tidy support 
  Build without bz2 support 
  Remove MySQL/MariaDB support 
  Build without Process Control support 
  Build without PEAR --HEAD install HEAD version

Even though it's probably not required for php56 I'm going to use the --with-phpdbg flag just to be safe. In addition I'm going to use a few other options for homebrew versions of software. Since we've already installed Homebrew OpenSSL we'll use that, as well as APXS and Curl. Apxs is a tool for building Apache modules (like PHP) - since we're using a newer version of Apache than shipped with OS X we should probably use a newer version of APXS too. Curl is another one of those tools that's slightly outdated and we may as well use the latest stable version. The version on my system is 7.30.0, and at the time of this writing the version shipping with Homebrew is 7.37.1. I've also used --with-tidy because if I recall correctly Moodle text editors can leverage it for WYSIWYG input. Finally, notice that we're not using the --with-intl flag, that's because currently intl is broken and we'll have to install a separate package after PHP builds. Here's my full install command (copy and paste):

brew install php56 --homebrew-apxs --with-apache --with-homebrew-curl --with-homebrew-openssl --with-phpdbg --with-tidy  

If you get an error about missing bzip2 like this:

==> ./configure --prefix=/usr/local/Cellar/php56/5.6.0-rc.2 --localstatedir=/usr 
checking for gzgets in -lz... yes  
checking whether to enable bc style precision math functions... yes  
checking for BZip2 support... yes  
checking for BZip2 in default path... not found  
configure: error: Please reinstall the BZip2 distribution  
READ THIS: https://github.com/Homebrew/homebrew/wiki/troubleshooting  
If reporting this issue please do so at (not Homebrew/homebrew): https://github.com/homebrew/homebrew-php/issues  
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in 'require': Class is not a module (TypeError) 
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in 'require'  
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/net/http.rb:390:in '<http>'  
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/net/http.rb:384:in '<net>'  
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/net/http.rb:25:in '<top>'  
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in 'require'  
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in 'require'  
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/net/https.rb:21:in '<top>'  
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in 'require'  
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in 'require'  
from /usr/local/Library/Homebrew/utils.rb:308:in 'open'  
from /usr/local/Library/Homebrew/utils.rb:350:in 'issues_matching' from /usr/local/Library/Homebrew/utils.rb:378:in 'issues_for_formula'  
from /usr/local/Library/Homebrew/exceptions.rb:171:in 'fetch_issues' from /usr/local/Library/Homebrew/exceptions.rb:167:in 'issues'  
from /usr/local/Library/Homebrew/exceptions.rb:207:in 'dump' from /usr/local/Library/brew.rb:158:in 'rescue in <main>'  
from /usr/local/Library/brew.rb:66:in '<main>'</main></main></top></top></net></http>  

Then you will need to edit the formula to set the path of your bzip2 binary manually. First, find where your bzip2 binary is located with the following command:

which bzip2  

Then edit the forumula for php:

brew edit php56  

and add this line:

args << "--with-bz2=/usr/bin/bzip2"  

after these lines:

def install_args args = super  

Another common error that seems to be occuring, and has been reported (here) is a compilation error while building phar. The basic error is:

make: *** [ext/phar/phar.phar] Illegal instruction: 4  


/Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in require': Class is not a module (TypeError)

I found this to be an error of trying to use Homebrew's OpenSSL, either by setting it as the default on the system or using the --with-homebrew-ssl flag. So for now my build is using the system version of OpenSSL. If the build goes correctly then your total output should look something like this:

brew install php56 --homebrew-apxs --with-apache --with-homebrew-curl  --with-phpdbg --with-tidy  
==> Installing dependencies for php56: curl, libpng, freetype, xz, gettex
==> Installing php56 dependency: curl
==> Downloading http://curl.haxx.se/download/curl-7.37.1.tar.gz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/curl/7.37.1 --with-darwinssl --withou
==> make install
==> Caveats
This formula is keg-only, so it was not symlinked into /usr/local.

Mac OS X already provides this software and installing another version in  
parallel can cause all kinds of trouble.

Generally there are no consequences of this for you. If you build your  
own software and it requires this formula, you'll need to add to your  
build variables:

    LDFLAGS:  -L/usr/local/opt/curl/lib
    CPPFLAGS: -I/usr/local/opt/curl/include

==> Summary
  /usr/local/Cellar/curl/7.37.1: 285 files, 2.8M, built in 91 seconds
==> Installing php56 dependency: libpng
==> Downloading https://downloads.sf.net/project/libpng/libpng16/1.6.12/libpng-1
######################################################################## 100.0%
==> ./configure --disable-silent-rules --prefix=/usr/local/Cellar/libpng/1.6.12
==> make install
  /usr/local/Cellar/libpng/1.6.12: 17 files, 1.2M, built in 24 seconds
==> Installing php56 dependency: freetype
==> Downloading https://downloads.sf.net/project/freetype/freetype2/2.5.3/freety
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/freetype/2.5.3_1 --without-harfbuzz
==> make
==> make install
  /usr/local/Cellar/freetype/2.5.3_1: 60 files, 2.5M, built in 26 seconds
==> Installing php56 dependency: xz
==> Downloading http://fossies.org/linux/misc/xz-5.0.5.tar.gz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/xz/5.0.5
==> make install
  /usr/local/Cellar/xz/5.0.5: 58 files, 1.5M, built in 34 seconds
==> Installing php56 dependency: gettext
==> Downloading http://ftpmirror.gnu.org/gettext/gettext-0.19.2.tar.xz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/gettext/0.19.2 --with-included-gettex
==> make
==> make install
==> Caveats
This formula is keg-only, so it was not symlinked into /usr/local.

OS X provides the BSD gettext library and some software gets confused if both are in the library path.

Generally there are no consequences of this for you. If you build your  
own software and it requires this formula, you'll need to add to your  
build variables:

    LDFLAGS:  -L/usr/local/opt/gettext/lib
    CPPFLAGS: -I/usr/local/opt/gettext/include

==> Summary
  /usr/local/Cellar/gettext/0.19.2: 1920 files, 18M, built in 5.3 minutes
==> Installing php56 dependency: jpeg
==> Downloading http://www.ijg.org/files/jpegsrc.v8d.tar.gz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/jpeg/8d
==> make install
  /usr/local/Cellar/jpeg/8d: 18 files, 780K, built in 21 seconds
==> Installing php56 dependency: autoconf
==> Downloading http://ftpmirror.gnu.org/autoconf/autoconf-2.69.tar.gz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/autoconf/2.69
==> make install
  /usr/local/Cellar/autoconf/2.69: 70 files, 3.1M, built in 28 seconds
==> Installing php56 dependency: automake
==> Downloading http://ftpmirror.gnu.org/automake/automake-1.14.1.tar.gz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/automake/1.14.1
==> make install
  /usr/local/Cellar/automake/1.14.1: 131 files, 3.2M, built in 37 seconds
==> Installing php56 dependency: libtool
==> Downloading http://ftpmirror.gnu.org/libtool/libtool-2.4.2.tar.gz
######################################################################## 100.0%
==> Patching
patching file libltdl/config/ltmain.sh  
==> ./configure --prefix=/usr/local/Cellar/libtool/2.4.2 --program-prefix=g --en
==> make install
==> Caveats
In order to prevent conflicts with Apple's own libtool we have prepended a "g"  
so, you have instead: glibtool and glibtoolize.  
==> Summary
  /usr/local/Cellar/libtool/2.4.2: 69 files, 2.5M, built in 46 seconds
==> Installing php56 dependency: homebrew/dupes/tidy
==> Downloading ftp://mirror.internode.on.net/pub/gentoo/distfiles/tidy-20090325
######################################################################## 100.0%
==> sh build/gnuauto/setup.sh
==> ./configure --prefix=/usr/local/Cellar/tidy/20090325 --mandir=/usr/local/Cel
==> make install
  /usr/local/Cellar/tidy/20090325: 10 files, 940K, built in 43 seconds
==> Installing php56 dependency: unixodbc
==> Downloading http://www.unixodbc.org/unixODBC-2.3.2.tar.gz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/unixodbc/2.3.2 --enable-gui=no
==> make install
  /usr/local/Cellar/unixodbc/2.3.2: 31 files, 960K, built in 2.4 minutes
==> Installing php56
==> Downloading http://downloads.php.net/tyrael/php-5.6.0RC3.tar.bz2
######################################################################## 100.0%

Warning: Backing up all known pear.conf and .pearrc files  
Warning: If you have a pre-existing pear install outside  
         of homebrew-php, or you are using a non-standard
         pear.conf location, installation may fail.
==> ./configure --prefix=/usr/local/Cellar/php56/5.6.0-rc.3 --localstatedir=/usr
==> make
==> make install
==> /usr/local/Cellar/php56/5.6.0-rc.3/bin/pear config-set php_ini /usr/local/et
==> Caveats
To enable PHP in Apache add the following to httpd.conf and restart Apache:  
    LoadModule php5_module    /usr/local/opt/php56/libexec/apache2/libphp5.so

The php.ini file can be found in:  

✩✩✩✩ PEAR ✩✩✩✩

If PEAR complains about permissions, 'fix' the default PEAR permissions and config:  
    chmod -R ug+w /usr/local/Cellar/php56/5.6.0-rc.3/lib/php
    pear config-set php_ini /usr/local/etc/php/5.6/php.ini

✩✩✩✩ Extensions ✩✩✩✩

If you are having issues with custom extension compiling, ensure that  
you are using the brew version, by placing /usr/local/bin before /usr/sbin in your PATH:


PHP56 Extensions will always be compiled against this PHP. Please install them  
using --without-homebrew-php to enable compiling against system PHP.

✩✩✩✩ PHP CLI ✩✩✩✩

If you wish to swap the PHP you use on the command line, you should add the following to ~/.bashrc,  
~/.zshrc, ~/.profile or your shell's equivalent configuration file:

      export PATH="$(brew --prefix homebrew/php/php56)/bin:$PATH"

To have launchd start php56 at login:  
    ln -sfv /usr/local/opt/php56/*.plist ~/Library/LaunchAgents
Then to load php56 now:  
    launchctl load ~/Library/LaunchAgents/homebrew.mxcl.php56.plist
==> Summary
  /usr/local/Cellar/php56/5.6.0-rc.3: 497 files, 50M, built in 5.9 minutes

As you can see looking at the caveats there are still a few steps we need to do. First we will fix the PEAR permissions, this might not be necessary, but it won't hurt anything so go ahead and copy and paste these commands into the terminal:

chmod -R ug+w /usr/local/Cellar/php56/5.6.0-rc.3/lib/php  
pear config-set php_ini /usr/local/etc/php/5.6/php.ini  

Next we'll make sure that PHP gets loaded at login by the launchd and load it now, too:

ln -sfv /usr/local/opt/php56/*.plist ~/Library/LaunchAgents  
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.php56.plist  

Then we'll check our command line version. First, exit or Quit Terminal and reopen it, then run this command in terminal:

php -v  

The output should have 5.6 listed as the version, like this:

php -v  
PHP 5.6.0RC3 (cli) (built: Aug 7 2014 10:21:55)  
Copyright (c) 1997-2014 The PHP Group  
Zend Engine v2.6.0-dev, Copyright (c) 1998-2014 Zend Technologies  

If it lists 5.5 as the version then you need to update you $PATH variable. Depending on the version of shell your're running you need to add the following line to your terminal config (default ~/.profile). The following commands should add it for you:

touch ~/.profile  
cat >> ~/.profile <<EOF  
export PATH="$(brew --prefix homebrew/php/php56)/bin:$PATH"  

Now you can exit Terminal and reopen it and run php -v again.

Finally you need to add the php handlers to the httpd.conf. Open up /usr/local/etc/apache2/2.4/httpd.conf and add the following three lines to the document:

LoadModule php5_module /usr/local/opt/php56/libexec/apache2/libphp5.so  
AddHandler php5-script .php  
AddType text/html .php  

and find the line that starts with DirectoryIndex and change it to read:

DirectoryIndex index.php /  

Save the document and then restart the httpd service with the following command:

sudo httpd -k restart  

Now we having functioning PHP on our server, but remember we need to install intl separately, so go back to terminal and run the following command:

brew install php56-intl  

When it finishes building you should have output similar to this:

brew install php56-intl  
==> Installing php56-intl dependency: icu4c
==> Downloading http://download.icu-project.org/files/icu4c/52.1/icu4c-52_1-src.
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/icu4c/52.1 --disable-samples --disabl
==> make VERBOSE=1
==> make VERBOSE=1 install
==> Caveats
This formula is keg-only, so it was not symlinked into /usr/local.

Conflicts; see: https://github.com/Homebrew/homebrew/issues/issue/167

Generally there are no consequences of this for you. If you build your  
own software and it requires this formula, you'll need to add to your  
build variables:

    LDFLAGS:  -L/usr/local/opt/icu4c/lib
    CPPFLAGS: -I/usr/local/opt/icu4c/include

==> Summary
  /usr/local/Cellar/icu4c/52.1: 239 files, 60M, built in 2.6 minutes
==> Installing php56-intl
==> Downloading http://downloads.php.net/tyrael/php-5.6.0RC3.tar.bz2
######################################################################## 100.0%
==> PHP_AUTOCONF="/usr/local/opt/autoconf/bin/autoconf" PHP_AUTOHEADER="/usr/loc
==> ./configure --prefix=/usr/local/Cellar/php56-intl/5.6.0-rc.3 --with-php-conf
==> make
==> Caveats
To finish installing intl for PHP 5.6:  
  * /usr/local/etc/php/5.6/conf.d/ext-intl.ini was created,
    do not forget to remove it upon extension removal.
  * Validate installation via one of the following methods:
  * Using PHP from a webserver:
  * - Restart your webserver.
  * - Write a PHP page that calls "phpinfo();"
  * - Load it in a browser and look for the info on the intl module.
  * - If you see it, you have been successful!
  * Using PHP from the command line:
  * - Run "php -i" (command-line "phpinfo()")
  * - Look for the info on the intl module.
  * - If you see it, you have been successful!
==> Summary
  /usr/local/Cellar/php56-intl/5.6.0-rc.3: 3 files, 360K, built in 52 seconds

brew install composer  
==> Downloading http://getcomposer.org/download/1.0.0-alpha8/composer.phar
######################################################################## 100.0%
==> Caveats
Verify your installation by running:  
  "composer --version".

You can read more about composer and packagist by running:  
  "brew home composer".
==> Summary
  /usr/local/Cellar/composer/1.0.0-alpha8: 3 files, 952K, built in 3 seconds

And there we have it! That will give us every required Moodle php extension, except for opcache, which at the time of this writing is not compatible with PHP 5.6. When it does become available you can simply run brew install php56-opcache and you're done.

Wrapping Up

That's the end of the first part of this extensive walk through of setting up a Moodle development server. This first part should really help anyone doing any sort of PHP development on a Mac whether it's WordPress, Drupal, or whatever. In part two we'll dive into configuring Apache and MySQL for Moodle development as well how to use the MDK, git, and JIRA to contribute your code and bug fixes back to the community.

https://iyware.com/osx-yosemite-mamp-homebrew-development-setup/30dbf1e7-e4cf-4997-84ef-d8fb05d78850Wed, 13 Aug 2014 11:17:52 GMT
<![CDATA[Elegance: What's coming?]]>This is just a quick note to say that the next minor version of Elegance is in development. The next release (version 2.7.1) will feature a lot of bug fixes and a couple of new features. If you’d like to follow development you can check the status of the 2.7.1 Milestone over at github. It’s already past due by a day- but we had a lot of new issues opened in the last week that we wanted to fix before a new release. In general we do a lot of testing on the weekly+ and master branches of Moodle so we should be able to get a release out the door one week behind a Moodle release.

So what do you need to do to get ready for the next version? I suggest you head over to the Favicon Generator and make sure you have branding for all your mobile devices.

https://iyware.com/elegance-whats-coming/79d0f9f9-72ae-4b96-9992-d33bb70ca661Tue, 22 Jul 2014 07:56:12 GMT
<![CDATA[Moodle $OUTPUT: An Introduction]]>Here at iyWare we’re not really developers. Sure we write some code, and we have even released a few plugins for Moodle, WordPress, and Mahara but really we’re in the educational technology consulting business. Recently we took over development of the Elegance Moodle theme from its original author and began auditing the code. We quickly released a Moodle 2.7 compatible version- but to the (understandable) dissatisfaction of a lot of users some of the changes we made were perceived as “worse” or at least “not better” than the older version of the theme. So in this post I’d like to clarify why some of the changes we made were made and why they’re actually better. This may get a bit technical but I’ll try to keep it simple.

$OUTPUT Rules All

In Moodle what a plugin can do is determined by what kind of plugin it is. This is logical AND makes sense. For example an activity (say Forum) is not allowed to ask you if you want to change your site logo. Other software, like WordPress aren’t so well defined. Have you ever changed themes and had an entire post-type (like testimonials) disappear? That’s because themes in WordPress are allowed to do things beyond change the look and feel of the site. Imagine in Moodle if you changed your theme and suddenly couldn’t use the Quiz module any more! So we have a good system of permissions in Moodle and what’s allowed to do what.

Now we’re going to introduce you to $OUTPUT. $OUTPUT is a variable (meaning its content changes) that stores everything that gets output to a page- i.e. what you see in Moodle. This is a not-quite-technically-accurate definition because there are other global variables like $CFG, $THEME, $PAGE, $USER, and $DB but I think you get the idea. When a plugin has some data or information that it wants to display on the screen it gives it to $OUTPUT. For example, a list of quiz questions, or a button group that says “ok” or “cancel”. Generally the way that things are given to, read from, or changed in $OUTPUT is via what’s called in programming language a "method". Simply stated a method is a way of doing things- this applies offline as well as online. I have a method of stacking our plates (largest on bottom, smallest on top) so they don’t fall over. People have various methods of parking their cars- and the method varies depending on the type of parking spot.

Method to the Madness

Methods in Moodle are defined by Moodle core- and this is why a quiz can’t change a logo or a theme can’t add an activity type. Moodle core will, in a programmatic way, tell the plugin “sorry you can’t do that.” There’s pretty much one exception to this, themes actually CAN do pretty much whatever they want with $OUTPUT. Sure, there are a few limitations (like the example above) but really they can add to, remove from, or change ANYTHING inside of $OUTPUT. This isn’t actually a bad thing, look and feel some times requires to a degree modifying the document structure- some times! The bad thing is that ONLY themes have this power. So let’s give an example of why this is good, and then an example of why it might be bad.

The Good: Font Icons

Iconography definitely falls into the “look & feel” department. So if a theme wants to use a font icon (like fontawesome it will have to change $OUTPUT. The transaction would look something like this:

Quiz Module: Hey, $OUTPUT the user is adding a quiz to the course page. Make sure you use that piece of paper with a fat red check mark on it as the icon.

$OUTPUT: Cool, thanks for that info.

$OUTPUT: Yo! Theme, here’s everything you need to put on the page, get to work.

Theme plugin: Hold up $OUTPUT Imma let you finish, but font-awesome has the best icons. So We’re going to use a “question mark” icon instead.

Ta-da! Now you’re using an icon font to change the look-and feel of your website, but not really changing the functionality- which is the textbook definition of what a theme should do.

Let’s say you work at an actually-forward-thinking school that’s not bound by stuff like COPPA or CISPA, or a think-of-the-children mindset, or just a university. Pretend your school really wants to be able to add an array of “share this” links at the bottom of every forum post, next to the “reply”, “edit”, and “permalink” links. There are a number of options that you might try (or think you can try):

“local” plugin type

At first a developer might think “I’ll just make a small plugin that modifies the method that makes forum posts to include “share” links. They write a “local” plugin- meaning it’s not one of the well-defined plugin types (like assignment, theme, authentication, etc…) and write their new version of the method- and voila! It doesn’t work! Remember, Moodle tells plugins that they can’t change other plugins’ methods or their part of $OUTPUT.

Forum 2: Electric Boogaloo!

The next option is to fork (or make a copy of) the forum module and change its name (and all mentions of its name) to something different, like “Forum 2″. Then you change the method of your new module to add sharing links. Install the new Forum 2 on your Moodle and people can start using it. But now you have 2 forum modules. Do you leave them both enabled? One without sharing links and the other with? No, that’s silly- turn off the old forum. And now your Moodle administrator has 9,000 emails from teachers and students saying they can’t find their old posts- their conditional activities and course completion are broken. Fix it! Fix it! Fix it!. Now you need to write another plugin called a “tool” that takes all of the old “Forum” instances on your site and converts them to “Forum 2″ types. What a nightmare! And don’t forget as soon as Moodle updates the original “Forum” you need to track those changes and copy them to your new version.


The Ugly: The Social-est Theme

Instead of continuing with the “social” sharing example let’s switch to another one. Assume that you want to add some touch icons to your theme. There’s a variety of ways to achieve this and we’ll look at some of them here, with their drawbacks and advantages.

The “HTML” Way

This is probably the most straight forward way to add touch icons to your theme. Simply put the icons in the /pix/ folder and add the meta tag to your layout. Of course if your theme uses multiple layout files (which it probably does) you’ll need to add it to all all 5 or 6 or 7 layout files. The downside here is that by working outside of the image API you will probably cause some caching issues- and you might even end up with broken images, depending on the site config.

The “Moodle” Way

So why not just use the pix_url() method instead? Good idea, actually! Then your touch icons are handled Moodle and all edge cases of config and cache are taken into account for you. But what if you have different themes for desktop and for mobile?

And On it Goes…

Okay, rather than itemizing every option we’re simply going to go down the rabbit trail. Now what if your site has a different theme for desktop vs. mobile? Simply add the new method and the images to both themes. Now what if your site allows teachers to choose their own course theme? Simply add the new method and images to all themes. Now what if some of those themes are “core” themes (included with Moodle) like “More”? Simply clone that theme (see the bullet point above) and add the method and images to your new theme OR create a new “child” theme (a theme that requires another theme) and add your method and images to that theme OR edit the core code [please don’t do that!!!!]

Did we mention that if you make a child theme to make a few modifications that settings aren’t inherited? They’re not. So if you want to clone “more” and make a few changes you’re really out of luck. You’re really just inheriting layout, styles, and JS – which would be good if that’s all themes could do. But that custom logo you created? Nope.

You might as well make your own theme. There is one final option, if you don’t have an in-house developer who can build and maintain a theme your only option is to ask a theme developer to add a feature to a theme. Which we get a lot of, and usually they’re very specific to that instance and wouldn’t be a good general inclusion for a them. But the reason that people ask is because of what’s specifically outlined above- there’s no other way to do it.

Rubber Meets the Road

Okay there’s a bit of a background, now let’s look at some of the things specific to Elegance.

Course Resource Tiles

As we’ve stated, one of the core responsibilities of a theme is to control the look and feel. The “Tiled Resources” was a cool look & feel feature but it wasn’t a complete feature- it took the big resource output and reformatted it as a box instead of a rectangle, and then “floated” it so they stacked next to each other. We removed this from the theme because it wasn’t fully functional- and the functionality that it was trying to create already has a definition: “Course Format”. Remember how we said a theme can’t create a new activity? Well we shouldn’t try to have it pretend like it has added other plugin types- we should actually create them. Very quickly, off the top of my head, here’s a list of the problems with the Tiled Resources as it existed in the theme that would need to be fixed for it to be fully functional:

  • Does it work across multiple course formats (including third party?)
  • How are labels output inside of the “box”?
  • How are folders w/ expanded content displayed in the “box”?
  • How does a resource with the “Description” shown on the page look?
  • How does the list of conditional access rules look on the page?
  • Does “drag and drop” reordering of resources work?
  • How do indented (move right/left) resources look?
  • How do boxes look on a mobile phone or tablet? Desktop?

All of these things fall into the definition of a course format- which a theme could then tweak the look and feel of, but ADDING all of this stuff to a theme is like cramming two plugins into one. On top of that you have a little bit of “vendor lock in”. We all know that teachers shouldn’t tailor their display to their theme and/or device – but they do. And this facilitates that. As an admin you’d never be able to remove or change Elegance as a theme option because suddenly poof Tiled Resources is gone. That’s not good.

My Course Tile Block

Let me just start by saying that the function that added this part to the theme was called something like “theme_elegance_my_course_tiles_fake_block”. That should be enough right there. A theme is a theme, a block is a block. A fake block in a theme is NOT a block. And here’s the problem(s) with it:

  • No block settings (global)
  • No block config (per-block)
  • Users can’t hide, move, or dock the block

All of those things are required by a block type plugin, but a theme modifying doesn’t have to follow the block rules to the detriment of the user. Not to mention, again, that if you switch themes you lose your block- unlike all other block types.


In conclusion there are three main ways that themes modify $OUTPUT of a Moodle site:

  1. Strictly changing look and feel without changing functionality
  2. Adding functionality that can’t be added by other plugin types
  3. Adding functionality that can by added by other plugin types

The above two examples are examples of #3. They were providing functionality that should be provided by “real” plugins. This makes testing and maintaining the theme exponentially harder because you usage cases and configuration options increase with each additional fake plugin you add to your theme.

Now, to be clear, we didn’t remove these features from Elegance simply because ideally they should be a standalone plugin. We removed them because they were broken. Sure, for most simple Moodle installs they mostly worked, but we have to support all Moodle installs. And the only way to ensure that it works properly is to build the functionality correctly. So, while we understand that it’s not fun to lose functionality (especially stuff you really like!) we’re now on the path to being able to do it the right way.

https://iyware.com/moodle-output-an-introduction/da4fb4ba-9b6e-4f6e-a3f1-41c4eeeb5c7fFri, 18 Jul 2014 16:44:33 GMT
<![CDATA[Elegance 2.7 release]]>It’s taken a little bit longer than we had hoped but we’re happy to announce that the official 2.7 version of Elegance has been released! It’s currently available in the Moodle plugins directory.

As with all of our releases we have also opened a new support thread at the moodle.org forums. Since this is our first release since taking over development of this plugin we were primarily on a “how does this work?” expedition. As such there’s not a lot of new functionality, but there are quite a few bugs that have been fixed.

The two biggest changes in this release are

  1. official Moodle 2.7 compatibility
  2. dependence on moodle-theme_bootstrap

This is not to be confused with “bootstrapbase” which is included in Moodle 2.7. But, in the effort of easing confusion – if you’re using the Moodle auto-updater (which you should be) you’ll automatically be prompted to install this parent theme first. This also allowed us to lighten the code base- a lot!

199 changed files with 982 additions and 36,422 deletions

Finally we’d like to thank all the contributors and bug reporters that helped make this release possible.

Well what are you waiting for? Go get it!

https://iyware.com/elegance-2-7-release/26a2e1d1-7c63-4255-bebf-89868ac5a236Fri, 04 Jul 2014 23:11:28 GMT