Post

ENG | Static website generator: Jekyll and Chirpy

ENG | Static website generator: Jekyll and Chirpy

This post is about Jekyll static site generator (SSG) and Chirpy theme. Brief info about SSGs is here.

Motivation to use Jekyll is to write sites completely in markdown, which is basically plain text file with some simple formatting. They are processed into HTML afterwards by jekyll build command. Advantage is versioning of pages by git(hub) and no need for a database which makes backup easier.

Chirpy is quite nice theme which has a few advantages over standard minima theme.

Advantages of Chirpy

  • Looks pretty good by default
  • It’s a bit easier to override CSS styles
  • Displays tags
  • Generates Table of Content
  • Supports separate images for dark and light theme

Disadvantages

  • It’s so good that author deserves a donation :)

Install process

  • Install ruby and some dependencies as root
1
dnf in rubygems ruby-devel openssl-devel @development-tools rubygem-jekyll nodejs-npm
  • Clone git repository (optionally only one branch)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[jekyll@marten ~]$ mkdir blog

[jekyll@marten ~]$ cd blog

[jekyll@marten blog]$ git clone --branch v6.1.0 --single-branch https://github.com/cotes2020/jekyll-theme-
chirpy.git
Cloning into 'jekyll-theme-chirpy'...
remote: Enumerating objects: 9008, done.
remote: Counting objects: 100% (24/24), done.
remote: Compressing objects: 100% (24/24), done.
remote: Total 9008 (delta 0), reused 24 (delta 0), pack-reused 8984
Receiving objects: 100% (9008/9008), 3.93 MiB | 5.62 MiB/s, done.
Resolving deltas: 100% (4921/4921), done.
Note: switching to '0da2f80dd47fd30321d11a913b02eb83c7689523'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

[jekyll@marten blog]$ ls
jekyll-theme-chirpy

[jekyll@marten blog]$ cd jekyll-theme-chirpy

[jekyll@marten jekyll-theme-chirpy]$ ls
assets        _data      index.html                   _layouts      _plugins   rollup.config.js  tools
CHANGELOG.md  Gemfile    _javascript                  LICENSE       _posts     _sass
_config.yml   _includes  jekyll-theme-chirpy.gemspec  package.json  README.md  _tabs

[jekyll@marten jekyll-theme-chirpy]$ git status
Not currently on any branch.
nothing to commit, working tree clean

[jekyll@marten jekyll-theme-chirpy]$ git branch --list
* (no branch)

[jekyll@marten jekyll-theme-chirpy]$ git checkout -b mybranch
Switched to a new branch 'mybranch'

[jekyll@marten jekyll-theme-chirpy]$ git branch --list
* mybranch
  • Install ruby gems
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[jekyll@marten jekyll-theme-chirpy]$ export GEM_HOME="$HOME/gems"

[jekyll@marten jekyll-theme-chirpy]$ bundle
Fetching gem metadata from https://rubygems.org/..........
Resolving dependencies...
Using bundler 2.4.10
Using eventmachine 1.2.7
Fetching public_suffix 5.0.3
|
Installing jekyll-include-cache 0.2.1
Installing jekyll-archives 2.2.1
Using jekyll-theme-chirpy 6.1.0 from source at `.`
Bundle complete! 6 Gemfile dependencies, 45 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
  • Put export GEM_HOME="$HOME/gems" to your .basrc, .zshrc or similar file(s)

  • Try jekyll. IP address is optional (-H ...), if you want to check it from other computer on the network.

1
2
3
4
5
6
7
8
9
10
11
[jekyll@marten jekyll-theme-chirpy]$ jekyll serve -H 192.168.0.143
Configuration file: /home/jekyll/blog/jekyll-theme-chirpy/_config.yml
 Theme Config file: /home/jekyll/blog/jekyll-theme-chirpy/_config.yml
            Source: /home/jekyll/blog/jekyll-theme-chirpy
       Destination: /home/jekyll/blog/jekyll-theme-chirpy/_site
 Incremental build: disabled. Enable with --incremental
      Generating...
                    done in 0.899 seconds.
 Auto-regeneration: enabled for '/home/jekyll/blog/jekyll-theme-chirpy'
    Server address: http://192.168.0.143:4000/
  Server running... press ctrl-c to stop.
  • Install npm modules (needed to make ToC work)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[jekyll@marten jekyll-theme-chirpy]$ npm install
npm WARN deprecated [email protected]: Please use @jridgewell/sourcemap-codec instead

added 398 packages, and audited 399 packages in 20s

65 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
npm notice
npm notice New minor version of npm available! 9.5.1 -> 9.8.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v9.8.1
npm notice Run npm install -g [email protected] to update!
npm notice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[jekyll@marten jekyll-theme-chirpy]$  npm run build

> [email protected] prebuild
> npx rimraf assets/js/dist


> [email protected] build
> NODE_ENV=production npx rollup -c --bundleConfigAsCjs


_javascript/commons.js → assets/js/dist/commons.min.js...
created assets/js/dist/commons.min.js in 1.6s

_javascript/home.js → assets/js/dist/home.min.js...
created assets/js/dist/home.min.js in 885ms

_javascript/categories.js → assets/js/dist/categories.min.js...
created assets/js/dist/categories.min.js in 817ms

_javascript/page.js → assets/js/dist/page.min.js...
created assets/js/dist/page.min.js in 785ms

_javascript/post.js → assets/js/dist/post.min.js...
created assets/js/dist/post.min.js in 811ms

_javascript/misc.js → assets/js/dist/misc.min.js...
created assets/js/dist/misc.min.js in 777ms
  • Check it again
1
[jekyll@marten jekyll-theme-chirpy]$ jekyll serve

Initial modifications

Deployment (podman container)

This part is missing, but it’s quite obvious from other articles, such as file collection. Also article about cloudflare tunnels might be useful to read.

Deployment script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/dash
# Updates jekyll site in container

target="nginx:/config/www"
source="/home/pavel/dev-blog"

cd $source
echo "👷 Rebuilding static web content ..."
JEKYLL_ENV=production jekyll build || exit 1
echo "🔥 Deleting old content, don't panic! ..."
podman exec nginx sh -c "cd /config/www && rm -rf *"
echo "🚚 Copying new content ..."

podman cp ~/ip.php $target
podman cp _site/. $target

My custom changes

End of file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* append your custom style below */
@import url('https://fonts.bunny.net/css2?family=IBM+Plex+Mono&family=IBM+Plex+Sans&family=IBM+Plex+Serif&display=swap');

body {
	font-family: "IBM Plex Sans", sans-serif;
}
#search-results a,h5,h4,h3,h2,h1 {
	font-family: "IBM Plex Serif", serif;
}
pre, code, kbd, samp {
	font-family: "IBM Plex Mono", monospace;
}
/*
$main-content-max-width: 1440px !default;
*/
#main-wrapper > .container {
  //max-width: $main-content-max-width;
  max-width: 1440px;
  padding-left: 1.75rem !important;
  padding-right: 1.75rem !important;
}

After Fedora update (F38->F39, November 2023)

It helps to run some of these commands in ~/dev-blog directory

1
2
3
4
5
6
7
8
9
# Maybe not this one, it took quite a long time
gem update
# /home/pavel/.local/share/gem/ruby/gems/bundler-2.4.22/lib/bundler/runtime.rb:304:in `check_for_activated_spec!': You have already activated public_suffix 5.0.3, but your Gemfile requires public_suffix 5.0.1. Prepending `bundle exec`
bundle update
bundle install
bundle clean --force

# /home/pavel/.local/share/gem/ruby/gems/logger-1.6.0/lib/logger.rb:384:in `level': undefined method `[]' for nil:NilClass (NoMethodError)
gem cleanup logger

Upgrade to new Chirpy version

Added 2024-04-13

Backup everything

This is basically unintended introduction to Git 😀

It’s easier to solve conflict on Windows using Fork and P4Merge, tools which I’m familiar with.

Note that these git commands are a bit unique to my setup. These could be done in Fork GUI, but I tried to educate myself.

Clone repo to Windows machine:

1
2
3
4
5
6
7
8
9
10
PS C:\dev-www> git clone https://git.pavelp.cz/pavel.perina/blog.git
Cloning into 'blog'...
remote: Enumerating objects: 9924, done.
remote: Counting objects: 100% (9924/9924), done.
remote: Compressing objects: 100% (4681/4681), done.
remote: Total 9924 (delta 5634), reused 9159 (delta 5114), pack-reused 0
Receiving objects: 100% (9924/9924), 32.35 MiB | 5.78 MiB/s, done.
Resolving deltas: 100% (5634/5634), done.

PS C:\dev-www> cd .\blog\

Add remote repositiory and fetch changes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
PS C:\dev-www\blog> git remote add github https://github.com/cotes2020/jekyll-theme-chirpy.git

PS C:\dev-www\blog> git remote -v
github  https://github.com/cotes2020/jekyll-theme-chirpy.git (fetch)
github  https://github.com/cotes2020/jekyll-theme-chirpy.git (push)
origin  https://git.pavelp.cz/pavel.perina/blog.git (fetch)
origin  https://git.pavelp.cz/pavel.perina/blog.git (push)

PS C:\dev-www\blog> git fetch github --tags
remote: Enumerating objects: 1210, done.
remote: Counting objects: 100% (140/140), done.
remote: Compressing objects: 100% (30/30), done.
remote: Total 1210 (delta 112), reused 126 (delta 110), pack-reused 1070
Receiving objects: 100% (1210/1210), 585.55 KiB | 3.11 MiB/s, done.
Resolving deltas: 100% (543/543), completed with 61 local objects.
From https://github.com/cotes2020/jekyll-theme-chirpy
 * [new branch]      dependabot/npm_and_yarn/npm-e7ba079bfe -> github/dependabot/npm_and_yarn/npm-e7ba079bfe
 * [new branch]      feature/auto-release -> github/feature/auto-release
 * [new branch]      master               -> github/master
 * [new branch]      production           -> github/production
 * [new tag]         v6.5.5               -> v6.5.5
 * [new tag]         v6.2.0               -> v6.2.0
 * [new tag]         v6.2.1               -> v6.2.1
 * [new tag]         v6.2.2               -> v6.2.2
 * [new tag]         v6.2.3               -> v6.2.3
 * [new tag]         v6.3.0               -> v6.3.0
 * [new tag]         v6.3.1               -> v6.3.1
 * [new tag]         v6.4.0               -> v6.4.0
 * [new tag]         v6.4.1               -> v6.4.1
 * [new tag]         v6.4.2               -> v6.4.2
 * [new tag]         v6.5.0               -> v6.5.0
 * [new tag]         v6.5.1               -> v6.5.1
 * [new tag]         v6.5.2               -> v6.5.2
 * [new tag]         v6.5.3               -> v6.5.3
 * [new tag]         v6.5.4               -> v6.5.4

PS C:\dev-www\blog> git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

PS C:\dev-www\blog> git checkout pavel
Switched to a new branch 'pavel'
branch 'pavel' set up to track 'origin/pavel'.

Create temporary/testing branch jekyll-update and merge github/production into it (alternatively git merge v7.0.0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS C:\dev-www\blog> git checkout -b jekyll-update
Switched to a new branch 'jekyll-update'

PS C:\dev-www\blog> git merge github/production
Auto-merging .gitignore
CONFLICT (content): Merge conflict in .gitignore
Auto-merging _config.yml
CONFLICT (content): Merge conflict in _config.yml
Auto-merging _data/contact.yml
CONFLICT (content): Merge conflict in _data/contact.yml
CONFLICT (modify/delete): _posts/2019-08-08-text-and-typography.md deleted in HEAD and modified in github/production.  Version github/production of _posts/2019-08-08-text-and-typography.md left in tree.
CONFLICT (modify/delete): _posts/2019-08-08-write-a-new-post.md deleted in HEAD and modified in github/production.  Version github/production of _posts/2019-08-08-write-a-new-post.md left in tree.
CONFLICT (modify/delete): _posts/2019-08-09-getting-started.md deleted in HEAD and modified in github/production.  Version github/production of _posts/2019-08-09-getting-started.md left in tree.
Auto-merging assets/css/jekyll-theme-chirpy.scss
CONFLICT (content): Merge conflict in assets/css/jekyll-theme-chirpy.scss
Auto-merging package.json
CONFLICT (content): Merge conflict in package.json
Automatic merge failed; fix conflicts and then commit the result.

Resolve conflicts!

Commit and push (done by Fork git client)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ git commit --file="C:/dev-www/blog/.git/COMMITMESSAGE"

[jekyll-update b050d7e] Merge remote-tracking branch 'github/production' into jekyll-update

$ git push origin refs/heads/jekyll-update --set-upstream --verbose

Enumerating objects: 1217, done.
Delta compression using up to 24 threads
Total 999 (delta 614), reused 769 (delta 391), pack-reused 0
POST git-receive-pack (343345 bytes)
remote: 
remote: Create a new pull request for 'jekyll-update':        
remote:   https://git.pavelp.cz/pavel.perina/blog/compare/master...jekyll-update        
remote: 
remote: . Processing 1 references        
remote: Processed 1 references in total        
Pushing to https://git.pavelp.cz/pavel.perina/blog.git
To https://git.pavelp.cz/pavel.perina/blog.git
 * [new branch]      jekyll-update -> jekyll-update
branch 'jekyll-update' set up to track 'origin/jekyll-update'.
updating local tracking ref 'refs/remotes/origin/jekyll-update'

Switching to Linux at this point.

It’s very important to update assetss/lib submodule. And it’s very easy to overlook it.

1
2
3
4
5
6
7
8
9
10
11
[pavel@marten -=- /home/pavel/dev-blog]$ git fetch origin
Username for 'https://git.pavelp.cz': pavel.perina
Password for 'https://[email protected]':

[pavel@marten -=- /home/pavel/dev-blog]$ git checkout jekyll-update
M       assets/lib
branch 'jekyll-update' set up to track 'origin/jekyll-update'.
Switched to a new branch 'jekyll-update'

[pavel@marten -=- /home/pavel/dev-blog]$ git submodule update
Submodule path 'assets/lib': checked out '7bc0d86b6af83d7acfc63db50f29a5975cec2513'

Now proceed with steps as when upgrading Fedora to get new packages:

1
2
3
4
5
6
# Maybe not this one, it took quite a long time
gem update
bundle update
bundle install
bundle clean --force
gem clean

Test it

It’s might be crucial to visit Cloudflare dashboard, go to Caching -> Configuration, turn on Development Mode and Purge Cache. Otherwise some old CSS styles can be mixed with new ones and if all that can be seen is white page with broken style, layout, not working JavaScript and it’s hard to debug. Also Ctrl+F5 or Ctrl+Shift+ in browser helps. Even better is to test changes in a private window.

This update changed code formatting and made page more narrow, which I previously modified in jekyll-theme-chirpy.css

Merge to my branch and remove temporary branch (local and remote):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[pavel@marten -=- /home/pavel/dev-blog]$ git checkout pavel
Switched to branch 'pavel'
Your branch is up to date with 'origin/pavel'.

[pavel@marten -=- /home/pavel/dev-blog]$ git merge jekyll-update
Updating 0fd79f6..6934c43
Fast-forward
|
120 files changed, 2832 insertions(+), 2505 deletions(-)
|

[pavel@marten -=- /home/pavel/dev-blog/_posts]$ git branch --delete jekyll-update
Deleted branch jekyll-update (was 6934c43).

[pavel@marten -=- /home/pavel/dev-blog/_posts]$ git push origin --delete jekyll-update
remote: . Processing 1 references
remote: Processed 1 references in total
To https://git.pavelp.cz/pavel.perina/blog.git
 - [deleted]         jekyll-update

Commit, push …

Fixes after update from v6.2 to v6.5

After some experimenting, this solved narrow window (extended from 1250px to 1440px)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
---
---

@import 'main';

/* append your custom style below */
@import url('https://fonts.bunny.net/css2?family=IBM+Plex+Mono&family=IBM+Plex+Sans&family=IBM+Plex+Serif&display=swap');

body {
	font-family: "IBM Plex Sans", sans-serif;
}
#search-results a,h5,h4,h3,h2,h1 {
	font-family: "IBM Plex Serif", serif;
}
pre, code, kbd, samp {
	font-family: "IBM Plex Mono", monospace;
}

#main-wrapper > .container {
    max-width: 1440px;
    padding-left: 1.75rem !important;
    padding-right: 1.75rem !important;
}

Fixes after update from v6.5.5 to v7.0.0

Added 2024-05-12

After update to Jekyll Chirpy v7.0.0 images are not displayed, because of breaking change.in this commit

In nearly all files in _posts and _tabs/about.md replace img_path by media_subpath in yaml pagefront header.

Sadly this sets all pages as modified today.

Additionally to previous upgrare I run

1
2
npm update
npm install build

Search and generating table of content seems broken, unless browser runs in private tab, cloudflare cache is in development mode and pruned.

Fixes after update from v7.0.1 to 7.2.0

Added 2024-12-01

Here I updated Fedora from v39 to v41 too in the early November.

  • I deleted content of ~/gems/ directory due to some errors in packages or dependencies
  • This solution finally fixed lot of warnings regarding to module versions
    1
    2
    3
    4
    5
    
    for i in `gem list --no-versions`; do gem uninstall -aIx $i; done
    cd /home/pavel
    rm -rf .bundle
    cd dev-blog
    bundle
    
  • I fixed some typo in deployment script, no idea why/how it worked: JEKYLL_ENV=production jekyll build || exit was on a single line (maybe it’s not a typo, but it fixed some problem) No, single line is correct - with two lines it has no effect, Umami stats don’t work. Why building the site did not work is a question (2024-12-22 - fixed stats)
  • I needed to delete ~/dev-blog/_sass/dist directory
  • npm run build

Added 2024-04-14

Option 1 (detect when html file is returned instead of image)

First let wget to browse page recursively:

1
2
cd ~/tmp
wget --spider -o wget.log -e robots=off -r -p http://192.168.0.143:8080/

Note: adding -d parameter makes log more verbose, it contains HTTP headers including Referer:

It gave no error, but I know arduino_ds3231_zs042_big.webp is missing. Relevant piece of wget.log is:

1
2
3
4
--2024-04-14 09:21:23--  http://192.168.0.143:8080/assets/img/arduino_ds3231_zs042_big.webp
Reusing existing connection to 192.168.0.143:8080.
HTTP request sent, awaiting response... 200 OK
Length: 41806 (41K) [text/html]

and it points to the home page.

Second analyze the log file. ChatGPT4 prompt was like:

Ok, other way … let’s use output from wget.
there is the line

1
--2024-04-14 09:21:23--  http://192.168.0.143:8080/assets/img/ arduino_pro_mini_cp2102_rtc3231_big.webp

we can match something that looks like this, starts with “–” and contains “http://192.> 168.0.143:8080/assets/img/” because images are stored here. Two next lines can be skipped and next line contains either:
Length: 308596 (301K) [image/webp]
Length: 41806 (41K) [text/html]
I want to get content type that matches txt html and report link in the first line as error

And the response

To accomplish this task, you can write a Python script that processes the log file generated by wget. This script will read through the file line by line, identify the lines corresponding to image requests based on the URL pattern you mentioned, skip two lines, and then check the content type on the following line to see if it matches text/html when it should be an image.

Here’s a Python script that does this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def process_wget_log(filename):
    with open(filename, 'r') as file:
        lines = file.readlines()
    
    errors = []
    i = 0
    while i < len(lines):
        line = lines[i].strip()
        if line.startswith('--') and "http://192.168.0.143:8080/assets/img/" in line:
            # Extract URL
            url = line.split()[-1]
            if i + 3 < len(lines):
                content_type_line = lines[i + 3].strip()
                if '[text/html]' in content_type_line:
                    errors.append(url)
            i += 4  # Skip the next three lines and check the next record
        else:
            i += 1

    return errors

log_filename = 'wget.log'
errors = process_wget_log(log_filename)
if errors:
    print("Errors found in the following image URLs:")
    for error in errors:
        print(error)
else:
    print("No errors found.")

Surprisingly it works:

1
python3 ./wget-spider-dissector.py
1
2
3
4
5
6
7
8
9
10
11
Errors found in the following image URLs:
http://192.168.0.143:8080/assets/img/arduino_ds3231_zs042_big.webp
http://192.168.0.143:8080/assets/img/arduino_ds3231_zs042_big.webp
http://192.168.0.143:8080/assets/img/logs-aritmetic-progression-light.webp
http://192.168.0.143:8080/assets/img/logs-aritmetic-progression-light.webp
http://192.168.0.143:8080/assets/img/logs-geometric-progression-of-2-light.webp
http://192.168.0.143:8080/assets/img/logs-geometric-progression-of-2-light.webp
http://192.168.0.143:8080/assets/img/matrix-vector-multiply-figure-01.webp
http://192.168.0.143:8080/assets/img/matrix-vector-multiply-figure-01.webp
http://192.168.0.143:8080/assets/img/%7Burl%7D
http://192.168.0.143:8080/assets/img/%7Burl%7D

Option 2 (configure nginx to return 404 errors)

1
2
3
4
5
6
[pavel@marten -=- /home/pavel/tmp]$  podman exec -it nginx /bin/bash
root@20bbd34b2cd8:/# cd config/nginx/site-confs/
root@20bbd34b2cd8:/config/nginx/site-confs# vi default.conf
root@20bbd34b2cd8:/config/nginx/site-confs#
exit
[pavel@marten -=- /home/pavel/tmp]$ podman restart nginx

Here I replaced the following line so it does not read index.html by default:

1
2
  #try_files $uri $uri/ /index.html /index.php$is_args$args =404;
  try_files $uri $uri/ =404;

And tried it again. It found lot of files like:

1
http://192.168.0.143:8080/tags/python/%7Burl%7D

But these are all from the search results javascript template where <a href="{url}"> is just a placeholder.

Note:

This was found in nginx.conf.cloudflare, I wonder if it works:

1
2
3
4
5
6
+    # Added 2023-07-08 22:11
+    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+                    '$status $body_bytes_sent "$http_referer" '
+                    '"$http_user_agent" "$http_x_forwarded_for" "$http_cf_connecting_ip"';
+
+

References

These were found later, but as a bonus:

  • RichardN This blog discusses setting Jekyll hosted on GitHub Pages guide to setup jekyll and seems like a blog with similar content to mine.
  • Techno Tim’s guide to setup Jekyll
  • O so techie guide to setup Jekyll in dev-container
This post is licensed under CC BY 4.0 by the author.