5 min read

New year, new blog

New year, new blog

It's been quite literally years that I've been putting off updating my blog, both in the underlying technology as well as the content that resides within. While August is probably eight months too late to invoke the new year in a blog title, it's a theme that I can work around.

The high level

My blog in its old invocation has been around since May 2012, using mostly the same technology and thematic components. Its age however definitely shows when its compared to more modern SaaS based blogging platforms (I'm looking at you Medium).

My old website.

One thing I was passionate about back in 2012 and still remain passionate about now is open-source software and keeping my eye in technically. It would be out of character for me to:

  1. Use proprietary technology to power my personal blog
  2. Use a SaaS platform which takes all of the fun out of understanding the tech and learning something new

To keep my blog, and underlying server free of vulnerabilities to this point, I've been semi-regimented about updating Drupal 7 soon after each release. To keep myself free of technical debt in the future however, I needed to update to Drupal 8. Because this was an involved task, I waited long enough for Drupal 9 to be released and I honestly couldn't be bothered.

The other thing that I couldn't get past for years is how much the 'look and feel' of my blog stunk.

This yearning for a more basic platform as well as one that looks great out of the box put me onto Ghost. It's open-source, it doesn't really allow much customisation (so it's easier to update), and looks literally 1000x better than what I made myself.

The details

While I could have used the SaaS version of Ghost and paid for the convenience, my technical eye likes to remain involved so I went down the DIY route. I've documented the process I took below using Ansible to provision it onto one of my servers.

Looking at Ghost's requirements, I already had pretty much everything installed on my server. That said, I've been using Apache instead of NGINX, and I religiously use configuration management so I'm more safe in the event of catastrophe. I recently had a home server die on me and thanks to Ansible was back up and running 2 hours after I had a new USB stick.

My server's main.yml for ghost looks something like this (unrelated components have been removed):

# Apache configuration
apache_listen_port: 80
apache_remove_default_vhost: true
apache_ssl_protocol: "all -SSLv3"
apache_ssl_cipher_suite: "HIGH:!aNULL"
apache_mods_enabled:
  - rewrite.load
  - ssl.load
  - proxy.load
  - proxy_http.load
  
apache_global_vhost_settings: |
  ServerName localhost
  <VirtualHost *:80>
    <Location />
        Deny from all
        Options None
        ErrorDocument 403 Forbidden.
    </Location>
  </VirtualHost>

apache_vhosts:
  - servername: "adammalone.net"
    serveralias: "www.adammalone.net"
    documentroot: "/var/www/html/ghost"
    extra_parameters: |
            ProxyRequests Off
            <proxy>
            # Require all granted
            </proxy>
            AllowEncodedSlashes On
            ProxyPass / http://127.0.0.1:2368/ nocanon
            ProxyPassReverse / http://127.0.0.1:2368/
            
apache_vhosts_ssl:
  - servername: "adammalone.net"
    serveralias: "www.adammalone.net"
    documentroot: "/var/www/html/ghost"
    certificate_file: "/etc/ansible/keys/adammalone.crt"
    certificate_key_file: "/etc/ansible/keys/adammalone.key"
    extra_parameters: |
            RewriteEngine on
            RewriteCond %{HTTP_HOST} ^adammalone.net$
            RewriteRule ^/(.*)$ https://www.adammalone.net/$1 [L,R=301]
            ProxyRequests Off
            <proxy>
            # Require all granted
            </proxy>
            AllowEncodedSlashes On
            ProxyPass / http://127.0.0.1:2368/ nocanon
            ProxyPassReverse / http://127.0.0.1:2368/
            
mysql_packages:
  - mariadb-client
  - mariadb-server
  - python-mysqldb
  
mysql_expire_logs_days: "7"
mysql_root_password: 'foobar'
mysql_bind_address: '127.0.0.1'
mysql_key_buffer_size: "164M"
mysql_max_allowed_packet: "64M"
mysql_table_open_cache: "750"
mysql_innodb_buffer_pool_size: "164M"
mysql_config_include_files:
  - name: 'my.overrides.cnf'
    src: '/etc/ansible/cnf/my.overrides.cnf'

mysql_databases:
  - {name: ghost, encoding: utf8mb4, collation: utf8mb4_general_ci}
mysql_users:
        - {name: ghost, host: localhost, password: 'foobar)', priv: 'ghost.*:ALL', append_privs: 'yes', state: 'present' }

nodejs_version: "12.x"
nodejs_install_npm_user: "typhonius"
nodejs_npm_global_packages:
  - ghost-cli

My server.yml looks similar to the below

- hosts: localhost
  vars_files:
    - vars/main.yml
  roles:
    - { role: geerlingguy.apache }
    - { role: geerlingguy.mysql }
    - { role: geerlingguy.nodejs }

Challenges

One of the stumbling blocks I spent some time fixing were the MySQL configuration in /etc/ansible/cnf/my.overrides.cnf. I kept getting the following error when running ghost install.

Message: alter table `members_stripe_customers` add unique `members_stripe_customers_customer_id_unique`(`customer_id`) - ER_INDEX_COLUMN_TOO_LONG: Index column size too large. The maximum column size is 767 bytes.

Looking around online, I realised that I needed that I'd be able to fix this issue by changing the innodb_default_row_format MySQL variable to dynamic rather than compact. The documentation told me that dynamic did much the same as compact but allows for variable length column lengths i.e. those over 767 bytes long.

I think I could also have changed by default encoding from utf8mb4 to utf8, but doing so would have prevented me from being able to use the extended character set (hello emojis?).

The Ansible role I use to manage MySQL had no parameter that I could easily adjust to change row formats, but it does have the ability to add custom parameters by file inclusion. My custom override is simple and below.

innodb_default_row_format = 'DYNAMIC'

Another challenge I faced was how to configure Apache to both proxy requests to a node server listening on a local port as well as passing through the correct parameters in the right format.

The keys to solving that particular problem were to ensure that I had the right Apache mods loaded (rewrite, ssl, proxy, proxy_http) and that I used the right parameters in my vhost directives. I needed to set AllowEncodedSlashes to on and add nocanon to ProxyPass. Both of these allowed me to pass URLs (and slashes) through from Apache to the Node backend and get the right results from the Ghost API.

The final issue I had was in updating the injected scripts I was planning to use in the header and footer. My access to the API was blocked when I tried to add anything with <script> tags. Understandably my first (and correct) suspect was Cloudflare.

I adjusted my configuration to restrict my admin URL to my home IP and VPN. I also added a firewall rule to remove managed rules on those path so I could adjust the settings and save to the database. As an aside, I did also work out that I could manually alter the codeinjection_head/codeinjection_foot keys in the settings table before restarting ghost to allow the changes to take effect.

With these relatively trivial issues fixed, I ported my content across, added some far nicer images from Unsplash, and set up my site how I wanted.

The future

I've missed writing.

I've long said that creativity manifests itself in a larger number of ways than images, music, words, and pictures. I postulate that creativity also comes from tech architecture, some neat code to solve a problem, or pretty much anything that can automate something away or increase utility.

This blog has been a place for me to creatively speak about my creative endeavours, and I want to continue doing that. I'll try to write about the things I'm playing with and intersperse with my own ideas, opinions and thoughts where appropriate.