<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Mind Drops - blog</title><link href="https://dmitry.khlebnikov.net/" rel="alternate"/><link href="https://dmitry.khlebnikov.net/feeds/blog.atom.xml" rel="self"/><id>https://dmitry.khlebnikov.net/</id><updated>2025-10-18T02:54:24+11:00</updated><subtitle>assorted bits of wit</subtitle><entry><title>Automating Static Website Deployment</title><link href="https://dmitry.khlebnikov.net/2020/05/17/automating-static-website-deployment/" rel="alternate"/><published>2020-05-17T21:32:00+10:00</published><updated>2025-10-18T02:54:24+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2020-05-17:/2020/05/17/automating-static-website-deployment/</id><summary type="html">&lt;p&gt;A comprehensive guide to setting up a fully automated deployment
pipeline for a static website using a private source repository and a
public GitHub Pages repository. The author details a process that
leverages GitHub Actions to build a Pelican-based site from a private
repository — keeping API tokens and experimental code secure—and then deploys the
generated static files to a public repository for hosting. Key steps include
configuring separate public and private repositories, using a deploy key for
secure authentication instead of a personal access token, and creating a
detailed GitHub Action workflow. This workflow automates checking out both
repositories, restoring file modification times for accurate content
generation, installing dependencies, building the site with Pelican, and
committing the changes to the public GitHub Pages repository only if new
content has been generated.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;&lt;span class="toctitle"&gt;Table of Contents&lt;/span&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#setting-up-github-pages"&gt;Setting up GitHub Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#setting-up-the-private-code-repository"&gt;Setting up the private, code repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#configuring-the-github-action-for-publishing"&gt;Configuring the GitHub Action for publishing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;In this post I am going to document the steps I took to implement a fully
automated deployment of my blog using GitHub Actions and GitHub Pages.&lt;/p&gt;
&lt;p&gt;As always, I started my journey with the definition of what I really wanted to
get at the end:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The website is published on GitHub pages&lt;/p&gt;
&lt;p&gt;Since the website is static and all of its content can be easily downloaded
using a web crawler (like &lt;code&gt;wget --mirror https://website.tld&lt;/code&gt;) I was OK
with exposing the structure in the public repository, which is what GitHub
offers on a free plan.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The code to generate the website should be private&lt;/p&gt;
&lt;p&gt;I do a lot of work on the &lt;abbr title="Static Site Generator"&gt;SSG&lt;/abbr&gt; (which is Pelican in my case) itself: extend
it with plug-ins that may contain &lt;abbr title="Application Programming Interface"&gt;API&lt;/abbr&gt; tokens to reach out to some third
party &lt;span&gt;&lt;abbr title="Application Programming Interface"&gt;API&lt;/abbr&gt;&lt;/span&gt;s, hack the core code when I want to quickly test stuff, etc. &amp;ndash;
so, I really did not have any desire to publish publicly all the commotions
I did in the background (sometimes I do more than a hundred commits per day
just to experiment with different ideas I have).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There should be a valid history of changes in both repositories&lt;/p&gt;
&lt;p&gt;Well, I would get the history on my private repository for free, since it
is the core value of maintaining a repository in the &lt;abbr title="Version Control System"&gt;VCS&lt;/abbr&gt;, but I also wanted
to have clean history of changes to the content I publish publicly.&lt;/p&gt;
&lt;p&gt;It would be a pleasant bonus if the changes in the public repository could
refer back to the corresponding commit in the private repository.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One may say that to do what I set out to do I would need to subscribe for a
paid account with GitHub since according to their &lt;a href="https://help.github.com/en/github/working-with-github-pages/getting-started-with-github-pages"&gt;help page&lt;/a&gt; GitHub Pages
for private repositories are only available on the paid plans.&lt;/p&gt;
&lt;p&gt;However, as I pointed out above, it does not make sense to hide the content of
the actual static website, hence all I needed to do is to find a way how to
&amp;ldquo;publish&amp;rdquo; the resulting artefact to the GitHub Pages repository, and,
preferably, that &amp;ldquo;publishing&amp;rdquo; should happen on GitHub&amp;rsquo;s side.&lt;/p&gt;
&lt;p&gt;Luckily for me, GitHub started to support GitHub Actions on the free plan some
time ago and as long as it is not abused according to their terms and
conditions, it is a perfect vehicle for what I am trying to do, in my opinion.&lt;/p&gt;
&lt;h2 id="setting-up-github-pages"&gt;Setting up GitHub Pages&lt;/h2&gt;
&lt;p&gt;There are multiple howtos and tutorials on the Internet regarding how to set GitHub
Pages up, including &lt;a href="https://help.github.com/en/github/working-with-github-pages/getting-started-with-github-pages"&gt;the official help section on this topic&lt;/a&gt;, so I will
only elaborate on details where I did something specific for the purposes of
achieving my goals.&lt;/p&gt;
&lt;p&gt;There are different types of GitHub Pages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;user or organisation&lt;/li&gt;
&lt;li&gt;per-project&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The difference between two is subtle (the former requires a dedicated
repository for your website, while the latter allows you to keep it in a branch
of the existing repository), but for the purposes of this article I am assuming
that we are working with the user level GitHub pages which are residing in the
repository named &amp;ldquo;&lt;strong&gt;&amp;lt;username&amp;gt;&lt;/strong&gt;.github.io&amp;rdquo; (where &lt;strong&gt;&amp;lt;username&amp;gt;&lt;/strong&gt;
is your GitHub user name) as per the official documentation.&lt;/p&gt;
&lt;p&gt;A few caveats I found and spent some time solving after following the official
documentation are listed below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GitHub&amp;rsquo;s documentation assumes use of Jekyll for the site generation.&lt;/p&gt;
&lt;p&gt;It is not obvious how to use a different &lt;abbr title="Static Site Generator"&gt;SSG&lt;/abbr&gt; (like Pelican).  As far as I
understand, there are multiple triggers for GitHub to consider that the
web site is in a &amp;ldquo;published&amp;rdquo; state, so just ignore any references to Jekyll
in the documentation: you will trip one of the triggers sooner or later,
for example by pushing HTML files into your repository.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure your &lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt; &lt;em&gt;before&lt;/em&gt; setting the custom domain name in GitHub Pages.&lt;/p&gt;
&lt;p&gt;Pushing the &lt;code&gt;CNAME&lt;/code&gt; file with the name of your custom domain within will
trigger a &lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt; check from GitHub to see that your custom domain name is
pointing back to GitHub Pages.&lt;/p&gt;
&lt;p&gt;&lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt; heavily relies on caching and is depending on the &lt;abbr title="Time To Live"&gt;TTL&lt;/abbr&gt; settings in your
zone: if a negative check is performed (that is, when GitHub fails to
retrieve the corresponding record) you will likely need to wait for quite a
while for GitHub to retry.&lt;/p&gt;
&lt;p&gt;Setting up the &lt;code&gt;CNAME&lt;/code&gt; record in advance and then verifying it with a query
&lt;em&gt;before&lt;/em&gt; you commit the &lt;code&gt;CNAME&lt;/code&gt; file to your repository ensures that you
will get the quickest validation response from GitHub, e.g. I set up my
&lt;code&gt;CNAME&lt;/code&gt; records and then verified it from the command line (before)
submitting the request to GitHub:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menub3624fa5c401a8f42a9ad178012f88e626ac6471" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menub3624fa5c401a8f42a9ad178012f88e626ac6471" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;host&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;cname&lt;span class="w"&gt; &lt;/span&gt;dmitry.khlebnikov.net&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.8.8.8
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Using domain server:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Name: 8.8.8.8&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Address: 8.8.8.8#53&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Aliases:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;dmitry.khlebnikov.net is an alias for galaxy4public.github.io.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are some shenanigans with the &amp;ldquo;Enforce HTTPS&amp;rdquo; option.&lt;/p&gt;
&lt;p&gt;It is not obvious from the documentation, but the enforcement of HTTPS for
custom domains on GitHub&amp;rsquo;s side is dependent on the several things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;before the checkbox is enabled your custom domain name should be
    confirmed by GitHub (your &lt;code&gt;CNAME&lt;/code&gt; file is in place and the repository
    settings show that the name was recognised);&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;CNAME&lt;/code&gt; record should point to your &amp;ldquo;&lt;strong&gt;&amp;lt;username&amp;gt;&lt;/strong&gt;.github.io.&amp;rdquo;
    &lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt; record (or, you can point it directly to GitHub Pages IP addresses
    if you want to conceal the repository name in the &lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt; output);&lt;/li&gt;
&lt;li&gt;if GitHub did not like something and you adjusted anything in the above
    dot points the &lt;strong&gt;only&lt;/strong&gt; way to trigger the enforcement of HTTPS is to
    re-submit the &lt;code&gt;CNAME&lt;/code&gt; file to the repository (yes, you read it right:
    you need to delete the file and push it to the repository again);&lt;/li&gt;
&lt;li&gt;Removing the &lt;code&gt;CNAME&lt;/code&gt; file from the repository is a disruptive action &amp;ndash;
    the site will not be accessible for the duration of the file being
    missing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OK, you have your public repository configured the way you want, so let&amp;rsquo;s look
at the settings we need to be able to publish our code to this public
repository.&lt;/p&gt;
&lt;p&gt;When I try to automate something, I usually start with writing down manual
steps I would do to achieve the results.  This helps me to see patterns and to
understand what I can easily automate and what will require some brain-storming
to resolve.&lt;/p&gt;
&lt;p&gt;In the case of updating the repository it is quite trivial: if I were to push
updates manually, all I need is a private &lt;abbr title="Secure Shell"&gt;SSH&lt;/abbr&gt; key with the corresponding public
&lt;abbr title="Secure Shell"&gt;SSH&lt;/abbr&gt; key configured with write privileges for the repository and I could push
with &lt;code&gt;git push&lt;/code&gt; from my local copy of the repository.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a name="quote-private-keys" href="#quote-private-keys" aria-hidden="true"&gt;&lt;/a&gt; &amp;hellip; private keys are called &amp;ldquo;private&amp;rdquo; for a reason &amp;ndash; they are not supposed
to leave the device under any circumstances. [&amp;hellip;] please pay attention when
you read of hear somebody advising you to upload your private keys somewhere,
it is usually bad advice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My private keys are called &amp;ldquo;private&amp;rdquo; for a reason &amp;ndash; they are not supposed to
leave my device(s) under any circumstances (except for backup purposes such as
storing them in a safe).  So, please pay attention when you read or hear
somebody advising you to upload your private keys somewhere, it is usually
bad advice.&lt;/p&gt;
&lt;p&gt;For the integration purposes, GitHub provides so-called &amp;ldquo;Deploy keys&amp;rdquo; and
&amp;ldquo;Personal access tokens&amp;rdquo;.  The former is just an &lt;abbr title="Secure Shell"&gt;SSH&lt;/abbr&gt; key pair associated with a
particular repository (you can configure it in repository&amp;rsquo;s setting) while the
latter is an OAuth access token associated with &lt;em&gt;your&lt;/em&gt; account.&lt;/p&gt;
&lt;p&gt;While you can successfully use both, I would recommend to use the &amp;ldquo;Deploy keys&amp;rdquo;
only: despite that you can try to scope access down for a personal token,
it would not be good enough and the actions performed using that token will
look like &lt;em&gt;you&lt;/em&gt; are executing them.&lt;/p&gt;
&lt;p&gt;To configure a &amp;ldquo;Deploy key&amp;rdquo; we need two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Generate an &lt;abbr title="Secure Shell"&gt;SSH&lt;/abbr&gt; key pair, e.g.:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuaa92d79e6e51ac7ad98647c1edb2562d3fd2f3f6" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuaa92d79e6e51ac7ad98647c1edb2562d3fd2f3f6" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh-keygen&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;ed25519&lt;span class="w"&gt; &lt;/span&gt;-N&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-C&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Updating the blog from GH Action&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;~/gh-action
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Generating public/private ed25519 key pair.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Your identification has been saved in /home/user/gh-action&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Your public key has been saved in /home/user/gh-action.pub&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;The key fingerprint is:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;SHA256:7W9pUV5IlrRVE0RAVkjLgKJz4RdtVbH7GKQu8AfYITw Updating the blog from GH Action&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;The key&amp;#39;s randomart image is:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;+--[ED25519 256]--+&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|          o.+BOX*|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|       + o o+.=oo|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|      o E +  =oo |&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|     o o B . oo o|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|      o S + .o.o |&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|         + o. .o.|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|          + oo. .|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|           ++    |&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|           o.    |&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;+----[SHA256]-----+&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, I chose the &lt;code&gt;ed25519&lt;/code&gt; key type since it is the shortest from the
GitHub supported key types at the moment, yet it is strong enough.&lt;/p&gt;
&lt;p&gt;I also made the key pair passphrase-less (&lt;code&gt;-N ''&lt;/code&gt;) since the purpose of the
key pair is to automate things in the unattended fashion and there will be
nobody to type in the passphrase.&lt;/p&gt;
&lt;p&gt;The key pair comment just makes it easier to maintain your keys, but is optional.&lt;/p&gt;
&lt;p&gt;Finally, the &lt;code&gt;-f ~/gh-action&lt;/code&gt; option specifies where the generated private
key is going to be stored.  The public counterpart will use the same path with
the &lt;code&gt;.pub&lt;/code&gt; suffix appended to it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the newly generated &lt;em&gt;public&lt;/em&gt; key up as the &amp;ldquo;Deploy key&amp;rdquo;:&lt;/p&gt;
&lt;p&gt;All you need to do is to go to the repository settings page for the public
 repository you created for GitHub Pages, click on &amp;ldquo;Deploy keys&amp;rdquo; in the
 left side menu, then click on the &amp;ldquo;Add deploy key&amp;rdquo; button in the upper
 right corner.&lt;/p&gt;
&lt;p&gt;On the next page, provide a sensible description for the deploy key (I
 used the same text as I put into the keys comment, i.e. &amp;ldquo;Updating the blog
 from GH Action&amp;rdquo;) and copy and paste the recently generated &lt;em&gt;public&lt;/em&gt; key.
 GitHub does not allow you to upload files over there, so you need to copy
 the content of the &lt;strong&gt;public&lt;/strong&gt; key file and paste it into the form, e.g.:&lt;/p&gt;
&lt;pre id="code3" class="highlight"&gt;&lt;input type="radio" name="code_menub22e806c4d34ece0b29796b1b2792941793e2f85" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menub22e806c4d34ece0b29796b1b2792941793e2f85" class="line-numbers icon list-numbered"&gt;&lt;code class="language-text" data-file="~/gh-action.pub"&gt;&lt;div id="code3.1" class="line"&gt;&lt;a href="#code3.1" aria-hidden="true"&gt;&lt;/a&gt;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGHCy+stVCBjsrVO2ld1DwKCwcKL9+i1sjxcZu4u4lFQ Updating the blog from GH Action&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You need to ensure that you tick the &amp;ldquo;Allow write access&amp;rdquo; checkbox,
 otherwise it would not be possible to push to the repository with the
 corresponding private key.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This, actually, concludes the configuration of the GitHub Pages repository for
now &amp;ndash; in later articles I will document how one could leverage the repository
Issues for managing comments on the web site and maintain the counters for
likes on the pages, but it would be a completely separate post :).&lt;/p&gt;
&lt;h2 id="setting-up-the-private-code-repository"&gt;Setting up the private, code repository&lt;/h2&gt;
&lt;p&gt;A typical Pelican repository layout is quite simple and comprises one
mandatory directory, one semi-mandatory file, and everything else is optional,
but could be used to enhance your experience.&lt;/p&gt;
&lt;p&gt;The mandatory directory is the so-called
&amp;ldquo;&lt;a href="https://docs.getpelican.com/en/stable/install.html#kickstart-your-site"&gt;content&lt;/a&gt;&amp;rdquo;
directory (in Pelican&amp;rsquo;s terms).  The name of the directory can be anything you
want, but it is better be reflected in &lt;a href="https://docs.getpelican.com/en/stable/settings.html#PATH"&gt;the &lt;code&gt;PATH =&lt;/code&gt;
directive&lt;/a&gt; of the
setting file.&lt;/p&gt;
&lt;p&gt;I am saying &amp;ldquo;better be&amp;rdquo; since Pelican can operate without any configuration
files, but the result will be limited, hence I call the &lt;code&gt;pelicanconf.py&lt;/code&gt; file
(which is the default name for the configuration file) to be &amp;ldquo;semi-mandatory&amp;rdquo;.
The name of the configuration file can be also anything you like, however, I
suggest to stick with the default for now.&lt;/p&gt;
&lt;p&gt;Basically, you can quickly start by following the Pelican documentation and
doing something as follows:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu660612c8263b4e0abe109b6ff3b7e08776303164" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu660612c8263b4e0abe109b6ff3b7e08776303164" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;~/venv/pelican
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;created virtual environment CPython3.8.2.final.0-64 in 477ms&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  creator CPython3Posix(dest=/home/user/venv/pelican, clear=False, global=False)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  seeder FromAppData(download=False, pip=latest, setuptools=latest, wheel=latest, via=copy, app_data_dir=/home/user/.local/share/virtualenv/seed-app-data/v1.0.1)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;~/venv/pelican/bin/activate
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;~/blog
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/blog
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;init
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Initialized empty Git repository in /home/user/blog/.git/&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--local&lt;span class="w"&gt; &lt;/span&gt;user.email&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;your@github-email.here&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--local&lt;span class="w"&gt; &lt;/span&gt;user.name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Joe Happy&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;pelican-quickstart&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Welcome to pelican-quickstart v4.2.0.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;This script will help you create a new Pelican-based website.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Please answer the following questions so this script can generate the files&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;needed by Pelican.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; Where do you want to create your new web site? [.] &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; What will be the title of this web site? My Awesome Blog&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; Who will be the author of this web site? Joe Happy&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; What will be the default language of this web site? [en] &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; Do you want to specify a URL prefix? e.g., https://example.com   (Y/n) n&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; Do you want to enable article pagination? (Y/n) &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; How many articles per page do you want? [10] &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; What is your time zone? [Europe/Paris] Australia/Melbourne&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; Do you want to generate a tasks.py/Makefile to automate generation and publishing? (Y/n) n&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Done. Your new project is available at /home/user/blog&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;total 16&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwxr-xr-x 2   user   user 4096 May 10 00:31 content&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwxr-xr-x 2   user   user 4096 May 10 00:31 output&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;-rw-r--r-- 1   user   user  869 May 10 00:31 pelicanconf.py&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;-rw-r--r-- 1   user   user  589 May 10 00:31 publishconf.py&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-rf&lt;span class="w"&gt; &lt;/span&gt;output&lt;span class="w"&gt; &lt;/span&gt;publishconf.py
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;content&lt;span class="w"&gt; &lt;/span&gt;pelicanconf.py&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Initial commit&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[master (root-commit) f077002] Initial commit&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; 1 file changed, 35 insertions(+)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; create mode 100644 pelicanconf.py&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A short break down of the above session snippet is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On line 1 we create a virtual Python environment, so we could install Pelican locally;&lt;/li&gt;
&lt;li&gt;We enter the newly created virtual environment on line 2, which makes Pelican available to us;&lt;/li&gt;
&lt;li&gt;We create an empty repository (&lt;code&gt;~/blog&lt;/code&gt;) and initialise it using Pelican&amp;rsquo;s quickstart;&lt;/li&gt;
&lt;li&gt;Since we are not using the default publishing capabilities and we are not interested in storing the generated pages in our code repository, we clean things up a bit;&lt;/li&gt;
&lt;li&gt;Finally, we commit the generated skeleton to Git.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A good test at this stage would be to ensure that Pelican is working and likes our structure:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu9ed554635baab30f3cc4e84f924e332b17bf3d0e" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu9ed554635baab30f3cc4e84f924e332b17bf3d0e" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;pelican
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;WARNING: No valid files found in content for the active readers:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  | BaseReader (static)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  | HTMLReader (htm, html)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  | MarkdownReader (md, markdown, mkd, mdown)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  | RstReader (rst)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Done: Processed 0 articles, 0 drafts, 0 pages, 0 hidden pages and 0 draft pages in 0.07 seconds.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So far so good, but it is not a real test, since there are no source files to generate something from, so let&amp;rsquo;s give Pelican something to work on:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuf690118aac4b259cb5f132a1d2aefc4155ddbd06" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuf690118aac4b259cb5f132a1d2aefc4155ddbd06" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Title: First Post\nDate: 2020-05-10\n\n#First post\nPelican is awesome!&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;content/first.md
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;pelican
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Done: Processed 1 article, 0 drafts, 0 pages, 0 hidden pages and 0 draft pages in 0.12 seconds.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;elinks&lt;span class="w"&gt; &lt;/span&gt;-dump&lt;span class="w"&gt; &lt;/span&gt;output/index.html&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;                               [1]My Awesome Blog&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;     • [2]misc&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;                                 [3]First Post&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;   Published: Sun 10 May 2020&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;    By [4]Joe Happy&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;   In [5]misc.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;                                   First post&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;   Pelican is awesome!&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output from &lt;code&gt;elinks&lt;/code&gt; was truncated on purpose since I just wanted to
showcase that Pelican has indeed generated the structure for a static website
from just one article file we created.&lt;/p&gt;
&lt;p&gt;Before we push our local repository to GitHub we may want to do some house
keeping first, e.g. create the &lt;code&gt;.gitignore&lt;/code&gt; file and list the temporary
things we do not want Git to track.  A good enough version of the &lt;code&gt;.gitignore&lt;/code&gt;
file I am using for my code repository is the following:&lt;/p&gt;
&lt;pre id="code7" class="highlight"&gt;&lt;input type="radio" name="code_menu7f4cf7b3cc8f2931067f3492b670d7935d5d410b" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu7f4cf7b3cc8f2931067f3492b670d7935d5d410b" class="line-numbers icon list-numbered"&gt;&lt;code class="language-txt" data-file=".gitignore"&gt;&lt;div id="code7.1" class="line"&gt;&lt;a href="#code7.1" aria-hidden="true"&gt;&lt;/a&gt;*~
&lt;/div&gt;&lt;div id="code7.2" class="line"&gt;&lt;a href="#code7.2" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="gs"&gt;*.pyc&lt;/span&gt;
&lt;/div&gt;&lt;div id="code7.3" class="line"&gt;&lt;a href="#code7.3" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="gs"&gt;.*&lt;/span&gt;.swp
&lt;/div&gt;&lt;div id="code7.4" class="line"&gt;&lt;a href="#code7.4" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code7.5" class="line"&gt;&lt;a href="#code7.5" aria-hidden="true"&gt;&lt;/a&gt;**/__pycache__
&lt;/div&gt;&lt;div id="code7.6" class="line"&gt;&lt;a href="#code7.6" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code7.7" class="line"&gt;&lt;a href="#code7.7" aria-hidden="true"&gt;&lt;/a&gt;/output&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not forget to actually commit that &lt;code&gt;.gitignore&lt;/code&gt; file to your local
repository using the &lt;code&gt;git add .gitignore &amp;amp;&amp;amp; git commit -m 'Added .gitignore'&lt;/code&gt;,
by the way.&lt;/p&gt;
&lt;p&gt;Now, we need to create a private repository on GitHub, so jump into your
browser, go to your GitHub account, press the &amp;ldquo;+&amp;rdquo; icon in the upper right
corner (right next to your profile icon), and select &amp;ldquo;New repository&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;On the &amp;ldquo;Create repository&amp;rdquo; page put whatever you desire as the name and the
description of the repository you are about to create.  Ensure that the
&amp;ldquo;Private&amp;rdquo; radio button is selected and uncheck the &amp;ldquo;Initialize this repository
with a README&amp;rdquo; if it was checked.&lt;/p&gt;
&lt;p&gt;Once the repository is created, you will be presented with a page that
enumerates your options for the next step, but I will just go ahead and show a
session dump of what you will need to do.  In the following session snippet
&lt;code&gt;blog&lt;/code&gt; is the repository name I chose for my private code repository and you
will need to replace it with your private repository name (the working
directory is our newly created local repository):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu767b12e1270d9628ac06fe6c1271acf21d6806c8" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu767b12e1270d9628ac06fe6c1271acf21d6806c8" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;git@github.com:galaxy4public/blog.git
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;master
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Enter passphrase for key &amp;#39;/home/user/.ssh/keys/github&amp;#39;: &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Enumerating objects: 6, done.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Counting objects: 100% (6/6), done.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Delta compression using up to 4 threads&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Compressing objects: 100% (4/4), done.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Writing objects: 100% (6/6), 1008 bytes | 1008.00 KiB/s, done.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Total 6 (delta 0), reused 0 (delta 0), pack-reused 0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;To github.com:galaxy4public/blog.git&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; * [new branch]      master -&amp;gt; master&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Branch &amp;#39;master&amp;#39; set up to track remote branch &amp;#39;master&amp;#39; from &amp;#39;origin&amp;#39;.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do you remember how we generated a deploy key pair earlier and installed the
public key part into the public blog repository, so GitHub would allow the
bearer of the private key to authenticate and deploy changes to the public blog
repository?  Well, since the purpose of this article is to introduce the full
automation, the bearer of the key would be the GitHub Action associated with
the private repository, hence we need to provide the action with the private
key somehow.&lt;/p&gt;
&lt;p&gt;GitHub has a feature called &amp;ldquo;repository secrets&amp;rdquo; and it is a perfect candidate
to pass the private key to the GitHub Action.  We need to follow the &lt;a href="https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository"&gt;official
documentation for the feature&lt;/a&gt; and create a secret called &amp;ldquo;DEPLOY_KEY&amp;rdquo; with the
content of the private part of the deploy key.  This will be used in the last step
of the GitHub Action we are about to define.&lt;/p&gt;
&lt;h2 id="configuring-the-github-action-for-publishing"&gt;Configuring the GitHub Action for publishing&lt;/h2&gt;
&lt;p&gt;Everything is well and good, but &amp;ldquo;where is the automation?&amp;rdquo; you may ask.  After
all, I suspect this was the primary reason you are reading this post.  Well, we
are about to start to look into the automation part and it is rather short in
comparison to all the steps we did to set repositories up.&lt;/p&gt;
&lt;p&gt;Our automation relies on the GitHub Action feature of GitHub.  In plain terms,
GitHub Action is a free compute resource provided by GitHub (there are some
limits, but for the purposes of a personal blog it is unlikely that you will
ever hit these limits).&lt;/p&gt;
&lt;p&gt;Each GitHub Action is associated with a specific repository and is defined
using quite a simple &lt;abbr title="YAML Ain't Markup Language"&gt;YAML&lt;/abbr&gt; configuration file which instructs GitHub on how to
provision a required compute environment and what to run inside that
environment.  The &lt;abbr title="YAML Ain't Markup Language"&gt;YAML&lt;/abbr&gt; file can be arbitrarily named and resides in the
&lt;code&gt;.github/workflows/&lt;/code&gt; subdirectory (starting from the root of the corresponding
repository).&lt;/p&gt;
&lt;p&gt;The GitHub Action I am using for my blog web site is stored in
&lt;code&gt;.github/workflows/pelican.yml&lt;/code&gt; and contains the following (we will dissect it
further down the post):&lt;/p&gt;
&lt;pre id="code9" class="highlight"&gt;&lt;input type="radio" name="code_menu606d183290ef7658eed278296fc46835e3446c0f" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu606d183290ef7658eed278296fc46835e3446c0f" class="line-numbers icon list-numbered"&gt;&lt;code class="language-yaml" data-file=".github/workflows/pelican.yml"&gt;&lt;div id="code9.1" class="line"&gt;&lt;a href="#code9.1" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Static Website Generator&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.2" class="line"&gt;&lt;a href="#code9.2" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code9.3" class="line"&gt;&lt;a href="#code9.3" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.4" class="line"&gt;&lt;a href="#code9.4" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.5" class="line"&gt;&lt;a href="#code9.5" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;master&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.6" class="line"&gt;&lt;a href="#code9.6" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code9.7" class="line"&gt;&lt;a href="#code9.7" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.8" class="line"&gt;&lt;a href="#code9.8" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;LANG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;en_AU.UTF-8&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.9" class="line"&gt;&lt;a href="#code9.9" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code9.10" class="line"&gt;&lt;a href="#code9.10" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.11" class="line"&gt;&lt;a href="#code9.11" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.12" class="line"&gt;&lt;a href="#code9.12" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code9.13" class="line"&gt;&lt;a href="#code9.13" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ubuntu-latest&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.14" class="line"&gt;&lt;a href="#code9.14" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code9.15" class="line"&gt;&lt;a href="#code9.15" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.16" class="line"&gt;&lt;a href="#code9.16" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Initialise locale&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.17" class="line"&gt;&lt;a href="#code9.17" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.18" class="line"&gt;&lt;a href="#code9.18" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;if [ &amp;quot;$LANG&amp;quot; != &amp;#39;C&amp;#39; -a &amp;quot;{$LANG:0:2}&amp;quot; != &amp;#39;C.&amp;#39; ]; then&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.19" class="line"&gt;&lt;a href="#code9.19" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;CP=&amp;quot;${LANG#*.}&amp;quot; &amp;amp;&amp;amp; [ -z &amp;quot;$CP&amp;quot; ] &amp;amp;&amp;amp; CP=UTF-8 ||:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.20" class="line"&gt;&lt;a href="#code9.20" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;sudo sed -i -E &amp;quot;/^\s*$LANG(\s|\$)/{:a;n;ba;q};\$a$LANG $CP&amp;quot; /etc/locale.gen&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.21" class="line"&gt;&lt;a href="#code9.21" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;sudo locale-gen&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.22" class="line"&gt;&lt;a href="#code9.22" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;sudo localectl set-locale LANG=&amp;quot;${LANG:-C.UTF-8}&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.23" class="line"&gt;&lt;a href="#code9.23" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.24" class="line"&gt;&lt;a href="#code9.24" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;locale -a&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.25" class="line"&gt;&lt;a href="#code9.25" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Checkout the primary repo&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.26" class="line"&gt;&lt;a href="#code9.26" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/checkout@v2&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.27" class="line"&gt;&lt;a href="#code9.27" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.28" class="line"&gt;&lt;a href="#code9.28" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;fetch-depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.29" class="line"&gt;&lt;a href="#code9.29" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;submodules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;recursive&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.30" class="line"&gt;&lt;a href="#code9.30" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Restore modification times for content&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.31" class="line"&gt;&lt;a href="#code9.31" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.32" class="line"&gt;&lt;a href="#code9.32" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;git log --pretty=tformat:&amp;quot;%at&amp;quot; --name-status --no-merges -- \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.33" class="line"&gt;&lt;a href="#code9.33" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;content \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.34" class="line"&gt;&lt;a href="#code9.34" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;themes/mind-drops/content \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.35" class="line"&gt;&lt;a href="#code9.35" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;| sed -nE &amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.36" class="line"&gt;&lt;a href="#code9.36" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;/^\s*$/d;/^[[:digit:]]+$/{h;d};&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.37" class="line"&gt;&lt;a href="#code9.37" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;/^[UXB]/d;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.38" class="line"&gt;&lt;a href="#code9.38" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;/^[AMT]/{s,^\S\s+,,;G;s,^(.+)\n(.+),\2 \1,;p};&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.39" class="line"&gt;&lt;a href="#code9.39" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;/^[DR]/{&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.40" class="line"&gt;&lt;a href="#code9.40" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="no"&gt;s,^(D|[CR][[:digit:]]+)\s+,\1 ,;G;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.41" class="line"&gt;&lt;a href="#code9.41" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="no"&gt;s,^(\S+) ([^[=\t=]]+[[=\t=]])?(.*)\n(.+),\4\1 \3,;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.42" class="line"&gt;&lt;a href="#code9.42" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="no"&gt;p&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.43" class="line"&gt;&lt;a href="#code9.43" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;}&amp;#39; \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.44" class="line"&gt;&lt;a href="#code9.44" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;| LC_ALL=C sort -k2 -k1rn | uniq -f1 \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.45" class="line"&gt;&lt;a href="#code9.45" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;| sed -E &amp;#39;/^[[:digit:]]+D /d&amp;#39; \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.46" class="line"&gt;&lt;a href="#code9.46" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;| while read TSTAMP FILE; do&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.47" class="line"&gt;&lt;a href="#code9.47" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;if [ -f &amp;quot;$FILE&amp;quot; ]; then&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.48" class="line"&gt;&lt;a href="#code9.48" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="no"&gt;echo &amp;quot;$FILE =&amp;gt; $(date -d @$TSTAMP)&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.49" class="line"&gt;&lt;a href="#code9.49" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="no"&gt;touch -m -d &amp;quot;@$TSTAMP&amp;quot; -- &amp;quot;$FILE&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.50" class="line"&gt;&lt;a href="#code9.50" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.51" class="line"&gt;&lt;a href="#code9.51" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;done&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.52" class="line"&gt;&lt;a href="#code9.52" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Checkout Pages repo&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.53" class="line"&gt;&lt;a href="#code9.53" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/checkout@v2&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.54" class="line"&gt;&lt;a href="#code9.54" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.55" class="line"&gt;&lt;a href="#code9.55" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;galaxy4public/galaxy4public.github.io&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.56" class="line"&gt;&lt;a href="#code9.56" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;output&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.57" class="line"&gt;&lt;a href="#code9.57" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Set up Python&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.58" class="line"&gt;&lt;a href="#code9.58" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/setup-python@v2&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.59" class="line"&gt;&lt;a href="#code9.59" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.60" class="line"&gt;&lt;a href="#code9.60" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;3.x&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.61" class="line"&gt;&lt;a href="#code9.61" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Install dependencies&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.62" class="line"&gt;&lt;a href="#code9.62" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.63" class="line"&gt;&lt;a href="#code9.63" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;python -m pip install --upgrade pip&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.64" class="line"&gt;&lt;a href="#code9.64" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;pip install -r requirements.txt&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.65" class="line"&gt;&lt;a href="#code9.65" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Generate the website&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.66" class="line"&gt;&lt;a href="#code9.66" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.67" class="line"&gt;&lt;a href="#code9.67" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;ls -laR content/&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.68" class="line"&gt;&lt;a href="#code9.68" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;rm -rf output/*&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.69" class="line"&gt;&lt;a href="#code9.69" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;ls -la output/&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.70" class="line"&gt;&lt;a href="#code9.70" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;TIMEZONE=$(sed -nE &amp;#39;s|^\s*TIMEZONE\s*=\s*[&amp;#39;&amp;quot;&amp;#39;&amp;quot;&amp;#39;&amp;quot;]([^&amp;#39;&amp;quot;&amp;#39;&amp;quot;&amp;#39;&amp;quot;]+)[&amp;#39;&amp;quot;&amp;#39;&amp;quot;&amp;#39;&amp;quot;].*|\1|;T;p&amp;#39; pelicanconf.py)&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.71" class="line"&gt;&lt;a href="#code9.71" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;TZ=&amp;quot;${TIMEZONE:-UTC}&amp;quot; pelican&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.72" class="line"&gt;&lt;a href="#code9.72" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Publish to GitHub Pages&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.73" class="line"&gt;&lt;a href="#code9.73" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.74" class="line"&gt;&lt;a href="#code9.74" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;DEPLOY_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;${{ secrets.DEPLOY_KEY }}&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.75" class="line"&gt;&lt;a href="#code9.75" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.76" class="line"&gt;&lt;a href="#code9.76" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;cd output&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.77" class="line"&gt;&lt;a href="#code9.77" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;git config --local user.email &amp;quot;action@github.com&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.78" class="line"&gt;&lt;a href="#code9.78" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;git config --local user.name &amp;quot;GitHub Action&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.79" class="line"&gt;&lt;a href="#code9.79" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;if output=$(git status --porcelain) &amp;amp;&amp;amp; [ -z &amp;quot;$output&amp;quot; ]; then&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.80" class="line"&gt;&lt;a href="#code9.80" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;echo &amp;quot;No new content was generated, exiting gracefully&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.81" class="line"&gt;&lt;a href="#code9.81" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;else &lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.82" class="line"&gt;&lt;a href="#code9.82" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;git add -A&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.83" class="line"&gt;&lt;a href="#code9.83" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;git commit -m &amp;quot;Updated content on $(date)&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.84" class="line"&gt;&lt;a href="#code9.84" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;eval $(ssh-agent)&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.85" class="line"&gt;&lt;a href="#code9.85" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;echo &amp;quot;$DEPLOY_KEY&amp;quot; | ssh-add -t 5m /dev/stdin&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.86" class="line"&gt;&lt;a href="#code9.86" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;ssh-add -l&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.87" class="line"&gt;&lt;a href="#code9.87" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;git push git@github.com:galaxy4public/galaxy4public.github.io.git&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.88" class="line"&gt;&lt;a href="#code9.88" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;ssh-add -D&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.89" class="line"&gt;&lt;a href="#code9.89" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;ssh-agent -k&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.90" class="line"&gt;&lt;a href="#code9.90" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.91" class="line"&gt;&lt;a href="#code9.91" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;cd ..&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.92" class="line"&gt;&lt;a href="#code9.92" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;echo Completed&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a copy of my live GitHub Action for deploying my blog that you are most
likely reading right now and I decided not to edit anything, so if you just
want to re-use it you will need to replace a few things, namely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;en_AU.UTF-8&lt;/code&gt; =&amp;gt; to a locale you are using (you can run &lt;code&gt;locale -a&lt;/code&gt; if you
    are running Linux to see the list of locales available on your system);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;content&lt;/code&gt; =&amp;gt; you may need to change that to the name of your content
    directory (if you did not use the default name);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;themes/mind-drops/content&lt;/code&gt; =&amp;gt; you will need to drop this line since it is
    my theme&amp;rsquo;s content directory and you would not have it;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;galaxy4public/galaxy4public.github.io&lt;/code&gt; =&amp;gt; to &lt;strong&gt;&amp;lt;your_username/your_blog_repo_name&amp;gt;&lt;/strong&gt;, obviously :)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s look a bit more closely to understand how this GitHub Action is
structured and what each step is doing.&lt;/p&gt;
&lt;p&gt;It all starts with the definition of the action itself, the conditions of
how it is triggered, and how it runs: you can get a formal description of
the &lt;abbr title="YAML Ain't Markup Language"&gt;YAML&lt;/abbr&gt; structure of this configuration file in the official GitHub
documentation on &lt;a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions"&gt;Workflows&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here, we are only going to focus on steps defined under the &amp;ldquo;jobs:&amp;rdquo; section of
the file since these steps are defining the logic we are after.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Initialise locale&amp;rdquo; step is quite important for Pelican since with a
misconfigured locale Pelican tends to produce incorrect output (which is kind
of expected).  So in this step we are trying to determine whether the user (us
:) ) has supplied the &lt;code&gt;LANG&lt;/code&gt; variable and if they did we update
&lt;code&gt;/etc/locale.gen&lt;/code&gt; file, run the &lt;code&gt;locale-gen&lt;/code&gt; command to update the
corresponding files, and set the locale of the container to the
requested locale.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Checkout the primary repo&amp;rdquo; step is leveraging the official &amp;ldquo;Checkout V2&amp;rdquo;
Action and checks out a full copy of the source code repository of our blog
and all the linked submodules.  Initially, I was using a shallow copy using
&lt;code&gt;fetch-depth: 1&lt;/code&gt;, but the next step was requiring the full repository history
to do its job reliably and I changed it to be a full history clone.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;git&lt;/code&gt; &lt;a href="https://git.wiki.kernel.org/index.php/GitFaq#Why_isn.27t_Git_preserving_modification_time_on_files.3F"&gt;does not store timestamps&lt;/a&gt; for the files and directories under its
control, yet Pelican relies on timestamps to populate the modification time of
the artefacts &amp;ndash; we need to find a way to reconstruct at least file timestamps
after the tree was checked out.  One of the possible approaches would be to
create a plugin that could determine whether we are inside a &lt;code&gt;git&lt;/code&gt; working tree
or not and depending on that apply different timestamp extraction policies, but
I thought that a much easier way would be to prepare the checked out tree,
hence making it compatible with the way Pelican expects things to be.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Restore modification times for content&amp;rdquo; step is my variant of how one
could reconstruct the timestamps for files close enough to make it possible to
use with Pelican.  The approach relies on the fact that &lt;code&gt;git&lt;/code&gt; records the
timestamp of each commit including adding, updating, and deleting files.  We
create a list of all these file events using &lt;code&gt;git log&lt;/code&gt; for file trees under
&amp;ldquo;content&amp;rdquo; (where our blog content lives) and &amp;ldquo;themes/mind-drops/content&amp;rdquo; (where
my custom theme injects some content such as the Web service worker script),
then we use &lt;code&gt;sed&lt;/code&gt; to filter and to re-arrange the output a bit, followed by
reverse sorting to help to remove the entries that were introduced and later
deleted.  In the end, we have a list of file names with timestamps, so we go
through the list in a loop and set the timestamps to files using &lt;code&gt;touch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Checkout Pages repo&amp;rdquo; step is cloning the public blog repository into the
&amp;ldquo;output&amp;rdquo; directory where Pelican will put the generated files.  This is needed
to ensure that we can track the changes to the public repository, since Pelican
is careful enough (if not instructed otherwise) only to update the files it
generates and leave everything else in place as is.  We use this later to
determine whether any new content has been generated or not.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Set up Python&amp;rdquo; and the &amp;ldquo;Install dependencies&amp;rdquo; steps are pretty generic:
the former is using the official GitHub Action to install and configure the
latest available version of Python 3.x and the latter is leveraging &lt;code&gt;pip&lt;/code&gt; to
install all blog&amp;rsquo;s dependencies (including Pelican itself).&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Generate the website&amp;rdquo; step is running &lt;code&gt;pelican&lt;/code&gt; to process our articles
and pages and to generate the result in the &amp;ldquo;output&amp;rdquo; directory.  There are a
couple of tricks with this step, though.&lt;/p&gt;
&lt;p&gt;The first trick, which is not that obvious, is that we are removing the content
of the &amp;ldquo;output&amp;rdquo; directory.  It seems a bit weird since we just checked it out
several steps before, does not it?  Well, we are removing everything &lt;strong&gt;except&lt;/strong&gt;
hidden files and directories which happen to contain the &amp;ldquo;.git&amp;rdquo; subdirectory
with all the actual data about the repository.  Why do we do it?  It is simple,
this helps us to determine a situation if some file or directory was removed,
so we could propagate that knowledge to the public blog repository.  If we did
not clean up the content of the &amp;ldquo;output&amp;rdquo; directory we would only append new
changes and would never remove anything &amp;ndash; this is how it was before I stumbled
upon the problem, by the way. :)&lt;/p&gt;
&lt;p&gt;The second trick of the &amp;ldquo;Generate the website&amp;rdquo; step is the extraction of the
time zone information from the configuration files and is setting the &lt;code&gt;TZ&lt;/code&gt;
variable correctly just before we call &lt;code&gt;pelican&lt;/code&gt;.  Without this either Pelican
may fail or if it does not it will produce &lt;abbr title="Coordinated Universal Time"&gt;UTC&lt;/abbr&gt; based date and times, which
would be undesirable (at least for me, since my time zone is in Australia).&lt;/p&gt;
&lt;p&gt;The final step is to push the updated content to the public blog repository,
which will make it visible via GitHub Pages.  Several things to notice there
are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In the &lt;code&gt;env:&lt;/code&gt; section we are setting up the &lt;code&gt;DEPLOY_KEY&lt;/code&gt; variable &amp;ndash; this
     syntax is used to retrieve a named secret value from the associated secret
     key for a repository.  We store the private part of the deploy key we
     specifically generated for this purpose at the beginning of this article
     in the private repository secret.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git status&lt;/code&gt; is used to determine whether there are any changes between
     what we have in the working tree and the repository index.  If no changes
     were detected we just exit gracefully.&lt;/li&gt;
&lt;li&gt;If any change to the generated content was detected, we temporarily load
     the private part of the deploy key into the &lt;code&gt;ssh-agent&lt;/code&gt; (for 5 minutes),
     push changes to the public blog repository, then clean up the key from
     the agent and kill the agent itself.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From this point on, any push to the private codebase repository will trigger
the GitHub Action and if the change has resulted in any updated content such
content will be published to GitHub Pages!&lt;/p&gt;
&lt;p&gt;There are quite a few things we could improve: such as introducing a broken
links check, doing some sanity checks, etc. &amp;ndash; but this would be for another
article, I guess. :)&lt;/p&gt;</content><category term="blog"/><category term="pelican"/><category term="blog"/><category term="github"/></entry><entry><title>Migrating blog to Pelican</title><link href="https://dmitry.khlebnikov.net/2020/05/01/migrating-blog-to-pelican/" rel="alternate"/><published>2020-05-01T20:00:00+10:00</published><updated>2025-10-15T23:06:20+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2020-05-01:/2020/05/01/migrating-blog-to-pelican/</id><summary type="html">&lt;p&gt;Dissatisfied with the Blogger platform&amp;rsquo;s changing features and lack of
control, the author decided to migrate their decade-old blog to a self-hosted,
low-maintenance solution. After researching static site generators (SSGs) that
aligned with Jamstack principles and met their requirements for stability,
extensibility, and language (preferring Python), they chose Pelican over
contenders like Jekyll and Gatsby. The author then undertook a significant
theme development project, initially attempting to port an existing HTML theme
called &amp;ldquo;Striped&amp;rdquo; but ultimately deciding to recreate a similar theme from
scratch due to inconsistencies in the original code. The resulting website is
the foundation for a planned series of posts about the development process and
infrastructure.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I had my blog site for more than a decade now, but until now I was not putting
any effort or thoughts into maintaining my audience or promoting the site.  It
was dormant for nearly a decade so I decided to rejuvenate it and start using
it as a platform I could leverage to share some ideas I think are worth
sharing.&lt;/p&gt;
&lt;p&gt;Before I embarked on the journey of renovating the blog site I needed to set
some goals and requirements up, so I would be able to assess my progress and
estimate how much effort is required.  The primary goal is already known at
this point: I need a tool that would allow me to easily share my ideas, grow
and nurture the audience, and accumulate knowledge in one place over time.&lt;/p&gt;
&lt;p&gt;I started to be increasingly unhappy with the &lt;a href="https://blogger.com"&gt;Blogger&lt;/a&gt; platform: they were
changing things and as the result parts of my blog became defunct, e.g.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;in 2017, I lost the comment capability and did not have enough time to
    investigate and fix it,&lt;/li&gt;
&lt;li&gt;in 2018, my theme became incompatible with the new trends of the platform,&lt;/li&gt;
&lt;li&gt;and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Therefore, the first requirement became to self-host my blog site, so I would
have full control over the software that is supporting the site.  This would
give the flexibility and determinism of how the site is configured and operates.&lt;/p&gt;
&lt;p&gt;The downside for hosting the site myself is that it would require ongoing
maintenance cost (primarily calculated in the time I spend on the maintenance)
and with my quite busy schedule I was not sure that I could afford it on the
ongoing basis.  So the second requirement became the low maintenance cost of
the solution.  Preferably, I wanted to make changes only when I needed to
change site&amp;rsquo;s functionality.&lt;/p&gt;
&lt;p&gt;I did some research and figured that a static website generator could be a
solution that addressed both aforementioned requirements assuming that I could
find a place where all underlying maintenance of the infrastructure and
application layer handled by somebody else (and, preferably, free of charge).&lt;/p&gt;
&lt;p&gt;There are plenty of static site generators (&lt;abbr title="Static Site Generator"&gt;SSG&lt;/abbr&gt;).  According to some articles,
there are more than 400 different &lt;abbr title="Static Site Generator"&gt;SSG&lt;/abbr&gt; at the moment, so the process of
selecting one that is right for you could be quite challenging.  I decided to
define what I would like to see in the generator I could use and be happy about
it before I start shopping around:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;should be written in one of the scripting languages that are widely adopted&lt;/p&gt;
&lt;p&gt;This requirement comes from my desire of running that software on a
platform that is fully managed by somebody else (my initial thought was to
host the site in an &lt;abbr title="Amazon Web Services"&gt;AWS&lt;/abbr&gt; S3 bucket and generate the site using an &lt;abbr title="Amazon Web Services"&gt;AWS&lt;/abbr&gt; Lambda
function)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;should be lean with minimum dependencies for the generator itself&lt;/p&gt;
&lt;p&gt;The more dependencies software has the more likely it is to get an
unexpected behaviour once one of the upstream dependencies make an
undesirable breaking change.  I really wanted stability and to touch the
configuration in rare occasions only when I needed to change the behaviour
of my site and not in an adhoc response to an upstream breaking change.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;should be extensible and should support a plug-in mechanism&lt;/p&gt;
&lt;p&gt;I have a really high bar on what the final result should be, e.g. valid and
standard compliant HTML and CSS documents, semantic structuring, etc.  In
order not to be limited by the generator I needed a way on how I could
influence the generation process without patching or tweaking the core of
the generator itself, since doing it this way would mean maintaining a fork
of the generator and as I mentioned above, it does not fit the second
requirement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the template language should be flexible and should allow conditional logic&lt;/p&gt;
&lt;p&gt;Over the years I worked with multiple template engines and I found that the
most comfortable engines are those that potentially allow you to break the
concept of separating design from code.  Not that I recommend doing so, but
having such power gives you yet another flexible interface to express
yourself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the architecture of the generator should feel clean, stable, and thought out&lt;/p&gt;
&lt;p&gt;I do not know how to easily describe this, but my understanding of the well
engineered software is when its layout is simple to understand, structured,
yet easily extendable without introducing any invasive changes to the
structure, e.g. callbacks/hooks in the key transition points, so you could
hook the external code up and influence the logic, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A bit more research brought me to the realisation that there is a trend in the
community and it has a name: &lt;a href="https://jamstack.org/"&gt;Javascript, API, and Markup Stack(Jamstack)&lt;/a&gt;.
The ideas behind Jamstack resonate with my vision on how I wanted to run my
blog, so if you want to understand the reasoning behind many choices I made
their website would be a good place to start reading about the approach.&lt;/p&gt;
&lt;p&gt;After a lot of consideration the following three &lt;span&gt;&lt;abbr title="Static Site Generator"&gt;SSG&lt;/abbr&gt;&lt;/span&gt;s were the primary
contenders to become the engine of my blog site:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://jekyllrb.com/"&gt;Jekill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gatsbyjs.org/"&gt;Gatsby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://getpelican.com/"&gt;Pelican&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of these three were aligned to the requirements, however I have an
indescribable allergy to NodeJS ecosystem and I prefer Python over Ruby, hence
Pelican was the first generator to spike with and to see whether we are a match
or not.&lt;/p&gt;
&lt;p&gt;The more I worked with Pelican the more I was falling in love with the
software: with less than 600K of source code spread across just 15 files (with
a couple being redundant in my configuration) it is packed with features and is
very extensible through a plug-in system.&lt;/p&gt;
&lt;p&gt;The next step was to create the templates (the collection of templates used to
generate the output is called a theme in Pelican).  While I was spiking I found
a theme I somewhat liked (&lt;a href="https://brutalistpelican.com/"&gt;Brutalist&lt;/a&gt; by Matt
McManus), then I found a free theme that I liked a lot
(&lt;a href="https://html5up.net/striped"&gt;Striped&lt;/a&gt; by AJ), but that theme was just an
HTML/CSS/JavaScript concept which was unrelated to Pelican in any way.&lt;/p&gt;
&lt;p&gt;Initially, I started to port the Striped theme to Pelican using the Brutalist
theme as the wireframe, but in the middle of that work I realised that despite
the Striped theme was posed as &amp;ldquo;&lt;q cite="https://html5up.net/striped"&gt;a free, fully responsive HTML5 site
template&lt;/q&gt;&amp;rdquo; it was generated in a haste and was not that responsive as the
claim would make you think.  There were lots of inconsistencies and no
overarching conventions on how things were structured within.&lt;/p&gt;
&lt;p&gt;After a while, struggling to fix everything I was not happy with in Striped
theme&amp;rsquo;s code, I decided to take a different approach: I decided to re-create
Striped-like Pelican theme from scratch using Pelican&amp;rsquo;s default theme called
&amp;ldquo;simple&amp;rdquo; as the foundation.&lt;/p&gt;
&lt;p&gt;The result (which is still an ongoing process) as you can see has some
resemblance to the theme I was taking inspiration from, but on the other hand
it is quite different and standalone in its own right.  Up until some point I
was even retaining &amp;ldquo;Design by HTML5 UP&amp;rdquo; at the bottom of the left hand side
menu, but when I realised that almost nothing left of the original design and
layout, I removed it, the theme evolved much further that the inspirational
theme I started with.&lt;/p&gt;
&lt;p&gt;This post is already getting too long, so I will conclude with the following:
I am going to publish a series of posts covering the development of this
website, the tips and tricks I learnt over the course of creating it, and the
infrastructure supporting the site and its deployment process in detail.&lt;/p&gt;
&lt;p&gt;All blog posts related to these topics will be tagged with the &amp;ldquo;&lt;a href="/tag/blog/"&gt;blog&lt;/a&gt;&amp;rdquo; tag, so
you should be able to find them easily (or even subscribe to the &lt;a href="/feeds/blog.atom.xml"&gt;blog RSS feed&lt;/a&gt;
(you may need to copy the link into your RSS Reader) to get the latest updates
as soon as I publish them).&lt;/p&gt;</content><category term="blog"/><category term="pelican"/><category term="blog"/></entry></feed>