captainarkdotnet/public/2017/11/19/installing-ghost/index.html

688 lines
24 KiB
HTML

<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="Installing Ghost"/>
<meta name="twitter:description" content=""/>
<meta name="twitter:site" content="@"/>
<meta property="og:title" content="Installing Ghost &middot; Sysadmining. All day. Every day." />
<meta property="og:site_name" content="Sysadmining. All day. Every day." />
<meta property="og:url" content="https://www.captainark.net/2017/11/19/installing-ghost/" />
<meta property="og:image" content="/images/cover.jpg"/>
<meta property="og:description" content="" />
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2017-11-19T00:00:00&#43;01:00" />
<title>Installing Ghost &middot; Sysadmining. All day. Every day.</title>
<meta name="description" content="I haven&amp;rsquo;t published an article on here for over a year and a half&amp;hellip; While this was mostly due to a lack of motivation, another reason was that I did" />
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="apple-touch-icon" sizes="180x180" href="https://www.captainark.net/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="https://www.captainark.net/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="https://www.captainark.net/favicon-16x16.png">
<link rel="manifest" href="https://www.captainark.net/site.webmanifest">
<link rel="mask-icon" href="https://www.captainark.net/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#9f00a7">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" type="text/css" href="https://www.captainark.net/css/screen.css" />
<link rel="stylesheet" type="text/css" href="https://www.captainark.net/css/nav.css" />
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Merriweather:300,700,700italic,300italic|Open+Sans:700,400|Inconsolata:700,400" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/solarized-light.min.css" integrity="sha384-bFKDPkG3geCujYJIbPornilfOgmYQoPS45Oh/8daqqo1SUwNY06OeHorpgnNvx82" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js" integrity="sha384-BlPof9RtjBqeJFskKv3sK3dh4Wk70iKlpIe92FeVN+6qxaGUOUu+mZNpALZ+K7ya" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://www.captainark.net/js/hjsload.js"></script>
<link href="https://www.captainark.net/index.xml" rel="alternate" type="application/rss+xml" title="Sysadmining. All day. Every day." />
<meta name="generator" content="Hugo 0.54.0" />
<link rel="canonical" href="https://www.captainark.net/2017/11/19/installing-ghost/" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"publisher": {
"@type": "Organization",
"name": ,
"logo": https://www.captainark.net/images/logo.png
},
"author": {
"@type": "Person",
"name": ,
"image": {
"@type": "ImageObject",
"url": https://www.captainark.net/images/author.jpg,
"width": 250,
"height": 250
},
"url": https://www.captainark.net,
"sameAs": [
],
"description": Geek | Gamer | TV Shows Aficionado
},
"headline": Installing Ghost,
"name": Installing Ghost,
"wordCount": 1509,
"timeRequired": "PT8M",
"inLanguage": {
"@type": "Language",
"alternateName": en
},
"url": https://www.captainark.net/2017/11/19/installing-ghost/,
"datePublished": 2017-11-19T00:00Z,
"dateModified": 2017-11-19T00:00Z,
"description": ,
"mainEntityOfPage": {
"@type": "WebPage",
"@id": https://www.captainark.net/2017/11/19/installing-ghost/
}
}
</script>
<script type="text/javascript" src="https://www.captainark.net/js/stats.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous">
</head>
<body class="nav-closed">
<div class="nav">
<h3 class="nav-title">Menu</h3>
<a href="#" class="nav-close">
<span class="hidden">Close</span>
</a>
<ul>
<h3>This site</h3>
<li class="nav-opened" role="presentation">
<a href="https://www.captainark.net/">Home</a>
</li>
<li class="nav-opened" role="presentation">
<a href="https://www.captainark.net/about">About</a>
</li>
<li class="nav-opened" role="presentation">
<a href="https://www.captainark.net/resume">Resume</a>
</li>
<h3>Other services</h3>
<li class="nav-opened" role="presentation">
<a href="https://chat.captainark.net">Rocket.Chat</a>
</li>
<li class="nav-opened" role="presentation">
<a href="https://notes.captainark.net">CodiMD</a>
</li>
<li class="nav-opened" role="presentation">
<a href="https://git.captainark.net">Gitea</a>
</li>
<li class="nav-opened" role="presentation">
<a href="https://paste.captainark.net">Privatebin</a>
</li>
<li class="nav-opened" role="presentation">
<a href="https://pics.captainark.net">Pics</a>
</li>
</ul>
<a class="subscribe-button icon-feed" href="https://www.captainark.net/index.xml">Subscribe</a>
</div>
<span class="nav-cover"></span>
<div class="site-wrapper">
<header class="main-header post-head no-cover">
<nav class="main-nav clearfix">
<a class="blog-logo" href="https://www.captainark.net/"><img src="https://www.captainark.net/images/logo.png" alt="Home" /></a>
<a class="menu-button" href="#"><span class="burger">&#9776;</span><span class="word">Menu</span></a>
</nav>
</header>
<main class="content" role="main">
<article class="post post">
<header class="post-header">
<h1 class="post-title">Installing Ghost</h1>
<small></small>
<section class="post-meta">
<time class="post-date" datetime="2017-11-19T00:00:00&#43;01:00">
19 November 2017
</time>
</section>
</header>
<section class="post-content">
<p>I haven&rsquo;t published an article on here for over a year and a half&hellip; While this was mostly due to a lack of motivation, another reason was that I didn&rsquo;t enjoy the blogging system I was using.</p>
<p>As lightweight as <a href="https://blog.getpelican.com/">Pelican</a> is, I found it cumbersome to use on a regular basis. Every time I wanted to publish or update an article, I had to :
- edit local markdown files ;
- regenerate the website files ;
- start a webserver locally to proofread the article ;
- commit and push the files to my git repo ;
- pull the files on the webserver.</p>
<p>I hadn&rsquo;t had a look at the CMS landscape for a while, and I started searching for one with a web editor that supports markdown. I also wanted to avoid anything that runs on PHP if possible.</p>
<p>I quickly discovered <a href="https://ghost.org/">Ghost</a>, and decided to give it a shot. I was convinced by it within a few hours and I decided to migrate this blog.</p>
<p>So, to celebrate my move to Ghost, I figured I&rsquo;d write an article on how I&rsquo;ve installed it on my server.</p>
<p>All commands in this article have to be run as the <code>root</code> user on a Debian server.</p>
<h1 id="installing-nodejs">Installing nodejs</h1>
<p>Unlike most CMS (Wordpress, for example), Ghost is not files that you have to upload to a webserver, but a daemon that runs on <a href="https://nodejs.org/en/">nodejs</a>.</p>
<p>Here&rsquo;s the official recommended way of installing the current LTS version of nodejs on Debian :</p>
<pre><code class="language-bash">curl -sL https://deb.nodesource.com/setup_8.x | bash -
apt-get install -y nodejs
</code></pre>
<p>If, like me, you don&rsquo;t want to run a bash script downloaded from the internet on your server, here are the commands you have to run to install it manually.</p>
<p>Since the nodejs repo uses https, we&rsquo;ll first need to install the required package to use those :</p>
<pre><code class="language-bash">apt install apt-transport-https
</code></pre>
<p>We&rsquo;ll then have to add the nodejs repository public key to the system :</p>
<pre><code class="language-bash">curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
</code></pre>
<p>Now we have to add the nodejs repository to our sourcelist :</p>
<pre><code class="language-bash">echo 'deb https://deb.nodesource.com/node_8.x stretch main' &gt; /etc/apt/sources.list.d/nodesource.list
</code></pre>
<p>We can now install nodejs</p>
<pre><code class="language-bash">apt update
apt install nodejs
</code></pre>
<h1 id="system-configuration">System configuration</h1>
<p>Before installing Ghost, some system configuration is required.</p>
<p>First, let&rsquo;s create a new <code>ghost</code> system user that&rsquo;ll be used to run the Ghost daemon :</p>
<pre><code class="language-bash">useradd -s /bin/false -r -d /opt/ghost -m ghost
</code></pre>
<p>Ghost needs an empty folder for the automated installation script to work. For that purpose, let&rsquo;s create a subfolder in the <code>ghost</code> user home folder :</p>
<pre><code class="language-bash">sudo -Hu ghost mkdir /opt/ghost/app
</code></pre>
<h1 id="database">Database</h1>
<p>Ghost requires a MySQL/MariaDB database to store its data (technically, you could use a SQLite database, but please don&rsquo;t).</p>
<p>I personnally have all my databases stored on a single LXC container running MariaDB. However, if you need to, you can install MariaDB locally this way :</p>
<pre><code class="language-bash">apt install mariadb-server mariadb-common
</code></pre>
<p>We now have to declare a <code>ghost</code> user and database in the MariaDB shell :</p>
<pre><code class="language-mysql">create database ghost;
create user `ghost`@`%` identified by 'password';
grant all privileges on ghost.* to 'ghost'@`%`;
</code></pre>
<p>You can change the <code>%</code> to <code>localhost</code> in the <code>create user</code> command if you&rsquo;ve installed MariaDB locally. Please also remember to change <code>'password'</code> by an actual password.</p>
<p>Once that&rsquo;s done, we&rsquo;re ready to install Ghost !</p>
<h1 id="installing-ghost">Installing Ghost</h1>
<h2 id="ghost-cli">Ghost CLI</h2>
<p>To install Ghost, we first have to install the Ghost CLI :</p>
<pre><code class="language-bash">npm i -g ghost-cli
</code></pre>
<p>The Ghost CLI is a tool that lets you install, upgrade and manage your Ghost installation easily. Its usage is thoroughly documented on the official website <a href="https://docs.ghost.org/v1/docs/ghost-cli">here</a>.</p>
<h2 id="installing-ghost-1">Installing Ghost</h2>
<p>Let&rsquo;s install Ghost :</p>
<pre><code class="language-bash">cd /opt/ghost/app
sudo -Hu ghost ghost install --no-setup-nginx --no-setup-systemd --no-setup-linux-user --no-setup-mysql
</code></pre>
<p>The command will ask you for the following information :
- the URL of your website ;
- the hostname or IP of the server that&rsquo;s hosting your MariaDB installation ;
- the username to use to connect to the database (<code>ghost</code>) ;
- the password you&rsquo;ve configured for the database user ;
- the database name (<code>ghost</code>).</p>
<p>Once the script has finished running, you&rsquo;ve successfully installed Ghost ! However, the daemon won&rsquo;t start since we haven&rsquo;t configured systemd yet.</p>
<p>Since it contains a password, let&rsquo;s fix the permissions on our installation&rsquo;s configuration file to make sure it&rsquo;s not world-readable :</p>
<pre><code class="language-bash">chmod 600 /opt/ghost/app/config.production.json
</code></pre>
<p>As you can see from the <code>ghost install</code> command, it can install and configure pretty much all of its dependencies on its own. However, since I&rsquo;m a <a href="https://xkcd.com/705/"><em>sysadmin</em></a>, that&rsquo;s not how I roll.</p>
<h2 id="systemd-configuration">Systemd configuration</h2>
<p>As I wrote earlier, Ghost runs as a daemon. For us to be able to start it, we now need to declare a systemd unit file :</p>
<p>Let&rsquo;s create the file :</p>
<pre><code class="language-bash">vim /etc/systemd/system/ghost.service
</code></pre>
<p>And add the following content to it :</p>
<pre><code class="language-text">[Unit]
Description=Ghost systemd service
Documentation=https://docs.ghost.org
[Service]
Type=simple
WorkingDirectory=/opt/ghost/app
User=ghost
Group=ghost
Environment=&quot;NODE_ENV=production&quot;
ExecStart=/usr/bin/ghost run
[Install]
WantedBy=multi-user.target
</code></pre>
<p>We can now reload systemd an start Ghost :</p>
<pre><code>systemctl daemon-reload
systemctl start ghost.service
</code></pre>
<p>The daemon should now be running :</p>
<pre><code>pgrep -alf ghost
14184 ghost run
</code></pre>
<h1 id="nginx">Nginx</h1>
<p>With its default configuration, Ghost runs as a webserver on localhost, on a non-standard HTTP port (TCP 2368). For your website to be publicly browseable, you&rsquo;ll need to configure a webserver as a reverse-proxy in front of your Ghost installation. We&rsquo;ll use nginx for that purpose.</p>
<p>If you already have nginx running on a different server from your Ghost installation, you can use it for that purpose. For it to work, you&rsquo;ll need to edit the server host IP in Ghost&rsquo;s <code>config.production.json</code> configuration file with your Ghost server public IP and to restart Ghost. If you do so, make sure to limit direct access to your Ghost installation to the IP of your reverse-proxy by using iptables.</p>
<p>If you need to, you can install nginx locally this way :</p>
<pre><code class="language-bash">apt install nginx
</code></pre>
<p>I won&rsquo;t go into details on how to configure and secure a nginx installation here as it is beyond the scope of this article.</p>
<p>Here is my nginx configuration for this website :</p>
<pre><code> location / {
proxy_pass http://127.0.0.1:2368;
include proxy.conf;
add_header Front-End-Https on;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_set_header Authorization &quot;&quot;;
proxy_set_header Accept-Encoding &quot;&quot;;
proxy_redirect off;
}
location /ghost/ {
proxy_pass http://127.0.0.1:2368/ghost/;
allow 192.0.2.100;
deny all;
include proxy.conf;
add_header Front-End-Https on;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_set_header Accept-Encoding &quot;&quot;;
proxy_redirect off;
}
</code></pre>
<p>As you can see, I&rsquo;ve declared two location blocks :
- <code>/</code> is publicly visible by anyone ;
- <code>/ghost</code> (Ghost&rsquo;s administation interface) is only accessible from 192.0.2.100 (my public IP address).</p>
<p>I&rsquo;d rather have left Ghost&rsquo;s administation interface accessible from anywhere. However, since there is currently no way to replace <code>/ghost</code> by another subfolder and two-factor authentification is not available, I&rsquo;ve decided against it.</p>
<h1 id="monit">Monit</h1>
<p>As I mentionned in previous articles, I have monit running on all of my servers to make sure that my services are running and to restart them should they crash.</p>
<p>I&rsquo;ve created a configuration file for Ghost :</p>
<pre><code class="language-bash">vim /etc/monit/conf.d/ghost
</code></pre>
<p>With the following content :</p>
<pre><code class="language-text">check process ghost
matching &quot;ghost run&quot;
start program = &quot;/bin/systemctl start ghost&quot;
stop program = &quot;/bin/systemctl stop ghost&quot;
if changed pid then alert
if changed ppid then alert
</code></pre>
<p>Let&rsquo;s reload monit :</p>
<pre><code class="language-bash">monit reload
</code></pre>
<p>Ghost should now appear in your <code>monit summary</code>.</p>
<h1 id="logging">Logging</h1>
<p>Ghost writes its logs through <code>syslog</code>. If you don&rsquo;t want those messages to end up in <code>/var/log/syslog</code>, you&rsquo;ll have to configure your <code>syslog</code> daemon. For me, that&rsquo;s <code>syslog-ng</code>.</p>
<h2 id="syslog-ng">Syslog-ng</h2>
<p>Let&rsquo;s create a dedicated folder for the Ghost daemon&rsquo;s log files :</p>
<pre><code class="language-bash">mkdir /var/log/ghost
chown root:adm /var/log/ghost
</code></pre>
<p>Then, we need to create a configuration file :</p>
<pre><code class="language-bash">vim /etc/syslog-ng/conf.d/ghost.conf
</code></pre>
<p>And add the following content to it :</p>
<pre><code>filter f_ghost { program (&quot;ghost&quot;); };
destination d_ghost { file (/var/log/ghost/ghost.log); };
log { source(s_src); filter (f_ghost); destination (d_ghost); flags(final); };
</code></pre>
<p>We can now reload <code>syslog-ng</code> :</p>
<pre><code>service syslog-ng reload
</code></pre>
<p>Once that&rsquo;s done, Ghost should start logging in <code>/var/log/ghost/ghost.log</code>. Accessing a page on your site will create a new log entry, so that&rsquo;ll be enough to make sure it&rsquo;s working properly.</p>
<h2 id="logrotate">Logrotate</h2>
<p>As always with logs, let&rsquo;s configure logrotate to make sure we don&rsquo;t end up with huge files.</p>
<p>Let&rsquo;s create a new logrotate configuration file :</p>
<pre><code class="language-bash">vim /etc/logrotate.d/ghost
</code></pre>
<p>And add the following content to it :</p>
<pre><code class="language-text">/var/log/ghost/ghost.log {
rotate 8
weekly
notifempty
missingok
create 640 root adm
compress
copytruncate
}
</code></pre>
<p>There&rsquo;s no need to reload anything here. This new configuration file will be read by logrotate automatically next time its cron runs.</p>
<h1 id="conclusion">Conclusion</h1>
<p>This blog uses a previous version of Ghost&rsquo;s default theme, <a href="https://github.com/TryGhost/Casper">Casper</a>.</p>
<p>I&rsquo;ve modified it a bit, and I really enjoy how it looks now ! You can get the theme with my modifications from my <a href="https://github.com/captainark/Casper">GitHub</a> ! Credits to <a href="http://www.brycematheson.io/fixing-ghosts-default-casper-theme/">this article</a> for some of the changes, and thanks <a href="https://mastodon.fun/@aguay">@Aguay</a> for the help !</p>
<p>You&rsquo;ve also probably noticed that I now use a private installation of <a href="https://nodebb.org/">NodeBB</a> for the comments section. I&rsquo;ll probably write an article on how I&rsquo;ve installed and implemented it in my Ghost installation in the near future. In the meantime, please feel free to make use of it !</p>
</section>
<footer class="post-footer">
<figure class="author-image">
<a class="img" href="https://www.captainark.net/" style="background-image: url(/images/author.jpg)"><span class="hidden">Antoine Joubert's Picture</span></a>
</figure>
<section class="author">
<h4><a href="https://www.captainark.net/">Antoine Joubert</a></h4>
<p>Geek | Gamer | TV Shows Aficionado</p>
<div class="author-meta">
<span class="author-location icon-location">Angers, France</span>
<span class="author-link icon-link"><a href="https://www.captainark.net">https://www.captainark.net</a></span>
</div>
</section>
<!-- isso -->
<script data-isso="https://www.captainark.net/comments/" src="https://www.captainark.net/comments/js/embed.min.js"></script>
<noscript>Please enable JavaScript to view comments</noscript>
<section id="isso-thread"></section>
<!-- end isso -->
</footer>
</article>
</main>
<aside class="read-next">
<a class="read-next-story" style="no-cover" href="https://www.captainark.net/2018/04/14/dns-zone-versioning/">
<section class="post">
<h2>DNS zone versioning</h2>
</section>
</a>
<a class="read-next-story prev" style="no-cover" href="https://www.captainark.net/2016/03/26/webdav-with-nginx/">
<section class="post">
<h2>WebDAV with nginx</h2>
</section>
</a>
</aside>
<center>
<a class="fa-icons" href="mailto:contact@captainark.net">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-envelope fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://twitter.com/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-twitter fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://social.captainark.net/users/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-mastodon-alt fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://github.com/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-github fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://www.last.fm/user/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-lastfm fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://steamcommunity.com/id/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-steam fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://www.twitch.tv/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-twitch fa-stack-1x fa-inverse"></i>
</span>
</a>
</center>
<footer class="site-footer clearfix">
<section class="copyright"><a href="">Sysadmining. All day. Every day.</a> © 2015 - 2019</section>
<section class="poweredby">Proudly generated by <a class="icon-hugo" href="http://gohugo.io">HUGO</a>, with <a class="icon-theme" href="https://github.com/vjeantet/hugo-theme-casper">Casper</a> theme</section>
</footer>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" integrity="sha384-CgeP3wqr9h5YanePjYLENwCTSSEz42NJkbFpAFgHWQz7u3Zk8D00752ScNpXqGjS" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fitvids/1.1.0/jquery.fitvids.min.js" integrity="sha384-2/VQUb0aZHixKnNLh7pD38DZk+acGpEw5LeHieWVDPR0h/H326kp/1qnRPDYmFXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://www.captainark.net/js/index.js"></script>
</body>
</html>