Ghost on SmartOS

Running and maintaining a blog on SmartOS is simple and easy. Today we're going to go over setting up Ghost on SmartOS, configuring an nginx front-end, as well as configuring SMF to boot it up on zone startup.

Why Ghost?

To be completely honest, I'm not a fan of Ghost. It's somewhat clunky at times, but that's true of most all software, especially version 0 software. Despite that, it's fairly easy to use, and definitely quite pretty, oh yes, it isn't wordpress.

Quite possibly the biggest appreciation I have for ghost is it's side-by-side source/rendered view editor. It's quite rewarding to see your blog form in real time on one side of the screen as you're writing it's markdown source on the other side. A close second is that it's quite easy to install and configure, allowing me to maximize my time in other pursuits.

Prerequisites

To fully install and configure ghost, you will need a few additional packages to decompress ghost and compile some of it's extensions.

# pkgin in gmake gcc49 unzip

Getting Ghost

Downloading and configuring Ghost is pretty straight forward. Switch into the directory you want to run it out of and do the following:

$ curl -LOk https://ghost.org/zip/ghost-latest.zip
$ unzip ghost-latest.zip -d ./

Edit config.js to your liking and run the following commands to configure your environment and initialize ghost for the first time, preferably as the user which will be running the blog:

$ npm install --production

Then start the blog.

$ npm start --production

If everything goes well, you should have a fully functional blog. Create your account and shut down the node.js server with ctrl-c, we're next going to setup nginx as a front-end.

Upgrading Ghost

Upgrading Ghost is an admittedly manual process right now. I recommend following the instructions at http://support.ghost.org/how-to-upgrade/, with some additional steps.

If you can, take a snapshot of the ZFS dataset containing ghost before you start sawing at your current installation. If something goes wrong, you can just rollback to the snapshot and none will be the wiser.

Configuring Nginx

For this section we're going to assume you've already installed and preconfigured nginx. Add the following to /opt/local/etc/nginx/nginx.conf or a file included by that configuration file:

server {
  listen 80;
  listen [::];
  server_name <blog_hostname>;

  location / {
    proxy_pass http://127.0.0.1:2368;
    proxy_http_version 1.1;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
  }
}

The set_header directives aren't necessary, but will make the logs a bit more usable. Restart nginx to re-read the configuration directives.

# svcadm restart nginx

Configuring SMF

Service management facility (SMF) has an unfortunately steep learning curve, which is a real shame, given how it excels at managing services on a running host. Without going into too much depth, We will define a service manifest that will facilitate starting and stopping and restarting the service, basically in the same way that Solaris starts stops and restarts any of it's other services.

Save the following into /root/ghost.xml:

<?xml version='1.0'?>
<!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
<service_bundle type='manifest' name='export'>
  <service name='application/ghost' type='service' version='0'>

    <dependency name='network' grouping='require_all' restart_on='error' type='service'>
      <service_fmri value='svc:/milestone/network:default'/>
    </dependency>

    <dependency name='filesystem-local' grouping='require_all' restart_on='none' type='service'>
      <service_fmri value='svc:/system/filesystem/local:default'/>
    </dependency>

    <exec_method name='start' type='method' exec='npm start --production' timeout_seconds='30'/>
    <exec_method name='stop' type='method' exec=':kill' timeout_seconds='30'/>
    <exec_method name='refresh' type='method' exec=':kill -HUP' timeout_seconds='30'/>

    <property_group name='startd' type='framework'>
      <propval name='duration' type='astring' value='child'/>
      <propval name='ignore_error' type='astring' value='core,signal'/>
    </property_group>

    <instance name='blog-brianewell' enabled='true'>
      <method_context working_directory='/home/brian/sites/blog.brianewell.com'>
        <method_credential group='other' user='brian'/>
        <method_environment>
          <envvar name='HOME' value='/home/brian'/>
          <envvar name='PATH' value='/opt/local/bin:/opt/local/sbin:/usr/bin:/usr/sbin'/>
        </method_environment>
      </method_context>
    </instance>
    <stability value='Evolving'/>
    <template>
      <common_name>
        <loctext xml:lang='C'>Ghost Blogging Platform</loctext>
      </common_name>
    </template>
  </service>
</service_bundle>

Customize everything that needs to be changed in the instance tag and descendants, namely the following:

  • name parameter of instance tag.
  • working_directory parameter of method_context tag.
  • user and (optionally) group parameter of method_credential tag.
  • value parameter of envvar tag with the name parameter set to HOME.

Once those changes have been made, import the service into SMF. Because enabled='true' was set under the instance tag, this instance will automatically start.

# svccfg import /root/ghost.xml

Enable User Management with RBAC

This is great and all, but what happens when we're changing something as our user and we need to restart the blogging software?

$ svcadm restart ghost
svcadm: svc:/application/ghost:blog-brianewell: Permission denied.

Nothing good. Fortunately, Role-Based Access Control (RBAC) really shines with SMF. First of all, define an authorization description under /etc/security/auth_attr:

solaris.smf.manage.ghost.blog-brianewell:::Manage Brian's Ghost Blog::

Next we authorize our user with this description.

# usermod -A solaris.smf.manage.ghost.blog-brianewell brian

And finally we tell SMF what to allow with that authorization description. Specifically, we want SMF to allow a user with the solaris.smf.manage.ghost.blog-brianewell authorization to be able to take the application down for maintenance and restart it (action_authorization) as well as enable or disable its starting during boot (value_authorization). Since we've been as specific as naming our blog, we should also grant this permission to this specific instance, and not all ghost instances which could be on the server.

# svccfg -s ghost:blog-brianewell setprop \
general/action_authorization=astring: 'solaris.smf.manage.ghost.blog-brianewell'
# svccfg -s ghost:blog-brianewell setprop \
general/value_authorization=astring: 'solaris.smf.manage.ghost.blog-brianewell'

Now you can use svcadm from your user account to control your blogging software in a stable and secure manner, much better than is possible using cron, init.d, or pretty much any other alternative.

Stay tuned for another blog post on SMF, since it's incredibly useful to understand while using Solaris or SmartOS.