Ghost setup

From Smith family
Server setup
← Previous Next →
Discourse Miscellaneous

Ghost is also a bit opinionated. In particular, it expects to use Nginx as a web server, rather than Apache.

Installation

First, install Nginx. To allow Nginx to work on the same box as the Apache, change /etc/nginx/sites-enabled/default to include

# listen 80 default_server;
# listen [::]:80 default_server;
listen 8080 default_server;
listen [::]:8080 default_server;

I created an additional user for the installation, but I'm not sure that was necessary. Remember to use that user's password when it asks you for sudo permission!

When asked, specify the URL of the blog as https://blog.domain.tld, noting that it's https, not http.

When asked, give server.domain.tld as the hostname of the MySQL server, not the default localhost.

I skipped SSL setup, instead later manually setting up Nginx to use the existing Lets Encrypt certificates by editing /etc/nginx/sites-enabled/blog.domain.tld to include the lines:

ssl_certificate_key /etc/letsencrypt/live/blog.domain.tld/privkey.pem;
ssl_certificate     /etc/letsencrypt/live/blog.domain.tld/fullchain.pem;

I did try using a Docker-based installation of Ghost, but that didn't work well with passing through requests to generate the images in blog posts.

Installing additional blogs on the same server

This is much the same as with installing the first blog, but you must override the defaults for some settings.

Before installing a second blog on the same server, manually create the database and the database user:

user@server:~$ mysql -u root -p
mysql> create database www_other_blog_org_prod;
mysql> create user 'ghost_otherblog'@'localhost' identified by 'secretpassword';
grant all privileges on www_other_blog_org_prod.* to 'ghost_otherblog'@'localhost';

Then run the installation as normal:

ghostinstall@server:/var/www/otherblog$ ghost install

When prompted, give the database details you produced before, and ask Ghost not to create the ghost database user.

Once you've done, take a look in /var/www/otherblog/config.production.json for the port the local Ghost server is running on

 "url": "https://www.otherblog.org",
 "server": {
   "port": 2369,
   "host": "127.0.0.1"
 },

Use that port number (2369) in the Apache proxy settings below.

Apache proxy

You'll need an SSL certificate which covers this domain.

Create the Apache site file as /etc/apache2/sites-available/blog.domain.tld.conf:

<VirtualHost *:80>
       ServerAdmin webmaster@localhost
       
       ServerName blog.domain.tld
       
       Redirect permanent / https://blog.domain.tld
       CustomLog /var/log/apache2/blog.domain.tld.access.log combined

       ServerSignature off
</VirtualHost>

<VirtualHost *:443>
       ServerAdmin webmaster@localhost
       
       SSLEngine On

       SSLCertificateKeyFile /etc/letsencrypt/live/blog.domain.tld/privkey.pem
       SSLCertificateFile /etc/letsencrypt/live/blog.domain.tld/fullchain.pem

       ServerName blog.domain.tld
       
       RequestHeader set X-Forwarded-Proto "https"
       ProxyPreserveHost On
       ProxyRequests Off
       ProxyPass / http://0.0.0.0:2368/
       ProxyPassReverse / http://0.0.0.0:2368/

       CustomLog /var/log/apache2/blog.domain.tld.access.log combined

       ServerSignature off
</VirtualHost>
Note the X-Forwarded-Proto request header.

Enable the site and reload the Apache configuration:

root@server:~# a2ensite blog.domain.tld
root@server:~# systemctl reload apache2.service

Configuration

Visit https://blog.domain.tld/ghost for final configuration.

Install the Ghost desktop app for easier control of the blog.

One thing that needs changing is the email service for transactional emails. The default "Direct" method doesn't seem to use any features such as SPF or DKIM, so those messages get blocked by the large email providers like Gmail. The solution is to use the existing Postfix_server_setup.

Edit the mail section of Ghost's config.production.json file to include the following:

 "mail": {
   "from": "Ghost <ghost@blog-domain.tld",
   "transport": "SMTP",
   "options": {
     "service": "sendmail"
   }
 },

You'll also need to configure the DNS records for your blog's domain to include your mail server in the SPF record. In your blog domain's DNS record, include a TXT field with contents v=spf1 mx a mx:domain.tld ~all

Install a theme

Several of the changes below will require changes to the theme, so it's a good idea to get the theme sorted out now. I use a variant on the Willow theme (my fork on Github).

  • Clone the theme, from the Github source, to your desktop machine.
  • In the theme directory, install Grunt and get it watching the file change. (This will re-create the assets/css/styles.css when there are changes in the files its derived from.
user@desktop:~/blog/theme$ npm install
user@desktop:~/blog/theme$ grunt
  • Alter package.json to reflect the changes you're about to make.
  • Alter partials/sidebar.hbs to adjust the sidebar links. (Look on Github, as Mediawiki doesn't like paired curly brackets in markup.)
  • Adjust source/sass/components/_post-view.scss to allow floating images. In the "Post view" section near the top of the file, add these three lines:
img[src$="#left"] { max-width:30%; float: left; } 
img[src$="#right"] { max-width:30%; float: right; } 
img[src$="#full"] { max-width:none;width:100vw }
You should see the Grunt watcher rebuild the style sheet now.
  • Move to the directory above and zip the theme:
user@desktop:~/blog$ zip -x '*/node_modules/*' '*/.git/*' -r ghost-theme-willow.zip theme
  • Use the Ghost desktop app to upload the theme. If you change styles, you'll also need to restart the Ghost server. Changes to .hbs files take effect immediately.
  • Commit your changes into Git, as you would for any other project.
  • Add some images to your blog.
    • Publication icon is the favicon, and should be 60×60 pixels.
    • Publication logo is a small symbol used in the sidebar header, about 135×135 pixels.
    • Publication cover is a large background image used in the sidebar. Mine's a 550×250 pixel Jpeg.

You can now add floating images with by appending #left, #right, or #full to the image URL when creating a post.

Install Disqus

The standard instructions are good. Remember to:

  • Note the Disqus site shortname.
  • Add blog.domain.tld to Disqus as a trusted domain
  • Disable the "tracking" and "affiliated sites" in Disqus advanced settings
  • Update the theme's post.hbs file to include the Disqus code. (Look on Github, as Mediawiki doesn't like paired curly brackets in markup.)
Note that you should use you own site's Disqus shortname.
  • Again, zip and upload the modified theme.

Install MathJax and Prism

MathJax renders Latex formulae. Prism does syntax highlighting. It can all be done by code injection.

  • In the Code Injection tab of the desktop app, add these lines to the blog header:
 <link href="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/themes/prism.min.css" rel="stylesheet"/>
 <link href="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/plugins/toolbar/prism-toolbar.min.css" rel="stylesheet"/>
 <link href="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/plugins/line-numbers/prism-line-numbers.min.css" rel="stylesheet"/>
  • and add these lines to the blog footer:
 <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/prism.min.js"></script>
 <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/plugins/toolbar/prism-toolbar.min.js"></script>
 <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/plugins/show-language/prism-show-language.min.js"></script>
 <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"></script>
 <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/plugins/line-numbers/prism-line-numbers.min.js"></script>
 <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/components/prism-clike.min.js"></script>
 <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/components/prism-javascript.min.js"></script>
 <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/components/prism-latex.min.js"></script>
 <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/components/prism-markdown.min.js"></script>
 <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/components/prism-haskell.min.js"></script>
 <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/prism/1.12.2/components/prism-python.min.js"></script>
 <script type="text/javascript" async src="//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML">
 MathJax.Hub.Config({
   tex2jax: {
       inlineMath: [["$", "$"], ["\\(", "\\)"]],
       processEscapes: true
   }
 });
 </script>

Add additional Prism lines to the footer for additional languages.

Prism markup examples

When adding code blocks to posts, use this formatting:

 ```python
 def tpack(text, width=100):
   """Pack a list of words into lines, so long as each line (including
   intervening spaces) is no longer than _width_"""
   lines = [text[0]]
   for word in text[1:]:
       if len(lines[-1]) + 1 + len(word) <= width:
           lines[-1] += (' ' + word)
       else:
           lines += [word]
   return lines
 ```

If you want line numbers:

 <pre><code class="language-python line-numbers">def tpack(text, width=100):
    """Pack a list of words into lines, so long as each line (including
    intervening spaces) is no longer than _width_"""
    lines = [text[0]]
    for word in text[1:]:
        if len(lines[-1]) + 1 + len(word) <= width:
            lines[-1] += (' ' + word)
        else:
            lines += [word]
    return lines
 </code></pre>

LaTeX markup examples

For inline equations, use \\( and \\) to delimit the equation, so

 And then some more text \\(\alpha \in \Gamma\\) to finish

will render as

And then some more text to finish

For block equations, use either $$ or \\[ / \\] to delimit the blocks, so

 $$\prod_{\mathfrak{p} \in \Omega}\(\frac{\alpha,-1}{\mathfrak{p}})=1$$
 and
 \\[(X,\beta) \oplus (X,-\beta) \text{ is split }.\\]

will render as:

and

Install ghostHunter search

Ghosthunter is a client-side search engine for Ghost blogs.

The instructions on the ghosthunter homepage are pretty good for covering the basics. The notes from Haunted Themes are a good supplement. However, I had to do a couple of other things.

1. Install the Content API package, so that ghosthunter can see the blog's content. (I think this shouldn't be necessary, but I seemed to require it.)

2. Add the site's host name to /assets/js/main.js, in the content-api-host field in the definition of the config object.

3. Change the prettyPubDate to pubDate in all the places it occurs, including files in the assets/ghosthunter/dist directory.

Startup scripts

The blog engine needs to start up when the system boots.

Create the file /lib/systemd/system/ghost_blog_domain_tld.service containing

[Unit]
Description=Ghost systemd service for blog: blog.domain.tld
Documentation=https://ghost.org/docs/
After=network-online.target remote-fs.target nss-lookup.target

[Service]
Type=simple
WorkingDirectory=/var/www/blog.domain.tld
User=999
Environment="NODE_ENV=production"
ExecStart=/usr/bin/node /usr/bin/ghost run
Restart=always

[Install]
WantedBy=multi-user.target

Ensure that SystemD knows about the new service:

root@server:~# systemctl daemon-reload

Start the service:

root@server:~#:systemctl start ghost_blog_domain_tld.service

That should be enough for the blog engine to start up on each boot.

See also