home blog tags about

My Blog - Server Set-Up

written by Robin Schubert on 2018-06-26 | Tags: free-software, wiki, miscellaneous

This whole blog is mainly about Free Software and some code snippets and patterns I use, that I spent some time with. That's why I dedicate this index page to the creation on this blog itself. For now the server and project is quite fresh, I will update it with any changes when they approach.

Motivation

I started this blog on wordpress, which is a nice service to quickly get started blogging. However, after some time I started to be annoyed by some aspects;

I don't want readers to be confronted with ads if possible, but I also didn't want to apply for a paid subscription. Not because I'm too greedy to pay for a good service, but because despite the offer of plugins I don't feel free to customize my page the way I want to. Also I cannot tell which services and JavaScript packages run on the page that I identify with, that may track readers of my blog etc.

So I decided to go with my own server (or better rented VM) that I have the most possible freedom and security on.

Setting up the Server

I took inspiration from Anwesha Das in her posts on how to set up a server and how to use Let's Encrypt with nginx to provide readers with a secure connection. I rent a small VM for ~ 10 Euros per annum, nothing fancy since I hardly have any hardware requirements, installed CentOS 7.5 and did some hardening as described in Anwesha's post.

To be prepared for my own failure, I've set up most things using Ansible playbooks, so I can easily recover a fresh server state if I mess up anything. To install basic packages I do stuff like

- name: "Installing epel repository and yum packages"
  yum:
    pkg: "{{ item }}"
    state: installed
  with_items:
  - epel-release
  - yum-utils

- name: "Update packages"
  yum: name=* state=latest update_cache=yes

- name: "Installing some basic packages"
  yum:
    pkg: "{{ item }}"
    state: installed
  with_items:
  - git
  - man
  - python-pip
  - cronie
  - vim

As webserver and proxy I use nginx. Certbot is a tool to request signed certificates from the Electronic Frontier Foundation (EFF). Encrypting the own website has never been that easy!

- name: "Installing nginx"
  yum:
    pkg: "{{ item }}"
    state: installed
  with_items:
  - nginx
  - yum-utils
  - epel-release

- name: "Update packages"
  yum: name=* state=latest update_cache=yes

- name: "Install Certbot"
  yum:
    pkg: python2-certbot-nginx
    state: installed

Again I could re-use many of the nginx settings shared in Anwesha's post. I don't need the Docker parts, however. With the signed ssl certificates in place, I can now safely redirect all http web requests:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name blog.schubisu.de;
    return 301 https://blog.schubisu.de$request_uri;
}

Once certbot was running correctly, I've set up a cron job to renew the certificate regularly:

0 0,12 * * * certbot renew

I'm quite happy with this server configuration, it feels small, simple and secure.

Static HTML Blog with Lektor

Lektor is a Python based Content Management System (CMS) to create and maintain static websites. It's a versatile and hackable tool, equally suitable for quick and small blogs like this or big professional projects.

As the documentation is excellent, I won't repeat it here. Lektor uses ini-files do declare models, yaml-like files called contents.lr as model instances and the powerful ninja2 template engine to render the HTML.

In the yaml syntax of the contents.lr files, content can be specified as plain text, html or markdown (there's a plugin that also supports restructured text). Model instances can be queried in the ninja2 templates and in model definitions; the syntax feels quite familiar to me as a django user.

While everything in Lektor is configured in plain text files, the CMS allows editing and managing content via admin web interface as well.

This blog is based on one of the many useful guides from the Lektor documentation. It declares two models, one that describes blog-post instances, and one that describes the page that holds blog-post instances as children.

What I'm particularly happy about is the fact, that I don't need anything of this on my server. I configured the Lektor project to deploy the page with rsync. A simple lektor build && lektor deploy will render the static HTML pages and push them to the server. I'm not sure if it could be any easier.

Further Additions

There are some minor features I wanted my blog to equip with.

Code Highlighting

I'm not a big fan of JavaScript and was able to avoid it throughout the page so far. However, to make the code snippets I include easier to read I at least want to have code highlighting in my <pre><code>...</code></pre> tags. I found Prism very easy to set up and implement.

Apart from the theme I can choose which languages to support to avoid the js file to become unnecessary big. Finally I add class="language-<programminglanguage>" to enable proper highlighting.

Update:

As stated above, I was not very happy with a JS solution on my blog. Learning more and more about the Lektor ecosystem (just a few seconds of searching would have brought me there quickly) I discovered the lektor plugin markdown-highlighter which makes use of Pythons pygments module. I don't know why I opted for a JS solution in the first place, how could I forget that I have the full power of Python in hands with Lektor? Now the most difficult question is which style to choose among the wide range that pygments offers.

Tags

The Lektor documentation already gave examples on how to sort instances by date. Since I may include posts of very different topics I also wanted to add tags, and the possibility to filter posts by tags.

Therefore I add another set of models; tags.ini and tag.ini. While tags.ini is quite boring (it specifies that it's children are of type tag), the trick is done by the children directive in tags.ini:

[children]
replaced_with = site.query('/blog').filter(F.tags.contains(this))

at the same time, I added a tags field to the blog-post model:

[fields.tags]
label = Tags
type = checkboxes
source = site.query('/tags')

RSS Feed

To easily update readers about new or changed contents on my site, I want to have an rss.xml file that contains the necessary information about the blog posts in the rss standard format.

To always keep the rss.xml file up to date, I wrote a plugin for Lektor that uses setup_env as entry point, so everytime Lektor builds the HTML sites, the xml file is created.

I use the Python package feedgen to generate and export the final rss.xml.

from lektor.pluginsystem import Plugin
from feedgen.feed import FeedGenerator
from datetime import datetime
import pytz
import os

class RssPlugin(Plugin):
    name = u'lektor-rss'
    description = u'Plugin to create an atom rss feed from selected models.'
    title = "Schubisu's blog feed"
    author = {
        'name': 'Robin Schubert',
        'email': 'robin.schubert@gmx.de'
    }
    destination_dir = '/home/robin/gitrepos/myserver/myblog/content'
    def on_setup_env(self):
        pad = self.env.new_pad()
        fg = FeedGenerator()
        fg.id('https://blog.schubisu.de')
        fg.title(self.title)
        fg.author(self.author)
        fg.link(href='https://blog.schubisu.de/rss.xml', rel='self')
        fg.description("Schubisu's blog feed")
        fg.language('en')
        for post in pad.query('blog').all():
            fe = fg.add_entry()
            fe.title(post['title'])
            fe.id(post.path)
            fe.link(href="http://blog.schubisu.de" + post.url_path)
            fe.description(post['body'].source[:200] + "...")
            fe.published(
                datetime.combine(
                    post['pub_date'],
                    datetime.min.time()
                ).replace(tzinfo=pytz.timezone('Europe/Berlin'))
            )
            fe.content(src="http://blog.schubisu.de" + post.url_path)
        fg.rss_file(os.path.join(self.destination_dir, 'rss.xml'))

In the <head> tag of my HTML base layout I add

<link rel="alternate" type="application/rss+xml" title="Schubisu's blog feed" href="https://blog.schubisu.de/rss.xml">

to notify the browser about the rss feed.

Update:

While this was a nice exercise, I realized that the HTML content should better be escaped in the xml file. While browsing for the simplest solution I had to find out that there is a plugin for Lektor, that generates my feed the way I want it. How could I think that this has not been done before? I was searching for rss plugins before but it turned out that the package is actually called lektor-atom.

So a simple lektor plugins add lektor-atom and configuring the configs/atom.ini almost did it for me. The lektor plugins statement added lektor-atom = 0.3 to my .lektorproject file, however, I'm using a Python2.7 based version that doesn't have version 0.3 in the PyPI. So switching to 0.2 installed the requirements smoothly and I'm happy with the result.

Update:

Although the Lektor documentation does not recommend to install the package via pip, this was the easiest solution for me to get Python3 support. Another option would have been to modify the package build in the AUR installation. I don't experience any issues with the pip installation, however. So upgraded the to lektor-atom 0.3 again Yay!

License

I have licensed the contents of this blog under a Creative Commons Attribution-ShareAlike license. Anything on this site can be copied, changed and re-distributed provided a similar free license is used.

Creative Commons License