Installing Ghost on a SmartOS zone

Published on 2017-11-21 22:12:18

Having decided that documenting what I'm doing would be a good idea, I proceeded to look at blogging platforms. Eventually deciding on Ghost, I followed various guides but none of them would quite work on my system. There are plenty of guides for downloading it and installing with npm, so I will only list the commands that I used and spend more time discussing the issues I had along the way.

Here is the chain of commands that got it running on an OS container running under SmartOS:


cd
mkdir ghost
cd !$ 
wget https://ghost.org/zip/ghost-latest.zip
pkgin install unzip
unzip ghost-latest.zip
cp config-example.js config.js
vim config.js
npm install sqlite3 --save
npm start --production

It was necessary to install sqlite3 before attempting to start Ghost, as otherwise it just gave errors about not being able to find it rather than just installing it.

If it's not obvious what to do when editing config.js then visit https://support.ghost.org/config/ for detailed information about the various configuration options available.

The other part of this setup was getting nginx to correctly proxy to the ghost server. My original intention was to proxy the /docs/ virtual subdirectory to ghost, but I had all sorts of redirection issues and just gave ghost its own subdomain, which, by virtue of the fact that you're reading this now, has appeared to work.

This redirection behaviour came from having the ghost base url set to https://docs.waifunet.moe, while http:// worked fine. I think that what was happening in the former case was that a user would request https://, which nginx would pass on to ghost as http, since it's behind a reverse proxy. Upon receiving the http request, ghost would be clever and redirect to the https version because specifying an https url in config.js causes ghost to force https on all pages. Of course, the https request then has to be resolved against the DNS, which sends it back to nginx...

The solution to this came from comments on Ghost#2796, and the fix was to pass the X-Forwarded-Proto https header to ghost, to trick it into thinking that the request was https, at which point it then started serving the content correctly. The most annoying part? Ghost was redirecting as 301: Moved Permanently, so I had to keep switching browser and clearing caches while testing this.

For the sake of completeness, here is the relevant part of my nginx config:


    location / {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_set_header X-NginX-Proxy true;
      proxy_set_header X-Forwarded-Proto https;

      proxy_pass http://<ghost host>;
    }


and ghost has


url: "https://docs.waifunet.moe"

to correctly provide https links.

Finally, it's a bit silly to have to type npm start --production every time I want the blog to be live, so following https://support.ghost.org/deploying-ghost/#making-ghost-run-forever I used the following commands to set up forever to run ghost in the background and restart it in the event of a crash:


cd ghost
npm install forever -g
NODE_ENV=production forever start index.js

Now, this by itself isn't enough as the forever will be lost when the system reboots. Time to learn how to create services! It's not too difficult -- the first step is to write the same init script as other systems would use: /usr/local/svc/method/ghost:


#!/bin/bash
PATH='/usr/local/sbin:/usr/local/bin:/opt/local/sbin:/opt/local/bin:/usr/sbin:/usr/bin:/sbin'
FOREVERPATH='/opt/local/bin/forever'

case $1 in
'start')
cd /root/ghost
NODE_ENV=production $FOREVERPATH start index.js
;;
'stop')
cd /root/ghost
$FOREVERPATH stop index.js
;;
*)
echo "Usage: $0 start|stop" >&2
exit 1
;;
esac
exit 0

I had to include the contents of the PATH environment variable as the /opt/local paths aren't in the standard environment that services are given. We then write a service manifest file.

/var/svc/manifest/site/ghost.xml:


<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">

<service_bundle type='manifest' name='ghost'>

<service
        name='site/ghost'
        type='service'
        version='0.10.0-rc1'>
        
        <create_default_instance enabled='true' />
        <single_instance/>

        <dependency
                name='ghost'
                type='service'
                grouping='require_all'
                restart_on='none'>
                        <service_fmri value='svc:/milestone/multi-user'/>
        </dependency>

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

        <exec_method
                type='method'
                name='start'
                exec='/usr/local/svc/method/ghost start'
                timeout_seconds='30' />
        <exec_method
                type='method'
                name='stop'
                exec='/usr/local/svc/method/ghost stop'
                timeout_seconds='30' />
        
        <property_group name='startd' type='framework'>
                <propval name='duration' type='astring' value='contract' />
        </property_group>

        <template>
                <common_name>
                        <loctext xml:lang='C'>
                                Ghost
                        </loctext>
                </common_name>
        </template>
</service>

</service_bundle>

Import service and check it exists:


# svccfg import /var/svc/manifest/site/ghost.xml
# svcs ghost
STATE          STIME    FMRI
online         23:50:49 svc:/site/ghost:default

Be prepared to start debugging xml errors at the import stage. I had to go back a few times and put in a missing slash from the end of a self-closing tag, as well as update the syntax from where I'd used an outdated guide.

All being well, we can run forever list to see that our service is running; it'll look something like /opt/local/bin/node index.js and after one final reboot just to make sure, we're done.