Autumn in Graubünden

, ,

It’s that time of the year when the trees show their nicest colours. What better time to visit the most Eastern part of Switzerland, the Canton of Graubünden (Grisons) and its narrow-gauge railway network operated by the Rhaetian Railway (RhB)? If you take the train from Freiburg (Germany) at 6 in the morning, it is possible to go as far as Pontresina in the Upper Engadin (or even further South as far as Alp Grüm or so) and return shortly after midnight the following day. So I got up early only to find out that my Nightjet train from Hamburg to Zürich was indefinitely delayed. Fortunately, I could take a regional train to Basel instead and arrived in Basel only 30 minutes later as planned.

Basel SBB

After another change in Zürich, I finally reached Graubünden and therefore RhB territory at Landquart.


For the outward journey, I decided to take the line through the Vereina tunnel to Sagliains and change there to the Engadin line towards Samedan. Here you can see my train leaving Klosters shortly before entering the Vereina tunnel.


From Sagliains, which is famous for its use as a connecting station, the Engadin line originating in Scuol-Tarasp near the Swiss-Austro-Italian border triangle passes along the Inn. In Autumn, the Engadin line is particularly scenic because of the yellow-coloured Larch trees filling the landscape.

Engadin Line

Engadin Line

Engadin Line

After merging with the Albula line from Chur in Bever, the next stop is Samedan, where the line continues straight to St. Moritz. The train from Scuol-Tarasp however makes a left turn to reach its end point at Pontresina where it meets the Bernina line from St. Moritz to Tirano in Italy.


Between Pontresina and Punt Muragl, both the Bernina line and the Pontresina line (as the line from Samedan is called here) run parallel to each other, the Pontresina line being powered by RhB’s usual 11kV AC while the Bernina line is being powered by 1 kV DC. Here is an Allegra unit carrying a Bernina line train towards Tirano.

Bernina Line near Punt Muragl

From Punt Muragl I took the next train to Samedan where I changed to the following InterRegio train on the Albula line to Chur. From Bever, the line runs along the Bever river until reaching the Albula Tunnel, the second highest tunnel through the Swiss Alps (only the Furka Summit Tunnel is higher, at Spinas.

Albula Line between Bever and Spinas

After leaving the tunnel at Preda, the railway descends to Bergün through the famous Albula Carousel, which includes two curved tunnels and three spiral tunnels.

Albula Line at Preda

Albula Line between Preda and Bergün

Albula Line between Preda and Bergün

From Bergün the train heads to Filisur, where you can change to Davos. I got out at Filisur in order to go on a small hike to a viewing point for the famous Landwasser Viaduct further along the line towards Thusis.

Landwasser Viaduct

Back on the train I crossed the Landwasser Viaduct myself. As you can see, my train consisted of another Allegra unit (which can cope with both 16 kV AC and 1kV DC) and a couple of the new Alvra coaches. Fortunately for the photographer, those coaches include a photo compartment (filled with Asian tourists) with windows that can be opened.

Alvra train on the Landwasser Viaduct

Leaving the Landwasser Viaduct behind, the line follows the eponymous Albula river until its confluence with the Hinterrhein at Thusis.

Albula Line between Filisur and Thusis

After Thusis, the line (now simply called the Landquart-Thusis Line) runs parallel to the Hinterrhein to Reichenau, where Hinterrhein and Vorderrhein merge to form the Rhine. Shortly afterwards, the train reaches Chur, the capital of Graubünden, where I changed to an SBB Intercity to Zürich.



From Zürich, another Intercity train brought me to Basel, where I finally caught one of DB’s infamous Intercity night trains (IC 208 to Kiel via Cologne), which together with ÖBB’s NightJet trains have replaced all of the former CityNightLine night trains. Fortunately, there was no need to spend the whole night on that train, since we arrived at Freiburg not even one hour after the departure at Basel SBB.

Secure Hosting with GitLab Pages and Let’s Encrypt

, ,

I’ve finally acted on my promise from 2016 to enable HTTPS on this blog, so your connection to this website is now encrypted.

Change of Plans

In contrast to my original plan of using GitHub Pages in connection with CloudFlare, I’ve decided to use GitLab Pages in connection with Let’s Encrypt for obtaining the TLS/SSL certificate. While GitHub Pages can be set up more easily, GitLab Pages has the advantage that you are not tied to a specific site generator or build process: you just describe how your site is built from the sources in a GitLab CI configuration and the output of that process is then copied to the web server. In particular, even if you use Jekyll as you would normally do with GitHub Pages, you can use any plugin you want and not just the ones that are integrated into GitHub Pages.

TLS support for GitLab Pages

Unfortunately, configuring TLS for your custom domain is more tricky with GitLab Pages than with GitHub Pages, where TLS is now standard for custom domains with automatic certificate management: With GitLab Pages you first need to obtain a TLS certificate for your domain, which you can then upload to (including the private key, of course). So how do you get a valid TLS certificate for your domain if you do not want to pay a substantial fee to one of the corporate certificate authorities?

Let’s Encrypt

In order to boost encryption on the web, the Internet Security Research Group (ISRG) has founded a certificate authority which offers TLS certificates for free: Let’s Encrypt. The catch is that these certificates are quite short lived (they expire after 90 days), so your certificate needs to be renewed frequently.

While GitLab is working on integrating Let’s Encrypt into GitLab Pages, you currently need to generate and renew the certificate by yourself and upload it to your GitLab Pages configuration. Fortunately, by using GitLab’s API for uploading the certificate from within a scheduled job in GitLab CI, this process can be fully automated.

Obtaining a TLS certificate from Let’s Encrypt

But first, let us review how you can obtain a certificate for your domain from Let’s Encrypt. Since Let’s Encrypt is offering certificates for free and requires certificates to be renewed frequently, obtaining and renewing a certificate needs to work without labour-intensive manual intervention, so that the process is fully automatic: Using a command-line tool called certbot, you send a request for obtaining a certificate to Let’s Encrypt which returns a challenge for proving that you’re in control of the domain for which the certificate is requested (authentication). After completing the challenge successfully, Let’s Encrypt responds with the certificate (the public key including the full certificate chain and the private key) so you can set up your web server to serve that certificate to the user (installation). In fact, depending on which plugin you use (certbot comes with plugins e.g. for Apache and Nginx), certbot performs authentication and installation by itself under the cover, so that you do not need to cope with that yourself.

If you use GitLab pages, you do not have full control over the web server so that most of the plugins will not be useful to you. However, if you are lucky enough that your DNS provider offers an API to modify DNS entries, you can use one of the DNS plugins for authentication (if not, you can still use manual mode, but that is harder to automate). Here the challenge is to add a specific DNS entry to your domain. Fortunately, my DNS provider INWX offers such an API and a certbot plugin is available on GitHub, installable as a Python package via pip. As for installation, we will use the certonly command of certbot to generate and retrieve the certificate only but skip installation; we can then upload and configure the generated certificate using GitLab’s API.

Putting it all together

We are now able to set up a job called update_cert in GitLab CI that requests a certificate from Let’s Encrypt and uses GitLab’s API to configure it on the web server. Since on, GitLab CI only supports the Docker executor, we need to specify a Docker image first, and we will use the official certbot Docker image for that.

    name: certbot/certbot
    entrypoint: [""]

We need to overwrite the default entrypoint so that we have shell access. The certbot image is based on the official python image (python:2-alpine3.7 to be precise) so that we can use pip for installing Python packages. Next we define two variables which contain the path where certbot will put the resulting certificate, respectively the private key.

    CERT_FILE: "/etc/letsencrypt/live/$DOMAIN/fullchain.pem"
    KEY_FILE: "/etc/letsencrypt/live/$DOMAIN/privkey.pem"

DOMAIN is a variable that points to your domain (i.e. in our case Next, in order to use certbot with INWX, we need to install the inwx DNS plugin and write a configuration file containing the credentials for accessing INWX’s API. The credentials itself are passed in as protected variables which we can configure in GitLab’s UI, so that they are hidden from somebody browsing your repository. We also need to install curl so that we can access GitLab’s API later on.

    - apk add --no-cache curl
    - pip install
    - echo "certbot_dns_inwx:dns_inwx_url =" > inwx.cfg
    - chmod 600 inwx.cfg
    - echo "certbot_dns_inwx:dns_inwx_username = $INWX_USER" >> inwx.cfg
    - echo "certbot_dns_inwx:dns_inwx_password = $INWX_PASSWORD" >> inwx.cfg
    - echo "certbot_dns_inwx:dns_inwx_shared_secret = optional" >> inwx.cfg

Now for the main script: First we call certbot with the certonly and the required options to generate the certificate (see Running with Docker). Then we use curl for uploading the resulting certificate to GitLab using their API.

    - certbot certonly -n --agree-tos -a certbot-dns-inwx:dns-inwx -d $DOMAIN -m $GITLAB_USER_EMAIL --certbot-dns-inwx:dns-inwx-credentials inwx.cfg
    - "curl --silent --fail --show-error --request PUT --header \"Private-Token: $GITLAB_TOKEN\" --form \"certificate=@$CERT_FILE\" --form \"key=@$KEY_FILE\"$CI_PROJECT_ID/pages/domains/$DOMAIN"

Finally, we configure the job so that it only runs as part of a scheduled pipeline (or on manual execution) and not as part of the normal integration build.

    - schedules

Since the script can be used for obtaining a new certificate as well as for replacing an existing one, we just need to set up a scheduled pipeline that runs e.g. on the 1st of every even month in order to always serve a valid certificate.


The full GitLab CI configuration including the job to build the website after a push to the master branch can be found here.

Going Static


After almost 10 years, I have decided to ditch Wordpress for Jekyll, a static blog engine. As opposed to Wordpress, Jekyll is not dependent on MySQL and PHP, but simply generates static HTML files from sources in Markdown, so you can create your blog posts comfortably using your favourite text editor, let Jekyll generate the HTML and put it on an arbitrary web server. As the server only needs to serve the HTML pages, the attack surface is greatly reduced.

Like Wordpress, Jekyll boasts a huge number of themes, of which I have chosen the excellent Minimal Mistakes theme in order to create a fresh but familiar look for this blog.

Since many image links in older posts were broken due to an outdated Gallery installation, I have decided to only convert posts from 2014 or later. Moreover, while I was able to copy the comments from these posts, I have not yet decided how to let people submit new comments since this requires at least some dynamic server code. Hence, at the moment it is not possible to post comments on this blog.

After restoring comment support, I am planning to move the blog over to GitHub Pages and CloudFlare GitLab Pages, so I can finally serve this site over HTTPS. For now you can already browse the complete source code on GitHub GitLab.