My Blog - Server Set-Up
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.